#[cfg(test)]
mod test {
use crate::{Framerate, Ntsc, Timecode};
use num::Rational64;
use rstest::{fixture, rstest};
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer};
use std::fmt::{self, Debug, Formatter};
use std::fs;
use std::str::FromStr;
const PATH_TO_SEQUENCE_JSON: &str =
"zdevelop/tests/test-timelines/PPRO/Many Basic Edits/Many Basic Edits.json";
#[derive(Debug, Clone, Copy)]
struct MyRat(Rational64);
impl<'de> Deserialize<'de> for MyRat {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let rational_str = deserializer.deserialize_string(RatVisitor {})?;
let rat = match Rational64::from_str(&rational_str) {
Ok(parsed) => parsed,
Err(err) => {
return Err(D::Error::custom(format!("error parsing rational: {}", err)))
}
};
Ok(Self(rat))
}
}
struct RatVisitor {}
impl<'de> Visitor<'de> for RatVisitor {
type Value = String;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "a rational string in format 'x/y'")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v.to_string())
}
}
#[derive(Debug, Clone, Copy)]
struct MyDecimal(f64);
impl<'de> Deserialize<'de> for MyDecimal {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let float = deserializer.deserialize_string(DecimalStringVisitor {})?;
Ok(Self(float))
}
}
struct DecimalStringVisitor {}
impl<'de> Visitor<'de> for DecimalStringVisitor {
type Value = f64;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "a decimal string in format 'x.x'")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
let float = match v.parse::<f64>() {
Ok(parsed) => parsed,
Err(err) => return Err(E::custom(format!("error parsing float: {}", err))),
};
Ok(float)
}
}
#[derive(Deserialize)]
struct TimecodeInfo {
timebase: i32,
ntsc: bool,
drop_frame: bool,
frame_rate_frac: MyRat,
timecode: String,
frame: i64,
seconds_rational: MyRat,
ppro_ticks: i64,
feet_and_frames: String,
runtime: String,
}
#[derive(Deserialize)]
struct Event {
duration_frames: i64,
source_in: TimecodeInfo,
source_out: TimecodeInfo,
record_in: TimecodeInfo,
record_out: TimecodeInfo,
}
#[derive(Deserialize)]
struct Sequence {
start_time: TimecodeInfo,
total_duration_frames: i64,
events: Vec<Event>,
}
#[fixture]
fn test_sequence() -> Sequence {
let contents = fs::read_to_string(PATH_TO_SEQUENCE_JSON).unwrap();
serde_json::from_str(&contents).unwrap()
}
#[rstest]
fn test_event_parsing(test_sequence: Sequence) {
assert_eq!(
19560, test_sequence.total_duration_frames,
"test sequence duration expected"
);
assert_eq!(
Rational64::new(24000, 1001),
test_sequence.start_time.frame_rate_frac.0
);
assert_eq!(215, test_sequence.events.len(), "event count");
check_timecode_info(&test_sequence.start_time, -1, "start time");
let mut event_num = 0;
for event in test_sequence.events.iter() {
event_num += 1;
check_timecode_info(&event.record_in, event_num, "record in");
check_timecode_info(&event.record_out, event_num, "record out");
check_timecode_info(&event.source_in, event_num, "source in");
check_timecode_info(&event.source_out, event_num, "source out");
}
}
fn check_timecode_info(info: &TimecodeInfo, event_num: i32, tc_val: &str) {
let ntsc = if !info.ntsc {
Ntsc::None
} else if info.drop_frame {
Ntsc::DropFrame
} else {
Ntsc::NonDropFrame
};
let rate = Framerate::with_timebase(info.timebase, ntsc).unwrap();
let tc = Timecode::with_frames(&info.timecode, rate).unwrap();
check_parsed_timecode(tc, info, event_num, tc_val, &info.timecode, "timecode");
let tc = Timecode::with_frames(info.frame, rate).unwrap();
check_parsed_timecode(tc, info, event_num, tc_val, &info.frame, "frame numer");
let tc = Timecode::with_seconds(info.seconds_rational.0, rate).unwrap();
check_parsed_timecode(
tc,
info,
event_num,
tc_val,
&info.seconds_rational.0,
"seconds",
);
let tc = Timecode::with_seconds(&info.runtime, rate).unwrap();
check_parsed_timecode(tc, info, event_num, tc_val, &info.runtime, "runtime");
let tc = Timecode::with_premiere_ticks(info.ppro_ticks, rate).unwrap();
check_parsed_timecode(tc, info, event_num, tc_val, &info.ppro_ticks, "ppro ticks");
}
fn check_parsed_timecode<'a, T>(
tc: Timecode,
info: &TimecodeInfo,
event_num: i32,
tc_field: &str,
source: &'a T,
source_type: &str,
) where
T: 'a + Debug,
&'a T: Debug,
{
assert_eq!(
info.timecode,
tc.timecode(),
"event {} {} tc string from {:?} ({})",
event_num,
tc_field,
source,
source_type,
);
assert_eq!(
info.frame,
tc.frames(),
"event {} {} frame number from {:?} ({})",
event_num,
tc_field,
source,
source_type,
);
assert_eq!(
info.seconds_rational.0,
tc.seconds(),
"event {} {} seconds rational from {:?} ({})",
event_num,
tc_field,
source,
source_type,
);
assert_eq!(
info.feet_and_frames,
tc.feet_and_frames(crate::FilmFormat::FF35mm4perf),
"event {} {} feet and frames from {:?} ({})",
event_num,
tc_field,
source,
source_type,
);
assert_eq!(
info.runtime,
tc.runtime(9),
"event {} {} feet and frames from {:?} ({})",
event_num,
tc_field,
source,
source_type,
);
}
#[rstest]
fn test_sequence_totals(test_sequence: Sequence) {
let mut current_out = parse_timecode(&test_sequence.start_time);
let mut current_total = Timecode::with_frames(0, current_out.rate()).unwrap();
for event in test_sequence.events.iter() {
let rec_in = parse_timecode(&event.record_in);
let rec_out = parse_timecode(&event.record_out);
let src_in = parse_timecode(&event.source_in);
let src_out = parse_timecode(&event.source_out);
let rec_duration = rec_out - rec_in;
let src_duration: Timecode = src_out - src_in;
assert_eq!(event.duration_frames, rec_duration.frames(), "rec duration");
assert_eq!(event.duration_frames, src_duration.frames(), "src duration");
current_out += src_duration;
assert_eq!(
event.record_out.timecode,
current_out.timecode(),
"rec out total expected"
);
current_total += rec_duration;
}
assert_eq!(
test_sequence.total_duration_frames,
current_total.frames(),
"total frames"
)
}
fn parse_timecode(info: &TimecodeInfo) -> Timecode {
let ntsc = if !info.ntsc {
Ntsc::None
} else if info.drop_frame {
Ntsc::DropFrame
} else {
Ntsc::NonDropFrame
};
let rate = Framerate::with_timebase(info.timebase, ntsc).unwrap();
Timecode::with_frames(&info.timecode, rate).unwrap()
}
}