Merge pull request #178 from wader/download-error

Handle errors at download better
This commit is contained in:
Mattias Wadman
2024-03-13 21:01:52 +01:00
committed by GitHub
3 changed files with 77 additions and 25 deletions

View File

@ -7,6 +7,7 @@ import (
"io"
"log"
"os"
"os/exec"
"github.com/wader/goutubedl"
)
@ -24,7 +25,11 @@ func main() {
result, err := goutubedl.New(
context.Background(),
flag.Arg(0),
goutubedl.Options{Type: optType, DebugLog: log.Default()},
goutubedl.Options{
Type: optType,
DebugLog: log.Default(),
StderrFn: func(cmd *exec.Cmd) io.Writer { return os.Stderr },
},
)
if err != nil {
log.Fatal(err)

View File

@ -9,7 +9,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
@ -129,7 +128,7 @@ type Info struct {
WebpageURL string `json:"webpage_url"`
Description string `json:"description"`
Thumbnail string `json:"thumbnail"`
// not unmarshalled, populated from image thumbnail file
// don't unmarshal, populated from image thumbnail file
ThumbnailBytes []byte `json:"-"`
Thumbnails []Thumbnail `json:"thumbnails"`
@ -180,7 +179,7 @@ type Subtitle struct {
URL string `json:"url"`
Ext string `json:"ext"`
Language string `json:"-"`
// not unmarshalled, populated from subtitle file
// don't unmarshal, populated from subtitle file
Bytes []byte `json:"-"`
}
@ -346,15 +345,15 @@ func infoFromURL(
case TypeAny:
break
default:
return Info{}, nil, fmt.Errorf("Unhandle options type value: %d", options.Type)
return Info{}, nil, fmt.Errorf("unhandled options type value: %d", options.Type)
}
tempPath, _ := ioutil.TempDir("", "ydls")
tempPath, _ := os.MkdirTemp("", "ydls")
defer os.RemoveAll(tempPath)
stdoutBuf := &bytes.Buffer{}
stderrBuf := &bytes.Buffer{}
stderrWriter := ioutil.Discard
stderrWriter := io.Discard
if options.StderrFn != nil {
stderrWriter = options.StderrFn(cmd)
}
@ -428,7 +427,7 @@ func infoFromURL(
if options.DownloadThumbnail && info.Thumbnail != "" {
resp, respErr := get(info.Thumbnail)
if respErr == nil {
buf, _ := ioutil.ReadAll(resp.Body)
buf, _ := io.ReadAll(resp.Body)
resp.Body.Close()
info.ThumbnailBytes = buf
}
@ -445,7 +444,7 @@ func infoFromURL(
for i, subtitle := range subtitles {
resp, respErr := get(subtitle.URL)
if respErr == nil {
buf, _ := ioutil.ReadAll(resp.Body)
buf, _ := io.ReadAll(resp.Body)
resp.Body.Close()
subtitles[i].Bytes = buf
}
@ -464,21 +463,21 @@ func infoFromURL(
// - 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 filteredEntries []Info
for _, e := range info.Entries {
if e.Type == "playlist" {
for _, ee := range e.Entries {
if ee.ID == "" {
continue
}
filteredEntrise = append(filteredEntrise, ee)
filteredEntries = append(filteredEntries, ee)
}
continue
} else if e.ID != "" {
filteredEntrise = append(filteredEntrise, e)
filteredEntries = append(filteredEntries, e)
}
}
info.Entries = filteredEntrise
info.Entries = filteredEntries
}
return info, stdoutBuf.Bytes(), nil
@ -533,7 +532,7 @@ func (result Result) DownloadWithOptions(
}
}
tempPath, tempErr := ioutil.TempDir("", "ydls")
tempPath, tempErr := os.MkdirTemp("", "ydls")
if tempErr != nil {
return nil, tempErr
}
@ -541,7 +540,7 @@ func (result Result) DownloadWithOptions(
var jsonTempPath string
if !result.Options.noInfoDownload {
jsonTempPath = path.Join(tempPath, "info.json")
if err := ioutil.WriteFile(jsonTempPath, result.RawJSON, 0600); err != nil {
if err := os.WriteFile(jsonTempPath, result.RawJSON, 0600); err != nil {
os.RemoveAll(tempPath)
return nil, err
}
@ -624,15 +623,17 @@ func (result Result) DownloadWithOptions(
}
cmd.Dir = tempPath
var w io.WriteCloser
dr.reader, w = io.Pipe()
stderrWriter := ioutil.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 {
@ -642,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) {

View File

@ -380,7 +380,7 @@ func TestDownloadSections(t *testing.T) {
}
seconds := int(gotDuration)
if seconds != duration {
t.Fatalf("didnot get expected duration of %d, but got %d", duration, seconds)
t.Fatalf("did not get expected duration of %d, but got %d", duration, seconds)
}
dr.Close()
}
@ -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())
}
}