mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-27 21:30:23 +02:00
feat(extensions): preserve provider metadata
This commit is contained in:
@@ -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 "",
|
||||
)
|
||||
|
||||
@@ -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" } }
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user