#![doc = include_str!("../README.md")]
#![cfg_attr(not(test), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, allow(unused_attributes))]
#![deny(missing_docs)]
#![forbid(unsafe_code)]
#[cfg(feature = "quickcheck")]
#[allow(unused_extern_crates)]
extern crate std;
use core::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
num::NonZeroI32,
time::Duration,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
mod parse;
pub use parse::{
ParseRateError, ParseSignedDurationError, ParseTimeRangeError, ParseTimebaseError,
ParseTimestampError,
};
const NANOS_PER_SEC: u128 = 1_000_000_000;
const fn nz(n: i32) -> NonZeroI32 {
match NonZeroI32::new(n) {
Some(v) => v,
None => unreachable!(),
}
}
pub(crate) const DEN_ONE: NonZeroI32 = nz(1);
#[derive(Debug, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "quickcheck",
derive(::quickcheck_richderive::Arbitrary),
quickcheck(arbitrary = "crate::quickcheck_impls::timebase")
)]
pub struct Timebase {
#[cfg_attr(
feature = "serde",
serde(rename = "numerator", deserialize_with = "de_num")
)]
num: i32,
#[cfg_attr(
feature = "serde",
serde(rename = "denominator", deserialize_with = "de_den")
)]
den: NonZeroI32,
}
impl Default for Timebase {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::new(1, DEN_ONE)
}
}
impl Timebase {
pub const SECONDS: Self = Self::new(1, nz(1));
pub const MILLIS: Self = Self::new(1, nz(1_000));
pub const MICROS: Self = Self::new(1, nz(1_000_000));
pub const NANOS: Self = Self::new(1, nz(1_000_000_000));
pub const MPEG_90K: Self = Self::new(1, nz(90_000));
pub const HZ_8K: Self = Self::new(1, nz(8_000));
pub const HZ_11_025K: Self = Self::new(1, nz(11_025));
pub const HZ_12K: Self = Self::new(1, nz(12_000));
pub const HZ_16K: Self = Self::new(1, nz(16_000));
pub const HZ_22_05K: Self = Self::new(1, nz(22_050));
pub const HZ_24K: Self = Self::new(1, nz(24_000));
pub const HZ_32K: Self = Self::new(1, nz(32_000));
pub const HZ_44_1K: Self = Self::new(1, nz(44_100));
pub const HZ_48K: Self = Self::new(1, nz(48_000));
pub const HZ_64K: Self = Self::new(1, nz(64_000));
pub const HZ_88_2K: Self = Self::new(1, nz(88_200));
pub const HZ_96K: Self = Self::new(1, nz(96_000));
pub const HZ_176_4K: Self = Self::new(1, nz(176_400));
pub const HZ_192K: Self = Self::new(1, nz(192_000));
pub const NTSC_FILM: Self = Self::new(1_001, nz(24_000));
pub const FILM_24: Self = Self::new(1, nz(24));
pub const PAL_25: Self = Self::new(1, nz(25));
pub const NTSC_VIDEO: Self = Self::new(1_001, nz(30_000));
pub const VIDEO_30: Self = Self::new(1, nz(30));
pub const PAL_50: Self = Self::new(1, nz(50));
pub const NTSC_60: Self = Self::new(1_001, nz(60_000));
pub const VIDEO_60: Self = Self::new(1, nz(60));
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(num: i32, den: NonZeroI32) -> Self {
assert!(num >= 0, "timebase numerator must not be negative");
assert!(den.get() > 0, "timebase denominator must be positive");
Self { num, den }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn try_new(num: i32, den: NonZeroI32) -> Option<Self> {
if num >= 0 && den.get() > 0 {
Some(Self { num, den })
} else {
None
}
}
pub fn from_name(name: &str) -> Option<Self> {
WELL_KNOWN
.iter()
.find_map(|(known, timebase)| known.eq_ignore_ascii_case(name).then_some(*timebase))
}
pub fn well_known_name(&self) -> Option<&'static str> {
WELL_KNOWN
.iter()
.find_map(|(name, timebase)| (timebase == self).then_some(*name))
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn num(&self) -> i32 {
self.num
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn den(&self) -> NonZeroI32 {
self.den
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_num(mut self, num: i32) -> Self {
self.set_num(num);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_den(mut self, den: NonZeroI32) -> Self {
self.set_den(den);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_num(&mut self, num: i32) -> &mut Self {
*self = Self::new(num, self.den);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_den(&mut self, den: NonZeroI32) -> &mut Self {
*self = Self::new(self.num, den);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn reduce(self) -> Self {
let g = gcd_u32(self.num.unsigned_abs(), self.den.get().unsigned_abs()) as i32;
Self {
num: self.num / g,
den: nz(self.den.get() / g),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_reduced(&self) -> bool {
gcd_u32(self.num.unsigned_abs(), self.den.get().unsigned_abs()) == 1
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn is_identical(&self, other: &Self) -> bool {
self.num == other.num && self.den.get() == other.den.get()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_recip(self) -> Option<Self> {
match NonZeroI32::new(self.num) {
Some(den) => Some(Self {
num: self.den.get(),
den,
}),
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_rescale(&self, pts: i64, to: Self) -> Option<i64> {
if to.num == 0 {
return None;
}
let q = rescaled(pts, *self, to);
if q > i64::MAX as i128 || q < i64::MIN as i128 {
None
} else {
Some(q as i64)
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_rescale(&self, pts: i64, to: Self) -> i64 {
assert!(to.num != 0, "target timebase numerator must be non-zero");
let q = rescaled(pts, *self, to);
if q > i64::MAX as i128 {
i64::MAX
} else if q < i64::MIN as i128 {
i64::MIN
} else {
q as i64
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_duration_to_pts(&self, d: Duration) -> Option<i64> {
if self.num == 0 {
return None;
}
let ticks = self.duration_ticks(d);
if ticks > i64::MAX as u128 {
None
} else {
Some(ticks as i64)
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_duration_to_pts(&self, d: Duration) -> i64 {
assert!(self.num != 0, "target timebase numerator must be non-zero");
let ticks = self.duration_ticks(d);
if ticks > i64::MAX as u128 {
i64::MAX
} else {
ticks as i64
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_pts_to_duration(&self, pts: i64) -> Option<Duration> {
if pts < 0 {
return None;
}
let nanos = self.tick_nanos(pts);
let secs = nanos / NANOS_PER_SEC;
if secs > u64::MAX as u128 {
return None;
}
Some(Duration::new(secs as u64, (nanos % NANOS_PER_SEC) as u32))
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_pts_to_duration(&self, pts: i64) -> Duration {
if pts < 0 {
return Duration::ZERO;
}
let nanos = self.tick_nanos(pts);
let secs = nanos / NANOS_PER_SEC;
if secs > u64::MAX as u128 {
return Duration::MAX;
}
Duration::new(secs as u64, (nanos % NANOS_PER_SEC) as u32)
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn duration_ticks(&self, d: Duration) -> u128 {
let numerator = d.as_nanos() * (self.den.get() as u128);
let denominator = (self.num as u128) * NANOS_PER_SEC;
div_round_half_up(numerator, denominator)
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn tick_nanos(&self, pts: i64) -> u128 {
let numerator = (pts as u128) * (self.num as u128) * NANOS_PER_SEC;
div_round_half_up(numerator, self.den.get() as u128)
}
}
impl PartialEq for Timebase {
#[cfg_attr(not(tarpaulin), inline(always))]
fn eq(&self, other: &Self) -> bool {
(self.num as i64) * (other.den.get() as i64) == (other.num as i64) * (self.den.get() as i64)
}
}
impl Hash for Timebase {
#[cfg_attr(not(tarpaulin), inline(always))]
fn hash<H: Hasher>(&self, state: &mut H) {
let reduced = self.reduce();
reduced.num.hash(state);
reduced.den.get().hash(state);
}
}
impl Ord for Timebase {
#[cfg_attr(not(tarpaulin), inline(always))]
fn cmp(&self, other: &Self) -> Ordering {
let lhs = (self.num as i64) * (other.den.get() as i64);
let rhs = (other.num as i64) * (self.den.get() as i64);
lhs.cmp(&rhs)
}
}
impl PartialOrd for Timebase {
#[cfg_attr(not(tarpaulin), inline(always))]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for Timebase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.num, self.den.get())
}
}
#[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "quickcheck",
derive(::quickcheck_richderive::Arbitrary),
quickcheck(arbitrary = "crate::quickcheck_impls::timestamp")
)]
pub struct Timestamp {
pts: i64,
timebase: Timebase,
}
impl Timestamp {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(pts: i64, timebase: Timebase) -> Self {
Self { pts, timebase }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn pts(&self) -> i64 {
self.pts
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn timebase(&self) -> Timebase {
self.timebase
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_pts(mut self, pts: i64) -> Self {
self.set_pts(pts);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_pts(&mut self, pts: i64) -> &mut Self {
self.pts = pts;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn rescale_to(self, target: Timebase) -> Self {
Self {
pts: self.timebase.saturating_rescale(self.pts, target),
timebase: target,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_sub_duration(self, d: Duration) -> Self {
let units = self.timebase.saturating_duration_to_pts(d);
Self::new(self.pts.saturating_sub(units), self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_add_duration(self, d: Duration) -> Self {
let units = self.timebase.saturating_duration_to_pts(d);
Self::new(self.pts.saturating_add(units), self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn signed_duration_since(&self, other: &Self) -> SignedDuration {
let earlier = saturating_recount(other.pts, other.timebase, self.timebase);
SignedDuration::new(self.pts.saturating_sub(earlier), self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_signed_duration_since(&self, other: &Self) -> Option<SignedDuration> {
match checked_recount(other.pts, other.timebase, self.timebase) {
Some(earlier) => match self.pts.checked_sub(earlier) {
Some(ticks) => Some(SignedDuration::new(ticks, self.timebase)),
None => None,
},
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_add_signed(self, d: SignedDuration) -> Option<Self> {
match checked_recount(d.ticks, d.timebase, self.timebase) {
Some(ticks) => match self.pts.checked_add(ticks) {
Some(pts) => Some(Self::new(pts, self.timebase)),
None => None,
},
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_add_signed(self, d: SignedDuration) -> Self {
let ticks = saturating_recount(d.ticks, d.timebase, self.timebase);
Self::new(self.pts.saturating_add(ticks), self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_sub_signed(self, d: SignedDuration) -> Option<Self> {
match checked_recount(d.ticks, d.timebase, self.timebase) {
Some(ticks) => match self.pts.checked_sub(ticks) {
Some(pts) => Some(Self::new(pts, self.timebase)),
None => None,
},
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_sub_signed(self, d: SignedDuration) -> Self {
let ticks = saturating_recount(d.ticks, d.timebase, self.timebase);
Self::new(self.pts.saturating_sub(ticks), self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn cmp_semantic(&self, other: &Self) -> Ordering {
if self.timebase.is_identical(&other.timebase) && self.timebase.num != 0 {
return cmp_i128(self.pts as i128, other.pts as i128);
}
let lhs = (self.pts as i128) * (self.timebase.num as i128) * (other.timebase.den.get() as i128);
let rhs =
(other.pts as i128) * (other.timebase.num as i128) * (self.timebase.den.get() as i128);
cmp_i128(lhs, rhs)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn duration(&self) -> Option<Duration> {
self.duration_since(&Self::new(0, self.timebase))
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn duration_since(&self, earlier: &Self) -> Option<Duration> {
const NS_PER_SEC: i128 = 1_000_000_000;
let self_den = self.timebase.den.get();
let earlier_den = earlier.timebase.den.get();
let mut a = self_den;
let mut b = earlier_den;
while b != 0 {
let r = a % b;
a = b;
b = r;
}
let gcd = a as i128;
let self_scale = (earlier_den as i128) / gcd;
let earlier_scale = (self_den as i128) / gcd;
let common_den = (self_den as i128) * self_scale;
let diff_num = (self.pts as i128) * (self.timebase.num as i128) * self_scale
- (earlier.pts as i128) * (earlier.timebase.num as i128) * earlier_scale;
if diff_num < 0 {
return None;
}
let secs_i128 = diff_num / common_den;
if secs_i128 > u64::MAX as i128 {
return Some(Duration::MAX);
}
let rem = diff_num % common_den;
let nanos = (rem * NS_PER_SEC / common_den) as u32;
Some(Duration::new(secs_i128 as u64, nanos))
}
}
impl PartialEq for Timestamp {
#[cfg_attr(not(tarpaulin), inline(always))]
fn eq(&self, other: &Self) -> bool {
self.cmp_semantic(other).is_eq()
}
}
impl Eq for Timestamp {}
impl Hash for Timestamp {
#[cfg_attr(not(tarpaulin), inline(always))]
fn hash<H: Hasher>(&self, state: &mut H) {
let n: i128 = (self.pts as i128) * (self.timebase.num as i128);
let d: u128 = self.timebase.den.get().unsigned_abs() as u128;
let g = gcd_u128(n.unsigned_abs(), d) as i128;
let rn = n / g;
let rd = (d as i128) / g;
rn.hash(state);
rd.hash(state);
}
}
impl Ord for Timestamp {
#[cfg_attr(not(tarpaulin), inline(always))]
fn cmp(&self, other: &Self) -> Ordering {
self.cmp_semantic(other)
}
}
impl PartialOrd for Timestamp {
#[cfg_attr(not(tarpaulin), inline(always))]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "{} @ {}", self.pts, self.timebase)
} else {
write_clock(f, self.pts, self.timebase)
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "quickcheck",
derive(::quickcheck_richderive::Arbitrary),
quickcheck(arbitrary = "crate::quickcheck_impls::signed_duration")
)]
pub struct SignedDuration {
ticks: i64,
timebase: Timebase,
}
impl SignedDuration {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(ticks: i64, timebase: Timebase) -> Self {
Self { ticks, timebase }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn ticks(&self) -> i64 {
self.ticks
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn timebase(&self) -> Timebase {
self.timebase
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_negative(&self) -> bool {
self.ticks < 0
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_positive(&self) -> bool {
self.ticks > 0
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_zero(&self) -> bool {
self.ticks == 0
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_neg(self) -> Option<Self> {
match self.ticks.checked_neg() {
Some(ticks) => Some(Self::new(ticks, self.timebase)),
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_neg(self) -> Self {
Self::new(self.ticks.saturating_neg(), self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_abs(self) -> Option<Self> {
match self.ticks.checked_abs() {
Some(ticks) => Some(Self::new(ticks, self.timebase)),
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_abs(self) -> Self {
Self::new(self.ticks.saturating_abs(), self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
match checked_recount(rhs.ticks, rhs.timebase, self.timebase) {
Some(ticks) => match self.ticks.checked_add(ticks) {
Some(sum) => Some(Self::new(sum, self.timebase)),
None => None,
},
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_add(self, rhs: Self) -> Self {
let ticks = saturating_recount(rhs.ticks, rhs.timebase, self.timebase);
Self::new(self.ticks.saturating_add(ticks), self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
match checked_recount(rhs.ticks, rhs.timebase, self.timebase) {
Some(ticks) => match self.ticks.checked_sub(ticks) {
Some(difference) => Some(Self::new(difference, self.timebase)),
None => None,
},
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_sub(self, rhs: Self) -> Self {
let ticks = saturating_recount(rhs.ticks, rhs.timebase, self.timebase);
Self::new(self.ticks.saturating_sub(ticks), self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn rescale_to(self, target: Timebase) -> Self {
Self {
ticks: self.timebase.saturating_rescale(self.ticks, target),
timebase: target,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_rescale_to(self, target: Timebase) -> Option<Self> {
match self.timebase.checked_rescale(self.ticks, target) {
Some(ticks) => Some(Self {
ticks,
timebase: target,
}),
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn cmp_semantic(&self, other: &Self) -> Ordering {
if self.timebase.is_identical(&other.timebase) && self.timebase.num != 0 {
return cmp_i128(self.ticks as i128, other.ticks as i128);
}
let lhs =
(self.ticks as i128) * (self.timebase.num as i128) * (other.timebase.den.get() as i128);
let rhs =
(other.ticks as i128) * (other.timebase.num as i128) * (self.timebase.den.get() as i128);
cmp_i128(lhs, rhs)
}
}
impl fmt::Display for SignedDuration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} @ {}", self.ticks, self.timebase)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(try_from = "de::TimeRangeRepr")
)]
#[cfg_attr(
feature = "quickcheck",
derive(::quickcheck_richderive::Arbitrary),
quickcheck(arbitrary = "crate::quickcheck_impls::time_range")
)]
pub struct TimeRange {
start: i64,
end: i64,
timebase: Timebase,
}
impl TimeRange {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(start: i64, end: i64, timebase: Timebase) -> Self {
assert!(start <= end, "end must not precede start");
Self {
start,
end,
timebase,
}
}
#[cfg(feature = "buffa")]
#[inline(always)]
pub(crate) const fn new_for_decode(start: i64, end: i64, timebase: Timebase) -> Self {
Self {
start,
end,
timebase,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn try_new(start: i64, end: i64, timebase: Timebase) -> Option<Self> {
if start <= end {
Some(Self {
start,
end,
timebase,
})
} else {
None
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn instant(ts: Timestamp) -> Self {
Self {
start: ts.pts(),
end: ts.pts(),
timebase: ts.timebase(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn start_pts(&self) -> i64 {
self.start
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn end_pts(&self) -> i64 {
self.end
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn timebase(&self) -> Timebase {
self.timebase
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn start(&self) -> Timestamp {
Timestamp::new(self.start, self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn end(&self) -> Timestamp {
Timestamp::new(self.end, self.timebase)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_start(mut self, val: i64) -> Self {
self.start = val;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_start(&mut self, val: i64) -> &mut Self {
self.start = val;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_end(mut self, val: i64) -> Self {
self.end = val;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_end(&mut self, val: i64) -> &mut Self {
self.end = val;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_timebase(mut self, timebase: Timebase) -> Self {
self.set_timebase(timebase);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_timebase(&mut self, timebase: Timebase) -> &mut Self {
self.timebase = timebase;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_instant(&self) -> bool {
self.start == self.end
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn total_pts(&self) -> i64 {
self.end.saturating_sub(self.start)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn duration(&self) -> Duration {
self
.end()
.duration_since(&self.start())
.expect("end must not precede start")
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn rescale_to(self, target: Timebase) -> Self {
Self {
start: self.timebase.saturating_rescale(self.start, target),
end: self.timebase.saturating_rescale(self.end, target),
timebase: target,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn interpolate(&self, t: f64) -> Timestamp {
let t = t.clamp(0.0, 1.0);
let delta = self.end.saturating_sub(self.start);
let offset = (delta as f64 * t) as i64;
Timestamp::new(self.start.saturating_add(offset), self.timebase)
}
}
impl fmt::Display for TimeRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
return write!(f, "[{}, {}) @ {}", self.start, self.end, self.timebase);
}
f.write_str("[")?;
write_clock(f, self.start, self.timebase)?;
f.write_str(", ")?;
write_clock(f, self.end, self.timebase)?;
f.write_str(")")
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
#[cfg_attr(
feature = "quickcheck",
derive(::quickcheck_richderive::Arbitrary),
quickcheck(arbitrary = "crate::quickcheck_impls::rate")
)]
pub struct Rate(Timebase);
impl Rate {
pub const FPS_23_976: Self = Self(Timebase::new(24_000, nz(1_001)));
pub const FPS_24: Self = Self(Timebase::new(24, nz(1)));
pub const FPS_25: Self = Self(Timebase::new(25, nz(1)));
pub const FPS_29_97: Self = Self(Timebase::new(30_000, nz(1_001)));
pub const FPS_30: Self = Self(Timebase::new(30, nz(1)));
pub const FPS_50: Self = Self(Timebase::new(50, nz(1)));
pub const FPS_59_94: Self = Self(Timebase::new(60_000, nz(1_001)));
pub const FPS_60: Self = Self(Timebase::new(60, nz(1)));
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn hz(n: i32) -> Self {
Self(Timebase::new(n, DEN_ONE))
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn try_hz(n: i32) -> Option<Self> {
match Timebase::try_new(n, DEN_ONE) {
Some(inner) => Some(Self(inner)),
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn fps(num: i32, den: NonZeroI32) -> Self {
Self(Timebase::new(num, den))
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn try_fps(num: i32, den: NonZeroI32) -> Option<Self> {
match Timebase::try_new(num, den) {
Some(inner) => Some(Self(inner)),
None => None,
}
}
pub fn from_name(name: &str) -> Option<Self> {
WELL_KNOWN_RATES
.iter()
.find_map(|(known, rate)| known.eq_ignore_ascii_case(name).then_some(*rate))
}
pub fn well_known_name(&self) -> Option<&'static str> {
WELL_KNOWN_RATES
.iter()
.find_map(|(name, rate)| (rate == self).then_some(*name))
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn num(&self) -> i32 {
self.0.num()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn den(&self) -> NonZeroI32 {
self.0.den()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn to_timebase(&self) -> Timebase {
match self.0.checked_recip() {
Some(timebase) => timebase,
None => panic!("rate numerator must be non-zero"),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_to_timebase(&self) -> Option<Timebase> {
self.0.checked_recip()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn from_timebase(timebase: Timebase) -> Self {
match timebase.checked_recip() {
Some(rate) => Self(rate),
None => panic!("timebase numerator must be non-zero"),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_from_timebase(timebase: Timebase) -> Option<Self> {
match timebase.checked_recip() {
Some(rate) => Some(Self(rate)),
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn checked_frames_to_duration(&self, frames: i64) -> Option<Duration> {
match self.checked_to_timebase() {
Some(timebase) => timebase.checked_pts_to_duration(frames),
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn saturating_frames_to_duration(&self, frames: i64) -> Duration {
self.to_timebase().saturating_pts_to_duration(frames)
}
}
impl fmt::Display for Rate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[cfg(feature = "serde")]
mod de {
use core::{fmt, num::NonZeroI32};
use serde::{Deserialize, Deserializer, de::Error};
use crate::{TimeRange, Timebase};
pub(super) fn de_num<'de, D: Deserializer<'de>>(d: D) -> Result<i32, D::Error> {
let v = i32::deserialize(d)?;
if v < 0 {
return Err(D::Error::custom("timebase numerator must not be negative"));
}
Ok(v)
}
pub(super) fn de_den<'de, D: Deserializer<'de>>(d: D) -> Result<NonZeroI32, D::Error> {
let v = NonZeroI32::deserialize(d)?;
if v.get() < 0 {
return Err(D::Error::custom("timebase denominator must be positive"));
}
Ok(v)
}
#[derive(Deserialize)]
pub(super) struct TimeRangeRepr {
start: i64,
end: i64,
timebase: Timebase,
}
pub(super) struct InvertedRange;
impl fmt::Display for InvertedRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("time range end must not precede start")
}
}
impl TryFrom<TimeRangeRepr> for TimeRange {
type Error = InvertedRange;
fn try_from(repr: TimeRangeRepr) -> Result<Self, Self::Error> {
Self::try_new(repr.start, repr.end, repr.timebase).ok_or(InvertedRange)
}
}
}
#[cfg(feature = "serde")]
use de::{de_den, de_num};
const WELL_KNOWN: &[(&str, Timebase)] = &[
("SECONDS", Timebase::SECONDS),
("MILLIS", Timebase::MILLIS),
("MICROS", Timebase::MICROS),
("NANOS", Timebase::NANOS),
("MPEG_90K", Timebase::MPEG_90K),
("HZ_8K", Timebase::HZ_8K),
("HZ_11_025K", Timebase::HZ_11_025K),
("HZ_12K", Timebase::HZ_12K),
("HZ_16K", Timebase::HZ_16K),
("HZ_22_05K", Timebase::HZ_22_05K),
("HZ_24K", Timebase::HZ_24K),
("HZ_32K", Timebase::HZ_32K),
("HZ_44_1K", Timebase::HZ_44_1K),
("HZ_48K", Timebase::HZ_48K),
("HZ_64K", Timebase::HZ_64K),
("HZ_88_2K", Timebase::HZ_88_2K),
("HZ_96K", Timebase::HZ_96K),
("HZ_176_4K", Timebase::HZ_176_4K),
("HZ_192K", Timebase::HZ_192K),
("NTSC_FILM", Timebase::NTSC_FILM),
("FILM_24", Timebase::FILM_24),
("PAL_25", Timebase::PAL_25),
("NTSC_VIDEO", Timebase::NTSC_VIDEO),
("VIDEO_30", Timebase::VIDEO_30),
("PAL_50", Timebase::PAL_50),
("NTSC_60", Timebase::NTSC_60),
("VIDEO_60", Timebase::VIDEO_60),
];
const WELL_KNOWN_RATES: &[(&str, Rate)] = &[
("FPS_23_976", Rate::FPS_23_976),
("FPS_24", Rate::FPS_24),
("FPS_25", Rate::FPS_25),
("FPS_29_97", Rate::FPS_29_97),
("FPS_30", Rate::FPS_30),
("FPS_50", Rate::FPS_50),
("FPS_59_94", Rate::FPS_59_94),
("FPS_60", Rate::FPS_60),
];
#[cfg_attr(not(tarpaulin), inline(always))]
const fn rescaled(pts: i64, from: Timebase, to: Timebase) -> i128 {
let numerator = (pts as i128) * (from.num as i128) * (to.den.get() as i128);
let denominator = (from.den.get() as i128) * (to.num as i128);
div_round_half_away(numerator, denominator)
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn checked_recount(ticks: i64, from: Timebase, to: Timebase) -> Option<i64> {
if from.is_identical(&to) {
Some(ticks)
} else {
from.checked_rescale(ticks, to)
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn saturating_recount(ticks: i64, from: Timebase, to: Timebase) -> i64 {
if from.is_identical(&to) {
ticks
} else {
from.saturating_rescale(ticks, to)
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn cmp_i128(lhs: i128, rhs: i128) -> Ordering {
if lhs < rhs {
Ordering::Less
} else if lhs > rhs {
Ordering::Greater
} else {
Ordering::Equal
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn div_round_half_away(n: i128, d: i128) -> i128 {
let q = n / d;
let r = n % d;
if r > 0 && 2 * r >= d {
q + 1
} else if r < 0 && -2 * r >= d {
q - 1
} else {
q
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn div_round_half_up(n: u128, d: u128) -> u128 {
let q = n / d;
let r = n % d;
if 2 * r >= d { q + 1 } else { q }
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn gcd_u32(mut a: u32, mut b: u32) -> u32 {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn gcd_u128(mut a: u128, mut b: u128) -> u128 {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
fn write_clock(f: &mut fmt::Formatter<'_>, pts: i64, timebase: Timebase) -> fmt::Result {
const MS_PER_SEC: u128 = 1_000;
const SECS_PER_MIN: u128 = 60;
const MINS_PER_HOUR: u128 = 60;
let total_ms =
(pts as i128) * (timebase.num as i128) * (MS_PER_SEC as i128) / (timebase.den.get() as i128);
let negative = total_ms < 0;
let magnitude_ms = total_ms.unsigned_abs();
let millis = magnitude_ms % MS_PER_SEC;
let total_secs = magnitude_ms / MS_PER_SEC;
let secs = total_secs % SECS_PER_MIN;
let total_mins = total_secs / SECS_PER_MIN;
let mins = total_mins % MINS_PER_HOUR;
let hours = total_mins / MINS_PER_HOUR;
if negative {
f.write_str("-")?;
}
write!(f, "{hours}:{mins:02}:{secs:02}.{millis:03}")
}
#[cfg(feature = "quickcheck")]
#[cfg_attr(docsrs, doc(cfg(feature = "quickcheck")))]
pub mod quickcheck_impls {
use crate::{Rate, SignedDuration, TimeRange, Timebase, Timestamp};
use core::num::NonZeroI32;
use quickcheck::{Arbitrary, Gen};
pub fn timebase(g: &mut Gen) -> Timebase {
const MAX: u32 = i32::MAX as u32;
let num = (u32::arbitrary(g) % (MAX + 1)) as i32;
let den = (u32::arbitrary(g) % MAX + 1) as i32;
Timebase::new(num, NonZeroI32::new(den).expect("den is in 1..=i32::MAX"))
}
pub fn timestamp(g: &mut Gen) -> Timestamp {
Timestamp::new(non_negative_i64(g), timebase(g))
}
pub fn rate(g: &mut Gen) -> Rate {
let rational = timebase(g);
Rate::fps(rational.num(), rational.den())
}
pub fn signed_duration(g: &mut Gen) -> SignedDuration {
SignedDuration::new(i64::arbitrary(g), timebase(g))
}
pub fn time_range(g: &mut Gen) -> TimeRange {
let a = non_negative_i64(g);
let b = non_negative_i64(g);
let start = a.min(b);
let mut end = a.max(b);
if start == end {
end = end.saturating_add(1);
}
TimeRange::new(start, end, timebase(g))
}
fn non_negative_i64(g: &mut Gen) -> i64 {
loop {
let d = i64::arbitrary(g);
if d >= 0 {
return d;
}
}
}
}
#[cfg(feature = "arbitrary")]
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
const _: () = {
use arbitrary::Arbitrary;
impl<'a> Arbitrary<'a> for Timebase {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let den = u.int_in_range(1..=i32::MAX)?;
let num = u.int_in_range(0..=i32::MAX)?;
let den = core::num::NonZeroI32::new(den).expect("den is in 1..=i32::MAX");
Ok(Timebase::new(num, den))
}
}
impl<'a> Arbitrary<'a> for Timestamp {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
non_negative_i64(u).and_then(|i| u.arbitrary().map(|tb| Self::new(i, tb)))
}
}
impl<'a> Arbitrary<'a> for Rate {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let rational: Timebase = u.arbitrary()?;
Ok(Self::fps(rational.num(), rational.den()))
}
}
impl<'a> Arbitrary<'a> for SignedDuration {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self::new(u.arbitrary()?, u.arbitrary()?))
}
}
impl<'a> Arbitrary<'a> for TimeRange {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let a = non_negative_i64(u)?;
let b = non_negative_i64(u)?;
let start = a.min(b);
let mut end = a.max(b);
if start == end {
end = end.saturating_add(1);
}
Ok(TimeRange::new(start, end, u.arbitrary()?))
}
}
fn non_negative_i64(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<i64> {
loop {
let val = u.arbitrary::<i64>()?;
if val >= 0 {
return Ok(val);
}
}
}
};
#[cfg(test)]
mod tests;
#[cfg(test)]
mod property_tests;
#[cfg(all(test, feature = "serde"))]
mod serde_impl_tests;
#[cfg(all(test, feature = "quickcheck"))]
mod quickcheck_arbitrary_tests;
#[cfg(all(test, feature = "arbitrary"))]
mod arbitrary_impl_tests;
#[cfg(feature = "buffa")]
mod buffa;
#[cfg(feature = "buffa")]
#[doc(hidden)]
pub mod __buffa {
pub mod view {
pub type TimebaseView<'a> = crate::Timebase;
pub type TimeRangeView<'a> = crate::TimeRange;
pub type TimestampView<'a> = crate::Timestamp;
}
}