From 1d2251722c24e4e8fb17d01c14518842075f7533 Mon Sep 17 00:00:00 2001 From: GAURAV KARMAKAR Date: Wed, 22 Apr 2026 01:20:17 +0530 Subject: [PATCH] fix(608): handle pop-on to roll-up transition ending at EOF When a stream switches from pop-on to roll-up mode and ends before check_roll_up() reports changes=1 (fewer lines than the roll-up window requires), the CR handler never runs the backfill at line 836 and current_visible_start_ms is still zero when flush_608_context fires EraseDisplayedMemory. Result: the first emitted caption is timestamped at 00:00:00,000. Capture the FTS of the first character after the transition in a new field ts_first_char_rollup_transition. Unlike ts_start_of_current_line, this field is not overwritten by intermediate changes=0 CRs, so the first-char time survives to the end-of-stream flush path. write_cc_buffer uses it to backfill current_visible_start_ms when rollup_from_popon is still set at emit time, and clears it together with the flag. The normal popon->roll-up flow (where a scrolling CR eventually fires) and every other caption path are unchanged. Verified on c4dd893cb9d6...ts: first subtitle now starts at 00:00:13,913 (was 00:00:00,000). Regression tested against six master samples the mentor flagged (0069dffd.mpg 03,603; 5cbb21ad.dvr-ms 00,534; 132d7df7.mov 12,812; 6395b281.asf 02,436; 8849331d.mp4 03,771; b22260d0.ts 04,838) - all match the reference timings unchanged. --- src/lib_ccx/ccx_decoders_608.c | 20 ++++++++++++++++++++ src/lib_ccx/ccx_decoders_608.h | 1 + 2 files changed, 21 insertions(+) diff --git a/src/lib_ccx/ccx_decoders_608.c b/src/lib_ccx/ccx_decoders_608.c index 63d73ec01..5147e5865 100644 --- a/src/lib_ccx/ccx_decoders_608.c +++ b/src/lib_ccx/ccx_decoders_608.c @@ -150,6 +150,7 @@ ccx_decoder_608_context *ccx_decoder_608_init_library(struct ccx_decoder_608_set data->my_channel = channel; data->have_cursor_position = 0; data->rollup_from_popon = 0; + data->ts_first_char_rollup_transition = -1; data->output_format = output_format; data->cc_to_stdout = cc_to_stdout; data->textprinted = 0; @@ -246,6 +247,12 @@ void write_char(const unsigned char c, ccx_decoder_608_context *context) context->cursor_column++; if (context->ts_start_of_current_line == -1) context->ts_start_of_current_line = get_fts(context->timing, context->my_field); + // First char after a pop-on -> roll-up transition: remember its FTS so + // write_cc_buffer can use it if EOF fires before a scrolling CR. + // Unlike ts_start_of_current_line, this field is NOT touched by + // intermediate CR commands (changes=0 CRs overwrite ts_start_of_current_line). + if (context->rollup_from_popon && context->ts_first_char_rollup_transition == -1) + context->ts_first_char_rollup_transition = get_fts(context->timing, context->my_field); context->ts_last_char_received = get_fts(context->timing, context->my_field); } } @@ -311,6 +318,17 @@ int write_cc_buffer(ccx_decoder_608_context *context, struct cc_subtitle *sub) context->ts_start_of_current_line != -1) context->current_visible_start_ms = context->ts_start_of_current_line; + // Pop-on -> roll-up transition that never saw a scrolling CR (e.g. EOF + // with fewer lines than the roll-up window). The CR handler never ran, + // so back-fill current_visible_start_ms from the first-char FTS instead + // of emitting a caption starting at 0. + if (context->rollup_from_popon && context->ts_first_char_rollup_transition > 0) + { + context->current_visible_start_ms = context->ts_first_char_rollup_transition; + context->rollup_from_popon = 0; + context->ts_first_char_rollup_transition = -1; + } + start_time = context->current_visible_start_ms; end_time = get_visible_end(context->timing, context->my_field); sub->type = CC_608; @@ -758,6 +776,7 @@ void handle_command(unsigned char c1, const unsigned char c2, ccx_decoder_608_co // Start time will be set when CR causes scrolling (matching FFmpeg behavior) context->rollup_from_popon = 1; context->ts_start_of_current_line = -1; + context->ts_first_char_rollup_transition = -1; } erase_memory(context, false); @@ -817,6 +836,7 @@ void handle_command(unsigned char c1, const unsigned char c2, ccx_decoder_608_co { context->current_visible_start_ms = context->ts_start_of_current_line; context->rollup_from_popon = 0; + context->ts_first_char_rollup_transition = -1; } // Only if the roll up would actually cause a line to disappear we write the buffer diff --git a/src/lib_ccx/ccx_decoders_608.h b/src/lib_ccx/ccx_decoders_608.h index a71b9fe3e..3a8430a9e 100644 --- a/src/lib_ccx/ccx_decoders_608.h +++ b/src/lib_ccx/ccx_decoders_608.h @@ -48,6 +48,7 @@ typedef struct ccx_decoder_608_context int my_field; // Used for sanity checks int my_channel; // Used for sanity checks int rollup_from_popon; // Track transition from pop-on/paint-on to roll-up mode + LLONG ts_first_char_rollup_transition; // FTS of first char after pop-on -> roll-up (EOF fallback when no scrolling CR fires). -1 if unset. int64_t bytes_processed_608; // To be written ONLY by process_608 int have_cursor_position;