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: u32,
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.{: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, profile_compatibility,
62 if *tier { 'H' } else { 'L' },
63 level, 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}