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