use std::collections::BTreeMap;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use serde_with::base64::Base64;
use serde_with::serde_as;
use crate::catalog::hang::CatalogExt;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Mpegts {
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub tracks: BTreeMap<String, Track>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub program_descriptors: Vec<Descriptor>,
}
impl Mpegts {
pub fn is_empty(&self) -> bool {
self.tracks.is_empty() && self.program_descriptors.is_empty()
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Track {
pub pid: u16,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub descriptors: Vec<Descriptor>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub verbatim: Option<Verbatim>,
}
impl Track {
pub fn new(pid: u16) -> Self {
Self {
pid,
descriptors: Vec::new(),
verbatim: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Verbatim {
pub stream_type: u8,
#[serde(default)]
pub framing: Framing,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream_id: Option<u8>,
}
impl Verbatim {
pub fn new(stream_type: u8, framing: Framing) -> Self {
Self {
stream_type,
framing,
stream_id: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum Framing {
#[default]
Pes,
Section,
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Descriptor {
pub tag: u8,
#[serde_as(as = "Base64")]
pub data: Bytes,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct Ext {
#[serde(default, skip_serializing_if = "Mpegts::is_empty")]
pub mpegts: Mpegts,
}
impl CatalogExt for Ext {}
pub trait Catalog: CatalogExt {
fn mpegts_mut(&mut self) -> Option<&mut Mpegts>;
}
impl Catalog for () {
fn mpegts_mut(&mut self) -> Option<&mut Mpegts> {
None
}
}
impl Catalog for crate::catalog::hang::Extra {
fn mpegts_mut(&mut self) -> Option<&mut Mpegts> {
None
}
}
impl Catalog for Ext {
fn mpegts_mut(&mut self) -> Option<&mut Mpegts> {
Some(&mut self.mpegts)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_section_omitted() {
let ext = Ext::default();
assert_eq!(serde_json::to_string(&ext).unwrap(), "{}");
}
#[test]
fn section_roundtrip() {
let mut mpegts = Mpegts::default();
mpegts.tracks.insert(
"audio".to_string(),
Track {
pid: 0x101,
descriptors: vec![Descriptor {
tag: 0x0a,
data: Bytes::from_static(b"eng\x00"),
}],
verbatim: None,
},
);
mpegts.tracks.insert(
".scte35".to_string(),
Track {
pid: 0x102,
descriptors: Vec::new(),
verbatim: Some(Verbatim::new(0x86, Framing::Section)),
},
);
mpegts.program_descriptors.push(Descriptor {
tag: 0x05,
data: Bytes::from_static(b"CUEI"),
});
let json = serde_json::to_string(&Ext { mpegts: mpegts.clone() }).unwrap();
assert!(json.contains("\"Q1VFSQ==\""), "descriptor data is base64: {json}");
let parsed: Ext = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.mpegts, mpegts, "mpegts section round-trips");
}
}