### Construct TimeRange using new/try_new Source: https://context7.com/findit-ai/mediatime/llms.txt Creates a `[start, end)` range sharing a single `Timebase`. `new` panics if `end < start`; `try_new` returns `None` instead. Degenerate ranges where `start == end` are accepted. ```rust use core::num::NonZeroU32; use core::time::Duration; use mediatime::{Timebase, TimeRange, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); // Standard range: 100 ms to 500 ms let r = TimeRange::new(100, 500, ms); assert_eq!(r.start_pts(), 100); assert_eq!(r.end_pts(), 500); assert_eq!(r.timebase(), ms); assert_eq!(r.duration(), Duration::from_millis(400)); assert_eq!(r.total_pts(), 400); assert!(!r.is_instant()); // Endpoints as Timestamp assert_eq!(r.start(), Timestamp::new(100, ms)); assert_eq!(r.end(), Timestamp::new(500, ms)); // Fallible constructor assert!(TimeRange::try_new(100, 500, ms).is_some()); assert!(TimeRange::try_new(42, 42, ms).is_some()); // instant: allowed assert!(TimeRange::try_new(500, 100, ms).is_none()); // inverted: None // Instant (degenerate) range from a Timestamp let ts = Timestamp::new(250, ms); let inst = TimeRange::instant(ts); assert!(inst.is_instant()); assert_eq!(inst.duration(), Duration::ZERO); // Panics on inverted range // TimeRange::new(500, 100, ms); // would panic ``` -------------------------------- ### TimeRange::new / TimeRange::try_new Source: https://context7.com/findit-ai/mediatime/llms.txt Constructs a half-open time interval [start, end) sharing a single Timebase. `new` panics if end < start, while `try_new` returns None. Degenerate ranges where start == end are accepted. ```APIDOC ## `TimeRange::new` / `TimeRange::try_new` — Construct a half-open time interval Creates a `[start, end)` range sharing a single `Timebase`. `new` panics if `end < start`; `try_new` returns `None` instead. Degenerate ranges where `start == end` are accepted (use `is_instant()` to detect them). ```rust use core::num::NonZeroU32; use core::time::Duration; use mediatime::{Timebase, TimeRange, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); // Standard range: 100 ms to 500 ms let r = TimeRange::new(100, 500, ms); assert_eq!(r.start_pts(), 100); assert_eq!(r.end_pts(), 500); assert_eq!(r.timebase(), ms); assert_eq!(r.duration(), Duration::from_millis(400)); assert_eq!(r.total_pts(), 400); assert!(!r.is_instant()); // Endpoints as Timestamp assert_eq!(r.start(), Timestamp::new(100, ms)); assert_eq!(r.end(), Timestamp::new(500, ms)); // Fallible constructor assert!(TimeRange::try_new(100, 500, ms).is_some()); assert!(TimeRange::try_new(42, 42, ms).is_some()); // instant: allowed assert!(TimeRange::try_new(500, 100, ms).is_none()); // inverted: None // Instant (degenerate) range from a Timestamp let ts = Timestamp::new(250, ms); let inst = TimeRange::instant(ts); assert!(inst.is_instant()); assert_eq!(inst.duration(), Duration::ZERO); // Panics on inverted range // TimeRange::new(500, 100, ms); // would panic ``` ``` -------------------------------- ### Rescale TimeRange to a different Timebase Source: https://context7.com/findit-ai/mediatime/llms.txt Returns a new `TimeRange` with both endpoints rescaled to a target `Timebase`. Monotonic rescaling preserves the `start <= end` invariant. Instant ranges stay instant after rescaling. ```rust use core::num::NonZeroU32; use mediatime::{Timebase, TimeRange, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); let r = TimeRange::new(1_000, 2_000, ms); // 1.0 s – 2.0 s let r2 = r.rescale_to(mpegts); assert_eq!(r2.start_pts(), 90_000); assert_eq!(r2.end_pts(), 180_000); assert_eq!(r2.timebase(), mpegts); // Duration is preserved across rescaling assert_eq!(r.duration(), r2.duration()); // Instant range stays instant let inst = TimeRange::instant(Timestamp::new(500, ms)); assert!(inst.rescale_to(mpegts).is_instant()); // Builder-style modification of endpoints let r3 = r.with_start(1_500).with_end(3_000); assert_eq!(r3.start_pts(), 1_500); assert_eq!(r3.end_pts(), 3_000); ``` -------------------------------- ### Linear Interpolation with TimeRange::interpolate Source: https://context7.com/findit-ai/mediatime/llms.txt Returns a `Timestamp` linearly interpolated between `start` and `end`. `t = 0.0` returns `start`, `t = 1.0` returns `end`. Values of `t` outside `[0.0, 1.0]` are clamped. ```rust use core::num::NonZeroU32; use mediatime::{Timebase, TimeRange}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let r = TimeRange::new(100, 500, ms); // [100 ms, 500 ms) assert_eq!(r.interpolate(0.0).pts(), 100); // start assert_eq!(r.interpolate(1.0).pts(), 500); // end assert_eq!(r.interpolate(0.5).pts(), 300); // midpoint assert_eq!(r.interpolate(0.25).pts(), 200); // Out-of-range t is clamped, not an error assert_eq!(r.interpolate(-1.0).pts(), 100); assert_eq!(r.interpolate(2.0).pts(), 500); // Map a bias value b ∈ [-1, 1] onto the range let bias = 0.6_f64; // slightly toward end let t = (bias + 1.0) * 0.5; // → 0.8 assert_eq!(r.interpolate(t).pts(), 420); ``` -------------------------------- ### TimeRange::interpolate Source: https://context7.com/findit-ai/mediatime/llms.txt Performs linear interpolation within a time range, returning a Timestamp. `t = 0.0` returns the start, `t = 1.0` returns the end, and values outside [0.0, 1.0] are clamped. ```APIDOC ## `TimeRange::interpolate` — Linear interpolation within a range Returns a `Timestamp` linearly interpolated between `start` and `end`. `t = 0.0` returns `start`, `t = 1.0` returns `end`, `t = 0.5` the midpoint. Values of `t` outside `[0.0, 1.0]` are clamped. Rounds toward zero. ```rust use core::num::NonZeroU32; use mediatime::{Timebase, TimeRange}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let r = TimeRange::new(100, 500, ms); // [100 ms, 500 ms) assert_eq!(r.interpolate(0.0).pts(), 100); // start assert_eq!(r.interpolate(1.0).pts(), 500); // end assert_eq!(r.interpolate(0.5).pts(), 300); // midpoint assert_eq!(r.interpolate(0.25).pts(), 200); // Out-of-range t is clamped, not an error assert_eq!(r.interpolate(-1.0).pts(), 100); assert_eq!(r.interpolate(2.0).pts(), 500); // Map a bias value b ∈ [-1, 1] onto the range let bias = 0.6_f64; // slightly toward end let t = (bias + 1.0) * 0.5; // → 0.8 assert_eq!(r.interpolate(t).pts(), 420); ``` ``` -------------------------------- ### TimeRange::rescale_to Source: https://context7.com/findit-ai/mediatime/llms.txt Rebases a time range into a different timebase, returning a new TimeRange. This operation preserves the duration and the start <= end invariant. ```APIDOC ## `TimeRange::rescale_to` — Rebase a range into a different timebase Returns a new `TimeRange` with both endpoints rescaled to a target `Timebase` via `Timebase::rescale_pts`. Monotonic rescaling preserves the `start <= end` invariant. Instant ranges stay instant after rescaling. ```rust use core::num::NonZeroU32; use mediatime::{Timebase, TimeRange, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); let r = TimeRange::new(1_000, 2_000, ms); // 1.0 s – 2.0 s let r2 = r.rescale_to(mpegts); assert_eq!(r2.start_pts(), 90_000); assert_eq!(r2.end_pts(), 180_000); assert_eq!(r2.timebase(), mpegts); // Duration is preserved across rescaling assert_eq!(r.duration(), r2.duration()); // Instant range stays instant let inst = TimeRange::instant(Timestamp::new(500, ms)); assert!(inst.rescale_to(mpegts).is_instant()); // Builder-style modification of endpoints let r3 = r.with_start(1_500).with_end(3_000); assert_eq!(r3.start_pts(), 1_500); assert_eq!(r3.end_pts(), 3_000); ``` ``` -------------------------------- ### Quickcheck Property-Based Testing for Mediatime Types Source: https://context7.com/findit-ai/mediatime/llms.txt Shows how to use quickcheck for property-based testing of Timebase, Timestamp, and TimeRange. Requires the 'quickcheck' feature. ```rust #[cfg(feature = "quickcheck")] { use quickcheck::{Arbitrary, Gen}; use mediatime::{Timebase, Timestamp, TimeRange}; let mut g = Gen::new(512); let tb = Timebase::arbitrary(&mut g); assert!(tb.den().get() != 0); // guaranteed by impl let ts = Timestamp::arbitrary(&mut g); assert!(ts.pts() >= 0); // impl only generates non-negative let r = TimeRange::arbitrary(&mut g); assert!(r.start_pts() <= r.end_pts()); } ``` -------------------------------- ### Adding Mediatime Dependency Source: https://github.com/findit-ai/mediatime/blob/main/README.md Shows how to add the mediatime crate to your project's Cargo.toml file. ```toml [dependencies] mediatime = "0.1" ``` -------------------------------- ### Enable mediatime features in Cargo.toml Source: https://context7.com/findit-ai/mediatime/llms.txt Enable serialization or property-testing support via Cargo features by adding the `features` key to your `mediatime` dependency in `Cargo.toml`. ```toml # Cargo.toml [dependencies] mediatime = { version = "0.1", features = ["serde"] } ``` -------------------------------- ### Construct Timebase with new() Source: https://context7.com/findit-ai/mediatime/llms.txt Creates a Timebase from numerator and denominator. Equality and hashing operate on the reduced rational. Use `with_num` and `with_den` for mutable construction or `set_num`/`set_den` for in-place modification. ```rust use core::num::NonZeroU32; use mediatime::Timebase; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } // Common media timebases let ms = Timebase::new(1, nz(1_000)); // millisecond PTS let mpegts = Timebase::new(1, nz(90_000)); // MPEG-TS / DVB let audio = Timebase::new(1, nz(48_000)); // 48 kHz audio samples let ntsc = Timebase::new(30_000, nz(1001)); // NTSC frame rate // Value-based equality: 1/2 == 2/4 == 3/6 assert_eq!(Timebase::new(1, nz(2)), Timebase::new(2, nz(4))); assert_eq!(Timebase::new(2, nz(4)), Timebase::new(3, nz(6))); // Ordering is numeric, not lexicographic assert!(Timebase::new(1, nz(3)) < Timebase::new(1, nz(2))); // 1/3 < 1/2 assert!(Timebase::new(1, nz(2)) < Timebase::new(2, nz(3))); // 1/2 < 2/3 // Mutating builders let tb = Timebase::new(1, nz(1000)) .with_num(30_000) .with_den(nz(1001)); assert_eq!(tb.num(), 30_000); assert_eq!(tb.den().get(), 1001); // In-place setters (return &mut Self for chaining) let mut tb2 = Timebase::new(1, nz(1000)); tb2.set_num(25).set_den(nz(2)); assert_eq!(tb2.num(), 25); ``` -------------------------------- ### TimeRange Interpolation and Duration Source: https://github.com/findit-ai/mediatime/blob/main/README.md Demonstrates creating a `TimeRange`, interpolating a point within it, and calculating its total duration. ```rust // 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(), Duration::from_millis(400)); ``` -------------------------------- ### Arbitrary/libFuzzer Integration for TimeRange Source: https://context7.com/findit-ai/mediatime/llms.txt Demonstrates integrating with arbitrary and libFuzzer for fuzz testing TimeRange. Requires the 'arbitrary' feature. ```rust #[cfg(feature = "arbitrary")] { use arbitrary::{Arbitrary, Unstructured}; use mediatime::TimeRange; let raw_data: &[u8] = &[0x01, 0x02, 0x03, /* ... corpus bytes ... */]; let mut u = Unstructured::new(raw_data); if let Ok(r) = TimeRange::arbitrary(&mut u) { assert!(r.start_pts() <= r.end_pts()); assert!(r.timebase().den().get() != 0); } } ``` -------------------------------- ### Comparing Timestamps Across Timebases Source: https://github.com/findit-ai/mediatime/blob/main/README.md Demonstrates how mediatime::Timestamp instances with different timebases but representing the same instant compare as equal. Also shows duration calculation between equal timestamps. ```rust 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)); ``` -------------------------------- ### Converting Frames to Duration Source: https://github.com/findit-ai/mediatime/blob/main/README.md Shows how to use `Timebase` as a frame rate to convert a number of frames into a `Duration`. ```rust // 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)); ``` -------------------------------- ### Serde Serialization/Deserialization of Timestamp Source: https://context7.com/findit-ai/mediatime/llms.txt Demonstrates how to serialize and deserialize a Timestamp using serde. Requires the 'serde' feature to be enabled. ```rust #[cfg(feature = "serde")] { use mediatime::{Timebase, Timestamp, TimeRange}; use core::num::NonZeroU32; let ms = Timebase::new(1, NonZeroU32::new(1_000).unwrap()); let ts = Timestamp::new(1_500, ms); let json = serde_json::to_string(&ts).unwrap(); // {"pts":1500,"timebase":{"numerator":1,"denominator":1000}} let ts2: Timestamp = serde_json::from_str(&json).unwrap(); assert_eq!(ts, ts2); } ``` -------------------------------- ### Rescaling Timestamps with Mediatime Source: https://github.com/findit-ai/mediatime/blob/main/README.md Illustrates the `rescale` method for converting a PTS value from one timebase to another, with rounding towards zero, similar to FFmpeg's `av_rescale_q`. ```rust // `av_rescale_q`-style conversion, rounding toward zero. assert_eq!(ms.rescale(500, mpegts), 45_000); ``` -------------------------------- ### Timebase::new Source: https://context7.com/findit-ai/mediatime/llms.txt Constructs a Timebase rational number from a numerator and a non-zero denominator. Supports chained mutation with `with_num` and `with_den`, and in-place modification with `set_num` and `set_den`. ```APIDOC ## Timebase::new — Construct a rational timebase Creates a `Timebase` from a `u32` numerator and a `NonZeroU32` denominator. Equality and hashing operate on the reduced rational, so structurally different fractions representing the same value are treated as equal. Ordering is numeric. ### Method `Timebase::new(num: u32, den: NonZeroU32) -> Timebase` ### Parameters #### Path Parameters - **num** (u32) - Required - The numerator of the rational number. - **den** (NonZeroU32) - Required - The denominator of the rational number. ### Request Example ```rust use core::num::NonZeroU32; use mediatime::Timebase; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } // Common media timebases let ms = Timebase::new(1, nz(1_000)); // millisecond PTS let mpegts = Timebase::new(1, nz(90_000)); // MPEG-TS / DVB let audio = Timebase::new(1, nz(48_000)); // 48 kHz audio samples let ntsc = Timebase::new(30_000, nz(1001)); // NTSC frame rate // Value-based equality: 1/2 == 2/4 == 3/6 assert_eq!(Timebase::new(1, nz(2)), Timebase::new(2, nz(4))); assert_eq!(Timebase::new(2, nz(4)), Timebase::new(3, nz(6))); // Ordering is numeric, not lexicographic assert!(Timebase::new(1, nz(3)) < Timebase::new(1, nz(2))); // 1/3 < 1/2 assert!(Timebase::new(1, nz(2)) < Timebase::new(2, nz(3))); // 1/2 < 2/3 // Mutating builders let tb = Timebase::new(1, nz(1000)) .with_num(30_000) .with_den(nz(1001)); assert_eq!(tb.num(), 30_000); assert_eq!(tb.den().get(), 1001); // In-place setters (return &mut Self for chaining) let mut tb2 = Timebase::new(1, nz(1000)); tb2.set_num(25).set_den(nz(2)); assert_eq!(tb2.num(), 25); ``` ``` -------------------------------- ### `Timestamp::new` Source: https://context7.com/findit-ai/mediatime/llms.txt Constructs a `Timestamp` by pairing an `i64` PTS (Presentation Timestamp) value with a `Timebase`. `Timestamp` objects support semantic comparison and hashing, meaning timestamps representing the same instant in different timebases are considered equal, making them suitable for use as keys in hash maps. ```APIDOC ## `Timestamp::new` — Construct a tagged presentation timestamp Creates a `Timestamp` as an `i64` PTS value paired with a `Timebase`. Comparison and hashing are semantic (same instant in different timebases are equal), making `Timestamp` safe as a `HashMap` or `BTreeMap` key without pre-canonicalization. Negative PTS values are valid and represent pre-roll / edit-list timestamps. ### Usage Example ```rust use core::num::NonZeroU32; use mediatime::{Timebase, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); // Same instant, different timebases → equal let a = Timestamp::new(1_000, ms); let b = Timestamp::new(90_000, mpegts); assert_eq!(a, b); assert!(!(a < b) && !(a > b)); // Ordering is by represented instant, not raw PTS let c = Timestamp::new(500, ms); // 0.5 s let d = Timestamp::new(1_000, ms); // 1.0 s assert!(c < d); // Negative PTS (pre-roll / edit lists) is valid let preroll = Timestamp::new(-500, ms); // -500 ms assert!(preroll < c); // Builder pattern let mut ts = Timestamp::new(100, ms); let ts2 = ts.with_pts(200); assert_eq!(ts2.pts(), 200); ts.set_pts(300); assert_eq!(ts.pts(), 300); ``` ``` -------------------------------- ### Construct and Manipulate Timestamps Source: https://context7.com/findit-ai/mediatime/llms.txt Create `Timestamp` objects with a PTS value and a `Timebase`. Timestamps are comparable and hashable semantically across different timebases. Negative PTS values are supported for pre-roll. Includes methods for modifying PTS values. ```rust use core::num::NonZeroU32; use mediatime::{Timebase, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); // Same instant, different timebases → equal let a = Timestamp::new(1_000, ms); let b = Timestamp::new(90_000, mpegts); assert_eq!(a, b); assert!(!(a < b) && !(a > b)); // Ordering is by represented instant, not raw PTS let c = Timestamp::new(500, ms); // 0.5 s let d = Timestamp::new(1_000, ms); // 1.0 s assert!(c < d); // Negative PTS (pre-roll / edit lists) is valid let preroll = Timestamp::new(-500, ms); // -500 ms assert!(preroll < c); // Builder pattern let mut ts = Timestamp::new(100, ms); let ts2 = ts.with_pts(200); assert_eq!(ts2.pts(), 200); ts.set_pts(300); assert_eq!(ts.pts(), 300); ``` -------------------------------- ### `Timestamp::rescale_to` Source: https://context7.com/findit-ai/mediatime/llms.txt Converts a `Timestamp` to represent the same instant but expressed in a different target `Timebase`. This operation uses `Timebase::rescale_pts` for the conversion, which rounds the resulting PTS value towards zero. Round-tripping through a coarser timebase may result in a loss of sub-tick precision. ```APIDOC ## `Timestamp::rescale_to` — Convert timestamp to a different timebase Returns a new `Timestamp` representing the same instant expressed in a target `Timebase`. Rounds toward zero via `Timebase::rescale_pts`. ### Usage Example ```rust use core::num::NonZeroU32; use mediatime::{Timebase, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); let ts_ms = Timestamp::new(1_000, ms); let ts_mpeg = ts_ms.rescale_to(mpegts); assert_eq!(ts_mpeg.pts(), 90_000); assert_eq!(ts_mpeg.timebase(), mpegts); assert_eq!(ts_ms, ts_mpeg); // same instant // Round-trip through a coarser timebase can lose sub-tick precision let coarse = Timebase::new(1, nz(25)); // 25 Hz let ts_coarse = ts_ms.rescale_to(coarse); assert_eq!(ts_coarse.pts(), 25); // 1.0 s = 25 ticks @ 25 Hz let roundtrip = ts_coarse.rescale_to(ms); assert_eq!(roundtrip.pts(), 1_000); // exact for whole-tick values ``` ``` -------------------------------- ### Convert Frame Count to Duration with Timebase Source: https://context7.com/findit-ai/mediatime/llms.txt Use `frames_to_duration` to convert a frame count to a `Duration` using a `Timebase` configured as a frame rate. It uses integer arithmetic for precision, especially with fractional frame rates like NTSC. ```rust use core::num::NonZeroU32; use core::time::Duration; use mediatime::Timebase; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let fps30 = Timebase::new(30, nz(1)); assert_eq!(fps30.frames_to_duration(30), Duration::from_secs(1)); assert_eq!(fps30.frames_to_duration(15), Duration::from_millis(500)); assert_eq!(fps30.frames_to_duration(0), Duration::ZERO); // NTSC: 30000 frames @ 30000/1001 fps = exactly 1001 seconds let ntsc = Timebase::new(30_000, nz(1001)); assert_eq!(ntsc.frames_to_duration(30_000), Duration::from_secs(1001)); // 15 frames ≈ 500.5 ms assert_eq!(ntsc.frames_to_duration(15), Duration::from_nanos(500_500_000)); // 48 kHz audio: 48000 samples = 1 second let audio = Timebase::new(48_000, nz(1)); assert_eq!(audio.frames_to_duration(48_000), Duration::from_secs(1)); assert_eq!(audio.frames_to_duration(24_000), Duration::from_millis(500)); ``` -------------------------------- ### `Timebase::frames_to_duration` Source: https://context7.com/findit-ai/mediatime/llms.txt Converts a frame count to a `Duration` using the `Timebase` as a frame rate (frames per second). It employs integer-only arithmetic via nanoseconds for precise calculations, especially for fractional frame rates like NTSC's 30000/1001. ```APIDOC ## `Timebase::frames_to_duration` — Frame count to wall-clock duration Treats a `Timebase` as a frame rate (frames per second) and converts a frame count to a `Duration`. Uses integer-only arithmetic via nanoseconds, so NTSC's `30000/1001` produces exact results for whole-frame counts. ### Usage Example ```rust use core::num::NonZeroU32; use core::time::Duration; use mediatime::Timebase; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let fps30 = Timebase::new(30, nz(1)); assert_eq!(fps30.frames_to_duration(30), Duration::from_secs(1)); assert_eq!(fps30.frames_to_duration(15), Duration::from_millis(500)); assert_eq!(fps30.frames_to_duration(0), Duration::ZERO); // NTSC: 30000 frames @ 30000/1001 fps = exactly 1001 seconds let ntsc = Timebase::new(30_000, nz(1001)); assert_eq!(ntsc.frames_to_duration(30_000), Duration::from_secs(1001)); // 15 frames ≈ 500.5 ms assert_eq!(ntsc.frames_to_duration(15), Duration::from_nanos(500_500_000)); // 48 kHz audio: 48000 samples = 1 second let audio = Timebase::new(48_000, nz(1)); assert_eq!(audio.frames_to_duration(48_000), Duration::from_secs(1)); assert_eq!(audio.frames_to_duration(24_000), Duration::from_millis(500)); ``` ``` -------------------------------- ### Rescale Timestamp to Different Timebase Source: https://context7.com/findit-ai/mediatime/llms.txt Convert a `Timestamp` to represent the same instant in a different `Timebase` using `rescale_to`. This operation rounds toward zero. Round-tripping through coarser timebases may lose sub-tick precision. ```rust use core::num::NonZeroU32; use mediatime::{Timebase, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); let ts_ms = Timestamp::new(1_000, ms); let ts_mpeg = ts_ms.rescale_to(mpegts); assert_eq!(ts_mpeg.pts(), 90_000); assert_eq!(ts_mpeg.timebase(), mpegts); assert_eq!(ts_ms, ts_mpeg); // same instant // Round-trip through a coarser timebase can lose sub-tick precision let coarse = Timebase::new(1, nz(25)); // 25 Hz let ts_coarse = ts_ms.rescale_to(coarse); assert_eq!(ts_coarse.pts(), 25); // 1.0 s = 25 ticks @ 25 Hz let roundtrip = ts_coarse.rescale_to(ms); assert_eq!(roundtrip.pts(), 1_000); // exact for whole-tick values ``` -------------------------------- ### Timestamp::saturating_sub_duration Source: https://context7.com/findit-ai/mediatime/llms.txt Shifts a timestamp backward by a given Duration, saturating at i64::MIN on underflow. Useful for seeding warmup states. ```APIDOC ## `Timestamp::saturating_sub_duration` — Shift a timestamp backward Returns a new `Timestamp` in the same timebase shifted backward by the given `Duration`. Saturates at `i64::MIN` rather than panicking on underflow. Useful for seeding warmup-filter state so the first detected event can fire immediately. ```rust use core::num::NonZeroU32; use core::time::Duration; use mediatime::{Timebase, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); // Normal case: 1500 ms − 500 ms = 1000 ms let ts = Timestamp::new(1_500, ms); let shifted = ts.saturating_sub_duration(Duration::from_millis(500)); assert_eq!(shifted.pts(), 1_000); assert_eq!(shifted.timebase(), ms); // Saturation: underflow clamps to i64::MIN rather than wrapping let near_floor = Timestamp::new(i64::MIN + 10, ms); let clamped = near_floor.saturating_sub_duration(Duration::from_secs(1)); assert_eq!(clamped.pts(), i64::MIN); // Practical use: seed a cut-detector warmup state let first_frame = Timestamp::new(0, ms); let warmup_start = first_frame.saturating_sub_duration(Duration::from_millis(200)); assert_eq!(warmup_start.pts(), -200); ``` ``` -------------------------------- ### Convert Duration to PTS with Timebase Source: https://context7.com/findit-ai/mediatime/llms.txt Use `duration_to_pts` to convert a `Duration` into PTS units for a given `Timebase`. This is the inverse of frame/sample conversion. It rounds toward zero and returns 0 for degenerate timebases, saturating to `i64::MAX` for extremely large durations. ```rust use core::num::NonZeroU32; use core::time::Duration; use mediatime::Timebase; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); assert_eq!(ms.duration_to_pts(Duration::from_millis(1500)), 1500); assert_eq!(ms.duration_to_pts(Duration::ZERO), 0); // 2 seconds → 180,000 MPEG-TS ticks assert_eq!(mpegts.duration_to_pts(Duration::from_secs(2)), 180_000); // Degenerate (zero numerator) → always 0 let degenerate = Timebase::new(0, nz(1)); assert_eq!(degenerate.duration_to_pts(Duration::from_secs(100)), 0); // Saturation on overflow let fps1 = Timebase::new(1, nz(1)); assert_eq!(fps1.duration_to_pts(Duration::new(u64::MAX, 0)), i64::MAX); ``` -------------------------------- ### Const-Evaluable Cross-Timebase Timestamp Comparison Source: https://context7.com/findit-ai/mediatime/llms.txt Employ `cmp_semantic` for `const fn` comparisons of timestamps, ensuring accurate ordering even across different timebases. It optimizes for identical timebases with a direct PTS comparison and uses precise cross-multiplication for differing timebases. ```rust use core::cmp::Ordering; use core::num::NonZeroU32; use mediatime::{Timebase, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); // Same-timebase fast path let a = Timestamp::new(100, ms); let b = Timestamp::new(200, ms); assert_eq!(a.cmp_semantic(&b), Ordering::Less); assert_eq!(b.cmp_semantic(&a), Ordering::Greater); assert_eq!(a.cmp_semantic(&a), Ordering::Equal); // Cross-timebase slow path — exact, no rounding let one_sec_ms = Timestamp::new(1_000, ms); let one_sec_mpeg = Timestamp::new(90_000, mpegts); let half_ms = Timestamp::new(500, ms); let two_mpeg = Timestamp::new(180_000, mpegts); assert_eq!(one_sec_ms.cmp_semantic(&one_sec_mpeg), Ordering::Equal); assert_eq!(half_ms.cmp_semantic(&one_sec_mpeg), Ordering::Less); assert_eq!(two_mpeg.cmp_semantic(&one_sec_ms), Ordering::Greater); // Works in const context const TB: Timebase = Timebase::new(1, unsafe { NonZeroU32::new_unchecked(1000) }); const A: Timestamp = Timestamp::new(100, TB); const B: Timestamp = Timestamp::new(200, TB); const ORD: Ordering = A.cmp_semantic(&B); assert_eq!(ORD, Ordering::Less); ``` -------------------------------- ### Shift Timestamp Backward with Saturation Source: https://context7.com/findit-ai/mediatime/llms.txt Use `saturating_sub_duration` to shift a timestamp backward by a specified duration. This method prevents panics on underflow by saturating at `i64::MIN`. It's useful for initializing states that require a preceding time reference. ```rust use core::num::NonZeroU32; use core::time::Duration; use mediatime::{Timebase, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); // Normal case: 1500 ms − 500 ms = 1000 ms let ts = Timestamp::new(1_500, ms); let shifted = ts.saturating_sub_duration(Duration::from_millis(500)); assert_eq!(shifted.pts(), 1_000); assert_eq!(shifted.timebase(), ms); // Saturation: underflow clamps to i64::MIN rather than wrapping let near_floor = Timestamp::new(i64::MIN + 10, ms); let clamped = near_floor.saturating_sub_duration(Duration::from_secs(1)); assert_eq!(clamped.pts(), i64::MIN); // Practical use: seed a cut-detector warmup state let first_frame = Timestamp::new(0, ms); let warmup_start = first_frame.saturating_sub_duration(Duration::from_millis(200)); assert_eq!(warmup_start.pts(), -200); ``` -------------------------------- ### `Timebase::duration_to_pts` Source: https://context7.com/findit-ai/mediatime/llms.txt Converts a `Duration` into the integer number of PTS (Presentation Timestamp) units for a given `Timebase`. This is the inverse of frame/sample conversion and rounds towards zero. It returns 0 for a degenerate timebase (zero numerator) and saturates to `i64::MAX` for extremely large durations. ```APIDOC ## `Timebase::duration_to_pts` — Wall-clock duration to PTS units Inverse of frame/sample conversion: converts a `Duration` into the integer number of PTS units this timebase represents, rounding toward zero. Returns `0` for a degenerate zero-numerator timebase. Saturates to `i64::MAX` if the duration is absurdly large. ### Usage Example ```rust use core::num::NonZeroU32; use core::time::Duration; use mediatime::Timebase; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); assert_eq!(ms.duration_to_pts(Duration::from_millis(1500)), 1500); assert_eq!(ms.duration_to_pts(Duration::ZERO), 0); // 2 seconds → 180,000 MPEG-TS ticks assert_eq!(mpegts.duration_to_pts(Duration::from_secs(2)), 180_000); // Degenerate (zero numerator) → always 0 let degenerate = Timebase::new(0, nz(1)); assert_eq!(degenerate.duration_to_pts(Duration::from_secs(100)), 0); // Saturation on overflow let fps1 = Timebase::new(1, nz(1)); assert_eq!(fps1.duration_to_pts(Duration::new(u64::MAX, 0)), i64::MAX); ``` ``` -------------------------------- ### Timebase::rescale_pts / Timebase::rescale Source: https://context7.com/findit-ai/mediatime/llms.txt Converts a PTS value from one timebase to another using 128-bit intermediates to prevent overflow. Rounds toward zero and saturates on pathological overflow. ```APIDOC ## Timebase::rescale_pts / Timebase::rescale — Cross-timebase PTS conversion Converts a PTS value from one timebase to another, equivalent to FFmpeg's `av_rescale_q`. Uses a 128-bit intermediate to avoid overflow across the full `i64` × `u32` × `u32` range. Rounds toward zero. Saturates to `i64::MAX` / `i64::MIN` on pathological overflow rather than wrapping. ### Method `Timebase::rescale_pts(pts: i64, from: Timebase, to: Timebase) -> i64` `from.rescale(pts: i64, to: Timebase) -> i64` ### Parameters #### Path Parameters - **pts** (i64) - Required - The presentation timestamp to convert. - **from** (Timebase) - Required - The source timebase. - **to** (Timebase) - Required - The target timebase. ### Request Example ```rust use core::num::NonZeroU32; use mediatime::Timebase; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); // Static form: Timebase::rescale_pts(pts, from, to) assert_eq!(Timebase::rescale_pts(1_000, ms, mpegts), 90_000); assert_eq!(Timebase::rescale_pts(90_000, mpegts, ms), 1_000); // Method form: from.rescale(pts, to) assert_eq!(ms.rescale(500, mpegts), 45_000); assert_eq!(mpegts.rescale(45_000, ms), 500); // Identity round-trip assert_eq!(ms.rescale(42, ms), 42); // Rounds toward zero (not floor) let coarse = Timebase::new(1, nz(3)); assert_eq!(ms.rescale( 1, coarse), 0); // +0.003 s → 0 assert_eq!(ms.rescale(-1, coarse), 0); // -0.003 s → 0 // Overflow saturates let huge = Timebase::new(u32::MAX, nz(1)); let fine = Timebase::new(1, nz(u32::MAX)); assert_eq!(huge.rescale( 1_000_000, fine), i64::MAX); assert_eq!(huge.rescale(-1_000_000, fine), i64::MIN); ``` ``` -------------------------------- ### Timestamp::cmp_semantic Source: https://context7.com/findit-ai/mediatime/llms.txt Provides a const fn for semantic comparison of timestamps, handling identical timebases efficiently and using cross-multiplication for different timebases. ```APIDOC ## `Timestamp::cmp_semantic` — `const fn` cross-timebase comparison A `const fn` equivalent of `Ord::cmp` that compares two timestamps by the instant they represent. Takes a fast path when both timebases are identical (direct PTS compare); otherwise uses 128-bit cross-multiplication for exact, division-free ordering. ```rust use core::cmp::Ordering; use core::num::NonZeroU32; use mediatime::{Timebase, Timestamp}; const fn nz(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() } let ms = Timebase::new(1, nz(1_000)); let mpegts = Timebase::new(1, nz(90_000)); // Same-timebase fast path let a = Timestamp::new(100, ms); let b = Timestamp::new(200, ms); assert_eq!(a.cmp_semantic(&b), Ordering::Less); assert_eq!(b.cmp_semantic(&a), Ordering::Greater); assert_eq!(a.cmp_semantic(&a), Ordering::Equal); // Cross-timebase slow path — exact, no rounding let one_sec_ms = Timestamp::new(1_000, ms); let one_sec_mpeg = Timestamp::new(90_000, mpegts); let half_ms = Timestamp::new(500, ms); let two_mpeg = Timestamp::new(180_000, mpegts); assert_eq!(one_sec_ms.cmp_semantic(&one_sec_mpeg), Ordering::Equal); assert_eq!(half_ms.cmp_semantic(&one_sec_mpeg), Ordering::Less); assert_eq!(two_mpeg.cmp_semantic(&one_sec_ms), Ordering::Greater); // Works in const context const TB: Timebase = Timebase::new(1, unsafe { NonZeroU32::new_unchecked(1000) }); const A: Timestamp = Timestamp::new(100, TB); const B: Timestamp = Timestamp::new(200, TB); const ORD: Ordering = A.cmp_semantic(&B); assert_eq!(ORD, Ordering::Less); ``` ```