fix: read yt-dlp progress from stdout instead of stderr; add explicit --proxy support
All checks were successful
CI / build (push) Successful in 52s
All checks were successful
CI / build (push) Successful in 52s
This commit is contained in:
@@ -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))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user