Fix Tidal API search — correct endpoint casing, JSON:API Accept header, and best-match selection

This commit is contained in:
Broque Thomas 2026-03-11 22:51:51 -07:00
parent ecfa30c918
commit 691e026208
3 changed files with 183 additions and 297 deletions

View file

@ -150,8 +150,7 @@ class TidalClient:
def _setup_session(self):
"""Setup requests session with headers"""
self.session.headers.update({
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/vnd.api+json',
'User-Agent': 'SoulSync/1.0'
})
@ -370,9 +369,10 @@ class TidalClient:
response = self.session.post(
self.token_url,
data=data,
headers={'Content-Type': 'application/x-www-form-urlencoded'},
timeout=10
)
if response.status_code == 200:
token_data = response.json()
self.access_token = token_data.get('access_token')
@ -414,9 +414,10 @@ class TidalClient:
response = self.session.post(
self.token_url,
data=data,
headers={'Content-Type': 'application/x-www-form-urlencoded'},
timeout=10
)
if response.status_code == 200:
token_data = response.json()
self.access_token = token_data.get('access_token')
@ -691,7 +692,7 @@ class TidalClient:
}
response = self.session.get(
f"{self.base_url}/searchresults/{encoded_query}",
f"{self.base_url}/searchResults/{encoded_query}",
params=params,
timeout=10
)
@ -737,21 +738,21 @@ class TidalClient:
@rate_limited
def search_artist(self, name: str) -> Optional[Dict]:
"""Search for an artist by name. Returns first result as raw dict or None."""
"""Search for an artist by name. Returns best matching result as raw dict or None."""
try:
if not self._ensure_valid_token():
return None
from urllib.parse import quote
from difflib import SequenceMatcher
encoded_query = quote(name, safe='')
params = {
'countryCode': 'US',
'include': 'artists',
'limit': 1
}
response = self.session.get(
f"{self.base_url}/searchresults/{encoded_query}",
f"{self.base_url}/searchResults/{encoded_query}",
params=params,
timeout=10
)
@ -769,13 +770,21 @@ class TidalClient:
elif 'included' in data:
items = [r for r in data['included'] if r.get('type') == 'artists']
if items:
item = items[0]
# Flatten JSON:API resource if needed
if 'attributes' in item and 'id' in item:
flat = dict(item['attributes'])
flat['id'] = item['id']
return flat
return item
# Flatten all items and pick best name match
best_item = None
best_score = 0.0
for item in items:
if 'attributes' in item and 'id' in item:
flat = dict(item['attributes'])
flat['id'] = item['id']
else:
flat = item
item_name = flat.get('name', '')
score = SequenceMatcher(None, name.lower(), item_name.lower()).ratio()
if score > best_score:
best_score = score
best_item = flat
return best_item
else:
logger.debug(f"Tidal artist search failed: {response.status_code}")
return None
@ -799,11 +808,10 @@ class TidalClient:
params = {
'countryCode': 'US',
'include': 'albums',
'limit': 1
}
response = self.session.get(
f"{self.base_url}/searchresults/{encoded_query}",
f"{self.base_url}/searchResults/{encoded_query}",
params=params,
timeout=10
)
@ -820,19 +828,29 @@ class TidalClient:
elif 'included' in data:
items = [r for r in data['included'] if r.get('type') == 'albums']
if items:
item = items[0]
if 'attributes' in item and 'id' in item:
flat = dict(item['attributes'])
flat['id'] = item['id']
# Preserve artist relationship for cross-verification
try:
rel_artists = item.get('relationships', {}).get('artists', {}).get('data', [])
if rel_artists:
flat['artist'] = {'id': rel_artists[0].get('id')}
except (AttributeError, IndexError, TypeError):
pass
return flat
return item
# Flatten all items and pick best title match
from difflib import SequenceMatcher
best_item = None
best_score = 0.0
for item in items:
if 'attributes' in item and 'id' in item:
flat = dict(item['attributes'])
flat['id'] = item['id']
# Preserve artist relationship for cross-verification
try:
rel_artists = item.get('relationships', {}).get('artists', {}).get('data', [])
if rel_artists:
flat['artist'] = {'id': rel_artists[0].get('id')}
except (AttributeError, IndexError, TypeError):
pass
else:
flat = item
item_title = flat.get('title', '')
score = SequenceMatcher(None, title.lower(), item_title.lower()).ratio()
if score > best_score:
best_score = score
best_item = flat
return best_item
else:
logger.debug(f"Tidal album search failed: {response.status_code}")
return None
@ -856,11 +874,10 @@ class TidalClient:
params = {
'countryCode': 'US',
'include': 'tracks',
'limit': 1
}
response = self.session.get(
f"{self.base_url}/searchresults/{encoded_query}",
f"{self.base_url}/searchResults/{encoded_query}",
params=params,
timeout=10
)
@ -877,19 +894,29 @@ class TidalClient:
elif 'included' in data:
items = [r for r in data['included'] if r.get('type') == 'tracks']
if items:
item = items[0]
if 'attributes' in item and 'id' in item:
flat = dict(item['attributes'])
flat['id'] = item['id']
# Preserve artist relationship for cross-verification
try:
rel_artists = item.get('relationships', {}).get('artists', {}).get('data', [])
if rel_artists:
flat['artist'] = {'id': rel_artists[0].get('id')}
except (AttributeError, IndexError, TypeError):
pass
return flat
return item
# Flatten all items and pick best title match
from difflib import SequenceMatcher
best_item = None
best_score = 0.0
for item in items:
if 'attributes' in item and 'id' in item:
flat = dict(item['attributes'])
flat['id'] = item['id']
# Preserve artist relationship for cross-verification
try:
rel_artists = item.get('relationships', {}).get('artists', {}).get('data', [])
if rel_artists:
flat['artist'] = {'id': rel_artists[0].get('id')}
except (AttributeError, IndexError, TypeError):
pass
else:
flat = item
item_title = flat.get('title', '')
score = SequenceMatcher(None, title.lower(), item_title.lower()).ratio()
if score > best_score:
best_score = score
best_item = flat
return best_item
else:
logger.debug(f"Tidal track search failed: {response.status_code}")
return None

View file

@ -30,7 +30,7 @@ from config.settings import config_manager
logger = get_logger("web_server")
# App version — single source of truth for backup metadata, version-info endpoint, etc.
SOULSYNC_VERSION = "1.8"
SOULSYNC_VERSION = "1.9"
# Dedicated source reuse logger — writes to logs/source_reuse.log
import logging as _logging
@ -15148,230 +15148,37 @@ def get_version_info():
"subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes",
"sections": [
{
"title": "🔧 Recent Updates",
"description": "Latest fixes and improvements",
"title": "🎵 Tidal & Qobuz Enrichment Workers",
"description": "Two new background enrichment workers for Tidal and Qobuz metadata",
"features": [
"• Profile Permissions — admin can control which pages each user can access and disable download functionality per profile",
"• Per-user home page — every user can choose their own landing page; non-admin defaults to Discover",
"• Enhanced Library Manager restricted to admin profiles only (management tool with inline editing)",
"• Download buttons hidden globally for profiles with downloads disabled, enforced on both frontend and backend",
"• Genius search fix — no longer blindly matches wrong artists (e.g. '50 Cent''108'); all bad matches auto-reset on first launch",
"• Library page fix — albums no longer merge across different artists with the same album title/year",
"• Post-processing race condition fix — no more MutagenError on files already moved by another thread",
"• iTunes storefront fallback — ID-based lookups automatically try 10 regional storefronts when the primary country returns no results",
"• iTunes country setting now applies immediately without restart",
"• Download modal progress bars no longer squished by media player CSS bleed",
"• Watchlist/wishlist button text no longer overflows on narrow screens",
"• Hover tooltips on truncated track names in download modals for full text visibility",
"• Library artist cards now show Spotify, iTunes, Last.fm, and Genius service badges alongside MusicBrainz, Deezer, and AudioDB",
"• Artist discography page now shows 'View on' buttons for all 7 matched services",
"• Redesigned 'View on' buttons with glassmorphic styling matching the app's design language",
"• Compact badge layout on artist cards — no longer overflows on mobile or when all services are matched",
"• Page headers upgraded with sidebar icons and gradient shimmer styling",
"• Help & Docs page — comprehensive in-app documentation covering every feature, setting, and flow across 71 subsections",
"• Docs sidebar with scroll spy navigation, search filter, and collapsible sections",
"• Genius 429 rate limit fix — exponential backoff (30s → 60s → 120s) now activates properly",
"• Watchlist badge integrated into service badge row with expand-on-hover animation"
"• Tidal worker enriches artists, albums, and tracks with Tidal IDs, thumbnails, and metadata",
"• Qobuz worker enriches artists, albums, and tracks with Qobuz IDs, labels, genres, and metadata",
"• Dashboard buttons with real-time status, progress tracking, and pause/resume controls",
"• Smart no-auth detection — buttons grey out when not authenticated to either service",
"• Module-level rate limiting for Qobuz — shared throttle across all client instances",
"• Full Enhanced Library Manager integration — status chips, manual matching, clickable service badges",
"• Library artist card badges and discography 'View on' buttons for both services",
"• Total enrichment worker count now at 9: Spotify, iTunes, MusicBrainz, AudioDB, Deezer, Last.fm, Genius, Tidal, Qobuz"
]
},
{
"title": "🎵 Now Playing Overhaul",
"description": "Redesigned media player with expanded Now Playing modal and smart radio",
"title": "🎧 Full Qobuz Support",
"description": "Qobuz added as a first-class download source alongside Tidal and Soulseek",
"features": [
"• Expanded Now Playing modal — click the sidebar player to open a full-screen playback experience",
"• Album art ambient glow — dominant color extracted from cover art tints the modal background",
"• Smart Radio mode — toggle on to auto-queue up to 50 similar tracks based on genre, mood, style, and artist",
"• Queue system — add tracks from the Enhanced Library Manager, manage queue in the Now Playing modal",
"• Web Audio visualizer — real frequency-driven bars responding to actual audio playback",
"• Repeat modes — off, repeat-all (loop queue), and repeat-one with shuffle support",
"• Redesigned sidebar player — always-visible controls with album art thumbnail and progress bar",
"• Media Session API — OS-level media controls with prev/next track support",
"• Keyboard shortcuts — Space (play/pause), arrows (seek/volume), M (mute), Escape (close)",
"• Track transition animations and buffering state indicator"
"• Search, browse, and download from Qobuz with quality selection up to Hi-Res 24-bit/192kHz",
"• Qobuz appears as a download source in hybrid mode with configurable priority",
"• Playlist import from Qobuz URLs with mirrored playlist support",
"• Settings page integration with conditional source visibility"
]
},
{
"title": "📚 Enhanced Library Manager",
"description": "Professional-grade library management view on the artist detail page",
"title": "🔀 Hybrid Mode Redesign",
"description": "Overhauled download source selection and priority system",
"features": [
"• Toggle between Standard and Enhanced views on any artist's discography",
"• Inline metadata editing — click any field to edit artist, album, or track data",
"• Per-service manual matching — click any service chip (Spotify, MusicBrainz, Deezer, AudioDB, iTunes, Last.fm, Genius) to search and manually pick the correct match",
"• Per-service enrichment — enrich from individual services with independent locks",
"• Clickable service ID badges that open the entity on the external service website",
"• Match status chips showing matched/not found state for all 7 services on every artist, album, and track",
"• Sortable track table columns — click headers to sort by title, duration, format, bitrate, BPM, disc, or track number",
"• Multi-disc album support with disc number column",
"• Play tracks directly from the library via the sidebar media player",
"• Delete tracks and albums from the database (files on disk are never touched)",
"• Bulk select and batch edit tracks across albums",
"• Two-column album grid with full-width expanded detail panels",
"• MusicBrainz album art via Cover Art Archive in manual match results",
"• Write Tags to File — sync database metadata to audio file tags (MP3/FLAC/OGG/M4A)",
"• Tag preview modal showing a diff of file vs database values before writing",
"• Batch write tags for entire albums or bulk-selected tracks with live progress",
"• Optional cover art embedding during tag writes with per-album caching",
"• Server sync after tag writes — optionally push updated metadata to Plex, Jellyfin, or Navidrome"
],
"usage_note": "Open any artist's detail page and click 'Enhanced' in the view toggle to access the library manager."
},
{
"title": "🤖 Automation Engine",
"description": "Visual drag-and-drop automation builder with 22 triggers and 19 actions",
"features": [
"• Drag-and-drop builder: connect WHEN triggers → DO actions → THEN notifications/signals",
"• Timer triggers: Schedule (interval), Daily Time, Weekly Schedule",
"• Event triggers: Track Downloaded, Batch Complete, New Release Found, Playlist Changed, Discovery Complete, and 15 more",
"• Actions: Process Wishlist, Scan Watchlist, Refresh/Discover/Sync Playlists, Update Database, Quality Scan, Backup, and more",
"• Multi-THEN slots: attach up to 3 notification or signal actions per automation",
"• Signal system: fire_signal and signal_received for chaining automations together with cycle detection",
"• Conditions with match modes (All/Any) and operators (contains, equals, starts_with, not_contains)",
"• Configurable delay on actions — wait N minutes after trigger fires before executing",
"• System automations for wishlist (every 30 min) and watchlist (every 24 hr) with cross-guards",
"• Run Now button on every card for instant testing",
"• Live progress tracking with real-time output panels on every automation card",
"• Rich run history with detailed stats grid (artists, tracks, albums, space freed, etc.)",
"• Stall detection and 2-hour timeout handling for long-running actions",
"• Glow effect on actively running automations"
]
},
{
"title": "💾 Backup Manager",
"description": "Database backup system with dashboard management",
"features": [
"• Scheduled automatic database backups via automation action",
"• Dashboard tool card to list, download, restore, and delete backups",
"• Rolling cleanup keeps only the 5 most recent backups",
"• Safe hot-copy using SQLite backup API — no downtime during backup"
]
},
{
"title": "🔍 Playlist Discovery Pipeline",
"description": "Official Spotify/iTunes metadata enforcement for mirrored playlist sync",
"features": [
"• New 'Discover Playlist' automation action matches raw YouTube/Tidal tracks to official Spotify or iTunes metadata",
"• Fuzzy title/artist matching with confidence scoring — minimum 0.7 threshold",
"• Discovery results cached globally across playlists for instant repeat lookups",
"• Discovery results now persist across refreshes — retry only failed tracks",
"• Sync Playlist now only includes discovered tracks — undiscovered tracks are skipped entirely",
"• Prevents garbage data (wrong artist, no album, no cover art) from reaching the wishlist",
"• Spotify-sourced playlists auto-discovered during refresh at confidence 1.0",
"• Chain automations: Refresh → Playlist Changed → Discover → Discovery Complete → Sync"
]
},
{
"title": "🔔 Notification Integrations",
"description": "Get alerted when automations run via Discord, Pushbullet, or Telegram",
"features": [
"• Discord Webhook — post automation results to any Discord channel",
"• Pushbullet — push notifications to phone and desktop",
"• Telegram Bot — send alerts via Telegram Bot API",
"• Variable substitution in messages: {artist}, {title}, {album}, {quality}, {time}, and more",
"• Attach notifications to any automation — event-based or scheduled"
]
},
{
"title": "🔄 YouTube & Tidal Playlist Refresh",
"description": "Automated re-fetching of mirrored playlists from any source",
"features": [
"• Refresh Mirrored Playlist action now supports YouTube and Tidal alongside Spotify",
"• YouTube URLs reconstructed from stored playlist ID for seamless re-parsing",
"• Tidal playlists refreshed via Tidal API using stored source ID",
"• Discovery data preserved across refreshes — previously matched tracks keep their official metadata",
"• Change detection emits Playlist Changed event for automation chaining",
"• User-configurable YouTube rate limiting and optional cookies for bot detection"
]
},
{
"title": "📋 Mirrored Playlists",
"description": "Persistent cross-service playlist archive on the Sync page",
"features": [
"• Automatically mirrors every parsed playlist from Spotify, Tidal, YouTube, and Beatport",
"• New 'Mirrored' tab with source-branded cards showing live discovery and download status",
"• Click any mirrored playlist to browse its full track list or run it through discovery",
"• Re-parsing the same playlist updates the existing mirror — no duplicates",
"• Cards reflect live state: Discovering, Discovered, Downloading, Downloaded",
"• Download progress survives page refresh — click a 'Downloading...' card to resume viewing",
"• Profile-scoped — each profile has its own mirrored playlists"
]
},
{
"title": "🔧 Dashboard Tools",
"description": "New tool cards on the dashboard for quick access to common operations",
"features": [
"• Discovery Pool card with revamped category-card modal design",
"• Backup Manager card with list, download, restore, and delete",
"• Deep Library Scan for enrichment-safe sync without re-downloading",
"• Download Now button on wishlist modal and library page download bubbles"
]
},
{
"title": "👥 Multi-Profile Support",
"description": "Netflix-style profile picker for shared households with per-profile permissions",
"features": [
"• Multiple profiles sharing one SoulSync instance with isolated personal data",
"• Each profile gets its own watchlist, wishlist, discovery pool, and similar artists",
"• Optional PIN protection per profile with admin-only management",
"• Profile avatar images via URL with colored-initial fallback",
"• Shared music library and service credentials across all profiles",
"• Admin-controlled page access — choose which sidebar pages each profile can see",
"• Per-profile download toggle — disable downloading for specific users (frontend + backend enforced)",
"• Customizable home page — each user picks their own landing page from their permitted pages",
"• Non-admin users default to Discover page instead of Dashboard",
"• Self-service profile editing — non-admin users can change their name and home page",
"• Zero-downtime migration — existing data maps to auto-created admin profile",
"• Single-user installs see no changes until a second profile is created"
]
},
{
"title": "⚡ WebSocket Real-Time Updates",
"description": "Live push updates replace polling for a faster, more responsive UI",
"features": [
"• Service status, watchlist count, and download progress pushed via WebSocket",
"• Dashboard stats, activity feed, and database stats update in real-time",
"• Enrichment worker statuses stream live to the sidebar",
"• Tool progress (quality scanner, duplicate cleaner, retag, DB update) pushed instantly",
"• Automatic HTTP polling fallback when WebSocket is unavailable",
"• Profile-scoped WebSocket rooms for per-profile count updates"
]
},
{
"title": "🔑 REST API (v1)",
"description": "Full programmatic access to SoulSync with API key authentication",
"features": [
"• API key authentication with generation and management UI",
"• Endpoints for library, watchlist, wishlist, downloads, and system status",
"• Reverse proxy support for external access"
]
},
{
"title": "👁️ Watchlist Redesign",
"description": "Enriched artist detail view and improved watchlist management",
"features": [
"• Redesigned watchlist modal with enriched artist detail view",
"• Add to watchlist button on each artist in library",
"'Watch All' button on hero slider to quickly add all displayed artists",
"• Add all recommended artists to watchlist in one click",
"• View recommended/similar artists panel",
"• Batched watchlist status checks to eliminate rate limit errors"
]
},
{
"title": "📦 Import Rebuild",
"description": "Import feature moved to its own dedicated page",
"features": [
"• Rebuilt import feature on a standalone page with improved workflow",
"• Import staging guide documentation"
]
},
{
"title": "🎨 UI & Customization",
"description": "Visual improvements and personalization options",
"features": [
"• Custom accent colors with persistent user preference",
"• Themed confirm dialog modal replacing all native browser confirms",
"• Retag tool layout redesign",
"• Watchlist page visual redesign"
"• Redesigned hybrid mode with drag-and-drop source priority ordering",
"• Tidal, Qobuz, and Soulseek sources with per-source quality preferences",
"• Conditional settings — source-specific options only appear when that source is enabled",
"• Reorganized settings page with clearer Download Source section"
]
},
{
@ -15379,51 +15186,103 @@ def get_version_info():
"description": "Smart detection and handling of Spotify API rate limits",
"features": [
"• Automatic detection of long rate limit bans (Retry-After > 60s) from Spotify",
"• Global suppression of all Spotify API calls during a ban — no wasted requests that could extend it",
"• Global suppression of all Spotify API calls during a ban — no wasted requests",
"• Seamless iTunes/Apple Music fallback for searches while Spotify is rate limited",
"• Enrichment worker auto-pauses during rate limit and resumes when ban expires",
"• Frontend popup modal with live countdown timer showing ban duration and triggering endpoint",
"• One-click Disconnect Spotify button in the modal to switch to Apple Music immediately",
"• Dashboard and sidebar indicators show 'Rate Limited' status with remaining time",
"• Auth probe no longer makes API calls during ban — prevents the vicious cycle of auth checks extending the ban"
"• Frontend modal with live countdown timer showing ban duration and triggering endpoint",
"• One-click Disconnect Spotify button to switch to Apple Music immediately",
"• Auth probe no longer makes API calls during ban — prevents extending the ban"
]
},
{
"title": "🎶 Last.fm & Genius Enrichment Workers",
"description": "Two new background enrichment workers bringing rich metadata to your library",
"title": "👤 Profile Permissions & Page Access Control",
"description": "Granular admin controls over what each profile can see and do",
"features": [
"• Last.fm worker enriches artists, albums, and tracks with listener counts, play counts, tags, similar artists, and bios",
"• Genius worker enriches artists and tracks with descriptions, alternate names, lyrics, and song art URLs",
"• Last.fm URLs stored as identifiers — Genius uses numeric IDs",
"• Dashboard buttons with real-time status, progress tracking, and pause/resume controls",
"• Smart no-auth detection — buttons grey out with guidance when API keys are missing",
"• Full Enhanced Library Manager integration — status chips, manual matching, and on-demand enrichment for both services",
"• Settings reload — changing API keys takes effect immediately without restarting",
"• Same retry logic as all other workers: 30-day retry for not found, 7-day retry for errors"
"• Admin can control which sidebar pages each profile can access",
"• Per-profile download toggle — disable downloading for specific users (frontend + backend enforced)",
"• Per-user home page — every user can choose their own landing page",
"• Enhanced Library Manager restricted to admin profiles only",
"• Non-admin users default to Discover page instead of Dashboard"
]
},
{
"title": "🎵 Now Playing Overhaul",
"description": "Redesigned media player with expanded Now Playing modal and smart radio",
"features": [
"• Expanded Now Playing modal — click the sidebar player to open a full-screen experience",
"• Album art ambient glow — dominant color from cover art tints the modal background",
"• Smart Radio mode — auto-queue up to 50 similar tracks based on genre, mood, and style",
"• Queue system — add tracks from the library, manage queue in Now Playing modal",
"• Web Audio visualizer — real frequency-driven bars responding to actual playback",
"• Repeat modes (off, repeat-all, repeat-one), shuffle, Media Session API controls",
"• Keyboard shortcuts — Space, arrows, M (mute), Escape (close)"
]
},
{
"title": "📚 Enhanced Library Manager",
"description": "Professional-grade library management with tag writing and server sync",
"features": [
"• Toggle between Standard and Enhanced views on any artist's discography",
"• Inline metadata editing — click any field to edit artist, album, or track data",
"• Per-service manual matching for all 9 enrichment services",
"• Write Tags to File — sync database metadata to audio file tags (MP3/FLAC/OGG/M4A)",
"• Tag preview modal showing a diff of file vs database values before writing",
"• Batch write tags for entire albums or bulk-selected tracks with live progress",
"• Optional cover art embedding with per-album caching",
"• Server sync after tag writes — push updated metadata to Plex, Jellyfin, or Navidrome",
"• Bulk select and batch edit tracks across albums",
"• Sortable track table columns, multi-disc support, play tracks from library"
],
"usage_note": "Open any artist's detail page and click 'Enhanced' in the view toggle to access the library manager."
},
{
"title": "🎶 Last.fm & Genius Enrichment Workers",
"description": "Background enrichment workers for Last.fm and Genius metadata",
"features": [
"• Last.fm worker enriches artists, albums, and tracks with listener counts, play counts, tags, and bios",
"• Genius worker enriches artists and tracks with descriptions, alternate names, and lyrics",
"• Dashboard buttons with status, progress, and pause/resume controls",
"• No-auth detection — buttons grey out with guidance when API keys are missing",
"• Settings reload — changing API keys takes effect immediately without restarting"
]
},
{
"title": "✨ UI & Visual Overhaul",
"description": "Per-page particle animations, sidebar visualizer, and design refresh",
"features": [
"• Per-page particle animations with unique themes for each page",
"• Sidebar audio visualizer with 5 reactive styles and settings toggle",
"• Sidebar SVG icons with accent-colored navigation and ambient aura",
"• Page headers with sidebar icons and gradient shimmer styling",
"• Service badges on library artist cards for all 9 enrichment services",
"• Glassmorphic 'View on' buttons on artist discography pages",
"• Help & Docs page — comprehensive in-app documentation covering every feature"
]
},
{
"title": "🔧 Tidal Download Improvements",
"description": "Stability and accuracy fixes for Tidal downloads",
"features": [
"• Tidal download validation — detect and clean up unplayable hi-res stubs",
"• Tidal playlist pagination rate limiting with exponential backoff",
"• Include Tidal version field in track names — fixes remixes resolving to base title",
"• Direct single-playlist fetch instead of redundantly re-fetching all playlists"
]
},
{
"title": "🐛 Bug Fixes & Stability",
"description": "Reliability improvements",
"description": "Reliability improvements across the board",
"features": [
"• Fix radio tracks missing cover art when played from the Now Playing modal",
"• Fix sync completion not reaching UI after WebSocket reconnect",
"• Fix automation timezone bug causing incorrect scheduling",
"• Fix Tidal OAuth PKCE verification on Flask callback route",
"• Fix batch completion detection for post-processing race condition",
"• Fix similar_artists profile_id column being dropped on startup",
"• Fix download retry fallback not cycling through available sources",
"• Fix chromaprint crash (SIGABRT) on 5.1 surround audio files",
"• Fix Spotify enrichment worker showing 'Not Authenticated' after re-auth",
"• Fix infinite monitor loop on post-processing completed tasks",
"• Fix cancelled downloads not sending album context to wishlist",
"• Fix various artist compilations causing AcoustID check failures",
"• Fix ListenBrainz database path not respecting DATABASE_PATH env var",
"• Fix deleted content not removed during incremental database updates",
"• Podman rootless (keep-id) compatibility for Arch/rootless Docker users",
"• Fix Docker upgrade crash from stale volume mounts overriding application code",
"• Fix partial DB migrations leaving missing columns after interrupted upgrades",
"• Entrypoint now always updates settings.py to prevent stale config class errors"
"• Fix Genius search blindly matching wrong artists — all bad matches auto-reset",
"• Fix library page albums merging across different artists with same album title/year",
"• Fix post-processing race condition on files already moved by another thread",
"• iTunes storefront fallback — ID lookups automatically try 10 regional storefronts",
"• Fix infinite Spotify rate limit loop from unguarded auth probes",
"• Fix playlist folder downloads marked as failed despite successful processing",
"• Fix Docker upgrade crashes from stale volume mounts and partial DB migrations",
"• Isolate service client initialization so one failure doesn't break the app",
"• Explicit content filter with configurable toggle to skip explicit tracks",
"• Full Cleanup automation for quarantine, downloads, staging, and search history"
]
}
]

View file

@ -243,7 +243,7 @@
<!-- Version Section -->
<div class="version-section">
<button class="version-button" onclick="showVersionInfo()">v1.8</button>
<button class="version-button" onclick="showVersionInfo()">v1.9</button>
</div>
<!-- Status Section -->
@ -4743,7 +4743,7 @@
<!-- Header -->
<div class="version-modal-header">
<h2 class="version-modal-title">What's New in SoulSync</h2>
<div class="version-modal-subtitle">Version 1.2 - Enhanced Search & Matching Engine Overhaul</div>
<div class="version-modal-subtitle">Version 1.9 — Latest Changes</div>
</div>
<!-- Content Area with Scroll -->