fix: progress stuck at 0%, container not respected, audio display, Spotify DRM fallback

- Remove --no-progress from yt-dlp args that suppressed all progress output
- Add --merge-output-format when FormatString is set (container was ignored for combined formats)
- Improve formatLabelForDisplay for audio-only formats missing bitrate (show ID+extension)
- Detect DRM errors (Spotify) and fall back to YouTube Music search via ytsearch1:
This commit is contained in:
2026-06-25 15:50:43 +03:30
parent e65ade6c3c
commit 30a99ac2fd
2 changed files with 77 additions and 12 deletions

View File

@@ -220,6 +220,9 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c
// Use explicit format string if set (video+audio combination) // Use explicit format string if set (video+audio combination)
if job.FormatString != "" { if job.FormatString != "" {
args = append(args, "-f", job.FormatString) args = append(args, "-f", job.FormatString)
if job.Container != "" {
args = append(args, "--merge-output-format", job.Container)
}
} else { } else {
// Build format string for merge // Build format string for merge
switch job.MediaType { switch job.MediaType {
@@ -243,8 +246,8 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c
outputPath := filepath.Join(downloadDir, job.ID+"_%(id)s.%(ext)s") outputPath := filepath.Join(downloadDir, job.ID+"_%(id)s.%(ext)s")
args = append(args, "--output", outputPath) args = append(args, "--output", outputPath)
// Progress reporting // Progress reporting (--newline for line-based, no --no-progress so we get actual output)
args = append(args, "--newline", "--no-progress") args = append(args, "--newline")
// Language selection if specified // Language selection if specified
if job.Language != "" { if job.Language != "" {
@@ -337,6 +340,40 @@ func CancelDownload(job *domain.DownloadJob) {
} }
} }
// ytdlpSearchResult holds a single flat search result from yt-dlp.
type ytdlpSearchResult struct {
Title string `json:"title"`
URL string `json:"url"`
}
// SearchYoutube searches YouTube via yt-dlp ytsearch and returns the first result URL and title.
func (c *Client) SearchYoutube(query string) (url, title string, err error) {
args := []string{
"--flat-playlist", "-J", "--no-download",
"--no-warnings",
"ytsearch1:" + query,
}
if c.cookiesFile != "" {
args = append(args, "--cookies", c.cookiesFile)
}
out, err := c.run(args)
if err != nil {
return "", "", fmt.Errorf("youtube search failed: %w", err)
}
var info struct {
Entries []ytdlpSearchResult `json:"entries"`
}
if err := json.Unmarshal(out, &info); err != nil {
return "", "", fmt.Errorf("parse search result: %w", err)
}
if len(info.Entries) == 0 {
return "", "", fmt.Errorf("no results found")
}
return info.Entries[0].URL, info.Entries[0].Title, nil
}
func (c *Client) run(args []string) ([]byte, error) { func (c *Client) run(args []string) ([]byte, error) {
cmd := exec.Command(c.binaryPath, args...) cmd := exec.Command(c.binaryPath, args...)
var stderr bytes.Buffer var stderr bytes.Buffer

View File

@@ -65,6 +65,22 @@ func (h *Handler) handleURLInput(ctx context.Context, chatID int64, text string,
info, err := h.YtDlp.GetMediaInfo(url) info, err := h.YtDlp.GetMediaInfo(url)
if err != nil { if err != nil {
errMsg := err.Error() errMsg := err.Error()
// DRM content (Spotify, etc.): fall back to YouTube search
if strings.Contains(errMsg, "DRM") {
youtubeURL, ytTitle, searchErr := h.YtDlp.SearchYoutube(text)
if searchErr != nil {
h.sendText(ctx, chatID, fmt.Sprintf("This content is DRM protected and no YouTube alternative was found: %s", errMsg))
return
}
h.sendText(ctx, chatID, fmt.Sprintf("This content is DRM protected. Found on YouTube: %s", ytTitle))
info, err = h.YtDlp.GetMediaInfo(youtubeURL)
if err != nil {
h.sendText(ctx, chatID, fmt.Sprintf("YouTube fallback failed: %s", err.Error()))
return
}
url = youtubeURL
} else {
switch { switch {
case strings.Contains(errMsg, "Unsupported URL") || strings.Contains(errMsg, "is not a valid URL"): case strings.Contains(errMsg, "Unsupported URL") || strings.Contains(errMsg, "is not a valid URL"):
h.sendText(ctx, chatID, "This URL is not supported.") h.sendText(ctx, chatID, "This URL is not supported.")
@@ -77,6 +93,7 @@ func (h *Handler) handleURLInput(ctx context.Context, chatID int64, text string,
} }
return return
} }
}
if info.IsPlaylist { if info.IsPlaylist {
h.handlePlaylist(ctx, chatID, userID, url, info) h.handlePlaylist(ctx, chatID, userID, url, info)
@@ -571,6 +588,17 @@ func formatLabelForDisplay(f domain.Format) string {
} }
return label return label
} }
// Audio-only with no bitrate: show format ID + extension
if f.IsAudioOnly {
label := f.ID
if f.Extension != "" {
label += " (" + f.Extension + ")"
}
if f.Filesize > 0 {
label += fmt.Sprintf(" (%s)", formatBytes(f.Filesize))
}
return label
}
return f.ID return f.ID
} }