Expand description
mediatime
Exact-integer rational time types for media pipelines — FFmpeg-style Timebase, Timestamp, and TimeRange for Rust. no_std by default, zero dependencies, const fn throughout.
§Overview
mediatime provides the same three primitives every media pipeline reinvents, done once with integer-exact semantics:
Timebase— a rationalnum/den(bothu32, non-zero denominator). Directly mirrors FFmpeg’sAVRational. Common values:1/1000(ms PTS),1/90000(MPEG-TS),30000/1001(NTSC frame rate).Timestamp— ani64PTS tagged with aTimebase. Two timestamps compare by the instant they represent, not by their raw(pts, timebase)tuple, soTimestamp(1_000, 1/1000)equalsTimestamp(90_000, 1/90_000). Cross-timebase comparison uses a 128-bit cross-multiply — no division, no rounding.TimeRange— a half-open[start, end)interval sharing a singleTimebase. Carries the endpoints as raw PTS; returnsTimestampon demand.
Everything is const fn. The crate only uses core — no allocation, no dependencies. Use it as the time layer for scene detectors, demuxers, NLE timelines, or anywhere you’d otherwise pass f64 seconds around and pay for rounding drift later.
§Why not f64 seconds?
Floating-point seconds accumulate drift: 0.1 + 0.2 != 0.3. Real video timestamps are already integer PTS in an integer timebase — converting to f64 for arithmetic only to convert back on output introduces rounding error. mediatime keeps the representation that the stream actually carries, and does exact rational arithmetic on it.
Equality semantics show the win:
f64 seconds: 0.1 + 0.2 == 0.3 → false
mediatime::Timestamp: 100 ms == 9000 ticks @ 1/90000 → true§Features
- Value-based equality and ordering.
1/2 == 2/4 == 3/6;Timestamp(1000, 1/1000) == Timestamp(90_000, 1/90_000). Cross-timebasecmpuses 128-bit cross-multiply — exact for anyu32numerator/denominator with anyi64PTS. - Hash agrees with Eq. Hashes the reduced-form rational, so equal rationals hash identically and you can use these types as
HashMapkeys. - FFmpeg-style utilities.
rescale_pts(a.k.a.av_rescale_q),frames_to_duration,duration_to_pts,duration_since,saturating_sub_duration. TimeRangeinterpolation. Linear midpoint (interpolate(t)) for placing an event somewhere between fade-out and fade-in frames, witht ∈ [0, 1]clamped.no_std+no_alloclibrary. The library builds withoutstdandalloc; tests usestd.const fnthroughout. BuildTimebase/Timestamp/TimeRangeinconstcontext.
§Example
use core::num::NonZeroU32;
use core::time::Duration;
use mediatime::{Timebase, Timestamp, TimeRange};
// FFmpeg-style rational timebases.
let ms = Timebase::new(1, NonZeroU32::new(1000).unwrap());
let mpegts = Timebase::new(1, NonZeroU32::new(90_000).unwrap());
// Same instant in two different timebases — they compare equal.
let a = Timestamp::new(1_000, ms);
let b = Timestamp::new(90_000, mpegts);
assert_eq!(a, b);
assert_eq!(a.duration_since(&b), Some(Duration::ZERO));
// `av_rescale_q`-style conversion, rounding toward zero.
assert_eq!(ms.rescale(500, mpegts), 45_000);
// Frame rate helpers — treat `Timebase` as fps and count frames.
let ntsc = Timebase::new(30_000, NonZeroU32::new(1001).unwrap());
assert_eq!(ntsc.frames_to_duration(30_000), Duration::from_secs(1001));
// A half-open [start, end) range with interpolation.
let r = TimeRange::new(100, 500, ms);
assert_eq!(r.interpolate(0.5).pts(), 300);
assert_eq!(r.duration(), Some(Duration::from_millis(400)));§Installation
[dependencies]
mediatime = "0.1"§MSRV
Rust 1.85.
§License
mediatime is under the terms of both the MIT license and the
Apache License (Version 2.0).
See LICENSE-APACHE, LICENSE-MIT for details.
Copyright (c) 2026 FinDIT Studio authors.