Merge pull request #172 from hejops/channel
add support for channel URLs
This commit is contained in:
52
goutubedl.go
52
goutubedl.go
@ -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,14 +454,30 @@ 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
|
continue
|
||||||
}
|
}
|
||||||
|
filteredEntrise = append(filteredEntrise, ee)
|
||||||
|
}
|
||||||
|
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",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)()
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user