mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-24 10:36:46 -06:00
Merge pull request #828 from MMexicann/fix/timecode-frame-count-overflow
Fix overflow in extreme timecode frame counts
This commit is contained in:
@@ -133,8 +133,10 @@ impl Timecode {
|
||||
/// Label for an absolute frame count.
|
||||
pub fn from_frames(frames: i64, rate: FrameRate) -> Timecode {
|
||||
let negative = frames < 0;
|
||||
let mut f = frames.saturating_abs();
|
||||
let base = rate.nominal().max(1);
|
||||
// Drop-frame labels can exceed i64::MAX even when the frame count fits. Widen before
|
||||
// taking the magnitude so i64::MIN is represented exactly too.
|
||||
let mut f = i128::from(frames).abs();
|
||||
let base = i128::from(rate.nominal().max(1));
|
||||
if rate.is_drop() {
|
||||
// Drop 2 (or 4) labels each minute except every tenth.
|
||||
let drop = if base >= 60 { 4 } else { 2 };
|
||||
@@ -147,7 +149,13 @@ impl Timecode {
|
||||
}
|
||||
let frames_l = f % base;
|
||||
let total_secs = f / base;
|
||||
Timecode { negative, hours: total_secs / 3600, minutes: (total_secs / 60) % 60, seconds: total_secs % 60, frames: frames_l }
|
||||
Timecode {
|
||||
negative,
|
||||
hours: i64::try_from(total_secs / 3600).unwrap_or(i64::MAX),
|
||||
minutes: i64::try_from((total_secs / 60) % 60).unwrap_or(0),
|
||||
seconds: i64::try_from(total_secs % 60).unwrap_or(0),
|
||||
frames: i64::try_from(frames_l).unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Absolute frame count of this label.
|
||||
@@ -247,6 +255,28 @@ mod tests {
|
||||
assert_eq!(tc.to_samples(FrameRate::Fps30, sr), 178_710_400);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extreme_frame_counts_round_trip_at_every_rate() {
|
||||
for rate in FrameRate::ALL {
|
||||
for frames in [i64::MIN, i64::MIN + 1, -1, 0, 1, i64::MAX - 1, i64::MAX] {
|
||||
let tc = Timecode::from_frames(frames, rate);
|
||||
assert_eq!(tc.to_frames(rate), frames, "{rate:?}: {frames}");
|
||||
assert_eq!(tc.negative, frames < 0);
|
||||
assert!((0..60).contains(&tc.minutes));
|
||||
assert!((0..60).contains(&tc.seconds));
|
||||
assert!((0..rate.nominal()).contains(&tc.frames));
|
||||
assert_eq!(Timecode::parse(&tc.format(rate)).unwrap(), tc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extreme_drop_frame_labels_do_not_overflow() {
|
||||
for rate in [FrameRate::Fps29_97Drop, FrameRate::Fps59_94Drop] {
|
||||
assert_eq!(Timecode::from_frames(i64::MAX, rate).to_frames(rate), i64::MAX);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_variants() {
|
||||
let tc = Timecode::parse("01:02:03:04").unwrap();
|
||||
|
||||
Reference in New Issue
Block a user