fix(metadata): resolve album artist consistently across separate downloads

This commit is contained in:
zarzet committed 2026-09-28 11:45:10 +07:00
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,
+42
View File
@@ -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;
}
}