use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::Result;
pub const DEFAULT_NAME: &str = "timeline.z";
pub trait RecordExt: serde::Serialize + serde::de::DeserializeOwned + Default + Clone + Send + Unpin + 'static {}
impl RecordExt for () {}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Range {
pub start: u64,
pub end: u64,
#[serde(default = "Range::default_keyframe", skip_serializing_if = "Clone::clone")]
pub keyframe: bool,
}
impl Range {
fn default_keyframe() -> bool {
true
}
pub fn new(start: u64, end: u64) -> Self {
Self {
start,
end,
keyframe: true,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(
rename_all = "camelCase",
bound(serialize = "E: serde::Serialize", deserialize = "E: serde::de::DeserializeOwned")
)]
pub struct Record<E: RecordExt = ()> {
pub segment: u64,
pub pts: u64,
pub duration: u64,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub tracks: BTreeMap<String, Vec<Range>>,
#[serde(flatten)]
pub ext: E,
}
impl<E: RecordExt> Record<E> {
pub fn new(segment: u64, pts: u64, duration: u64) -> Self {
Self {
segment,
pts,
duration,
tracks: BTreeMap::new(),
ext: E::default(),
}
}
pub fn from_slice(v: &[u8]) -> Result<Self> {
Ok(serde_json::from_slice(v)?)
}
pub fn to_vec(&self) -> Result<Vec<u8>> {
Ok(serde_json::to_vec(self)?)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn roundtrip() {
let mut record = Record::<()>::new(3, 84_000, 6_000);
record.tracks.insert("video".to_string(), vec![Range::new(42, 42)]);
record
.tracks
.insert("audio".to_string(), vec![Range::new(512, 540), Range::new(550, 560)]);
let json = record.to_vec().unwrap();
assert_eq!(
std::str::from_utf8(&json).unwrap(),
concat!(
r#"{"segment":3,"pts":84000,"duration":6000,"tracks":{"#,
r#""audio":[{"start":512,"end":540},{"start":550,"end":560}],"#,
r#""video":[{"start":42,"end":42}]}}"#
)
);
assert_eq!(Record::<()>::from_slice(&json).unwrap(), record);
}
#[test]
fn keyframe_false_roundtrips_and_true_is_omitted() {
let mut range = Range::new(7, 9);
range.keyframe = false;
let mut record = Record::<()>::new(0, 0, 2_000);
record.tracks.insert("video".to_string(), vec![range]);
let json = record.to_vec().unwrap();
assert_eq!(
std::str::from_utf8(&json).unwrap(),
r#"{"segment":0,"pts":0,"duration":2000,"tracks":{"video":[{"start":7,"end":9,"keyframe":false}]}}"#
);
let decoded = Record::<()>::from_slice(&json).unwrap();
assert_eq!(decoded, record);
let plain: Record =
Record::from_slice(br#"{"segment":0,"pts":0,"duration":1,"tracks":{"v":[{"start":1,"end":1}]}}"#).unwrap();
assert!(plain.tracks["v"][0].keyframe);
}
#[test]
fn empty_tracks_is_omitted() {
let record = Record::<()>::new(1, 1_000, 500);
let json = record.to_vec().unwrap();
assert_eq!(
std::str::from_utf8(&json).unwrap(),
r#"{"segment":1,"pts":1000,"duration":500}"#
);
assert_eq!(Record::<()>::from_slice(&json).unwrap(), record);
}
#[test]
fn typed_extension_flattens() {
#[derive(serde::Serialize, serde::Deserialize, Default, Clone, PartialEq, Debug)]
struct Ext {
#[serde(skip_serializing_if = "std::ops::Not::not", default)]
discontinuity: bool,
}
impl RecordExt for Ext {}
let record = Record {
segment: 2,
pts: 14_000,
duration: 2_000,
tracks: BTreeMap::new(),
ext: Ext { discontinuity: true },
};
let json = record.to_vec().unwrap();
assert_eq!(
std::str::from_utf8(&json).unwrap(),
r#"{"segment":2,"pts":14000,"duration":2000,"discontinuity":true}"#
);
assert_eq!(Record::<Ext>::from_slice(&json).unwrap(), record);
}
}