Commit Graph
14 Commits
Author SHA1 Message Date
aiongg 228515e77c Merge PR #1829: fix(codecs): read the frame rate phone clips average to (#1700)
# Conflicts:
#	crates/codecs/src/mp4.rs
2026-10-31 02:14:21 +00:00
aiongg 5fea1fe3d7 refactor(time): move lib.rs tests to their own file 2026-10-26 18:20:20 +00:00
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
Earl Co e20895cec1 fix(codecs): read the frame rate phone clips average to (#1700)
New Sequence From Clip used the median sample duration, so a 90 kHz track whose frame times were rounded to whole milliseconds misread 30 fps as 30.303 fps and 60 fps as 58.824 fps. Derive the track's nominal rate from the mean of durations near the median, then map it to the closest standard rate within 0.5% when present. Share that helper between media probing and frame-grid snapping, and add regression tests for 30 and 60 fps.

Signed-off-by: Earl Co <hello@tekiz.ai>
2026-10-14 07:50:54 +02: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