Commit Graph
218 Commits
Author SHA1 Message Date
Jonathan CardosoandBrandon Thomas afdb8019c1 media: say which colour values no tag gave (#4086) (#4089)
With no colour tags, FilmCraft decodes with BT.709 transfer and
primaries and the matrix by frame size, and probe and media.colorInfo
showed those values with nothing saying they were defaults, so an
untagged file looked the same as one tagged BT.709.

VideoStreamInfo gets color_assumed (optional, left out when unknown):
which of the matrix, transfer and primaries no tag in the file gave.
MP4/MOV and Matroska take it from the same sources as the colour, APV
from its colour description, and AVI, MXF and Y4M mark all three.
probe prints it as video.color_assumed and media.colorInfo as assumed,
next to the unchanged metadata.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-03 00:04:13 -05:00
scks1andClaude Opus 5.5 8e81d41567 Name Apple Intermediate Codec and show why a clip is unreadable (part of #3646) (#4010)
* media: name Apple Intermediate Codec and say why a clip is unreadable

Part of #3646. A MOV in Apple Intermediate Codec (fourcc icod, the
editing codec of iMovie and Final Cut) imported with its codec named
"unknown", and its frames failed with "no decoder for unknown video".
Only the log showed that error: the Project panel's badge said
"Unreadable" and nothing more.

- codecs: codec_label names icod "Apple Intermediate Codec". The
  "no decoder" errors of make_video_decoder and software_video_decoder
  use that label for sample entries the parser keeps as Unknown, so
  the error reads "no decoder for Apple Intermediate Codec video".
  Other codecs keep their short names in that error.
- ui-egui: an Unreadable badge shows on hover the error the media pool
  recorded, on cards (Icon and Freeform view) and in List view, which
  now registers its badge as project.item.<id>.offline as cards do.
  This covers every cause the pool records, such as "H.264: field
  pictures", not only this codec.
- docs: an Apple Intermediate Codec row in codec-parity.md, and the
  hover in project-files.md.

FilmCraft still has no AIC decoder. Apple publishes no specification
for it, so a clean-room decoder (AGENTS.md section 2) needs a
maintainer ruling, and #3646 stays open for that.

Tests: apple_intermediate_codec_is_named_though_nothing_decodes_it in
filmcraft-codecs (the label and all three errors), and
unreadable_badge_says_why_on_hover in the ui-egui it suite (both views).

Developed with an AI assistant (Árbol, running Muse).

* ui: keep the List view's badges beside a long name (#3646)

In List view, a name that left less than 40 px of its column hid the
item's offline icon and proxy chip, and with them the Unreadable
badge's automation id and the reason it shows on hover. The badges now
keep their room at the end of the Name column, and a long name is cut
short before them. Only a column too narrow for the badges leaves them
out. Rows that showed their badges before, and rows without badges,
draw as they did.

unreadable_badge_says_why_on_hover imports a file whose name is wider
than the Name column, so its List view pass covers this case. Found by
CodeRabbit in its review of the pull request.

Developed with an AI assistant (Árbol, running Muse).

* codecs, ui: doc comments for codec_label and media_badge (#3646)

Both functions gained a role with the Apple Intermediate Codec change:
codec_label also names the codec in the no-decoder errors, and
media_badge records why an Unreadable file cannot be read. Their doc
comments say so.

Developed with an AI assistant (Árbol, running Muse).

* ui: end a List-view name cut short with "…" and show it in full on hover (#3646)

Keeping the badges' room at the end of the Name column can cut short a name
that used to fit, and the cut left no sign of it. The List view now draws the
name with the cards' elided_name, so a cut name ends in "…", and hovering it
shows the whole name, as the cards already do. Names that fit draw as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:02:55 -05:00
Agapenor fdfb6f2182 codecs: an MP4 edit rounded to the movie timescale keeps the last frame (#3994)
* codecs: an MP4 edit rounded to the movie timescale keeps the last frame

Edit durations are whole movie-timescale units (milliseconds in ffmpeg,
x264 and FilmCraft's own exports), so a clip whose length is not a whole
number of milliseconds imported one frame short: 320 frames at 24 fps
(13.3333 s, written as 13 333) read as 319. When the last edit plays the
media to the end of its last picture (within one unit), the clip now lasts
exactly that long.

* codecs: an edit exactly one movie-timescale unit short of the media is a trim

Rounding to the movie timescale is always less than one unit off, so a
difference of exactly one unit (a one-frame trim when the movie and track
timescales are equal) keeps the edit's own length.

* codecs: compare the edit with the media exactly, and never across a whole picture

The comparison now runs in the two timescales themselves (Tick flooring
could put a one-unit trim at 23/23 under the bound), and a difference of a
whole last picture or more is a trim even when a coarse movie unit (1/24 s
against 1/30 s pictures) is longer than that.
2026-11-03 00:02:45 -05:00
aiongg 01608757f5 Merge integration track fx 2026-10-31 03:39:22 +00:00
aiongg 69ed33ae57 Merge integration track io 2026-10-31 02:17:17 +00:00
aiongg b583037822 Merge integration track audio 2026-10-31 02:17:03 +00:00
aiongg 30f24cdfbe Merge PR #3376: codecs: the rayon deadlock test fails 120 s after the last answered request 2026-10-31 02:16:00 +00:00
aiongg be410971f4 Merge PR #2302: AVI: recover the video when its idx1 entries are damaged 2026-10-31 02:14:43 +00:00
aiongg edc0fcc24e Integration: #1829 drop the stray empty doc line 2026-10-31 02:14:38 +00:00
aiongg a47e67d7d0 Integration: #1829 doc comment back above its test 2026-10-31 02:14:27 +00:00
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 9bae3a8fc4 Merge PR #1707: mpegts/avi: drop redundant PES header check and doubled frame_info call 2026-10-31 02:14:02 +00:00
aiongg 9e9f79cd65 Integration: #1440 move the BDAV comment back above its m2ts_h264_lpcm arm 2026-10-31 02:14:01 +00:00
aiongg a6a5cda113 Merge PR #1440: #1436: mpegts: H.264/HEVC units are access units; H.264 recovery points are key 2026-10-31 02:13:54 +00:00
aiongg f2cd5f0780 Merge PR #2391: #1422: Matroska audio without a decoder is labelled unsupported 2026-10-31 02:10:57 +00:00
aiongg 7bd3ff97a8 Merge PR #3181: h264: Decoder::reset keeps the thread pool and cancels in-flight pictures 2026-10-31 02:10:54 +00:00
aiongg 5169ec8bee Merge PR #2047: codecs: an ipcm/fpcm entry declaring zero bits decodes to silence 2026-10-31 02:10:13 +00:00
aiongg 29fddf546c Integration: #1835 move ac3_mp4_layout test into tests/it 2026-10-31 02:10:07 +00:00
aiongg cf02b12c6c Merge PR #1835: fix(codecs): read AC-3/E-AC-3 MP4 layout from dac3/dec3 (#1701) 2026-10-31 02:09:55 +00:00
tylerjenningsw e2cb8d43b2 Merge main into fix/avi-video-index-recovery
# Conflicts:
#	contributors/people.toml
2026-10-30 16:59:46 -04:00
Gerrit Timmer 4d57c52ab6 Free decoder frame caches and plane pools when an export ends (#3637)
Every source keeps up to 384 MiB of decoded frames plus its live decoders
for the whole session, and the 1 GiB process budget only trims caches that
were idle while another cache asks for frames. The 8/16-bit plane shelves
(192 MiB each) were never trimmed. A long-lived process (MCP) therefore
held 2-3 GiB after an export and ran out of memory on the second one.

Add filmcraft_codecs::release_idle(), which empties every cache nobody is
decoding on (try-locked, so users are untouched), and
filmcraft_frame::pool::trim_planes(). A standalone export calls both next
to trim_f32 when its pipeline drops; parts of a batch keep them.
2026-10-30 01:42:27 -04:00
alexanderalber 8aaa2adc8b fix(codecs): frame lookup inside a frame with one timestamp unit per frame (#3452)
* fix(codecs): frame lookup inside a frame with one timestamp unit per frame

MP4 and Matroska look a frame up by rounding the requested time to the
nearest timestamp unit (#73). With a unit of a frame (MP4 timescale =
frame rate, Matroska TimestampScale = frame duration) half a unit is
half a frame, so from every frame's middle on the next frame was shown.
With a unit of half a frame or more the request now moves back to the
start of its frame (nominal rate) before rounding; finer units keep
#73's round-to-nearest.

generated with Claude Code

* fix(codecs): snap coarse-unit lookups only on constant-rate tracks

A timestamp unit of half a frame or more is no proof of a constant
rate: a variable-rate track (timescale 50, samples 2, 1, 2 units long)
has a frame starting between two nominal frames, and moving a request
at that start back to its nominal frame showed the frame before.

The snapping is now decided once per track at open (gop::
snap_coarse_units): only when every sample stamp is where the snapped
lookup lands for its nearest nominal frame. Other tracks keep the
lookup by the nearest unit (#73). Regression test for MP4 and Matroska.

generated with Claude Code
2026-10-30 01:22:26 -04:00
7a14c3db49 #2798: contain Symphonia audio decoder panics (#3338)
* #2798: contain Symphonia audio decoder panics

Guard decoder creation, packet conversion and standalone imports. Keep
panicked decoders disabled across reset and reject partial imports while
preserving ordinary corrupt-packet recovery. Add synthetic regression
and hostile-header tests plus crate documentation.

Refs #2798; H.264/HEVC worker containment remains separate.

Co-authored-by: Codex <noreply@openai.com>

* #2798: document panic strategy limits of audio guards

Clarify that decoder panics become CodecError::Decode only with panic=unwind. Document the default wasm32 abort behavior and retain the process-abort and allocation-failure caveats.

Co-authored-by: Codex <noreply@openai.com>

* codecs README: drop the PR's local test log

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 01:20:28 -04:00
dotCooCoo 465afcb700 Merge branch 'main' into fix/gop-deadlock-test-last-answer 2026-10-27 22:27:21 -07:00
aiongg 806fbe8af5 style: cargo fmt the moved test files 2026-10-26 18:20:21 +00:00
aiongg 6d42ef9ba0 refactor(codecs): move mp4.rs tests to their own file 2026-10-26 18:20:20 +00:00
aiongg c369ccbc86 refactor(codecs): move gop.rs tests to their own file 2026-10-26 18:20:20 +00:00
dotCooCoo 4e12cc7e2e codecs: the rayon deadlock test fails 120 s after the last answered request
rayon_frame_requests_while_decoding_on_rayon_never_deadlock waited 120 s
at a time and checked for progress only when a wait ran out. When the
requests stopped just after one was answered at the start of a wait, the
next check still saw progress, and the test reported the deadlock about
240 s after the last answer.

- wait_while_answered polls every second and fails once 120 s have
  passed since the answered count last grew.
- a_stall_right_after_an_answer_is_reported_once_the_limit_has_passed
  checks it with a 2 s limit: one answer after 50 ms, then none.
2026-10-25 21:41:08 -07:00
Dylan M. Taylor de529c88f4 chore: resolve SonarCloud maintainability issues (#3329)
* chore: resolve SonarCloud maintainability issues

* fix: repair CI on the Sonar maintainability refactor

- h264: the coefficient scratch is i32, so the method path is <[i32]>::first_chunk_mut
- hevc: Px::w32 takes self, so keep the closure over the &P from slice::get
- tts: upsamples is a method, not an importable item
- vp8: drop the unused B_DC_PRED/DC_PRED imports; cargo fmt the import lists
- ui-egui: editing no longer needs to be mutable
- packaging: keep literal printf formats (shellcheck SC2059)

* fix: restore the mutable binding in hot_number
2026-10-25 19:40:57 -04:00
Dylan M. Taylor 74f2aca772 security(media): bound generator frames, imported audio dimensions, and render images (#3080)
* security(media): bound generator frames, imported audio dimensions, and render images

* fix(media): unwrap render result in demo_frames example

* fix(render, codecs): guard zero-sized images, check post-scale sequence pixels, and bound combined MXF channels

* fix(codecs): annotate channels as u32 in MXF sound track combination

* fix(render): preserve exact output_size scaling calculation for renderable_sequence validation

* fix(media,render): reject unsupported audio dimensions and invalid render scale

Sample rates above MAX_AUDIO_SAMPLE_RATE were clamped without resampling, so a
768 kHz stream was reported as 384 kHz and played at half speed. Reject them
instead, and drop the stream at the four codec call sites rather than
advertising dimensions the samples do not match.

output_size now treats a non-finite or non-positive scale as 1.0 instead of
casting it to zero and collapsing the frame to 1x1.

* fix(media): keep Matroska audio tracks and their metadata together; guard empty images

A track whose advertised sample rate or channel count is refused is dropped before the track list is built, so atracks[k] and audio_streams[k] stay the same track. over_black_with_alpha_rgba8 and Image::transformed return early for an empty image instead of chunking by zero.
2026-10-25 16:09:17 -04:00
aiongg 83835cb966 Merge pull request #3288 from aiongg/fred-verified-batch
Merge 15 open PRs verified on a packaged build (tested batch)
2026-10-25 12:07:06 -04:00
.CooCoo e7e372a36e codecs: the rayon deadlock test allows 120 s between two answered frame requests (#3241)
gop::tests::rayon_frame_requests_while_decoding_on_rayon_never_deadlock
gave its 3,840 frame requests 120 s in total. On a loaded machine they
take longer while every request is still answered. With six copies of
the test running at once, all 12 runs on `main` failed after 121 to
123 s with "frame requests deadlocked".

The test now counts answered requests and fails when none is answered
for 120 s. The same 12 runs pass, in 166 to 259 s. With the #480 fix in
`isolated` turned off, the test still fails ("frame requests deadlocked
after 927 frames").
2026-10-25 09:42:20 -04:00
bz 3c92231842 Keep draft-decoder counter assertions in one test (#3286)
* Tests: keep draft-decoder counter assertions in one test

* #3287: Document the shared H.264 draft test helper
2026-10-25 09:42:06 -04:00
aiongg 3eab582d6e Merge #2415: #2340: uncompressed QuickTime video (raw 24-bit RGB, 2vuy, v210) decodes 2026-10-25 12:33:10 +00:00
vangel76andClaude Opus 5.5 9e69e8ebb3 h264: Decoder::reset keeps the thread pool and cancels in-flight pictures
Seeking used to build a new decoder (a new rayon pool) and drop the old one,
whose queued picture jobs kept decoding and competed with the new position.
Decoder::reset forgets the stream like a new decoder but keeps the worker
threads; jobs of the old position stop at their next macroblock row and still
publish all their rows, so nothing waiting on them blocks. The codecs H.264
decoder resets with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:30:53 +02:00
vangel76andClaude Sonnet 5.5 17825ed9df mpeg_opengop_seek: share tests/it/common after upstream test layout move
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 13:30:39 +02:00
vangel76 0d27850b80 Merge remote-tracking branch 'upstream/main' into fix-mpegts-h264-access-units 2026-10-24 13:24:16 +02:00
Earl Co 894a9b56b6 security(codecs): cap GOP cache frame retention (#2899) 2026-10-24 04:50:09 -04:00
Earl Co ea8380a24f security(avi): bound framed audio scans (#2932) 2026-10-24 04:49:42 -04:00
aiongg fa550ce2d0 Build: use craft-media for bitstream, isobmff and matroska (#2435)
* Build: use craft-media for bitstream, isobmff and matroska

filmcraft-bitstream, filmcraft-isobmff and filmcraft-matroska re-export
craft_media::{bitstream, isobmff, matroska} from craft-libs. The packages
and their public paths stay, so dependent crates build unchanged. The
integration tests in crates/isobmff/tests and crates/matroska/tests run
against the re-exports.

* Build: pin craft-media at 9db732e

craft-media spells the Matroska colour types in US English: Colour is
Color and VideoInfo::colour is VideoInfo::color. The spec names
element_ids::COLOUR, COLOUR_SPACE and VpcConfig::colour_primaries stay.

* matroska: the oracle skips the duration of a track's last frame

Ported from craft-media: the last audio or video frame of a track cut by
-t is shorter than a codec frame. ffmpeg 8 writes a shortened
BlockDuration and reports the codec frame duration; a remux by our writer
stores DefaultDuration and ffprobe reports the shortened one. Subtitle
cue durations are still compared. This fixes oracle::mjpeg_ac3 with
ffmpeg 8. The HDR test is named hevc_hdr_color, as in craft-media.

* matroska: laced_remux laces every audio track and checks timestamps

craft-media laces only tracks with a DefaultDuration. The remux gives
each audio track without one its mean packet spacing, so Vorbis, FLAC
and PCM lace too, and compares every packet's timestamp with the
source's. Ported from craft-media's oracle_matroska.rs.

* codecs: the Matroska audio timing test laces its Xiph case

craft-media laces only tracks with a DefaultDuration. The test's
Matroska helper sets the AAC access unit's duration (1024 samples,
rounded to the nanosecond), and the test checks that the Xiph-laced file
has laced blocks and the unlaced one has none.

* Build: repin craft-media to f97c84d27 (NAL length-size checks)

craft-libs#245 is on storytold/craft-libs main, so the dependency points there.

* Build: repin craft-media to f0ad6c1e1 (QuickTime v2 PCM entry limits)
2026-10-23 17:51:38 -04:00
midasmatterhorn 0e87221db1 codecs: format zero-bit PCM regression fixture 2026-10-23 21:24:56 +03:00
aiongg 0a881266c8 CI: xtask ci in the dev profile, one test binary per crate, nextest (#2860)
* Tests: one integration-test binary per crate

Each crate with several tests/*.rs files keeps them as modules of tests/it/,
so cargo links one test executable per crate instead of one per file. Files
that change process-wide state or time a workload stay binaries of their own.

* Profiles: line tables for workspace crates, no debug info for dependencies

* Windows ARM64: nightly and on release pushes instead of every push to main

* xtask ci: clippy and tests in the dev profile

The release profile's thin LTO and single codegen unit made linking the test
binaries take most of the gate. The codec and audio crates stay at opt-level 3
in the dev profile, so their tests run at release speed.

* xtask ci: nextest, doctests, no examples, one wasm check

Tests run with cargo nextest when it is installed (cargo test otherwise) and
build with --lib --bins --tests, so examples aren't linked; doctests run
separately. The wasm check keeps going past the first failing crate. VP8
fixture generation locks across processes, since nextest runs each test in
its own.

* VP9 synthetic fixtures: write aside and rename

A test in another process could read a fixture ffmpeg was still writing.

* Fuzz tests: a third of each sweep by default, all of it with FILMCRAFT_FUZZ_ITERS

Every seed, mutation kind and thread count still runs. Header bytes each get
one overwrite value and two bit flips in rotation, so every value and bit
index still occurs across the region. FILMCRAFT_FUZZ_ITERS=N runs the whole
sweep plus N random rounds per seed, as before.

* Platform fallback tests: one failure point per hybrid path

The failure-anywhere test injected a failure at four points around every
restart point; the hybrid's paths differ only in the first GOP, around the
second and last restart points, at the end and never. The replay test needs
the first two GOPs.

* Export tests: shorter ranges with the same checks

The bitrate test measures 0.5–1.5 s, where each preset reaches its own
target (on 0–3 s the simple demo left two presets at the same rate). The
every-preset test exports three frames per preset.

* Audio tests: effects in parallel, a table-driven FFT for the Opus quality metric

The extreme-settings test keeps both settings and the 40 s tail for every
effect, one thread each. The Opus quality metric's FFT builds its twiddles
once and allocates nothing per transform (same algorithm and summation order).

* CI: dev-profile gate with nextest

xtask ci no longer builds with --release, so the release-profile overrides
go; nextest is installed for the test step.

* CI: apt packages through the cached install-apt-packages action

* Review fixes: fuzz tests with a panic hook in their own binaries, loud fixture lock

The fuzz tests of av1, h264, hevc, mpeg2v, vp8 and vp9 count panics through
a process-wide hook, so a panic in another test of the same process would
fail them; they are binaries of their own again. The VP8 fixture lock fails
the test instead of skipping it silently, and the full-sweep doc comments say
how many truncation points the full run samples.

* Fuzz tests: rotate the default subset per run (FILMCRAFT_FUZZ_SEED replays one)

* platform tests: decode-switch tests in their own binaries, one NVENC lock
2026-10-23 13:34:16 -04:00
Earl Co f5a9a6f5e7 security(codecs): deduplicate hardware parameter-set checks (#2890) 2026-10-23 10:54:07 -04:00
980c388325 codecs: walk open-GOP leading pictures back in a loop, not by recursion (#2920)
* codecs: walk open-GOP leading pictures back in a loop, not by recursion

MxfVideo::sync_before and MpegVideo::sync_before recursed one GOP back
per leading picture, so a crafted index whose every GOP leads its key
frame recursed once per GOP: tens of thousands of frames for a small
file, a plausible stack overflow in dev builds.

Both now use gop::open_gop_sync, a loop over the same steps. It stops
when the index's sync sample lies past the picture instead of looping,
and reads pts and keys with get().

Fixes #2803

* gop: keep decode_start's doc comment on decode_start

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:52:55 -04:00
Earl Co 8fa72cf18d security(codecs): avoid quadratic MPEG timestamp lookup (#2883) 2026-10-23 10:52:03 -04:00
skylar taylor-barrick ed7d11dbca Merge pull request #1987 from nuclearspike/codex/security-mp4-audio-duration
codecs: avoid duration-sized silence allocation for damaged MP4 audio
2026-10-21 17:09:22 -06:00
vangel76andClaude Sonnet 5.5 157cc6b5f7 #1440: software H.264 is_random_access accepts a recovery point SEI with recovery_frame_cnt 0
Seeks in open-GOP streams restarted at the previous IDR because gop.rs asked
is_random_access, which only accepted NAL type 5. Use the splitter's rule
(sei_has_recovery_point, now exported by filmcraft-mpegts). Test counts the
samples decoded by a seek into the last GOP of the open-GOP fixture.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 00:45:53 +02:00
tylerjenningsw 9b6275c1ef AVI: a video stream with pictures comes before one that only declares frames 2026-10-19 10:14:21 -04:00
irgendwasmitfelixandClaude Opus 5.5 dd2d3111d6 #2340: uncompressed QuickTime video (raw 24-bit RGB, 2vuy, v210) decodes
MOV / MP4 files with uncompressed video were reported unreadable: the
fourccs were recognised as video but no decoder factory took them (the
raw decoder was wired up for AVI only). A raw_factory now decodes
`raw ` at depth 24 (packed RGB, unpadded rows), `2vuy` (UYVY 4:2:2)
and `v210` (10-bit 4:2:2, shown at 8 bits), and the media info names
them. fl32 audio in these files already played.

Fixes #2340

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:07:35 +02:00
irgendwasmitfelixandClaude Opus 5.5 32fde18874 #1422: Matroska audio without a decoder is labelled unsupported
A DTS (or TrueHD, or any other undecodable) audio track in an MKV showed
up as a normal stream and simply played silent. Label it
"<codec> (unsupported)" in the media info as MPEG and MXF already do,
name DTS / TrueHD instead of their codec ids, and say in the decode error
that FilmCraft has no decoder for it. The DTS decoder itself stays open.

Refs #1422

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:47:24 +02:00