Compare commits
5 Commits
v3.2.5
...
feat-suppo
| Author | SHA1 | Date | |
|---|---|---|---|
| a4ba8cea27 | |||
|
|
1c62084c7b | ||
| 3c21253562 | |||
| b243c1c958 | |||
|
|
7be5bc7b1f |
@@ -25,6 +25,11 @@ docker pull ghcr.io/marcopiovanello/yt-dlp-web-ui:latest
|
|||||||
|
|
||||||
*Keeps the project alive!* 😃
|
*Keeps the project alive!* 😃
|
||||||
|
|
||||||
|
## Community stuff
|
||||||
|
Feel free to join :)
|
||||||
|
|
||||||
|
[](https://discord.gg/3Sj9ZZHv)
|
||||||
|
|
||||||
## Some screeshots
|
## Some screeshots
|
||||||

|

|
||||||

|

|
||||||
|
|||||||
@@ -69,3 +69,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -71,3 +71,12 @@ keys:
|
|||||||
noFilesFound: 'Keine Dateien gefunden'
|
noFilesFound: 'Keine Dateien gefunden'
|
||||||
tableView: 'Tabellenansicht'
|
tableView: 'Tabellenansicht'
|
||||||
deleteSelected: 'Ausgewählte löschen'
|
deleteSelected: 'Ausgewählte löschen'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -27,11 +27,11 @@ keys:
|
|||||||
customPath: Custom path
|
customPath: Custom path
|
||||||
customArgs: Enable custom yt-dlp args (great power = great responsibilities)
|
customArgs: Enable custom yt-dlp args (great power = great responsibilities)
|
||||||
customArgsInput: Custom yt-dlp arguments
|
customArgsInput: Custom yt-dlp arguments
|
||||||
rpcConnErr: Error while conencting to RPC server
|
rpcConnErr: Error while connecting to RPC server
|
||||||
splashText: No active downloads
|
splashText: No active downloads
|
||||||
archiveTitle: Archive
|
archiveTitle: Archive
|
||||||
clipboardAction: Copied URL to clipboard
|
clipboardAction: Copied URL to clipboard
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may close this window)
|
playlistCheckbox: Download playlist
|
||||||
restartAppMessage: Needs a page reload to take effect
|
restartAppMessage: Needs a page reload to take effect
|
||||||
servedFromReverseProxyCheckbox: Is behind a reverse proxy
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy
|
||||||
urlBase: URL base, for reverse proxy support (subdir), defaults to empty
|
urlBase: URL base, for reverse proxy support (subdir), defaults to empty
|
||||||
|
|||||||
@@ -69,3 +69,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -73,3 +73,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -71,3 +71,12 @@ keys:
|
|||||||
noFilesFound: 'Nem található fájlok'
|
noFilesFound: 'Nem található fájlok'
|
||||||
tableView: 'Táblázatos Nézet'
|
tableView: 'Táblázatos Nézet'
|
||||||
deleteSelected: 'Kiválasztottak törlése'
|
deleteSelected: 'Kiválasztottak törlése'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
keys:
|
keys:
|
||||||
urlInput: URL Video (uno per linea)
|
urlInput: URL Video (uno per linea)
|
||||||
statusTitle: Stato
|
statusTitle: Stato
|
||||||
startButton: Inizia
|
|
||||||
statusReady: Pronto
|
statusReady: Pronto
|
||||||
|
selectFormatButton: Seziona formato
|
||||||
|
startButton: Inizia
|
||||||
abortAllButton: Termina tutto
|
abortAllButton: Termina tutto
|
||||||
updateBinButton: Aggiorna yt-dlp
|
updateBinButton: Aggiorna yt-dlp
|
||||||
darkThemeButton: Tema scuro
|
darkThemeButton: Tema scuro
|
||||||
@@ -22,51 +23,60 @@ keys:
|
|||||||
pathOverrideOption: Abilita sovrascrittura percorso di output
|
pathOverrideOption: Abilita sovrascrittura percorso di output
|
||||||
filenameOverrideOption: Abilita sovrascrittura del nome del file di output
|
filenameOverrideOption: Abilita sovrascrittura del nome del file di output
|
||||||
autoFileExtensionOption: Aggiungi estensione automaticamente
|
autoFileExtensionOption: Aggiungi estensione automaticamente
|
||||||
customFilename: Custom filename (leave blank to use default)
|
customFilename: Nome file personalizzato (lascia vuoto per utilizzare quello predefinito)
|
||||||
customPath: Custom path
|
customPath: Percorso personalizzato
|
||||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
customArgs: Abilita argomenti yt-dlp personalizzati (grande potere = grandi responsabilità)
|
||||||
customArgsInput: Custom yt-dlp arguments
|
customArgsInput: Argomenti yt-dlp personalizzati
|
||||||
rpcConnErr: Error nella connessione al server RPC
|
rpcConnErr: Errore nella connessione al server RPC
|
||||||
splashText: Nessun download attivo
|
splashText: Nessun download attivo
|
||||||
archiveTitle: Archivio
|
archiveTitle: Archivio
|
||||||
clipboardAction: URL copiato negli appunti
|
clipboardAction: URL copiato negli appunti
|
||||||
playlistCheckbox: Download playlist (richiederà tempo, puoi chiudere la finestra dopo l'inoltro)
|
playlistCheckbox: Download playlist (richiederà tempo, puoi chiudere la finestra dopo l'inoltro)
|
||||||
restartAppMessage: La finestra deve essere ricaricata perché abbia effetto
|
restartAppMessage: La finestra deve essere ricaricata affinché abbia effetto
|
||||||
servedFromReverseProxyCheckbox: Is behind a reverse proxy
|
servedFromReverseProxyCheckbox: È dietro un reverse proxy
|
||||||
|
urlBase: base URL, per supporto a reverse proxy (subdir), default vuoto
|
||||||
newDownloadButton: Nuovo download
|
newDownloadButton: Nuovo download
|
||||||
homeButtonLabel: Home
|
homeButtonLabel: Home
|
||||||
archiveButtonLabel: Archive
|
archiveButtonLabel: Archivio
|
||||||
settingsButtonLabel: Settings
|
settingsButtonLabel: Impostazioni
|
||||||
rpcAuthenticationLabel: RPC authentication
|
rpcAuthenticationLabel: Autenticazione RPC
|
||||||
themeTogglerLabel: Theme toggler
|
themeTogglerLabel: Selettore Tema
|
||||||
loadingLabel: Loading...
|
loadingLabel: Caricamento...
|
||||||
appTitle: Titolo applicazione
|
appTitle: Titolo applicazione
|
||||||
savedTemplates: Template salvati
|
savedTemplates: Modelli salvati
|
||||||
templatesEditor: Editor template
|
templatesEditor: Editor modelli
|
||||||
templatesEditorNameLabel: Nome template
|
templatesEditorNameLabel: Nome modello
|
||||||
templatesEditorContentLabel: Contentunto template
|
templatesEditorContentLabel: Contenuto del modello
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
bulkDownload: 'Download files in a zip archive'
|
bulkDownload: 'Scaricare i file in un archivio zip'
|
||||||
templatesReloadInfo: To register a new template it might need a page reload.
|
|
||||||
livestreamURLInput: Livestream URL
|
|
||||||
livestreamStatusWaiting: Waiting/Wait start
|
|
||||||
livestreamStatusDownloading: Downloading
|
|
||||||
livestreamStatusCompleted: Completed
|
|
||||||
livestreamStatusErrored: Errored
|
|
||||||
livestreamStatusUnknown: Unknown
|
|
||||||
livestreamNoMonitoring: No livestreams monitored
|
|
||||||
livestreamDownloadInfo: |
|
|
||||||
This will monitor yet to start livestream. Each process will be executed with --wait-for-video 10.
|
|
||||||
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
|
|
||||||
Once started the livestream will be migrated to the downloads page.
|
|
||||||
livestreamExperimentalWarning: This feature is still experimental. Something might break!
|
|
||||||
accentSelect: 'Accent'
|
|
||||||
urlBase: base URL, per supporto a reverse proxy (subdir), default vuoto
|
|
||||||
rpcPollingTimeTitle: Intervallo di polling RPC
|
rpcPollingTimeTitle: Intervallo di polling RPC
|
||||||
rpcPollingTimeDescription: Un intervallo più corto implica un maggior utilizzo di CPU (lato client e server)
|
rpcPollingTimeDescription: Un intervallo più corto implica un maggior utilizzo di CPU (lato client e server)
|
||||||
generalDownloadSettings: 'General Download Settings'
|
templatesReloadInfo: Per registrare un nuovo modello potrebbe essere necessario ricaricare la pagina.
|
||||||
deleteCookies: Delete Cookies
|
livestreamURLInput: Livestream URL
|
||||||
noFilesFound: 'No Files Found'
|
livestreamStatusWaiting: Attesa inizio
|
||||||
tableView: 'Table View'
|
livestreamStatusDownloading: Downloading
|
||||||
deleteSelected: 'Delete selected'
|
livestreamStatusCompleted: Completato
|
||||||
|
livestreamStatusErrored: Errore
|
||||||
|
livestreamStatusUnknown: Sconosciuto
|
||||||
|
livestreamNoMonitoring: Nessun livestream monitorato
|
||||||
|
livestreamDownloadInfo: |
|
||||||
|
Questo monitorerà il livestream ancora da avviare. Ogni processo verrà eseguito con --wait-for-video 10.
|
||||||
|
Se viene fornito un livestream già avviato, questo verrà comunque scaricato, ma il suo progresso non verrà monitorato.
|
||||||
|
Una volta avviato, il livestream verrà migrato nella pagina dei download.
|
||||||
|
livestreamExperimentalWarning: Questa funzione è ancora sperimentale. Qualcosa potrebbe rompersi!
|
||||||
|
accentSelect: 'Accent'
|
||||||
|
generalDownloadSettings: 'Impostazioni generali di download'
|
||||||
|
deleteCookies: Elimina Cookies
|
||||||
|
noFilesFound: 'Nessun file trovato'
|
||||||
|
tableView: 'Vista Tabella'
|
||||||
|
deleteSelected: 'Elimina selezionati'
|
||||||
|
subscriptionsButtonLabel: 'Abbonamenti'
|
||||||
|
subscriptionsEmptyLabel: 'Nessuna iscrizione'
|
||||||
|
subscriptionsURLInput: 'URL Canale'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Iscrive a un canale definito. Verrà scaricato solo l'ultimo video.
|
||||||
|
Il lavoro di monitoraggio sarà programmato/attivato da un'espressione cron definita (se lasciata vuota, l'impostazione predefinita è ogni 5 minuti).
|
||||||
|
cronExpressionLabel: 'Espressione Cron'
|
||||||
|
editButtonLabel: 'Modifica'
|
||||||
|
newSubscriptionButton: Nuova iscrizione
|
||||||
@@ -70,3 +70,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -69,3 +69,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -69,3 +69,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -71,3 +71,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -69,3 +69,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -71,3 +71,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -69,3 +69,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -71,3 +71,12 @@ keys:
|
|||||||
noFilesFound: 'No Files Found'
|
noFilesFound: 'No Files Found'
|
||||||
tableView: 'Table View'
|
tableView: 'Table View'
|
||||||
deleteSelected: 'Delete selected'
|
deleteSelected: 'Delete selected'
|
||||||
|
subscriptionsButtonLabel: 'Subscriptions'
|
||||||
|
subscriptionsEmptyLabel: 'No subscriptions'
|
||||||
|
subscriptionsURLInput: 'Channel URL'
|
||||||
|
subscriptionsInfo: |
|
||||||
|
Subscribes to a defined channel. Only the last video will be downloaded.
|
||||||
|
The monitor job will be scheduled/triggered by a defined cron expression (defaults to every 5 minutes if left blank).
|
||||||
|
cronExpressionLabel: 'Cron expression'
|
||||||
|
editButtonLabel: 'Edit'
|
||||||
|
newSubscriptionButton: New subscription
|
||||||
@@ -110,7 +110,7 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
|
|||||||
if (pickedAudioFormat !== '') codes.push(pickedAudioFormat)
|
if (pickedAudioFormat !== '') codes.push(pickedAudioFormat)
|
||||||
if (pickedBestFormat !== '') codes.push(pickedBestFormat)
|
if (pickedBestFormat !== '') codes.push(pickedBestFormat)
|
||||||
|
|
||||||
const downloadTemplate = `${customArgsState} ${cookies}`
|
const downloadTemplate = `${customArgs} ${cookies}`
|
||||||
.replace(/ +/g, ' ')
|
.replace(/ +/g, ' ')
|
||||||
.trim()
|
.trim()
|
||||||
|
|
||||||
|
|||||||
18
server/common/types.go
Normal file
18
server/common/types.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Used to deser the yt-dlp -J output
|
||||||
|
type DownloadInfo struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Resolution string `json:"resolution"`
|
||||||
|
Size int32 `json:"filesize_approx"`
|
||||||
|
VCodec string `json:"vcodec"`
|
||||||
|
ACodec string `json:"acodec"`
|
||||||
|
Extension string `json:"ext"`
|
||||||
|
OriginalURL string `json:"original_url"`
|
||||||
|
FileName string `json:"filename"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/common"
|
||||||
|
)
|
||||||
|
|
||||||
// Used to unmarshall yt-dlp progress
|
// Used to unmarshall yt-dlp progress
|
||||||
type ProgressTemplate struct {
|
type ProgressTemplate struct {
|
||||||
@@ -29,27 +31,12 @@ type DownloadProgress struct {
|
|||||||
ETA float64 `json:"eta"`
|
ETA float64 `json:"eta"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to deser the yt-dlp -J output
|
|
||||||
type DownloadInfo struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
Resolution string `json:"resolution"`
|
|
||||||
Size int32 `json:"filesize_approx"`
|
|
||||||
VCodec string `json:"vcodec"`
|
|
||||||
ACodec string `json:"acodec"`
|
|
||||||
Extension string `json:"ext"`
|
|
||||||
OriginalURL string `json:"original_url"`
|
|
||||||
FileName string `json:"filename"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// struct representing the response sent to the client
|
// struct representing the response sent to the client
|
||||||
// as JSON-RPC result field
|
// as JSON-RPC result field
|
||||||
type ProcessResponse struct {
|
type ProcessResponse struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Progress DownloadProgress `json:"progress"`
|
Progress DownloadProgress `json:"progress"`
|
||||||
Info DownloadInfo `json:"info"`
|
Info common.DownloadInfo `json:"info"`
|
||||||
Output DownloadOutput `json:"output"`
|
Output DownloadOutput `json:"output"`
|
||||||
Params []string `json:"params"`
|
Params []string `json:"params"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,20 +9,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/common"
|
||||||
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/config"
|
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/config"
|
||||||
|
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/playlist"
|
||||||
)
|
)
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
Entries []DownloadInfo `json:"entries"`
|
|
||||||
Count int `json:"playlist_count"`
|
|
||||||
PlaylistTitle string `json:"title"`
|
|
||||||
Type string `json:"_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func PlaylistDetect(req DownloadRequest, mq *MessageQueue, db *MemoryDB) error {
|
func PlaylistDetect(req DownloadRequest, mq *MessageQueue, db *MemoryDB) error {
|
||||||
|
params := append(req.Params, "--flat-playlist", "-J")
|
||||||
|
urlWithParams := append([]string{req.URL}, params...)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
downloader = config.Instance().DownloaderPath
|
downloader = config.Instance().DownloaderPath
|
||||||
cmd = exec.Command(downloader, req.URL, "--flat-playlist", "-J")
|
cmd = exec.Command(downloader, urlWithParams...)
|
||||||
)
|
)
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
@@ -30,7 +28,7 @@ func PlaylistDetect(req DownloadRequest, mq *MessageQueue, db *MemoryDB) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var m metadata
|
var m playlist.Metadata
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -53,12 +51,20 @@ func PlaylistDetect(req DownloadRequest, mq *MessageQueue, db *MemoryDB) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if m.Type == "playlist" {
|
if m.Type == "playlist" {
|
||||||
entries := slices.CompactFunc(slices.Compact(m.Entries), func(a DownloadInfo, b DownloadInfo) bool {
|
entries := slices.CompactFunc(slices.Compact(m.Entries), func(a common.DownloadInfo, b common.DownloadInfo) bool {
|
||||||
return a.URL == b.URL
|
return a.URL == b.URL
|
||||||
})
|
})
|
||||||
|
|
||||||
|
entries = slices.DeleteFunc(entries, func(e common.DownloadInfo) bool {
|
||||||
|
return strings.Contains(e.URL, "list=")
|
||||||
|
})
|
||||||
|
|
||||||
slog.Info("playlist detected", slog.String("url", req.URL), slog.Int("count", len(entries)))
|
slog.Info("playlist detected", slog.String("url", req.URL), slog.Int("count", len(entries)))
|
||||||
|
|
||||||
|
if err := playlist.ApplyModifiers(&entries, req.Params); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for i, meta := range entries {
|
for i, meta := range entries {
|
||||||
// detect playlist title from metadata since each playlist entry will be
|
// detect playlist title from metadata since each playlist entry will be
|
||||||
// treated as an individual download
|
// treated as an individual download
|
||||||
@@ -87,6 +93,8 @@ func PlaylistDetect(req DownloadRequest, mq *MessageQueue, db *MemoryDB) error {
|
|||||||
db.Set(proc)
|
db.Set(proc)
|
||||||
mq.Publish(proc)
|
mq.Publish(proc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
proc := &Process{
|
proc := &Process{
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/archiver"
|
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/archiver"
|
||||||
|
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/common"
|
||||||
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/config"
|
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ type Process struct {
|
|||||||
Livestream bool
|
Livestream bool
|
||||||
AutoRemove bool
|
AutoRemove bool
|
||||||
Params []string
|
Params []string
|
||||||
Info DownloadInfo
|
Info common.DownloadInfo
|
||||||
Progress DownloadProgress
|
Progress DownloadProgress
|
||||||
Output DownloadOutput
|
Output DownloadOutput
|
||||||
proc *os.Process
|
proc *os.Process
|
||||||
@@ -302,7 +303,7 @@ func (p *Process) GetFileName(o *DownloadOutput) error {
|
|||||||
|
|
||||||
func (p *Process) SetPending() {
|
func (p *Process) SetPending() {
|
||||||
// Since video's title isn't available yet, fill in with the URL.
|
// Since video's title isn't available yet, fill in with the URL.
|
||||||
p.Info = DownloadInfo{
|
p.Info = common.DownloadInfo{
|
||||||
URL: p.Url,
|
URL: p.Url,
|
||||||
Title: p.Url,
|
Title: p.Url,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
@@ -334,7 +335,7 @@ func (p *Process) SetMetadata() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := DownloadInfo{
|
info := common.DownloadInfo{
|
||||||
URL: p.Url,
|
URL: p.Url,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|||||||
86
server/playlist/modifiers.go
Normal file
86
server/playlist/modifiers.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package playlist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/marcopiovanello/yt-dlp-web-ui/v3/server/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Applicable modifiers
|
||||||
|
|
||||||
|
full | short | description
|
||||||
|
---------------------------------------------------------------------------------
|
||||||
|
--playlist-start NUMBER | -I NUMBER: | discard first N entries
|
||||||
|
--playlist-end NUMBER | -I :NUMBER | discard last N entries
|
||||||
|
--playlist-reverse | -I ::-1 | self explanatory
|
||||||
|
--max-downloads NUMBER | | stops after N completed downloads
|
||||||
|
*/
|
||||||
|
|
||||||
|
func ApplyModifiers(entries *[]common.DownloadInfo, args []string) error {
|
||||||
|
for i, modifier := range args {
|
||||||
|
switch modifier {
|
||||||
|
case "--playlist-start":
|
||||||
|
return playlistStart(i, modifier, args, entries)
|
||||||
|
|
||||||
|
case "--playlist-end":
|
||||||
|
return playlistEnd(i, modifier, args, entries)
|
||||||
|
|
||||||
|
case "--max-downloads":
|
||||||
|
return maxDownloads(i, modifier, args, entries)
|
||||||
|
|
||||||
|
case "--playlist-reverse":
|
||||||
|
slices.Reverse(*entries)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func playlistStart(i int, modifier string, args []string, entries *[]common.DownloadInfo) error {
|
||||||
|
if !guard(i, len(modifier)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.Atoi(args[i+1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*entries = (*entries)[n:]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func playlistEnd(i int, modifier string, args []string, entries *[]common.DownloadInfo) error {
|
||||||
|
if !guard(i, len(modifier)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.Atoi(args[i+1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*entries = (*entries)[:n]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxDownloads(i int, modifier string, args []string, entries *[]common.DownloadInfo) error {
|
||||||
|
if !guard(i, len(modifier)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.Atoi(args[i+1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*entries = (*entries)[0:n]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func guard(i, len int) bool { return i+1 < len-1 }
|
||||||
10
server/playlist/types.go
Normal file
10
server/playlist/types.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package playlist
|
||||||
|
|
||||||
|
import "github.com/marcopiovanello/yt-dlp-web-ui/v3/server/common"
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Entries []common.DownloadInfo `json:"entries"`
|
||||||
|
Count int `json:"playlist_count"`
|
||||||
|
PlaylistTitle string `json:"title"`
|
||||||
|
Type string `json:"_type"`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user