1use mp4ra_rust::{ObjectTypeIdentifier, SampleEntryCode};
71use mpeg4_audio_const::AudioObjectType;
72use std::convert::TryFrom;
73use std::fmt;
74use std::str::FromStr;
75
76#[derive(Debug)]
77#[non_exhaustive]
78pub enum Codec {
79 Avc1(Avc1),
80 Mp4a(Mp4a),
81 Unknown(String),
82}
83impl Codec {
84 pub fn parse_codecs(codecs: &str) -> impl Iterator<Item = Result<Codec, CodecError>> + '_ {
85 codecs.split(',').map(|s| s.trim().parse())
86 }
87
88 pub fn avc1(profile: u8, constraints: u8, level: u8) -> Self {
89 Codec::Avc1(Avc1 {
90 profile,
91 constraints,
92 level,
93 })
94 }
95}
96impl FromStr for Codec {
97 type Err = CodecError;
98
99 fn from_str(codec: &str) -> Result<Codec, Self::Err> {
100 if let Some(pos) = codec.find('.') {
101 let (fourcc, rest) = codec.split_at(pos);
102 if fourcc.len() != 4 {
103 return Ok(Codec::Unknown(codec.to_string()));
104 }
105 let fourcc = mp4ra_rust::FourCC::from(fourcc.as_bytes());
106 let sample_entry = SampleEntryCode::from(fourcc);
107 match sample_entry {
108 SampleEntryCode::MP4A => Ok(Codec::Mp4a(get_rest(rest)?.parse()?)),
109 SampleEntryCode::AVC1 => Ok(Codec::Avc1(get_rest(rest)?.parse()?)),
110 _ => Ok(Codec::Unknown(codec.to_owned())),
111 }
112 } else {
113 Err(CodecError::ExpectedHierarchySeparator(codec.to_string()))
114 }
115 }
116}
117impl fmt::Display for Codec {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
119 match self {
120 Codec::Avc1(Avc1 {
121 profile,
122 constraints,
123 level,
124 }) => write!(f, "avc1.{:02X}{:02X}{:02X}", profile, constraints, level),
125 Codec::Mp4a(mp4a) => write!(f, "mp4a.{}", mp4a),
126 Codec::Unknown(val) => f.write_str(val),
127 }
128 }
129}
130
131fn get_rest(text: &str) -> Result<&str, CodecError> {
132 if text.is_empty() {
133 Ok(text)
134 } else if let Some(rest) = text.strip_prefix('.') {
135 Ok(rest)
136 } else {
137 Err(CodecError::ExpectedHierarchySeparator(text.to_string()))
138 }
139}
140
141#[derive(Debug)]
142pub enum CodecError {
143 InvalidComponent(String),
145 ExpectedHierarchySeparator(String),
147 UnexpectedLength { expected: usize, got: String },
149}
150
151#[derive(Debug)]
152pub struct Avc1 {
153 profile: u8,
154 constraints: u8,
155 level: u8,
156}
157impl Avc1 {
158 pub fn profile(&self) -> u8 {
159 self.profile
160 }
161 pub fn constraints(&self) -> u8 {
162 self.constraints
163 }
164 pub fn level(&self) -> u8 {
165 self.level
166 }
167}
168impl FromStr for Avc1 {
169 type Err = CodecError;
170
171 fn from_str(value: &str) -> Result<Self, Self::Err> {
172 if value.len() != 6 {
173 return Err(CodecError::UnexpectedLength {
174 expected: 6,
175 got: value.to_string(),
176 });
177 }
178
179 let profile = u8::from_str_radix(&value[0..2], 16)
180 .map_err(|_| CodecError::InvalidComponent(value.to_string()))?;
181
182 let constraints = u8::from_str_radix(&value[2..4], 16)
183 .map_err(|_| CodecError::InvalidComponent(value.to_string()))?;
184
185 let level = u8::from_str_radix(&value[4..6], 16)
186 .map_err(|_| CodecError::InvalidComponent(value.to_string()))?;
187
188 Ok(Avc1 {
189 profile,
190 constraints,
191 level,
192 })
193 }
194}
195
196#[derive(Debug)]
197#[non_exhaustive]
198pub enum Mp4a {
199 Mpeg4Audio {
200 audio_object_type: Option<AudioObjectType>,
201 },
202 Unknown {
203 object_type_indication: ObjectTypeIdentifier,
204 audio_object_type_indication: Option<u8>,
205 },
206}
207impl fmt::Display for Mp4a {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 match self {
210 Mp4a::Mpeg4Audio { audio_object_type } => {
211 write!(
212 f,
213 "{:02x}",
214 u8::from(ObjectTypeIdentifier::AUDIO_ISO_IEC_14496_3)
215 )?;
216 if let Some(aoti) = audio_object_type {
217 write!(f, ".{}", u8::from(*aoti))?;
218 }
219 Ok(())
220 }
221 Mp4a::Unknown {
222 object_type_indication,
223 audio_object_type_indication,
224 } => {
225 write!(f, "{:02x}", u8::from(*object_type_indication))?;
226 if let Some(aoti) = audio_object_type_indication {
227 write!(f, ".{}", aoti)?;
228 }
229 Ok(())
230 }
231 }
232 }
233}
234
235impl FromStr for Mp4a {
236 type Err = CodecError;
237
238 fn from_str(value: &str) -> Result<Self, Self::Err> {
239 let mut i = value.splitn(2, '.');
240 let s = i.next().unwrap();
241 let oti =
242 u8::from_str_radix(s, 16).map_err(|_| CodecError::InvalidComponent(s.to_string()))?;
243 let oti = ObjectTypeIdentifier::from(oti);
244 let aoti = i
245 .next()
246 .map(u8::from_str)
247 .transpose()
248 .map_err(|e| CodecError::InvalidComponent(e.to_string()))?;
249 match oti {
250 ObjectTypeIdentifier::AUDIO_ISO_IEC_14496_3 => {
251 let aoti = aoti
252 .map(AudioObjectType::try_from)
253 .transpose()
254 .map_err(|_e| CodecError::InvalidComponent(aoti.unwrap().to_string()))?;
255 Ok(Mp4a::Mpeg4Audio {
256 audio_object_type: aoti,
257 })
258 }
259 _ => Ok(Mp4a::Unknown {
260 object_type_indication: oti,
261 audio_object_type_indication: aoti,
262 }),
263 }
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use assert_matches::*;
271
272 fn roundtrip(codec: &str) {
273 assert_eq!(codec, Codec::from_str(codec).unwrap().to_string())
274 }
275
276 #[test]
277 fn mp4a() {
278 assert_matches!(
279 Codec::from_str("mp4a.40.3"),
280 Ok(Codec::Mp4a(Mp4a::Mpeg4Audio {
281 audio_object_type: Some(AudioObjectType::AAC_SSR)
282 }))
283 );
284 roundtrip("mp4a.40.3");
285 }
286
287 #[test]
288 fn unknown_oti() {
289 const RESERVED_X41: ObjectTypeIdentifier = ObjectTypeIdentifier(0x41);
290 assert_matches!(
291 Codec::from_str("mp4a.41"),
292 Ok(Codec::Mp4a(Mp4a::Unknown {
293 object_type_indication: RESERVED_X41,
294 audio_object_type_indication: None
295 }))
296 );
297 roundtrip("mp4a.41");
298 }
299
300 #[test]
301 fn bad_oti_digit() {
302 assert_matches!(Codec::from_str("mp4a.4g"), Err(_));
303 }
304
305 #[test]
306 fn list() {
307 let mut i = Codec::parse_codecs("mp4a.40.2,avc1.4d401e");
308 assert_matches!(
309 i.next().unwrap(),
310 Ok(Codec::Mp4a(Mp4a::Mpeg4Audio {
311 audio_object_type: Some(AudioObjectType::AAC_LC)
312 }))
313 );
314 assert_matches!(
315 i.next().unwrap(),
316 Ok(Codec::Avc1(Avc1 {
317 profile: 0x4d,
318 constraints: 0x40,
319 level: 0x1e
320 }))
321 );
322 }
323
324 #[test]
325 fn avc1() {
326 assert_matches!(
327 Codec::from_str("avc1.4d401e"),
328 Ok(Codec::Avc1(Avc1 {
329 profile: 0x4d,
330 constraints: 0x40,
331 level: 0x1e
332 }))
333 );
334 roundtrip("avc1.4D401E");
335 }
336
337 #[test]
338 fn bad_avc1_lengths() {
339 assert_matches!(Codec::from_str("avc1.41141"), Err(CodecError::UnexpectedLength { expected: 6, got: text }) if text == "41141");
340 assert_matches!(Codec::from_str("avc1.4114134"), Err(CodecError::UnexpectedLength { expected: 6, got: text }) if text == "4114134");
341 }
342
343 #[test]
344 fn unknown_fourcc() {
345 assert_matches!(Codec::from_str("badd.41"), Ok(Codec::Unknown(v)) if v == "badd.41");
346 roundtrip("badd.41");
347 }
348
349 #[test]
350 fn invalid_unicode_boundary() {
351 assert!(Codec::from_str("cod👍ec").is_err())
354 }
355}