import 'package:spotiflac_android/models/settings.dart'; import 'package:spotiflac_android/utils/audio_format_utils.dart'; import 'package:spotiflac_android/utils/string_utils.dart'; const highQualityBadgeBitrateThresholdKbps = 900; String normalizeLibraryQualityLabelMode(String? mode) { return switch (mode) { AppSettings.libraryQualityLabelBitDepth => AppSettings.libraryQualityLabelBitDepth, AppSettings.libraryQualityLabelBitDepthBitrate => AppSettings.libraryQualityLabelBitDepthBitrate, _ => AppSettings.libraryQualityLabelBitrate, }; } /// 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. String? buildLibraryAudioQualityLabel({ required String mode, String? format, int? bitrateKbps, int? bitDepth, int? sampleRate, String? storedQuality, }) { final stored = normalizeOptionalString(storedQuality); final storedBitrate = _bitrateFromStoredQuality(stored); final effectiveBitrate = bitrateKbps != null && bitrateKbps > 0 ? bitrateKbps : storedBitrate; final bitrateLabel = effectiveBitrate != null ? buildDisplayAudioQuality(bitrateKbps: effectiveBitrate, format: format) : null; final bitDepthLabel = bitDepth != null && bitDepth > 0 && sampleRate != null && sampleRate > 0 ? buildDisplayAudioQuality(bitDepth: bitDepth, sampleRate: sampleRate) : null; final normalizedMode = normalizeLibraryQualityLabelMode(mode); if (isLossyAudioFormat(format)) { return bitrateLabel; } return switch (normalizedMode) { AppSettings.libraryQualityLabelBitDepth => bitDepthLabel ?? bitrateLabel ?? stored, AppSettings.libraryQualityLabelBitDepthBitrate => _buildBitDepthBitrateLabel( bitDepth: bitDepth, bitrateKbps: effectiveBitrate, ) ?? bitrateLabel ?? bitDepthLabel ?? stored, // Do not display bit depth/sample rate while the user selected bitrate. // Legacy rows are backfilled separately; a stored measured bitrate remains // usable while that migration completes. _ => bitrateLabel, }; } int? _bitrateFromStoredQuality(String? quality) { final match = RegExp( r'\b(\d+(?:\.\d+)?)\s*(k(?:bps)?|mbps)\b', caseSensitive: false, ).firstMatch(quality ?? ''); final value = double.tryParse(match?.group(1) ?? ''); if (value == null || value <= 0) return null; final unit = match?.group(2)?.toLowerCase(); return unit == 'mbps' ? (value * 1000).round() : value.round(); } String? _buildBitDepthBitrateLabel({int? bitDepth, int? bitrateKbps}) { if (bitDepth == null || bitDepth <= 0 || bitrateKbps == null || bitrateKbps <= 0) { return null; } return '$bitDepth-bit/${bitrateKbps}kbps'; } /// Preserves the highlighted color used by legacy 24-bit Library badges while /// also supporting newer labels that display a measured bitrate instead. bool shouldHighlightAudioQualityBadge(String quality) { final normalized = quality.trim().toLowerCase(); if (RegExp(r'\b24(?:\s*[- ]?\s*bit|/)').hasMatch(normalized)) { return true; } final kbpsMatch = RegExp( r'\b(\d+(?:\.\d+)?)\s*k(?:bps)?\b', ).firstMatch(normalized); final kbps = double.tryParse(kbpsMatch?.group(1) ?? ''); if (kbps != null) { return kbps > highQualityBadgeBitrateThresholdKbps; } final mbpsMatch = RegExp( r'\b(\d+(?:\.\d+)?)\s*mbps\b', ).firstMatch(normalized); final mbps = double.tryParse(mbpsMatch?.group(1) ?? ''); return mbps != null && mbps * 1000 > highQualityBadgeBitrateThresholdKbps; }