The CLUT seed is built from every region that may be drawn, so it is
the same for every image of a caption, but it was rebuilt for each one.
Build it once instead. Converting an image only appends colors past the
seed and never changes the alpha table, so restoring the CLUT size at
the start of each image restores the seed.
The output is unchanged: the encoded bitmaps of both aribcaption FATE
samples are identical, with and without ignore_ruby.
Reported-by: Forgejo_Fairy
Reference: #24992
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
Text mode output is caption.text, which never holds ruby. It decided
whether there was anything to show from region_count, so a caption with
only ruby regions was output as text. Without CS, that event held an
empty text and erased the previous caption, which only CS may do. With
CS it failed: libaribcaption returns a NULL text when it is empty, and
av_strdup(NULL) was reported as ENOMEM.
Decide on the regions that are not ruby and hold a character instead.
The text itself is not a reliable test, as it can hold line breaks
without any character. Nothing to show gives no event, or the empty
clear event with CS, as in the other modes; a NULL text is taken as
empty.
Add a text mode test on the ruby sample. The ruby only caption without
CS at 5s now produces no event.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
ARIBCC_RENDER_STATUS_NO_IMAGE leaves the render result empty, so it
reaches the same "no image" check as a GOT_IMAGE result without images.
Let it fall through instead of keeping a second copy of the clear
handling, and log the image count before the result is cleaned up,
which reset it to 0.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
The bitmap CLUT seed, the single rect position and the ass loop each
tested "ruby with ignore_ruby" on their own. Use one helper so they
always agree on which regions are left out. It also leaves out regions
without any character, which would otherwise produce an event holding
only tags.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
clut_alpha holds the alpha values pixels are snapped to, but only its
first entry was reset for each rect; the others kept the values seeded
by earlier captions. Those stayed available for quantization even when
the current seed palette has no such alpha, so pixels could snap to a
stale alpha, e.g. to 128 instead of 255 after an earlier caption with a
semi-transparent color. Reset all entries with the CLUT.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
In multi rect mode each region position is made non-negative by adding
the plane size when it is negative. Single rect mode instead took the
minimum of the raw positions, then fixed only y. A negative x, or a mix
of negative and non-negative y, put the single rect elsewhere than the
regions it holds. Normalize each region position first, as multi rect
mode does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
A caption without regions and a caption whose regions were all skipped
as ruby both end up with nothing to show, and were handled by two
copies of the same clear-screen check. With no region the loop writes
nothing, so the later check already covers it. Drop the early return.
Also clean up through the fail path if adding the clear rect fails,
like every other error in this function, instead of leaving the
partially built subtitle to the caller.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
The new sample sub/aribcaption_ruby.ts (1316 bytes, synthetic) is an
ARIB STD-B24 profile A caption stream. Small size (SSZ) text is decoded
as a ruby region. Caption glyphs are one DRCS pattern and ruby is an
alphanumeric character that is never drawn with ignore_ruby, so the
results do not depend on installed fonts:
t=2s CS + caption
t=3s CS + ruby only
t=4s CS + ruby line above a caption line
t=5s ruby only, without CS
With ignore_ruby=1, only CS clears, ruby is not drawn, and a caption
that is all skipped ruby without CS produces no event:
bitmap: 2s 1 rect, 3s clear, 4s 1 rect (the one caption line)
ass, multi rect: 2s, 3s clear, 4s
ass, single rect: same events, checked with the dialogue text so the
4s position comes from the caption line, not the
skipped ruby above it
Before the fixes in this series, the bitmap test overflows the rects
array at 4s, the multi rect ass test misses the 3s clear, and the single
rect ass test has an extra 5s event and the ruby position.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
With ass_single_rect=1 and ignore_ruby=1, a caption whose regions are
all ruby still produced one event holding only the position tags, even
without a clear screen (CS). Track whether any region was written and
treat "none" like the multi rect mode: emit nothing, or the empty clear
event if the caption carries CS.
The line separator was also appended after every region but the last
one, so skipping a trailing ruby region left a dangling "{\r}\N". Write
it before every written region but the first instead. Output is
unchanged when no region is skipped.
The position of the single rect was also taken from all regions,
including skipped ruby above the text. Ignore skipped regions there too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
The ass error path freed sub->rects[0] .. [region_count - 1], but
ff_ass_add_rect() grows the array one rect at a time and counts the
rects it added in sub->num_rects, including one whose dialog string
failed to allocate. Entries past num_rects are not initialized, so an
allocation failure part way through read them, or past the array.
Free up to sub->num_rects instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
Reported-by: Forgejo_Fairy
Reference: #24992
Found-by: Joshua Rogers <joshua.rogers@aisle.com>
The bitmap path allocated one rect per rendered image but filled them,
freed them and placed them by looping over the caption regions. The
renderer does not draw every region: with ignore_ruby it drops ruby
regions, and it always drops regions that are too small to draw. A
caption with one ordinary and one ruby region then wrote past the end
of the rects array.
Loop over the images instead. The renderer does not say which region an
image comes from, so the CLUT can no longer be seeded from "its" region.
Seed it from the colors of every region that may be drawn; colors that
are not seeded are still added from the image itself, as before.
While at it, use bounded insertion for every CLUT seed color,
including the text and background colors added in the character loop.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
Reported-by: Forgejo_Fairy
Reference: #24992
With ignore_ruby=1 and ass_single_rect=0, a caption whose regions are all
ruby skips every region and returned no event, even when it carries a
clear screen (CS). The previous caption then stayed on screen. The bitmap
path and ass_single_rect=1 already erase it in that case.
Emit the empty clear event in that case too.
Such captions do occur in broadcasts: among 2293 captions from 40 recent
Japanese TV recordings, 3 carried CS with only ruby regions. With this
change, ignore_ruby=1 output gains exactly those 3 clear events, and
output with the default options is unchanged for all 40 recordings.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
The clear path ignored the return value of ff_ass_add_rect() and
reported an event even if adding the empty rect failed, unlike the other
two calls in this function. Return the error instead; the caller frees
the partially built subtitle.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
The text and ass paths treated every caption without regions as a clear
and emitted an empty ("") subtitle. A statement containing only TIME
also decodes to zero regions, without the clear-screen flag, so it
erased the caption that was being displayed.
Test ARIBCC_CAPTIONFLAGS_CLEARSCREEN as the bitmap path already does,
and emit nothing for other regionless captions.
Add text and ass mode tests on the existing aribcaption_clear.ts sample
(visible caption, TIME-only statement, CS-only statement). Each
reference has two events; before this change there was a third, empty
one for the TIME-only statement.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
aribcaption_trans_text_subtitle() always fills exactly one rect, but
allocated the rects array with ctx->caption.region_count entries. For a
caption without regions, such as a clear screen, that is a zero-sized
allocation, and writing sub->rects[0] goes past its end.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
Found-by: Joshua Rogers <joshua.rogers@aisle.com>
aribcc_renderer_render() fills ctx->render_result with images that are
owned by the caller and must be released with
aribcc_render_result_cleanup(). The bitmap path did this only for the
"unchanged", "no image" and unknown-status returns. After converting the
images into AVSubtitleRects, and on the error path, the result was left
allocated, and the next call overwrote it without freeing it. Every
rendered caption therefore leaked its bitmaps.
The images are not used after conversion, so release them before
returning from both paths.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
Reported-by: Forgejo_Fairy
Reference: #24067
Add a regression test for the bitmap-mode clear handling in the
libaribcaption wrapper. The sample (sub/aribcaption_clear.ts, 1128 bytes,
synthetic) carries an ARIB STD-B24 profile A caption stream with:
t=1s caption management data
t=2s CS + one DRCS glyph -> visible caption (num_rects=1)
t=3s TIME(1.0s) only -> zero regions, must not emit an event
t=4s CS only -> empty subtitle event (num_rects=0)
The reference contains exactly two SUBTITLE frames. Before the fix the
CS-only statement produced no event (one frame); a region_count == 0
predicate would also erase on the TIME-only statement (three frames).
The visible glyph is a DRCS pattern, so the result does not depend on
installed fonts; the test passes on a system without any fonts.
The test uses ffprobe -show_frames, so only num_rects and timestamps are
compared; no rendered bitmap data enters the reference.
An ARIB caption statement whose data units contain only CS (clear screen)
decodes to a caption with the ARIBCC_CAPTIONFLAGS_CLEARSCREEN flag set and
region_count == 0. In SUBTITLE_TEXT mode this is translated into a rect
holding "" so that the previous caption is erased. In SUBTITLE_BITMAP mode
nothing is rendered, the function returns 0 and *got_sub_ptr is left at 0,
so the caller never learns that the caption ended.
Broadcasters send such a statement at the end of every caption segment (for
example when going to a commercial break). Combined with wait_duration ==
ARIBCC_DURATION_INDEFINITE, which maps to end_display_time = UINT32_MAX, the
last caption of a segment stays on screen until the next one arrives - in a
real recording that was 22 minutes later.
Report an empty subtitle (num_rects = 0) when the clear-screen flag is set,
which sub2video and the usual subtitle consumers already treat as "erase".
The flag is tested instead of region_count == 0 because a statement
containing only TIME also decodes to zero regions but must not erase the
currently displayed caption.