diff --git a/go_backend/extension_manager.go b/go_backend/extension_manager.go index 38b5451a..38656584 100644 --- a/go_backend/extension_manager.go +++ b/go_backend/extension_manager.go @@ -908,6 +908,7 @@ func (m *ExtensionManager) GetInstalledExtensionsJSON() (string, error) { HasDownloadProvider bool `json:"has_download_provider"` HasLyricsProvider bool `json:"has_lyrics_provider"` SkipMetadataEnrichment bool `json:"skip_metadata_enrichment"` + SkipLyrics bool `json:"skip_lyrics"` SearchBehavior *SearchBehaviorConfig `json:"search_behavior,omitempty"` TrackMatching *TrackMatchingConfig `json:"track_matching,omitempty"` PostProcessing *PostProcessingConfig `json:"post_processing,omitempty"` @@ -965,6 +966,7 @@ func (m *ExtensionManager) GetInstalledExtensionsJSON() (string, error) { HasDownloadProvider: ext.Manifest.IsDownloadProvider(), HasLyricsProvider: ext.Manifest.IsLyricsProvider(), SkipMetadataEnrichment: ext.Manifest.SkipMetadataEnrichment, + SkipLyrics: ext.Manifest.SkipLyrics, SearchBehavior: ext.Manifest.SearchBehavior, TrackMatching: ext.Manifest.TrackMatching, PostProcessing: ext.Manifest.PostProcessing, diff --git a/go_backend/extension_manifest.go b/go_backend/extension_manifest.go index 6166a667..6d11aca6 100644 --- a/go_backend/extension_manifest.go +++ b/go_backend/extension_manifest.go @@ -115,6 +115,7 @@ type ExtensionManifest struct { QualityOptions []QualityOption `json:"qualityOptions,omitempty"` MinAppVersion string `json:"minAppVersion,omitempty"` SkipMetadataEnrichment bool `json:"skipMetadataEnrichment,omitempty"` + SkipLyrics bool `json:"skipLyrics,omitempty"` SkipBuiltInFallback bool `json:"skipBuiltInFallback,omitempty"` SearchBehavior *SearchBehaviorConfig `json:"searchBehavior,omitempty"` URLHandler *URLHandlerConfig `json:"urlHandler,omitempty"` diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index aa2b4657..367a8043 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -2215,6 +2215,27 @@ class DownloadQueueNotifier extends Notifier { return _isrcRegex.hasMatch(value.toUpperCase()); } + /// Returns true if any enabled extension matching [source] or [service] + /// declares `skipLyrics: true` in its manifest. + bool _shouldSkipLyrics( + ExtensionState extensionState, + String? source, + String? service, + ) { + final candidates = {}; + if (source != null && source.isNotEmpty) { + candidates.add(source.trim().toLowerCase()); + } + if (service != null && service.isNotEmpty) { + candidates.add(service.trim().toLowerCase()); + } + if (candidates.isEmpty) return false; + return extensionState.extensions.any( + (e) => + e.enabled && e.skipLyrics && candidates.contains(e.id.toLowerCase()), + ); + } + String _newQueueItemId(Track track, {Set? takenIds}) { final trimmedIsrc = track.isrc?.trim(); final trimmedTrackId = track.id.trim(); @@ -3227,6 +3248,7 @@ class DownloadQueueNotifier extends Notifier { String? genre, String? label, String? copyright, + String? downloadService, bool writeExternalLrc = true, }) async { final settings = ref.read(settingsProvider); @@ -3314,11 +3336,19 @@ class DownloadQueueNotifier extends Notifier { _log.d('Metadata map content: $metadata'); final lyricsMode = settings.lyricsMode; + final extensionState = ref.read(extensionProvider); + final skipLyrics = _shouldSkipLyrics( + extensionState, + track.source, + downloadService, + ); final shouldEmbedLyrics = settings.embedLyrics && + !skipLyrics && (lyricsMode == 'embed' || lyricsMode == 'both'); final shouldSaveExternalLyrics = settings.embedLyrics && + !skipLyrics && (lyricsMode == 'external' || lyricsMode == 'both'); final shouldFetchLyrics = shouldEmbedLyrics || shouldSaveExternalLyrics; String? lrcContent; @@ -3453,6 +3483,7 @@ class DownloadQueueNotifier extends Notifier { String? genre, String? label, String? copyright, + String? downloadService, }) async { final settings = ref.read(settingsProvider); if (!settings.embedMetadata) { @@ -3541,9 +3572,16 @@ class DownloadQueueNotifier extends Notifier { _log.d('MP3 Metadata map content: $metadata'); final lyricsMode = settings.lyricsMode; - final shouldEmbed = lyricsMode == 'embed' || lyricsMode == 'both'; + final mp3ExtState = ref.read(extensionProvider); + final mp3SkipLyrics = _shouldSkipLyrics( + mp3ExtState, + track.source, + downloadService, + ); + final shouldEmbed = + !mp3SkipLyrics && (lyricsMode == 'embed' || lyricsMode == 'both'); final shouldSaveExternal = - lyricsMode == 'external' || lyricsMode == 'both'; + !mp3SkipLyrics && (lyricsMode == 'external' || lyricsMode == 'both'); if (settings.embedLyrics && (shouldEmbed || shouldSaveExternal)) { try { @@ -3639,6 +3677,7 @@ class DownloadQueueNotifier extends Notifier { String? genre, String? label, String? copyright, + String? downloadService, }) async { final settings = ref.read(settingsProvider); if (!settings.embedMetadata) { @@ -3724,9 +3763,16 @@ class DownloadQueueNotifier extends Notifier { _log.d('Opus Metadata map content: $metadata'); final lyricsMode = settings.lyricsMode; - final shouldEmbed = lyricsMode == 'embed' || lyricsMode == 'both'; + final opusExtState = ref.read(extensionProvider); + final opusSkipLyrics = _shouldSkipLyrics( + opusExtState, + track.source, + downloadService, + ); + final shouldEmbed = + !opusSkipLyrics && (lyricsMode == 'embed' || lyricsMode == 'both'); final shouldSaveExternal = - lyricsMode == 'external' || lyricsMode == 'both'; + !opusSkipLyrics && (lyricsMode == 'external' || lyricsMode == 'both'); if (settings.embedLyrics && (shouldEmbed || shouldSaveExternal)) { try { @@ -4685,7 +4731,14 @@ class DownloadQueueNotifier extends Notifier { quality: quality, embedMetadata: metadataEmbeddingEnabled, artistTagMode: settings.artistTagMode, - embedLyrics: metadataEmbeddingEnabled && settings.embedLyrics, + embedLyrics: + metadataEmbeddingEnabled && + settings.embedLyrics && + !_shouldSkipLyrics( + extensionState, + trackToDownload.source, + item.service, + ), embedMaxQualityCover: metadataEmbeddingEnabled && settings.maxQualityCover, trackNumber: normalizedTrackNumber, @@ -5010,6 +5063,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, ); } else { await _embedMetadataToOpus( @@ -5018,6 +5072,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, ); } @@ -5104,6 +5159,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, writeExternalLrc: false, ); @@ -5197,6 +5253,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, ); } else { await _embedMetadataToOpus( @@ -5205,6 +5262,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, ); } _log.d('Metadata embedded successfully'); @@ -5275,6 +5333,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, ); _log.d('Metadata and cover embedded successfully'); } catch (e) { @@ -5340,6 +5399,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, ); } else if (isOpusFile) { await _embedMetadataToOpus( @@ -5348,6 +5408,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, ); } else { await _embedMetadataAndCover( @@ -5356,6 +5417,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, writeExternalLrc: false, ); } @@ -5420,6 +5482,7 @@ class DownloadQueueNotifier extends Notifier { genre: backendGenre ?? genre, label: backendLabel ?? label, copyright: backendCopyright, + downloadService: item.service, ); _log.d('Local FLAC metadata embedding completed'); } catch (e) { @@ -5500,6 +5563,11 @@ class DownloadQueueNotifier extends Notifier { final shouldSaveExternalLrc = metadataEmbeddingEnabled && settings.embedLyrics && + !_shouldSkipLyrics( + extensionState, + trackToDownload.source, + item.service, + ) && (lyricsMode == 'external' || lyricsMode == 'both'); if (shouldSaveExternalLrc && effectiveSafMode && diff --git a/lib/providers/extension_provider.dart b/lib/providers/extension_provider.dart index 5c7426fd..81bce27b 100644 --- a/lib/providers/extension_provider.dart +++ b/lib/providers/extension_provider.dart @@ -33,6 +33,7 @@ class Extension { final bool hasDownloadProvider; final bool hasLyricsProvider; final bool skipMetadataEnrichment; + final bool skipLyrics; final SearchBehavior? searchBehavior; final URLHandler? urlHandler; final TrackMatching? trackMatching; @@ -57,6 +58,7 @@ class Extension { this.hasDownloadProvider = false, this.hasLyricsProvider = false, this.skipMetadataEnrichment = false, + this.skipLyrics = false, this.searchBehavior, this.urlHandler, this.trackMatching, @@ -94,6 +96,7 @@ class Extension { hasLyricsProvider: json['has_lyrics_provider'] as bool? ?? false, skipMetadataEnrichment: json['skip_metadata_enrichment'] as bool? ?? false, + skipLyrics: json['skip_lyrics'] as bool? ?? false, searchBehavior: json['search_behavior'] != null ? SearchBehavior.fromJson( json['search_behavior'] as Map, @@ -134,6 +137,7 @@ class Extension { bool? hasDownloadProvider, bool? hasLyricsProvider, bool? skipMetadataEnrichment, + bool? skipLyrics, SearchBehavior? searchBehavior, URLHandler? urlHandler, TrackMatching? trackMatching, @@ -159,6 +163,7 @@ class Extension { hasLyricsProvider: hasLyricsProvider ?? this.hasLyricsProvider, skipMetadataEnrichment: skipMetadataEnrichment ?? this.skipMetadataEnrichment, + skipLyrics: skipLyrics ?? this.skipLyrics, searchBehavior: searchBehavior ?? this.searchBehavior, urlHandler: urlHandler ?? this.urlHandler, trackMatching: trackMatching ?? this.trackMatching,