From 2b117415ae1cb417d48954804fbd641e4eaf2a9c Mon Sep 17 00:00:00 2001 From: zarzet Date: Thu, 27 Aug 2026 16:50:46 +0700 Subject: [PATCH] feat: add file format library quality label --- lib/l10n/app_localizations.dart | 6 +++ lib/l10n/app_localizations_de.dart | 3 ++ lib/l10n/app_localizations_en.dart | 3 ++ lib/l10n/app_localizations_es.dart | 3 ++ lib/l10n/app_localizations_fr.dart | 3 ++ lib/l10n/app_localizations_id.dart | 3 ++ lib/l10n/app_localizations_ja.dart | 3 ++ lib/l10n/app_localizations_ko.dart | 3 ++ lib/l10n/app_localizations_pt.dart | 3 ++ lib/l10n/app_localizations_ru.dart | 3 ++ lib/l10n/app_localizations_tr.dart | 3 ++ lib/l10n/app_localizations_uk.dart | 3 ++ lib/l10n/arb/app_en.arb | 4 ++ lib/l10n/arb/app_id.arb | 1 + lib/models/settings.dart | 5 ++- lib/providers/settings_provider.dart | 2 + .../settings/library_settings_page.dart | 8 ++++ .../settings/settings_search_catalog.dart | 11 ++++- lib/utils/audio_quality_badge_policy.dart | 30 ++++++++++++- test/audio_quality_badge_policy_test.dart | 42 +++++++++++++++++++ 20 files changed, 137 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 56a0a46e..0b5601b6 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2206,6 +2206,12 @@ abstract class AppLocalizations { /// **'Audio quality'** String get trackAudioQuality; + /// Library audio quality label mode that shows the detected file format + /// + /// In en, this message translates to: + /// **'File format'** + String get libraryQualityLabelFileFormat; + /// Metadata label - release date /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index b928a16e..4fddacdc 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1203,6 +1203,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get trackAudioQuality => 'Audioqualität'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => 'Erscheinungsdatum'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index fa16e4b7..c5f619c0 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1188,6 +1188,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get trackAudioQuality => 'Audio quality'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => 'Release date'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 031e4dc2..c638277b 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1188,6 +1188,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get trackAudioQuality => 'Audio quality'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => 'Release date'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index b164e9c8..f87cec88 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1217,6 +1217,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get trackAudioQuality => 'Qualité audio'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => 'Date de sortie'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 0d3079c6..ffb3725c 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -1193,6 +1193,9 @@ class AppLocalizationsId extends AppLocalizations { @override String get trackAudioQuality => 'Kualitas audio'; + @override + String get libraryQualityLabelFileFormat => 'Format berkas'; + @override String get trackReleaseDate => 'Tanggal rilis'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 35f5bd25..6724583b 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1183,6 +1183,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get trackAudioQuality => 'オーディオ品質'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => 'リリース日'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 364dc364..af3e7053 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1162,6 +1162,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get trackAudioQuality => '오디오 음질'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => '발매일자'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 4e0a4163..4a529373 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1188,6 +1188,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get trackAudioQuality => 'Audio quality'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => 'Release date'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 8fe8ed55..fdc4cb0e 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1194,6 +1194,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get trackAudioQuality => 'Качество записи'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => 'Дата выхода'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index c341c237..1c64866b 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -1199,6 +1199,9 @@ class AppLocalizationsTr extends AppLocalizations { @override String get trackAudioQuality => 'Ses kalitesi'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => 'Yayın tarihi'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index bfca1089..307de286 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1203,6 +1203,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get trackAudioQuality => 'Якість звуку'; + @override + String get libraryQualityLabelFileFormat => 'File format'; + @override String get trackReleaseDate => 'Дата випуску'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 412ddd00..d456d3ad 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1568,6 +1568,10 @@ "@trackAudioQuality": { "description": "Metadata label - audio quality" }, + "libraryQualityLabelFileFormat": "File format", + "@libraryQualityLabelFileFormat": { + "description": "Library audio quality label mode that shows the detected file format" + }, "trackReleaseDate": "Release date", "@trackReleaseDate": { "description": "Metadata label - release date" diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index e9db3d0e..28ba0847 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -2164,6 +2164,7 @@ "@trackAudioQuality": { "description": "Metadata label - audio quality" }, + "libraryQualityLabelFileFormat": "Format berkas", "libraryRemovedMissingFiles": "Removed {count} missing files from library", "@libraryRemovedMissingFiles": { "description": "Snackbar after cleanup", diff --git a/lib/models/settings.dart b/lib/models/settings.dart index df26f0b3..3860628f 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -7,6 +7,7 @@ part 'settings.g.dart'; class AppSettings { static const String homeFeedProviderOff = '__off__'; static const String libraryQualityLabelBitrate = 'bitrate'; + static const String libraryQualityLabelFileFormat = 'file_format'; static const String libraryQualityLabelBitDepthOnly = 'bit_depth_only'; static const String libraryQualityLabelBitDepth = 'bit_depth'; static const String libraryQualityLabelBitDepthBitrate = 'bit_depth_bitrate'; @@ -42,8 +43,8 @@ class AppSettings { /// 'last' (keep last used), 'all', 'albums', 'singles', or 'playlists'. final String defaultLibraryView; - /// Library badge text: measured bitrate, bit depth, bit depth/sample rate, - /// or the combined bit depth/bitrate label. + /// Library badge text: measured bitrate, file format, bit depth, + /// bit depth/sample rate, or the combined bit depth/bitrate label. final String libraryQualityLabelMode; final bool askQualityBeforeDownload; final bool enableLogging; diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 90c9cae3..5d209105 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -381,6 +381,8 @@ class SettingsNotifier extends Notifier { String _normalizeLibraryQualityLabelMode(String value) { return switch (value) { + AppSettings.libraryQualityLabelFileFormat => + AppSettings.libraryQualityLabelFileFormat, AppSettings.libraryQualityLabelBitDepthOnly => AppSettings.libraryQualityLabelBitDepthOnly, AppSettings.libraryQualityLabelBitDepth => diff --git a/lib/screens/settings/library_settings_page.dart b/lib/screens/settings/library_settings_page.dart index b91a2f87..7aff8114 100644 --- a/lib/screens/settings/library_settings_page.dart +++ b/lib/screens/settings/library_settings_page.dart @@ -435,6 +435,9 @@ class _LibrarySettingsPageState extends ConsumerState { } String _getQualityLabelModeLabel(BuildContext context, String mode) { + if (mode == AppSettings.libraryQualityLabelFileFormat) { + return context.l10n.libraryQualityLabelFileFormat; + } if (mode == AppSettings.libraryQualityLabelBitDepthOnly) { return context.l10n.audioAnalysisBitDepth; } @@ -474,6 +477,11 @@ class _LibrarySettingsPageState extends ConsumerState { '${context.l10n.audioAnalysisBitDepth} & ' '${context.l10n.trackConvertBitrate}', ), + ( + AppSettings.libraryQualityLabelFileFormat, + Icons.audio_file_rounded, + context.l10n.libraryQualityLabelFileFormat, + ), ]; showModalBottomSheet( context: context, diff --git a/lib/screens/settings/settings_search_catalog.dart b/lib/screens/settings/settings_search_catalog.dart index 21279430..d0c7f797 100644 --- a/lib/screens/settings/settings_search_catalog.dart +++ b/lib/screens/settings/settings_search_catalog.dart @@ -149,7 +149,16 @@ class SettingsSearchCatalog { SettingsSearchEntry( icon: Icons.graphic_eq_rounded, title: l10n.trackAudioQuality, - keywords: const ['quality label', 'bitrate', 'bit depth', 'kbps'], + keywords: const [ + 'quality label', + 'bitrate', + 'bit depth', + 'file format', + 'codec', + 'flac', + 'mp3', + 'kbps', + ], ), SettingsSearchEntry( icon: Icons.library_music_outlined, diff --git a/lib/utils/audio_quality_badge_policy.dart b/lib/utils/audio_quality_badge_policy.dart index c7380280..43e26c05 100644 --- a/lib/utils/audio_quality_badge_policy.dart +++ b/lib/utils/audio_quality_badge_policy.dart @@ -11,6 +11,8 @@ final RegExp _bitDepthQualityPattern = RegExp( String normalizeLibraryQualityLabelMode(String? mode) { return switch (mode) { + AppSettings.libraryQualityLabelFileFormat => + AppSettings.libraryQualityLabelFileFormat, AppSettings.libraryQualityLabelBitDepthOnly => AppSettings.libraryQualityLabelBitDepthOnly, AppSettings.libraryQualityLabelBitDepth => @@ -21,9 +23,29 @@ String normalizeLibraryQualityLabelMode(String? mode) { }; } +String? _libraryFileFormatLabel(String? format) { + final rawFormat = normalizeOptionalString(format); + if (rawFormat == null) return null; + + return switch (normalizeAudioFormatValue(rawFormat)) { + 'flac' => 'FLAC', + 'alac' => 'ALAC', + 'wav' => 'WAV', + 'aiff' => 'AIFF', + 'aac' => 'AAC', + 'eac3' => 'EAC3', + 'ac3' => 'AC3', + 'ac4' => 'AC4', + 'mp3' => 'MP3', + 'opus' => 'OPUS', + 'm4a' => 'M4A', + _ => rawFormat.replaceFirst(RegExp(r'^\.'), '').toUpperCase(), + }; +} + /// Builds a Library label from metadata already held in memory. Lossy formats -/// keep their bitrate label because bit depth is not a useful quality signal -/// for encoded MP3/AAC/Opus audio. +/// keep their bitrate label in quality modes because bit depth is not a useful +/// quality signal for encoded MP3/AAC/Opus audio. String? buildLibraryAudioQualityLabel({ required String mode, String? format, @@ -53,6 +75,10 @@ String? buildLibraryAudioQualityLabel({ : null; final normalizedMode = normalizeLibraryQualityLabelMode(mode); + if (normalizedMode == AppSettings.libraryQualityLabelFileFormat) { + return _libraryFileFormatLabel(format); + } + if (isLossyAudioFormat(format)) { return bitrateLabel; } diff --git a/test/audio_quality_badge_policy_test.dart b/test/audio_quality_badge_policy_test.dart index 0f0c59c5..cde06fb6 100644 --- a/test/audio_quality_badge_policy_test.dart +++ b/test/audio_quality_badge_policy_test.dart @@ -54,6 +54,48 @@ void main() { ); }); + test('shows detected file format without quality metrics', () { + expect( + normalizeLibraryQualityLabelMode( + AppSettings.libraryQualityLabelFileFormat, + ), + AppSettings.libraryQualityLabelFileFormat, + ); + expect( + buildLibraryAudioQualityLabel( + mode: AppSettings.libraryQualityLabelFileFormat, + format: 'flac', + bitrateKbps: 1760, + bitDepth: 24, + sampleRate: 96000, + ), + 'FLAC', + ); + expect( + buildLibraryAudioQualityLabel( + mode: AppSettings.libraryQualityLabelFileFormat, + format: 'm4a', + bitrateKbps: 256, + ), + 'M4A', + ); + expect( + buildLibraryAudioQualityLabel( + mode: AppSettings.libraryQualityLabelFileFormat, + format: 'mp3', + bitrateKbps: 320, + ), + 'MP3', + ); + expect( + buildLibraryAudioQualityLabel( + mode: AppSettings.libraryQualityLabelFileFormat, + format: 'ape', + ), + 'APE', + ); + }); + test('shows only bit depth when requested', () { expect( buildLibraryAudioQualityLabel(