Skip to content

feat(cc608): choose the timecc608 caption mode, defaulting to paint-on - #329

Merged
tobbee merged 1 commit into
mainfrom
feat/cc608-caption-modes
Aug 11, 2026
Merged

feat(cc608): choose the timecc608 caption mode, defaulting to paint-on#329
tobbee merged 1 commit into
mainfrom
feat/cc608-caption-modes

Conversation

@tobbee

@tobbee tobbee commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Replaces #326, which fixed only the pop-on flip placement. With go-608 v0.9.0 generating all three CTA-608 caption modes per unit, timecc608 can name the mode instead, and the default becomes the one whose segments are independent.

The value is now <channel>-<lang>[-<mode>[-<modifier>]]:

URL mode caption appears self-contained segments
timecc608_CC1-eng paint-on (default) types on, two characters per frame yes
timecc608_CC1-eng-roll3 roll-up (roll2, roll3) types onto a scrolling window yes
timecc608_CC1-eng-pop pop-on whole, exactly on its second no
timecc608_CC1-eng-pop-sc pop-on whole, ~0.5 s late yes

Paint-on is the default. Each cue clears the screen and writes the caption straight onto it, so no cue's data leaves its own segment: every segment decodes standalone and a client that starts, seeks or joins anywhere is correct from the first cue boundary it sees. The typing doubles as a liveness tell.

Roll-up types each line onto the base row of a window that scrolls the earlier lines up, reset at the start of every segment. Limited to 2 or 3 rows because the caption's own lines put its base row at row 3 — a 4-row window would need a row 0.

Pop-on keeps both placements. -pop puts each EOC on its cue's first frame with the build draining over the frames before it, so the caption is displayed over exactly the interval its text names; the cost is that a cue's build lives in the preceding segment, and a receiver joining mid-stream gets a leading EOC without it — blank on a fresh decoder, one cue of a stale caption on one that keeps 608 state, correct from the next cue on. A sender cannot paper over that: an ENM ahead of the EOC would erase the build about to be flipped. -pop-sc trades the frame accuracy back for a segment that stands alone.

Also in here

  • go-608 v0.7.0 → v0.9.0. The scheduling is go-608's per-unit builders throughout; the local copy of that scheduler is gone. Their generate.Unit carries a unit's number, start time and frame count as independent facts, so a segment whose number does not match its media time (startnr_, a non-zero availabilityStartTime) still gets the right clock, and the next unit's start comes from the fragment's media duration in ticks rather than frames × frame duration, which a fractional frame rate would drift on.
  • A cue is never shorter than a second — v0.9.0's NumCues divides a segment down into whole periods, so a 1.92 s segment gets one 1.92 s cue instead of two of 0.96 s. Segments of 2 s and 2.002 s are unaffected.
  • The language must be a three-letter ISO 639-2 code. It is used in exactly one place, the SCTE 214-1 accessibility descriptor value (value="CC1=eng"), whose scheme is defined in those terms — not DASH's RFC 5646 @lang, which this option never sets. en, en-US and zh-Hans were previously passed straight into a non-conformant descriptor. A single-token language also makes the fields positional, so a mistyped mode or modifier is reported instead of being read as part of the language.

Verification

The tests decode the injected cc_data back in presentation order — the order a conformant receiver reassembles it in — and assert the caption text and the frame it lands on: across two consecutive segments for the cross-segment pop-on case, for AVC and HEVC, at 30, 29.97 and 24 fps, through the low-latency chunked path, and per frame for paint-on's clear-and-type and roll-up's scroll history. Test configurations come from CreateCC608Config, so each test names its mode exactly as a request does.

go test ./... and golangci-lint run are clean. Not yet watched in a real player.

🤖 Generated with Claude Code

timecc608 injected one pop-on caption per second, built and flipped inside the cue's own frames, so the caption became visible ~0.5 s into the second its clock named. go-608 v0.9.0 generates all three CTA-608 caption modes per unit, so the option now names one: <channel>-<lang>[-<mode>[-<modifier>]] with paint (the default), pop, pop-sc, roll2 and roll3.

Paint-on is the default because it is self-contained by construction: each cue clears the screen and writes the caption straight onto it two characters per frame, so no cue's data leaves its own segment, every segment decodes standalone, and a client that starts, seeks or joins anywhere is correct from the first cue boundary it sees. The typing doubles as a liveness tell. Roll-up (roll2, roll3) types each line onto the base row of a window that scrolls the earlier lines up, reset at the start of every segment so the display owes nothing to the previous one; it is limited to 2 or 3 rows because the caption's own lines put its base row at row 3.

Pop-on keeps both placements. With -pop each cue's EOC rides its cue's first frame and the build drains over the frames before it, so the caption is displayed over exactly the interval its text names; the cost is that a cue's build lives in the preceding segment, and a receiver joining mid-stream gets a leading EOC without it - blank on a fresh decoder, one cue of a stale caption on one that keeps 608 state, correct from the next cue on. That is a receiver-side matter: an ENM ahead of the EOC would erase the build about to be flipped. -pop-sc trades the frame accuracy back for a segment that stands alone.

The scheduling is go-608's per-unit builders throughout. Their generate.Unit carries a unit's number, start time and frame count as independent facts, so a segment whose number does not match its media time - startnr_, a non-zero availabilityStartTime - still gets the right clock, and the next unit's start comes from the fragment's media duration in ticks rather than frames times frame duration, which a fractional frame rate would drift on. NumCues divides a segment down into whole periods, so a cue is never shorter than a second: a 1.92s segment gets one 1.92s cue instead of two of 0.96s.

The language field is a three-letter ISO 639-2 code. It is used in one place, the SCTE 214-1 accessibility descriptor value (value="CC1=eng"), whose scheme is defined in those terms; it is not DASH's RFC 5646 @lang, which this option never sets, so en, en-US and zh-Hans would have gone straight into a non-conformant descriptor. A single-token language also makes the value's fields positional, so a mistyped mode or modifier is reported instead of being read as part of the language.

The tests decode the injected cc_data back in presentation order - the order a conformant receiver reassembles it in - and assert the caption text and the frame it lands on: across two consecutive segments for the cross-segment pop-on case, for AVC and HEVC, at 30, 29.97 and 24 fps, through the low-latency chunked path, and per frame for paint-on's clear-and-type and roll-up's scroll history. Test configurations come from CreateCC608Config, so each test names its mode exactly as a request does.
@tobbee
tobbee merged commit 3cc7093 into main Aug 11, 2026
11 of 14 checks passed
@tobbee
tobbee deleted the feat/cc608-caption-modes branch August 11, 2026 08:06
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