use yog_data::with::Raw;
mod clock;
pub use clock::{Clock, Plan};
#[derive(yog_data::Schema, Debug, Clone, PartialEq)]
pub struct VideoDef {
pub id: String,
pub width: u32,
pub height: u32,
pub fps_num: u32,
pub fps_den: u32,
pub sizes: Vec<u32>,
#[data(with = "Raw")]
pub packets: Vec<u8>,
pub keyframes: Vec<u32>,
pub sound: Option<String>,
pub volume: f32,
pub looping: bool,
}
impl VideoDef {
#[must_use]
pub fn frames(&self) -> u32 {
self.sizes.len() as u32
}
#[must_use]
pub fn frame_seconds(&self) -> f64 {
f64::from(self.fps_den) / f64::from(self.fps_num.max(1))
}
#[must_use]
pub fn seconds(&self) -> f64 {
f64::from(self.frames()) * self.frame_seconds()
}
pub fn packets(&self) -> impl Iterator<Item = &[u8]> {
let mut at = 0usize;
self.sizes.iter().map(move |&n| {
let end = (at + n as usize).min(self.packets.len());
let p = &self.packets[at.min(end)..end];
at = end;
p
})
}
#[must_use]
pub fn packet(&self, frame: u32) -> Option<&[u8]> {
let i = frame as usize;
let size = *self.sizes.get(i)? as usize;
let start: usize = self.sizes[..i].iter().map(|&n| n as usize).sum();
self.packets.get(start..start + size)
}
#[must_use]
pub fn keyframe_before(&self, frame: u32) -> u32 {
let i = self.keyframes.partition_point(|&k| k <= frame);
i.checked_sub(1).map_or(0, |i| self.keyframes[i])
}
}
#[cfg(test)]
mod tests {
use super::*;
fn def() -> VideoDef {
VideoDef {
id: "m:intro".into(),
width: 4,
height: 2,
fps_num: 20,
fps_den: 1,
sizes: vec![3, 1, 2],
packets: vec![1, 2, 3, 4, 5, 6],
keyframes: vec![0, 2],
sound: Some("m:video.intro".into()),
volume: 1.0,
looping: false,
}
}
#[test]
fn packets_are_cut_by_their_sizes() {
let v = def();
let p: Vec<&[u8]> = v.packets().collect();
assert_eq!(p, vec![&[1, 2, 3][..], &[4][..], &[5, 6][..]]);
assert_eq!(v.packet(2), Some(&[5, 6][..]));
assert_eq!(v.packet(3), None);
assert_eq!(v.frames(), 3);
assert!((v.seconds() - 0.15).abs() < 1e-9);
}
#[test]
fn a_seek_starts_at_the_keyframe_before() {
let v = def();
assert_eq!(
(
v.keyframe_before(0),
v.keyframe_before(1),
v.keyframe_before(2)
),
(0, 0, 2)
);
}
#[test]
fn it_crosses_as_yog_data() {
let v = def();
let bytes = yog_data::binary::encode(&yog_data::Schema::write(&v));
let back: VideoDef = yog_data::read_bytes(&bytes).unwrap();
assert_eq!(back, v);
}
}