Merge pull request #172 from hejops/channel

add support for channel URLs
This commit is contained in:
Mattias Wadman
2024-02-24 16:32:20 +01:00
committed by GitHub
2 changed files with 84 additions and 13 deletions

View File

@ -205,12 +205,15 @@ const (
TypeSingle TypeSingle
// TypePlaylist playlist with multiple tracks, files etc // TypePlaylist playlist with multiple tracks, files etc
TypePlaylist TypePlaylist
// TypeChannel channel containing one or more playlists, which will be flattened
TypeChannel
) )
var TypeFromString = map[string]Type{ var TypeFromString = map[string]Type{
"any": TypeAny, "any": TypeAny,
"single": TypeSingle, "single": TypeSingle,
"playlist": TypePlaylist, "playlist": TypePlaylist,
"channel": TypeChannel,
} }
// Options for New() // Options for New()
@ -248,7 +251,12 @@ func Version(ctx context.Context) (string, error) {
// Downloads given URL using the given options and filter (usually a format id or quality designator). // Downloads given URL using the given options and filter (usually a format id or quality designator).
// If filter is empty, then youtube-dl will use its default format selector. // If filter is empty, then youtube-dl will use its default format selector.
func Download(ctx context.Context, rawURL string, options Options, filter string) (*DownloadResult, error) { func Download(
ctx context.Context,
rawURL string,
options Options,
filter string,
) (*DownloadResult, error) {
options.noInfoDownload = true options.noInfoDownload = true
d, err := New(ctx, rawURL, options) d, err := New(ctx, rawURL, options)
if err != nil { if err != nil {
@ -286,7 +294,11 @@ func New(ctx context.Context, rawURL string, options Options) (result Result, er
}, nil }, nil
} }
func infoFromURL(ctx context.Context, rawURL string, options Options) (info Info, rawJSON []byte, err error) { func infoFromURL(
ctx context.Context,
rawURL string,
options Options,
) (info Info, rawJSON []byte, err error) {
cmd := exec.CommandContext( cmd := exec.CommandContext(
ctx, ctx,
ProbePath(), ProbePath(),
@ -309,7 +321,7 @@ func infoFromURL(ctx context.Context, rawURL string, options Options) (info Info
cmd.Args = append(cmd.Args, "--downloader", options.Downloader) cmd.Args = append(cmd.Args, "--downloader", options.Downloader)
} }
switch options.Type { switch options.Type {
case TypePlaylist: case TypePlaylist, TypeChannel:
cmd.Args = append(cmd.Args, "--yes-playlist") cmd.Args = append(cmd.Args, "--yes-playlist")
if options.PlaylistStart > 0 { if options.PlaylistStart > 0 {
@ -442,13 +454,29 @@ func infoFromURL(ctx context.Context, rawURL string, options Options) (info Info
} }
// as we ignore errors for playlists some entries might show up as null // as we ignore errors for playlists some entries might show up as null
if options.Type == TypePlaylist { //
// note: instead of doing full recursion, we assume entries in
// playlists and channels are at most 2 levels deep, and we just
// collect entries from both levels.
//
// the following cases have not been tested:
//
// - entries that are more than 2 levels deep (will be missed)
// - the ability to restrict entries to a single level (we include both levels)
if options.Type == TypePlaylist || options.Type == TypeChannel {
var filteredEntrise []Info var filteredEntrise []Info
for _, e := range info.Entries { for _, e := range info.Entries {
if e.ID == "" { if e.Type == "playlist" {
for _, ee := range e.Entries {
if ee.ID == "" {
continue
}
filteredEntrise = append(filteredEntrise, ee)
}
continue continue
} else if e.ID != "" {
filteredEntrise = append(filteredEntrise, e)
} }
filteredEntrise = append(filteredEntrise, e)
} }
info.Entries = filteredEntrise info.Entries = filteredEntrise
} }
@ -488,12 +516,20 @@ type DownloadOptions struct {
PlaylistIndex int PlaylistIndex int
} }
func (result Result) DownloadWithOptions(ctx context.Context, options DownloadOptions) (*DownloadResult, error) { func (result Result) DownloadWithOptions(
ctx context.Context,
options DownloadOptions,
) (*DownloadResult, error) {
debugLog := result.Options.DebugLog debugLog := result.Options.DebugLog
if !result.Options.noInfoDownload { if !result.Options.noInfoDownload {
if (result.Info.Type == "playlist" || result.Info.Type == "multi_video") && options.PlaylistIndex == 0 { if (result.Info.Type == "playlist" ||
return nil, fmt.Errorf("can't download a playlist when the playlist index options is not set") result.Info.Type == "multi_video" ||
result.Info.Type == "channel") &&
options.PlaylistIndex == 0 {
return nil, fmt.Errorf(
"can't download a playlist when the playlist index options is not set",
)
} }
} }

View File

@ -21,9 +21,12 @@ import (
"github.com/wader/osleaktest" "github.com/wader/osleaktest"
) )
const testVideoRawURL = "https://www.youtube.com/watch?v=C0DPdy98e4c" const (
const playlistRawURL = "https://soundcloud.com/mattheis/sets/kindred-phenomena" testVideoRawURL = "https://www.youtube.com/watch?v=C0DPdy98e4c"
const subtitlesTestVideoRawURL = "https://www.youtube.com/watch?v=QRS8MkLhQmM" playlistRawURL = "https://soundcloud.com/mattheis/sets/kindred-phenomena"
channelRawURL = "https://www.youtube.com/channel/UCHDm-DKoMyJxKVgwGmuTaQA"
subtitlesTestVideoRawURL = "https://www.youtube.com/watch?v=QRS8MkLhQmM"
)
func leakChecks(t *testing.T) func() { func leakChecks(t *testing.T) func() {
leakFn := leaktest.Check(t) leakFn := leaktest.Check(t)
@ -135,7 +138,7 @@ func TestParseInfo(t *testing.T) {
expectedTitle string expectedTitle string
}{ }{
{"https://soundcloud.com/avalonemerson/avalon-emerson-live-at-printworks-london-march-2017", "Avalon Emerson Live at Printworks London 2017"}, {"https://soundcloud.com/avalonemerson/avalon-emerson-live-at-printworks-london-march-2017", "Avalon Emerson Live at Printworks London 2017"},
{"https://www.infoq.com/presentations/Simple-Made-Easy", "Simple Made Easy"}, {"https://www.infoq.com/presentations/Simple-Made-Easy", "Simple Made Easy - InfoQ"},
{"https://www.youtube.com/watch?v=uVYWQJ5BB_w", "A Radiolab Producer on the Making of a Podcast"}, {"https://www.youtube.com/watch?v=uVYWQJ5BB_w", "A Radiolab Producer on the Making of a Podcast"},
} { } {
t.Run(c.url, func(t *testing.T) { t.Run(c.url, func(t *testing.T) {
@ -217,6 +220,38 @@ func TestPlaylist(t *testing.T) {
} }
} }
func TestChannel(t *testing.T) {
defer leakChecks(t)()
ydlResult, ydlResultErr := goutubedl.New(
context.Background(),
channelRawURL,
goutubedl.Options{
Type: goutubedl.TypeChannel,
DownloadThumbnail: false,
},
)
if ydlResultErr != nil {
t.Errorf("failed to download: %s", ydlResultErr)
}
expectedTitle := "Simon Yapp"
if ydlResult.Info.Title != expectedTitle {
t.Errorf("expected title %q got %q", expectedTitle, ydlResult.Info.Title)
}
expectedEntries := 5
if len(ydlResult.Info.Entries) != expectedEntries {
t.Errorf("expected %d entries got %d", expectedEntries, len(ydlResult.Info.Entries))
}
expectedTitleOne := "#RNLI Shoreham #LifeBoat demo of launch."
if ydlResult.Info.Entries[0].Title != expectedTitleOne {
t.Errorf("expected title %q got %q", expectedTitleOne, ydlResult.Info.Entries[0].Title)
}
}
func TestUnsupportedURL(t *testing.T) { func TestUnsupportedURL(t *testing.T) {
defer leaktest.Check(t)() defer leaktest.Check(t)()