mirror of
https://github.com/CCExtractor/ccextractor.git
synced 2026-04-05 21:51:23 +00:00
[FEATURE] Allow output \0 terminated frames (for WebSocket streaming support) (#2105)
* [feat] Allow output \0 terminated frames * Fix rust `FromCType` * use encoded_end_frame for text-based captions * add changelog entry * fix CEA-708 Rust decoder * fix Rust formating * remove unused `crlf` field - satisfy clippy function argument limit * silence clippy function argument limit in `Writer` * Fix writing frame end with multiline captions * fix formatting errors
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 *)"<br>");
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -960,6 +960,7 @@ impl CType<encoder_cfg> 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,
|
||||
|
||||
@@ -442,6 +442,7 @@ impl FromCType<encoder_cfg> 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,
|
||||
|
||||
@@ -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<u8>,
|
||||
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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user