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