Skip to main content

ff_format/
codec.rs

1//! Video and audio codec definitions.
2//!
3//! This module provides enums for identifying video and audio codecs
4//! commonly used in media files.
5//!
6//! # Examples
7//!
8//! ```
9//! use ff_format::codec::{VideoCodec, AudioCodec};
10//!
11//! let video = VideoCodec::H264;
12//! assert!(video.is_h264_family());
13//! assert_eq!(video.name(), "h264");
14//!
15//! let audio = AudioCodec::Aac;
16//! assert!(audio.is_lossy());
17//! ```
18
19use std::fmt;
20
21/// Video codec identifier.
22///
23/// This enum represents common video codecs used in media files.
24/// It covers the most widely used codecs while remaining extensible
25/// via the `Unknown` variant.
26///
27/// # Common Usage
28///
29/// - **H.264/AVC**: Most common codec for HD video, excellent compatibility
30/// - **H.265/HEVC**: Better compression than H.264, used for 4K content
31/// - **VP9**: Google's open codec for web video streaming
32/// - **AV1**: Next-gen open codec, excellent compression
33/// - **Apple `ProRes`**: Apple's professional editing codec
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
35#[non_exhaustive]
36pub enum VideoCodec {
37    /// H.264/AVC - most common video codec
38    #[default]
39    H264,
40    /// H.265/HEVC - successor to H.264, better compression
41    H265,
42    /// VP8 - Google's older open codec
43    Vp8,
44    /// VP9 - Google's open codec for web video streaming
45    Vp9,
46    /// AV1 - Alliance for Open Media codec, next-gen compression
47    Av1,
48    /// AV1 encoded via SVT-AV1 (libsvtav1) — LGPL-licensed, often faster than libaom-av1.
49    ///
50    /// Requires an `FFmpeg` build with `--enable-libsvtav1`.
51    Av1Svt,
52    /// Apple's professional editing codec
53    ProRes,
54    /// Avid DNxHD/DNxHR — professional editing codec for post-production
55    DnxHd,
56    /// MPEG-4 Part 2 - older codec, legacy support
57    Mpeg4,
58    /// MPEG-2 Video - DVD and broadcast standard
59    Mpeg2,
60    /// MJPEG - Motion JPEG, used by some cameras
61    Mjpeg,
62    /// PNG - lossless image codec, used for image sequence output
63    Png,
64    /// Unknown or unsupported codec
65    Unknown,
66}
67
68impl VideoCodec {
69    /// Returns the codec name as a human-readable string.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use ff_format::codec::VideoCodec;
75    ///
76    /// assert_eq!(VideoCodec::H264.name(), "h264");
77    /// assert_eq!(VideoCodec::H265.name(), "hevc");
78    /// ```
79    #[must_use]
80    pub const fn name(&self) -> &'static str {
81        match self {
82            Self::H264 => "h264",
83            Self::H265 => "hevc",
84            Self::Vp8 => "vp8",
85            Self::Vp9 => "vp9",
86            Self::Av1 | Self::Av1Svt => "av1",
87            Self::ProRes => "prores",
88            Self::DnxHd => "dnxhd",
89            Self::Mpeg4 => "mpeg4",
90            Self::Mpeg2 => "mpeg2video",
91            Self::Mjpeg => "mjpeg",
92            Self::Png => "png",
93            Self::Unknown => "unknown",
94        }
95    }
96
97    /// Returns the human-readable display name for the codec.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use ff_format::codec::VideoCodec;
103    ///
104    /// assert_eq!(VideoCodec::H264.display_name(), "H.264/AVC");
105    /// assert_eq!(VideoCodec::H265.display_name(), "H.265/HEVC");
106    /// ```
107    #[must_use]
108    pub const fn display_name(&self) -> &'static str {
109        match self {
110            Self::H264 => "H.264/AVC",
111            Self::H265 => "H.265/HEVC",
112            Self::Vp8 => "VP8",
113            Self::Vp9 => "VP9",
114            Self::Av1 => "AV1",
115            Self::Av1Svt => "AV1 (SVT)",
116            Self::ProRes => "Apple ProRes",
117            Self::DnxHd => "Avid DNxHD/DNxHR",
118            Self::Mpeg4 => "MPEG-4 Part 2",
119            Self::Mpeg2 => "MPEG-2",
120            Self::Mjpeg => "Motion JPEG",
121            Self::Png => "PNG",
122            Self::Unknown => "Unknown",
123        }
124    }
125
126    /// Returns `true` if this is part of the H.264 family.
127    ///
128    /// # Examples
129    ///
130    /// ```
131    /// use ff_format::codec::VideoCodec;
132    ///
133    /// assert!(VideoCodec::H264.is_h264_family());
134    /// assert!(!VideoCodec::H265.is_h264_family());
135    /// ```
136    #[must_use]
137    pub const fn is_h264_family(&self) -> bool {
138        matches!(self, Self::H264)
139    }
140
141    /// Returns `true` if this is part of the H.265/HEVC family.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use ff_format::codec::VideoCodec;
147    ///
148    /// assert!(VideoCodec::H265.is_h265_family());
149    /// assert!(!VideoCodec::H264.is_h265_family());
150    /// ```
151    #[must_use]
152    pub const fn is_h265_family(&self) -> bool {
153        matches!(self, Self::H265)
154    }
155
156    /// Returns `true` if this is a Google/WebM codec (VP8, VP9).
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// use ff_format::codec::VideoCodec;
162    ///
163    /// assert!(VideoCodec::Vp8.is_vp_family());
164    /// assert!(VideoCodec::Vp9.is_vp_family());
165    /// assert!(!VideoCodec::H264.is_vp_family());
166    /// ```
167    #[must_use]
168    pub const fn is_vp_family(&self) -> bool {
169        matches!(self, Self::Vp8 | Self::Vp9)
170    }
171
172    /// Returns `true` if this is a professional/editing codec.
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// use ff_format::codec::VideoCodec;
178    ///
179    /// assert!(VideoCodec::ProRes.is_professional());
180    /// assert!(!VideoCodec::H264.is_professional());
181    /// ```
182    #[must_use]
183    pub const fn is_professional(&self) -> bool {
184        matches!(self, Self::ProRes | Self::DnxHd)
185    }
186
187    /// Returns `true` if this codec supports hardware acceleration on most platforms.
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// use ff_format::codec::VideoCodec;
193    ///
194    /// assert!(VideoCodec::H264.has_hardware_support());
195    /// assert!(VideoCodec::H265.has_hardware_support());
196    /// assert!(!VideoCodec::ProRes.has_hardware_support());
197    /// ```
198    #[must_use]
199    pub const fn has_hardware_support(&self) -> bool {
200        matches!(self, Self::H264 | Self::H265 | Self::Vp9 | Self::Av1)
201    }
202
203    /// Returns `true` if the codec is unknown.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// use ff_format::codec::VideoCodec;
209    ///
210    /// assert!(VideoCodec::Unknown.is_unknown());
211    /// assert!(!VideoCodec::H264.is_unknown());
212    /// ```
213    #[must_use]
214    pub const fn is_unknown(&self) -> bool {
215        matches!(self, Self::Unknown)
216    }
217}
218
219impl fmt::Display for VideoCodec {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        write!(f, "{}", self.display_name())
222    }
223}
224
225/// Audio codec identifier.
226///
227/// This enum represents common audio codecs used in media files.
228/// It covers the most widely used codecs while remaining extensible
229/// via the `Unknown` variant.
230///
231/// # Common Usage
232///
233/// - **AAC**: Most common for streaming and mobile
234/// - **MP3**: Legacy but still widely supported
235/// - **Opus**: Excellent quality at low bitrates, used for voice communication
236/// - **FLAC**: Lossless compression
237/// - **PCM**: Uncompressed audio
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
239#[non_exhaustive]
240pub enum AudioCodec {
241    /// AAC (Advanced Audio Coding) - most common lossy codec
242    #[default]
243    Aac,
244    /// MP3 (MPEG-1 Audio Layer 3) - legacy lossy codec
245    Mp3,
246    /// Opus - modern lossy codec, excellent at low bitrates
247    Opus,
248    /// FLAC (Free Lossless Audio Codec) - lossless compression
249    Flac,
250    /// PCM (Pulse Code Modulation) - uncompressed audio
251    Pcm,
252    /// PCM signed 16-bit little-endian — uncompressed, explicit bit depth
253    Pcm16,
254    /// PCM signed 24-bit little-endian — uncompressed, professional audio
255    Pcm24,
256    /// Vorbis - open lossy codec, used in Ogg containers
257    Vorbis,
258    /// AC3 (Dolby Digital) - surround sound codec
259    Ac3,
260    /// EAC3 (Dolby Digital Plus) - enhanced AC3
261    Eac3,
262    /// DTS (Digital Theater Systems) - surround sound codec
263    Dts,
264    /// ALAC (Apple Lossless Audio Codec)
265    Alac,
266    /// Unknown or unsupported codec
267    Unknown,
268}
269
270impl AudioCodec {
271    /// Returns the codec name as a human-readable string.
272    ///
273    /// # Examples
274    ///
275    /// ```
276    /// use ff_format::codec::AudioCodec;
277    ///
278    /// assert_eq!(AudioCodec::Aac.name(), "aac");
279    /// assert_eq!(AudioCodec::Flac.name(), "flac");
280    /// ```
281    #[must_use]
282    pub const fn name(&self) -> &'static str {
283        match self {
284            Self::Aac => "aac",
285            Self::Mp3 => "mp3",
286            Self::Opus => "opus",
287            Self::Flac => "flac",
288            Self::Pcm => "pcm",
289            Self::Pcm16 => "pcm_s16le",
290            Self::Pcm24 => "pcm_s24le",
291            Self::Vorbis => "vorbis",
292            Self::Ac3 => "ac3",
293            Self::Eac3 => "eac3",
294            Self::Dts => "dts",
295            Self::Alac => "alac",
296            Self::Unknown => "unknown",
297        }
298    }
299
300    /// Returns the human-readable display name for the codec.
301    ///
302    /// # Examples
303    ///
304    /// ```
305    /// use ff_format::codec::AudioCodec;
306    ///
307    /// assert_eq!(AudioCodec::Aac.display_name(), "AAC");
308    /// assert_eq!(AudioCodec::Flac.display_name(), "FLAC");
309    /// ```
310    #[must_use]
311    pub const fn display_name(&self) -> &'static str {
312        match self {
313            Self::Aac => "AAC",
314            Self::Mp3 => "MP3",
315            Self::Opus => "Opus",
316            Self::Flac => "FLAC",
317            Self::Pcm => "PCM",
318            Self::Pcm16 => "PCM 16-bit",
319            Self::Pcm24 => "PCM 24-bit",
320            Self::Vorbis => "Vorbis",
321            Self::Ac3 => "Dolby Digital (AC-3)",
322            Self::Eac3 => "Dolby Digital Plus (E-AC-3)",
323            Self::Dts => "DTS",
324            Self::Alac => "Apple Lossless",
325            Self::Unknown => "Unknown",
326        }
327    }
328
329    /// Returns `true` if this is a lossy codec.
330    ///
331    /// Lossy codecs discard some audio data for smaller file sizes.
332    ///
333    /// # Examples
334    ///
335    /// ```
336    /// use ff_format::codec::AudioCodec;
337    ///
338    /// assert!(AudioCodec::Aac.is_lossy());
339    /// assert!(AudioCodec::Mp3.is_lossy());
340    /// assert!(!AudioCodec::Flac.is_lossy());
341    /// ```
342    #[must_use]
343    pub const fn is_lossy(&self) -> bool {
344        matches!(
345            self,
346            Self::Aac | Self::Mp3 | Self::Opus | Self::Vorbis | Self::Ac3 | Self::Eac3 | Self::Dts
347        )
348    }
349
350    /// Returns `true` if this is a lossless codec.
351    ///
352    /// Lossless codecs preserve all audio data.
353    ///
354    /// # Examples
355    ///
356    /// ```
357    /// use ff_format::codec::AudioCodec;
358    ///
359    /// assert!(AudioCodec::Flac.is_lossless());
360    /// assert!(AudioCodec::Pcm.is_lossless());
361    /// assert!(AudioCodec::Alac.is_lossless());
362    /// assert!(!AudioCodec::Aac.is_lossless());
363    /// ```
364    #[must_use]
365    pub const fn is_lossless(&self) -> bool {
366        matches!(
367            self,
368            Self::Flac | Self::Pcm | Self::Pcm16 | Self::Pcm24 | Self::Alac
369        )
370    }
371
372    /// Returns `true` if this is a surround sound codec.
373    ///
374    /// # Examples
375    ///
376    /// ```
377    /// use ff_format::codec::AudioCodec;
378    ///
379    /// assert!(AudioCodec::Ac3.is_surround());
380    /// assert!(AudioCodec::Dts.is_surround());
381    /// assert!(!AudioCodec::Aac.is_surround());
382    /// ```
383    #[must_use]
384    pub const fn is_surround(&self) -> bool {
385        matches!(self, Self::Ac3 | Self::Eac3 | Self::Dts)
386    }
387
388    /// Returns `true` if the codec is unknown.
389    ///
390    /// # Examples
391    ///
392    /// ```
393    /// use ff_format::codec::AudioCodec;
394    ///
395    /// assert!(AudioCodec::Unknown.is_unknown());
396    /// assert!(!AudioCodec::Aac.is_unknown());
397    /// ```
398    #[must_use]
399    pub const fn is_unknown(&self) -> bool {
400        matches!(self, Self::Unknown)
401    }
402}
403
404impl fmt::Display for AudioCodec {
405    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406        write!(f, "{}", self.display_name())
407    }
408}
409
410/// Subtitle codec identifier.
411///
412/// This enum represents common subtitle codecs used in media files.
413/// It covers text-based and bitmap-based formats while remaining extensible
414/// via the `Other` variant.
415///
416/// Note: No `Copy` or `Default` derive because `Other(String)` is not `Copy`.
417#[derive(Debug, Clone, PartialEq, Eq, Hash)]
418pub enum SubtitleCodec {
419    /// `SubRip` / SRT — text-based timed subtitles
420    Srt,
421    /// ASS/SSA (Advanced `SubStation` Alpha) — styled text subtitles
422    Ass,
423    /// DVB bitmap subtitles (digital broadcast)
424    Dvb,
425    /// HDMV/PGS — Blu-ray bitmap subtitles
426    Hdmv,
427    /// `WebVTT` — web-standard text subtitles
428    Webvtt,
429    /// Unrecognized codec; raw codec name stored for transparency
430    Other(String),
431}
432
433impl SubtitleCodec {
434    /// Returns the codec name as a short string.
435    ///
436    /// # Examples
437    ///
438    /// ```
439    /// use ff_format::codec::SubtitleCodec;
440    ///
441    /// assert_eq!(SubtitleCodec::Srt.name(), "srt");
442    /// assert_eq!(SubtitleCodec::Ass.name(), "ass");
443    /// ```
444    #[must_use]
445    pub fn name(&self) -> &str {
446        match self {
447            Self::Srt => "srt",
448            Self::Ass => "ass",
449            Self::Dvb => "dvb_subtitle",
450            Self::Hdmv => "hdmv_pgs_subtitle",
451            Self::Webvtt => "webvtt",
452            Self::Other(name) => name.as_str(),
453        }
454    }
455
456    /// Returns the human-readable display name for the codec.
457    ///
458    /// # Examples
459    ///
460    /// ```
461    /// use ff_format::codec::SubtitleCodec;
462    ///
463    /// assert_eq!(SubtitleCodec::Srt.display_name(), "SubRip (SRT)");
464    /// assert_eq!(SubtitleCodec::Hdmv.display_name(), "HDMV/PGS");
465    /// ```
466    #[must_use]
467    pub fn display_name(&self) -> &str {
468        match self {
469            Self::Srt => "SubRip (SRT)",
470            Self::Ass => "ASS/SSA",
471            Self::Dvb => "DVB Subtitle",
472            Self::Hdmv => "HDMV/PGS",
473            Self::Webvtt => "WebVTT",
474            Self::Other(name) => name.as_str(),
475        }
476    }
477
478    /// Returns `true` if this is a text-based subtitle codec.
479    ///
480    /// # Examples
481    ///
482    /// ```
483    /// use ff_format::codec::SubtitleCodec;
484    ///
485    /// assert!(SubtitleCodec::Srt.is_text_based());
486    /// assert!(SubtitleCodec::Ass.is_text_based());
487    /// assert!(!SubtitleCodec::Dvb.is_text_based());
488    /// ```
489    #[must_use]
490    pub fn is_text_based(&self) -> bool {
491        matches!(self, Self::Srt | Self::Ass | Self::Webvtt)
492    }
493
494    /// Returns `true` if this is a bitmap-based subtitle codec.
495    ///
496    /// # Examples
497    ///
498    /// ```
499    /// use ff_format::codec::SubtitleCodec;
500    ///
501    /// assert!(SubtitleCodec::Dvb.is_bitmap_based());
502    /// assert!(SubtitleCodec::Hdmv.is_bitmap_based());
503    /// assert!(!SubtitleCodec::Srt.is_bitmap_based());
504    /// ```
505    #[must_use]
506    pub fn is_bitmap_based(&self) -> bool {
507        matches!(self, Self::Dvb | Self::Hdmv)
508    }
509
510    /// Returns `true` if the codec is unrecognized.
511    ///
512    /// # Examples
513    ///
514    /// ```
515    /// use ff_format::codec::SubtitleCodec;
516    ///
517    /// assert!(SubtitleCodec::Other("dvd_subtitle".to_string()).is_unknown());
518    /// assert!(!SubtitleCodec::Srt.is_unknown());
519    /// ```
520    #[must_use]
521    pub fn is_unknown(&self) -> bool {
522        matches!(self, Self::Other(_))
523    }
524}
525
526impl fmt::Display for SubtitleCodec {
527    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528        write!(f, "{}", self.display_name())
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535
536    mod video_codec_tests {
537        use super::*;
538
539        #[test]
540        fn test_names() {
541            assert_eq!(VideoCodec::H264.name(), "h264");
542            assert_eq!(VideoCodec::H265.name(), "hevc");
543            assert_eq!(VideoCodec::Vp8.name(), "vp8");
544            assert_eq!(VideoCodec::Vp9.name(), "vp9");
545            assert_eq!(VideoCodec::Av1.name(), "av1");
546            assert_eq!(VideoCodec::ProRes.name(), "prores");
547            assert_eq!(VideoCodec::DnxHd.name(), "dnxhd");
548            assert_eq!(VideoCodec::Mpeg4.name(), "mpeg4");
549            assert_eq!(VideoCodec::Mpeg2.name(), "mpeg2video");
550            assert_eq!(VideoCodec::Mjpeg.name(), "mjpeg");
551            assert_eq!(VideoCodec::Unknown.name(), "unknown");
552        }
553
554        #[test]
555        fn test_display_names() {
556            assert_eq!(VideoCodec::H264.display_name(), "H.264/AVC");
557            assert_eq!(VideoCodec::H265.display_name(), "H.265/HEVC");
558            assert_eq!(VideoCodec::ProRes.display_name(), "Apple ProRes");
559        }
560
561        #[test]
562        fn test_display() {
563            assert_eq!(format!("{}", VideoCodec::H264), "H.264/AVC");
564            assert_eq!(format!("{}", VideoCodec::Av1), "AV1");
565        }
566
567        #[test]
568        fn test_default() {
569            assert_eq!(VideoCodec::default(), VideoCodec::H264);
570        }
571
572        #[test]
573        fn test_codec_families() {
574            assert!(VideoCodec::H264.is_h264_family());
575            assert!(!VideoCodec::H265.is_h264_family());
576
577            assert!(VideoCodec::H265.is_h265_family());
578            assert!(!VideoCodec::H264.is_h265_family());
579
580            assert!(VideoCodec::Vp8.is_vp_family());
581            assert!(VideoCodec::Vp9.is_vp_family());
582            assert!(!VideoCodec::H264.is_vp_family());
583        }
584
585        #[test]
586        fn test_is_professional() {
587            assert!(VideoCodec::ProRes.is_professional());
588            assert!(VideoCodec::DnxHd.is_professional());
589            assert!(!VideoCodec::H264.is_professional());
590            assert!(!VideoCodec::Unknown.is_professional());
591        }
592
593        #[test]
594        fn av1svt_name_should_return_av1() {
595            assert_eq!(VideoCodec::Av1Svt.name(), "av1");
596        }
597
598        #[test]
599        fn av1svt_display_name_should_return_av1_svt() {
600            assert_eq!(VideoCodec::Av1Svt.display_name(), "AV1 (SVT)");
601        }
602
603        #[test]
604        fn dnxhd_should_have_correct_name_and_display_name() {
605            assert_eq!(VideoCodec::DnxHd.name(), "dnxhd");
606            assert_eq!(VideoCodec::DnxHd.display_name(), "Avid DNxHD/DNxHR");
607        }
608
609        #[test]
610        fn dnxhd_should_be_professional() {
611            assert!(VideoCodec::DnxHd.is_professional());
612            assert!(!VideoCodec::DnxHd.is_h264_family());
613            assert!(!VideoCodec::DnxHd.is_h265_family());
614            assert!(!VideoCodec::DnxHd.is_vp_family());
615            assert!(!VideoCodec::DnxHd.is_unknown());
616        }
617
618        #[test]
619        fn dnxhd_should_not_have_hardware_support() {
620            assert!(!VideoCodec::DnxHd.has_hardware_support());
621        }
622
623        #[test]
624        fn test_hardware_support() {
625            assert!(VideoCodec::H264.has_hardware_support());
626            assert!(VideoCodec::H265.has_hardware_support());
627            assert!(VideoCodec::Vp9.has_hardware_support());
628            assert!(VideoCodec::Av1.has_hardware_support());
629            assert!(!VideoCodec::ProRes.has_hardware_support());
630            assert!(!VideoCodec::Mjpeg.has_hardware_support());
631        }
632
633        #[test]
634        fn test_is_unknown() {
635            assert!(VideoCodec::Unknown.is_unknown());
636            assert!(!VideoCodec::H264.is_unknown());
637        }
638
639        #[test]
640        fn test_debug() {
641            assert_eq!(format!("{:?}", VideoCodec::H264), "H264");
642            assert_eq!(format!("{:?}", VideoCodec::H265), "H265");
643        }
644
645        #[test]
646        fn test_equality_and_hash() {
647            use std::collections::HashSet;
648
649            assert_eq!(VideoCodec::H264, VideoCodec::H264);
650            assert_ne!(VideoCodec::H264, VideoCodec::H265);
651
652            let mut set = HashSet::new();
653            set.insert(VideoCodec::H264);
654            set.insert(VideoCodec::H265);
655            assert!(set.contains(&VideoCodec::H264));
656            assert!(!set.contains(&VideoCodec::Vp9));
657        }
658
659        #[test]
660        fn test_copy() {
661            let codec = VideoCodec::H264;
662            let copied = codec;
663            assert_eq!(codec, copied);
664        }
665    }
666
667    mod subtitle_codec_tests {
668        use super::*;
669
670        #[test]
671        fn name_should_return_short_codec_name() {
672            assert_eq!(SubtitleCodec::Srt.name(), "srt");
673            assert_eq!(SubtitleCodec::Ass.name(), "ass");
674            assert_eq!(SubtitleCodec::Dvb.name(), "dvb_subtitle");
675            assert_eq!(SubtitleCodec::Hdmv.name(), "hdmv_pgs_subtitle");
676            assert_eq!(SubtitleCodec::Webvtt.name(), "webvtt");
677            assert_eq!(
678                SubtitleCodec::Other("dvd_subtitle".to_string()).name(),
679                "dvd_subtitle"
680            );
681        }
682
683        #[test]
684        fn display_name_should_return_human_readable_name() {
685            assert_eq!(SubtitleCodec::Srt.display_name(), "SubRip (SRT)");
686            assert_eq!(SubtitleCodec::Ass.display_name(), "ASS/SSA");
687            assert_eq!(SubtitleCodec::Dvb.display_name(), "DVB Subtitle");
688            assert_eq!(SubtitleCodec::Hdmv.display_name(), "HDMV/PGS");
689            assert_eq!(SubtitleCodec::Webvtt.display_name(), "WebVTT");
690        }
691
692        #[test]
693        fn display_should_use_display_name() {
694            assert_eq!(format!("{}", SubtitleCodec::Srt), "SubRip (SRT)");
695            assert_eq!(format!("{}", SubtitleCodec::Hdmv), "HDMV/PGS");
696        }
697
698        #[test]
699        fn is_text_based_should_return_true_for_text_codecs() {
700            assert!(SubtitleCodec::Srt.is_text_based());
701            assert!(SubtitleCodec::Ass.is_text_based());
702            assert!(SubtitleCodec::Webvtt.is_text_based());
703            assert!(!SubtitleCodec::Dvb.is_text_based());
704            assert!(!SubtitleCodec::Hdmv.is_text_based());
705        }
706
707        #[test]
708        fn is_bitmap_based_should_return_true_for_bitmap_codecs() {
709            assert!(SubtitleCodec::Dvb.is_bitmap_based());
710            assert!(SubtitleCodec::Hdmv.is_bitmap_based());
711            assert!(!SubtitleCodec::Srt.is_bitmap_based());
712            assert!(!SubtitleCodec::Ass.is_bitmap_based());
713            assert!(!SubtitleCodec::Webvtt.is_bitmap_based());
714        }
715
716        #[test]
717        fn is_unknown_should_return_true_only_for_other_variant() {
718            assert!(SubtitleCodec::Other("dvd_subtitle".to_string()).is_unknown());
719            assert!(!SubtitleCodec::Srt.is_unknown());
720            assert!(!SubtitleCodec::Dvb.is_unknown());
721        }
722
723        #[test]
724        fn equality_should_compare_by_value() {
725            assert_eq!(SubtitleCodec::Srt, SubtitleCodec::Srt);
726            assert_ne!(SubtitleCodec::Srt, SubtitleCodec::Ass);
727            assert_eq!(
728                SubtitleCodec::Other("foo".to_string()),
729                SubtitleCodec::Other("foo".to_string())
730            );
731            assert_ne!(
732                SubtitleCodec::Other("foo".to_string()),
733                SubtitleCodec::Other("bar".to_string())
734            );
735        }
736
737        #[test]
738        fn clone_should_produce_equal_value() {
739            let codec = SubtitleCodec::Other("test".to_string());
740            let cloned = codec.clone();
741            assert_eq!(codec, cloned);
742        }
743    }
744
745    mod audio_codec_tests {
746        use super::*;
747
748        #[test]
749        fn test_names() {
750            assert_eq!(AudioCodec::Aac.name(), "aac");
751            assert_eq!(AudioCodec::Mp3.name(), "mp3");
752            assert_eq!(AudioCodec::Opus.name(), "opus");
753            assert_eq!(AudioCodec::Flac.name(), "flac");
754            assert_eq!(AudioCodec::Pcm.name(), "pcm");
755            assert_eq!(AudioCodec::Vorbis.name(), "vorbis");
756            assert_eq!(AudioCodec::Ac3.name(), "ac3");
757            assert_eq!(AudioCodec::Eac3.name(), "eac3");
758            assert_eq!(AudioCodec::Dts.name(), "dts");
759            assert_eq!(AudioCodec::Alac.name(), "alac");
760            assert_eq!(AudioCodec::Unknown.name(), "unknown");
761        }
762
763        #[test]
764        fn test_display_names() {
765            assert_eq!(AudioCodec::Aac.display_name(), "AAC");
766            assert_eq!(AudioCodec::Flac.display_name(), "FLAC");
767            assert_eq!(AudioCodec::Ac3.display_name(), "Dolby Digital (AC-3)");
768        }
769
770        #[test]
771        fn test_display() {
772            assert_eq!(format!("{}", AudioCodec::Aac), "AAC");
773            assert_eq!(format!("{}", AudioCodec::Opus), "Opus");
774        }
775
776        #[test]
777        fn test_default() {
778            assert_eq!(AudioCodec::default(), AudioCodec::Aac);
779        }
780
781        #[test]
782        fn test_lossy_lossless() {
783            // Lossy codecs
784            assert!(AudioCodec::Aac.is_lossy());
785            assert!(AudioCodec::Mp3.is_lossy());
786            assert!(AudioCodec::Opus.is_lossy());
787            assert!(AudioCodec::Vorbis.is_lossy());
788            assert!(AudioCodec::Ac3.is_lossy());
789            assert!(AudioCodec::Eac3.is_lossy());
790            assert!(AudioCodec::Dts.is_lossy());
791
792            // Lossless codecs
793            assert!(AudioCodec::Flac.is_lossless());
794            assert!(AudioCodec::Pcm.is_lossless());
795            assert!(AudioCodec::Alac.is_lossless());
796
797            // Mutual exclusion
798            assert!(!AudioCodec::Aac.is_lossless());
799            assert!(!AudioCodec::Flac.is_lossy());
800        }
801
802        #[test]
803        fn test_surround() {
804            assert!(AudioCodec::Ac3.is_surround());
805            assert!(AudioCodec::Eac3.is_surround());
806            assert!(AudioCodec::Dts.is_surround());
807            assert!(!AudioCodec::Aac.is_surround());
808            assert!(!AudioCodec::Flac.is_surround());
809        }
810
811        #[test]
812        fn pcm16_name_should_return_pcm_s16le() {
813            assert_eq!(AudioCodec::Pcm16.name(), "pcm_s16le");
814        }
815
816        #[test]
817        fn pcm24_name_should_return_pcm_s24le() {
818            assert_eq!(AudioCodec::Pcm24.name(), "pcm_s24le");
819        }
820
821        #[test]
822        fn pcm16_should_be_lossless() {
823            assert!(AudioCodec::Pcm16.is_lossless());
824            assert!(!AudioCodec::Pcm16.is_lossy());
825        }
826
827        #[test]
828        fn pcm24_should_be_lossless() {
829            assert!(AudioCodec::Pcm24.is_lossless());
830            assert!(!AudioCodec::Pcm24.is_lossy());
831        }
832
833        #[test]
834        fn test_is_unknown() {
835            assert!(AudioCodec::Unknown.is_unknown());
836            assert!(!AudioCodec::Aac.is_unknown());
837        }
838
839        #[test]
840        fn test_debug() {
841            assert_eq!(format!("{:?}", AudioCodec::Aac), "Aac");
842            assert_eq!(format!("{:?}", AudioCodec::Flac), "Flac");
843        }
844
845        #[test]
846        fn test_equality_and_hash() {
847            use std::collections::HashSet;
848
849            assert_eq!(AudioCodec::Aac, AudioCodec::Aac);
850            assert_ne!(AudioCodec::Aac, AudioCodec::Mp3);
851
852            let mut set = HashSet::new();
853            set.insert(AudioCodec::Aac);
854            set.insert(AudioCodec::Flac);
855            assert!(set.contains(&AudioCodec::Aac));
856            assert!(!set.contains(&AudioCodec::Opus));
857        }
858
859        #[test]
860        fn test_copy() {
861            let codec = AudioCodec::Aac;
862            let copied = codec;
863            assert_eq!(codec, copied);
864        }
865    }
866}