mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 12:52:13 +02:00
fix(download): align native metadata preparation and ReplayGain support
This commit is contained in:
@@ -44,6 +44,7 @@ import 'package:spotiflac_android/utils/progress_stream_poller.dart';
|
||||
|
||||
import 'package:spotiflac_android/providers/download_history_provider.dart';
|
||||
import 'package:spotiflac_android/services/native_download_history.dart';
|
||||
import 'package:spotiflac_android/services/download_track_metadata.dart';
|
||||
|
||||
export 'package:spotiflac_android/providers/download_history_provider.dart';
|
||||
export 'package:spotiflac_android/providers/download_queue_state.dart';
|
||||
|
||||
@@ -22,6 +22,13 @@ class _DeezerExtendedMetadataFields {
|
||||
}
|
||||
|
||||
extension _DownloadQueueEmbedding on DownloadQueueNotifier {
|
||||
Future<Track> _prepareDownloadSourceTrack(Track track) async {
|
||||
if (!track.id.startsWith('deezer:')) return track;
|
||||
final id = track.id.substring('deezer:'.length);
|
||||
final enriched = await enrichIncompleteDownloadTrack(track, 'deezer', id);
|
||||
return identical(enriched, track) ? track : enriched.copyWith(deezerId: id);
|
||||
}
|
||||
|
||||
Future<Track> _resolveDownloadAlbumCredit(
|
||||
Track track,
|
||||
AppSettings settings,
|
||||
|
||||
@@ -877,8 +877,9 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
|
||||
return null;
|
||||
}
|
||||
|
||||
final sourceTrack = await _prepareDownloadSourceTrack(item.track);
|
||||
item = item.copyWith(
|
||||
track: await _resolveDownloadAlbumCredit(item.track, settings),
|
||||
track: await _resolveDownloadAlbumCredit(sourceTrack, settings),
|
||||
);
|
||||
|
||||
final isSafMode = _isSafMode(settings);
|
||||
@@ -915,12 +916,6 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
|
||||
}
|
||||
|
||||
final outputExt = _determineOutputExt(quality, item.service);
|
||||
if (settings.embedReplayGain &&
|
||||
outputExt != '.flac' &&
|
||||
outputExt != '.m4a') {
|
||||
return null;
|
||||
}
|
||||
|
||||
String? safFileName;
|
||||
final safOutputExt = isSafMode ? outputExt : '';
|
||||
final baseFilenameFormat = _shouldTreatAsSingleRelease(item.track)
|
||||
@@ -992,7 +987,7 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
|
||||
).withStrategy(useExtensions: true, useFallback: state.autoFallback);
|
||||
|
||||
return _NativeWorkerRequestContext(
|
||||
item: item,
|
||||
item: item.copyWith(track: trackForPayload),
|
||||
requestJson: jsonEncode(payload.toJson()),
|
||||
outputDir: outputDir,
|
||||
quality: quality,
|
||||
|
||||
@@ -307,107 +307,8 @@ class _DownloadRun {
|
||||
}
|
||||
|
||||
Future<bool> _enrichDeezerTrackIfNeeded() async {
|
||||
final needsEnrichment =
|
||||
trackToDownload.id.startsWith('deezer:') &&
|
||||
(trackToDownload.isrc == null ||
|
||||
trackToDownload.isrc!.isEmpty ||
|
||||
trackToDownload.trackNumber == null ||
|
||||
trackToDownload.trackNumber == 0 ||
|
||||
trackToDownload.totalTracks == null ||
|
||||
trackToDownload.totalTracks == 0 ||
|
||||
(trackToDownload.composer == null ||
|
||||
trackToDownload.composer!.isEmpty));
|
||||
|
||||
if (needsEnrichment) {
|
||||
try {
|
||||
_log.d(
|
||||
'Enriching incomplete metadata for Deezer track: ${trackToDownload.name}',
|
||||
);
|
||||
_log.d(
|
||||
'Current ISRC: ${trackToDownload.isrc}, TrackNumber: ${trackToDownload.trackNumber}',
|
||||
);
|
||||
final rawId = trackToDownload.id.split(':')[1];
|
||||
_log.d('Fetching full metadata for Deezer ID: $rawId');
|
||||
final fullData = await PlatformBridge.getProviderMetadata(
|
||||
'deezer',
|
||||
'track',
|
||||
rawId,
|
||||
);
|
||||
_log.d('Got response keys: ${fullData.keys.toList()}');
|
||||
|
||||
if (fullData.containsKey('track')) {
|
||||
final trackData = fullData['track'];
|
||||
_log.d('Track data type: ${trackData.runtimeType}');
|
||||
if (trackData is Map<String, dynamic>) {
|
||||
final data = trackData;
|
||||
_log.d('Track data keys: ${data.keys.toList()}');
|
||||
_log.d('ISRC from API: ${data['isrc']}');
|
||||
_log.d('album_type from API: ${data['album_type']}');
|
||||
final enrichedTotalTracks = readPositiveInt(data['total_tracks']);
|
||||
final enrichedTotalDiscs = readPositiveInt(data['total_discs']);
|
||||
final enrichedComposer = normalizeOptionalString(
|
||||
data['composer']?.toString(),
|
||||
);
|
||||
trackToDownload = trackToDownload.copyWith(
|
||||
id: (data['spotify_id'] as String?) ?? trackToDownload.id,
|
||||
name: (data['name'] as String?) ?? trackToDownload.name,
|
||||
artistName:
|
||||
(data['artists'] as String?) ?? trackToDownload.artistName,
|
||||
albumName:
|
||||
(data['album_name'] as String?) ?? trackToDownload.albumName,
|
||||
albumArtist: data['album_artist'] as String?,
|
||||
artistId:
|
||||
(data['artist_id'] ?? data['artistId'])?.toString() ??
|
||||
trackToDownload.artistId,
|
||||
albumId: data['album_id']?.toString() ?? trackToDownload.albumId,
|
||||
coverUrl: data['images'] as String?,
|
||||
duration:
|
||||
((data['duration_ms'] as int?) ??
|
||||
(trackToDownload.duration * 1000)) ~/
|
||||
1000,
|
||||
isrc: (data['isrc'] as String?) ?? trackToDownload.isrc,
|
||||
trackNumber: data['track_number'] as int?,
|
||||
discNumber: data['disc_number'] as int?,
|
||||
totalDiscs: enrichedTotalDiscs ?? trackToDownload.totalDiscs,
|
||||
releaseDate: data['release_date'] as String?,
|
||||
deezerId: rawId,
|
||||
albumType:
|
||||
(data['album_type'] as String?) ?? trackToDownload.albumType,
|
||||
totalTracks: enrichedTotalTracks ?? trackToDownload.totalTracks,
|
||||
composer: enrichedComposer ?? trackToDownload.composer,
|
||||
genre: data['genre']?.toString() ?? trackToDownload.genre,
|
||||
label: data['label']?.toString() ?? trackToDownload.label,
|
||||
copyright:
|
||||
data['copyright']?.toString() ?? trackToDownload.copyright,
|
||||
comment: data['comment']?.toString() ?? trackToDownload.comment,
|
||||
explicit:
|
||||
parseExplicitFlag(data['explicit']) ??
|
||||
trackToDownload.explicit,
|
||||
upc:
|
||||
(data['upc'] ?? data['barcode'])?.toString() ??
|
||||
trackToDownload.upc,
|
||||
);
|
||||
_log.d(
|
||||
'Metadata enriched: Track ${trackToDownload.trackNumber}, Disc ${trackToDownload.discNumber}, ISRC ${trackToDownload.isrc}, AlbumType ${trackToDownload.albumType}',
|
||||
);
|
||||
} else {
|
||||
_log.w('Unexpected track data type: ${trackData.runtimeType}');
|
||||
}
|
||||
} else {
|
||||
_log.w('Response does not contain track key');
|
||||
}
|
||||
} catch (e, stack) {
|
||||
_log.w('Failed to enrich metadata: $e');
|
||||
_log.w('Stack trace: $stack');
|
||||
}
|
||||
|
||||
if (await _shouldAbort('during metadata enrichment')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_log.d('Track coverUrl after enrichment: ${trackToDownload.coverUrl}');
|
||||
return true;
|
||||
trackToDownload = await n._prepareDownloadSourceTrack(trackToDownload);
|
||||
return !await _shouldAbort('during metadata enrichment');
|
||||
}
|
||||
|
||||
Future<void> _resolveOutputTarget() async {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/utils/int_utils.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
import 'package:spotiflac_android/utils/string_utils.dart';
|
||||
|
||||
/// Shared preparation before either queue chooses folders, filenames or tags.
|
||||
Future<Track> enrichIncompleteDownloadTrack(
|
||||
Track track,
|
||||
String providerId,
|
||||
String providerTrackId, {
|
||||
Future<Map<String, dynamic>> Function(String, String, String)? loadMetadata,
|
||||
}) async {
|
||||
if (normalizeOptionalString(track.isrc) != null &&
|
||||
(track.trackNumber ?? 0) > 0 &&
|
||||
(track.totalTracks ?? 0) > 0 &&
|
||||
normalizeOptionalString(track.composer) != null) {
|
||||
return track;
|
||||
}
|
||||
try {
|
||||
final response = await (loadMetadata ?? PlatformBridge.getProviderMetadata)(
|
||||
providerId,
|
||||
'track',
|
||||
providerTrackId,
|
||||
).timeout(const Duration(seconds: 8));
|
||||
final data = response['track'];
|
||||
if (data is! Map<String, dynamic>) return track;
|
||||
String? text(String key) => normalizeOptionalString(data[key]?.toString());
|
||||
final durationMs = readPositiveInt(data['duration_ms']);
|
||||
return track.copyWith(
|
||||
id: text('spotify_id') ?? track.id,
|
||||
name: text('name') ?? track.name,
|
||||
artistName: text('artists') ?? track.artistName,
|
||||
albumName: text('album_name') ?? track.albumName,
|
||||
albumArtist: text('album_artist') ?? track.albumArtist,
|
||||
artistId: text('artist_id') ?? text('artistId') ?? track.artistId,
|
||||
albumId: text('album_id') ?? track.albumId,
|
||||
coverUrl: text('images') ?? track.coverUrl,
|
||||
duration: durationMs == null ? track.duration : durationMs ~/ 1000,
|
||||
isrc: text('isrc') ?? track.isrc,
|
||||
trackNumber: readPositiveInt(data['track_number']) ?? track.trackNumber,
|
||||
discNumber: readPositiveInt(data['disc_number']) ?? track.discNumber,
|
||||
totalDiscs: readPositiveInt(data['total_discs']) ?? track.totalDiscs,
|
||||
releaseDate: text('release_date') ?? track.releaseDate,
|
||||
albumType: text('album_type') ?? track.albumType,
|
||||
totalTracks: readPositiveInt(data['total_tracks']) ?? track.totalTracks,
|
||||
composer: text('composer') ?? track.composer,
|
||||
genre: text('genre') ?? track.genre,
|
||||
label: text('label') ?? track.label,
|
||||
copyright: text('copyright') ?? track.copyright,
|
||||
comment: text('comment') ?? track.comment,
|
||||
explicit: parseExplicitFlag(data['explicit']) ?? track.explicit,
|
||||
upc: text('upc') ?? text('barcode') ?? track.upc,
|
||||
);
|
||||
} catch (error) {
|
||||
AppLogger('DownloadMetadata').w('Track enrichment failed: $error');
|
||||
return track;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user