Conflicts: NOTICE, vendor/README.craft-reader.md and the hayro-interpret and
hayro-syntax SOURCE.json keep the ec0a965 provenance and add hayro-cmap
(a2ec27b) and hayro-postscript (crates.io 0.1.0). type0.rs keeps PdfCraft's
flat sample array with its 64-bit largest sample and main's 32-bit test.
Local sha256 values recomputed for the merged files.
craft-cms has no workflow of its own, and craft-prepress and craft-plate-test
depend on it, so the PDF workflow is the only place its changes are tested.
- hayro-syntax ascii_85.rs: main's decode-limit version (same bound as
the pdfcraft ec0a965 copy, plus its test).
- hayro-interpret and krilla SOURCE.json: source fields from pdfcraft
ec0a965 / krilla 0.8.2, sha256 recomputed for merged files.
- krilla PNG decode cap from main recorded as patches/11-png-decode-cap.patch
with its ledger row and README section.
* media: look up the track of each traf in a hash map
* media: bound Matroska indexing by a file-wide frame budget
Every indexed frame claims its stored bytes and at least one, so fixed-size
laces of empty frames cannot describe more frames than the file has bytes.
The frame count across tracks is capped at 2^26, the TrackEntry count at
2^16, and blocks find their track through a hash map.
* media: keep the spec spellings of Colour, ColourSpace and colourPrimaries
* media: PCM chunks stay bounded in MOV and MP4
The writer ends a PCM chunk at 1 MiB or half a second, and returns an
error past 2^32 - 1 samples in a track instead of clamping the stsz count.
The reader splits a PCM chunk above 16 MiB into several samples, so files
from writers without chunk limits read whole past 4 GiB. A sparse output
lets the tests write past 4 GiB, which also exercises the writer's co64
path.
* media: clamp max_lace_frames to 256 and round BlockDuration
* media: level-1 Matroska elements read at most the file's length in all
SeekHead entries pointing at nested elements made each one read the rest
of the file.
* media: set_sample_entry rejects a codec change
* media: the mux oracle compares stream counts before streams
* ci: media checkout does not persist credentials
* media: README lists the renames from FilmCraft and the output that differs on valid input
* media: rustfmt
* media: the stream count comparison leaves out timecode streams
* media: PCM chunks end at 1 MiB only, as ffmpeg's do
ffmpeg's PCM chunks run past half a second, so a duration limit split
remuxed chunks the source kept whole.
* media: frames and samples indexed scale with the file's length
Matroska indexes at most one frame per 8 bytes of file and ISO BMFF at most
one sample per 4 bytes, beyond the first 2^20 of each, so the sample tables
stay within about ten times the file's size. Empty fixed-size laces and
one-byte constant-size samples could make them 40 to 76 times its size.
* media: clamp the frame and sample allowances
Pin third-party actions to full-length commit SHAs (with version comments)
instead of mutable tags/branches to prevent supply-chain tampering via
retagged releases. Resolves SonarCloud rule githubactions:S7637.
Calls storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
with secrets: inherit. Adds SonarCloud badge to README where missing and
removes the redundant sonar-project.properties (config now passed by the
reusable workflow).
* feat(window): shared crate deciding who draws the window frame
Add craft-window, which decides whether the desktop or the app draws the
window frame (titlebar, borders, window buttons) and applies it to an
egui ViewportBuilder. This fixes the drift where some Craft apps drew
their own titlebar on KDE while others kept KWin's.
The rule: macOS, Windows and X11 keep the desktop frame; a Wayland
compositor with zxdg_decoration_manager_v1 (KDE, Sway, Hyprland, COSMIC)
keeps its frame; one without it (GNOME) leaves the frame to the app.
<PREFIX>_TITLEBAR=app|system|desktop overrides. When the app draws the
frame, the decision carries the window-button Layout from GNOME's
button-layout so the app's header bar matches the desktop's order.
Decision logic only; drawing the buttons stays in each app. Extracted and
generalized from pdfcraft's decorations.rs. forbid(unsafe_code); default
build has no egui dependency (egui feature adds decorate()).
* style(window): rustfmt the Wayland probe and Dispatch impl
cargo fmt --check failed CI: collapse the thread-spawn builder chain and
the Dispatch::event signature onto the lines rustfmt wants (verified with
rustfmt 1.98 against the workspace rustfmt.toml).
* fix(window): address review — WAYLAND_SOCKET, bounded gsettings, checkout creds
- Skip the Wayland probe when WAYLAND_SOCKET is set: connect_to_env would
take ownership of the inherited fd and unset the variable, closing the
descriptor winit needs. Keep the desktop frame instead.
- Run the gsettings button-layout lookup behind the same 500 ms bounded
worker as the Wayland probe, so a stalled gsettings can't block startup.
Factored the worker into a shared bounded() helper.
- window.yml: set persist-credentials: false on checkout so the token
isn't left in .git/config while cargo runs dependency build scripts.
- README: document the socket guard and the bounded gsettings lookup.
Verified with rustfmt 1.98 and clippy -D warnings (both feature sets).
* fix(window): reap the gsettings child on timeout; document probe tradeoffs
Follow-up to review: the button-layout lookup now spawns gsettings with
piped output, reads it on a worker, and kills+reaps the child whether it
finishes or times out — so a stalled gsettings leaves neither a blocked
process nor a parked worker (it is a one-shot startup call).
Also document, on the Wayland probe, that an inherited WAYLAND_SOCKET
(rare; normal launches get WAYLAND_DISPLAY) deliberately falls back to the
desktop frame to protect winit's connection — the same frame an app that
doesn't probe would get, a safe default rather than a regression.
Verified: rustfmt 1.98 clean, clippy -D warnings clean (default + egui),
tests pass.
* media: add craft-media with bitstream and isobmff from FilmCraft
Moves filmcraft-bitstream and filmcraft-isobmff (storytold/filmcraft@6c3948a)
into one crate with a feature per module. The public API matches FilmCraft's so
its crates can become re-exports. Tests, oracle tests and fuzzing come along;
the ffmpeg discovery helpers replace filmcraft-testkit.
Out-of-contract inputs no longer panic: bit widths above 32, a zero NAL length
prefix width, and PCM frame sizes that overflow u32. Adds
BitReader::read_ue_plus from DeckCraft's copy.
* media: public ISO BMFF box reader (isobmff::boxes)
Make the demuxer's box iterator and big-endian reader public as
isobmff::boxes::{BoxIter, BoxRef, BoxHeader, Reader}, for walkers of
boxes the demuxer does not model (CR3 uuid boxes, HEIF meta items,
JPEG 2000). BoxIter::within keeps offsets relative to the outer slice,
and allow_truncated yields a cut last box. The fuzz test walks every
box of each mutated file.
* media: matroska feature from FilmCraft's filmcraft-matroska
Port storytold/filmcraft@6c3948a crates/matroska as craft_media::matroska
behind the opt-in feature matroska, with the same public API so FilmCraft
can make filmcraft-matroska a re-export. Its unit and ffprobe oracle tests
come with it, on craft-media's test helpers.
From DeckCraft's copy: cluster and block timestamps no longer overflow;
they saturate at i64::MAX. MuxingApp and the default WritingApp name
craft-media. New tests: block additions and mutation fuzzing. The oracle
skips the duration of a track's last packet when ffmpeg 8 shortened it.
* media: MOV PCM uses lpcm v2 above 16.16 rates and for unsigned 16-bit; signed 8-bit as twos
Version 0 sound descriptions store the rate as 16.16 fixed point, so 88.2 kHz
and higher were written as 0 Hz. 8-bit signed PCM was labelled raw (unsigned)
and 16-bit unsigned as twos/sowt (signed). The reader also took raw under a
sound handler for uncompressed video.
* media: stop the top-level box scan at a box that runs past the end of the file
A 64-bit box size near u64::MAX saturated the scan offset, and the next
offset + 8 overflowed.
* media: one sample budget for the whole ISO BMFF file
The sample cap and the file-length bound on samples without table entries
(constant-size stsz, trun without per-sample fields) applied per track or
per trun, so repeating a small trak or trun multiplied the allocation.
Both are now shared by all tracks and fragments.
* media: set_sample_entry rejects entries that change the track's media type or PCM frame size
The handler, stsz constant size and PCM timescale check are fixed at add_track;
a replacement that changed them produced a corrupt file. A bad track index
returns NoSuchTrack like the other writer methods.
* media: saturate the sample end in MkvFile::duration_ns
Cluster timestamps saturate at i64::MAX, so pts + duration could overflow
for files without Info/Duration.
* media: checked add for the Matroska cue cluster position
CueClusterPosition comes from the file; an add past u64::MAX panicked in
debug builds and wrapped in release. Such a cue now falls back to the index.
* media: cap the Matroska TimestampScale at 2^32 and saturate tick conversions
A scale near 2^64 overflowed u128/i128 in the per-lace split and in
Track::ticks_to_ns. A BlockDuration near u64::MAX also overflowed the tick
difference at the default scale. Larger scales are ignored like a zero scale,
and tick and nanosecond values outside i64 saturate.
* media: Matroska fixture generation no longer truncates the shared subtitle inputs
Parallel tests generating subs.mkv or webvtt.webm could read in.srt or in.vtt
while another thread rewrote it, and cache a fixture with missing packets.
* media: rustfmt parse_moof signature
* ci: add craft-media workflow covering feature sets, WASM and ffmpeg oracles
The lint job runs fmt, clippy for each feature set (none, bitstream,
isobmff, matroska, all) and a wasm32 check. The test job runs the
all-features tests with Ubuntu's ffmpeg and CRAFT_MEDIA_REQUIRE_ORACLES=1,
installing ffmpeg while the tests compile.
* media: Matroska lacing keeps timestamp gaps
A laced frame's timestamp is implied by the block's and the track's
DefaultDuration, so lacing collapsed gaps in the input. A frame now joins
a lace only when that implied tick equals its own, and tracks without a
DefaultDuration do not lace. The laced remux oracle compares timestamps.
* media: required oracles fail on fixture generator and ffprobe failures
Every path that gave up on an oracle check (missing tool, failed fixture
generator, failed ffprobe run) goes through one helper that prints
SKIPPED, or fails the test with CRAFT_MEDIA_REQUIRE_ORACLES=1. Fixture
generators of both containers share run_generator, which takes the
required flag explicitly so tests can exercise it without changing the
process environment.
* media: use US spelling
* media: a failed ffprobe probe skips the optional oracle check
* media: the Matroska oracle compares the last subtitle cue's duration
craft-plate-test writes JSON scenes through craft-pdf and checks the written PDF's
structure (CI, under a second) and, locally only, its separated plates through
Ghostscript tiffsep (scripts/plate-check.sh). Apps can run the same scenes through
their own export as a dev-dependency.
The per-crate workflows name their crates with -p, so the new crate had no job
and its tests, including the Windows-only colon and stream assertions, never ran
in CI.
Consent expiry is now per kind of data: renewing text no longer renews an expired image permission. A connection edit keeps models found by a test at the new address, and drops only the ones saved for the old one.