mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-30 21:29:36 +02:00
fix(metadata): resolve album artist consistently across separate downloads
This commit is contained in:
1 parent
25c6292783
commit
fb13b87561
6 files changed
+206
No files matched your search
@@ -23,6 +23,7 @@ import 'package:spotiflac_android/providers/download_queue_state.dart';
|
||||
import 'package:spotiflac_android/services/app_state_database.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/services/download_request_payload.dart';
|
||||
import 'package:spotiflac_android/services/download_album_metadata.dart';
|
||||
import 'package:spotiflac_android/services/download_motion_artwork_source.dart';
|
||||
import 'package:spotiflac_android/services/ffmpeg_service.dart';
|
||||
import 'package:spotiflac_android/services/hires_check_service.dart';
|
||||
|
||||
@@ -22,6 +22,41 @@ class _DeezerExtendedMetadataFields {
|
||||
}
|
||||
|
||||
extension _DownloadQueueEmbedding on DownloadQueueNotifier {
|
||||
Future<Track> _resolveDownloadAlbumCredit(
|
||||
Track track,
|
||||
AppSettings settings,
|
||||
) async {
|
||||
if (!settings.embedMetadata ||
|
||||
normalizeOptionalString(track.albumId) == null) {
|
||||
return track;
|
||||
}
|
||||
final source =
|
||||
normalizeOptionalString(track.source)?.toLowerCase() ??
|
||||
(track.id.contains(':') ? track.id.split(':').first.toLowerCase() : '');
|
||||
if (source.isEmpty) return track;
|
||||
final providers = ref
|
||||
.read(extensionProvider)
|
||||
.extensions
|
||||
.where(
|
||||
(extension) => extension.enabled && extension.hasMetadataProvider,
|
||||
);
|
||||
final provider =
|
||||
providers
|
||||
.where((extension) => extension.id.toLowerCase() == source)
|
||||
.firstOrNull ??
|
||||
providers
|
||||
.where(
|
||||
(extension) =>
|
||||
extension.replacesBuiltInProviders.contains(source),
|
||||
)
|
||||
.firstOrNull;
|
||||
// Never send an album ID to an unrelated provider selected as a fallback.
|
||||
// This resolves the source release itself, even when the provider disables
|
||||
// supplemental cross-provider enrichment (for example genre/label lookup).
|
||||
if (provider == null) return track;
|
||||
return resolveDownloadAlbumArtist(track, provider.id);
|
||||
}
|
||||
|
||||
String? _resolveAlbumArtistForMetadata(Track track, AppSettings settings) {
|
||||
var albumArtist = normalizeOptionalString(track.albumArtist);
|
||||
if (settings.filterContributingArtistsInAlbumArtist) {
|
||||
|
||||
@@ -877,6 +877,10 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
|
||||
return null;
|
||||
}
|
||||
|
||||
item = item.copyWith(
|
||||
track: await _resolveDownloadAlbumCredit(item.track, settings),
|
||||
);
|
||||
|
||||
final isSafMode = _isSafMode(settings);
|
||||
final rawOutputDir = isSafMode
|
||||
? _buildRelativeOutputDir(
|
||||
|
||||
@@ -187,6 +187,12 @@ class _DownloadRun {
|
||||
|
||||
if (!await _enrichDeezerTrackIfNeeded()) return;
|
||||
|
||||
trackToDownload = await n._resolveDownloadAlbumCredit(
|
||||
trackToDownload,
|
||||
settings,
|
||||
);
|
||||
if (await _shouldAbort('during album metadata lookup')) return;
|
||||
|
||||
resolvedAlbumArtist = n._resolveAlbumArtistForMetadata(
|
||||
trackToDownload,
|
||||
settings,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
import 'package:spotiflac_android/utils/string_utils.dart';
|
||||
|
||||
final _log = AppLogger('DownloadAlbumMetadata');
|
||||
|
||||
/// Resolve the release credit independently of which tracks were selected.
|
||||
/// The bridge caches album responses and coalesces concurrent album lookups.
|
||||
Future<Track> resolveDownloadAlbumArtist(
|
||||
Track track,
|
||||
String providerId, {
|
||||
Future<Map<String, dynamic>> Function(String, String, String)? loadMetadata,
|
||||
}) async {
|
||||
final albumId = normalizeOptionalString(track.albumId);
|
||||
if (albumId == null || providerId.isEmpty) return track;
|
||||
try {
|
||||
final response = await (loadMetadata ?? PlatformBridge.getProviderMetadata)(
|
||||
providerId,
|
||||
'album',
|
||||
albumId,
|
||||
).timeout(const Duration(seconds: 8));
|
||||
final info = response['album_info'] ?? response['album'] ?? response;
|
||||
if (info is! Map<String, dynamic>) return track;
|
||||
final returnedId = normalizeOptionalString(info['id']?.toString());
|
||||
if (returnedId != null && returnedId != albumId) return track;
|
||||
// Only album-level credits are authoritative. Track artists may include
|
||||
// guests, and compilations/joint albums must retain their complete credit.
|
||||
String? artist;
|
||||
for (final key in ['album_artist', 'artists', 'artist']) {
|
||||
final value = info[key];
|
||||
if (value is String) artist = normalizeOptionalString(value);
|
||||
if (artist != null) break;
|
||||
}
|
||||
return artist == null || artist == track.albumArtist
|
||||
? track
|
||||
: track.copyWith(albumArtist: artist);
|
||||
} catch (error) {
|
||||
_log.w('Album credit lookup failed; keeping track metadata: $error');
|
||||
return track;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user