From a4ba8cea2758b2db9cedc51d3671ce8aa268359a Mon Sep 17 00:00:00 2001 From: marcobaobao Date: Thu, 6 Feb 2025 11:24:09 +0100 Subject: [PATCH] initial support for playlist modifiers supported modifiers are --playlist-start, --playlist-end, --playlist-reverse, --max-downloads --- frontend/src/assets/i18n/en_US.yaml | 2 +- server/common/types.go | 18 ++++++ server/internal/common_types.go | 29 +++------- server/internal/playlist.go | 28 ++++++---- server/internal/process.go | 7 ++- server/playlist/modifiers.go | 86 +++++++++++++++++++++++++++++ server/playlist/types.go | 10 ++++ 7 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 server/common/types.go create mode 100644 server/playlist/modifiers.go create mode 100644 server/playlist/types.go diff --git a/frontend/src/assets/i18n/en_US.yaml b/frontend/src/assets/i18n/en_US.yaml index 73c67c8..5c838fc 100644 --- a/frontend/src/assets/i18n/en_US.yaml +++ b/frontend/src/assets/i18n/en_US.yaml @@ -31,7 +31,7 @@ keys: splashText: No active downloads archiveTitle: Archive 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 servedFromReverseProxyCheckbox: Is behind a reverse proxy urlBase: URL base, for reverse proxy support (subdir), defaults to empty diff --git a/server/common/types.go b/server/common/types.go new file mode 100644 index 0000000..51825d1 --- /dev/null +++ b/server/common/types.go @@ -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"` +} diff --git a/server/internal/common_types.go b/server/internal/common_types.go index 17daacf..3a6af76 100644 --- a/server/internal/common_types.go +++ b/server/internal/common_types.go @@ -1,6 +1,8 @@ package internal -import "time" +import ( + "github.com/marcopiovanello/yt-dlp-web-ui/v3/server/common" +) // Used to unmarshall yt-dlp progress type ProgressTemplate struct { @@ -29,29 +31,14 @@ type DownloadProgress struct { 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 // as JSON-RPC result field type ProcessResponse struct { - Id string `json:"id"` - Progress DownloadProgress `json:"progress"` - Info DownloadInfo `json:"info"` - Output DownloadOutput `json:"output"` - Params []string `json:"params"` + Id string `json:"id"` + Progress DownloadProgress `json:"progress"` + Info common.DownloadInfo `json:"info"` + Output DownloadOutput `json:"output"` + Params []string `json:"params"` } // struct representing the current status of the memoryDB diff --git a/server/internal/playlist.go b/server/internal/playlist.go index f3674f5..6a00e15 100644 --- a/server/internal/playlist.go +++ b/server/internal/playlist.go @@ -9,20 +9,18 @@ import ( "strings" "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/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 { + params := append(req.Params, "--flat-playlist", "-J") + urlWithParams := append([]string{req.URL}, params...) + var ( downloader = config.Instance().DownloaderPath - cmd = exec.Command(downloader, req.URL, "--flat-playlist", "-J") + cmd = exec.Command(downloader, urlWithParams...) ) stdout, err := cmd.StdoutPipe() @@ -30,7 +28,7 @@ func PlaylistDetect(req DownloadRequest, mq *MessageQueue, db *MemoryDB) error { return err } - var m metadata + var m playlist.Metadata if err := cmd.Start(); err != nil { return err @@ -53,12 +51,20 @@ func PlaylistDetect(req DownloadRequest, mq *MessageQueue, db *MemoryDB) error { } 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 }) + 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))) + if err := playlist.ApplyModifiers(&entries, req.Params); err != nil { + return err + } + for i, meta := range entries { // detect playlist title from metadata since each playlist entry will be // treated as an individual download @@ -87,6 +93,8 @@ func PlaylistDetect(req DownloadRequest, mq *MessageQueue, db *MemoryDB) error { db.Set(proc) mq.Publish(proc) } + + return nil } proc := &Process{ diff --git a/server/internal/process.go b/server/internal/process.go index c28c7ab..1ba1f54 100644 --- a/server/internal/process.go +++ b/server/internal/process.go @@ -19,6 +19,7 @@ import ( "time" "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" ) @@ -50,7 +51,7 @@ type Process struct { Livestream bool AutoRemove bool Params []string - Info DownloadInfo + Info common.DownloadInfo Progress DownloadProgress Output DownloadOutput proc *os.Process @@ -302,7 +303,7 @@ func (p *Process) GetFileName(o *DownloadOutput) error { func (p *Process) SetPending() { // Since video's title isn't available yet, fill in with the URL. - p.Info = DownloadInfo{ + p.Info = common.DownloadInfo{ URL: p.Url, Title: p.Url, CreatedAt: time.Now(), @@ -334,7 +335,7 @@ func (p *Process) SetMetadata() error { return err } - info := DownloadInfo{ + info := common.DownloadInfo{ URL: p.Url, CreatedAt: time.Now(), } diff --git a/server/playlist/modifiers.go b/server/playlist/modifiers.go new file mode 100644 index 0000000..37adc1e --- /dev/null +++ b/server/playlist/modifiers.go @@ -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 } diff --git a/server/playlist/types.go b/server/playlist/types.go new file mode 100644 index 0000000..003171a --- /dev/null +++ b/server/playlist/types.go @@ -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"` +}