feat(extensions): preserve provider metadata

This commit is contained in:
zarzet
2026-08-18 12:28:35 +07:00
parent 7fc27e8314
commit 48883d51d4
19 changed files with 260 additions and 69 deletions
@@ -283,6 +283,9 @@ internal fun NativeDownloadFinalizer.embedBasicMetadata(context: Context, path:
val genre = resultString(input, "genre").ifBlank { requestString(input, "genre") }
val label = resultString(input, "label").ifBlank { requestString(input, "label") }
val copyright = resultString(input, "copyright").ifBlank { requestString(input, "copyright") }
val comment = resultString(input, "comment").ifBlank {
trackString(input, "comment", requestString(input, "comment"))
}
val lyricsMode = input.request.optString("lyrics_mode", "embed")
val shouldResolveLyrics = input.request.optBoolean("embed_lyrics", false) &&
(lyricsMode == "embed" || lyricsMode == "both")
@@ -308,6 +311,7 @@ internal fun NativeDownloadFinalizer.embedBasicMetadata(context: Context, path:
.put("genre", genre)
.put("label", label)
.put("copyright", copyright)
.put("comment", comment)
if (trackNumberValue > 0) fields.put("track_number", trackNumberValue.toString())
if (totalTracksValue > 0) fields.put("track_total", totalTracksValue.toString())
if (discNumberValue > 0) fields.put("disc_number", discNumberValue.toString())
@@ -356,6 +360,7 @@ internal fun NativeDownloadFinalizer.embedBasicMetadata(context: Context, path:
"genre" to genre,
labelKey to label,
"copyright" to copyright,
"comment" to comment,
"lyrics" to if (shouldEmbedLyrics) lyrics else "",
"unsyncedlyrics" to if (shouldEmbedLyrics) lyrics else "",
)
+2
View File
@@ -72,6 +72,7 @@ function track(id) {
copyright: "Copyright",
genre: "Pop",
composer: "Composer",
comment: "https://example.test/album/1",
audioQuality: "FLAC 24-bit",
audioModes: "DOLBY_ATMOS",
explicit: true
@@ -165,6 +166,7 @@ registerExtension({
label: "Label",
copyright: "Copyright",
composer: "Composer",
comment: "https://example.test/album/1",
lyricsLrc: "[00:00.00]Hello",
decryptionKey: "001122",
decryption: { strategy: "mp4_decryption_key", options: { kid: "1" } }
+8
View File
@@ -45,6 +45,7 @@ type DownloadRequest struct {
Label string `json:"label,omitempty"`
Copyright string `json:"copyright,omitempty"`
Composer string `json:"composer,omitempty"`
Comment string `json:"comment,omitempty"`
TidalID string `json:"tidal_id,omitempty"`
QobuzID string `json:"qobuz_id,omitempty"`
DeezerID string `json:"deezer_id,omitempty"`
@@ -89,6 +90,7 @@ type DownloadResponse struct {
Label string `json:"label,omitempty"`
Copyright string `json:"copyright,omitempty"`
Composer string `json:"composer,omitempty"`
Comment string `json:"comment,omitempty"`
SkipMetadataEnrichment bool `json:"skip_metadata_enrichment,omitempty"`
LyricsLRC string `json:"lyrics_lrc,omitempty"`
DecryptionKey string `json:"decryption_key,omitempty"`
@@ -114,6 +116,7 @@ type DownloadResult struct {
Label string
Copyright string
Composer string
Comment string
LyricsLRC string
DecryptionKey string
Decryption *DownloadDecryptionInfo
@@ -175,6 +178,10 @@ func buildDownloadSuccessResponse(
if composer == "" {
composer = req.Composer
}
comment := result.Comment
if comment == "" {
comment = req.Comment
}
coverURL := strings.TrimSpace(result.CoverURL)
if coverURL == "" {
@@ -210,6 +217,7 @@ func buildDownloadSuccessResponse(
Label: label,
Copyright: copyright,
Composer: composer,
Comment: comment,
LyricsLRC: result.LyricsLRC,
DecryptionKey: result.DecryptionKey,
Decryption: normalizeDownloadDecryptionInfo(result.Decryption, result.DecryptionKey),
+33 -28
View File
@@ -27,34 +27,39 @@ func normalizeExtensionTrackMetadataMap(
}
return map[string]any{
"id": track.ID,
"name": track.Name,
"artists": track.Artists,
"album_name": track.AlbumName,
"album_artist": track.AlbumArtist,
"album_id": track.AlbumID,
"album_url": track.AlbumURL,
"artist_id": track.ArtistID,
"artist_url": track.ArtistURL,
"external_urls": track.ExternalURL,
"duration_ms": track.DurationMS,
"images": coverURL,
"cover_url": coverURL,
"preview_url": track.PreviewURL,
"release_date": track.ReleaseDate,
"track_number": trackNum,
"total_tracks": track.TotalTracks,
"disc_number": track.DiscNumber,
"total_discs": track.TotalDiscs,
"isrc": track.ISRC,
"provider_id": track.ProviderID,
"item_type": track.ItemType,
"album_type": track.AlbumType,
"spotify_id": track.SpotifyID,
"composer": track.Composer,
"audio_quality": track.AudioQuality,
"audio_modes": track.AudioModes,
"explicit": track.Explicit,
"id": track.ID,
"name": track.Name,
"artists": track.Artists,
"album_name": track.AlbumName,
"album_artist": track.AlbumArtist,
"album_id": track.AlbumID,
"album_url": track.AlbumURL,
"artist_id": track.ArtistID,
"artist_url": track.ArtistURL,
"external_urls": track.ExternalURL,
"duration_ms": track.DurationMS,
"images": coverURL,
"cover_url": coverURL,
"preview_url": track.PreviewURL,
"release_date": track.ReleaseDate,
"track_number": trackNum,
"total_tracks": track.TotalTracks,
"disc_number": track.DiscNumber,
"total_discs": track.TotalDiscs,
"isrc": track.ISRC,
"provider_id": track.ProviderID,
"item_type": track.ItemType,
"album_type": track.AlbumType,
"spotify_id": track.SpotifyID,
"external_links": track.ExternalLinks,
"genre": track.Genre,
"label": track.Label,
"copyright": track.Copyright,
"composer": track.Composer,
"comment": track.Comment,
"audio_quality": track.AudioQuality,
"audio_modes": track.AudioModes,
"explicit": track.Explicit,
}
}
+3 -1
View File
@@ -88,7 +88,9 @@ func attemptExtensionDownload(
if downloadSucceeded {
metadataStartedAt := time.Now()
enrichRequestExtendedMetadata(&req)
if !ext.Manifest.SkipMetadataEnrichment {
enrichRequestExtendedMetadata(&req)
}
LogDebug(
"DownloadPipeline",
"item=%s provider=%s post-transfer metadataMs=%.1f",
+3
View File
@@ -191,6 +191,7 @@ func normalizeExtensionDownloadResult(result *ExtDownloadResult) (DownloadResult
Label: result.Label,
Copyright: result.Copyright,
Composer: result.Composer,
Comment: result.Comment,
LyricsLRC: result.LyricsLRC,
DecryptionKey: result.DecryptionKey,
Decryption: normalizeDownloadDecryptionInfo(result.Decryption, result.DecryptionKey),
@@ -261,6 +262,7 @@ func overlayExtensionDownloadMetadata(resp *DownloadResponse, result *ExtDownloa
overlayStrTrim(&resp.Label, result.Label)
overlayStrTrim(&resp.Copyright, result.Copyright)
overlayStrTrim(&resp.Composer, result.Composer)
overlayStrTrim(&resp.Comment, result.Comment)
if result.LyricsLRC != "" {
resp.LyricsLRC = result.LyricsLRC
}
@@ -295,6 +297,7 @@ func applyExtensionRequestFallbacks(resp *DownloadResponse, req DownloadRequest)
overlayInt(&resp.DiscNumber, req.DiscNumber, "")
overlayInt(&resp.TotalDiscs, req.TotalDiscs, "")
overlayStr(&resp.CoverURL, req.CoverURL, "")
overlayStr(&resp.Comment, req.Comment, "")
}
func shouldStopProviderFallback(availability *ExtAvailabilityResult) bool {
+1
View File
@@ -170,6 +170,7 @@ func embedExtensionDownloadMetadata(resp DownloadResponse, req DownloadRequest,
Label: firstNonEmptyTrimmed(resp.Label, req.Label),
Copyright: firstNonEmptyTrimmed(resp.Copyright, req.Copyright),
Composer: firstNonEmptyTrimmed(resp.Composer, req.Composer),
Comment: firstNonEmptyTrimmed(resp.Comment, req.Comment),
}
if req.EmbedLyrics {
metadata.Lyrics = resp.LyricsLRC
+2
View File
@@ -184,6 +184,7 @@ func parseExtensionTrackValue(vm *goja.Runtime, value goja.Value) ExtTrackMetada
Copyright: gojaObjectString(obj, "copyright"),
Genre: gojaObjectString(obj, "genre"),
Composer: gojaObjectString(obj, "composer"),
Comment: gojaObjectString(obj, "comment", "comments"),
AudioQuality: gojaObjectString(obj, "audio_quality", "audioQuality"),
AudioModes: gojaObjectString(obj, "audio_modes", "audioModes"),
}
@@ -504,6 +505,7 @@ func parseExtensionDownloadResultValue(vm *goja.Runtime, value goja.Value) ExtDo
Label: gojaObjectString(obj, "label"),
Copyright: gojaObjectString(obj, "copyright"),
Composer: gojaObjectString(obj, "composer"),
Comment: gojaObjectString(obj, "comment", "comments"),
LyricsLRC: gojaObjectString(obj, "lyrics_lrc", "lyricsLrc"),
DecryptionKey: gojaObjectString(obj, "decryption_key", "decryptionKey"),
Decryption: parseExtensionDownloadDecryptionValue(vm, gojaObjectValue(obj, "decryption")),
@@ -21,7 +21,7 @@ func TestExtensionProviderWrapperFullSurface(t *testing.T) {
if err != nil {
t.Fatalf("GetTrack: %v", err)
}
if track.Name != "Track track-1" || track.ProviderID != ext.ID || track.AudioQuality == "" {
if track.Name != "Track track-1" || track.ProviderID != ext.ID || track.AudioQuality == "" || track.Comment != "https://example.test/album/1" {
t.Fatalf("track = %#v", track)
}
@@ -72,7 +72,7 @@ func TestExtensionProviderWrapperFullSurface(t *testing.T) {
if err != nil {
t.Fatalf("Download: %v", err)
}
if !download.Success || download.Decryption == nil || download.DecryptionKey != "001122" || len(progress) != 1 || progress[0] != 100 {
if !download.Success || download.Decryption == nil || download.DecryptionKey != "001122" || download.Comment != "https://example.test/album/1" || len(progress) != 1 || progress[0] != 100 {
t.Fatalf("download = %#v progress=%v", download, progress)
}
+2
View File
@@ -38,6 +38,7 @@ type ExtTrackMetadata struct {
Copyright string `json:"copyright,omitempty"`
Genre string `json:"genre,omitempty"`
Composer string `json:"composer,omitempty"`
Comment string `json:"comment,omitempty"`
AudioQuality string `json:"audio_quality,omitempty"`
AudioModes string `json:"audio_modes,omitempty"`
@@ -127,6 +128,7 @@ type ExtDownloadResult struct {
Label string `json:"label,omitempty"`
Copyright string `json:"copyright,omitempty"`
Composer string `json:"composer,omitempty"`
Comment string `json:"comment,omitempty"`
LyricsLRC string `json:"lyrics_lrc,omitempty"`
DecryptionKey string `json:"decryption_key,omitempty"`
Decryption *DownloadDecryptionInfo `json:"decryption,omitempty"`
+20
View File
@@ -27,6 +27,10 @@ class Track {
final String? albumType;
final int? totalTracks;
final String? composer;
final String? genre;
final String? label;
final String? copyright;
final String? comment;
final String? itemType;
final String? audioQuality;
final String? audioModes;
@@ -54,6 +58,10 @@ class Track {
this.albumType,
this.totalTracks,
this.composer,
this.genre,
this.label,
this.copyright,
this.comment,
this.itemType,
this.audioQuality,
this.audioModes,
@@ -128,6 +136,10 @@ class Track {
source: effectiveSource,
albumType: normalizeOptionalString(data['album_type']?.toString()),
composer: data['composer']?.toString(),
genre: data['genre']?.toString(),
label: data['label']?.toString(),
copyright: data['copyright']?.toString(),
comment: data['comment']?.toString(),
itemType: itemType,
audioQuality: data['audio_quality']?.toString(),
audioModes: data['audio_modes']?.toString(),
@@ -158,6 +170,10 @@ class Track {
String? albumType,
int? totalTracks,
String? composer,
String? genre,
String? label,
String? copyright,
String? comment,
String? itemType,
String? audioQuality,
String? audioModes,
@@ -185,6 +201,10 @@ class Track {
albumType: albumType ?? this.albumType,
totalTracks: totalTracks ?? this.totalTracks,
composer: composer ?? this.composer,
genre: genre ?? this.genre,
label: label ?? this.label,
copyright: copyright ?? this.copyright,
comment: comment ?? this.comment,
itemType: itemType ?? this.itemType,
audioQuality: audioQuality ?? this.audioQuality,
audioModes: audioModes ?? this.audioModes,
+8
View File
@@ -32,6 +32,10 @@ Track _$TrackFromJson(Map<String, dynamic> json) => Track(
albumType: json['albumType'] as String?,
totalTracks: (json['totalTracks'] as num?)?.toInt(),
composer: json['composer'] as String?,
genre: json['genre'] as String?,
label: json['label'] as String?,
copyright: json['copyright'] as String?,
comment: json['comment'] as String?,
itemType: json['itemType'] as String?,
audioQuality: json['audioQuality'] as String?,
audioModes: json['audioModes'] as String?,
@@ -60,6 +64,10 @@ Map<String, dynamic> _$TrackToJson(Track instance) => <String, dynamic>{
'albumType': instance.albumType,
'totalTracks': instance.totalTracks,
'composer': instance.composer,
'genre': instance.genre,
'label': instance.label,
'copyright': instance.copyright,
'comment': instance.comment,
'itemType': instance.itemType,
'audioQuality': instance.audioQuality,
'audioModes': instance.audioModes,
+4 -3
View File
@@ -721,10 +721,11 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
itemId: item.id,
durationMs: track.duration * 1000,
source: track.source ?? '',
genre: genre ?? '',
label: label ?? '',
copyright: copyright ?? '',
genre: genre ?? track.genre ?? '',
label: label ?? track.label ?? '',
copyright: copyright ?? track.copyright ?? '',
composer: track.composer ?? '',
comment: track.comment ?? '',
qobuzId: payloadQobuzId,
tidalId: payloadTidalId,
deezerId: deezerTrackId ?? '',
@@ -59,6 +59,27 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
);
}
bool _shouldSkipMetadataEnrichment(
ExtensionState extensionState,
String? source,
String? service,
) {
final candidates = <String>{};
for (final value in [source, service]) {
final normalized = value?.trim().toLowerCase();
if (normalized != null && normalized.isNotEmpty) {
candidates.add(normalized);
}
}
if (candidates.isEmpty) return false;
return extensionState.extensions.any(
(extension) =>
extension.enabled &&
extension.skipMetadataEnrichment &&
candidates.contains(extension.id.toLowerCase()),
);
}
String? _extractKnownDeezerTrackId(Track track) {
final deezerId = track.deezerId?.trim();
if (deezerId != null && deezerId.isNotEmpty) {
@@ -155,7 +176,14 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
composer: (track.composer != null && track.composer!.isNotEmpty)
? track.composer
: normalizedComposer,
genre: track.genre,
label: track.label,
copyright: track.copyright,
comment: track.comment,
itemType: track.itemType,
audioQuality: track.audioQuality,
audioModes: track.audioModes,
explicit: track.explicit,
);
}
@@ -482,6 +510,18 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
final sourceIsrc = normalizeOptionalString(baseTrack.isrc);
final sourceReleaseDate = normalizeOptionalString(baseTrack.releaseDate);
final sourceComposer = normalizeOptionalString(baseTrack.composer);
final backendGenre = normalizeOptionalString(
backendResult['genre']?.toString(),
);
final backendLabel = normalizeOptionalString(
backendResult['label']?.toString(),
);
final backendCopyright = normalizeOptionalString(
backendResult['copyright']?.toString(),
);
final backendComment = normalizeOptionalString(
backendResult['comment']?.toString(),
);
final resolvedTotalTracks = _resolvePositiveMetadataInt(
baseTrack.totalTracks,
backendTotalTracks,
@@ -541,7 +581,15 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
albumType: baseTrack.albumType,
totalTracks: resolvedTotalTracks,
composer: sourceComposer ?? backendComposer,
genre: baseTrack.genre ?? backendGenre,
label: baseTrack.label ?? backendLabel,
copyright: baseTrack.copyright ?? backendCopyright,
comment: baseTrack.comment ?? backendComment,
source: baseTrack.source,
itemType: baseTrack.itemType,
audioQuality: baseTrack.audioQuality,
audioModes: baseTrack.audioModes,
explicit: baseTrack.explicit,
);
}
@@ -559,6 +607,7 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
String? genre,
String? label,
String? copyright,
String? comment,
String? downloadService,
bool writeExternalLrc = true,
}) async {
@@ -573,6 +622,10 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
final isFlac = format == 'flac';
final isM4a = format == 'm4a';
final isMp3 = format == 'mp3';
final resolvedGenre = genre ?? track.genre;
final resolvedLabel = label ?? track.label;
final resolvedCopyright = copyright ?? track.copyright;
final resolvedComment = comment ?? track.comment;
Future<String?>? coverFuture;
final coverUrl = normalizeRemoteHttpUrl(track.coverUrl);
@@ -622,10 +675,17 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
}
}
if (track.isrc != null) metadata['ISRC'] = track.isrc!;
if (genre != null && genre.isNotEmpty) metadata['GENRE'] = genre;
if (label != null && label.isNotEmpty) metadata['ORGANIZATION'] = label;
if (copyright != null && copyright.isNotEmpty) {
metadata['COPYRIGHT'] = copyright;
if (resolvedGenre != null && resolvedGenre.isNotEmpty) {
metadata['GENRE'] = resolvedGenre;
}
if (resolvedLabel != null && resolvedLabel.isNotEmpty) {
metadata['ORGANIZATION'] = resolvedLabel;
}
if (resolvedCopyright != null && resolvedCopyright.isNotEmpty) {
metadata['COPYRIGHT'] = resolvedCopyright;
}
if (resolvedComment != null && resolvedComment.isNotEmpty) {
metadata['COMMENT'] = resolvedComment;
}
if (track.composer != null && track.composer!.isNotEmpty) {
metadata['COMPOSER'] = track.composer!;
@@ -727,7 +787,8 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
'album': track.albumName,
'albumArtist': ?albumArtist,
if (track.releaseDate != null) 'date': track.releaseDate!,
if (genre != null && genre.isNotEmpty) 'genre': genre,
if (resolvedGenre != null && resolvedGenre.isNotEmpty)
'genre': resolvedGenre,
if (track.composer != null && track.composer!.isNotEmpty)
'composer': track.composer!,
if (track.trackNumber != null && track.trackNumber! > 0)
@@ -739,9 +800,12 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
if (track.totalDiscs != null && track.totalDiscs! > 0)
'totalDiscs': track.totalDiscs!.toString(),
if (track.isrc != null) 'isrc': track.isrc!,
if (label != null && label.isNotEmpty) 'label': label,
if (copyright != null && copyright.isNotEmpty)
'copyright': copyright,
if (resolvedLabel != null && resolvedLabel.isNotEmpty)
'label': resolvedLabel,
if (resolvedCopyright != null && resolvedCopyright.isNotEmpty)
'copyright': resolvedCopyright,
if (resolvedComment != null && resolvedComment.isNotEmpty)
'comment': resolvedComment,
if (shouldEmbedLyrics) 'lyrics': ?lrcContent,
};
final ac4Result = await PlatformBridge.writeAC4Metadata(
@@ -777,10 +841,14 @@ extension _DownloadQueueEmbedding on DownloadQueueNotifier {
if (track.isrc != null) 'isrc': track.isrc!,
if (track.composer != null && track.composer!.isNotEmpty)
'composer': track.composer!,
if (genre != null && genre.isNotEmpty) 'genre': genre,
if (label != null && label.isNotEmpty) 'label': label,
if (copyright != null && copyright.isNotEmpty)
'copyright': copyright,
if (resolvedGenre != null && resolvedGenre.isNotEmpty)
'genre': resolvedGenre,
if (resolvedLabel != null && resolvedLabel.isNotEmpty)
'label': resolvedLabel,
if (resolvedCopyright != null && resolvedCopyright.isNotEmpty)
'copyright': resolvedCopyright,
if (resolvedComment != null && resolvedComment.isNotEmpty)
'comment': resolvedComment,
if (track.trackNumber != null && track.trackNumber! > 0)
'track_number': track.trackNumber!.toString(),
if (track.totalTracks != null && track.totalTracks! > 0)
@@ -148,6 +148,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
genre: result['genre'] as String?,
label: result['label'] as String?,
copyright: result['copyright'] as String?,
comment: result['comment'] as String?,
downloadService: downloadService,
writeExternalLrc: storageMode != 'saf',
);
@@ -952,6 +953,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
genre: result['genre'] as String?,
label: result['label'] as String?,
copyright: result['copyright'] as String?,
comment: result['comment'] as String?,
downloadService: context.item.service,
);
}
@@ -1088,6 +1090,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
genre: result['genre'] as String?,
label: result['label'] as String?,
copyright: result['copyright'] as String?,
comment: result['comment'] as String?,
downloadService: context.item.service,
writeExternalLrc: context.storageMode != 'saf',
);
@@ -790,24 +790,34 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
}
var trackForPayload = item.track;
String? nativeDeezerTrackId = await _resolveDeezerIdFromKnownOrIsrc(
trackForPayload,
item.id,
lookupContext: 'native worker ISRC',
);
final providerResolved = await _resolveDeezerIdViaProviderIfNeeded(
trackForPayload,
nativeDeezerTrackId,
item.id,
);
trackForPayload = providerResolved.track;
nativeDeezerTrackId = providerResolved.deezerTrackId;
final extendedMetadata = await _loadExtendedMetadataForDeezerId(
nativeDeezerTrackId,
);
final extensionState = ref.read(extensionProvider);
final skipMetadataEnrichment = _shouldSkipMetadataEnrichment(
extensionState,
trackForPayload.source,
item.service,
);
String? nativeDeezerTrackId;
if (skipMetadataEnrichment) {
nativeDeezerTrackId = _extractKnownDeezerTrackId(trackForPayload);
} else {
nativeDeezerTrackId = await _resolveDeezerIdFromKnownOrIsrc(
trackForPayload,
item.id,
lookupContext: 'native worker ISRC',
);
final providerResolved = await _resolveDeezerIdViaProviderIfNeeded(
trackForPayload,
nativeDeezerTrackId,
item.id,
);
trackForPayload = providerResolved.track;
nativeDeezerTrackId = providerResolved.deezerTrackId;
}
final extendedMetadata = skipMetadataEnrichment
? null
: await _loadExtendedMetadataForDeezerId(nativeDeezerTrackId);
final payload = _buildDownloadRequestPayload(
track: trackForPayload,
item: item,
@@ -107,6 +107,7 @@ class _DownloadRun {
late ExtensionState extensionState;
late bool selectedExtensionDownloadProvider;
late bool shouldSkipExtensionSongLinkPrelookup;
late bool shouldSkipMetadataEnrichment;
late bool useExtensions;
String? deezerTrackId;
@@ -193,12 +194,14 @@ class _DownloadRun {
// copy is just echoed back in the response), so this lookup overlaps
// with the download instead of delaying its start; it is awaited right
// after the download returns.
final extendedMetadataFuture = n
._loadExtendedMetadataForDeezerId(deezerTrackId)
.catchError((Object e) {
_log.w('Extended metadata lookup failed: $e');
return null;
});
final extendedMetadataFuture = shouldSkipMetadataEnrichment
? Future<_DeezerExtendedMetadataFields?>.value(null)
: n._loadExtendedMetadataForDeezerId(deezerTrackId).catchError((
Object e,
) {
_log.w('Extended metadata lookup failed: $e');
return null;
});
if (await _shouldAbort('before native download start')) {
return;
@@ -350,7 +353,16 @@ class _DownloadRun {
(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,
source: trackToDownload.source,
itemType: trackToDownload.itemType,
audioQuality: trackToDownload.audioQuality,
audioModes: trackToDownload.audioModes,
explicit: trackToDownload.explicit,
);
_log.d(
'Metadata enriched: Track ${trackToDownload.trackNumber}, Disc ${trackToDownload.discNumber}, ISRC ${trackToDownload.isrc}, AlbumType ${trackToDownload.albumType}',
@@ -456,11 +468,21 @@ class _DownloadRun {
e.hasMetadataProvider &&
e.id.toLowerCase() == trackSource,
);
shouldSkipMetadataEnrichment = n._shouldSkipMetadataEnrichment(
extensionState,
trackToDownload.source,
item.service,
);
final hasActiveExtensions = extensionState.extensions.any((e) => e.enabled);
useExtensions = settings.useExtensionProviders && hasActiveExtensions;
}
Future<bool> _resolveTrackIdentifiers() async {
if (shouldSkipMetadataEnrichment) {
deezerTrackId = n._extractKnownDeezerTrackId(trackToDownload);
_log.d('Skipping cross-provider metadata enrichment for ${item.service}');
return true;
}
deezerTrackId = await n._resolveDeezerIdFromKnownOrIsrc(
trackToDownload,
item.id,
@@ -1451,6 +1473,7 @@ class _DownloadRun {
genre: (result['genre'] as String?) ?? genre,
label: (result['label'] as String?) ?? label,
copyright: result['copyright'] as String?,
comment: result['comment'] as String?,
downloadService: item.service,
writeExternalLrc: writeExternalLrc,
);
@@ -38,6 +38,7 @@ class DownloadRequestPayload {
final String label;
final String copyright;
final String composer;
final String comment;
final String tidalId;
final String qobuzId;
final String deezerId;
@@ -96,6 +97,7 @@ class DownloadRequestPayload {
this.label = '',
this.copyright = '',
this.composer = '',
this.comment = '',
this.tidalId = '',
this.qobuzId = '',
this.deezerId = '',
@@ -156,6 +158,7 @@ class DownloadRequestPayload {
'label': label,
'copyright': copyright,
'composer': composer,
'comment': comment,
'tidal_id': tidalId,
'qobuz_id': qobuzId,
'deezer_id': deezerId,
@@ -220,6 +223,7 @@ class DownloadRequestPayload {
label: label,
copyright: copyright,
composer: composer,
comment: comment,
tidalId: tidalId,
qobuzId: qobuzId,
deezerId: deezerId,
+24
View File
@@ -443,6 +443,28 @@ void main() {
expect(track.isExplicit, isTrue);
});
test('preserves extension tagging metadata', () {
final track = Track.fromBackendMap({
'id': 'extension-track-1',
'name': 'Song',
'artists': 'Artist',
'album_name': 'Album',
'duration_ms': 180000,
'genre': 'Pop',
'label': 'Label',
'copyright': 'Copyright',
'composer': 'Composer',
'comment': 'https://example.test/album/1',
});
expect(track.genre, 'Pop');
expect(track.label, 'Label');
expect(track.copyright, 'Copyright');
expect(track.composer, 'Composer');
expect(track.comment, 'https://example.test/album/1');
expect(track.toJson()['comment'], 'https://example.test/album/1');
});
test('does not treat a playlist container name as a track album', () {
final playlistTrack = Track.fromBackendMap({
'id': 'playlist-track-1',
@@ -855,6 +877,7 @@ void main() {
label: 'Label',
copyright: 'Copyright',
composer: 'Composer',
comment: 'https://example.test/album/1',
tidalId: 'tidal-1',
qobuzId: 'qobuz-1',
deezerId: 'deezer-1',
@@ -911,6 +934,7 @@ void main() {
'label': 'Label',
'copyright': 'Copyright',
'composer': 'Composer',
'comment': 'https://example.test/album/1',
'tidal_id': 'tidal-1',
'qobuz_id': 'qobuz-1',
'deezer_id': 'deezer-1',