1use core::fmt;
4use std::io;
5
6use bytes::Bytes;
7use scuffle_amf0::de::MultiValue;
8use scuffle_amf0::decoder::Amf0Decoder;
9use scuffle_amf0::{Amf0Object, Amf0Value};
10use scuffle_bytes_util::{BytesCursorExt, StringCow};
11use serde::de::VariantAccess;
12
13use crate::audio::header::enhanced::AudioFourCc;
14use crate::audio::header::legacy::SoundFormat;
15use crate::error::FlvError;
16use crate::video::header::enhanced::VideoFourCc;
17use crate::video::header::legacy::VideoCodecId;
18
19#[derive(Debug, Clone, PartialEq)]
24pub enum OnMetaDataAudioCodecId {
25 Legacy(SoundFormat),
27 Enhanced(AudioFourCc),
29}
30
31impl<'de> serde::Deserialize<'de> for OnMetaDataAudioCodecId {
32 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
33 where
34 D: serde::Deserializer<'de>,
35 {
36 let n: u32 = serde::Deserialize::deserialize(deserializer)?;
37
38 if n > u8::MAX as u32 {
43 Ok(Self::Enhanced(AudioFourCc::from(n.to_be_bytes())))
44 } else {
45 Ok(Self::Legacy(SoundFormat::from(n as u8)))
46 }
47 }
48}
49
50#[derive(Debug, Clone, PartialEq)]
55pub enum OnMetaDataVideoCodecId {
56 Legacy(VideoCodecId),
58 Enhanced(VideoFourCc),
60}
61
62impl<'de> serde::Deserialize<'de> for OnMetaDataVideoCodecId {
63 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
64 where
65 D: serde::Deserializer<'de>,
66 {
67 let n: u32 = serde::Deserialize::deserialize(deserializer)?;
68
69 if n > u8::MAX as u32 {
74 Ok(Self::Enhanced(VideoFourCc::from(n.to_be_bytes())))
75 } else {
76 Ok(Self::Legacy(VideoCodecId::from(n as u8)))
77 }
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
87#[serde(rename_all = "camelCase", bound = "'a: 'de")]
88pub struct OnMetaData<'a> {
89 #[serde(default)]
91 pub audiocodecid: Option<OnMetaDataAudioCodecId>,
92 #[serde(default)]
94 pub audiodatarate: Option<f64>,
95 #[serde(default)]
97 pub audiodelay: Option<f64>,
98 #[serde(default)]
100 pub audiosamplerate: Option<f64>,
101 #[serde(default)]
103 pub audiosamplesize: Option<f64>,
104 #[serde(default)]
106 pub can_seek_to_end: Option<bool>,
107 #[serde(default)]
109 pub creationdate: Option<String>,
110 #[serde(default)]
112 pub duration: Option<f64>,
113 #[serde(default)]
115 pub filesize: Option<f64>,
116 #[serde(default)]
118 pub framerate: Option<f64>,
119 #[serde(default)]
121 pub height: Option<f64>,
122 #[serde(default)]
124 pub stereo: Option<bool>,
125 #[serde(default)]
127 pub videocodecid: Option<OnMetaDataVideoCodecId>,
128 #[serde(default)]
130 pub videodatarate: Option<f64>,
131 #[serde(default)]
133 pub width: Option<f64>,
134 #[serde(default, borrow)]
166 pub audio_track_id_info_map: Option<Amf0Object<'a>>,
167 #[serde(default, borrow)]
169 pub video_track_id_info_map: Option<Amf0Object<'a>>,
170 #[serde(flatten, borrow)]
172 pub other: Amf0Object<'a>,
173}
174
175#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
180#[serde(rename_all = "camelCase", bound = "'a: 'de")]
181pub struct OnXmpData<'a> {
182 #[serde(default, rename = "liveXML")]
186 live_xml: Option<StringCow<'a>>,
187 #[serde(flatten, borrow)]
189 other: Amf0Object<'a>,
190}
191
192#[derive(Debug, Clone, PartialEq)]
197pub enum ScriptData<'a> {
198 OnMetaData(Box<OnMetaData<'a>>),
202 OnXmpData(OnXmpData<'a>),
204 Other {
206 name: StringCow<'a>,
208 data: Vec<Amf0Value<'static>>,
210 },
211}
212
213impl<'de> serde::Deserialize<'de> for ScriptData<'de> {
214 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215 where
216 D: serde::Deserializer<'de>,
217 {
218 struct Visitor;
219
220 const SCRIPT_DATA: &str = "ScriptData";
221 const ON_META_DATA: &str = "onMetaData";
222 const ON_XMP_DATA: &str = "onXMPData";
223
224 impl<'de> serde::de::Visitor<'de> for Visitor {
225 type Value = ScriptData<'de>;
226
227 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
228 formatter.write_str(SCRIPT_DATA)
229 }
230
231 fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
232 where
233 A: serde::de::EnumAccess<'de>,
234 {
235 let (name, content): (StringCow<'de>, A::Variant) = data.variant()?;
236
237 match name.as_ref() {
238 ON_META_DATA => Ok(ScriptData::OnMetaData(Box::new(content.newtype_variant()?))),
239 ON_XMP_DATA => Ok(ScriptData::OnXmpData(content.newtype_variant()?)),
240 _ => Ok(ScriptData::Other {
241 name,
242 data: content
243 .newtype_variant::<MultiValue<Vec<Amf0Value>>>()?
244 .0
245 .into_iter()
246 .map(|v| v.into_owned())
247 .collect(),
248 }),
249 }
250 }
251 }
252
253 deserializer.deserialize_enum(SCRIPT_DATA, &[ON_META_DATA, ON_XMP_DATA], Visitor)
254 }
255}
256
257impl ScriptData<'_> {
258 pub fn demux(reader: &mut io::Cursor<Bytes>) -> Result<Self, FlvError> {
260 let buf = reader.extract_remaining();
261 let mut decoder = Amf0Decoder::from_buf(buf);
262
263 serde::de::Deserialize::deserialize(&mut decoder).map_err(FlvError::Amf0)
264 }
265}
266
267#[cfg(test)]
268#[cfg_attr(all(test, coverage_nightly), coverage(off))]
269mod tests {
270 use scuffle_amf0::Amf0Marker;
271 use scuffle_amf0::encoder::Amf0Encoder;
272
273 use super::*;
274
275 #[test]
276 fn script_on_meta_data() {
277 let width = 1280.0f64.to_be_bytes();
278 #[rustfmt::skip]
279 let data = [
280 Amf0Marker::String as u8,
281 0, 10, b'o', b'n', b'M', b'e', b't', b'a', b'D', b'a', b't', b'a',Amf0Marker::Object as u8,
284 0, 5, b'w', b'i', b'd', b't', b'h', Amf0Marker::Number as u8,
287 width[0],
288 width[1],
289 width[2],
290 width[3],
291 width[4],
292 width[5],
293 width[6],
294 width[7],
295 0, 0, Amf0Marker::ObjectEnd as u8,
296 ];
297
298 let mut reader = io::Cursor::new(Bytes::from_owner(data));
299
300 let script_data = ScriptData::demux(&mut reader).unwrap();
301
302 let ScriptData::OnMetaData(metadata) = script_data else {
303 panic!("expected onMetaData");
304 };
305
306 assert_eq!(
307 *metadata,
308 OnMetaData {
309 audiocodecid: None,
310 audiodatarate: None,
311 audiodelay: None,
312 audiosamplerate: None,
313 audiosamplesize: None,
314 can_seek_to_end: None,
315 creationdate: None,
316 duration: None,
317 filesize: None,
318 framerate: None,
319 height: None,
320 stereo: None,
321 videocodecid: None,
322 videodatarate: None,
323 width: Some(1280.0),
324 audio_track_id_info_map: None,
325 video_track_id_info_map: None,
326 other: Amf0Object::new(),
327 }
328 );
329 }
330
331 #[test]
332 fn script_on_meta_data_full() {
333 let mut data = Vec::new();
334 let mut encoder = Amf0Encoder::new(&mut data);
335
336 let audio_track_id_info_map = [("test".into(), Amf0Value::Number(1.0))].into_iter().collect();
337 let video_track_id_info_map = [("test2".into(), Amf0Value::Number(2.0))].into_iter().collect();
338
339 encoder.encode_string("onMetaData").unwrap();
340 let object: Amf0Object = [
341 (
342 "audiocodecid".into(),
343 Amf0Value::Number(u32::from_be_bytes(AudioFourCc::Aac.0) as f64),
344 ),
345 ("audiodatarate".into(), Amf0Value::Number(128.0)),
346 ("audiodelay".into(), Amf0Value::Number(0.0)),
347 ("audiosamplerate".into(), Amf0Value::Number(44100.0)),
348 ("audiosamplesize".into(), Amf0Value::Number(16.0)),
349 ("canSeekToEnd".into(), Amf0Value::Boolean(true)),
350 ("creationdate".into(), Amf0Value::String("2025-01-01T00:00:00Z".into())),
351 ("duration".into(), Amf0Value::Number(60.0)),
352 ("filesize".into(), Amf0Value::Number(1024.0)),
353 ("framerate".into(), Amf0Value::Number(30.0)),
354 ("height".into(), Amf0Value::Number(720.0)),
355 ("stereo".into(), Amf0Value::Boolean(true)),
356 (
357 "videocodecid".into(),
358 Amf0Value::Number(u32::from_be_bytes(VideoFourCc::Avc.0) as f64),
359 ),
360 ("videodatarate".into(), Amf0Value::Number(1024.0)),
361 ("width".into(), Amf0Value::Number(1280.0)),
362 ("audioTrackIdInfoMap".into(), Amf0Value::Object(audio_track_id_info_map)),
363 ("videoTrackIdInfoMap".into(), Amf0Value::Object(video_track_id_info_map)),
364 ]
365 .into_iter()
366 .collect();
367 encoder.encode_object(&object).unwrap();
368
369 let mut reader = io::Cursor::new(Bytes::from_owner(data));
370 let script_data = ScriptData::demux(&mut reader).unwrap();
371
372 let ScriptData::OnMetaData(metadata) = script_data else {
373 panic!("expected onMetaData");
374 };
375
376 assert_eq!(
377 *metadata,
378 OnMetaData {
379 audiocodecid: Some(OnMetaDataAudioCodecId::Enhanced(AudioFourCc::Aac)),
380 audiodatarate: Some(128.0),
381 audiodelay: Some(0.0),
382 audiosamplerate: Some(44100.0),
383 audiosamplesize: Some(16.0),
384 can_seek_to_end: Some(true),
385 creationdate: Some("2025-01-01T00:00:00Z".to_string()),
386 duration: Some(60.0),
387 filesize: Some(1024.0),
388 framerate: Some(30.0),
389 height: Some(720.0),
390 stereo: Some(true),
391 videocodecid: Some(OnMetaDataVideoCodecId::Enhanced(VideoFourCc::Avc)),
392 videodatarate: Some(1024.0),
393 width: Some(1280.0),
394 audio_track_id_info_map: Some([("test".into(), Amf0Value::Number(1.0))].into_iter().collect()),
395 video_track_id_info_map: Some([("test2".into(), Amf0Value::Number(2.0))].into_iter().collect()),
396 other: Amf0Object::new(),
397 }
398 );
399 }
400
401 #[test]
402 fn script_on_xmp_data() {
403 #[rustfmt::skip]
404 let data = [
405 Amf0Marker::String as u8,
406 0, 9, b'o', b'n', b'X', b'M', b'P', b'D', b'a', b't', b'a',Amf0Marker::Object as u8,
409 0, 7, b'l', b'i', b'v', b'e', b'X', b'M', b'L', Amf0Marker::String as u8,
412 0, 5, b'h', b'e', b'l', b'l', b'o', 0, 4, b't', b'e', b's', b't', Amf0Marker::Null as u8,
417 0, 0, Amf0Marker::ObjectEnd as u8,
418 ];
419
420 let mut reader = io::Cursor::new(Bytes::from_owner(data));
421
422 let script_data = ScriptData::demux(&mut reader).unwrap();
423
424 let ScriptData::OnXmpData(xmp_data) = script_data else {
425 panic!("expected onXMPData");
426 };
427
428 assert_eq!(
429 xmp_data,
430 OnXmpData {
431 live_xml: Some("hello".into()),
432 other: [("test".into(), Amf0Value::Null)].into_iter().collect(),
433 }
434 );
435 }
436
437 #[test]
438 fn script_other() {
439 #[rustfmt::skip]
440 let data = [
441 Amf0Marker::String as u8,
442 0, 10, b'o', b'n', b'W', b'h', b'a', b't', b'e', b'v', b'e', b'r',Amf0Marker::Object as u8,
445 0, 4, b't', b'e', b's', b't', Amf0Marker::String as u8,
448 0, 5, b'h', b'e', b'l', b'l', b'o', 0, 0, Amf0Marker::ObjectEnd as u8,
451 ];
452
453 let mut reader = io::Cursor::new(Bytes::from_owner(data));
454
455 let script_data = ScriptData::demux(&mut reader).unwrap();
456
457 let ScriptData::Other { name, data } = script_data else {
458 panic!("expected onXMPData");
459 };
460
461 let object: Amf0Object = [("test".into(), Amf0Value::String("hello".into()))].into_iter().collect();
462
463 assert_eq!(name, "onWhatever");
464 assert_eq!(data.len(), 1);
465 assert_eq!(data[0], Amf0Value::Object(object));
466 }
467}