import 'package:spotiflac_android/utils/int_utils.dart'; import 'package:spotiflac_android/utils/string_utils.dart'; /// Audio format/quality helpers shared by the download queue and history /// providers. int? readPositiveBitrateKbps(dynamic value) { final parsed = readPositiveInt(value); if (parsed == null) return null; final kbps = parsed >= 10000 ? (parsed / 1000).round() : parsed; return kbps >= 16 ? kbps : null; } String? audioFormatForPath(String? filePath, {String? fileName}) { final candidates = [?filePath, ?fileName]; for (final candidate in candidates) { final lower = candidate.trim().toLowerCase(); if (lower.endsWith('.opus') || lower.endsWith('.ogg')) return 'OPUS'; if (lower.endsWith('.mp3')) return 'MP3'; if (lower.endsWith('.aac')) return 'AAC'; if (lower.endsWith('.m4a') || lower.endsWith('.mp4')) return 'M4A'; } return null; } String? nonPlaceholderQuality(String? quality) { final normalized = normalizeOptionalString(quality); if (normalized == null || isPlaceholderQualityLabel(normalized)) { return null; } final bitrateMatch = RegExp( r'\b(\d+)\s*kbps\b', caseSensitive: false, ).firstMatch(normalized); if (bitrateMatch != null) { final bitrate = int.tryParse(bitrateMatch.group(1) ?? ''); if (bitrate != null && bitrate < 16) return null; } final lower = normalized.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), '_'); const requestedLosslessLabels = { 'hi_res_lossless', 'hires_lossless', 'hi_res', 'hires', 'flac_best_available', }; if (requestedLosslessLabels.contains(lower)) return null; return normalized; } String? normalizeAudioFormatValue(String? value) { final normalized = normalizeOptionalString( value, )?.toLowerCase().replaceAll('-', '_'); return switch (normalized) { 'flac' => 'flac', 'alac' => 'alac', 'aac' || 'mp4a' => 'aac', 'eac3' || 'ec_3' => 'eac3', 'ac3' || 'ac_3' => 'ac3', 'ac4' || 'ac_4' => 'ac4', 'mp3' => 'mp3', 'opus' || 'ogg' => 'opus', 'm4a' || 'mp4' => 'm4a', _ => null, }; } bool isLossyAudioFormat(String? value) { return const { 'aac', 'eac3', 'ac3', 'ac4', 'mp3', 'opus', 'm4a', }.contains(normalizeAudioFormatValue(value)); } String lossyFormatForSetting(String value) { final normalized = value.trim().toLowerCase(); if (normalized.startsWith('opus')) return 'opus'; if (normalized.startsWith('aac') || normalized.startsWith('m4a')) { return 'aac'; } return 'mp3'; } String lossyExtensionForFormat(String format) { return switch (format) { 'opus' => '.opus', 'aac' => '.m4a', _ => '.mp3', }; } String metadataFormatForLossyFormat(String format) { return format == 'aac' ? 'm4a' : format; } String displayFormatForLossyFormat(String format) { return format == 'aac' ? 'AAC' : format.toUpperCase(); } String? resolveDisplayQuality({ required String? filePath, String? fileName, String? detectedFormat, int? bitDepth, int? sampleRate, int? bitrateKbps, String? storedQuality, }) { final format = displayFormatForCodec(detectedFormat) ?? audioFormatForPath(filePath, fileName: fileName); if (format == 'OPUS' || format == 'MP3' || format == 'AAC' || format == 'EAC3' || format == 'AC3' || format == 'AC4' || (format == 'M4A' && (bitDepth == null || bitDepth <= 0))) { return buildDisplayAudioQuality(bitrateKbps: bitrateKbps, format: format) ?? nonPlaceholderQuality(storedQuality) ?? format; } return buildDisplayAudioQuality( bitDepth: bitDepth, sampleRate: sampleRate, storedQuality: nonPlaceholderQuality(storedQuality) ?? storedQuality, ); } String? displayFormatForCodec(String? value) { final normalized = normalizeOptionalString( value, )?.toLowerCase().replaceAll('-', '_'); return switch (normalized) { 'flac' => 'FLAC', 'alac' => 'ALAC', 'aac' || 'mp4a' => 'AAC', 'eac3' || 'ec_3' => 'EAC3', 'ac3' || 'ac_3' => 'AC3', 'ac4' || 'ac_4' => 'AC4', 'mp3' => 'MP3', 'opus' => 'OPUS', _ => null, }; }