From cd7aaeebc2cbe9f2a955323c70a23c771221ef02 Mon Sep 17 00:00:00 2001 From: db123 Date: Thu, 25 Jun 2026 16:12:03 +0330 Subject: [PATCH] feat: capture yt-dlp stderr on download failures for debugging Add StderrLog field to DownloadJob and collect all stderr lines during download. Log the captured stderr output when a download fails to help diagnose exit code 2 errors (geo-restrictions, auth, etc.). --- internal/dl/progress.go | 13 ++++++++----- internal/dl/ytdlp.go | 2 +- internal/domain/types.go | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/internal/dl/progress.go b/internal/dl/progress.go index 71d5460..e37e454 100644 --- a/internal/dl/progress.go +++ b/internal/dl/progress.go @@ -25,8 +25,6 @@ func parseProgressLine(line string) *progressUpdate { if line == "" || !strings.HasPrefix(line, "[download]") { return nil } - // Try to extract the simple percentage from the default format: - // [download] 45.2% of 123.45MiB at 2.34MiB/s ETA 00:45 m := progressRE.FindStringSubmatch(line) if m == nil { return nil @@ -38,12 +36,10 @@ func parseProgressLine(line string) *progressUpdate { u := &progressUpdate{pct: pct} - // Extract speed and ETA from remaining parts rest := line if idx := strings.Index(rest, "%"); idx >= 0 { rest = rest[idx+1:] } - // Look for "at " if atIdx := strings.Index(rest, " at "); atIdx >= 0 { afterAt := rest[atIdx+4:] if spaceIdx := strings.Index(afterAt, " "); spaceIdx >= 0 { @@ -51,7 +47,6 @@ func parseProgressLine(line string) *progressUpdate { rest = afterAt[spaceIdx:] } } - // Look for "ETA " if etaIdx := strings.Index(rest, "ETA "); etaIdx >= 0 { etaStr := strings.TrimSpace(rest[etaIdx+4:]) u.eta = etaStr @@ -63,9 +58,16 @@ func parseProgressLine(line string) *progressUpdate { // readProgress reads yt-dlp stderr and sends progress updates to the channel. // The caller is responsible for closing the updates channel. func readProgress(stderr io.ReadCloser, updates chan<- *domain.DownloadJob, job *domain.DownloadJob) { + var stderrBuf strings.Builder scanner := bufio.NewScanner(stderr) for scanner.Scan() { line := scanner.Text() + + if stderrBuf.Len() < 4096 { + stderrBuf.WriteString(line) + stderrBuf.WriteByte('\n') + } + parsed := parseProgressLine(line) if parsed == nil { continue @@ -75,4 +77,5 @@ func readProgress(stderr io.ReadCloser, updates chan<- *domain.DownloadJob, job job.ETA = parsed.eta updates <- job } + job.StderrLog = stderrBuf.String() } diff --git a/internal/dl/ytdlp.go b/internal/dl/ytdlp.go index 3bbff6c..733d56b 100644 --- a/internal/dl/ytdlp.go +++ b/internal/dl/ytdlp.go @@ -315,7 +315,7 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c <-progressDone if err != nil { job.Status = domain.StatusFailed - slog.Error("download failed", "url", job.URL, "error", err) + slog.Error("download failed", "url", job.URL, "error", err, "stderr", job.StderrLog) } else { job.Status = domain.StatusCompleted } diff --git a/internal/domain/types.go b/internal/domain/types.go index e37365a..66efc84 100644 --- a/internal/domain/types.go +++ b/internal/domain/types.go @@ -128,6 +128,7 @@ type DownloadJob struct { FilePath string Title string QuotaApplied bool + StderrLog string CancelCh chan struct{} Cmd *exec.Cmd }