Merge pull request #178 from wader/download-error
Handle errors at download better
This commit is contained in:
@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
"github.com/wader/goutubedl"
|
"github.com/wader/goutubedl"
|
||||||
)
|
)
|
||||||
@ -24,7 +25,11 @@ func main() {
|
|||||||
result, err := goutubedl.New(
|
result, err := goutubedl.New(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
flag.Arg(0),
|
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 {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
67
goutubedl.go
67
goutubedl.go
@ -9,7 +9,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -129,7 +128,7 @@ type Info struct {
|
|||||||
WebpageURL string `json:"webpage_url"`
|
WebpageURL string `json:"webpage_url"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Thumbnail string `json:"thumbnail"`
|
Thumbnail string `json:"thumbnail"`
|
||||||
// not unmarshalled, populated from image thumbnail file
|
// don't unmarshal, populated from image thumbnail file
|
||||||
ThumbnailBytes []byte `json:"-"`
|
ThumbnailBytes []byte `json:"-"`
|
||||||
Thumbnails []Thumbnail `json:"thumbnails"`
|
Thumbnails []Thumbnail `json:"thumbnails"`
|
||||||
|
|
||||||
@ -180,7 +179,7 @@ type Subtitle struct {
|
|||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Ext string `json:"ext"`
|
Ext string `json:"ext"`
|
||||||
Language string `json:"-"`
|
Language string `json:"-"`
|
||||||
// not unmarshalled, populated from subtitle file
|
// don't unmarshal, populated from subtitle file
|
||||||
Bytes []byte `json:"-"`
|
Bytes []byte `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,15 +345,15 @@ func infoFromURL(
|
|||||||
case TypeAny:
|
case TypeAny:
|
||||||
break
|
break
|
||||||
default:
|
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)
|
defer os.RemoveAll(tempPath)
|
||||||
|
|
||||||
stdoutBuf := &bytes.Buffer{}
|
stdoutBuf := &bytes.Buffer{}
|
||||||
stderrBuf := &bytes.Buffer{}
|
stderrBuf := &bytes.Buffer{}
|
||||||
stderrWriter := ioutil.Discard
|
stderrWriter := io.Discard
|
||||||
if options.StderrFn != nil {
|
if options.StderrFn != nil {
|
||||||
stderrWriter = options.StderrFn(cmd)
|
stderrWriter = options.StderrFn(cmd)
|
||||||
}
|
}
|
||||||
@ -428,7 +427,7 @@ func infoFromURL(
|
|||||||
if options.DownloadThumbnail && info.Thumbnail != "" {
|
if options.DownloadThumbnail && info.Thumbnail != "" {
|
||||||
resp, respErr := get(info.Thumbnail)
|
resp, respErr := get(info.Thumbnail)
|
||||||
if respErr == nil {
|
if respErr == nil {
|
||||||
buf, _ := ioutil.ReadAll(resp.Body)
|
buf, _ := io.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
info.ThumbnailBytes = buf
|
info.ThumbnailBytes = buf
|
||||||
}
|
}
|
||||||
@ -445,7 +444,7 @@ func infoFromURL(
|
|||||||
for i, subtitle := range subtitles {
|
for i, subtitle := range subtitles {
|
||||||
resp, respErr := get(subtitle.URL)
|
resp, respErr := get(subtitle.URL)
|
||||||
if respErr == nil {
|
if respErr == nil {
|
||||||
buf, _ := ioutil.ReadAll(resp.Body)
|
buf, _ := io.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
subtitles[i].Bytes = buf
|
subtitles[i].Bytes = buf
|
||||||
}
|
}
|
||||||
@ -464,21 +463,21 @@ func infoFromURL(
|
|||||||
// - entries that are more than 2 levels deep (will be missed)
|
// - entries that are more than 2 levels deep (will be missed)
|
||||||
// - the ability to restrict entries to a single level (we include both levels)
|
// - the ability to restrict entries to a single level (we include both levels)
|
||||||
if options.Type == TypePlaylist || options.Type == TypeChannel {
|
if options.Type == TypePlaylist || options.Type == TypeChannel {
|
||||||
var filteredEntrise []Info
|
var filteredEntries []Info
|
||||||
for _, e := range info.Entries {
|
for _, e := range info.Entries {
|
||||||
if e.Type == "playlist" {
|
if e.Type == "playlist" {
|
||||||
for _, ee := range e.Entries {
|
for _, ee := range e.Entries {
|
||||||
if ee.ID == "" {
|
if ee.ID == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filteredEntrise = append(filteredEntrise, ee)
|
filteredEntries = append(filteredEntries, ee)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else if e.ID != "" {
|
} else if e.ID != "" {
|
||||||
filteredEntrise = append(filteredEntrise, e)
|
filteredEntries = append(filteredEntries, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info.Entries = filteredEntrise
|
info.Entries = filteredEntries
|
||||||
}
|
}
|
||||||
|
|
||||||
return info, stdoutBuf.Bytes(), nil
|
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 {
|
if tempErr != nil {
|
||||||
return nil, tempErr
|
return nil, tempErr
|
||||||
}
|
}
|
||||||
@ -541,7 +540,7 @@ func (result Result) DownloadWithOptions(
|
|||||||
var jsonTempPath string
|
var jsonTempPath string
|
||||||
if !result.Options.noInfoDownload {
|
if !result.Options.noInfoDownload {
|
||||||
jsonTempPath = path.Join(tempPath, "info.json")
|
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)
|
os.RemoveAll(tempPath)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -624,15 +623,17 @@ func (result Result) DownloadWithOptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Dir = tempPath
|
cmd.Dir = tempPath
|
||||||
var w io.WriteCloser
|
var stdoutW io.WriteCloser
|
||||||
dr.reader, w = io.Pipe()
|
var stderrW io.WriteCloser
|
||||||
|
var stderrR io.Reader
|
||||||
stderrWriter := ioutil.Discard
|
dr.reader, stdoutW = io.Pipe()
|
||||||
|
stderrR, stderrW = io.Pipe()
|
||||||
|
optStderrWriter := io.Discard
|
||||||
if result.Options.StderrFn != nil {
|
if result.Options.StderrFn != nil {
|
||||||
stderrWriter = result.Options.StderrFn(cmd)
|
optStderrWriter = result.Options.StderrFn(cmd)
|
||||||
}
|
}
|
||||||
cmd.Stdout = w
|
cmd.Stdout = stdoutW
|
||||||
cmd.Stderr = stderrWriter
|
cmd.Stderr = io.MultiWriter(optStderrWriter, stderrW)
|
||||||
|
|
||||||
debugLog.Print("cmd", " ", cmd.Args)
|
debugLog.Print("cmd", " ", cmd.Args)
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
@ -642,12 +643,32 @@ func (result Result) DownloadWithOptions(
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = cmd.Wait()
|
_ = cmd.Wait()
|
||||||
w.Close()
|
stdoutW.Close()
|
||||||
|
stderrW.Close()
|
||||||
os.RemoveAll(tempPath)
|
os.RemoveAll(tempPath)
|
||||||
close(dr.waitCh)
|
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) {
|
func (dr *DownloadResult) Read(p []byte) (n int, err error) {
|
||||||
|
@ -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")
|
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