Review of the elision fix: the cheap cost went to every apostrophe form the
dictionary accepts, so English possessives (churchs → church's), contractions
(neer → ne'er) and Catalan enclitics (acusit → acusi't) jumped ahead of the
one-letter fix. A missing apostrophe now ranks first only before a dictionary
word of two letters or more (j'adore, l'home, qu'il, dell'anno) and only when
the language elides (new Tuning::elision, off for English). Any other form costs
COST_MISTYPE + 1, after every one-letter fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco
Suggestions tried a missing space but never a missing apostrophe, so French
"Jadore" got "Adore, J adore, Jade" and never "J'adore". Round 4 now also
offers a'b after a short first part (at most 7 letters), ranked before every
split. The dictionary's own check decides whether the elision is right, so
French, Italian and Catalan elision (l'acqua, dell'anno) work and a dictionary
without elided forms is unaffected. Suggestions keep the writer's case and the
apostrophe the dictionary's OCONV writes (J’adore). Costs at most one lookup
per split.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco
A track could list any number of ContentEncoding entries, and readers walk
the whole list for every frame they return (frames_readable,
stripped_header). A track with more than 16 entries, far beyond the one or
two a muxer writes, is now treated as having an unsupported compression, so
its frames are returned as stored and the list is never long.
* craft-media: Ogg demuxer
FilmCraft's `filmcraft-ogg` as `craft_media::ogg`, behind the opt-in
`ogg` feature: pages (CRC-32 checked, damage skipped by searching for
the next `OggS`), logical streams, packets across pages, and the Ogg
Opus, Vorbis and FLAC mappings. `OpusTiming` gives each Opus packet's
start, the pre-skip and the final page's end trimming.
`Stream::codec_params()` returns `CodecParams` like the other
containers; for FLAC each stream keeps its own header count, and an
unstated count is bounded (it ends at the first audio frame, or at
32 MiB with a warning). `ogg::ByteSource` is `craft_media::ByteSource`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-media: Ogg review fixes: bounded recovery, lost pages, chained links, caps
From the review of the Ogg demuxer (storytold/craft-libs#440):
- The damage scan charges the positions it examines, not a whole 1 MiB read,
reads 64 KiB at a time, and says when the 64 MiB budget runs out. A file
with many damaged pages lost its tail without a warning.
- A page whose lacing claims more bytes than the file has left is skipped
and the scan resumes, instead of ending the parse.
- A gap in a stream's page sequence numbers drops the packet the lost page
continued; its two ends were joined into one corrupted packet.
- A beginning-of-stream page for a serial already seen starts a new stream,
so a chained file whose links reuse a serial no longer hands the second
link's headers to the decoder as audio.
- The packets kept from a file are capped by its size (as Matroska caps
frames), so zero lacing values cannot make the packet lists many times the
file. FLAC metadata under an unstated header count must be a block of the
length its header states, and at most 1024 are kept.
- A packet over 4 GiB is dropped instead of overflowing its size.
- OpusTiming saturates, so hostile granule positions cannot overflow, and a
clip whose only audio page is also the last is trimmed at the end
(RFC 7845 §4.5) instead of at the start.
- Speex streams read extra_headers (offset 68) for their header count.
codec_params gives Theora its Xiph-laced headers and Speex its header,
rate and channels; the FLAC extradata flags exactly the last block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-media: Ogg damage recovery charges every page it reads and hashes
From CodeRabbit's review of 2308071: a page that fails its CRC was read and
hashed in full (up to about 64 KB) before the parser stepped one byte on,
and the damage scan read 64 KiB per call however near the next page was.
Neither was charged to the recovery budget, so a file packed with fake page
headers could cost gigabytes of reading and hashing.
- A failed page's bytes are charged to the recovery budget; opening stops
with a warning when it is spent.
- find_capture starts with a 256-byte read and doubles it up to 64 KiB.
- Warnings about damaged pages are capped at 1024, with a closing count of
the rest.
- Filing a completed packet as header or audio moves into Stream::add_packet,
keeping open under the cognitive-complexity limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-media: Ogg recovery budget in proportion to the file; stray BOS; header caps
- Damage recovery charges every byte it reads again (a high-water mark), against a budget of
twice the file's length plus 1 MiB, instead of a fixed 64 MiB that cut off large damaged
files. A page's body is read without reading its header again, so a page is not charged twice.
- A beginning-of-stream page that carries on the page sequence of a stream with no
end-of-stream page yet is a stray flag: a warning, and the stream is not split. A link that
starts again before its end-of-stream page warns.
- Header packet counts a file states (FLAC, Speex) are capped like the unstated FLAC count.
- Reserved FLAC metadata block types (7-126) are metadata blocks.
- Theora codec_params carries the picture size; the extradata table lists Theora and Speex.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-media: Ogg header reads checked before they are made; scan span on 32-bit
- A header packet past the size cap is checked by its stated size and not read, and a stream's
first packet is read once (only its first 80 bytes when it is too large to be a header).
- MAX_HEADER_BYTES is 64 MiB, room for two of the largest FLAC metadata blocks with their block
headers and the rest of the headers.
- find_capture clamps the scan span in u64 before casting, so a span of 2^32 bytes cannot become
0 on 32-bit targets.
- The Recovery doc says which reads it charges, and what header packet reads add.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Index::assemble searched every earlier position listed under a PostScript
name before adding the next, so an index that gives one name to many files
took time quadratic in their number. Files are visited in order, so a file
that lists a name twice can only be the last position pushed: comparing
with the end is enough.
find_postscript compared each hit's family name with every name collected
so far; it now keeps the lowercased names in a set.
A non-Unicode document path holding '..' (/a/../\xff/d.doc) was refused,
because the native join kept the '..' while the lexical result folded it.
join_native now folds the folder's components as normalize does, so such a
document resolves as a Unicode one would, and a '..' that leaves a Unix name
holding a backslash is still caught by the comparison.
- A VectorCraft or PhotoCraft `generator`, and a VectorCraft `preview` or
`pdf`, are read as they are parsed. A value of the wrong shape is
skipped element by element with `IgnoredAny` and none of it is kept;
before, serde's untagged enum buffered the whole value first, with no
limit on that buffer. The preview and PDF still read as `File::read`
reads them, the array form of `Image` included.
- A rendition after the JSON of a gzip file is counted against
`max_total` once, as it is decompressed. A 40 KiB PDF with `max_total`
at 64 KiB now reads compressed as it does uncompressed. A preview and
PDF that name the same bytes are counted as two copies.
- A repeated VectorCraft `version` is malformed, not too new, and
`renditions` refuses a missing or repeated `document`, as `File::read`
does.
- The README says that a VectorCraft file written as a JSON array is not
recognized by `sniff`.
std::path sees no \??\ or \\.\ prefix and no //server/share boundary on Unix,
so the native root check let those document roots through. Resolve the link
against the lossy document path with the Unicode rules, and return the native
join only when it names the same components.
- pcraft_layer_rgba builds a name-to-entry map of the bundle once per
call instead of walking the central directory for every tile.
- A tile that several references name is read, decoded and counted
against max_total once per call. A hash referenced at two pixel sizes
is malformed.
- Surfaces stored as RGB or grayscale are read in documents of every
mode, so bitmap, duotone, indexed and multichannel documents read. A
CMYK or Lab layer or mask gives Unsupported, and an RGB mask gives its
first channel.
- A canvas of zero width or height is malformed.
- The docs name video layers, the masks of layers without pixels, and
tile references counting against max_items.
- doc.yml runs the tests without features again, next to
--all-features.