WebVTT cue settings, teletext grid-row placement, and the blank-row fold - #241
Merged
superuser404notfound merged 4 commits intoJul 28, 2026
Merged
Conversation
#233 shipped styling for every text format and reported WebVTT placement as upstream-blocked. That was the wrong layer. libavcodec's WebVTT decoder really does drop line/position/align (webvttdec.c says so as a @todo in its header), so nothing about them is in the ASS event line it synthesises. The demuxer keeps them: libavformat/webvttdec.c attaches the verbatim settings string per packet as AV_PKT_DATA_WEBVTT_SETTINGS, and matroskadec.c propagates the same side data for WebVTT in Matroska. So both decoders now read that side data and map it onto SubtitleTextPlacement, and the #112 packet store carries the string through a rebuild (side data does not survive in the payload, so a stored packet would otherwise lose the placement a freshly demuxed one has). An ASS \an or \pos still wins: that came from the payload itself. Two deliberate limits, both because the placement models an anchor and a point rather than a box: `size` has nowhere to go, and a `position` without a `line` keeps only the alignment column, since a point needs both axes. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01At1agC2qU2Y2MX2BKLdT4N
Reported on #233: teletext captions lost their vertical placement. Only on pages libzvbi does not classify as subtitle pages. gen_sub_ass derives the vertical anchor from the grid row itself and emits it as {\anN}: vertical_align = (2 - (av_clip(i + 1, 0, 23) / 8)); av_bprintf(&buf, "{\\an%d}", alignment + vertical_align * 3); but that block sits behind is_subtitle_page, which comes from the row-0 header flags (NEWSFLASH clear AND SUBTITLE set AND SUPPRESS_HEADER set). When a broadcaster does not set all three, or the header has not been seen yet, the else path writes the whole page instead, one " \N" per grid row with the empty ones included. The row ordinal is then the only carrier of the position, and edgeTrimmed was throwing it away. teletextBody now falls back to ffmpeg's own formula on that ordinal rather than inventing a scale, and never overrides an \an that arrived: {\an2} is explicitly bottom and produces exactly the same empty output that "no information" would. Coarse on purpose, three bands is what the source encodes; per-row offsets make consecutive cues of different heights sit at different heights, which reads as a blink. Two consistency fixes in the same area, found while measuring this: - The styled path edge-trimmed with a literal space/tab/newline test while the plain path used .whitespacesAndNewlines, so a Unicode space (U+00A0 among them) survived on a styled cue and not on an unstyled one. Teletext cannot produce one (IS_TXT_SPACE maps U+00A0 to a space, which decode_string writes as \h), but SRT, WebVTT and ASS payloads can. - The #107 blank-line fold worked per run plus one run boundary, which misses the case the source produces most easily: the padding of an empty row carries the spacing attribute that changes colour, so the blank row lands in a whitespace-only run of its own and breaks the chain. It now folds the flattened sequence and re-splits along the original run boundaries. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01At1agC2qU2Y2MX2BKLdT4N
Brings the subtitle section up to what 5.26.0 actually shipped (the text-codec bullet still described the pre-#233 behaviour, where override blocks were stripped) and adds the two paths placement now arrives on. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01At1agC2qU2Y2MX2BKLdT4N
Standalone .vtt files could not be opened at all: only the webvtt decoder was in the build, so avformat_open_input rejected the file with AVERROR_INVALIDDATA and an external WebVTT subtitle never loaded. 2.3.0 enables the demuxer, which is also what attaches the cue settings this branch reads. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01At1agC2qU2Y2MX2BKLdT4N
superuser404notfound
deleted the
feat/issue233-vtt-settings-teletext-rows
branch
July 28, 2026 18:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #233 from tresby's retest. Three separate findings, one of which contradicts a note that shipped with 5.26.0.
WebVTT cue settings arrive after all
5.26.0 reported
line/position/alignas upstream-blocked. That was a conclusion about the wrong layer. libavcodec's WebVTT decoder does drop them (webvttdec.csays so as a@todoin its file header), so the ASS event line carries no trace. The demuxer keeps them:libavformat/webvttdec.c:166attaches the verbatim settings string to every packet asAV_PKT_DATA_WEBVTT_SETTINGS, andmatroskadec.c:3973propagates the same side data for WebVTT in Matroska (verified by round-tripping a settings-carrying.vttthrough MKV with-c:s copy).Both decoders now read that side data and map it onto
SubtitleCue.placement, and the #112 packet store carries the string through a packet rebuild, since side data does not live in the payload.Mapping rules and their reasons are in
WebVTTCueSettings: a percentagelineanchors to the frame edge it is nearer (the spec's default line alignment pins the box top, soline:90%would hang a two-line cue off the frame), alinenumber keeps only the half of the frame it names (line boxes need a rendered line height the engine does not have),sizeandverticalhave no equivalent in the placement model, and apositionwithout alinekeeps only the alignment column.Teletext keeps its row placement on non-subtitle-flagged pages
The report claimed libzvbi does not emit
\an. It does:gen_sub_assderives the vertical anchor from the grid row and writes{\anN}, in thirds. But that block sits behindis_subtitle_page, which comes from the row-0 header flags (NEWSFLASH clear AND SUBTITLE set AND SUPPRESS_HEADER set,libzvbi-teletextdec.c:617). Off that path it writes the whole page instead, one" \N"per grid row with the empty rows included, and the ordinal of the first non-blank row becomes the only carrier of the position.edgeTrimmedwas removing it.teletextBodynow applies ffmpeg's own formula to that ordinal, gated on no\anhaving arrived and on teletext only. Note that on a flagged page the leading\Ns are NOT the grid row: they are theempty_lines / 2filler that nudges the block inside its anchor, which is why the gate matters more than the counting.Two consistency fixes in the same area
.whitespacesAndNewlines, so a Unicode space survived on a styled cue and not on an unstyled one. Not reachable from teletext (IS_TXT_SPACEmaps U+00A0 to a space, written as\h), reachable from SRT, WebVTT and ASS.Blocked on FFmpegBuild
The sidecar half needs the WebVTT demuxer, which was never compiled in: only the decoder was, so
avformat_open_inputrejected a.vttfile withAVERROR_INVALIDDATAand an external WebVTT subtitle never loaded at all. Same shape as the raw-PGSsupdemuxer gap. FFmpegBuild main carries the flag now (superuser404notfound/FFmpegBuild@ac70e2c); this branch still needs the pin bump once that is tagged.Tests
Issue233WebVTTCueSettingsTests(10, including an end-to-end sidecar decode through the real demuxer),Issue233TeletextRowPlacementTests(9). Full suite green against the local FFmpegBuild build: 1229 Swift Testing, 392 XCTest.