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 { profile: u8, constraint_set: u8, level: u8 },
10 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 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, profile_compatibility,
78 if *tier { 'H' } else { 'L' },
79 level, 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}