diff --git a/cmd/goutubedl/main.go b/cmd/goutubedl/main.go index 3652c85..1ec1418 100644 --- a/cmd/goutubedl/main.go +++ b/cmd/goutubedl/main.go @@ -25,7 +25,11 @@ func main() { result, err := goutubedl.New( context.Background(), flag.Arg(0), - goutubedl.Options{Type: optType, DebugLog: log.Default(), StderrFn: func(cmd *exec.Cmd) io.Writer { return os.Stderr }}, + goutubedl.Options{ + Type: optType, + DebugLog: log.Default(), + StderrFn: func(cmd *exec.Cmd) io.Writer { return os.Stderr }, + }, ) if err != nil { log.Fatal(err) diff --git a/goutubedl.go b/goutubedl.go index 74765b6..2324365 100644 --- a/goutubedl.go +++ b/goutubedl.go @@ -623,15 +623,17 @@ func (result Result) DownloadWithOptions( } cmd.Dir = tempPath - var w io.WriteCloser - dr.reader, w = io.Pipe() - - stderrWriter := io.Discard + var stdoutW io.WriteCloser + var stderrW io.WriteCloser + var stderrR io.Reader + dr.reader, stdoutW = io.Pipe() + stderrR, stderrW = io.Pipe() + optStderrWriter := io.Discard if result.Options.StderrFn != nil { - stderrWriter = result.Options.StderrFn(cmd) + optStderrWriter = result.Options.StderrFn(cmd) } - cmd.Stdout = w - cmd.Stderr = stderrWriter + cmd.Stdout = stdoutW + cmd.Stderr = io.MultiWriter(optStderrWriter, stderrW) debugLog.Print("cmd", " ", cmd.Args) if err := cmd.Start(); err != nil { @@ -641,12 +643,32 @@ func (result Result) DownloadWithOptions( go func() { _ = cmd.Wait() - w.Close() + stdoutW.Close() + stderrW.Close() os.RemoveAll(tempPath) close(dr.waitCh) }() - return dr, nil + // blocks return until yt-dlp is downloading or has errored + ytErrCh := make(chan error) + go func() { + stderrLineScanner := bufio.NewScanner(stderrR) + for stderrLineScanner.Scan() { + const downloadPrefix = "[download]" + const errorPrefix = "ERROR: " + line := stderrLineScanner.Text() + if strings.HasPrefix(line, downloadPrefix) { + break + } else if strings.HasPrefix(line, errorPrefix) { + ytErrCh <- errors.New(line[len(errorPrefix):]) + return + } + } + ytErrCh <- nil + _, _ = io.Copy(io.Discard, stderrR) + }() + + return dr, <-ytErrCh } func (dr *DownloadResult) Read(p []byte) (n int, err error) { diff --git a/goutubedl_test.go b/goutubedl_test.go index a1d3f6d..80dd6dd 100644 --- a/goutubedl_test.go +++ b/goutubedl_test.go @@ -552,3 +552,29 @@ func TestDownloadPlaylistEntry(t *testing.T) { t.Error("not the same content between the playlist index entry and the direct link entry") } } + +func TestFormatDownloadError(t *testing.T) { + defer leaktest.Check(t)() + + ydl, ydlErr := goutubedl.New( + context.Background(), + "https://www.reddit.com/r/newsbabes/s/92rflI0EB0", + goutubedl.Options{}, + ) + + if ydlErr != nil { + // reddit seems to not like github action hosts + if strings.Contains(ydlErr.Error(), "HTTPError 403: Blocked") { + t.Skip() + } + t.Error(ydlErr) + } + + // no pre-muxed audio/video format available + _, ytDlErr := ydl.Download(context.Background(), "best") + expectedErr := "Requested format is not available" + if ydlErr != nil && !strings.Contains(ytDlErr.Error(), expectedErr) { + t.Errorf("expected error prefix %q got %q", expectedErr, ytDlErr.Error()) + + } +}