mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-23 01:50:44 +02:00
feat(metadata): add safe batch editing and matching
This commit is contained in:
@@ -20,13 +20,34 @@ class ReEnrichFields {
|
||||
];
|
||||
}
|
||||
|
||||
enum ReEnrichBatchMode { isrcOnly, missingOnly, selectedFields }
|
||||
enum ReEnrichBatchMode { isrcOnly, missingOnly, selectedFields, manualValues }
|
||||
|
||||
/// Tags where applying one shared value to multiple tracks is normally safe.
|
||||
/// Per-track identifiers, titles, and track/disc numbers are deliberately
|
||||
/// excluded so the batch editor cannot accidentally duplicate them.
|
||||
const List<String> manualBatchMetadataFields = [
|
||||
'artist_name',
|
||||
'album_name',
|
||||
'album_artist',
|
||||
'release_date',
|
||||
'genre',
|
||||
'composer',
|
||||
'label',
|
||||
'copyright',
|
||||
];
|
||||
|
||||
class ReEnrichFieldSelection {
|
||||
final ReEnrichBatchMode mode;
|
||||
final List<String> fields;
|
||||
final Map<String, String> manualValues;
|
||||
|
||||
const ReEnrichFieldSelection({required this.mode, this.fields = const []});
|
||||
const ReEnrichFieldSelection({
|
||||
required this.mode,
|
||||
this.fields = const [],
|
||||
this.manualValues = const {},
|
||||
});
|
||||
|
||||
bool get usesManualValues => mode == ReEnrichBatchMode.manualValues;
|
||||
|
||||
List<String> updateFieldsFor(LocalLibraryItem item) {
|
||||
switch (mode) {
|
||||
@@ -36,6 +57,15 @@ class ReEnrichFieldSelection {
|
||||
return fields;
|
||||
case ReEnrichBatchMode.missingOnly:
|
||||
return missingReEnrichFields(item);
|
||||
case ReEnrichBatchMode.manualValues:
|
||||
return manualValues.entries
|
||||
.where(
|
||||
(entry) =>
|
||||
manualBatchMetadataFields.contains(entry.key) &&
|
||||
entry.value.trim().isNotEmpty,
|
||||
)
|
||||
.map((entry) => entry.key)
|
||||
.toList(growable: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,6 +168,36 @@ class BatchReEnrichPreview {
|
||||
});
|
||||
}
|
||||
|
||||
BatchReEnrichPreview? buildManualBatchReEnrichPreview(
|
||||
LocalLibraryItem item,
|
||||
ReEnrichFieldSelection selection,
|
||||
) {
|
||||
if (!selection.usesManualValues) return null;
|
||||
|
||||
final enrichedMetadata = <String, dynamic>{
|
||||
for (final entry in selection.manualValues.entries)
|
||||
if (manualBatchMetadataFields.contains(entry.key) &&
|
||||
entry.value.trim().isNotEmpty)
|
||||
entry.key: entry.value.trim(),
|
||||
};
|
||||
final updateFields = enrichedMetadata.keys.toList(growable: false);
|
||||
if (updateFields.isEmpty) return null;
|
||||
|
||||
final changes = buildReEnrichMetadataChanges(
|
||||
item,
|
||||
enrichedMetadata,
|
||||
updateFields,
|
||||
);
|
||||
if (changes.isEmpty) return null;
|
||||
|
||||
return BatchReEnrichPreview(
|
||||
item: item,
|
||||
updateFields: updateFields,
|
||||
enrichedMetadata: enrichedMetadata,
|
||||
changes: changes,
|
||||
);
|
||||
}
|
||||
|
||||
String _displayValue(Object? value) {
|
||||
if (value == null) return '';
|
||||
if (value is num && value == 0) return '';
|
||||
|
||||
@@ -2,6 +2,11 @@ part of 'library_database.dart';
|
||||
|
||||
// SQL builders for the queue tab's history+local union queries.
|
||||
|
||||
String confirmedMissingLyricsSqlPredicate({
|
||||
required String hasLyricsExpr,
|
||||
required String lyricsKnownExpr,
|
||||
}) => '($lyricsKnownExpr) AND COALESCE($hasLyricsExpr, 0) = 0';
|
||||
|
||||
class _QueueOrderTerm {
|
||||
final String column;
|
||||
final bool descending;
|
||||
@@ -398,6 +403,7 @@ extension _LibraryDbQueueSql on LibraryDatabase {
|
||||
isrcExpr: 'h.isrc',
|
||||
labelExpr: 'h.label',
|
||||
hasLyricsExpr: 'h.has_lyrics',
|
||||
lyricsKnownExpr: 'COALESCE(h.lyrics_metadata_scan_version, 0) >= 1',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -441,6 +447,8 @@ extension _LibraryDbQueueSql on LibraryDatabase {
|
||||
isrcExpr: 'l.isrc',
|
||||
labelExpr: 'l.label',
|
||||
hasLyricsExpr: 'l.has_lyrics',
|
||||
lyricsKnownExpr:
|
||||
'COALESCE(l.audio_metadata_scan_version, 0) >= ${LibraryDatabase.audioMetadataScanVersion}',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -461,6 +469,7 @@ extension _LibraryDbQueueSql on LibraryDatabase {
|
||||
required String isrcExpr,
|
||||
required String labelExpr,
|
||||
required String hasLyricsExpr,
|
||||
required String lyricsKnownExpr,
|
||||
}) {
|
||||
final quality = request.quality?.trim().toLowerCase();
|
||||
if (quality != null && quality.isNotEmpty) {
|
||||
@@ -546,7 +555,14 @@ extension _LibraryDbQueueSql on LibraryDatabase {
|
||||
where.add('NOT ($hasLabel)');
|
||||
break;
|
||||
case 'missing-lyrics':
|
||||
where.add('COALESCE($hasLyricsExpr, 0) = 0');
|
||||
// A default false value on legacy rows means "not scanned yet", not
|
||||
// "confirmed missing". Only show files whose lyrics probe completed.
|
||||
where.add(
|
||||
confirmedMissingLyricsSqlPredicate(
|
||||
hasLyricsExpr: hasLyricsExpr,
|
||||
lyricsKnownExpr: lyricsKnownExpr,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user