mirror of
https://github.com/CCExtractor/ccextractor.git
synced 2026-02-04 05:44:53 +00:00
Fix #447: Resolve DVB split mode crash and routing logic
- Fixed NULL pointer dereference in dvb_subtitle_decoder.c (sub->prev check). - Corrected logic in dvbsub_handle_display_segment to prevent dropped subtitles. - Implemented robust encoder context swapping in general_loop.c for DVB streams. - Added regression test: tests/regression/dvb_split.txt. - Verified 100% completion in split mode and correct Teletext/DVB routing.
This commit is contained in:
@@ -47,6 +47,8 @@ struct ccx_stream_metadata
|
||||
int stream_type; // Logical type (CCX_STREAM_TYPE_*)
|
||||
int mpeg_type; // Raw MPEG stream type from PMT (e.g., 0x06)
|
||||
char lang[4]; // ISO 639-2/B three-letter language code
|
||||
int composition_id;
|
||||
int ancillary_id;
|
||||
};
|
||||
|
||||
struct program_info
|
||||
|
||||
@@ -1733,6 +1733,22 @@ void dvbsub_handle_display_segment(struct encoder_ctx *enc_ctx,
|
||||
enc_ctx->prev->last_string = NULL; // Reset last recognized sub text
|
||||
// Get the current FTS, which will be the start_time of the new subtitle
|
||||
LLONG next_start_time = get_fts(dec_ctx->timing, dec_ctx->current_field);
|
||||
|
||||
if (!sub->prev)
|
||||
{
|
||||
// Previous subtitle is missing or invalid, skipping write_previous
|
||||
enc_ctx->write_previous = 0;
|
||||
if (enc_ctx->prev)
|
||||
{
|
||||
free_encoder_context(enc_ctx->prev);
|
||||
enc_ctx->prev = NULL;
|
||||
enc_ctx->prev = copy_encoder_context(enc_ctx);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// For DVB subtitles, a subtitle is displayed until the next one appears.
|
||||
// For DVB subtitles, a subtitle is displayed until the next one appears.
|
||||
// Use next_start_time as the end_time to ensure subtitle N ends when N+1 starts.
|
||||
// This prevents any overlap between consecutive subtitles.
|
||||
@@ -1787,6 +1803,8 @@ void dvbsub_handle_display_segment(struct encoder_ctx *enc_ctx,
|
||||
{
|
||||
encode_sub(enc_ctx->prev, sub->prev); // we encode it
|
||||
|
||||
|
||||
|
||||
enc_ctx->last_string = enc_ctx->prev->last_string; // Update last recognized string (used in Matroska)
|
||||
enc_ctx->prev->last_string = NULL;
|
||||
|
||||
@@ -1799,8 +1817,12 @@ void dvbsub_handle_display_segment(struct encoder_ctx *enc_ctx,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* copy previous encoder context*/
|
||||
|
||||
free_encoder_context(enc_ctx->prev);
|
||||
|
||||
|
||||
enc_ctx->prev = NULL;
|
||||
enc_ctx->prev = copy_encoder_context(enc_ctx);
|
||||
|
||||
@@ -1828,6 +1850,10 @@ void dvbsub_handle_display_segment(struct encoder_ctx *enc_ctx,
|
||||
|
||||
write_dvb_sub(dec_ctx->prev, sub->prev); // we write the current dvb sub to update decoder context
|
||||
enc_ctx->write_previous = 1; // we update our boolean value so next time the program reaches this block of code, it encodes the previous sub
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef ENABLE_OCR
|
||||
if (sub->prev)
|
||||
{
|
||||
@@ -1903,6 +1929,8 @@ int dvbsub_decode(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, co
|
||||
dbg_print(CCX_DMT_DVB, "FTS: %d, ", dec_ctx->timing->fts_now);
|
||||
dbg_print(CCX_DMT_DVB, "SEGMENT TYPE: %2X, ", segment_type);
|
||||
|
||||
|
||||
|
||||
switch (segment_type)
|
||||
{
|
||||
case DVBSUB_PAGE_SEGMENT:
|
||||
@@ -1937,6 +1965,8 @@ int dvbsub_decode(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, co
|
||||
case DVBSUB_DISPLAY_SEGMENT: // when we get a display segment, we save the current page
|
||||
dbg_print(CCX_DMT_DVB, "(DVBSUB_DISPLAY_SEGMENT), SEGMENT LENGTH: %d", segment_length);
|
||||
dvbsub_handle_display_segment(enc_ctx, dec_ctx, sub, pre_fts_max);
|
||||
|
||||
|
||||
got_segment |= 16;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -982,10 +982,19 @@ int process_non_multiprogram_general_loop(struct lib_ccx_ctx *ctx,
|
||||
// struct encoder_ctx *enc_ctx = NULL;
|
||||
// Find most promising stream: teletex, DVB, ISDB
|
||||
int pid = get_best_stream(ctx->demux_ctx);
|
||||
|
||||
// NOTE: For DVB split mode, we do NOT mutate pid here.
|
||||
// Mutating pid to -1 causes demuxer PES buffer errors because it changes
|
||||
// the stream selection semantics unexpectedly. Instead, we keep primary
|
||||
// stream processing unchanged and handle DVB streams in a separate
|
||||
// read-only secondary pass after the primary stream is processed.
|
||||
|
||||
if (pid < 0)
|
||||
{
|
||||
// Let get_best_data pick the primary stream (usually Teletext)
|
||||
*data_node = get_best_data(*datalist);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
ignore_other_stream(ctx->demux_ctx, pid);
|
||||
@@ -1126,19 +1135,89 @@ int process_non_multiprogram_general_loop(struct lib_ccx_ctx *ctx,
|
||||
}
|
||||
if ((*data_node)->bufferdatatype == CCX_TELETEXT && (*dec_ctx)->private_data) // if we have teletext subs, we set the min_pts here
|
||||
set_tlt_delta(*dec_ctx, (*dec_ctx)->timing->current_pts);
|
||||
|
||||
// Primary stream processing (Teletext or whatever get_best_data selected)
|
||||
ret = process_data(*enc_ctx, *dec_ctx, *data_node);
|
||||
|
||||
if (*enc_ctx != NULL)
|
||||
{
|
||||
if ((*enc_ctx)->srt_counter || (*enc_ctx)->cea_708_counter || (*dec_ctx)->saw_caption_block || ret == 1)
|
||||
{
|
||||
*caps = 1;
|
||||
/* Also update ret to indicate captions were found.
|
||||
This is needed for CEA-708 which writes directly via Rust
|
||||
and doesn't set got_output like CEA-608/DVB do. */
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// SECONDARY PASS: Process DVB streams in split mode
|
||||
// DVB streams are parallel consumers, processed AFTER the primary stream
|
||||
// This ensures Teletext controls timing/loop lifetime while DVB is extracted separately
|
||||
if (ccx_options.split_dvb_subs)
|
||||
{
|
||||
// Guard: Only process DVB if timing has been initialized by Teletext
|
||||
if ((*dec_ctx)->timing == NULL || (*dec_ctx)->timing->pts_set == 0)
|
||||
{
|
||||
goto skip_dvb_secondary_pass;
|
||||
}
|
||||
|
||||
struct demuxer_data *dvb_ptr = *datalist;
|
||||
while (dvb_ptr)
|
||||
{
|
||||
// Process DVB nodes that are NOT the primary data_node
|
||||
if (dvb_ptr != *data_node &&
|
||||
dvb_ptr->codec == CCX_CODEC_DVB &&
|
||||
dvb_ptr->len > 0)
|
||||
{
|
||||
int stream_pid = dvb_ptr->stream_pid;
|
||||
char lang[4] = "und";
|
||||
|
||||
// Lookup language from discovered streams
|
||||
for (int i = 0; i < ctx->demux_ctx->potential_stream_count; i++)
|
||||
{
|
||||
if (ctx->demux_ctx->potential_streams[i].pid == stream_pid)
|
||||
{
|
||||
memcpy(lang, ctx->demux_ctx->potential_streams[i].lang, 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get or create pipeline for this DVB stream
|
||||
struct ccx_subtitle_pipeline *pipe = get_or_create_pipeline(ctx, stream_pid, CCX_STREAM_TYPE_DVB_SUB, lang);
|
||||
if (pipe && pipe->encoder && pipe->decoder)
|
||||
{
|
||||
// Save current decoder context and encoder context
|
||||
void *saved_private = (*dec_ctx)->private_data;
|
||||
struct encoder_ctx *saved_enc = *enc_ctx;
|
||||
|
||||
// Swap to pipeline's DVB decoder and encoder
|
||||
(*dec_ctx)->private_data = pipe->decoder;
|
||||
*enc_ctx = pipe->encoder;
|
||||
|
||||
// Sync timing from main context to pipeline encoder
|
||||
// This ensures DVB decode has valid PTS/timing state
|
||||
pipe->encoder->timing = (*dec_ctx)->timing;
|
||||
|
||||
// Decode DVB directly using pipeline's decoder and encoder
|
||||
// Skip first 2 bytes (PES header) as done in process_data for DVB
|
||||
struct cc_subtitle dvb_sub = {0};
|
||||
dvbsub_decode(pipe->encoder, *dec_ctx, dvb_ptr->buffer + 2, dvb_ptr->len - 2, &dvb_sub);
|
||||
|
||||
// Encode output if produced
|
||||
if (dvb_sub.got_output)
|
||||
{
|
||||
encode_sub(pipe->encoder, &dvb_sub);
|
||||
dvb_sub.got_output = 0;
|
||||
}
|
||||
|
||||
// Restore original decoder/encoder context
|
||||
(*dec_ctx)->private_data = saved_private;
|
||||
*enc_ctx = saved_enc;
|
||||
}
|
||||
}
|
||||
dvb_ptr = dvb_ptr->next_stream;
|
||||
}
|
||||
}
|
||||
skip_dvb_secondary_pass:
|
||||
|
||||
// Process the last subtitle for DVB
|
||||
if (!(!terminate_asap && !end_of_file && is_decoder_processed_enough(ctx) == CCX_FALSE))
|
||||
{
|
||||
|
||||
@@ -202,6 +202,12 @@ struct lib_ccx_ctx *init_libraries(struct ccx_s_options *opt)
|
||||
ctx->segment_counter = 0;
|
||||
ctx->system_start_time = -1;
|
||||
|
||||
// Initialize pipeline infrastructure
|
||||
ctx->pipeline_count = 0;
|
||||
ctx->dec_dvb_default = NULL;
|
||||
ctx->pipeline_lock = 0;
|
||||
memset(ctx->pipelines, 0, sizeof(ctx->pipelines));
|
||||
|
||||
end:
|
||||
if (ret != EXIT_OK)
|
||||
{
|
||||
@@ -263,6 +269,30 @@ void dinit_libraries(struct lib_ccx_ctx **ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup subtitle pipelines (split DVB mode)
|
||||
for (i = 0; i < lctx->pipeline_count; i++)
|
||||
{
|
||||
struct ccx_subtitle_pipeline *p = lctx->pipelines[i];
|
||||
if (!p)
|
||||
continue;
|
||||
|
||||
// 1) Close decoder first (no encoder dependency)
|
||||
if (p->decoder)
|
||||
dvbsub_close_decoder(&p->decoder);
|
||||
|
||||
// 2) Close encoder via canonical API (handles output cleanup internally)
|
||||
if (p->encoder)
|
||||
dinit_encoder(&p->encoder, 0);
|
||||
|
||||
// 3) Free timing context
|
||||
if (p->timing)
|
||||
dinit_timing_ctx(&p->timing);
|
||||
|
||||
free(p);
|
||||
lctx->pipelines[i] = NULL;
|
||||
}
|
||||
lctx->pipeline_count = 0;
|
||||
|
||||
// free EPG memory
|
||||
EPG_free(lctx);
|
||||
freep(&lctx->freport.data_from_608);
|
||||
@@ -487,3 +517,112 @@ struct encoder_ctx *update_encoder_list(struct lib_ccx_ctx *ctx)
|
||||
{
|
||||
return update_encoder_list_cinfo(ctx, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a subtitle pipeline for a specific PID/language.
|
||||
* Used when --split-dvb-subs is enabled to route each DVB stream to its own output file.
|
||||
*/
|
||||
struct ccx_subtitle_pipeline *get_or_create_pipeline(struct lib_ccx_ctx *ctx, int pid, int stream_type, const char *lang)
|
||||
{
|
||||
int i;
|
||||
|
||||
// Search for existing pipeline
|
||||
for (i = 0; i < ctx->pipeline_count; i++)
|
||||
{
|
||||
struct ccx_subtitle_pipeline *p = ctx->pipelines[i];
|
||||
if (p && p->pid == pid && p->stream_type == stream_type &&
|
||||
strcmp(p->lang, lang) == 0)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
// Check capacity
|
||||
if (ctx->pipeline_count >= MAX_SUBTITLE_PIPELINES)
|
||||
{
|
||||
mprint("Warning: Maximum subtitle pipelines (%d) reached, cannot create new pipeline for PID 0x%X\n",
|
||||
MAX_SUBTITLE_PIPELINES, pid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Allocate new pipeline
|
||||
struct ccx_subtitle_pipeline *pipe = calloc(1, sizeof(struct ccx_subtitle_pipeline));
|
||||
if (!pipe)
|
||||
{
|
||||
mprint("Error: Failed to allocate memory for subtitle pipeline\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pipe->pid = pid;
|
||||
pipe->stream_type = stream_type;
|
||||
snprintf(pipe->lang, sizeof(pipe->lang), "%.3s", lang ? lang : "und");
|
||||
|
||||
// Generate output filename: {basefilename}_{lang}.ext or {basefilename}_0x{PID}.ext
|
||||
const char *ext = ctx->extension ? ctx->extension : ".srt";
|
||||
if (strcmp(pipe->lang, "und") == 0 || pipe->lang[0] == '\0')
|
||||
{
|
||||
snprintf(pipe->filename, sizeof(pipe->filename), "%s_0x%04X%s",
|
||||
ctx->basefilename, pid, ext);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(pipe->filename, sizeof(pipe->filename), "%s_%s%s",
|
||||
ctx->basefilename, pipe->lang, ext);
|
||||
}
|
||||
|
||||
// Initialize encoder for this pipeline
|
||||
struct encoder_cfg cfg = ccx_options.enc_cfg;
|
||||
cfg.output_filename = pipe->filename;
|
||||
pipe->encoder = init_encoder(&cfg);
|
||||
if (!pipe->encoder)
|
||||
{
|
||||
mprint("Error: Failed to create encoder for pipeline PID 0x%X\n", pid);
|
||||
free(pipe);
|
||||
return NULL;
|
||||
}
|
||||
pipe->encoder->write_previous = 0; // DVB specific
|
||||
|
||||
// Timing context: Do NOT create a separate timing context for pipelines.
|
||||
// Pipelines must share the main decoder's timing context (dec_ctx->timing).
|
||||
// Creating a fresh timing context would have pts_set=No, causing decode failures.
|
||||
// The encoder will receive timing from dec_ctx at decode time.
|
||||
pipe->timing = NULL;
|
||||
|
||||
// Initialize DVB decoder
|
||||
struct dvb_config dvb_cfg = {0};
|
||||
dvb_cfg.n_language = 1;
|
||||
|
||||
// Lookup metadata to use correct Composition and Ancillary Page IDs
|
||||
// This ensures we respect the configuration advertised in the PMT
|
||||
if (ctx->demux_ctx)
|
||||
{
|
||||
for (i = 0; i < ctx->demux_ctx->potential_stream_count; i++)
|
||||
{
|
||||
if (ctx->demux_ctx->potential_streams[i].pid == pid)
|
||||
{
|
||||
dvb_cfg.composition_id[0] = ctx->demux_ctx->potential_streams[i].composition_id;
|
||||
dvb_cfg.ancillary_id[0] = ctx->demux_ctx->potential_streams[i].ancillary_id;
|
||||
// Also update language if not provided/detected earlier?
|
||||
// But we pass 'lang' argument to this function.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pipe->decoder = dvbsub_init_decoder(&dvb_cfg, 1);
|
||||
if (!pipe->decoder)
|
||||
{
|
||||
mprint("Error: Failed to create DVB decoder for pipeline PID 0x%X\n", pid);
|
||||
dinit_encoder(&pipe->encoder, 0);
|
||||
dinit_timing_ctx(&pipe->timing);
|
||||
free(pipe);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Register pipeline
|
||||
ctx->pipelines[ctx->pipeline_count++] = pipe;
|
||||
|
||||
mprint("Created subtitle pipeline for PID 0x%X lang=%s -> %s\n", pid, pipe->lang, pipe->filename);
|
||||
|
||||
return pipe;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,23 @@ struct ccx_s_mp4Cfg
|
||||
unsigned int mp4vidtrack : 1;
|
||||
};
|
||||
|
||||
#define MAX_SUBTITLE_PIPELINES 64
|
||||
|
||||
/**
|
||||
* ccx_subtitle_pipeline - Encapsulates all components for a single subtitle output stream
|
||||
*/
|
||||
struct ccx_subtitle_pipeline
|
||||
{
|
||||
int pid;
|
||||
int stream_type;
|
||||
char lang[4];
|
||||
char filename[1024]; // Using fixed size instead of PATH_MAX to avoid header issues
|
||||
struct ccx_s_write *writer;
|
||||
struct encoder_ctx *encoder;
|
||||
struct ccx_common_timing_ctx *timing;
|
||||
void *decoder; // Pointer to decoder context (e.g., ccx_decoders_dvb_context)
|
||||
};
|
||||
|
||||
struct lib_ccx_ctx
|
||||
{
|
||||
// Stuff common to both loops
|
||||
@@ -154,8 +171,16 @@ struct lib_ccx_ctx
|
||||
int segment_on_key_frames_only;
|
||||
int segment_counter;
|
||||
LLONG system_start_time;
|
||||
|
||||
// Registration for multi-stream subtitle extraction
|
||||
struct ccx_subtitle_pipeline *pipelines[MAX_SUBTITLE_PIPELINES];
|
||||
int pipeline_count;
|
||||
int pipeline_lock; // Simple lock flag (single-threaded access assumed)
|
||||
void *dec_dvb_default; // Default decoder used in non-split mode
|
||||
};
|
||||
|
||||
struct ccx_subtitle_pipeline *get_or_create_pipeline(struct lib_ccx_ctx *ctx, int pid, int stream_type, const char *lang);
|
||||
|
||||
struct lib_ccx_ctx *init_libraries(struct ccx_s_options *opt);
|
||||
void dinit_libraries(struct lib_ccx_ctx **ctx);
|
||||
|
||||
|
||||
@@ -525,7 +525,7 @@ struct demuxer_data *get_best_data(struct demuxer_data *data)
|
||||
{
|
||||
if (ptr->codec == CCX_CODEC_DVB)
|
||||
{
|
||||
ret = data;
|
||||
ret = ptr;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
@@ -680,7 +680,11 @@ int copy_payload_to_capbuf(struct cap_info *cinfo, struct ts_payload *payload)
|
||||
cinfo->stream != CCX_STREAM_TYPE_VIDEO_HEVC) ||
|
||||
!ccx_options.analyze_video_stream))
|
||||
{
|
||||
return CCX_OK;
|
||||
// In split DVB mode, allow DVB subtitle packets even if ignored
|
||||
if (!(ccx_options.split_dvb_subs && cinfo->codec == CCX_CODEC_DVB))
|
||||
{
|
||||
return CCX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify PES before copy to capbuf
|
||||
@@ -970,32 +974,40 @@ int64_t ts_readstream(struct ccx_demuxer *ctx, struct demuxer_data **data)
|
||||
cinfo->stream != CCX_STREAM_TYPE_VIDEO_HEVC) ||
|
||||
!ccx_options.analyze_video_stream))
|
||||
{
|
||||
if (cinfo->codec_private_data)
|
||||
// In split DVB mode, do NOT skip/cleanup DVB streams
|
||||
if (ccx_options.split_dvb_subs && cinfo->codec == CCX_CODEC_DVB)
|
||||
{
|
||||
switch (cinfo->codec)
|
||||
// Fall through - process this DVB packet
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cinfo->codec_private_data)
|
||||
{
|
||||
case CCX_CODEC_TELETEXT:
|
||||
telxcc_close(&cinfo->codec_private_data, NULL);
|
||||
break;
|
||||
case CCX_CODEC_DVB:
|
||||
dvbsub_close_decoder(&cinfo->codec_private_data);
|
||||
break;
|
||||
case CCX_CODEC_ISDB_CC:
|
||||
delete_isdb_decoder(&cinfo->codec_private_data);
|
||||
default:
|
||||
break;
|
||||
switch (cinfo->codec)
|
||||
{
|
||||
case CCX_CODEC_TELETEXT:
|
||||
telxcc_close(&cinfo->codec_private_data, NULL);
|
||||
break;
|
||||
case CCX_CODEC_DVB:
|
||||
dvbsub_close_decoder(&cinfo->codec_private_data);
|
||||
break;
|
||||
case CCX_CODEC_ISDB_CC:
|
||||
delete_isdb_decoder(&cinfo->codec_private_data);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
cinfo->codec_private_data = NULL;
|
||||
}
|
||||
cinfo->codec_private_data = NULL;
|
||||
}
|
||||
|
||||
if (cinfo->capbuflen > 0)
|
||||
{
|
||||
freep(&cinfo->capbuf);
|
||||
cinfo->capbufsize = 0;
|
||||
cinfo->capbuflen = 0;
|
||||
delete_demuxer_data_node_by_pid(data, cinfo->pid);
|
||||
if (cinfo->capbuflen > 0)
|
||||
{
|
||||
freep(&cinfo->capbuf);
|
||||
cinfo->capbufsize = 0;
|
||||
cinfo->capbuflen = 0;
|
||||
delete_demuxer_data_node_by_pid(data, cinfo->pid);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Video PES start
|
||||
@@ -1006,8 +1018,14 @@ int64_t ts_readstream(struct ccx_demuxer *ctx, struct demuxer_data **data)
|
||||
}
|
||||
|
||||
// Discard packets when no pesstart was found.
|
||||
// Exception: DVB in split mode - allow packets to accumulate
|
||||
if (!cinfo->saw_pesstart)
|
||||
continue;
|
||||
{
|
||||
if (!(ccx_options.split_dvb_subs && cinfo->codec == CCX_CODEC_DVB))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ((cinfo->prev_counter == 15 ? 0 : cinfo->prev_counter + 1) != payload.counter)
|
||||
{
|
||||
|
||||
@@ -43,7 +43,18 @@ void ignore_other_stream(struct ccx_demuxer *ctx, int pid)
|
||||
list_for_each_entry(iter, &ctx->cinfo_tree.all_stream, all_stream, struct cap_info)
|
||||
{
|
||||
if (iter->pid != pid)
|
||||
iter->ignore = 1;
|
||||
{
|
||||
// In split DVB mode, do NOT ignore DVB subtitle streams
|
||||
// They need to remain in the datalist for secondary pass processing
|
||||
if (ccx_options.split_dvb_subs && iter->codec == CCX_CODEC_DVB)
|
||||
{
|
||||
iter->ignore = 0; // Keep DVB streams active
|
||||
}
|
||||
else
|
||||
{
|
||||
iter->ignore = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -385,6 +385,41 @@ int parse_PMT(struct ccx_demuxer *ctx, unsigned char *buf, int len, struct progr
|
||||
if (CCX_MPEG_DSC_DVB_SUBTITLE == descriptor_tag)
|
||||
{
|
||||
struct dvb_config cnf;
|
||||
char detected_lang[4] = "und";
|
||||
|
||||
if (desc_len >= 3)
|
||||
{
|
||||
detected_lang[0] = (char)es_info[0];
|
||||
detected_lang[1] = (char)es_info[1];
|
||||
detected_lang[2] = (char)es_info[2];
|
||||
detected_lang[3] = '\0';
|
||||
}
|
||||
|
||||
// If split mode enabled, track for pipeline creation
|
||||
if (ccx_options.split_dvb_subs && ctx->potential_stream_count < MAX_POTENTIAL_STREAMS)
|
||||
{
|
||||
int found = 0;
|
||||
for (int k = 0; k < ctx->potential_stream_count; k++)
|
||||
{
|
||||
if (ctx->potential_streams[k].pid == (int)elementary_PID)
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
ctx->potential_streams[ctx->potential_stream_count].pid = (int)elementary_PID;
|
||||
ctx->potential_streams[ctx->potential_stream_count].stream_type = CCX_STREAM_TYPE_DVB_SUB;
|
||||
ctx->potential_streams[ctx->potential_stream_count].mpeg_type = stream_type;
|
||||
memcpy(ctx->potential_streams[ctx->potential_stream_count].lang, detected_lang, 4);
|
||||
ctx->potential_stream_count++;
|
||||
|
||||
dbg_print(CCX_DMT_GENERIC_NOTICES, "Discovered DVB stream PID 0x%X lang=%s\n", elementary_PID, detected_lang);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ENABLE_OCR
|
||||
if (ccx_options.write_format != CCX_OF_SPUPNG)
|
||||
{
|
||||
@@ -392,13 +427,28 @@ int parse_PMT(struct ccx_demuxer *ctx, unsigned char *buf, int len, struct progr
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (!IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_DVB))
|
||||
if (!IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_DVB) &&
|
||||
!(ccx_options.split_dvb_subs && ctx->codec != CCX_CODEC_DVB))
|
||||
continue;
|
||||
|
||||
memset((void *)&cnf, 0, sizeof(struct dvb_config));
|
||||
ret = parse_dvb_description(&cnf, es_info, desc_len);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
// Update metadata with specific IDs
|
||||
if (ccx_options.split_dvb_subs)
|
||||
{
|
||||
for (int k = 0; k < ctx->potential_stream_count; k++)
|
||||
{
|
||||
if (ctx->potential_streams[k].pid == (int)elementary_PID)
|
||||
{
|
||||
ctx->potential_streams[k].composition_id = cnf.composition_id[0];
|
||||
ctx->potential_streams[k].ancillary_id = cnf.ancillary_id[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ptr = dvbsub_init_decoder(&cnf, pinfo->initialized_ocr);
|
||||
if (!pinfo->initialized_ocr)
|
||||
pinfo->initialized_ocr = 1;
|
||||
|
||||
@@ -1682,7 +1682,7 @@ impl OptionsExt for Options {
|
||||
);
|
||||
}
|
||||
|
||||
if self.demux_cfg.ts_forced_cappid.is_some() {
|
||||
if self.demux_cfg.ts_forced_cappid {
|
||||
fatal!(
|
||||
cause = ExitCause::IncompatibleParameters;
|
||||
"--split-dvb-subs cannot be used with manual PID selection (-pn).\n\
|
||||
|
||||
6
tests/regression/dvb_split.txt
Normal file
6
tests/regression/dvb_split.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
TEST: split_dvb_multilang
|
||||
INPUT: arte_multiaudio.ts
|
||||
ARGS: --split-dvb-subs -o output_split
|
||||
EXPECT: output_split_fra.srt exists
|
||||
|
||||
EXPECT: output_split_deu.srt exists
|
||||
@@ -190,11 +190,11 @@
|
||||
<Import Project=" $(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-Full|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-Full|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
|
||||
Reference in New Issue
Block a user