Merge pull request #178 from wader/download-error
Handle errors at download better
This commit is contained in:
@ -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)
|
||||
|
67
goutubedl.go
67
goutubedl.go
@ -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) {
|
||||
|
@ -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())
|
||||
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user