feat(metadata): add a primary artist tag mode

Artist Tag Mode gains "Primary artist only": downloads and re-enrichment write only the first credited artist to Artist and Album Artist, so players list one artist. The FFmpeg MP3, M4A and Vorbis embeds and the AC-4 path now honor the mode; MP3 and M4A previously ignored it.

Manual metadata edits, lyrics embedding and format conversion keep existing or typed credits joined instead of dropping names. The picker scrolls so three options fit on small and landscape screens. Closes #618.
This commit is contained in:
zarzet committed 2026-10-01 16:12:58 +07:00
1 parent 41340a4a21
commit a549671c4e
16 files changed
+358 -59

No files matched your search

+1 -1
View File
@@ -406,7 +406,7 @@ class AudioMetadataMapper {
}
if (!shouldSplitVorbisArtistTags(artistTagMode)) {
entries.add(MapEntry(key, value));
entries.add(MapEntry(key, artistTagValueForMode(value, artistTagMode)));
return;
}
+16
View File
@@ -1343,8 +1343,13 @@ class FFmpegService {
required String mp3Path,
String? coverPath,
Map<String, String>? metadata,
String artistTagMode = artistTagModeJoined,
bool preserveMetadata = false,
}) async {
// ID3 keeps one artist frame: only the primary mode changes its value.
if (metadata != null) {
metadata = applyArtistTagModeToMetadata(metadata, artistTagMode);
}
final tempDir = await getTemporaryDirectory();
final tempOutput = _nextTempEmbedPath(tempDir.path, '.mp3');
final lyrics = AudioMetadataMapper.extractLyricsForId3(metadata);
@@ -1614,8 +1619,13 @@ class FFmpegService {
required String m4aPath,
String? coverPath,
Map<String, String>? metadata,
String artistTagMode = artistTagModeJoined,
bool preserveMetadata = true,
}) async {
// MP4 keeps one artist atom: only the primary mode changes its value.
if (metadata != null) {
metadata = applyArtistTagModeToMetadata(metadata, artistTagMode);
}
final tempDir = await getTemporaryDirectory();
final tempOutput = _nextTempEmbedPath(tempDir.path, '.m4a');
@@ -1866,6 +1876,10 @@ class FFmpegService {
_log.e('Unsupported target format: $targetFormat');
return null;
}
// Conversion carries existing tags over. Primary mode applies to provider
// credits (download, re-enrichment) and must not drop names here; the
// download auto-conversion embeds its credits after this step.
artistTagMode = artistTagModeForExistingTags(artistTagMode);
final resolvedLosslessQuality = isLosslessConversionTarget(format)
? await _resolveLosslessQuality(
@@ -1947,12 +1961,14 @@ class FFmpegService {
mp3Path: outputPath,
coverPath: coverPath,
metadata: metadata,
artistTagMode: artistTagMode,
);
} else if (format == 'aac') {
embedResult = await embedMetadataToM4a(
m4aPath: outputPath,
coverPath: coverPath,
metadata: metadata,
artistTagMode: artistTagMode,
preserveMetadata: true,
);
} else {