pub struct Timebase { /* private fields */ }Expand description
A media timebase represented as a rational number: a non-negative numerator over a strictly positive denominator.
Typical values: 1/1000 for millisecond PTS, 1/90000 for MPEG-TS,
1/48000 for audio samples, 30000/1001 for NTSC video (when used as a
frame rate).
§Why both halves are signed
FFmpeg’s rational is signed — AVRational { int num; int den; } — and it is
the type this crate exists to interoperate with: av_rescale_q takes two of
them, AVFrame::time_base is one, and AVFrame::pts is an int64_t whose
AV_NOPTS_VALUE sentinel is i64::MIN, so signedness is load-bearing
throughout that API. An unsigned numerator or denominator above i32::MAX
is representable but cannot round-trip into an AVRational — usable in
Rust, unusable at the boundary. Matching the width and the sign removes that
failure mode by construction.
Storage points the same way: sqlx has no Type<Postgres>/Encode<Postgres>
for u32, whereas i32 is a native INTEGER on PostgreSQL, MySQL and
SQLite alike, so a storage face reads these fields directly instead of
widening to i64 and narrowing back through an error path.
Two other decoder SDKs were surveyed and impose no counter-pressure:
Blackmagic RAW (GetFrameRate(float*)) and RED R3D
(float VideoAudioFramerate()) are frame-indexed with a floating-point
rate and never hand out a rational at all.
§Invariants
num >= 0 and den > 0. NonZeroI32 carries only the non-zero half, so
the rest is enforced by Timebase::new (and by every setter, which routes
through it). A zero numerator stays legal: it is a degenerate timebase,
valid to construct and to compare, but not a valid rescale target — see
Timebase::checked_rescale.
AVRational itself permits a negative denominator and normalizes the sign
into the numerator via av_reduce; that is a convention rather than a type
guarantee, and AVRational is laxer than this crate needs because it also
serves aspect ratios. Here it is a type-level guarantee instead.
§Equality and ordering
Comparison is value-based: 1/2 equals 2/4, and 1/3 < 2/3 < 1/1.
Hash hashes the reduced (lowest-terms) form, so equal rationals hash
the same. Cross-multiplication uses i64 intermediates — exact for any
i32 numerator / denominator.
§The well-known roster
The constants on this type are the timebases containers and codecs
actually declare, each with a name Self::from_name reads and
Self::well_known_name writes back. They come in three families:
- Clock subdivisions —
SECONDS,MILLIS,MICROSandNANOS, plusMPEG_90K, the fixed clock MPEG counts PTS in. - Audio sample intervals — fourteen,
HZ_8Kup toHZ_192K: one tick per sample at each rate the audio codecs declare. - Frame intervals — eight, the reciprocals of
Rate’s eight frame rates entry for entry. AnNTSC_prefix marks the three carrying NTSC’s 1001 pulldown (NTSC_FILM,NTSC_VIDEO,NTSC_60); the other five are exact, and are named for the convention that declares them (FILM_24,PAL_25,VIDEO_30and so on).
Every one of them is a timebase: seconds per tick. The frame-rate
entries are therefore the reciprocals of the rate they are named for —
FILM_24 is 1/24, not 24/1 — because a PTS timebase
and a frame rate are reciprocal readings of one rational. Rate is the
other reading, with its own roster over the reciprocal values, and
Self::checked_recip is the conversion under both of them.
§What earns a name
A name is worth carrying where every file that declares the value means
the same thing by it, so the roster holds the values a convention travels
with: a codec’s sample rate, a region’s frame rate, a container’s fixed
clock. Matroska’s default TimecodeScale and FLV’s timestamps are both
millisecond counts, so both read as MILLIS — one value,
one name, and no container-specific alias standing beside it. An MP4/MOV
timescale is chosen per file by the muxer, so it carries no convention to
name and this type carries it as the rational it is.
Implementations§
Source§impl Timebase
impl Timebase
Sourcepub const SECONDS: Timebase
pub const SECONDS: Timebase
One tick per second — the coarsest of the roster, and the timebase a value already counted in whole seconds carries.
Sourcepub const MILLIS: Timebase
pub const MILLIS: Timebase
Millisecond ticks — Matroska’s default TimecodeScale (1 000 000 ns),
FLV’s timestamps, WebVTT and SRT cue times, and the unit most
application-level media APIs report positions in.
Sourcepub const MICROS: Timebase
pub const MICROS: Timebase
Microsecond ticks — FFmpeg’s AV_TIME_BASE, which is the unit
AVFormatContext::duration and av_seek_frame’s default are expressed
in (AV_TIME_BASE_Q is exactly this rational).
Sourcepub const MPEG_90K: Timebase
pub const MPEG_90K: Timebase
The 90 kHz clock MPEG counts PTS and DTS in — MPEG-TS, MPEG-PS, and RTP’s video clock rate all use it.
Sourcepub const HZ_8K: Timebase
pub const HZ_8K: Timebase
One tick per audio sample at 8 kHz — narrowband telephony: G.711 and AMR-NB, and the clock rate RTP fixes the PCM payload types at.
Sourcepub const HZ_11_025K: Timebase
pub const HZ_11_025K: Timebase
One tick per audio sample at 11.025 kHz — a quarter of the CD rate, which is how legacy WAV and MPEG-2.5 Layer III reach a low rate without leaving the 44.1 kHz family.
Sourcepub const HZ_12K: Timebase
pub const HZ_12K: Timebase
One tick per audio sample at 12 kHz — a quarter of 48 kHz, and the bottom of that family in MPEG-2.5 Layer III and MPEG-4 AAC.
Sourcepub const HZ_16K: Timebase
pub const HZ_16K: Timebase
One tick per audio sample at 16 kHz — wideband speech: AMR-WB, Opus’s wideband mode, and the rate most speech models take their input at.
Sourcepub const HZ_22_05K: Timebase
pub const HZ_22_05K: Timebase
One tick per audio sample at 22.05 kHz — half the CD rate, carried by legacy WAV and by MPEG-2’s low-sampling-frequency Layer III.
Sourcepub const HZ_24K: Timebase
pub const HZ_24K: Timebase
One tick per audio sample at 24 kHz — half of 48 kHz: MPEG-2’s low-sampling-frequency extension, and what a low-bitrate AAC or Vorbis stream commonly decodes to.
Sourcepub const HZ_32K: Timebase
pub const HZ_32K: Timebase
One tick per audio sample at 32 kHz — MPEG-1 audio’s third rate, and the one NICAM television sound carries.
Sourcepub const HZ_44_1K: Timebase
pub const HZ_44_1K: Timebase
One tick per audio sample at 44.1 kHz — CD-DA’s rate, and the one most MP3 and AAC music files carry.
Sourcepub const HZ_48K: Timebase
pub const HZ_48K: Timebase
One tick per audio sample at 48 kHz — DVD and broadcast audio, professional interchange, and Opus, whose clock rate is always 48 kHz.
Sourcepub const HZ_64K: Timebase
pub const HZ_64K: Timebase
One tick per audio sample at 64 kHz — the step between 48 kHz and the doubled rates, declared by MPEG-4 AAC’s sample-frequency table.
Sourcepub const HZ_88_2K: Timebase
pub const HZ_88_2K: Timebase
One tick per audio sample at 88.2 kHz — double the CD rate, so a high-resolution master stays in the 44.1 kHz family and a downconvert to CD is an exact halving.
Sourcepub const HZ_96K: Timebase
pub const HZ_96K: Timebase
One tick per audio sample at 96 kHz — double 48 kHz: DVD-Audio, Blu-ray, and the rate professional recording works at above a 48 kHz delivery.
Sourcepub const HZ_176_4K: Timebase
pub const HZ_176_4K: Timebase
One tick per audio sample at 176.4 kHz — quadruple the CD rate, the top of the 44.1 kHz family in high-resolution PCM.
Sourcepub const HZ_192K: Timebase
pub const HZ_192K: Timebase
One tick per audio sample at 192 kHz — quadruple 48 kHz, and the highest PCM rate Blu-ray and professional audio interfaces carry.
Sourcepub const NTSC_FILM: Timebase
pub const NTSC_FILM: Timebase
One tick per frame at 24000/1001 fps (23.976) — film pulled down for
NTSC, which is what most film-sourced MP4 and MOV files declare.
The reciprocal of the frame rate, per the roster’s note.
Sourcepub const FILM_24: Timebase
pub const FILM_24: Timebase
One tick per frame at exactly 24 fps — cinema’s rate, and what a DCP counts in.
The reciprocal of the frame rate, per the roster’s note.
Sourcepub const PAL_25: Timebase
pub const PAL_25: Timebase
One tick per frame at 25 fps — PAL and SECAM broadcast, and EBU timecode.
The reciprocal of the frame rate, per the roster’s note.
Sourcepub const NTSC_VIDEO: Timebase
pub const NTSC_VIDEO: Timebase
One tick per frame at 30000/1001 fps (29.97) — NTSC video, and the
rate broadcast-sourced material in North America and Japan carries.
The reciprocal of the frame rate, per the roster’s note.
Sourcepub const VIDEO_30: Timebase
pub const VIDEO_30: Timebase
One tick per frame at exactly 30 fps — digital capture that skips the
NTSC pulldown, and what most screen recordings declare. The
pulldown-free twin of NTSC_VIDEO.
The reciprocal of the frame rate, per the roster’s note.
Sourcepub const PAL_50: Timebase
pub const PAL_50: Timebase
One tick per frame at exactly 50 fps — PAL-region broadcast at double rate, which is what 1080p50 and most European sports feeds carry.
The reciprocal of the frame rate, per the roster’s note.
Sourcepub const NTSC_60: Timebase
pub const NTSC_60: Timebase
One tick per frame at 60000/1001 fps (59.94) — NTSC-region broadcast
at double rate, and what 1080p59.94 cameras record. The NTSC_ prefix
is the pulldown, as it is on NTSC_VIDEO; exactly
sixty is VIDEO_60.
The reciprocal of the frame rate, per the roster’s note.
Sourcepub const VIDEO_60: Timebase
pub const VIDEO_60: Timebase
One tick per frame at exactly 60 fps — high-frame-rate capture and game
recordings, the pulldown-free twin of NTSC_60.
The reciprocal of the frame rate, per the roster’s note.
Sourcepub const fn new(num: i32, den: NonZero<i32>) -> Timebase
pub const fn new(num: i32, den: NonZero<i32>) -> Timebase
Creates a new Timebase with the given numerator and denominator.
§Panics
- Panics if
num < 0(a negative timebase is meaningless). - Panics if
den <= 0(NonZeroI32rules out zero; this rules out the negative denominatorsAVRationalwould tolerate).
Sourcepub const fn try_new(num: i32, den: NonZero<i32>) -> Option<Timebase>
pub const fn try_new(num: i32, den: NonZero<i32>) -> Option<Timebase>
Fallible variant of Self::new: returns None instead of panicking
when num < 0 or den < 0. Accepts num == 0 (degenerate timebase).
Sourcepub fn from_name(name: &str) -> Option<Timebase>
pub fn from_name(name: &str) -> Option<Timebase>
Looks up a well-known timebase by the name
of its constant — "MPEG_90K", "mpeg_90k", "Mpeg_90k".
Name lookup is ASCII-case-insensitive, and case is the whole of the
folding: the name is otherwise the constant’s own SCREAMING_SNAKE_CASE
spelling, with no alias and no trimming, so a name written in a config
file is greppable in this one. The canonical spelling is the one
Self::well_known_name writes back.
None for anything else — including a num/den rendering, which
FromStr accepts on its other arm.
Sourcepub fn well_known_name(&self) -> Option<&'static str>
pub fn well_known_name(&self) -> Option<&'static str>
The canonical name of the well-known
timebase this one equals, if any — the
inverse of Self::from_name, and the spelling to write back out.
Matched by value, as PartialEq matches: 2/2000 is
MILLIS and answers to that name, even though
Display will still print the 2/2000 the stream
declared. No two roster entries are equal, so the answer is unambiguous.
Written for an output face that wants to say which timebase a stream carries rather than hand a reader two integers to divide.
Sourcepub const fn reduce(self) -> Timebase
pub const fn reduce(self) -> Timebase
Reduces the rational to lowest terms: 2/4 becomes 1/2, 0/3 becomes
0/1.
The value is unchanged — the reduced form compares equal to what it came
from and hashes with it — so this is a canonicalization, useful where a
declared form has to be stored or rendered once per distinct value rather
than once per way of writing it. Display deliberately
does not reduce.
No sign handling: num >= 0 and den > 0 are constructor invariants, so
the gcd of the magnitudes is the gcd, and it is at least 1 because
den >= 1.
Sourcepub const fn is_reduced(&self) -> bool
pub const fn is_reduced(&self) -> bool
Whether the rational is already in lowest terms — true for 1/2 and
0/1, false for 2/4 and 0/3.
Exactly *self == self.reduce() in the structural sense that ==
itself cannot express, == being value-based here.
Sourcepub const fn checked_recip(self) -> Option<Timebase>
pub const fn checked_recip(self) -> Option<Timebase>
The reciprocal — 1/24 becomes 24/1 — or None when the numerator is
zero and no reciprocal exists.
This is the conversion between the two readings of a rational: a PTS
timebase (seconds per tick) and a rate (events per second) are
reciprocals, which is why the roster spells
FILM_24 as 1/24 while Rate::FPS_24 is 24/1.
Rate::from_timebase and Rate::to_timebase are this method under
the names the reading is asked for by.
A zero numerator is the only failure: the swap is otherwise total,
because the constructor’s den > 0 becomes the new numerator’s
num >= 0 and a num > 0 becomes a legal denominator.
Sourcepub const fn checked_rescale(&self, pts: i64, to: Timebase) -> Option<i64>
pub const fn checked_rescale(&self, pts: i64, to: Timebase) -> Option<i64>
Rescales pts from this timebase to to, or None if the answer is not
an i64.
self is the source timebase, so this is FFmpeg’s
av_rescale_q(pts, self, to) — including its rounding, which is
to nearest, halfway cases away from zero (AV_ROUND_NEAR_INF, the
posture av_rescale and av_rescale_q take by default). Rescaling
1/1000 ticks into 1/3 ticks sends 500 to 2 rather than to 1.
The product is formed in i128, which cannot overflow: the operands are
bounded by 2^63, 2^31 and 2^31, so the intermediate stays under
2^125. Two things are then reported as None rather than answered
wrongly:
- a quotient outside
i64’s range (pathological for real video); - a
towhose numerator is zero — a degenerate timebase names one single instant, so no tick count in it can represent a non-zero one.
Self::saturating_rescale is the same arithmetic with the other
posture toward the first of those.
Sourcepub const fn saturating_rescale(&self, pts: i64, to: Timebase) -> i64
pub const fn saturating_rescale(&self, pts: i64, to: Timebase) -> i64
Rescales pts from this timebase to to, clamping to i64::MIN or
i64::MAX instead of overflowing.
The saturating rung of Self::checked_rescale: same arithmetic, same
rounding (to nearest, halfway cases away from zero), and the only
difference is that a quotient too large for an i64 comes back as the
nearest i64 rather than as None.
§Panics
Panics if to.num() == 0, the divide-by-zero a degenerate target
timebase would be — as i64::saturating_div panics on a zero divisor,
and for the same reason: saturation is a posture toward overflow, and
there is no quotient here to clamp. Use Self::checked_rescale where
the target may be degenerate.
Sourcepub const fn checked_duration_to_pts(&self, d: Duration) -> Option<i64>
pub const fn checked_duration_to_pts(&self, d: Duration) -> Option<i64>
Converts a Duration into the number of ticks of this timebase that
span it, or None if that count is not an i64.
The inverse of Self::checked_pts_to_duration, and the same conversion
Self::checked_rescale performs out of NANOS — with
the same rounding, to nearest with halfway cases away from zero. Since a
Duration is never negative, “away from zero” is “up” here.
Two things come back as None rather than as a wrong answer:
- a count too large for an
i64(the duration is absurd for this timebase); - a
self.num() == 0degenerate timebase, whose every tick lands on the same instant, so no count of them spans a non-zero duration.
The second is where this rung earns its keep: it is the only spelling of
the conversion that answers at all on a degenerate timebase, its twin
Self::saturating_duration_to_pts panicking there.
Sourcepub const fn saturating_duration_to_pts(&self, d: Duration) -> i64
pub const fn saturating_duration_to_pts(&self, d: Duration) -> i64
Converts a Duration into the number of ticks of this timebase that
span it, clamping at i64::MAX instead of overflowing.
The saturating rung of Self::checked_duration_to_pts: same
arithmetic, same rounding, and a count too large for an i64 comes back
as i64::MAX.
§Panics
Panics if self.num() == 0, the divide-by-zero a degenerate timebase
would be — the same posture Self::saturating_rescale takes toward the
same degeneracy, and for the reason i64::saturating_div panics on a
zero divisor: saturation answers overflow, and a timebase whose every
tick lands on one instant leaves no count to clamp. Use
Self::checked_duration_to_pts where the timebase may be degenerate.
Sourcepub const fn checked_pts_to_duration(&self, pts: i64) -> Option<Duration>
pub const fn checked_pts_to_duration(&self, pts: i64) -> Option<Duration>
Converts a tick count in this timebase into the Duration it spans, or
None if no Duration represents it.
The inverse of Self::checked_duration_to_pts, rounded to the nearest
nanosecond with halfway cases away from zero. Two things come back as
None:
- a negative
pts, which pre-roll and edit lists produce and whichDurationcannot represent (it is unsigned) — seeTimestamp::durationfor the same refusal on a whole timestamp; - a span whose seconds exceed
u64::MAX, pastDuration::MAX.
A degenerate self.num() == 0 timebase is not a failure in this
direction: every tick of it lands on the same instant, and
Duration::ZERO is that instant.
Sourcepub const fn saturating_pts_to_duration(&self, pts: i64) -> Duration
pub const fn saturating_pts_to_duration(&self, pts: i64) -> Duration
Converts a tick count in this timebase into the Duration it spans,
clamping at both ends of what a Duration can hold.
The saturating rung of Self::checked_pts_to_duration: same
arithmetic, same rounding. A negative pts clamps to Duration::ZERO
and a span past Duration::MAX clamps to it — the two bounds of the
type, which is what saturation means for a type that has no negative
half.
Trait Implementations§
Source§impl<'a> Arbitrary<'a> for Timebase
impl<'a> Arbitrary<'a> for Timebase
Source§fn arbitrary(u: &mut Unstructured<'a>) -> Result<Timebase, Error>
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Timebase, Error>
Self from the given unstructured data. Read moreSource§fn arbitrary_take_rest(u: Unstructured<'a>) -> Result<Self, Error>
fn arbitrary_take_rest(u: Unstructured<'a>) -> Result<Self, Error>
Self from the entirety of the given
unstructured data. Read moreSource§fn size_hint(depth: usize) -> (usize, Option<usize>)
fn size_hint(depth: usize) -> (usize, Option<usize>)
Unstructured this type
needs to construct itself. Read moreSource§fn try_size_hint(
depth: usize,
) -> Result<(usize, Option<usize>), MaxRecursionReached>
fn try_size_hint( depth: usize, ) -> Result<(usize, Option<usize>), MaxRecursionReached>
Unstructured this type
needs to construct itself. Read moreimpl Copy for Timebase
Source§impl<'de> Deserialize<'de> for Timebase
impl<'de> Deserialize<'de> for Timebase
Source§fn deserialize<__D>(
__deserializer: __D,
) -> Result<Timebase, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(
__deserializer: __D,
) -> Result<Timebase, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
Source§impl Display for Timebase
Writes the rational as num/den — 1/1000, 1/90000, 30000/1001.
impl Display for Timebase
Writes the rational as num/den — 1/1000, 1/90000, 30000/1001.
The stored form is printed, not the reduced one: 2/4 prints as 2/4
even though it equals 1/2 and hashes with it. In a log the interesting
fact is which timebase a stream declared, and reducing would erase the
difference between a container that said 30000/1001 and one that said
60000/2002.
Unlike Timestamp’s and TimeRange’s, this rendering is exact — a
numerator and a denominator are the whole value — so {:#} renders
identically; there is nothing to expand into. FromStr
inverts it.
Width and alignment flags ({:>12}) are ignored: honouring them means
measuring the finished string, and this crate has no alloc to build one
in.
impl Eq for Timebase
Source§impl FromStr for Timebase
Parses either a well-known name —
MILLIS, MPEG_90K — or num/den, the form Timebase’s Display
writes in both {} and {:#}.
impl FromStr for Timebase
Parses either a well-known name —
MILLIS, MPEG_90K — or num/den, the form Timebase’s Display
writes in both {} and {:#}.
The roster is tried first, via Timebase::from_name, so the name arm
folds ASCII case as that door does — millis parses — and nothing else:
no alias, no separator guessing. Nothing in the roster contains a slash, so
the two arms cannot collide. It is an input convenience for
hand-written configuration
and command lines: Display still writes num/den for every value, so the
Display → FromStr round trip is unchanged and lossless. The reverse is
deliberately not injective — "MILLIS" and "1/1000" parse to the same
timebase, and Timebase::well_known_name is where the name goes to be
recovered.
Surrounding and interior whitespace is trimmed, so 1 / 1000 parses; on
the num/den arm the slash is required. The value is not reduced:
2/4 parses to a numerator of 2 over a denominator of 4, which is what was
written, and what Display will write back.
Timestamp, TimeRange and SignedDuration parse their timebase
half through this impl, so 12345 @ MPEG_90K parses too. Rate’s roster
is not read here, nor this one there — see that impl for why.
§Errors
Returns ParseTimebaseError if the input is neither a roster name nor a
num/den pair: the slash is missing, either half is not an i32, or the
pair is one Timebase::try_new refuses — a negative numerator, or a
denominator that is zero or negative.