scuffle_mp4/
codec.rs

1use std::fmt;
2use std::str::FromStr;
3
4use scuffle_aac::AudioObjectType;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum VideoCodec {
8    /// <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter>
9    Avc { profile: u8, constraint_set: u8, level: u8 },
10    /// There is barely any documentation on this.
11    /// <https://hevcvideo.xp3.biz/html5_video.html>
12    Hevc {
13        general_profile_space: u8,
14        profile_compatibility: scuffle_h265::ProfileCompatibilityFlags,
15        profile: u8,
16        level: u8,
17        tier: bool,
18        constraint_indicator: u64,
19    },
20    /// <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter#av1>
21    Av1 {
22        profile: u8,
23        level: u8,
24        tier: bool,
25        depth: u8,
26        monochrome: bool,
27        sub_sampling_x: bool,
28        sub_sampling_y: bool,
29        color_primaries: u8,
30        transfer_characteristics: u8,
31        matrix_coefficients: u8,
32        full_range_flag: bool,
33    },
34}
35
36impl fmt::Display for VideoCodec {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            VideoCodec::Avc {
40                profile,
41                constraint_set,
42                level,
43            } => write!(f, "avc1.{profile:02x}{constraint_set:02x}{level:02x}"),
44            VideoCodec::Hevc {
45                general_profile_space,
46                profile,
47                level,
48                tier,
49                profile_compatibility,
50                constraint_indicator,
51            } => {
52                let profile_compatibility = profile_compatibility
53                    .bits()
54                    .to_be_bytes()
55                    .into_iter()
56                    .filter(|b| *b != 0)
57                    .map(|b| format!("{b:x}"))
58                    .collect::<String>();
59                let constraint_indicator = constraint_indicator
60                    .to_be_bytes()
61                    .into_iter()
62                    .filter(|b| *b != 0)
63                    .map(|b| format!("{b:x}"))
64                    .collect::<Vec<_>>()
65                    .join(".");
66
67                write!(
68                    f,
69                    "hev1.{}{:x}.{}.{}{:x}.{}",
70                    match general_profile_space {
71                        1 => "A",
72                        2 => "B",
73                        3 => "C",
74                        _ => "",
75                    },
76                    profile, // 1 or 2 chars (hex)
77                    profile_compatibility,
78                    if *tier { 'H' } else { 'L' },
79                    level, // 1 or 2 chars (hex)
80                    constraint_indicator,
81                )
82            }
83            VideoCodec::Av1 {
84                profile,
85                level,
86                tier,
87                depth,
88                monochrome,
89                sub_sampling_x,
90                sub_sampling_y,
91                color_primaries,
92                transfer_characteristics,
93                matrix_coefficients,
94                full_range_flag,
95            } => write!(
96                f,
97                "av01.{}.{}{}.{:02}.{}.{}{}{}.{:02}.{:02}.{:02}.{}",
98                profile,
99                level,
100                if *tier { 'H' } else { 'M' },
101                depth,
102                if *monochrome { 1 } else { 0 },
103                if *sub_sampling_x { 1 } else { 0 },
104                if *sub_sampling_y { 1 } else { 0 },
105                if *monochrome { 1 } else { 0 },
106                color_primaries,
107                transfer_characteristics,
108                matrix_coefficients,
109                if *full_range_flag { 1 } else { 0 },
110            ),
111        }
112    }
113}
114
115impl FromStr for VideoCodec {
116    type Err = String;
117
118    fn from_str(s: &str) -> Result<Self, Self::Err> {
119        let splits = s.split('.').collect::<Vec<_>>();
120        if splits.is_empty() {
121            return Err("invalid codec, empty string".into());
122        }
123
124        match splits[0] {
125            "avc1" => {
126                if splits.len() < 2 {
127                    return Err("invalid codec, missing profile".into());
128                }
129
130                let profile = u8::from_str_radix(&splits[1][..2], 16)
131                    .map_err(|e| format!("invalid codec, invalid profile: {}, {}", splits[1], e))?;
132                let constraint_set = u8::from_str_radix(&splits[1][2..4], 16)
133                    .map_err(|e| format!("invalid codec, invalid constraint set: {}, {}", splits[1], e))?;
134                let level = u8::from_str_radix(&splits[1][4..6], 16)
135                    .map_err(|e| format!("invalid codec, invalid level: {}, {}", splits[1], e))?;
136
137                Ok(VideoCodec::Avc {
138                    profile,
139                    constraint_set,
140                    level,
141                })
142            }
143            "hev1" => {
144                if splits.len() < 6 {
145                    return Err("invalid codec, missing profile".into());
146                }
147
148                let general_profile_space = match splits[1] {
149                    "A" => 1,
150                    "B" => 2,
151                    "C" => 3,
152                    _ => {
153                        return Err(format!("invalid codec, invalid general profile space: {}", splits[1]));
154                    }
155                };
156
157                let profile = u8::from_str_radix(splits[2], 16)
158                    .map_err(|e| format!("invalid codec, invalid profile: {}, {}", splits[2], e))?;
159
160                let profile_compatibility = scuffle_h265::ProfileCompatibilityFlags::from_bits_retain(
161                    u32::from_str_radix(splits[3], 16)
162                        .map_err(|e| format!("invalid codec, invalid profile compatibility: {}, {}", splits[3], e))?,
163                );
164
165                let tier = match splits[4] {
166                    "H" => true,
167                    "L" => false,
168                    _ => return Err(format!("invalid codec, invalid tier: {}", splits[4])),
169                };
170
171                let level = u8::from_str_radix(splits[5], 16)
172                    .map_err(|e| format!("invalid codec, invalid level: {}, {}", splits[5], e))?;
173
174                let constraint_indicator = u64::from_str_radix(splits[6], 16)
175                    .map_err(|e| format!("invalid codec, invalid constraint indicator: {}, {}", splits[6], e))?;
176
177                Ok(VideoCodec::Hevc {
178                    general_profile_space,
179                    profile,
180                    level,
181                    tier,
182                    profile_compatibility,
183                    constraint_indicator,
184                })
185            }
186            "av01" => {
187                if splits.len() < 12 {
188                    return Err("invalid codec, missing profile".into());
189                }
190
191                let profile = u8::from_str_radix(splits[1], 16)
192                    .map_err(|e| format!("invalid codec, invalid profile: {}, {}", splits[1], e))?;
193
194                let level = u8::from_str_radix(splits[2], 16)
195                    .map_err(|e| format!("invalid codec, invalid level: {}, {}", splits[2], e))?;
196
197                let tier = match splits[3] {
198                    "H" => true,
199                    "M" => false,
200                    _ => return Err(format!("invalid codec, invalid tier: {}", splits[3])),
201                };
202
203                let depth = splits[4]
204                    .parse::<u8>()
205                    .map_err(|e| format!("invalid codec, invalid depth: {}, {}", splits[4], e))?;
206
207                let monochrome = match splits[5] {
208                    "1" => true,
209                    "0" => false,
210                    _ => return Err(format!("invalid codec, invalid monochrome: {}", splits[5])),
211                };
212
213                let sub_sampling_x = match splits[6] {
214                    "1" => true,
215                    "0" => false,
216                    _ => {
217                        return Err(format!("invalid codec, invalid sub_sampling_x: {}", splits[6]));
218                    }
219                };
220
221                let sub_sampling_y = match splits[7] {
222                    "1" => true,
223                    "0" => false,
224                    _ => {
225                        return Err(format!("invalid codec, invalid sub_sampling_y: {}", splits[7]));
226                    }
227                };
228
229                let color_primaries = splits[8]
230                    .parse::<u8>()
231                    .map_err(|e| format!("invalid codec, invalid color_primaries: {}, {}", splits[8], e))?;
232
233                let transfer_characteristics = splits[9]
234                    .parse::<u8>()
235                    .map_err(|e| format!("invalid codec, invalid transfer_characteristics: {}, {}", splits[9], e))?;
236
237                let matrix_coefficients = splits[10]
238                    .parse::<u8>()
239                    .map_err(|e| format!("invalid codec, invalid matrix_coefficients: {}, {}", splits[10], e))?;
240
241                let full_range_flag = splits[11]
242                    .parse::<u8>()
243                    .map_err(|e| format!("invalid codec, invalid full_range_flag: {}, {}", splits[11], e))?
244                    == 1;
245
246                Ok(VideoCodec::Av1 {
247                    profile,
248                    level,
249                    tier,
250                    depth,
251                    monochrome,
252                    sub_sampling_x,
253                    sub_sampling_y,
254                    color_primaries,
255                    transfer_characteristics,
256                    matrix_coefficients,
257                    full_range_flag,
258                })
259            }
260            r => Err(format!("invalid codec, unknown type: {r}")),
261        }
262    }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub enum AudioCodec {
267    Aac { object_type: AudioObjectType },
268    Opus,
269}
270
271impl fmt::Display for AudioCodec {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        match self {
274            AudioCodec::Aac { object_type } => write!(f, "mp4a.40.{}", u16::from(*object_type)),
275            AudioCodec::Opus => write!(f, "opus"),
276        }
277    }
278}
279
280impl FromStr for AudioCodec {
281    type Err = String;
282
283    fn from_str(s: &str) -> Result<Self, Self::Err> {
284        let splits = s.split('.').collect::<Vec<_>>();
285        if splits.is_empty() {
286            return Err("invalid codec, empty string".into());
287        }
288
289        match splits[0] {
290            "mp4a" => {
291                if splits.len() < 3 {
292                    return Err("invalid codec, missing object type".into());
293                }
294
295                let object_type = splits[2]
296                    .parse::<u16>()
297                    .map_err(|e| format!("invalid codec, invalid object type: {}, {}", splits[2], e))?;
298
299                Ok(AudioCodec::Aac {
300                    object_type: AudioObjectType::from(object_type),
301                })
302            }
303            "opus" => Ok(AudioCodec::Opus),
304            r => Err(format!("invalid codec, unknown type: {r}")),
305        }
306    }
307}