1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-22 03:05:32 +02:00

feat(core+frontend): Spotify album/playlist capture via yt-dlp container path

Previously SpotifyAlbum and SpotifyPlaylist hard-failed with a DRM error.
yt-dlp does support Spotify (experimentally), so they now go through the
same probe/container/child path as YouTube playlists.

ytdlp.rs:
- Extract normalize_item_url() helper used by both fetch_playlist_info
  and probe_playlist_qualities; eliminates the duplicate URL-normalization
  blocks and the YouTube-specific fallback_host variable
- Fallback logic is now platform-aware: YouTube/YTM bare IDs → watch URL,
  Spotify → open.spotify.com/track/{id}, unknown → skip with warning

capture.rs:
- locator_to_playlist_url: accept SpotifyAlbum | SpotifyPlaylist so the
  probe-playlist endpoint accepts Spotify album/playlist URLs
- Container branch: add SpotifyAlbum | SpotifyPlaylist to the matches!
- is_audio: true for SpotifyAlbum | SpotifyPlaylist (audio-only, like YTM)
- child_source: SpotifyTrack for Spotify containers (not YouTubeMusicTrack)
- generate_entry_title and record_media_entry both use the correct child
  source so entity_kind, source_kind, and representation_kind are right

CaptureDialog.jsx:
- isPlaylistSource: recognise open.spotify.com/album/ and /playlist/,
  and spotify:album:ID / spotify:playlist:ID shorthands, so Spotify
  containers get the probe UI, per-track excludes, and sync toggle
This commit is contained in:
TheGeneralist 2026-07-21 17:58:55 +02:00
parent 5e7ce4774b
commit 6a7826eba5
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4
5 changed files with 82 additions and 71 deletions

View file

@ -85,24 +85,28 @@ function isPlaylistSource(locator) {
return ll.slice(4).startsWith('playlist/')
}
// spotify: shorthands album and playlist (not track)
if (ll.startsWith('spotify:')) {
const after = ll.slice(8)
return after.startsWith('album:') || after.startsWith('playlist:')
}
// HTTP/HTTPS URLs
if (ll.startsWith('http://') || ll.startsWith('https://')) {
try {
const url = new URL(l)
const host = url.hostname
if (host === 'youtube.com' || host === 'www.youtube.com') {
// Mirror determine_source exactly:
// - /playlist?list=... YouTubePlaylist
// - /@handle, /channel/, /c/, /user/ YouTubeChannel
// - everything else (incl. /watch&list=, /shorts?list=) NOT a playlist
if (url.pathname === '/playlist' && url.searchParams.has('list')) return true
if (url.pathname.startsWith('/@') || url.pathname.startsWith('/channel/') ||
url.pathname.startsWith('/c/') || url.pathname.startsWith('/user/')) return true
}
if (host === 'music.youtube.com') {
// /watch is a single YTM track; only /playlist?list=... is a YTM playlist
if (url.pathname === '/playlist' && url.searchParams.has('list')) return true
}
if (host === 'open.spotify.com') {
if (url.pathname.startsWith('/album/') || url.pathname.startsWith('/playlist/')) return true
}
} catch {}
}