feat: add skipLyrics manifest field for extensions to opt out of lyrics fetching

This commit is contained in:
zarzet 2026-04-03 03:14:51 +07:00
parent 59f2fe880a
commit ac711efadc
4 changed files with 81 additions and 5 deletions

View file

@ -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,

View file

@ -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"`

View file

@ -2215,6 +2215,27 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
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 = <String>{};
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<String>? takenIds}) {
final trimmedIsrc = track.isrc?.trim();
final trimmedTrackId = track.id.trim();
@ -3227,6 +3248,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
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<DownloadQueueState> {
_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<DownloadQueueState> {
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<DownloadQueueState> {
_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<DownloadQueueState> {
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<DownloadQueueState> {
_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<DownloadQueueState> {
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<DownloadQueueState> {
genre: backendGenre ?? genre,
label: backendLabel ?? label,
copyright: backendCopyright,
downloadService: item.service,
);
} else {
await _embedMetadataToOpus(
@ -5018,6 +5072,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
genre: backendGenre ?? genre,
label: backendLabel ?? label,
copyright: backendCopyright,
downloadService: item.service,
);
}
@ -5104,6 +5159,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
genre: backendGenre ?? genre,
label: backendLabel ?? label,
copyright: backendCopyright,
downloadService: item.service,
writeExternalLrc: false,
);
@ -5197,6 +5253,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
genre: backendGenre ?? genre,
label: backendLabel ?? label,
copyright: backendCopyright,
downloadService: item.service,
);
} else {
await _embedMetadataToOpus(
@ -5205,6 +5262,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
genre: backendGenre ?? genre,
label: backendLabel ?? label,
copyright: backendCopyright,
downloadService: item.service,
);
}
_log.d('Metadata embedded successfully');
@ -5275,6 +5333,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
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<DownloadQueueState> {
genre: backendGenre ?? genre,
label: backendLabel ?? label,
copyright: backendCopyright,
downloadService: item.service,
);
} else if (isOpusFile) {
await _embedMetadataToOpus(
@ -5348,6 +5408,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
genre: backendGenre ?? genre,
label: backendLabel ?? label,
copyright: backendCopyright,
downloadService: item.service,
);
} else {
await _embedMetadataAndCover(
@ -5356,6 +5417,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
genre: backendGenre ?? genre,
label: backendLabel ?? label,
copyright: backendCopyright,
downloadService: item.service,
writeExternalLrc: false,
);
}
@ -5420,6 +5482,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
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<DownloadQueueState> {
final shouldSaveExternalLrc =
metadataEmbeddingEnabled &&
settings.embedLyrics &&
!_shouldSkipLyrics(
extensionState,
trackToDownload.source,
item.service,
) &&
(lyricsMode == 'external' || lyricsMode == 'both');
if (shouldSaveExternalLrc &&
effectiveSafMode &&

View file

@ -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<String, dynamic>,
@ -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,