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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user