1 Commits

5 changed files with 42 additions and 103 deletions

View File

@ -1,18 +1,18 @@
# bump: golang /GOLANG_VERSION=([\d.]+)/ docker:golang|^1 # bump: golang /GOLANG_VERSION=([\d.]+)/ docker:golang|^1
# bump: golang link "Release notes" https://golang.org/doc/devel/release.html # bump: golang link "Release notes" https://golang.org/doc/devel/release.html
ARG GOLANG_VERSION=1.20.5 ARG GOLANG_VERSION=1.17.5
# bump: yt-dlp /YT_DLP=([\d.-]+)/ https://github.com/yt-dlp/yt-dlp.git|/^\d/|sort # bump: youtube-dl /YDL_VERSION=([\d.]+)/ https://github.com/ytdl-org/youtube-dl.git|/^\d/|sort
# bump: yt-dlp link "Release notes" https://github.com/yt-dlp/yt-dlp/releases/tag/$LATEST # bump: youtube-dl link "Release notes" https://github.com/ytdl-org/youtube-dl/releases/tag/$LATEST
ARG YT_DLP=2023.06.21 ARG YDL_VERSION=2021.12.17
FROM golang:$GOLANG_VERSION AS base FROM golang:$GOLANG_VERSION AS base
ARG YT_DLP ARG YDL_VERSION
RUN \ RUN \
apt-get update -q && \ apt-get update -q && \
apt-get install -y -q python-is-python3 && \ apt-get install -y -q python-is-python3 && \
curl -L https://github.com/yt-dlp/yt-dlp/releases/download/$YT_DLP/yt-dlp -o /usr/local/bin/yt-dlp && \ curl -L -o /usr/local/bin/youtube-dl https://yt-dl.org/downloads/$YDL_VERSION/youtube-dl && \
chmod a+x /usr/local/bin/yt-dlp chmod a+x /usr/local/bin/youtube-dl
FROM base AS dev FROM base AS dev

View File

@ -1,15 +1,12 @@
## goutubedl ## goutubedl
Go wrapper for [youtube-dl](https://github.com/ytdl-org/youtube-dl) and [yt-dlp](https://github.com/yt-dlp/yt-dlp), currently tested and Go wrapper for [youtube-dl](https://github.com/ytdl-org/youtube-dl). API documentation can be found at [godoc.org](https://pkg.go.dev/github.com/wader/goutubedl?tab=doc).
developed using yt-dlp.
API documentation can be found at [godoc.org](https://pkg.go.dev/github.com/wader/goutubedl?tab=doc).
See [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp) for how to See [youtube-dl documentation](https://github.com/ytdl-org/youtube-dl) for how to
install and what is recommended to install in addition to youtube-dl. install and what is recommended to install in addition to youtube-dl.
goutubedl default uses `PATH` to find youtube-dl but it can be configured with the `goutubedl.Path` goutubedl default uses `PATH` to find youtube-dl but it can be configured with the `goutubedl.Path`
variable. Default is currently `youtube-dl` for backwards compability. If your using yt-dlp you variable.
probably want to set it to `yt-dlp`.
Due to the nature and frequent updates of youtube-dl only the latest version Due to the nature and frequent updates of youtube-dl only the latest version
is tested. But it seems to work well with older versions also. is tested. But it seems to work well with older versions also.

View File

@ -15,23 +15,17 @@ var dumpFlag = flag.Bool("J", false, "Dump JSON")
var typeFlag = flag.String("t", "any", "Type") var typeFlag = flag.String("t", "any", "Type")
func main() { func main() {
goutubedl.Path = "yt-dlp"
log.SetFlags(0) log.SetFlags(0)
flag.Parse() flag.Parse()
optType := goutubedl.TypeFromString[*typeFlag] optType, _ := goutubedl.TypeFromString[*typeFlag]
result, err := goutubedl.New( result, err := goutubedl.New(context.Background(), flag.Arg(0), goutubedl.Options{Type: optType})
context.Background(),
flag.Arg(0),
goutubedl.Options{Type: optType, DebugLog: log.Default()},
)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if *dumpFlag { if *dumpFlag {
_ = json.NewEncoder(os.Stdout).Encode(result.Info) json.NewEncoder(os.Stdout).Encode(result.Info)
return return
} }
@ -50,8 +44,6 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
defer f.Close() defer f.Close()
if _, err := io.Copy(f, dr); err != nil { io.Copy(f, dr)
log.Fatal(err)
}
dr.Close() dr.Close()
} }

View File

@ -202,12 +202,10 @@ var TypeFromString = map[string]Type{
// Options for New() // Options for New()
type Options struct { type Options struct {
Type Type Type Type
PlaylistStart uint // --playlist-start PlaylistStart uint // --playlist-start
PlaylistEnd uint // --playlist-end PlaylistEnd uint // --playlist-end
Downloader string // --downloader
DownloadThumbnail bool DownloadThumbnail bool
DownloadSubtitles bool DownloadSubtitles bool
ProxyUrl string // --proxy URL http://host:port or socks5://host:port
DebugLog Printer DebugLog Printer
StderrFn func(cmd *exec.Cmd) io.Writer // if not nil, function to get Writer for stderr StderrFn func(cmd *exec.Cmd) io.Writer // if not nil, function to get Writer for stderr
HTTPClient *http.Client // Client for download thumbnail and subtitles (nil use http.DefaultClient) HTTPClient *http.Client // Client for download thumbnail and subtitles (nil use http.DefaultClient)
@ -260,15 +258,6 @@ func infoFromURL(ctx context.Context, rawURL string, options Options) (info Info
"--batch-file", "-", "--batch-file", "-",
"-J", "-J",
) )
if options.ProxyUrl != "" {
cmd.Args = append(cmd.Args, "--proxy", options.ProxyUrl)
}
if options.Downloader != "" {
cmd.Args = append(cmd.Args, "--downloader", options.Downloader)
}
if options.Type == TypePlaylist { if options.Type == TypePlaylist {
cmd.Args = append(cmd.Args, "--yes-playlist") cmd.Args = append(cmd.Args, "--yes-playlist")
@ -466,14 +455,6 @@ func (result Result) Download(ctx context.Context, filter string) (*DownloadResu
cmd.Args = append(cmd.Args, "-f", filter) cmd.Args = append(cmd.Args, "-f", filter)
} }
if result.Options.ProxyUrl != "" {
cmd.Args = append(cmd.Args, "--proxy", result.Options.ProxyUrl)
}
if result.Options.Downloader != "" {
cmd.Args = append(cmd.Args, "--downloader", result.Options.Downloader)
}
cmd.Dir = tempPath cmd.Dir = tempPath
var w io.WriteCloser var w io.WriteCloser
dr.reader, w = io.Pipe() dr.reader, w = io.Pipe()
@ -492,7 +473,7 @@ func (result Result) Download(ctx context.Context, filter string) (*DownloadResu
} }
go func() { go func() {
_ = cmd.Wait() cmd.Wait()
w.Close() w.Close()
os.RemoveAll(tempPath) os.RemoveAll(tempPath)
close(dr.waitCh) close(dr.waitCh)

View File

@ -1,4 +1,4 @@
package goutubedl_test package goutubedl
// TODO: currently the tests only run on linux as they use osleaktest which only // TODO: currently the tests only run on linux as they use osleaktest which only
// has linux support // has linux support
@ -14,15 +14,9 @@ import (
"testing" "testing"
"github.com/fortytw2/leaktest" "github.com/fortytw2/leaktest"
"github.com/wader/goutubedl"
"github.com/wader/osleaktest" "github.com/wader/osleaktest"
) )
func init() {
// we're using yt-dlp at the moment
goutubedl.Path = "yt-dlp"
}
const testVideoRawURL = "https://www.youtube.com/watch?v=C0DPdy98e4c" const testVideoRawURL = "https://www.youtube.com/watch?v=C0DPdy98e4c"
const playlistRawURL = "https://soundcloud.com/mattheis/sets/kindred-phenomena" const playlistRawURL = "https://soundcloud.com/mattheis/sets/kindred-phenomena"
const subtitlesTestVideoRawURL = "https://www.youtube.com/watch?v=QRS8MkLhQmM" const subtitlesTestVideoRawURL = "https://www.youtube.com/watch?v=QRS8MkLhQmM"
@ -39,10 +33,10 @@ func leakChecks(t *testing.T) func() {
func TestBinaryNotPath(t *testing.T) { func TestBinaryNotPath(t *testing.T) {
defer leakChecks(t)() defer leakChecks(t)()
defer func(orig string) { goutubedl.Path = orig }(goutubedl.Path) defer func(orig string) { Path = orig }(Path)
goutubedl.Path = "/non-existing" Path = "/non-existing"
_, versionErr := goutubedl.Version(context.Background()) _, versionErr := Version(context.Background())
if versionErr == nil || !strings.Contains(versionErr.Error(), "no such file or directory") { if versionErr == nil || !strings.Contains(versionErr.Error(), "no such file or directory") {
t.Fatalf("err should be nil 'no such file or directory': %v", versionErr) t.Fatalf("err should be nil 'no such file or directory': %v", versionErr)
} }
@ -52,7 +46,7 @@ func TestVersion(t *testing.T) {
defer leakChecks(t)() defer leakChecks(t)()
versionRe := regexp.MustCompile(`^\d{4}\.\d{2}.\d{2}.*$`) versionRe := regexp.MustCompile(`^\d{4}\.\d{2}.\d{2}.*$`)
version, versionErr := goutubedl.Version(context.Background()) version, versionErr := Version(context.Background())
if versionErr != nil { if versionErr != nil {
t.Fatalf("err: %s", versionErr) t.Fatalf("err: %s", versionErr)
@ -67,7 +61,7 @@ func TestDownload(t *testing.T) {
defer leakChecks(t)() defer leakChecks(t)()
stderrBuf := &bytes.Buffer{} stderrBuf := &bytes.Buffer{}
r, err := goutubedl.New(context.Background(), testVideoRawURL, goutubedl.Options{ r, err := New(context.Background(), testVideoRawURL, Options{
StderrFn: func(cmd *exec.Cmd) io.Writer { StderrFn: func(cmd *exec.Cmd) io.Writer {
return stderrBuf return stderrBuf
}, },
@ -90,8 +84,8 @@ func TestDownload(t *testing.T) {
t.Errorf("copy n not equal to download buffer: %d!=%d", n, downloadBuf.Len()) t.Errorf("copy n not equal to download buffer: %d!=%d", n, downloadBuf.Len())
} }
if n < 10000 { if n < 29000 {
t.Errorf("should have copied at least 10000 bytes: %d", n) t.Errorf("should have copied at least 29000 bytes: %d", n)
} }
if !strings.Contains(stderrBuf.String(), "Destination") { if !strings.Contains(stderrBuf.String(), "Destination") {
@ -112,7 +106,7 @@ func TestParseInfo(t *testing.T) {
defer leakChecks(t)() defer leakChecks(t)()
ctx, cancelFn := context.WithCancel(context.Background()) ctx, cancelFn := context.WithCancel(context.Background())
ydlResult, err := goutubedl.New(ctx, c.url, goutubedl.Options{ ydlResult, err := New(ctx, c.url, Options{
DownloadThumbnail: true, DownloadThumbnail: true,
}) })
if err != nil { if err != nil {
@ -162,8 +156,8 @@ func TestParseInfo(t *testing.T) {
func TestPlaylist(t *testing.T) { func TestPlaylist(t *testing.T) {
defer leakChecks(t)() defer leakChecks(t)()
ydlResult, ydlResultErr := goutubedl.New(context.Background(), playlistRawURL, goutubedl.Options{ ydlResult, ydlResultErr := New(context.Background(), playlistRawURL, Options{
Type: goutubedl.TypePlaylist, Type: TypePlaylist,
DownloadThumbnail: false, DownloadThumbnail: false,
}) })
@ -187,14 +181,14 @@ func TestPlaylist(t *testing.T) {
} }
} }
func TestUnsupportedURL(t *testing.T) { func TestTestUnsupportedURL(t *testing.T) {
defer leaktest.Check(t)() defer leaktest.Check(t)()
_, ydlResultErr := goutubedl.New(context.Background(), "https://www.google.com", goutubedl.Options{}) _, ydlResultErr := New(context.Background(), "https://www.google.com", Options{})
if ydlResultErr == nil { if ydlResultErr == nil {
t.Errorf("expected unsupported url") t.Errorf("expected unsupported url")
} }
expectedErrPrefix := "Unsupported URL:" expectedErrPrefix := "Unsupported URL: https://www.google.com"
if ydlResultErr != nil && !strings.HasPrefix(ydlResultErr.Error(), expectedErrPrefix) { if ydlResultErr != nil && !strings.HasPrefix(ydlResultErr.Error(), expectedErrPrefix) {
t.Errorf("expected error prefix %q got %q", expectedErrPrefix, ydlResultErr.Error()) t.Errorf("expected error prefix %q got %q", expectedErrPrefix, ydlResultErr.Error())
@ -205,8 +199,8 @@ func TestPlaylistWithPrivateVideo(t *testing.T) {
defer leaktest.Check(t)() defer leaktest.Check(t)()
playlistRawURL := "https://www.youtube.com/playlist?list=PLX0g748fkegS54oiDN4AXKl7BR7mLIydP" playlistRawURL := "https://www.youtube.com/playlist?list=PLX0g748fkegS54oiDN4AXKl7BR7mLIydP"
ydlResult, ydlResultErr := goutubedl.New(context.Background(), playlistRawURL, goutubedl.Options{ ydlResult, ydlResultErr := New(context.Background(), playlistRawURL, Options{
Type: goutubedl.TypePlaylist, Type: TypePlaylist,
DownloadThumbnail: false, DownloadThumbnail: false,
}) })
@ -224,10 +218,10 @@ func TestPlaylistWithPrivateVideo(t *testing.T) {
func TestSubtitles(t *testing.T) { func TestSubtitles(t *testing.T) {
defer leakChecks(t)() defer leakChecks(t)()
ydlResult, ydlResultErr := goutubedl.New( ydlResult, ydlResultErr := New(
context.Background(), context.Background(),
subtitlesTestVideoRawURL, subtitlesTestVideoRawURL,
goutubedl.Options{ Options{
DownloadSubtitles: true, DownloadSubtitles: true,
}) })
@ -256,11 +250,11 @@ func TestSubtitles(t *testing.T) {
func TestErrorNotAPlaylist(t *testing.T) { func TestErrorNotAPlaylist(t *testing.T) {
defer leakChecks(t)() defer leakChecks(t)()
_, ydlResultErr := goutubedl.New(context.Background(), testVideoRawURL, goutubedl.Options{ _, ydlResultErr := New(context.Background(), testVideoRawURL, Options{
Type: goutubedl.TypePlaylist, Type: TypePlaylist,
DownloadThumbnail: false, DownloadThumbnail: false,
}) })
if ydlResultErr != goutubedl.ErrNotAPlaylist { if ydlResultErr != ErrNotAPlaylist {
t.Errorf("expected is playlist error, got %s", ydlResultErr) t.Errorf("expected is playlist error, got %s", ydlResultErr)
} }
} }
@ -268,36 +262,11 @@ func TestErrorNotAPlaylist(t *testing.T) {
func TestErrorNotASingleEntry(t *testing.T) { func TestErrorNotASingleEntry(t *testing.T) {
defer leakChecks(t)() defer leakChecks(t)()
_, ydlResultErr := goutubedl.New(context.Background(), playlistRawURL, goutubedl.Options{ _, ydlResultErr := New(context.Background(), playlistRawURL, Options{
Type: goutubedl.TypeSingle, Type: TypeSingle,
DownloadThumbnail: false, DownloadThumbnail: false,
}) })
if ydlResultErr != goutubedl.ErrNotASingleEntry { if ydlResultErr != ErrNotASingleEntry {
t.Fatalf("expected is single entry error, got %s", ydlResultErr) t.Errorf("expected is single entry error, got %s", ydlResultErr)
} }
} }
func TestOptionDownloader(t *testing.T) {
defer leakChecks(t)()
ydlResult, ydlResultErr := goutubedl.New(
context.Background(),
testVideoRawURL,
goutubedl.Options{
Downloader: "ffmpeg",
})
if ydlResultErr != nil {
t.Fatalf("failed to download: %s", ydlResultErr)
}
dr, err := ydlResult.Download(context.Background(), ydlResult.Info.Formats[0].FormatID)
if err != nil {
t.Fatal(err)
}
downloadBuf := &bytes.Buffer{}
_, err = io.Copy(downloadBuf, dr)
if err != nil {
t.Fatal(err)
}
dr.Close()
}