use crate::Result;
use crate::catalog::{Audio, Chat, User, Video};
use serde::{Deserialize, Serialize};
#[serde_with::serde_as]
#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[serde(default, rename_all = "camelCase")]
pub struct Catalog {
#[serde(default)]
pub video: Video,
#[serde(default)]
pub audio: Audio,
#[serde(default)]
pub user: Option<User>,
#[serde(default)]
pub chat: Option<Chat>,
#[serde(default)]
pub preview: Option<moq_net::Track>,
}
impl Catalog {
pub const DEFAULT_NAME: &str = "catalog.json";
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<Self> {
Ok(serde_json::from_str(s)?)
}
pub fn from_slice(v: &[u8]) -> Result<Self> {
Ok(serde_json::from_slice(v)?)
}
pub fn from_reader(reader: impl std::io::Read) -> Result<Self> {
Ok(serde_json::from_reader(reader)?)
}
pub fn to_string(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
pub fn to_string_pretty(&self) -> Result<String> {
Ok(serde_json::to_string_pretty(self)?)
}
pub fn to_vec(&self) -> Result<Vec<u8>> {
Ok(serde_json::to_vec(self)?)
}
pub fn to_writer(&self, writer: impl std::io::Write) -> Result<()> {
Ok(serde_json::to_writer(writer, self)?)
}
pub fn default_track() -> moq_net::Track {
moq_net::Track {
name: Catalog::DEFAULT_NAME.to_string(),
priority: 100,
}
}
}
#[cfg(test)]
mod test {
use std::collections::BTreeMap;
use crate::catalog::{AudioCodec::Opus, AudioConfig, Container, H264, VideoConfig};
use super::*;
#[test]
fn simple() {
let mut encoded = r#"{
"video": {
"renditions": {
"video": {
"codec": "avc1.64001f",
"codedWidth": 1280,
"codedHeight": 720,
"bitrate": 6000000,
"framerate": 30.0,
"container": {"kind": "legacy"}
}
}
},
"audio": {
"renditions": {
"audio": {
"codec": "opus",
"sampleRate": 48000,
"numberOfChannels": 2,
"bitrate": 128000,
"container": {"kind": "legacy"}
}
}
}
}"#
.to_string();
encoded.retain(|c| !c.is_whitespace());
let mut video_config = VideoConfig::new(H264 {
profile: 0x64,
constraints: 0x00,
level: 0x1f,
inline: false,
});
video_config.coded_width = Some(1280);
video_config.coded_height = Some(720);
video_config.bitrate = Some(6_000_000);
video_config.framerate = Some(30.0);
video_config.container = Container::Legacy;
let mut video_renditions = BTreeMap::new();
video_renditions.insert("video".to_string(), video_config);
let mut audio_config = AudioConfig::new(Opus, 48_000, 2);
audio_config.bitrate = Some(128_000);
audio_config.container = Container::Legacy;
let mut audio_renditions = BTreeMap::new();
audio_renditions.insert("audio".to_string(), audio_config);
let decoded = Catalog {
video: Video {
renditions: video_renditions,
display: None,
rotation: None,
flip: None,
},
audio: Audio {
renditions: audio_renditions,
},
..Default::default()
};
let output = Catalog::from_str(&encoded).expect("failed to decode");
assert_eq!(decoded, output, "wrong decoded output");
let output = decoded.to_string().expect("failed to encode");
assert_eq!(encoded, output, "wrong encoded output");
}
}