fix: resolve download failures and duplicate messages, add fast playlist detection
All checks were successful
CI / build (push) Successful in 39s

- Remove --audio-multi-streams flag (not supported by Alpine yt-dlp 2026.03.17)
- Reuse the fetching message as the format selection message to avoid duplicate text
- Add DetectPlaylist method for fast flat playlist detection before expensive GetMediaInfo
- This avoids stalls on YouTube Music radio URLs with large playlists
This commit is contained in:
2026-06-26 12:39:11 +03:30
parent f5204b9554
commit 3f56ffbba9
2 changed files with 40 additions and 31 deletions

View File

@@ -194,6 +194,24 @@ func (c *Client) GetPlaylistInfo(url string) (*domain.MediaInfo, error) {
return cached, nil
}
isPlaylist, mi, err := c.DetectPlaylist(url)
if err != nil {
return nil, err
}
if !isPlaylist {
mi.IsPlaylist = false
mi.PlaylistCount = 0
mi.PlaylistEntries = nil
}
c.setCache(url, mi)
return mi, nil
}
// DetectPlaylist runs a fast flat yt-dlp check to determine if the URL is a playlist.
// It returns basic info without format details. For non-playlist URLs it returns false.
// Does NOT cache the result so a subsequent GetMediaInfo call fetches full format info.
func (c *Client) DetectPlaylist(url string) (bool, *domain.MediaInfo, error) {
args := []string{
"-J", "--flat-playlist", "--no-download",
"--no-warnings",
@@ -206,29 +224,29 @@ func (c *Client) GetPlaylistInfo(url string) (*domain.MediaInfo, error) {
args = append(args, "--proxy", c.proxyURL)
}
slog.Info("fetching playlist info", "url", url)
slog.Info("detecting playlist", "url", url)
out, err := c.run(args)
if err != nil {
return nil, fmt.Errorf("yt-dlp playlist info failed: %w", err)
return false, nil, fmt.Errorf("yt-dlp playlist detection failed: %w", err)
}
var info ytdlpInfo
if err := json.Unmarshal(out, &info); err != nil {
return nil, fmt.Errorf("parse yt-dlp playlist output: %w", err)
var rawInfo ytdlpInfo
if err := json.Unmarshal(out, &rawInfo); err != nil {
return false, nil, fmt.Errorf("parse yt-dlp output: %w", err)
}
mi := &domain.MediaInfo{
URL: url,
Title: info.Title,
Uploader: info.Uploader,
Thumbnail: info.Thumbnail,
IsPlaylist: true,
PlaylistCount: info.PlaylistCount,
PlaylistEntries: c.parsePlaylistEntries(info.Entries),
Title: rawInfo.Title,
Uploader: rawInfo.Uploader,
Thumbnail: rawInfo.Thumbnail,
Duration: rawInfo.Duration,
IsPlaylist: rawInfo.Playlist != "",
PlaylistCount: rawInfo.PlaylistCount,
PlaylistEntries: c.parsePlaylistEntries(rawInfo.Entries),
}
c.setCache(url, mi)
return mi, nil
return mi.IsPlaylist, mi, nil
}
func (c *Client) parsePlaylistEntries(entries []json.RawMessage) []domain.PlaylistEntry {
@@ -319,7 +337,6 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c
// Language selection if specified
if job.Language != "" {
args = append(args, "--sub-langs", job.Language)
args = append(args, "--audio-multi-streams")
}
// Cookies