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