diff --git a/docs/CHANGES.TXT b/docs/CHANGES.TXT
index 593f5bc0..4d114298 100644
--- a/docs/CHANGES.TXT
+++ b/docs/CHANGES.TXT
@@ -1,5 +1,6 @@
0.96.7 (unreleased)
-------------------
+- New: Allow output \0 terminated frames via --null-terminated
- New: Added ASS/SSA \pos-based positioning for CEA-608 captions when layout is simple (1–2 rows) (#1726)
- Fix: Remove strdup() memory leaks in WebVTT styling encoder, fix invalid CSS rgba(0,256,0) green value, fix missing free(unescaped) on write-error path (#2154)
- Fix: Prevent crash in Rust timing module when logging out-of-range PTS/FTS timestamps from malformed streams.
diff --git a/src/lib_ccx/ccx_common_option.c b/src/lib_ccx/ccx_common_option.c
index 0eda6ab0..07660945 100644
--- a/src/lib_ccx/ccx_common_option.c
+++ b/src/lib_ccx/ccx_common_option.c
@@ -134,6 +134,7 @@ void init_options(struct ccx_s_options *options)
options->enc_cfg.trim_subs = 0; // " Remove spaces at sides? "
options->enc_cfg.in_format = 1;
options->enc_cfg.line_terminator_lf = 0; // 0 = CRLF
+ options->enc_cfg.frame_terminator_0 = 0; // 0 = frames terminated by line_terminator_lf
options->enc_cfg.start_credits_text = NULL;
options->enc_cfg.end_credits_text = NULL;
options->enc_cfg.encoding = CCX_ENC_UTF_8;
diff --git a/src/lib_ccx/ccx_common_option.h b/src/lib_ccx/ccx_common_option.h
index 85e0f156..d81d1409 100644
--- a/src/lib_ccx/ccx_common_option.h
+++ b/src/lib_ccx/ccx_common_option.h
@@ -67,6 +67,7 @@ struct encoder_cfg
int no_type_setting;
int cc_to_stdout; // If this is set to 1, the stdout will be flushed when data was written to the screen during a process_608 call.
int line_terminator_lf; // 0 = CRLF, 1=LF
+ int frame_terminator_0; // 0 = frames terminated by line_terminator_lf, 1 = frames terminated by \0
LLONG subs_delay; // ms to delay (or advance) subs
int program_number;
unsigned char in_format;
diff --git a/src/lib_ccx/ccx_encoders_common.c b/src/lib_ccx/ccx_encoders_common.c
index 411526d4..8cb4b1f0 100644
--- a/src/lib_ccx/ccx_encoders_common.c
+++ b/src/lib_ccx/ccx_encoders_common.c
@@ -843,6 +843,17 @@ struct encoder_ctx *init_encoder(struct encoder_cfg *opt)
ctx->encoded_br_length = encode_line(ctx, ctx->encoded_br, (unsigned char *)"
");
+ if (opt->frame_terminator_0)
+ {
+ ctx->encoded_end_frame[0] = '\0';
+ ctx->encoded_end_frame_length = 1;
+ }
+ else
+ {
+ memcpy(ctx->encoded_end_frame, ctx->encoded_crlf, ctx->encoded_crlf_length + 1);
+ ctx->encoded_end_frame_length = ctx->encoded_crlf_length;
+ }
+
for (i = 0; i < ctx->nb_out; i++)
write_subtitle_file_header(ctx, ctx->out + i);
diff --git a/src/lib_ccx/ccx_encoders_common.h b/src/lib_ccx/ccx_encoders_common.h
index b58f2bb0..2165d23e 100644
--- a/src/lib_ccx/ccx_encoders_common.h
+++ b/src/lib_ccx/ccx_encoders_common.h
@@ -146,6 +146,8 @@ struct encoder_ctx
unsigned int encoded_crlf_length;
unsigned char encoded_br[16];
unsigned int encoded_br_length;
+ unsigned char encoded_end_frame[16];
+ unsigned int encoded_end_frame_length;
// MCC File
int header_printed_flag;
diff --git a/src/lib_ccx/ccx_encoders_transcript.c b/src/lib_ccx/ccx_encoders_transcript.c
index 81f1dc94..3e13652c 100644
--- a/src/lib_ccx/ccx_encoders_transcript.c
+++ b/src/lib_ccx/ccx_encoders_transcript.c
@@ -92,7 +92,7 @@ int write_cc_bitmap_as_transcript(struct cc_subtitle *sub, struct encoder_ctx *c
}
}
- write_wrapped(context->out->fh, context->encoded_crlf, context->encoded_crlf_length);
+ write_wrapped(context->out->fh, context->encoded_end_frame, context->encoded_end_frame_length);
}
}
#endif
@@ -134,6 +134,7 @@ int write_cc_subtitle_as_transcript(struct cc_subtitle *sub, struct encoder_ctx
str = sub->data;
+ int wrote_something = 0;
str = strtok_r(str, "\r\n", &save_str);
do
{
@@ -143,6 +144,15 @@ int write_cc_subtitle_as_transcript(struct cc_subtitle *sub, struct encoder_ctx
continue;
}
+ if (wrote_something)
+ {
+ ret = write(context->out->fh, context->encoded_crlf, context->encoded_crlf_length);
+ if (ret < context->encoded_crlf_length)
+ {
+ mprint("Warning:Loss of data\n");
+ }
+ }
+
if (context->transcript_settings->showStartTime)
{
char buf[80];
@@ -200,14 +210,16 @@ int write_cc_subtitle_as_transcript(struct cc_subtitle *sub, struct encoder_ctx
mprint("Warning:Loss of data\n");
}
- ret = write(context->out->fh, context->encoded_crlf, context->encoded_crlf_length);
- if (ret < context->encoded_crlf_length)
- {
- mprint("Warning:Loss of data\n");
- }
+ wrote_something = 1;
} while ((str = strtok_r(NULL, "\r\n", &save_str)));
+ ret = write(context->out->fh, context->encoded_end_frame, context->encoded_end_frame_length);
+ if (ret < context->encoded_end_frame_length)
+ {
+ mprint("Warning:Loss of data\n");
+ }
+
freep(&sub->data);
lsub = sub;
sub = sub->next;
@@ -321,18 +333,13 @@ void write_cc_line_as_transcript2(struct eia608_screen *data, struct encoder_ctx
{
mprint("Warning:Loss of data\n");
}
-
- ret = write(context->out->fh, context->encoded_crlf, context->encoded_crlf_length);
- if (ret < context->encoded_crlf_length)
- {
- mprint("Warning:Loss of data\n");
- }
}
// fprintf (wb->fh,encoded_crlf);
}
int write_cc_buffer_as_transcript2(struct eia608_screen *data, struct encoder_ctx *context)
{
+ int ret;
int wrote_something = 0;
dbg_print(CCX_DMT_DECODER_608, "\n- - - TRANSCRIPT caption - - -\n");
@@ -340,10 +347,29 @@ int write_cc_buffer_as_transcript2(struct eia608_screen *data, struct encoder_ct
{
if (data->row_used[i])
{
+ if (wrote_something)
+ {
+ ret = write(context->out->fh, context->encoded_crlf, context->encoded_crlf_length);
+ if (ret < context->encoded_crlf_length)
+ {
+ mprint("Warning:Loss of data\n");
+ }
+ }
+
write_cc_line_as_transcript2(data, context, i);
+ wrote_something = 1;
}
- wrote_something = 1;
}
+
+ if (wrote_something)
+ {
+ ret = write(context->out->fh, context->encoded_end_frame, context->encoded_end_frame_length);
+ if (ret < context->encoded_end_frame_length)
+ {
+ mprint("Warning:Loss of data\n");
+ }
+ }
+
dbg_print(CCX_DMT_DECODER_608, "- - - - - - - - - - - -\r\n");
return wrote_something;
}
diff --git a/src/lib_ccx/params.c b/src/lib_ccx/params.c
index f344cf16..e9399e7c 100644
--- a/src/lib_ccx/params.c
+++ b/src/lib_ccx/params.c
@@ -347,6 +347,9 @@ void print_usage(void)
mprint(" to the output file.\n");
mprint(" --lf: Use LF (UNIX) instead of CRLF (DOS, Windows) as line\n");
mprint(" terminator.\n");
+ mprint(" --null-terminated: Use \\0 instead of CRLF or LF for frame termination (see '--lf').\n");
+ mprint(" e.g use '--txt --stdout --null-terminated' when piping to\n");
+ mprint(" 'websocat -0' (https://github.com/vi/websocat)\n");
mprint(" --df: For MCC Files, force dropframe frame count.\n");
mprint(" --autodash: Based on position on screen, attempt to determine\n");
mprint(" the different speakers and a dash (-) when each\n");
diff --git a/src/rust/lib_ccxr/src/common/options.rs b/src/rust/lib_ccxr/src/common/options.rs
index 4a15e37c..48b3f021 100644
--- a/src/rust/lib_ccxr/src/common/options.rs
+++ b/src/rust/lib_ccxr/src/common/options.rs
@@ -248,6 +248,7 @@ impl Default for EncoderConfig {
no_type_setting: false,
cc_to_stdout: false,
line_terminator_lf: false,
+ frame_terminator_0: false,
subs_delay: Timestamp::default(),
program_number: 0,
in_format: 1,
@@ -324,6 +325,8 @@ pub struct EncoderConfig {
pub cc_to_stdout: bool,
/// false = CRLF, true = LF
pub line_terminator_lf: bool,
+ /// false = frames terminated by line_terminator_lf, true = frames terminated by \0
+ pub frame_terminator_0: bool,
/// ms to delay (or advance) subs
pub subs_delay: Timestamp,
pub program_number: u32,
diff --git a/src/rust/src/args.rs b/src/rust/src/args.rs
index ae1b8405..c221ed9f 100644
--- a/src/rust/src/args.rs
+++ b/src/rust/src/args.rs
@@ -565,6 +565,9 @@ pub struct Args {
/// terminator.
#[arg(long, verbatim_doc_comment, help_heading=OUTPUT_AFFECTING_OUTPUT_FILES)]
pub lf: bool,
+ /// Use \0 instead of CRLF or LF for frame termination (see '--lf')
+ #[arg(long, verbatim_doc_comment, help_heading=OUTPUT_AFFECTING_OUTPUT_FILES)]
+ pub null_terminated: bool,
/// For MCC Files, force dropframe frame count.
#[arg(long, verbatim_doc_comment, help_heading=OUTPUT_AFFECTING_OUTPUT_FILES)]
pub df: bool,
diff --git a/src/rust/src/common.rs b/src/rust/src/common.rs
index aa5df41f..9bb92c72 100755
--- a/src/rust/src/common.rs
+++ b/src/rust/src/common.rs
@@ -960,6 +960,7 @@ impl CType for EncoderConfig {
no_type_setting: self.no_type_setting as _,
cc_to_stdout: self.cc_to_stdout as _,
line_terminator_lf: self.line_terminator_lf as _,
+ frame_terminator_0: self.frame_terminator_0 as _,
subs_delay: self.subs_delay.millis(),
program_number: self.program_number as _,
in_format: self.in_format,
diff --git a/src/rust/src/ctorust.rs b/src/rust/src/ctorust.rs
index f240c502..2e075e0d 100755
--- a/src/rust/src/ctorust.rs
+++ b/src/rust/src/ctorust.rs
@@ -442,6 +442,7 @@ impl FromCType for EncoderConfig {
no_type_setting: cfg.no_type_setting != 0,
cc_to_stdout: cfg.cc_to_stdout != 0,
line_terminator_lf: cfg.line_terminator_lf != 0,
+ frame_terminator_0: cfg.frame_terminator_0 != 0,
subs_delay: Timestamp::from_millis(cfg.subs_delay),
program_number: cfg.program_number as u32,
in_format: cfg.in_format,
diff --git a/src/rust/src/decoder/output.rs b/src/rust/src/decoder/output.rs
index 7cc2b6a7..031f381a 100644
--- a/src/rust/src/decoder/output.rs
+++ b/src/rust/src/decoder/output.rs
@@ -14,7 +14,7 @@ use log::{debug, warn};
pub struct Writer<'a> {
pub cea_708_counter: &'a mut u32,
pub subs_delay: LLONG,
- pub crlf: String,
+ pub end_frame: Vec,
pub write_format: ccx_output_format,
pub writer_ctx: &'a mut dtvcc_writer_ctx,
pub no_font_color: bool,
@@ -25,6 +25,7 @@ pub struct Writer<'a> {
impl<'a> Writer<'a> {
/// Create a new writer context
+ #[allow(clippy::too_many_arguments)]
pub fn new(
cea_708_counter: &'a mut u32,
subs_delay: LLONG,
@@ -33,11 +34,12 @@ impl<'a> Writer<'a> {
no_font_color: i32,
transcript_settings: &'a ccx_encoders_transcript_format,
no_bom: i32,
+ encoded_end_frame: &[u8],
) -> Self {
Self {
cea_708_counter,
subs_delay,
- crlf: "\r\n".to_owned(),
+ end_frame: encoded_end_frame.to_vec(),
write_format,
writer_ctx,
no_font_color: is_true(no_font_color),
diff --git a/src/rust/src/decoder/service_decoder.rs b/src/rust/src/decoder/service_decoder.rs
index a4b04fdc..f57ab2c2 100644
--- a/src/rust/src/decoder/service_decoder.rs
+++ b/src/rust/src/decoder/service_decoder.rs
@@ -873,6 +873,7 @@ impl dtvcc_service_decoder {
} else {
&ccx_encoders_transcript_format::default()
};
+ let end_frame = &encoder.encoded_end_frame[..encoder.encoded_end_frame_length as usize];
let mut writer = Writer::new(
&mut encoder.cea_708_counter,
encoder.subs_delay,
@@ -881,6 +882,7 @@ impl dtvcc_service_decoder {
encoder.no_font_color,
transcript_settings,
encoder.no_bom,
+ end_frame,
);
tv.writer_output(&mut writer).unwrap();
tv.clear();
@@ -1202,6 +1204,7 @@ impl dtvcc_service_decoder {
let sn = tv.service_number;
let writer_ctx = &mut encoder.dtvcc_writers[(sn - 1) as usize];
+ let end_frame = &encoder.encoded_end_frame[..encoder.encoded_end_frame_length as usize];
let mut writer = Writer::new(
&mut encoder.cea_708_counter,
encoder.subs_delay,
@@ -1210,6 +1213,7 @@ impl dtvcc_service_decoder {
encoder.no_font_color,
transcript_settings,
encoder.no_bom,
+ end_frame,
);
writer.write_done();
}
diff --git a/src/rust/src/decoder/tv_screen.rs b/src/rust/src/decoder/tv_screen.rs
index 7ee5ec53..df0e2e41 100644
--- a/src/rust/src/decoder/tv_screen.rs
+++ b/src/rust/src/decoder/tv_screen.rs
@@ -330,8 +330,13 @@ impl dtvcc_tv_screen {
let time_show = get_time_str(self.time_ms_show);
let time_hide = get_time_str(self.time_ms_hide);
+ let mut wrote_something = false;
for row_index in 0..CCX_DTVCC_SCREENGRID_ROWS as usize {
if !self.is_row_empty(row_index) {
+ if wrote_something {
+ writer.write_to_file(b"\r\n")?;
+ }
+
let mut buf = String::new();
if is_true(writer.transcript_settings.showStartTime) {
@@ -350,9 +355,11 @@ impl dtvcc_tv_screen {
}
writer.write_to_file(buf.as_bytes())?;
self.write_row(writer, row_index, false)?;
- writer.write_to_file(b"\r\n")?;
+ wrote_something = true;
}
}
+ let end_frame = writer.end_frame.clone();
+ writer.write_to_file(&end_frame)?;
Ok(())
}
@@ -800,6 +807,7 @@ mod test {
0,
&transcript_settings,
0,
+ b"\r\n",
);
// This should succeed without error (fd is valid, not -1)
@@ -835,6 +843,7 @@ mod test {
0,
&transcript_settings,
0,
+ b"\r\n",
);
// This should return an error, not panic
diff --git a/src/rust/src/parser.rs b/src/rust/src/parser.rs
index 086a8d34..49fa7313 100644
--- a/src/rust/src/parser.rs
+++ b/src/rust/src/parser.rs
@@ -1257,6 +1257,10 @@ impl OptionsExt for Options {
self.enc_cfg.line_terminator_lf = true;
}
+ if args.null_terminated {
+ self.enc_cfg.frame_terminator_0 = true;
+ }
+
if args.df {
self.enc_cfg.force_dropframe = true;
}