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 @@ -2,6 +2,7 @@
-------------------
- 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)
- New: Auto-extract multi-language DVB subtitles into per-language files.(#2243)
- 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.
- Fix: Resolve Windows MSVC debug build crash caused by cross-CRT invalid free on Rust-allocated output_filename (#2126)
Expand Down
4 changes: 2 additions & 2 deletions src/lib_ccx/ccx_demuxer.c
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,9 @@ struct ccx_demuxer *init_demuxer(void *parent, struct demuxer_cfg *cfg)
for (i = 0; i < cfg->nb_ts_cappid; i++)
{
if (ctx->codec == CCX_CODEC_ANY)
update_capinfo(ctx, cfg->ts_cappids[i], cfg->ts_datastreamtype, CCX_CODEC_NONE, 0, NULL);
update_capinfo(ctx, cfg->ts_cappids[i], cfg->ts_datastreamtype, CCX_CODEC_NONE, 0, NULL, NULL);
else
update_capinfo(ctx, cfg->ts_cappids[i], cfg->ts_datastreamtype, ctx->codec, 0, NULL);
update_capinfo(ctx, cfg->ts_cappids[i], cfg->ts_datastreamtype, ctx->codec, 0, NULL, NULL);
}

ctx->flag_ts_forced_cappid = cfg->nb_ts_cappid ? CCX_TRUE : CCX_FALSE;
Expand Down
9 changes: 6 additions & 3 deletions src/lib_ccx/ccx_demuxer.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct program_info
int16_t pcr_pid;
uint64_t got_important_streams_min_pts[COUNT];
int has_all_min_pts;
char virtual_channel[16]; // Stores ATSC virtual channel like "2.1"
char virtual_channel[16]; // Stores ATSC virtual channel like "2.1"
};

struct cap_info
Expand All @@ -63,6 +63,7 @@ struct cap_info
int prev_counter;
void *codec_private_data;
int ignore;
char lang[4]; /* ISO-639 language code (DVB subtitle) */

/**
List joining all stream in TS
Expand Down Expand Up @@ -162,7 +163,8 @@ struct ccx_demuxer
int (*open)(struct ccx_demuxer *ctx, const char *file_name);
int (*is_open)(struct ccx_demuxer *ctx);
int (*get_stream_mode)(struct ccx_demuxer *ctx);
LLONG (*get_filesize)(struct ccx_demuxer *ctx);
LLONG(*get_filesize)
(struct ccx_demuxer *ctx);
};

struct demuxer_data
Expand All @@ -185,7 +187,7 @@ struct ccx_demuxer *init_demuxer(void *parent, struct demuxer_cfg *cfg);
void ccx_demuxer_delete(struct ccx_demuxer **ctx);
struct demuxer_data *alloc_demuxer_data(void);
void delete_demuxer_data(struct demuxer_data *data);
int update_capinfo(struct ccx_demuxer *ctx, int pid, enum ccx_stream_type stream, enum ccx_code_type codec, int pn, void *private_data);
int update_capinfo(struct ccx_demuxer *ctx, int pid, enum ccx_stream_type stream, enum ccx_code_type codec, int pn, void *private_data, const char *lang);
struct cap_info *get_cinfo(struct ccx_demuxer *ctx, int pid);
int need_cap_info(struct ccx_demuxer *ctx, int program_number);
int need_cap_info_for_pid(struct ccx_demuxer *ctx, int pid);
Expand All @@ -194,6 +196,7 @@ struct demuxer_data *get_data_stream(struct demuxer_data *data, int pid);
int get_best_stream(struct ccx_demuxer *ctx);
void ignore_other_stream(struct ccx_demuxer *ctx, int pid);
void dinit_cap(struct ccx_demuxer *ctx);
int copy_capbuf_demux_data(struct ccx_demuxer *ctx, struct demuxer_data **data, struct cap_info *cinfo);
int get_programme_number(struct ccx_demuxer *ctx, int pid);
struct cap_info *get_best_sib_stream(struct cap_info *program);
void ignore_other_sib_stream(struct cap_info *head, int pid);
Expand Down
12 changes: 11 additions & 1 deletion src/lib_ccx/ccx_encoders_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,17 @@ struct encoder_ctx *init_encoder(struct encoder_cfg *opt)
ctx->is_mkv = 0;
ctx->last_string = NULL;

ctx->transcript_settings = &opt->transcript_settings;
/* Deep-copy transcript_settings so the encoder owns it independently of
the caller's encoder_cfg (which may be a stack-local variable). */
ctx->transcript_settings = malloc(sizeof(struct ccx_encoders_transcript_format));
if (!ctx->transcript_settings)
{
freep(&ctx->out);
freep(&ctx->buffer);
free(ctx);
return NULL;
}
memcpy(ctx->transcript_settings, &opt->transcript_settings, sizeof(struct ccx_encoders_transcript_format));
ctx->no_bom = opt->no_bom;
ctx->sentence_cap = opt->sentence_cap;
ctx->filter_profanity = opt->filter_profanity;
Expand Down
1 change: 1 addition & 0 deletions src/lib_ccx/ccx_encoders_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ struct encoder_ctx
int new_sentence; // Capitalize next letter?

int program_number;
char dvb_lang[4]; /* ISO-639 language code for DVB subtitle encoder */
struct list_head list;

/* split-by-sentence stuff */
Expand Down
56 changes: 56 additions & 0 deletions src/lib_ccx/general_loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,62 @@ int process_non_multiprogram_general_loop(struct lib_ccx_ctx *ctx,
}
}
}

/* -----------------------------------------------------------------------
* Process additional DVB subtitle PIDs (extra languages).
* ----------------------------------------------------------------------- */
{
struct cap_info *dvb_iter;
list_for_each_entry(dvb_iter, &ctx->demux_ctx->cinfo_tree.all_stream, all_stream, struct cap_info)
{
if (dvb_iter->codec != CCX_CODEC_DVB)
continue;
if (dvb_iter->pid == pid)
continue;

struct demuxer_data *dvb_data = get_data_stream(*datalist, dvb_iter->pid);
if (!dvb_data || dvb_data->len == 0)
continue;

struct encoder_ctx *dvb_enc = update_encoder_list_cinfo(ctx, dvb_iter);
struct lib_cc_decode *dvb_dec = update_decoder_list_cinfo(ctx, dvb_iter);

if (!dvb_enc || !dvb_dec || !dvb_dec->timing)
continue;

if (dvb_data->pts != CCX_NOPTS)
{
struct ccx_rational tb = {1, MPEG_CLOCK_FREQ};
LLONG pts = (dvb_data->tb.num != 1 || dvb_data->tb.den != MPEG_CLOCK_FREQ)
? change_timebase(dvb_data->pts, dvb_data->tb, tb)
: dvb_data->pts;
set_current_pts(dvb_dec->timing, pts);
if (dvb_dec->timing->min_pts == 0x01FFFFFFFFLL)
{
dvb_dec->timing->min_pts = pts;
dvb_dec->timing->pts_set = 2;
dvb_dec->timing->sync_pts = pts;
}
set_fts(dvb_dec->timing);
}
dvb_enc->timing = dvb_dec->timing;

int dvb_ret = process_data(dvb_enc, dvb_dec, dvb_data);
if (dvb_ret || dvb_enc->srt_counter)
*caps = 1;

if (!(!terminate_asap && !end_of_file && is_decoder_processed_enough(ctx) == CCX_FALSE))
{
if (dvb_dec->dec_sub.prev && dvb_dec->dec_sub.prev->end_time == 0)
{
dvb_dec->dec_sub.prev->end_time = get_fts(dvb_dec->timing, dvb_dec->current_field);
if (dvb_enc != NULL && dvb_enc->prev != NULL)
encode_sub(dvb_enc->prev, dvb_dec->dec_sub.prev);
dvb_dec->dec_sub.prev->got_output = 0;
}
}
}
}
return ret;
}

Expand Down
107 changes: 99 additions & 8 deletions src/lib_ccx/lib_ccx.c
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,8 @@ void dinit_libraries(struct lib_ccx_ctx **ctx)
struct encoder_ctx *enc_ctx;
struct lib_cc_decode *dec_ctx;
struct lib_cc_decode *dec_ctx1;
int i;
list_for_each_entry_safe(dec_ctx, dec_ctx1, &lctx->dec_ctx_head, list, struct lib_cc_decode)
{
LLONG cfts;
void *saved_private_data = dec_ctx->private_data; // Save before close NULLs it
if (dec_ctx->codec == CCX_CODEC_DVB)
dvbsub_close_decoder(&dec_ctx->private_data);
Expand All @@ -248,7 +246,6 @@ void dinit_libraries(struct lib_ccx_ctx **ctx)
}

flush_cc_decode(dec_ctx, &dec_ctx->dec_sub);
cfts = get_fts(dec_ctx->timing, dec_ctx->current_field);
enc_ctx = get_encoder_by_pn(lctx, dec_ctx->program_number);
if (enc_ctx && dec_ctx->dec_sub.got_output == CCX_TRUE)
{
Expand All @@ -257,8 +254,16 @@ void dinit_libraries(struct lib_ccx_ctx **ctx)
}
list_del(&dec_ctx->list);
dinit_cc_decode(&dec_ctx);
if (enc_ctx)
}
/* Clean up all encoders separately.
With multi-DVB, multiple decoders share the same program_number
but have different language-specific encoders. Cleaning encoders
inside the decoder loop caused wrong encoder pairing and double-free. */
{
struct encoder_ctx *enc_tmp;
list_for_each_entry_safe(enc_ctx, enc_tmp, &lctx->enc_ctx_head, list, struct encoder_ctx)
{
LLONG cfts = 0; // Use 0 as fallback since decoders are already freed
list_del(&enc_ctx->list);
dinit_encoder(&enc_ctx, cfts);
}
Expand Down Expand Up @@ -337,6 +342,7 @@ struct lib_cc_decode *update_decoder_list(struct lib_ccx_ctx *ctx)
dec_ctx->dec_sub.prev = malloc(sizeof(struct cc_subtitle));
if (!dec_ctx->prev || !dec_ctx->dec_sub.prev)
ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "update_decoder_list: Not enough memory for DVB context");
memset(dec_ctx->prev, 0, sizeof(struct lib_cc_decode));
memset(dec_ctx->dec_sub.prev, 0, sizeof(struct cc_subtitle));
}
}
Expand All @@ -351,6 +357,14 @@ struct lib_cc_decode *update_decoder_list_cinfo(struct lib_ccx_ctx *ctx, struct
{
if (!cinfo || ctx->multiprogram == CCX_FALSE)
{
/* For DVB subtitles, match by private_data (per-PID decoder) */
if (cinfo && cinfo->codec == CCX_CODEC_DVB)
{
if (dec_ctx->program_number == cinfo->program_number &&
dec_ctx->private_data == cinfo->codec_private_data)
return dec_ctx;
continue;
}
/* Update private_data from cinfo if available.
This is needed after PAT changes when dinit_cap() freed the old context
and a new cap_info was created with a new codec_private_data. */
Expand All @@ -375,7 +389,16 @@ struct lib_cc_decode *update_decoder_list_cinfo(struct lib_ccx_ctx *ctx, struct
}
if (ctx->multiprogram == CCX_FALSE)
{
if (list_empty(&ctx->dec_ctx_head))
/* For DVB, always create a new per-PID decoder even in single-program mode,
because each DVB subtitle PID has its own codec_private_data context. */
if (cinfo && cinfo->codec == CCX_CODEC_DVB)
{
dec_ctx = init_cc_decode(ctx->dec_global_setting);
if (!dec_ctx)
fatal(EXIT_NOT_ENOUGH_MEMORY, "In update_decoder_list_cinfo: Not enough memory allocating dec_ctx for DVB\n");
list_add_tail(&(dec_ctx->list), &(ctx->dec_ctx_head));
}
else if (list_empty(&ctx->dec_ctx_head))
{
dec_ctx = init_cc_decode(ctx->dec_global_setting);
if (!dec_ctx)
Expand All @@ -394,6 +417,15 @@ struct lib_cc_decode *update_decoder_list_cinfo(struct lib_ccx_ctx *ctx, struct
// DVB related
dec_ctx->prev = NULL;
dec_ctx->dec_sub.prev = NULL;
if (cinfo && cinfo->codec == CCX_CODEC_DVB)
{
dec_ctx->prev = malloc(sizeof(struct lib_cc_decode));
dec_ctx->dec_sub.prev = malloc(sizeof(struct cc_subtitle));
if (!dec_ctx->prev || !dec_ctx->dec_sub.prev)
ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "update_decoder_list_cinfo: Not enough memory for DVB context");
memset(dec_ctx->prev, 0, sizeof(struct lib_cc_decode));
memset(dec_ctx->dec_sub.prev, 0, sizeof(struct cc_subtitle));
}

return dec_ctx;
}
Expand All @@ -420,7 +452,18 @@ struct encoder_ctx *update_encoder_list_cinfo(struct lib_ccx_ctx *ctx, struct ca
list_for_each_entry(enc_ctx, &ctx->enc_ctx_head, list, struct encoder_ctx)
{
if (ctx->multiprogram == CCX_FALSE)
{
/* For DVB subtitles, match by language — skip non-DVB encoders */
if (cinfo && cinfo->codec == CCX_CODEC_DVB && cinfo->lang[0])
{
if (enc_ctx->dvb_lang[0] &&
enc_ctx->program_number == pn &&
strcmp(enc_ctx->dvb_lang, cinfo->lang) == 0)
return enc_ctx;
continue;
}
return enc_ctx;
}

if (enc_ctx->program_number == pn)
return enc_ctx;
Expand All @@ -430,6 +473,47 @@ struct encoder_ctx *update_encoder_list_cinfo(struct lib_ccx_ctx *ctx, struct ca
if (!extension && ccx_options.enc_cfg.write_format != CCX_OF_CURL)
return NULL;

/* Create per-language DVB encoder only when there are 2+ DVB subtitle PIDs.
Single-DVB-stream recordings fall through to the standard encoder path
to preserve backward compatible filenames. */
if (cinfo && cinfo->codec == CCX_CODEC_DVB && cinfo->lang[0])
{
/* Count DVB subtitle PIDs in this program */
int dvb_pid_count = 0;
struct cap_info *ci;
list_for_each_entry(ci, &ctx->demux_ctx->cinfo_tree.all_stream, all_stream, struct cap_info)
{
if (ci->codec == CCX_CODEC_DVB && ci->program_number == cinfo->program_number)
dvb_pid_count++;
}

if (dvb_pid_count >= 2)
{
struct encoder_cfg local_cfg = ccx_options.enc_cfg;
local_cfg.program_number = pn;
local_cfg.in_format = in_format;
char *basefilename = get_basename(ctx->basefilename);
char suffix[8];
snprintf(suffix, sizeof(suffix), "_%s", cinfo->lang);
local_cfg.output_filename = create_outfilename(basefilename, suffix, extension);
free(basefilename);

enc_ctx = init_encoder(&local_cfg);
if (enc_ctx)
{
enc_ctx->program_number = pn;
strncpy(enc_ctx->dvb_lang, cinfo->lang, 3);
enc_ctx->dvb_lang[3] = '\0';
list_add_tail(&enc_ctx->list, &ctx->enc_ctx_head);
enc_ctx->prev = NULL;
enc_ctx->write_previous = 0;
}
free(local_cfg.output_filename);
return enc_ctx;
}
/* Single DVB stream: fall through to standard encoder creation */
}

if (ctx->multiprogram == CCX_FALSE)
{
if (ctx->out_interval != -1)
Expand Down Expand Up @@ -492,9 +576,16 @@ struct encoder_ctx *update_encoder_list_cinfo(struct lib_ccx_ctx *ctx, struct ca
}
// DVB related
enc_ctx->prev = NULL;
if (cinfo)
if (cinfo->codec == CCX_CODEC_DVB)
enc_ctx->write_previous = 0;
if (cinfo && cinfo->codec == CCX_CODEC_DVB && cinfo->lang[0])
{
strncpy(enc_ctx->dvb_lang, cinfo->lang, 3);
enc_ctx->dvb_lang[3] = '\0';
enc_ctx->write_previous = 0;
}
else
{
memset(enc_ctx->dvb_lang, 0, sizeof(enc_ctx->dvb_lang));
}
return enc_ctx;
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib_ccx/ts_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ void look_for_caption_data(struct ccx_demuxer *ctx, struct ts_payload *payload)
stream_type == CCX_STREAM_TYPE_VIDEO_H264 ? "H.264" : (stream_type == CCX_STREAM_TYPE_VIDEO_HEVC ? "HEVC" : "MPEG-2"));

// Register this PID as a video stream that may contain captions
update_capinfo(ctx, payload->pid, stream_type, CCX_CODEC_ATSC_CC, 0, NULL);
update_capinfo(ctx, payload->pid, stream_type, CCX_CODEC_ATSC_CC, 0, NULL, NULL);
ctx->PIDs_seen[payload->pid] = 3;
return;
}
Expand All @@ -434,7 +434,7 @@ void look_for_caption_data(struct ccx_demuxer *ctx, struct ts_payload *payload)
if (cinfo == NULL)
{
mprint("Registering PID %u as MPEG-2 video with captions (no PAT/PMT detected).\n", payload->pid);
update_capinfo(ctx, payload->pid, CCX_STREAM_TYPE_VIDEO_MPEG2, CCX_CODEC_ATSC_CC, 0, NULL);
update_capinfo(ctx, payload->pid, CCX_STREAM_TYPE_VIDEO_MPEG2, CCX_CODEC_ATSC_CC, 0, NULL, NULL);
}

ctx->PIDs_seen[payload->pid] = 3;
Expand Down
Loading
Loading