From 4eba28db7af3b4e06c960d1afc9514def37062c5 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 11 Jan 2026 06:09:48 +0700 Subject: [PATCH] v2.2.7: CSV import metadata enrichment with Deezer fallback --- CHANGELOG.md | 50 ++++++++++----- lib/constants/app_info.dart | 4 +- lib/providers/download_queue_provider.dart | 6 ++ lib/services/csv_import_service.dart | 72 +++++++++++++++++----- pubspec.yaml | 2 +- pubspec_ios.yaml | 2 +- 6 files changed, 100 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8cb395c..8d815009 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,24 +4,42 @@ ### Added -- **Deezer Metadata Support**: Enhanced metadata viewer for Deezer tracks - - "Open in Deezer" button for Deezer-sourced tracks (opens app or web) - - Displays "Deezer ID" instead of "Spotify ID" when applicable +- **CSV Import Metadata Enrichment**: Tracks imported from CSV now automatically fetch metadata from Deezer + - Cover art, duration, track/disc number fetched via ISRC lookup + - Fallback to text search (artist + track name) when ISRC not found in Deezer + - Progress dialog shows enrichment status during import + - Ensures downloaded files have proper cover art and metadata -### Changed +### Fixed -- **UI Modernization**: Major UI consistency updates across the app - - **Unified App Bars**: Home, History, and Settings now share identical behavior - - Lowered expanded header for easier one-handed reachability - - Dynamic title text scaling (20px to 34px) - - **Appearance Settings**: Completely redesigned appearance page - - New "Theme Preview" card showing visualizing current theme - - Modern color palette picker replacing old color dots - - Clean, grouped layout - - **App Logo**: Refined logo style on Home and About screens - - Inverted colors: Filled primary color circle with on-color icon - - Removed padding for a cleaner, bolder look - - **Material 3 Switches**: Added checkmark icon to active switches +- **CSV Import Missing Cover Art**: Fixed tracks from CSV having no cover art in download history + - Cover URL now properly fetched from Deezer during enrichment + - Falls back to text search when ISRC lookup fails +- **CSV Import Missing Duration**: Fixed duration showing 0:00 for CSV-imported tracks + - Duration now fetched from Deezer metadata during enrichment +- **Disc Number Not Displayed**: Fixed disc number not showing in track metadata screen + - Changed condition from `discNumber > 1` to `discNumber > 0` + - Now displays disc 1 instead of hiding it +- **Download History Using Wrong Track Data**: Fixed history using original CSV data instead of enriched data + - Now uses `trackToDownload` (enriched) instead of `item.track` (original) + +### Technical + +- Updated `lib/services/csv_import_service.dart`: + - Added `_enrichTracksMetadata()` with ISRC lookup + text search fallback + - Added progress callback for UI feedback +- Updated `lib/screens/home_tab.dart`: + - Added progress dialog during CSV enrichment +- Updated `lib/providers/download_queue_provider.dart`: + - Uses enriched track data for download history +- Updated `lib/screens/track_metadata_screen.dart`: + - Show disc number when > 0 (was > 1) +- Updated `go_backend/metadata.go`: + - Added `TotalSamples` to `AudioQuality` struct for duration calculation +- Updated `go_backend/exports.go`: + - `ReadFileMetadata` now returns duration calculated from FLAC stream info + +--- ## [2.2.6] - 2026-01-11 diff --git a/lib/constants/app_info.dart b/lib/constants/app_info.dart index 069c4766..0d51fd7d 100644 --- a/lib/constants/app_info.dart +++ b/lib/constants/app_info.dart @@ -1,8 +1,8 @@ /// App version and info constants /// Update version here only - all other files will reference this class AppInfo { - static const String version = '2.2.5'; - static const String buildNumber = '47'; + static const String version = '2.2.7'; + static const String buildNumber = '49'; static const String fullVersion = '$version+$buildNumber'; diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index 9abba0d4..5e4d87c5 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -1269,6 +1269,9 @@ class DownloadQueueNotifier extends Notifier { } } + // Log cover URL for debugging CSV import issues + _log.d('Track coverUrl after enrichment: ${trackToDownload.coverUrl}'); + final outputDir = await _buildOutputDir( trackToDownload, settings.folderOrganization, @@ -1522,6 +1525,9 @@ class DownloadQueueNotifier extends Notifier { final backendSampleRate = result['actual_sample_rate'] as int?; final backendISRC = result['isrc'] as String?; + // Log cover URL for debugging + _log.d('Saving to history - coverUrl: ${trackToDownload.coverUrl}'); + ref .read(downloadHistoryProvider.notifier) .addToHistory( diff --git a/lib/services/csv_import_service.dart b/lib/services/csv_import_service.dart index 980b2848..e06eb9ce 100644 --- a/lib/services/csv_import_service.dart +++ b/lib/services/csv_import_service.dart @@ -35,7 +35,7 @@ class CsvImportService { return []; } - /// Enrich tracks with metadata from Deezer using ISRC + /// Enrich tracks with metadata from Deezer using ISRC or search /// This fetches cover URL, duration, and other metadata that CSV doesn't have static Future> _enrichTracksMetadata( List tracks, { @@ -48,21 +48,63 @@ class CsvImportService { final track = tracks[i]; onProgress?.call(i + 1, tracks.length); - // Only enrich if we have ISRC and missing cover/duration - if (track.isrc != null && - track.isrc!.isNotEmpty && - (track.coverUrl == null || track.duration == 0)) { - try { - // searchDeezerByISRC returns TrackMetadata directly (not wrapped in "track" key) - final trackData = await PlatformBridge.searchDeezerByISRC(track.isrc!); - - // Extract enriched data from TrackMetadata + // Only enrich if missing cover/duration + if (track.coverUrl == null || track.duration == 0) { + Map? trackData; + + // Try ISRC first if available + if (track.isrc != null && track.isrc!.isNotEmpty) { + try { + trackData = await PlatformBridge.searchDeezerByISRC(track.isrc!); + _log.d('ISRC enrichment success for ${track.name}'); + } catch (e) { + _log.w('ISRC search failed for ${track.name}, trying text search...'); + } + } + + // Fallback to text search if ISRC failed or not available + if (trackData == null) { + try { + final query = '${track.artistName} ${track.name}'; + final searchResult = await PlatformBridge.searchDeezerAll(query, trackLimit: 5); + + if (searchResult.containsKey('tracks')) { + final tracksList = searchResult['tracks'] as List?; + if (tracksList != null && tracksList.isNotEmpty) { + // Find best match by comparing names + for (final result in tracksList) { + final resultMap = result as Map; + final resultName = (resultMap['name'] as String?)?.toLowerCase() ?? ''; + final trackNameLower = track.name.toLowerCase(); + + // Check if track name matches (contains or equals) + if (resultName.contains(trackNameLower) || trackNameLower.contains(resultName)) { + trackData = resultMap; + _log.d('Text search match for ${track.name}: $resultName'); + break; + } + } + + // If no exact match, use first result + if (trackData == null && tracksList.isNotEmpty) { + trackData = tracksList.first as Map; + _log.d('Using first search result for ${track.name}'); + } + } + } + } catch (e) { + _log.w('Text search also failed for ${track.name}: $e'); + } + } + + // Apply enriched data if found + if (trackData != null) { final coverUrl = trackData['images'] as String?; final durationMs = trackData['duration_ms'] as int? ?? 0; - final deezerIdRaw = trackData['spotify_id'] as String?; // Format: "deezer:123456" + final deezerIdRaw = trackData['spotify_id'] as String?; enrichedTracks.add(Track( - id: deezerIdRaw ?? track.id, // Use Deezer ID if available + id: deezerIdRaw ?? track.id, name: trackData['name'] as String? ?? track.name, artistName: trackData['artists'] as String? ?? track.artistName, albumName: trackData['album_name'] as String? ?? track.albumName, @@ -77,13 +119,11 @@ class CsvImportService { _log.d('Enriched: ${track.name} - cover: ${coverUrl != null}, duration: ${durationMs ~/ 1000}s'); - // Small delay to avoid rate limiting (50ms between requests) + // Small delay to avoid rate limiting if (i < tracks.length - 1) { - await Future.delayed(const Duration(milliseconds: 50)); + await Future.delayed(const Duration(milliseconds: 100)); } continue; - } catch (e) { - _log.w('Failed to enrich ${track.name}: $e'); } } diff --git a/pubspec.yaml b/pubspec.yaml index e4176962..3eec9e85 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: spotiflac_android description: Download Spotify tracks in FLAC from Tidal, Qobuz & Amazon Music publish_to: "none" -version: 2.2.5+47 +version: 2.2.7+49 environment: sdk: ^3.10.0 diff --git a/pubspec_ios.yaml b/pubspec_ios.yaml index d3a3e359..8e383a2e 100644 --- a/pubspec_ios.yaml +++ b/pubspec_ios.yaml @@ -1,7 +1,7 @@ name: spotiflac_android description: Download Spotify tracks in FLAC from Tidal, Qobuz & Amazon Music publish_to: "none" -version: 2.2.5+47 +version: 2.2.7+49 environment: sdk: ^3.10.0