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;