Skip to content

WebVTT cue settings, teletext grid-row placement, and the blank-row fold - #241

Merged
superuser404notfound merged 4 commits into
mainfrom
feat/issue233-vtt-settings-teletext-rows
Jul 28, 2026
Merged

WebVTT cue settings, teletext grid-row placement, and the blank-row fold#241
superuser404notfound merged 4 commits into
mainfrom
feat/issue233-vtt-settings-teletext-rows

Conversation

@superuser404notfound

@superuser404notfound superuser404notfound commented Jul 28, 2026

Copy link
Copy Markdown
Owner

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 / align as upstream-blocked. That was a conclusion about the wrong layer. libavcodec's WebVTT decoder does drop them (webvttdec.c says so as a @todo in its file header), so the ASS event line carries no trace. The demuxer keeps them: libavformat/webvttdec.c:166 attaches the verbatim settings string to every packet as AV_PKT_DATA_WEBVTT_SETTINGS, and matroskadec.c:3973 propagates the same side data for WebVTT in Matroska (verified by round-tripping a settings-carrying .vtt through 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 percentage line anchors to the frame edge it is nearer (the spec's default line alignment pins the box top, so line:90% would hang a two-line cue off the frame), a line number keeps only the half of the frame it names (line boxes need a rendered line height the engine does not have), size and vertical have no equivalent in the placement model, and a position without a line keeps 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_ass derives the vertical anchor from the grid row and writes {\anN}, in thirds. 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, 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. edgeTrimmed was removing it.

teletextBody now applies ffmpeg's own formula to that ordinal, gated on no \an having arrived and on teletext only. Note that on a flagged page the leading \Ns are NOT the grid row: they are the empty_lines / 2 filler that nudges the block inside its anchor, which is why the gate matters more than the counting.

Two consistency fixes in the same area

  • The styled path edge-trimmed with a literal space/tab/newline test while the plain path used .whitespacesAndNewlines, so a Unicode space survived on a styled cue and not on an unstyled one. Not reachable from teletext (IS_TXT_SPACE maps U+00A0 to a space, written as \h), reachable from SRT, WebVTT and ASS.
  • The AU live tv #107 blank-line fold worked per run plus one run boundary, which misses the shape 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 run boundaries.

Blocked on FFmpegBuild

The sidecar half needs the WebVTT demuxer, which was never compiled in: only the decoder was, so avformat_open_input rejected a .vtt file with AVERROR_INVALIDDATA and an external WebVTT subtitle never loaded at all. Same shape as the raw-PGS sup demuxer 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.

superuser404notfound and others added 4 commits July 28, 2026 20:04
#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
superuser404notfound merged commit e5a28e9 into main Jul 28, 2026
3 checks passed
@superuser404notfound
superuser404notfound deleted the feat/issue233-vtt-settings-teletext-rows branch July 28, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant