From ad9066c4f426944cc0fa84a0c63819570d6f0459 Mon Sep 17 00:00:00 2001 From: Galiley Date: Tue, 15 Aug 2023 20:28:52 +0200 Subject: [PATCH 1/4] add options to handle downloading one entry from a playlist --- goutubedl.go | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/goutubedl.go b/goutubedl.go index 213b71f..7ee38f7 100644 --- a/goutubedl.go +++ b/goutubedl.go @@ -270,8 +270,8 @@ func infoFromURL(ctx context.Context, rawURL string, options Options) (info Info if options.Downloader != "" { cmd.Args = append(cmd.Args, "--downloader", options.Downloader) } - - if options.Type == TypePlaylist { + switch options.Type { + case TypePlaylist: cmd.Args = append(cmd.Args, "--yes-playlist") if options.PlaylistStart > 0 { @@ -284,7 +284,7 @@ func infoFromURL(ctx context.Context, rawURL string, options Options) (info Info "--playlist-end", strconv.Itoa(int(options.PlaylistEnd)), ) } - } else { + case TypeSingle: if options.DownloadSubtitles { cmd.Args = append(cmd.Args, "--all-subs", @@ -293,6 +293,10 @@ func infoFromURL(ctx context.Context, rawURL string, options Options) (info Info cmd.Args = append(cmd.Args, "--no-playlist", ) + case TypeAny: + break + default: + return Info{}, nil, fmt.Errorf("Unhandle options type value: %d", options.Type) } tempPath, _ := ioutil.TempDir("", "ydls") @@ -427,13 +431,29 @@ type DownloadResult struct { waitCh chan struct{} } -// Download format matched by filter (usually a format id or quality designator). -// If filter is empty, then youtube-dl will use its default format selector. +// Download is a shortcut of DownloadOptions where the options use the default value func (result Result) Download(ctx context.Context, filter string) (*DownloadResult, error) { + return result.DownloadWithOptions(ctx, DownloadOptions{ + Filter: filter, + PlaylistIndex: -1, + }) +} + +type DownloadOptions struct { + // Download format matched by filter (usually a format id or quality designator). + // If filter is empty, then youtube-dl will use its default format selector. + Filter string + // The index of the entry to download from the playlist that would be + // passed to youtube-dl wia --playlist-items. + // The index value starts at 1 + PlaylistIndex int +} + +func (result Result) DownloadWithOptions(ctx context.Context, options DownloadOptions) (*DownloadResult, error) { debugLog := result.Options.DebugLog - if result.Info.Type == "playlist" || result.Info.Type == "multi_video" { - return nil, fmt.Errorf("can't download a playlist") + if (result.Info.Type == "playlist" || result.Info.Type == "multi_video") && options.PlaylistIndex < 0 { + return nil, fmt.Errorf("can't download a playlist when the playlist index options is not set") } tempPath, tempErr := ioutil.TempDir("", "ydls") @@ -463,8 +483,12 @@ func (result Result) Download(ctx context.Context, filter string) (*DownloadResu ) // don't need to specify if direct as there is only one // also seems to be issues when using filter with generic extractor - if !result.Info.Direct && filter != "" { - cmd.Args = append(cmd.Args, "-f", filter) + if !result.Info.Direct && options.Filter != "" { + cmd.Args = append(cmd.Args, "-f", options.Filter) + } + + if options.PlaylistIndex >= 0 { + cmd.Args = append(cmd.Args, "--playlist-items", fmt.Sprint(options.PlaylistIndex)) } if result.Options.ProxyUrl != "" { From de2e0d1840c21fbaa955f1ac834b338bae53b978 Mon Sep 17 00:00:00 2001 From: Galiley Date: Wed, 16 Aug 2023 01:28:47 +0200 Subject: [PATCH 2/4] add tests --- goutubedl_test.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/goutubedl_test.go b/goutubedl_test.go index c4eb51b..27a74ca 100644 --- a/goutubedl_test.go +++ b/goutubedl_test.go @@ -301,3 +301,122 @@ func TestOptionDownloader(t *testing.T) { } dr.Close() } + +func TestInvalidOptionTypeField(t *testing.T) { + defer leakChecks(t)() + + _, err := goutubedl.New(context.Background(), playlistRawURL, goutubedl.Options{ + Type: 42, + }) + if err == nil { + t.Error("should have failed") + } +} + +func TestDownloadPlaylistEntry(t *testing.T) { + defer leakChecks(t)() + // Download file by specifying the playlist index + stderrBuf := &bytes.Buffer{} + r, err := goutubedl.New(context.Background(), playlistRawURL, goutubedl.Options{ + StderrFn: func(cmd *exec.Cmd) io.Writer { + return stderrBuf + }, + }) + if err != nil { + t.Fatal(err) + } + + expectedTitle := "Kindred Phenomena" + if r.Info.Title != expectedTitle { + t.Errorf("expected title %q got %q", expectedTitle, r.Info.Title) + } + + expectedEntries := 8 + if len(r.Info.Entries) != expectedEntries { + t.Errorf("expected %d entries got %d", expectedEntries, len(r.Info.Entries)) + } + + expectedTitleOne := "B1 Mattheis - Ben M" + playlistIndex := 2 + if r.Info.Entries[playlistIndex].Title != expectedTitleOne { + t.Errorf("expected title %q got %q", expectedTitleOne, r.Info.Entries[playlistIndex].Title) + } + + dr, err := r.DownloadWithOptions(context.Background(), goutubedl.DownloadOptions{ + PlaylistIndex: playlistIndex + 1, + Filter: r.Info.Entries[playlistIndex].Formats[0].FormatID, + }) + if err != nil { + t.Fatal(err) + } + playlistBuf := &bytes.Buffer{} + n, err := io.Copy(playlistBuf, dr) + if err != nil { + t.Fatal(err) + } + dr.Close() + + if n != int64(playlistBuf.Len()) { + t.Errorf("copy n not equal to download buffer: %d!=%d", n, playlistBuf.Len()) + } + + if n < 10000 { + t.Errorf("should have copied at least 10000 bytes: %d", n) + } + + if !strings.Contains(stderrBuf.String(), "Destination") { + t.Errorf("did not find expected log message on stderr: %q", stderrBuf.String()) + } + + // Download the same file but with the direct link + url := "https://soundcloud.com/mattheis/b1-mattheis-ben-m" + stderrBuf = &bytes.Buffer{} + r, err = goutubedl.New(context.Background(), url, goutubedl.Options{ + StderrFn: func(cmd *exec.Cmd) io.Writer { + return stderrBuf + }, + }) + if err != nil { + t.Fatal(err) + } + + if r.Info.Title != expectedTitleOne { + t.Errorf("expected title %q got %q", expectedTitleOne, r.Info.Title) + } + + expectedEntries = 0 + if len(r.Info.Entries) != expectedEntries { + t.Errorf("expected %d entries got %d", expectedEntries, len(r.Info.Entries)) + } + + dr, err = r.Download(context.Background(), r.Info.Formats[0].FormatID) + if err != nil { + t.Fatal(err) + } + directLinkBuf := &bytes.Buffer{} + n, err = io.Copy(directLinkBuf, dr) + if err != nil { + t.Fatal(err) + } + dr.Close() + + if n != int64(directLinkBuf.Len()) { + t.Errorf("copy n not equal to download buffer: %d!=%d", n, directLinkBuf.Len()) + } + + if n < 10000 { + t.Errorf("should have copied at least 10000 bytes: %d", n) + } + + if !strings.Contains(stderrBuf.String(), "Destination") { + t.Errorf("did not find expected log message on stderr: %q", stderrBuf.String()) + } + + if directLinkBuf.Len() != playlistBuf.Len() { + t.Errorf("not the same content size between the playlist index entry and the direct link entry: %d != %d", playlistBuf.Len(), directLinkBuf.Len()) + } + + if !bytes.Equal(directLinkBuf.Bytes(), playlistBuf.Bytes()) { + t.Error("not the same content between the playlist index entry and the direct link entry") + } +} From fcfd4acd74c983663c3bb15cc9be4fd1035ad9ec Mon Sep 17 00:00:00 2001 From: Galiley Date: Wed, 16 Aug 2023 11:58:20 +0200 Subject: [PATCH 3/4] edit default value --- goutubedl.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/goutubedl.go b/goutubedl.go index 7ee38f7..7475010 100644 --- a/goutubedl.go +++ b/goutubedl.go @@ -434,8 +434,7 @@ type DownloadResult struct { // Download is a shortcut of DownloadOptions where the options use the default value func (result Result) Download(ctx context.Context, filter string) (*DownloadResult, error) { return result.DownloadWithOptions(ctx, DownloadOptions{ - Filter: filter, - PlaylistIndex: -1, + Filter: filter, }) } @@ -444,15 +443,14 @@ type DownloadOptions struct { // If filter is empty, then youtube-dl will use its default format selector. Filter string // The index of the entry to download from the playlist that would be - // passed to youtube-dl wia --playlist-items. - // The index value starts at 1 + // passed to youtube-dl wia --playlist-items. The index value starts at 1 PlaylistIndex int } func (result Result) DownloadWithOptions(ctx context.Context, options DownloadOptions) (*DownloadResult, error) { debugLog := result.Options.DebugLog - if (result.Info.Type == "playlist" || result.Info.Type == "multi_video") && options.PlaylistIndex < 0 { + if (result.Info.Type == "playlist" || result.Info.Type == "multi_video") && options.PlaylistIndex == 0 { return nil, fmt.Errorf("can't download a playlist when the playlist index options is not set") } @@ -487,7 +485,7 @@ func (result Result) DownloadWithOptions(ctx context.Context, options DownloadOp cmd.Args = append(cmd.Args, "-f", options.Filter) } - if options.PlaylistIndex >= 0 { + if options.PlaylistIndex > 0 { cmd.Args = append(cmd.Args, "--playlist-items", fmt.Sprint(options.PlaylistIndex)) } From f97b09d082a8fb486aebe71968472b06be080b61 Mon Sep 17 00:00:00 2001 From: Galiley Date: Wed, 16 Aug 2023 17:19:03 +0200 Subject: [PATCH 4/4] fix typo --- goutubedl.go | 6 ++++-- goutubedl_test.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/goutubedl.go b/goutubedl.go index 7475010..6a14b65 100644 --- a/goutubedl.go +++ b/goutubedl.go @@ -431,7 +431,9 @@ type DownloadResult struct { waitCh chan struct{} } -// Download is a shortcut of DownloadOptions where the options use the default value +// Download format matched by filter (usually a format id or quality designator). +// If filter is empty, then youtube-dl will use its default format selector. +// It's a shortcut of DownloadWithOptions where the options use the default value func (result Result) Download(ctx context.Context, filter string) (*DownloadResult, error) { return result.DownloadWithOptions(ctx, DownloadOptions{ Filter: filter, @@ -443,7 +445,7 @@ type DownloadOptions struct { // If filter is empty, then youtube-dl will use its default format selector. Filter string // The index of the entry to download from the playlist that would be - // passed to youtube-dl wia --playlist-items. The index value starts at 1 + // passed to youtube-dl via --playlist-items. The index value starts at 1 PlaylistIndex int } diff --git a/goutubedl_test.go b/goutubedl_test.go index 27a74ca..54adab9 100644 --- a/goutubedl_test.go +++ b/goutubedl_test.go @@ -343,7 +343,7 @@ func TestDownloadPlaylistEntry(t *testing.T) { } dr, err := r.DownloadWithOptions(context.Background(), goutubedl.DownloadOptions{ - PlaylistIndex: playlistIndex + 1, + PlaylistIndex: int(r.Info.Entries[playlistIndex].PlaylistIndex), Filter: r.Info.Entries[playlistIndex].Formats[0].FormatID, }) if err != nil {