mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-22 11:15:41 +02:00
feat: YouTube Music audio capture (ytm: shorthand, Spotify detection, stalled job recovery) (#19)
* feat: add YouTube Music and Spotify source detection - Add Source variants: YouTubeMusicTrack, YouTubeMusicPlaylist, SpotifyTrack, SpotifyAlbum, SpotifyPlaylist - ytm:ID shorthand → music.youtube.com/watch?v=ID (audio-only, forced in core regardless of caller quality hint) - ytm:playlist/ID and music.youtube.com/playlist URLs detected but fail with 'not yet implemented' via fail_run - Spotify URLs/shorthands detected and fail fast with clear DRM error via fail_run (after run item created, so status is visible in /runs) - source_metadata: youtube_music/music/audio and spotify/music/audio (entity_kind='music' for UI pill, representation_kind='audio' stored) - locator_to_ytdlp_url includes YouTubeMusicTrack for probe endpoint - generate_entry_title: 'Title — Artist' for YTM tracks - Frontend: isVideoSource handles ytm: and music.youtube.com/watch; Spotify returns false (no probe, clear server error on submit) - Placeholder updated to include ytm:ID - SOURCE_ICONS: youtube_music (red disc) and spotify (green waves) - 14 new tests covering all new sources (163 total, all pass) * fix: prevent yt-dlp playlist expansion and stalled run recovery - Add --no-playlist to ytdlp::download and fetch_metadata: URLs with a list= parameter (e.g. music.youtube.com/watch?v=ID&list=RDAMVM…) no longer cause yt-dlp to expand the full playlist and hang; both the metadata probe and the download are now single-item only - Fix fail_stalled_capture_jobs to also recover archive_runs and archive_run_items: capture_jobs.run_uid is NULL at crash time so a join is unreliable; instead fail all archive_runs/items still in_progress directly, then recount failed_count via subquery. Startup recovery now makes the Runs UI reflect the correct failed state after a hard shutdown - Expand fail_stalled_jobs_on_restart test to assert archive_run and archive_run_item rows are also marked failed, not just capture_jobs * fix: use play triangle for youtube_music icon
This commit is contained in:
parent
339076e6a2
commit
21b11c211f
7 changed files with 362 additions and 7 deletions
|
|
@ -18,6 +18,11 @@ function isVideoSource(locator) {
|
|||
}
|
||||
}
|
||||
|
||||
// ytm: shorthand — track only; playlist is not yet implemented
|
||||
if (ll.startsWith('ytm:')) {
|
||||
return !ll.slice(4).startsWith('playlist/')
|
||||
}
|
||||
|
||||
// x: / twitter: / tweet: shorthands — only x:media:ID routes to yt-dlp (Source::X)
|
||||
for (const scheme of ['x:', 'twitter:', 'tweet:']) {
|
||||
if (ll.startsWith(scheme)) {
|
||||
|
|
@ -25,6 +30,9 @@ function isVideoSource(locator) {
|
|||
}
|
||||
}
|
||||
|
||||
// spotify: shorthands — all will fail with a clear error; no probe needed
|
||||
if (ll.startsWith('spotify:')) return false
|
||||
|
||||
// Other platform shorthands — all go to yt-dlp
|
||||
if (ll.startsWith('instagram:') || ll.startsWith('facebook:') ||
|
||||
ll.startsWith('tiktok:') || ll.startsWith('reddit:') ||
|
||||
|
|
@ -32,6 +40,8 @@ function isVideoSource(locator) {
|
|||
|
||||
// HTTP/HTTPS URLs — match the same regexes and prefix checks as determine_source
|
||||
if (ll.startsWith('http://') || ll.startsWith('https://')) {
|
||||
// YouTube Music track (watch) — before generic YouTube check
|
||||
if (/^https?:\/\/music\.youtube\.com\/watch/.test(ll)) return true
|
||||
// YouTube video (watch, youtu.be, shorts) — not playlist or channel
|
||||
if (/^https?:\/\/(?:www\.)?(?:youtu\.be\/[0-9A-Za-z_-]+|youtube\.com\/watch\?v=[0-9A-Za-z_-]+|youtube\.com\/shorts\/[0-9A-Za-z_-]+)/.test(l)) return true
|
||||
// x.com → Source::X → yt-dlp (note: twitter.com URLs fall through to Source::Url, not yt-dlp)
|
||||
|
|
@ -46,6 +56,8 @@ function isVideoSource(locator) {
|
|||
if (/^https?:\/\/(?:www\.)?reddit\.com\//.test(ll) || ll.startsWith('https://redd.it/') || ll.startsWith('http://redd.it/')) return true
|
||||
// Snapchat
|
||||
if (/^https?:\/\/(?:www\.)?snapchat\.com\//.test(ll)) return true
|
||||
// Spotify — all will fail with a clear error; no probe needed
|
||||
if (ll.startsWith('https://open.spotify.com/') || ll.startsWith('http://open.spotify.com/')) return false
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
@ -402,7 +414,7 @@ function CaptureRow({ item, autoFocus, onLocatorChange, onQualityChange, onRemov
|
|||
ref={inputRef}
|
||||
className="capture-input"
|
||||
type="text"
|
||||
placeholder="https://… · yt:ID · tweet:ID · x:ID"
|
||||
placeholder="https://… · yt:ID · ytm:ID · tweet:ID · x:ID"
|
||||
value={item.locator}
|
||||
onChange={e => onLocatorChange(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter') onSubmit() }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue