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