import '../i18n/strings.g.dart'; /// Utility class for codec-related operations. /// /// Provides centralized codec name mappings, file extension lookups, /// and display name formatting. class CodecUtils { CodecUtils._(); static String getSubtitleExtension(String? codec) { if (codec == null) return 'srt'; switch (codec.toLowerCase()) { case 'subrip': case 'srt': return 'srt'; case 'ass': case 'ssa': return 'ass'; case 'webvtt': case 'vtt': return 'vtt'; case 'mov_text': return 'srt'; case 'pgs': case 'pgssub': case 'hdmv_pgs_subtitle': return 'sup'; case 'dvd_subtitle': case 'dvdsub': case 'vobsub': case 'dvb_sub': case 'dvb_subtitle': return 'sub'; default: return 'srt'; } } static bool isTextSubtitleCodec(String? codec) { if (codec == null) return false; return switch (codec.toLowerCase()) { 'srt' || 'subrip' || 'ass' || 'ssa' || 'webvtt' || 'vtt' || 'mov_text' => true, _ => false, }; } /// Image-based (bitmap) subtitle codecs. Plex burns these into the video /// when the selected output transport cannot carry a bitmap subtitle /// rendition. static bool isImageSubtitleCodec(String? codec) { if (codec == null) return false; return switch (codec.toLowerCase()) { 'pgs' || 'pgssub' || 'hdmv_pgs_subtitle' || 'dvd_subtitle' || 'dvdsub' || 'vobsub' || 'dvb_sub' || // Jellyfin's own spelling, which is what the transcode profile asks it to burn. 'dvbsub' || 'dvb_subtitle' => true, _ => false, }; } /// Subtitle codecs Plex can deliver in a transcode. Text codecs can become /// segmented HLS WebVTT; image codecs can be burned into the video. static bool isTranscodableSubtitleCodec(String? codec) { return isTextSubtitleCodec(codec) || isImageSubtitleCodec(codec); } /// Formats a subtitle codec name to a user-friendly display format. /// /// Converts internal codec names like 'SUBRIP' to friendly names like 'SRT'. static String formatSubtitleCodec(String codec) { final upper = codec.toUpperCase(); return switch (upper) { 'SUBRIP' => 'SRT', 'DVD_SUBTITLE' => 'DVD', 'WEBVTT' => 'VTT', 'HDMV_PGS_SUBTITLE' => 'PGS', 'MOV_TEXT' => 'MOV', _ => upper, }; } /// Formats a video codec name to a user-friendly display format. /// /// Converts internal codec names like 'hevc' to friendly names like 'HEVC'. static String formatVideoCodec(String codec) { final lower = codec.toLowerCase(); return switch (lower) { 'h264' || 'avc1' || 'avc' => 'H.264', 'hevc' || 'h265' || 'hev1' => 'HEVC', 'av1' => 'AV1', 'vp8' => 'VP8', 'vp9' => 'VP9', 'mpeg2video' || 'mpeg2' => 'MPEG-2', 'mpeg4' => 'MPEG-4', 'vc1' => 'VC-1', _ => codec.toUpperCase(), }; } /// Formats an audio channel count as a friendly layout name (2 → 'Stereo', /// 6 → '5.1'). Returns null when [channels] is null or not positive. static String? formatAudioChannels(int? channels) { if (channels == null || channels <= 0) return null; return switch (channels) { 1 => t.fileInfo.channelsMono, 2 => t.videoSettings.audioOutputStereo, 3 => '3.0', 4 => '4.0', 5 => '4.1', 6 => '5.1', 7 => '6.1', 8 => '7.1', _ => '${channels}ch', }; } /// Formats an audio codec name to a user-friendly display format. /// /// Accepts both ffmpeg-style names as reported by mpv and the media /// servers ('aac', 'eac3') and RFC 6381 codec IDs as reported by /// ExoPlayer's `Format.codecs` ('mp4a.40.2', 'ec-3', 'dtsc'). static String formatAudioCodec(String codec) { final lower = codec.toLowerCase(); // MP4 object types 0x69/0x6B under the mp4a prefix are MPEG layer // audio; every other mp4a object type in the wild is an AAC variant. if (lower == 'mp4a.69' || lower == 'mp4a.6b') return 'MP3'; if (lower == 'mp4a' || lower.startsWith('mp4a.')) return 'AAC'; if (lower == 'ac-4' || lower.startsWith('ac-4.')) return 'AC4'; return switch (lower) { 'aac' => 'AAC', 'ac3' || 'ac-3' => 'AC3', 'eac3' || 'ec3' || 'ec-3' => 'E-AC3', 'truehd' || 'mlpa' => 'TrueHD', 'dts' || 'dca' || 'dtsc' || 'dtse' => 'DTS', 'dtshd' || 'dts-hd' || 'dtsh' || 'dtsl' => 'DTS-HD', 'dtsx' => 'DTS:X', 'flac' => 'FLAC', 'mp3' || 'mp3float' => 'MP3', 'opus' => 'Opus', 'vorbis' => 'Vorbis', 'pcm_s16le' || 'pcm_s24le' || 'pcm' => 'PCM', _ => codec.toUpperCase(), }; } }