11 Commits
Author SHA1 Message Date
skylar taylor-barrick edb501a3fe Fix false overlaps for empty time ranges (#2601)
Treat zero- and negative-duration time ranges as empty in overlap checks.
2026-10-22 22:51:02 -06:00
NetterSilverandClaude Haiku 5.5 58b58440eb Reject signed fields inside multi-field timecodes
Found while testing crates/time/src/lib.rs:483 (parse_timecode, multi-field branch)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 03:43:17 +02:00
Lucenx9andOpenAI Codex 50152171e6 M3: Ignore empty time ranges in overlap checks
Require both ranges to be nonempty before comparing endpoints. This prevents zero and negative durations inside a clip from reporting a false overlap, matching is_empty and intersect.

Validation: 22 time tests and 69 edit tests pass; crate Clippy, formatting, layers and assets pass. Full CI is blocked by three existing UI nonminimal_bool diagnostics; contributor approved publishing with this blocker documented.

Co-authored-by: OpenAI Codex <noreply@openai.com>
2026-10-20 19:25:05 +02:00
Brandon Thomas d90538d094 Merge main into #2472 (resolve append conflicts)
# Conflicts:
#	crates/time/src/lib.rs
2026-10-19 12:53:33 -04:00
NetterSilverandClaude Haiku 5.5 405619b417 Fix "-0.000" in Seconds time display for tiny negative times
Found while testing crates/time/src/lib.rs:423 (format_time, TimeDisplay::Seconds)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 12:18:13 +00:00
NetterSilverandClaude Haiku 5.5 9a93d175a4 Fix AudioSamples time display for negative times
Found while testing crates/time/src/lib.rs:418 (format_time, TimeDisplay::AudioSamples)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 11:27:22 +00:00
sanyamsingh001andCodex a3ad7a8ce7 TIME.FRAME_NAVIGATION: Preserve exact frame steps and bound time arithmetic
Co-authored-by: Codex <codex@openai.com>
2026-10-10 09:00:34 +10:00
mjohnson7cd 7048a7905a fix: frame stepping broken for non-standard frame rates (#301)
tick_of() used floor division (div_euclid) which truncated the frame
duration for non-standard rates like 37.516 fps (9379/250) where the
frame duration is 6770871094.999 ticks, not an integer. This caused
frame_at(tick_of(f)) to return f-1 instead of f, making step forward
stall at frame 0 and step back jump backward by 2+ frames.

Changed tick_of() to use ceiling division so the round-trip is exact
for all integer frame indices. Ceiling is correct because the frame
duration is always > 1 tick, so floor(ceil(f*D) / D) == f for every
integer f. frame_duration() remains floor division (unchanged) since
many callers do end - frame_duration() to get the previous frame.

All 11 standard broadcast rates are unaffected since their frame
durations are already exact integers (ceil == floor there).

Added regression tests:
- exotic_frame_rate_roundtrip: round-trip over -20k..20k for 5
  non-standard rates, snap invariant, step-back via frame_duration,
  forward/backward stepping simulation
- exotic_frame_rate_durations: frame_duration is floor for exotic,
  exact for standard
- exotic_frame_tick_roundtrip proptest: arbitrary FrameRate::new(num,
  den) round-trip

Thanks to @justjohn12345 for catching that round (not ceil) still
fails for ~50% of frames starting at f=938.
2026-10-08 18:37:41 -04:00
EthanSuquetandClaude Opus 5.5 2b49201e84 codecs: video frames are looked up by the nearest timestamp unit, so a 1 ms WebM shows every frame
Problem (#73): a 30 fps WebM played and exported with every third frame
missing and the frame before it shown twice (20 unique frames a second).
The same happens at 29.97, 60, 59.94, 24 and 23.976 fps, and with MP4
files whose video track has a 1 ms timescale. 25 and 50 fps were fine.

Cause: Matroska stores block timestamps in TimestampScale units (usually
1 ms), rounded to the nearest unit, so at 30 fps frame 2 (66.67 ms) is
stored as 67. MkvSource::video_frame converted the requested time to track
units with a floor (66), and the lookup (last sample with pts <= target)
returned frame 1. Every frame whose timestamp was rounded up was skipped.
Mp4Source::video_at, and the web build's WebCodecs MP4 path, floored the
same way against the track timescale; normal MP4 timescales hold frame
times exactly, so only coarse (1000) timescales were hit.

Fix: new Tick::to_rational_round (nearest unit, halves up, total for zero
and negative timebases), used for the video lookup target and the
catch-up threshold in mkv.rs, mp4.rs and webcodecs.rs. A requested time
now matches a frame whose stored timestamp is within half a unit of it,
the same precision the timestamps have. Truncating muxers (stamps rounded
down) still work. Times on exact timebases (90 kHz, 15360, 30000/1001…)
convert exactly as before.

Test: crates/codecs/src/rounded_pts_tests.rs builds synthetic Motion-JPEG
streams in the test (each picture shows its index as black/white blocks),
muxed as WebM with 1 ms timestamps and as MOV with a 1000 timescale, at
30, 29.97, 60, 59.94, 24, 23.976 and 25 fps with nearest and truncated
stamps. Requesting every frame's start over 3 s must show every frame
exactly once, and every frame's midpoint must show that frame. Before the
fix both tests failed ("30 fps, Nearest ms stamps: 30 of 90 frames never
shown [2, 5, 8, 11]…, 30 shown twice [1, 4, 7, 10]…"). The time crate
tests cover the rounding, ties and hostile timebases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 15:16:37 -04:00
Brandon ThomasandClaude Opus 5.5 953f0aca8b time: never crash — total conversions for zero rates, saturating timecodes
Mutation fuzzing found two crashes on import: an MP4 track with timescale 0 panicked in
`Tick::from_rational` (division by zero) and an MPEG-TS stream whose timestamps gave a zero
frame rate panicked in `FrameRate::tick_of`. A FrameRate with a zero num/den can also come from a
project file (it's serde).

- `Tick::{from_units, from_rational, to_rational_floor, mul_ratio}` return 0 for a zero
  denominator instead of dividing by zero.
- `FrameRate::sane()`: an invalid (zero / negative) rate counts as the default rate in
  `frame_duration`, `frame_at`, `tick_of` and `timecode_base`.
- `Tick::clamp` no longer panics when the bounds cross.
- Timecode parsing and drop-frame conversion use saturating math (huge typed or agent-sent
  timecodes overflowed).
- Regression test `hostile_rates_and_timecodes_never_panic`.
- `#![deny(clippy::unwrap_used, …)]` outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:51:31 -04:00
Brandon ThomasandClaude Opus 5.5 936b28e4c4 M0.1/M0.2: workspace skeleton, time and bitstream crates
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 05:33:20 -04:00