From 58baa1b769f3a31d06ccc61114752866de13e435 Mon Sep 17 00:00:00 2001 From: db123 Date: Fri, 26 Jun 2026 00:41:04 +0330 Subject: [PATCH] fix: read yt-dlp progress from stdout instead of stderr; add explicit --proxy support --- internal/dl/progress.go | 15 ++++----------- internal/dl/ytdlp.go | 42 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/internal/dl/progress.go b/internal/dl/progress.go index b754d41..e725610 100644 --- a/internal/dl/progress.go +++ b/internal/dl/progress.go @@ -98,22 +98,16 @@ func parseProgressLine(line string) *progressUpdate { return u } -// readProgress reads yt-dlp stderr and sends progress updates to the channel. +// readProgress reads yt-dlp output 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) +func readProgress(reader io.ReadCloser, updates chan<- *domain.DownloadJob, job *domain.DownloadJob) { + scanner := bufio.NewScanner(reader) lineCount := 0 matchCount := 0 for scanner.Scan() { line := scanner.Text() lineCount++ - if stderrBuf.Len() < 4096 { - stderrBuf.WriteString(line) - stderrBuf.WriteByte('\n') - } - parsed := parseProgressLine(line) if parsed == nil { continue @@ -124,6 +118,5 @@ func readProgress(stderr io.ReadCloser, updates chan<- *domain.DownloadJob, job job.ETA = parsed.eta updates <- job } - job.StderrLog = stderrBuf.String() - slog.Info("readProgress finished", "url", job.URL, "lines", lineCount, "matches", matchCount, "stderr_len", len(job.StderrLog)) + slog.Info("readProgress finished", "url", job.URL, "lines", lineCount, "matches", matchCount) } diff --git a/internal/dl/ytdlp.go b/internal/dl/ytdlp.go index ade0764..99353f0 100644 --- a/internal/dl/ytdlp.go +++ b/internal/dl/ytdlp.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "log/slog" "os" "os/exec" @@ -21,6 +22,7 @@ import ( type Client struct { binaryPath string cookiesFile string + proxyURL string cache map[string]*cachedMediaInfo cacheMu sync.Mutex } @@ -60,9 +62,19 @@ func NewClient(cookiesFile string) (*Client, error) { return nil, fmt.Errorf("yt-dlp not found in PATH: %w", err) } slog.Info("yt-dlp found", "path", path) + + proxyURL := os.Getenv("HTTP_PROXY") + if proxyURL == "" { + proxyURL = os.Getenv("HTTPS_PROXY") + } + if proxyURL != "" { + slog.Info("using proxy for yt-dlp", "proxy", proxyURL) + } + return &Client{ binaryPath: path, cookiesFile: cookiesFile, + proxyURL: proxyURL, cache: make(map[string]*cachedMediaInfo), }, nil } @@ -118,6 +130,9 @@ func (c *Client) GetMediaInfo(url string) (*domain.MediaInfo, error) { if c.cookiesFile != "" { args = append(args, "--cookies", c.cookiesFile) } + if c.proxyURL != "" { + args = append(args, "--proxy", c.proxyURL) + } slog.Info("fetching media info", "url", url) out, err := c.run(args) @@ -184,6 +199,9 @@ func (c *Client) GetPlaylistInfo(url string) (*domain.MediaInfo, error) { if c.cookiesFile != "" { args = append(args, "--cookies", c.cookiesFile) } + if c.proxyURL != "" { + args = append(args, "--proxy", c.proxyURL) + } slog.Info("fetching playlist info", "url", url) out, err := c.run(args) @@ -297,6 +315,9 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c if c.cookiesFile != "" { args = append(args, "--cookies", c.cookiesFile) } + if c.proxyURL != "" { + args = append(args, "--proxy", c.proxyURL) + } args = append(args, job.URL) @@ -305,6 +326,13 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c cmd := exec.Command(c.binaryPath, args...) cmd.Env = append(os.Environ(), "PYTHONUNBUFFERED=1") + // yt-dlp outputs [download] progress lines to stdout when saving to a file + // and to stderr when using -o -. Since we save to a file, read stdout for progress. + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("create stdout pipe: %w", err) + } + stderr, err := cmd.StderrPipe() if err != nil { return nil, fmt.Errorf("create stderr pipe: %w", err) @@ -329,10 +357,16 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c progressDone := make(chan struct{}) go func() { - readProgress(stderr, updates, job) + readProgress(stdout, updates, job) close(progressDone) }() + // Capture stderr (warnings, errors) for diagnostics + go func() { + slurp, _ := io.ReadAll(stderr) + job.StderrLog = string(slurp) + }() + // Wait for either completion or cancellation done := make(chan error, 1) go func() { @@ -395,6 +429,9 @@ func (c *Client) SearchYoutube(query string) (url, title string, err error) { if c.cookiesFile != "" { args = append(args, "--cookies", c.cookiesFile) } + if c.proxyURL != "" { + args = append(args, "--proxy", c.proxyURL) + } out, err := c.run(args) if err != nil { @@ -428,6 +465,9 @@ func (c *Client) Search(query, source string, limit int) ([]domain.PlaylistEntry if c.cookiesFile != "" { args = append(args, "--cookies", c.cookiesFile) } + if c.proxyURL != "" { + args = append(args, "--proxy", c.proxyURL) + } slog.Info("searching", "query", query, "source", source) out, err := c.run(args)