fix: read yt-dlp progress from stdout instead of stderr; add explicit --proxy support
All checks were successful
CI / build (push) Successful in 52s

This commit is contained in:
2026-06-26 00:41:04 +03:30
parent df08a8840f
commit 58baa1b769
2 changed files with 45 additions and 12 deletions

View File

@@ -98,22 +98,16 @@ func parseProgressLine(line string) *progressUpdate {
return u 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. // The caller is responsible for closing the updates channel.
func readProgress(stderr io.ReadCloser, updates chan<- *domain.DownloadJob, job *domain.DownloadJob) { func readProgress(reader io.ReadCloser, updates chan<- *domain.DownloadJob, job *domain.DownloadJob) {
var stderrBuf strings.Builder scanner := bufio.NewScanner(reader)
scanner := bufio.NewScanner(stderr)
lineCount := 0 lineCount := 0
matchCount := 0 matchCount := 0
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
lineCount++ lineCount++
if stderrBuf.Len() < 4096 {
stderrBuf.WriteString(line)
stderrBuf.WriteByte('\n')
}
parsed := parseProgressLine(line) parsed := parseProgressLine(line)
if parsed == nil { if parsed == nil {
continue continue
@@ -124,6 +118,5 @@ func readProgress(stderr io.ReadCloser, updates chan<- *domain.DownloadJob, job
job.ETA = parsed.eta job.ETA = parsed.eta
updates <- job updates <- job
} }
job.StderrLog = stderrBuf.String() slog.Info("readProgress finished", "url", job.URL, "lines", lineCount, "matches", matchCount)
slog.Info("readProgress finished", "url", job.URL, "lines", lineCount, "matches", matchCount, "stderr_len", len(job.StderrLog))
} }

View File

@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"log/slog" "log/slog"
"os" "os"
"os/exec" "os/exec"
@@ -21,6 +22,7 @@ import (
type Client struct { type Client struct {
binaryPath string binaryPath string
cookiesFile string cookiesFile string
proxyURL string
cache map[string]*cachedMediaInfo cache map[string]*cachedMediaInfo
cacheMu sync.Mutex 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) return nil, fmt.Errorf("yt-dlp not found in PATH: %w", err)
} }
slog.Info("yt-dlp found", "path", path) 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{ return &Client{
binaryPath: path, binaryPath: path,
cookiesFile: cookiesFile, cookiesFile: cookiesFile,
proxyURL: proxyURL,
cache: make(map[string]*cachedMediaInfo), cache: make(map[string]*cachedMediaInfo),
}, nil }, nil
} }
@@ -118,6 +130,9 @@ func (c *Client) GetMediaInfo(url string) (*domain.MediaInfo, error) {
if c.cookiesFile != "" { if c.cookiesFile != "" {
args = append(args, "--cookies", c.cookiesFile) args = append(args, "--cookies", c.cookiesFile)
} }
if c.proxyURL != "" {
args = append(args, "--proxy", c.proxyURL)
}
slog.Info("fetching media info", "url", url) slog.Info("fetching media info", "url", url)
out, err := c.run(args) out, err := c.run(args)
@@ -184,6 +199,9 @@ func (c *Client) GetPlaylistInfo(url string) (*domain.MediaInfo, error) {
if c.cookiesFile != "" { if c.cookiesFile != "" {
args = append(args, "--cookies", c.cookiesFile) args = append(args, "--cookies", c.cookiesFile)
} }
if c.proxyURL != "" {
args = append(args, "--proxy", c.proxyURL)
}
slog.Info("fetching playlist info", "url", url) slog.Info("fetching playlist info", "url", url)
out, err := c.run(args) out, err := c.run(args)
@@ -297,6 +315,9 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c
if c.cookiesFile != "" { if c.cookiesFile != "" {
args = append(args, "--cookies", c.cookiesFile) args = append(args, "--cookies", c.cookiesFile)
} }
if c.proxyURL != "" {
args = append(args, "--proxy", c.proxyURL)
}
args = append(args, job.URL) 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 := exec.Command(c.binaryPath, args...)
cmd.Env = append(os.Environ(), "PYTHONUNBUFFERED=1") 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() stderr, err := cmd.StderrPipe()
if err != nil { if err != nil {
return nil, fmt.Errorf("create stderr pipe: %w", err) 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{}) progressDone := make(chan struct{})
go func() { go func() {
readProgress(stderr, updates, job) readProgress(stdout, updates, job)
close(progressDone) close(progressDone)
}() }()
// Capture stderr (warnings, errors) for diagnostics
go func() {
slurp, _ := io.ReadAll(stderr)
job.StderrLog = string(slurp)
}()
// Wait for either completion or cancellation // Wait for either completion or cancellation
done := make(chan error, 1) done := make(chan error, 1)
go func() { go func() {
@@ -395,6 +429,9 @@ func (c *Client) SearchYoutube(query string) (url, title string, err error) {
if c.cookiesFile != "" { if c.cookiesFile != "" {
args = append(args, "--cookies", c.cookiesFile) args = append(args, "--cookies", c.cookiesFile)
} }
if c.proxyURL != "" {
args = append(args, "--proxy", c.proxyURL)
}
out, err := c.run(args) out, err := c.run(args)
if err != nil { if err != nil {
@@ -428,6 +465,9 @@ func (c *Client) Search(query, source string, limit int) ([]domain.PlaylistEntry
if c.cookiesFile != "" { if c.cookiesFile != "" {
args = append(args, "--cookies", c.cookiesFile) args = append(args, "--cookies", c.cookiesFile)
} }
if c.proxyURL != "" {
args = append(args, "--proxy", c.proxyURL)
}
slog.Info("searching", "query", query, "source", source) slog.Info("searching", "query", query, "source", source)
out, err := c.run(args) out, err := c.run(args)