Playlist Sync Internals¶
Sync Strategy (src/playlist/sync.py)¶
sync_playlist() uses a replace-then-verify approach rather than incremental diffing:
- Replace content -
_replace_playlist_content()removes all tracks, then adds the desired list - Verify - re-fetch the playlist and compare against desired state
- Detect substitutions - if mismatches exist, check whether YouTube replaced videos with equivalent ones
- Retry - up to
verify_attempts(default 2) if verification fails
Precondition Retry¶
YouTube's API returns 400 (Precondition) or 409 (Conflict) when the playlist state changed between read and write. _replace_playlist_content() retries these with increasing delays (3s, 6s) by re-fetching the playlist state before each attempt.
YouTube Substitution Detection¶
_are_same_song() detects when YouTube silently replaces a requested video ID with a different upload of the same song.
Comparison logic:
- Fetch metadata for both video IDs via
get_song() - Normalize titles by stripping artist prefixes (
"artist - ") and suffixes:(audio),(official audio),(official video),(lyric video),(lyrics),[official audio],[audio],- audio,- official audio - Check for exact title match and at least one shared artist → substitution
- Check for substring containment (min length 3) with artist match → substitution
Detected substitutions are:
- Logged as info messages
- Recorded in the history DB as
"substitution"actions - Accepted silently - the adjusted desired list is used for further verification
Retry & Backoff¶
_retry_with_backoff() wraps all YTM API calls with exponential backoff:
| Parameter | Value |
|---|---|
| Max retries | 3 |
| Initial delay | 1s |
| Backoff factor | 2× (1s → 2s → 4s) |
| Retryable errors | 403, Forbidden, Expecting value (invalid JSON) |
| Non-retryable | 400 + Precondition (handled by precondition retry), 409 Conflict |
Invalid Video ID Handling¶
When a bulk add_playlist_items call fails with 400/409:
- Each video ID is validated individually via
get_song() - Invalid IDs are collected into an
InvalidVideoIDsError - The caller (
run()) evicts bad IDs from the search cache via_evict_from_cache() - The full track list is re-resolved with fresh API searches for evicted tracks
- Sync is retried with corrected video IDs
_evict_from_cache() scans all cache entries, matches video IDs against the bad set, and deletes the corresponding (artist, title) entries.
Video ID Validation¶
Video IDs must be exactly 11 characters (alphanumeric + underscore/hyphen). Invalid IDs are filtered out during playlist retrieval by _get_playlist_video_ids().
Weekly Playlist Snapshots (src/playlist/weekly.py)¶
Creates a rolling weekly copy of the main playlist.
Naming Convention¶
"{prefix} week of YYYY-MM-DD" where:
- Prefix is either
WEEKLY_PLAYLIST_PREFIXor auto-derived from the main playlist name (strips trailing(auto)) - Date is the start of the current week
Week Calculation¶
- Timezone-aware via
ZoneInfo(falls back to UTC) - Week start day configurable (
MON-SUN) _start_of_week()computes the most recent occurrence of the configured start day at00:00:00
Pruning¶
_prune_old_weeklies() keeps only the N most recent weekly playlists:
- Lists all library playlists matching
"{prefix} week of "pattern - Parses ISO dates from titles
- Sorts by date descending
- Deletes everything beyond
WEEKLY_KEEP_WEEKS(default 2)
Template Caching¶
Weekly playlists use the same PlaylistCache template system - sync is skipped if the video ID list hasn't changed since last run.
upsert_playlist() Helper¶
A convenience function that combines create-or-update with template checking:
- Look up existing playlist by name
- If exists and template changed →
sync_playlist() - If exists and template unchanged → skip
- If not found →
create_playlist_with_items()
Used by tag-based custom playlists to avoid duplicating the create/update logic.