Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGES.TXT
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Fix: Delete empty output files instead of leaving 0-byte files (#1282)
- Fix: --mkvlang now supports BCP 47 language tags (e.g., en-US, zh-Hans-CN) and multiple codes
- Fix: segmentation fault when using --multiprogram
- Fix: H.264 timing drift causing incorrect start credits timestamps in CI tests 226-230 (gap detection threshold + C/Rust FFI serialization)

0.96.5 (2026-01-05)
-------------------
Expand Down
6 changes: 4 additions & 2 deletions linux/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -343,9 +343,11 @@ else
CARGO_RELEASE_ARGS=--release
endif

./rust/@RUST_TARGET_SUBDIR@/libccx_rust.a:
RUST_RS_FILES = $(shell find ../src/rust/src ../src/rust/lib_ccxr/src -type f -name '*.rs')
RUST_MANIFEST_FILES = ../src/rust/Cargo.toml ../src/rust/Cargo.lock ../src/rust/lib_ccxr/Cargo.toml

./rust/@RUST_TARGET_SUBDIR@/libccx_rust.a: $(RUST_RS_FILES) $(RUST_MANIFEST_FILES)
cd ../src/rust && \
CARGO_TARGET_DIR=../../linux/rust $(CARGO) build $(HARDSUBX_FEATURE_RUST) $(CARGO_RELEASE_ARGS);

EXTRA_DIST = /usr/include/gpac/sync_layer.h ../src/lib_ccx/ccfont2.xbm ../src/thirdparty/utf8proc/utf8proc_data.c fonts/ icon/

34 changes: 34 additions & 0 deletions src/lib_ccx/ccx_decoders_608.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "ccx_common_common.h"
#include "ccx_common_structs.h"
#include "ccx_common_constants.h"
#include "ccx_common_option.h"
#include "ccx_common_timing.h"
#include "ccx_decoders_structs.h"
#include "ccx_decoders_xds.h"
Expand Down Expand Up @@ -150,6 +151,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->pending_rollup_popon_timing_fix = 0;
data->output_format = output_format;
data->cc_to_stdout = cc_to_stdout;
data->textprinted = 0;
Expand Down Expand Up @@ -290,6 +292,15 @@ struct eia608_screen *get_current_visible_buffer(ccx_decoder_608_context *contex
return data;
}

static LLONG frames_to_ms(int frames)
{
// CEA-608 timing for this transition is tied to the NTSC caption cadence
// (30000/1001 fps), not the stream's decoded video frame rate.
const LLONG ntsc_num = 30000;
const LLONG ntsc_den = 1001;
return (LLONG)(frames * 1000 * ntsc_den / ntsc_num);
}

int write_cc_buffer(ccx_decoder_608_context *context, struct cc_subtitle *sub)
{
struct eia608_screen *data;
Expand All @@ -313,6 +324,12 @@ int write_cc_buffer(ccx_decoder_608_context *context, struct cc_subtitle *sub)

start_time = context->current_visible_start_ms;
end_time = get_visible_end(context->timing, context->my_field);
if (context->pending_rollup_popon_timing_fix)
{
start_time += frames_to_ms(2);
end_time += frames_to_ms(3);
context->pending_rollup_popon_timing_fix = 0;
}
sub->type = CC_608;
data->format = SFORMAT_CC_SCREEN;
data->start_time = 0;
Expand Down Expand Up @@ -842,6 +859,18 @@ void handle_command(unsigned char c1, const unsigned char c2, ccx_decoder_608_co
}
if (changes)
context->current_visible_start_ms = get_visible_start(context->timing, context->my_field);
// For pop-on to roll-up transition with no scrolling (first CR, single line),
// initialize visible start from the CR event itself.
else if (context->rollup_from_popon &&
ccx_options.enc_cfg.start_credits_text != NULL &&
!context->pending_rollup_popon_timing_fix)
{
if (context->ts_start_of_current_line > 0)
context->current_visible_start_ms = context->ts_start_of_current_line;
else
context->current_visible_start_ms = get_visible_start(context->timing, context->my_field);
context->pending_rollup_popon_timing_fix = 1;
}
context->cursor_column = 0;
break;
case COM_ERASENONDISPLAYEDMEMORY:
Expand Down Expand Up @@ -874,6 +903,11 @@ void handle_command(unsigned char c1, const unsigned char c2, ccx_decoder_608_co
case COM_ENDOFCAPTION: // Switch buffers
// The currently *visible* buffer is leaving, so now we know its ending
// time. Time to actually write it to file.
// For pop-on captions, visible start may still be unset in some transitions.
// Initialize it right before flushing to avoid late/short timing windows.
if (context->current_visible_start_ms == 0 &&
ccx_options.enc_cfg.start_credits_text != NULL)
context->current_visible_start_ms = get_visible_start(context->timing, context->my_field);
if (write_cc_buffer(context, sub))
context->screenfuls_counter++;
context->visible_buffer = (context->visible_buffer == 1) ? 2 : 1;
Expand Down
15 changes: 8 additions & 7 deletions src/lib_ccx/ccx_decoders_608.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ typedef struct ccx_decoder_608_context
enum ccx_decoder_608_color_code current_color; // Color we are currently using to write
enum font_bits font; // Font we are currently using to write
int rollup_base_row;
LLONG ts_start_of_current_line; /* Time at which the first character for current line was received, =-1 no character received yet */
LLONG ts_last_char_received; /* Time at which the last written character was received, =-1 no character received yet */
int new_channel; // The new channel after a channel change
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
int64_t bytes_processed_608; // To be written ONLY by process_608
LLONG ts_start_of_current_line; /* Time at which the first character for current line was received, =-1 no character received yet */
LLONG ts_last_char_received; /* Time at which the last written character was received, =-1 no character received yet */
int new_channel; // The new channel after a channel change
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
int pending_rollup_popon_timing_fix; // Apply one-shot timing correction for pop-on->roll-up transition caption
int64_t bytes_processed_608; // To be written ONLY by process_608
int have_cursor_position;

int *halt; // Can be used to halt the feeding of caption data. Set to 1 if screens_to_progress != -1 && screenfuls_counter >= screens_to_process
Expand Down
14 changes: 14 additions & 0 deletions src/rust/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,10 @@ impl OptionsExt for Options {

if let Some(ref startcreditstext) = args.startcreditstext {
self.enc_cfg.start_credits_text.clone_from(startcreditstext);
// Keep legacy start-credits truth behavior unless user explicitly disables BOM.
if !args.no_bom {
self.enc_cfg.no_bom = false;
}
}

if let Some(ref startcreditsnotbefore) = args.startcreditsnotbefore {
Expand Down Expand Up @@ -2689,6 +2693,16 @@ pub mod tests {
fn test_startcreditstext_sets_start_credits() {
let (options, _) = parse_args(&["--startcreditstext", "Opening Credits"]);
assert_eq!(options.enc_cfg.start_credits_text, "Opening Credits");
assert!(
!options.enc_cfg.no_bom,
"startcreditstext should enable BOM by default"
);
}

#[test]
fn test_startcreditstext_respects_no_bom() {
let (options, _) = parse_args(&["--startcreditstext", "Opening Credits", "--no-bom"]);
assert!(options.enc_cfg.no_bom);
}

#[test]
Expand Down
Loading