#![allow(dead_code)]
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FrameCount {
frames: u64,
fps: u32,
}
impl FrameCount {
#[must_use]
pub fn new(frames: u64, fps: u32) -> Self {
assert!(fps > 0, "fps must be > 0");
Self { frames, fps }
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn from_hmsf(hours: u32, minutes: u32, seconds: u32, frame: u32, fps: u32) -> Self {
assert!(fps > 0, "fps must be > 0");
let total_seconds = u64::from(hours) * 3600 + u64::from(minutes) * 60 + u64::from(seconds);
let total_frames = total_seconds * u64::from(fps) + u64::from(frame);
Self {
frames: total_frames,
fps,
}
}
#[must_use]
pub const fn frames(&self) -> u64 {
self.frames
}
#[must_use]
pub const fn fps(&self) -> u32 {
self.fps
}
#[must_use]
pub fn to_timecode(&self) -> String {
let fps64 = u64::from(self.fps);
let total_seconds = self.frames / fps64;
let ff = self.frames % fps64;
let ss = total_seconds % 60;
let total_minutes = total_seconds / 60;
let mm = total_minutes % 60;
let hh = total_minutes / 60;
format!("{hh:02}:{mm:02}:{ss:02}:{ff:02}")
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn to_seconds(&self) -> f64 {
self.frames as f64 / f64::from(self.fps)
}
#[must_use]
pub fn add_frames(self, n: u64) -> Self {
Self {
frames: self.frames.saturating_add(n),
fps: self.fps,
}
}
#[must_use]
pub fn sub_frames(self, n: u64) -> Self {
Self {
frames: self.frames.saturating_sub(n),
fps: self.fps,
}
}
#[must_use]
pub fn distance(self, other: Self) -> u64 {
self.frames.abs_diff(other.frames)
}
}
impl fmt::Display for FrameCount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_timecode())
}
}
#[derive(Debug, Clone)]
pub struct FrameCounter {
current: u64,
fps: u32,
additions: u64,
}
impl FrameCounter {
#[must_use]
pub fn new(fps: u32) -> Self {
assert!(fps > 0, "fps must be > 0");
Self {
current: 0,
fps,
additions: 0,
}
}
pub fn add(&mut self, frames: u64) {
self.current = self.current.saturating_add(frames);
self.additions += 1;
}
pub fn reset(&mut self) {
self.current = 0;
self.additions = 0;
}
#[must_use]
pub const fn current(&self) -> u64 {
self.current
}
#[must_use]
pub const fn additions(&self) -> u64 {
self.additions
}
#[must_use]
pub fn snapshot(&self) -> FrameCount {
FrameCount::new(self.current, self.fps)
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn duration_seconds(&self) -> f64 {
self.current as f64 / f64::from(self.fps)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FrameCountRange {
start: FrameCount,
end: FrameCount,
}
impl FrameCountRange {
#[must_use]
pub fn new(start: FrameCount, end: FrameCount) -> Self {
assert_eq!(start.fps, end.fps, "fps must match");
assert!(start.frames <= end.frames, "start must be <= end");
Self { start, end }
}
#[must_use]
pub const fn start(&self) -> FrameCount {
self.start
}
#[must_use]
pub const fn end(&self) -> FrameCount {
self.end
}
#[must_use]
pub fn frame_count(&self) -> u64 {
self.end.frames - self.start.frames + 1
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn duration_seconds(&self) -> f64 {
self.frame_count() as f64 / f64::from(self.start.fps)
}
#[must_use]
pub fn contains(&self, frame: u64) -> bool {
frame >= self.start.frames && frame <= self.end.frames
}
#[must_use]
pub fn overlaps(&self, other: &Self) -> bool {
self.start.frames <= other.end.frames && other.start.frames <= self.end.frames
}
#[must_use]
pub fn start_timecode(&self) -> String {
self.start.to_timecode()
}
#[must_use]
pub fn end_timecode(&self) -> String {
self.end.to_timecode()
}
}
impl fmt::Display for FrameCountRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} - {}", self.start, self.end)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_count_creation() {
let fc = FrameCount::new(100, 25);
assert_eq!(fc.frames(), 100);
assert_eq!(fc.fps(), 25);
}
#[test]
fn test_frame_count_from_hmsf() {
let fc = FrameCount::from_hmsf(1, 0, 0, 0, 25);
assert_eq!(fc.frames(), 90_000); }
#[test]
fn test_frame_count_to_timecode() {
let fc = FrameCount::new(90_000, 25);
assert_eq!(fc.to_timecode(), "01:00:00:00");
let fc2 = FrameCount::new(25 * 61 + 12, 25); assert_eq!(fc2.to_timecode(), "00:01:01:12");
}
#[test]
fn test_frame_count_to_seconds() {
let fc = FrameCount::new(75, 25);
let secs = fc.to_seconds();
assert!((secs - 3.0).abs() < 1e-9);
}
#[test]
fn test_frame_count_add_sub() {
let fc = FrameCount::new(100, 25);
assert_eq!(fc.add_frames(50).frames(), 150);
assert_eq!(fc.sub_frames(30).frames(), 70);
assert_eq!(fc.sub_frames(200).frames(), 0); }
#[test]
fn test_frame_count_distance() {
let a = FrameCount::new(100, 25);
let b = FrameCount::new(150, 25);
assert_eq!(a.distance(b), 50);
assert_eq!(b.distance(a), 50);
}
#[test]
fn test_frame_count_display() {
let fc = FrameCount::new(0, 30);
assert_eq!(format!("{fc}"), "00:00:00:00");
}
#[test]
fn test_frame_count_ordering() {
let a = FrameCount::new(10, 25);
let b = FrameCount::new(20, 25);
assert!(a < b);
}
#[test]
fn test_frame_counter_basic() {
let mut counter = FrameCounter::new(25);
assert_eq!(counter.current(), 0);
counter.add(100);
counter.add(50);
assert_eq!(counter.current(), 150);
assert_eq!(counter.additions(), 2);
}
#[test]
fn test_frame_counter_reset() {
let mut counter = FrameCounter::new(25);
counter.add(100);
counter.reset();
assert_eq!(counter.current(), 0);
assert_eq!(counter.additions(), 0);
}
#[test]
fn test_frame_counter_snapshot() {
let mut counter = FrameCounter::new(30);
counter.add(90);
let snap = counter.snapshot();
assert_eq!(snap.frames(), 90);
assert_eq!(snap.fps(), 30);
}
#[test]
fn test_frame_counter_duration() {
let mut counter = FrameCounter::new(25);
counter.add(75);
let dur = counter.duration_seconds();
assert!((dur - 3.0).abs() < 1e-9);
}
#[test]
fn test_frame_count_range_basic() {
let start = FrameCount::new(100, 25);
let end = FrameCount::new(199, 25);
let range = FrameCountRange::new(start, end);
assert_eq!(range.frame_count(), 100);
}
#[test]
fn test_frame_count_range_contains() {
let range = FrameCountRange::new(FrameCount::new(10, 25), FrameCount::new(20, 25));
assert!(range.contains(10));
assert!(range.contains(15));
assert!(range.contains(20));
assert!(!range.contains(9));
assert!(!range.contains(21));
}
#[test]
fn test_frame_count_range_overlaps() {
let a = FrameCountRange::new(FrameCount::new(0, 25), FrameCount::new(50, 25));
let b = FrameCountRange::new(FrameCount::new(25, 25), FrameCount::new(75, 25));
let c = FrameCountRange::new(FrameCount::new(51, 25), FrameCount::new(100, 25));
assert!(a.overlaps(&b));
assert!(!a.overlaps(&c));
}
#[test]
fn test_frame_count_range_duration() {
let range = FrameCountRange::new(FrameCount::new(0, 25), FrameCount::new(24, 25));
let dur = range.duration_seconds();
assert!((dur - 1.0).abs() < 1e-9);
}
#[test]
fn test_frame_count_range_display() {
let range = FrameCountRange::new(FrameCount::new(0, 25), FrameCount::new(24, 25));
let s = format!("{range}");
assert!(s.contains(" - "));
}
}