#![allow(dead_code)]
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MediaTime {
pub ticks: i64,
pub time_base: u64,
}
impl MediaTime {
#[must_use]
pub const fn zero(time_base: u64) -> Self {
Self {
ticks: 0,
time_base,
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
pub fn from_secs(secs: f64, time_base: u64) -> Self {
assert!(time_base > 0, "time_base must be non-zero");
let ticks = (secs * time_base as f64).round() as i64;
Self { ticks, time_base }
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn to_secs(&self) -> f64 {
self.ticks as f64 / self.time_base as f64
}
#[must_use]
pub const fn add_offset(&self, offset_ticks: i64) -> Self {
Self {
ticks: self.ticks + offset_ticks,
time_base: self.time_base,
}
}
#[must_use]
pub fn is_before(&self, other: &Self) -> bool {
if self.time_base == other.time_base {
self.ticks < other.ticks
} else {
self.to_secs() < other.to_secs()
}
}
#[must_use]
pub fn is_after(&self, other: &Self) -> bool {
other.is_before(self)
}
#[must_use]
pub fn abs_diff_secs(&self, other: &Self) -> f64 {
(self.to_secs() - other.to_secs()).abs()
}
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
#[must_use]
pub fn rescale(&self, new_base: u64) -> Self {
assert!(new_base > 0, "time_base must be non-zero");
let new_ticks =
(self.ticks as f64 * new_base as f64 / self.time_base as f64).round() as i64;
Self {
ticks: new_ticks,
time_base: new_base,
}
}
}
impl fmt::Display for MediaTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.6}s", self.to_secs())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimeRange {
pub start: MediaTime,
pub end: MediaTime,
}
impl TimeRange {
#[must_use]
pub fn new(start: MediaTime, end: MediaTime) -> Self {
assert!(
!end.is_before(&start),
"TimeRange: start must not be after end"
);
Self { start, end }
}
#[must_use]
pub fn duration(&self) -> f64 {
self.end.to_secs() - self.start.to_secs()
}
#[must_use]
pub fn contains(&self, time: &MediaTime) -> bool {
!time.is_before(&self.start) && time.is_before(&self.end)
}
#[must_use]
pub fn overlaps(&self, other: &Self) -> bool {
self.start.is_before(&other.end) && other.start.is_before(&self.end)
}
#[must_use]
pub fn intersection(&self, other: &Self) -> Option<Self> {
let start_secs = self.start.to_secs().max(other.start.to_secs());
let end_secs = self.end.to_secs().min(other.end.to_secs());
if end_secs <= start_secs {
return None;
}
let tb = self.start.time_base;
Some(Self::new(
MediaTime::from_secs(start_secs, tb),
MediaTime::from_secs(end_secs, tb),
))
}
}
impl fmt::Display for TimeRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}, {})", self.start, self.end)
}
}
#[derive(Debug, Clone, Copy)]
pub struct MediaTimeCalc {
pub b_frame_delay: u32,
pub time_base: u64,
}
impl MediaTimeCalc {
#[must_use]
pub const fn new(b_frame_delay: u32, time_base: u64) -> Self {
Self {
b_frame_delay,
time_base,
}
}
#[must_use]
pub fn pts_to_dts(&self, pts: MediaTime) -> MediaTime {
let delay = i64::from(self.b_frame_delay);
pts.add_offset(-delay)
}
#[must_use]
pub fn dts_to_pts(&self, dts: MediaTime) -> MediaTime {
let delay = i64::from(self.b_frame_delay);
dts.add_offset(delay)
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn dts_offset_secs(&self) -> f64 {
f64::from(self.b_frame_delay) / self.time_base as f64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zero() {
let t = MediaTime::zero(90_000);
assert_eq!(t.ticks, 0);
assert_eq!(t.to_secs(), 0.0);
}
#[test]
fn test_from_secs_to_secs() {
let t = MediaTime::from_secs(1.0, 90_000);
assert!((t.to_secs() - 1.0).abs() < 1e-6);
}
#[test]
fn test_from_secs_half() {
let t = MediaTime::from_secs(0.5, 90_000);
assert!((t.to_secs() - 0.5).abs() < 1e-6);
}
#[test]
fn test_add_offset() {
let t = MediaTime::from_secs(1.0, 90_000);
let t2 = t.add_offset(90_000);
assert!((t2.to_secs() - 2.0).abs() < 1e-6);
}
#[test]
fn test_is_before() {
let t1 = MediaTime::from_secs(1.0, 90_000);
let t2 = MediaTime::from_secs(2.0, 90_000);
assert!(t1.is_before(&t2));
assert!(!t2.is_before(&t1));
}
#[test]
fn test_is_after() {
let t1 = MediaTime::from_secs(1.0, 90_000);
let t2 = MediaTime::from_secs(2.0, 90_000);
assert!(t2.is_after(&t1));
assert!(!t1.is_after(&t2));
}
#[test]
fn test_abs_diff_secs() {
let t1 = MediaTime::from_secs(1.0, 90_000);
let t2 = MediaTime::from_secs(3.0, 90_000);
assert!((t1.abs_diff_secs(&t2) - 2.0).abs() < 1e-6);
}
#[test]
fn test_rescale() {
let t = MediaTime::from_secs(1.0, 90_000);
let t2 = t.rescale(48_000);
assert!((t2.to_secs() - 1.0).abs() < 1e-4);
}
#[test]
fn test_display() {
let t = MediaTime::from_secs(1.5, 90_000);
let s = format!("{t}");
assert!(s.contains("1.5"));
}
#[test]
fn test_time_range_duration() {
let s = MediaTime::from_secs(0.0, 90_000);
let e = MediaTime::from_secs(2.0, 90_000);
let r = TimeRange::new(s, e);
assert!((r.duration() - 2.0).abs() < 1e-6);
}
#[test]
fn test_time_range_contains() {
let s = MediaTime::from_secs(1.0, 90_000);
let e = MediaTime::from_secs(5.0, 90_000);
let r = TimeRange::new(s, e);
let mid = MediaTime::from_secs(3.0, 90_000);
assert!(r.contains(&mid));
assert!(!r.contains(&MediaTime::from_secs(0.5, 90_000)));
assert!(!r.contains(&e));
}
#[test]
fn test_time_range_overlaps() {
let r1 = TimeRange::new(
MediaTime::from_secs(0.0, 90_000),
MediaTime::from_secs(4.0, 90_000),
);
let r2 = TimeRange::new(
MediaTime::from_secs(2.0, 90_000),
MediaTime::from_secs(6.0, 90_000),
);
assert!(r1.overlaps(&r2));
}
#[test]
fn test_time_range_no_overlap() {
let r1 = TimeRange::new(
MediaTime::from_secs(0.0, 90_000),
MediaTime::from_secs(2.0, 90_000),
);
let r2 = TimeRange::new(
MediaTime::from_secs(3.0, 90_000),
MediaTime::from_secs(5.0, 90_000),
);
assert!(!r1.overlaps(&r2));
}
#[test]
fn test_time_range_intersection() {
let r1 = TimeRange::new(
MediaTime::from_secs(0.0, 90_000),
MediaTime::from_secs(4.0, 90_000),
);
let r2 = TimeRange::new(
MediaTime::from_secs(2.0, 90_000),
MediaTime::from_secs(6.0, 90_000),
);
let inter = r1.intersection(&r2).expect("intersection should exist");
assert!((inter.start.to_secs() - 2.0).abs() < 1e-4);
assert!((inter.end.to_secs() - 4.0).abs() < 1e-4);
}
#[test]
fn test_time_range_intersection_none() {
let r1 = TimeRange::new(
MediaTime::from_secs(0.0, 90_000),
MediaTime::from_secs(2.0, 90_000),
);
let r2 = TimeRange::new(
MediaTime::from_secs(3.0, 90_000),
MediaTime::from_secs(5.0, 90_000),
);
assert!(r1.intersection(&r2).is_none());
}
#[test]
fn test_pts_to_dts_no_delay() {
let calc = MediaTimeCalc::new(0, 90_000);
let pts = MediaTime::from_secs(1.0, 90_000);
let dts = calc.pts_to_dts(pts);
assert_eq!(dts, pts);
}
#[test]
fn test_pts_to_dts_with_delay() {
let calc = MediaTimeCalc::new(2, 90_000);
let pts = MediaTime {
ticks: 100,
time_base: 90_000,
};
let dts = calc.pts_to_dts(pts);
assert_eq!(dts.ticks, 98);
}
#[test]
fn test_dts_to_pts_roundtrip() {
let calc = MediaTimeCalc::new(3, 90_000);
let pts = MediaTime::from_secs(5.0, 90_000);
let dts = calc.pts_to_dts(pts);
let pts2 = calc.dts_to_pts(dts);
assert_eq!(pts, pts2);
}
#[test]
fn test_dts_offset_secs() {
let calc = MediaTimeCalc::new(2, 90_000);
let offset = calc.dts_offset_secs();
assert!((offset - 2.0 / 90_000.0).abs() < 1e-12);
}
}
use crate::types::Rational;
pub const TB_90K: Rational = Rational {
num: 1,
den: 90_000,
};
pub const TB_44100: Rational = Rational {
num: 1,
den: 44_100,
};
pub const TB_48K: Rational = Rational {
num: 1,
den: 48_000,
};
pub const TB_1K: Rational = Rational { num: 1, den: 1_000 };
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PtsMediaTime {
pub pts: i64,
pub time_base: Rational,
}
impl PtsMediaTime {
pub const ZERO: Self = Self {
pts: 0,
time_base: TB_90K,
};
#[must_use]
pub fn new(pts: i64, time_base: Rational) -> Self {
Self { pts, time_base }
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
pub fn from_secs(secs: f64, time_base: Rational) -> Self {
let ticks = (secs * time_base.den as f64 / time_base.num as f64).round() as i64;
Self {
pts: ticks,
time_base,
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn to_secs(&self) -> f64 {
self.pts as f64 * self.time_base.to_f64()
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
pub fn to_ms(&self) -> i64 {
(self.to_secs() * 1000.0) as i64
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn rebase(&self, new_base: Rational) -> Self {
let scale_num = i128::from(self.time_base.num) * i128::from(new_base.den);
let scale_den = i128::from(self.time_base.den) * i128::from(new_base.num);
let half = scale_den / 2;
let new_pts = ((i128::from(self.pts) * scale_num + half) / scale_den) as i64;
Self {
pts: new_pts,
time_base: new_base,
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.pts >= 0
}
#[must_use]
pub fn add_duration(&self, d: MediaDuration) -> Self {
let d_rebased = d.rebase(self.time_base);
Self {
pts: self.pts + d_rebased.ticks,
time_base: self.time_base,
}
}
#[must_use]
pub fn subtract(&self, other: &Self) -> MediaDuration {
let other_rebased = other.rebase(self.time_base);
MediaDuration {
ticks: self.pts - other_rebased.pts,
time_base: self.time_base,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MediaDuration {
pub ticks: i64,
pub time_base: Rational,
}
impl MediaDuration {
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
pub fn from_secs(secs: f64, time_base: Rational) -> Self {
let ticks = (secs * time_base.den as f64 / time_base.num as f64).round() as i64;
Self { ticks, time_base }
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn to_secs(&self) -> f64 {
self.ticks as f64 * self.time_base.to_f64()
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn rebase(&self, new_base: Rational) -> Self {
let scale_num = i128::from(self.time_base.num) * i128::from(new_base.den);
let scale_den = i128::from(self.time_base.den) * i128::from(new_base.num);
let half = scale_den / 2;
let new_ticks = ((i128::from(self.ticks) * scale_num + half) / scale_den) as i64;
Self {
ticks: new_ticks,
time_base: new_base,
}
}
}
#[cfg(test)]
mod tests_pts_media_time {
use super::*;
#[test]
fn test_zero_constant() {
assert_eq!(PtsMediaTime::ZERO.pts, 0);
assert_eq!(PtsMediaTime::ZERO.time_base, TB_90K);
}
#[test]
fn test_new_and_to_secs() {
let t = PtsMediaTime::new(90_000, TB_90K);
assert!((t.to_secs() - 1.0).abs() < 1e-9);
}
#[test]
fn test_from_secs() {
let t = PtsMediaTime::from_secs(2.5, TB_90K);
assert!((t.to_secs() - 2.5).abs() < 1e-6);
}
#[test]
fn test_to_ms() {
let t = PtsMediaTime::from_secs(1.5, TB_1K);
assert_eq!(t.to_ms(), 1500);
}
#[test]
fn test_rebase_90k_to_1k() {
let t = PtsMediaTime::new(90_000, TB_90K);
let r = t.rebase(TB_1K);
assert_eq!(r.pts, 1000);
assert!((r.to_secs() - 1.0).abs() < 1e-6);
}
#[test]
fn test_rebase_1k_to_44100() {
let t = PtsMediaTime::from_secs(1.0, TB_1K);
let r = t.rebase(TB_44100);
assert_eq!(r.pts, 44_100);
}
#[test]
fn test_is_valid() {
assert!(PtsMediaTime::new(0, TB_90K).is_valid());
assert!(PtsMediaTime::new(100, TB_90K).is_valid());
assert!(!PtsMediaTime::new(-1, TB_90K).is_valid());
}
#[test]
fn test_add_duration() {
let t = PtsMediaTime::new(0, TB_1K);
let d = MediaDuration::from_secs(1.5, TB_1K);
let t2 = t.add_duration(d);
assert_eq!(t2.pts, 1500);
}
#[test]
fn test_add_duration_different_base() {
let t = PtsMediaTime::new(90_000, TB_90K); let d = MediaDuration::from_secs(0.5, TB_1K); let t2 = t.add_duration(d);
assert!((t2.to_secs() - 1.5).abs() < 1e-4);
}
#[test]
fn test_subtract() {
let a = PtsMediaTime::new(3000, TB_1K);
let b = PtsMediaTime::new(1000, TB_1K);
let d = a.subtract(&b);
assert!((d.to_secs() - 2.0).abs() < 1e-9);
}
#[test]
fn test_media_duration_from_secs_to_secs() {
let d = MediaDuration::from_secs(0.25, TB_90K);
assert!((d.to_secs() - 0.25).abs() < 1e-6);
}
#[test]
fn test_media_duration_rebase() {
let d = MediaDuration::from_secs(1.0, TB_90K);
let r = d.rebase(TB_48K);
assert_eq!(r.ticks, 48_000);
}
#[test]
fn test_tb_constants() {
assert_eq!(TB_90K.den, 90_000);
assert_eq!(TB_44100.den, 44_100);
assert_eq!(TB_48K.den, 48_000);
assert_eq!(TB_1K.den, 1_000);
}
}