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_lite::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_lite::Track {
moq_lite::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_renditions = BTreeMap::new();
video_renditions.insert(
"video".to_string(),
VideoConfig {
codec: H264 {
profile: 0x64,
constraints: 0x00,
level: 0x1f,
inline: false,
}
.into(),
description: None,
coded_width: Some(1280),
coded_height: Some(720),
display_ratio_width: None,
display_ratio_height: None,
bitrate: Some(6_000_000),
framerate: Some(30.0),
optimize_for_latency: None,
container: Container::Legacy,
jitter: None,
},
);
let mut audio_renditions = BTreeMap::new();
audio_renditions.insert(
"audio".to_string(),
AudioConfig {
codec: Opus,
sample_rate: 48_000,
channel_count: 2,
bitrate: Some(128_000),
description: None,
container: Container::Legacy,
jitter: None,
},
);
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");
}
}