From f5bd5663919569715c1c3c6e2ce1d9e006090abe Mon Sep 17 00:00:00 2001 From: tank0nf Date: Fri, 28 Feb 2025 00:32:31 +0530 Subject: [PATCH 1/3] checkpoint_1 --- docs/CHANGES.TXT | 1 + src/ccextractor.c | 263 +++++++++++++++++--------------- src/lib_ccx/ccx_common_option.h | 1 + src/lib_ccx/file_functions.c | 6 +- src/lib_ccx/matroska.c | 133 ++++++++++------ src/lib_ccx/matroska.h | 1 + src/lib_ccx/params.c | 10 +- 7 files changed, 248 insertions(+), 167 deletions(-) diff --git a/docs/CHANGES.TXT b/docs/CHANGES.TXT index f250da30a..684bb7458 100644 --- a/docs/CHANGES.TXT +++ b/docs/CHANGES.TXT @@ -1,5 +1,6 @@ 1.0 (to be released) ----------------- +- Added Support to just show list of subtitle tracks in the file. - New: Create unit test for rust code (#1615) - Breaking: Major argument flags revamp for CCExtractor (#1564 & #1619) - New: Create a Docker image to simplify the CCExtractor usage without any environmental hustle (#1611) diff --git a/src/ccextractor.c b/src/ccextractor.c index a8676405c..169afaaa9 100644 --- a/src/ccextractor.c +++ b/src/ccextractor.c @@ -40,149 +40,163 @@ void print_end_msg(void) int api_start(struct ccx_s_options api_options) { - struct lib_ccx_ctx *ctx = NULL; // Context for libs - struct lib_cc_decode *dec_ctx = NULL; // Context for decoder - int ret = 0, tmp = 0; - enum ccx_stream_mode_enum stream_mode = CCX_SM_ELEMENTARY_OR_NOT_FOUND; + struct lib_ccx_ctx *ctx = NULL; // Context for libs + struct lib_cc_decode *dec_ctx = NULL; // Context for decoder + int ret = 0, tmp = 0; + enum ccx_stream_mode_enum stream_mode = CCX_SM_ELEMENTARY_OR_NOT_FOUND; #if defined(ENABLE_OCR) && defined(_WIN32) - setMsgSeverity(LEPT_MSG_SEVERITY); + setMsgSeverity(LEPT_MSG_SEVERITY); #endif - // Initialize CCExtractor libraries - ctx = init_libraries(&api_options); - - if (!ctx) - { - if (errno == ENOMEM) - fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory, could not initialize libraries\n"); - else if (errno == EINVAL) - fatal(CCX_COMMON_EXIT_BUG_BUG, "Invalid option to CCextractor Library\n"); - else if (errno == EPERM) - fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Operation not permitted.\n"); - else if (errno == EACCES) - fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Permission denied\n"); - else - fatal(EXIT_NOT_CLASSIFIED, "Unable to create Library Context %d\n", errno); - } + // Initialize CCExtractor libraries + ctx = init_libraries(&api_options); + + if (!ctx) + { + if (errno == ENOMEM) + fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory, could not initialize libraries\n"); + else if (errno == EINVAL) + fatal(CCX_COMMON_EXIT_BUG_BUG, "Invalid option to CCextractor Library\n"); + else if (errno == EPERM) + fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Operation not permitted.\n"); + else if (errno == EACCES) + fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Permission denied\n"); + else + fatal(EXIT_NOT_CLASSIFIED, "Unable to create Library Context %d\n", errno); + } #ifdef ENABLE_HARDSUBX - if (api_options.hardsubx) - { - // Perform burned in subtitle extraction - hardsubx(&api_options, ctx); - return 0; - } + if (api_options.hardsubx) + { + // Perform burned in subtitle extraction + hardsubx(&api_options, ctx); + return 0; + } #endif #ifdef WITH_LIBCURL - curl_global_init(CURL_GLOBAL_ALL); - - /* get a curl handle */ - curl = curl_easy_init(); - if (!curl) - { - curl_global_cleanup(); // Must be done even on init fail - fatal(EXIT_NOT_CLASSIFIED, "Unable to init curl."); - } + curl_global_init(CURL_GLOBAL_ALL); + + /* get a curl handle */ + curl = curl_easy_init(); + if (!curl) + { + curl_global_cleanup(); // Must be done even on init fail + fatal(EXIT_NOT_CLASSIFIED, "Unable to init curl."); + } #endif - int show_myth_banner = 0; - - params_dump(ctx); - - // default teletext page - if (tlt_config.page > 0) - { - // dec to BCD, magazine pages numbers are in BCD (ETSI 300 706) - tlt_config.page = ((tlt_config.page / 100) << 8) | (((tlt_config.page / 10) % 10) << 4) | (tlt_config.page % 10); - } - - if (api_options.transcript_settings.xds) - { - if (api_options.write_format != CCX_OF_TRANSCRIPT) - { - api_options.transcript_settings.xds = 0; - mprint("Warning: -xds ignored, XDS can only be exported to transcripts at this time.\n"); - } - } - - time_t start, final; - time(&start); - - if (api_options.binary_concat) - { - ctx->total_inputsize = get_total_file_size(ctx); - if (ctx->total_inputsize < 0) - { - switch (ctx->total_inputsize) - { - case -1 * ENOENT: - fatal(EXIT_NO_INPUT_FILES, "Failed to open one of the input file(s): File does not exist."); - case -1 * EACCES: - fatal(EXIT_READ_ERROR, "Failed to open input file: Unable to access"); - case -1 * EINVAL: - fatal(EXIT_READ_ERROR, "Failed to open input file: Invalid opening flag."); - case -1 * EMFILE: - fatal(EXIT_TOO_MANY_INPUT_FILES, "Failed to open input file: Too many files are open."); - default: - fatal(EXIT_READ_ERROR, "Failed to open input file: Reason unknown"); - } - } - } + int show_myth_banner = 0; + + // Only show params dump if we're not just listing tracks + // if (!api_options.list_tracks_only) + params_dump(ctx); + + // default teletext page + if (tlt_config.page > 0) + { + // dec to BCD, magazine pages numbers are in BCD (ETSI 300 706) + tlt_config.page = ((tlt_config.page / 100) << 8) | (((tlt_config.page / 10) % 10) << 4) | (tlt_config.page % 10); + } + + if (api_options.transcript_settings.xds) + { + if (api_options.write_format != CCX_OF_TRANSCRIPT) + { + api_options.transcript_settings.xds = 0; + mprint("Warning: -xds ignored, XDS can only be exported to transcripts at this time.\n"); + } + } + + time_t start, final; + time(&start); + + if (api_options.binary_concat) + { + ctx->total_inputsize = get_total_file_size(ctx); + if (ctx->total_inputsize < 0) + { + switch (ctx->total_inputsize) + { + case -1 * ENOENT: + fatal(EXIT_NO_INPUT_FILES, "Failed to open one of the input file(s): File does not exist."); + case -1 * EACCES: + fatal(EXIT_READ_ERROR, "Failed to open input file: Unable to access"); + case -1 * EINVAL: + fatal(EXIT_READ_ERROR, "Failed to open input file: Invalid opening flag."); + case -1 * EMFILE: + fatal(EXIT_TOO_MANY_INPUT_FILES, "Failed to open input file: Too many files are open."); + default: + fatal(EXIT_READ_ERROR, "Failed to open input file: Reason unknown"); + } + } + } #ifndef _WIN32 - signal_ctx = ctx; - m_signal(SIGINT, sigint_handler); - m_signal(SIGTERM, sigterm_handler); - m_signal(SIGUSR1, sigusr1_handler); + signal_ctx = ctx; + m_signal(SIGINT, sigint_handler); + m_signal(SIGTERM, sigterm_handler); + m_signal(SIGUSR1, sigusr1_handler); #endif - terminate_asap = 0; + terminate_asap = 0; #ifdef ENABLE_SHARING - if (api_options.translate_enabled && ctx->num_input_files > 1) - { - mprint("[share] WARNING: simultaneous translation of several input files is not supported yet\n"); - api_options.translate_enabled = 0; - api_options.sharing_enabled = 0; - } - if (api_options.translate_enabled) - { - mprint("[share] launching translate service\n"); - ccx_share_launch_translator(api_options.translate_langs, api_options.translate_key); - } + if (api_options.translate_enabled && ctx->num_input_files > 1) + { + mprint("[share] WARNING: simultaneous translation of several input files is not supported yet\n"); + api_options.translate_enabled = 0; + api_options.sharing_enabled = 0; + } + if (api_options.translate_enabled) + { + mprint("[share] launching translate service\n"); + ccx_share_launch_translator(api_options.translate_langs, api_options.translate_key); + } #endif // ENABLE_SHARING - ret = 0; - while (switch_to_next_file(ctx, 0)) - { - prepare_for_new_file(ctx); + ret = 0; + while (switch_to_next_file(ctx, 0)) + { + prepare_for_new_file(ctx); #ifdef ENABLE_SHARING - if (api_options.sharing_enabled) - ccx_share_start(ctx->basefilename); + if (api_options.sharing_enabled) + ccx_share_start(ctx->basefilename); #endif // ENABLE_SHARING - stream_mode = ctx->demux_ctx->get_stream_mode(ctx->demux_ctx); - // Disable sync check for raw formats - they have the right timeline. - // Also true for bin formats, but -nosync might have created a - // broken timeline for debug purposes. - // Disable too in MP4, specs doesn't say that there can't be a jump - switch (stream_mode) - { - case CCX_SM_MCPOODLESRAW: - case CCX_SM_RCWT: - case CCX_SM_MP4: + stream_mode = ctx->demux_ctx->get_stream_mode(ctx->demux_ctx); + + // Check if we're listing tracks and we have an MKV file + if (api_options.list_tracks_only && stream_mode == CCX_SM_MKV) + { + // Simplified processing for track listing + // mprint("\nCCExtractor Track Listing\n"); + // mprint("------------------------\n"); + ret = matroska_loop(ctx); + return ret; + } + + // Disable sync check for raw formats - they have the right timeline. + // Also true for bin formats, but -nosync might have created a + // broken timeline for debug purposes. + // Disable too in MP4, specs doesn't say that there can't be a jump + switch (stream_mode) + { + case CCX_SM_MCPOODLESRAW: + case CCX_SM_RCWT: + case CCX_SM_MP4: #ifdef WTV_DEBUG - case CCX_SM_HEX_DUMP: + case CCX_SM_HEX_DUMP: #endif - ccx_common_timing_settings.disable_sync_check = 1; - break; - default: - break; - } - /* ----------------------------------------------------------------- - MAIN LOOP - ----------------------------------------------------------------- */ - switch (stream_mode) - { + ccx_common_timing_settings.disable_sync_check = 1; + break; + default: + break; + } + + /* ----------------------------------------------------------------- + MAIN LOOP + ----------------------------------------------------------------- */ + switch (stream_mode) + { case CCX_SM_ELEMENTARY_OR_NOT_FOUND: if (!api_options.use_gop_as_pts) // If !0 then the user selected something api_options.use_gop_as_pts = 1; // Force GOP timing for ES @@ -471,6 +485,11 @@ int main(int argc, char *argv[]) { exit(compile_ret); } + if (api_options->list_tracks_only) + { + // Disable all logger output except for our specific track listing + ccx_common_logging.debug_mask = 0; + } int start_ret = api_start(*api_options); return start_ret; diff --git a/src/lib_ccx/ccx_common_option.h b/src/lib_ccx/ccx_common_option.h index 7bc35ac1a..15adb9e64 100644 --- a/src/lib_ccx/ccx_common_option.h +++ b/src/lib_ccx/ccx_common_option.h @@ -99,6 +99,7 @@ struct ccx_s_options // Options from user parameters int notypesetting; struct ccx_boundary_time extraction_start, extraction_end; // Segment we actually process int print_file_reports; + int list_tracks_only; // Flag to only list tracks without processing them ccx_decoder_608_settings settings_608; // Contains the settings for the 608 decoder. ccx_decoder_dtvcc_settings settings_dtvcc; // Same for 708 decoder diff --git a/src/lib_ccx/file_functions.c b/src/lib_ccx/file_functions.c index 064ba0d35..215bae13e 100644 --- a/src/lib_ccx/file_functions.c +++ b/src/lib_ccx/file_functions.c @@ -164,7 +164,11 @@ int switch_to_next_file(struct lib_ccx_ctx *ctx, LLONG bytesinbuffer) break; // The following \n keeps the progress percentage from being overwritten. - mprint("\n\r-----------------------------------------------------------------\n"); + if (!ccx_options.list_tracks_only) + { + // The following \n keeps the progress percentage from being overwritten. + mprint("\n\r-----------------------------------------------------------------\n"); + } mprint("\rOpening file: %s\n", ctx->inputfile[ctx->current_file]); ret = ctx->demux_ctx->open(ctx->demux_ctx, ctx->inputfile[ctx->current_file]); if (ret < 0) diff --git a/src/lib_ccx/matroska.c b/src/lib_ccx/matroska.c index 01b7e634f..766cbcbf5 100644 --- a/src/lib_ccx/matroska.c +++ b/src/lib_ccx/matroska.c @@ -719,7 +719,6 @@ enum matroska_track_subtitle_codec_id get_track_subtitle_codec_id(char *codec_id void parse_segment_track_entry(struct matroska_ctx *mkv_ctx) { FILE *file = mkv_ctx->file; - mprint("\nTrack entry:\n"); ULLONG len = read_vint_length(file); ULLONG pos = get_current_byte(file); @@ -1358,49 +1357,97 @@ FILE *create_file(struct lib_ccx_ctx *ctx) return file; } -int matroska_loop(struct lib_ccx_ctx *ctx) +void print_track_list(struct matroska_ctx *mkv_ctx) { - if (ccx_options.write_format_rewritten) - { - mprint(MATROSKA_WARNING "You are using --out=, but Matroska parser extract subtitles in a recorded format\n"); - mprint("--out= will be ignored\n"); - } - - // Don't need generated input file - // Will read bytes by FILE* - close_input_file(ctx); - - struct matroska_ctx *mkv_ctx = malloc(sizeof(struct matroska_ctx)); - mkv_ctx->ctx = ctx; - mkv_ctx->sub_tracks_count = 0; - mkv_ctx->sentence_count = 0; - mkv_ctx->current_second = 0; - mkv_ctx->filename = ctx->inputfile[ctx->current_file]; - mkv_ctx->file = create_file(ctx); - mkv_ctx->sub_tracks = malloc(sizeof(struct matroska_sub_track **)); - // EIA-608 - memset(&mkv_ctx->dec_sub, 0, sizeof(mkv_ctx->dec_sub)); - mkv_ctx->avc_track_number = -1; - - matroska_parse(mkv_ctx); - - // 100% done - activity_progress(100, (int)(mkv_ctx->current_second / 60), - (int)(mkv_ctx->current_second % 60)); - - matroska_save_all(mkv_ctx, ccx_options.mkvlang); - int sentence_count = mkv_ctx->sentence_count; - matroska_free_all(mkv_ctx); - - mprint("\n\n"); + mprint("\n"); + mprint("Available tracks in input file:\n"); + mprint("------------------------------\n"); + + for (int i = 0; i < mkv_ctx->sub_tracks_count; i++) + { + struct matroska_sub_track *track = mkv_ctx->sub_tracks[i]; + + mprint("Track %d: Type: Subtitle, Language: %s, Codec: %s\n", + (int)track->track_number, + track->lang ? track->lang : "unknown", + track->codec_id_string ? track->codec_id_string : "unknown"); + } + + // Print video track if found + if (mkv_ctx->avc_track_number > -1) + { + mprint("Track %d: Type: Video\n", (int)mkv_ctx->avc_track_number); + } + + mprint("\n"); +} - // Support only one AVC track by now - if (mkv_ctx->avc_track_number > -1) - mprint("Found AVC track. "); - else - mprint("Found no AVC track. "); - if (mkv_ctx->dec_sub.got_output) - return 1; - return sentence_count; +int matroska_loop(struct lib_ccx_ctx *ctx) +{ + // If we're just listing tracks, completely disable ALL output during parsing + int original_debug_mask = 0; + if (ccx_options.list_tracks_only) + { + original_debug_mask = ccx_common_logging.debug_mask; + ccx_common_logging.debug_mask = 0; + } + else if (ccx_options.write_format_rewritten) + { + mprint(MATROSKA_WARNING "You are using --out=, but Matroska parser extract subtitles in a recorded format\n"); + mprint("--out= will be ignored\n"); + } + + // Don't need generated input file + // Will read bytes by FILE* + close_input_file(ctx); + + struct matroska_ctx *mkv_ctx = malloc(sizeof(struct matroska_ctx)); + mkv_ctx->ctx = ctx; + mkv_ctx->sub_tracks_count = 0; + mkv_ctx->sentence_count = 0; + mkv_ctx->current_second = 0; + mkv_ctx->filename = ctx->inputfile[ctx->current_file]; + mkv_ctx->file = create_file(ctx); + mkv_ctx->sub_tracks = malloc(sizeof(struct matroska_sub_track **)); + // EIA-608 + memset(&mkv_ctx->dec_sub, 0, sizeof(mkv_ctx->dec_sub)); + mkv_ctx->avc_track_number = -1; + + matroska_parse(mkv_ctx); + + // Check if we're just listing tracks + if (ccx_options.list_tracks_only) + { + // Restore output capabilities for our specific output + ccx_common_logging.debug_mask = original_debug_mask; + + // Print tracks in a clean format + print_track_list(mkv_ctx); + + // Cleanup and exit early + matroska_free_all(mkv_ctx); + // mprint("\nTrack listing completed. Exiting as requested.\n"); + return 0; + } + + // 100% done + activity_progress(100, (int)(mkv_ctx->current_second / 60), + (int)(mkv_ctx->current_second % 60)); + + matroska_save_all(mkv_ctx, ccx_options.mkvlang); + int sentence_count = mkv_ctx->sentence_count; + matroska_free_all(mkv_ctx); + + mprint("\n\n"); + + // Support only one AVC track by now + if (mkv_ctx->avc_track_number > -1) + mprint("Found AVC track. "); + else + mprint("Found no AVC track. "); + + if (mkv_ctx->dec_sub.got_output) + return 1; + return sentence_count; } diff --git a/src/lib_ccx/matroska.h b/src/lib_ccx/matroska.h index fe8506001..3a99129f7 100644 --- a/src/lib_ccx/matroska.h +++ b/src/lib_ccx/matroska.h @@ -276,6 +276,7 @@ void save_sub_track(struct matroska_ctx* mkv_ctx, struct matroska_sub_track* tra void free_sub_track(struct matroska_sub_track* track); void matroska_save_all(struct matroska_ctx* mkv_ctx,char* lang); void matroska_free_all(struct matroska_ctx* mkv_ctx); +void print_track_list(struct matroska_ctx *mkv_ctx); void matroska_parse(struct matroska_ctx* mkv_ctx); FILE* create_file(struct lib_ccx_ctx *ctx); diff --git a/src/lib_ccx/params.c b/src/lib_ccx/params.c index 570b18084..c8fe0a2a0 100644 --- a/src/lib_ccx/params.c +++ b/src/lib_ccx/params.c @@ -535,6 +535,7 @@ void print_usage(void) mprint(" --datastreamtype: Instead of selecting the stream by its PID, select it\n"); mprint(" by its type (pick the stream that has this type in\n"); mprint(" the PMT)\n"); + mprint(" -lt, --list-tracks: List all tracks found in the input file and exit\n"); mprint(" --streamtype: Assume the data is of this type, don't autodetect. This\n"); mprint(" parameter may be needed if --datapid or -datastreamtype\n"); mprint(" is used and CCExtractor cannot determine how to process\n"); @@ -1618,7 +1619,14 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) fatal(EXIT_MALFORMED_PARAMETER, "--codec has no argument.\n"); } } - + //list tracks in the file + if (strcmp(argv[i], "--list-tracks") == 0 || strcmp(argv[i], "-lt") == 0) + { + opt->cc_to_stdout = 1; + opt->list_tracks_only = 1; + continue; + } + /*user specified subtitle to be selected */ if (strcmp(argv[i], "--no-codec") == 0) { From 1116140cc610802d340d4f6ee2a5dd81f5aa6be5 Mon Sep 17 00:00:00 2001 From: tank0nf Date: Fri, 28 Feb 2025 17:38:15 +0530 Subject: [PATCH 2/3] added code for preview subs --- src/ccextractor.c | 67 ++- src/lib_ccx/ccx_encoders_common.c | 2 +- src/lib_ccx/lib_ccx.h | 1 + src/lib_ccx/mp4.c | 581 ++++++++++++++---------- src/lib_ccx/ts_functions.c | 57 +++ src/rust/lib_ccxr/src/common/options.rs | 3 + src/rust/src/args.rs | 3 + src/rust/src/common.rs | 1 + src/rust/src/parser.rs | 4 + 9 files changed, 461 insertions(+), 258 deletions(-) diff --git a/src/ccextractor.c b/src/ccextractor.c index 169afaaa9..ecbd7212e 100644 --- a/src/ccextractor.c +++ b/src/ccextractor.c @@ -165,14 +165,65 @@ int api_start(struct ccx_s_options api_options) stream_mode = ctx->demux_ctx->get_stream_mode(ctx->demux_ctx); // Check if we're listing tracks and we have an MKV file - if (api_options.list_tracks_only && stream_mode == CCX_SM_MKV) - { - // Simplified processing for track listing - // mprint("\nCCExtractor Track Listing\n"); - // mprint("------------------------\n"); - ret = matroska_loop(ctx); - return ret; - } + if (api_options.list_tracks_only) + { + // Clear all output + ccx_common_logging.debug_mask = 0; + + // Format-specific track listing + switch (stream_mode) + { + case CCX_SM_MKV: + ret = matroska_loop(ctx); + return ret; + + case CCX_SM_MP4: + // MP4 will be handled in its own case + break; + + case CCX_SM_TRANSPORT: + case CCX_SM_PROGRAM: + if (api_options.list_tracks_only) + { + mprint("\nCCExtractor Track Listing\n"); + mprint("------------------------\n"); + + // Process some packets to populate program information + struct demuxer_data *data = NULL; + int i, ret = 0; + + // Read enough packets to detect program info (PAT and PMT) + // Usually this information appears early in the stream + for (i = 0; i < 2000 && !ctx->demux_ctx->nb_program; i++) + { + ret = ts_readstream(ctx->demux_ctx, &data); + if (ret == CCX_EOF) + break; + if (data) + delete_demuxer_data(data); + data = NULL; + } + + // Now list the tracks with the populated program information + list_ts_tracks(ctx); + return EXIT_OK; + } + + if (!api_options.use_gop_as_pts) // If !0 then the user selected something + api_options.use_gop_as_pts = 0; + if (api_options.ignore_pts_jumps) + ccx_common_timing_settings.disable_sync_check = 1; + mprint("\rAnalyzing data in general mode\n"); + tmp = general_loop(ctx); + if (!ret) + ret = tmp; + break; + + default: + mprint("\nTrack listing is only supported for MKV, MP4 and TS files.\n"); + return EXIT_OK; + } + } // Disable sync check for raw formats - they have the right timeline. // Also true for bin formats, but -nosync might have created a diff --git a/src/lib_ccx/ccx_encoders_common.c b/src/lib_ccx/ccx_encoders_common.c index c2540dd10..6ab4bc776 100644 --- a/src/lib_ccx/ccx_encoders_common.c +++ b/src/lib_ccx/ccx_encoders_common.c @@ -851,7 +851,7 @@ static int init_output_ctx(struct encoder_ctx *ctx, struct encoder_cfg *cfg) ctx->out[0].filename = NULL; ctx->out[0].with_semaphore = 0; ctx->out[0].semaphore_filename = NULL; - mprint("Sending captions to stdout.\n"); + // mprint("Sending captions to stdout.\n"); } if (cfg->send_to_srv == CCX_TRUE) diff --git a/src/lib_ccx/lib_ccx.h b/src/lib_ccx/lib_ccx.h index a765ae8f9..b9e1144d4 100644 --- a/src/lib_ccx/lib_ccx.h +++ b/src/lib_ccx/lib_ccx.h @@ -246,6 +246,7 @@ void parse_EPG_packet (struct lib_ccx_ctx *ctx); void EPG_free(struct lib_ccx_ctx *ctx); char* EPG_DVB_decode_string(uint8_t *in, size_t size); void parse_SDT(struct ccx_demuxer *ctx); +void list_ts_tracks(struct lib_ccx_ctx *ctx); // ts_info.c int get_video_stream(struct ccx_demuxer *ctx); diff --git a/src/lib_ccx/mp4.c b/src/lib_ccx/mp4.c index 05df43fe0..dc122962c 100644 --- a/src/lib_ccx/mp4.c +++ b/src/lib_ccx/mp4.c @@ -538,264 +538,347 @@ static int process_tx3g(struct lib_ccx_ctx *ctx, struct encoder_ctx *enc_ctx, } */ + int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) { - int mp4_ret = 0; - GF_ISOFile *f; - u32 i, j, track_count, avc_track_count, cc_track_count; - struct cc_subtitle dec_sub; - struct lib_cc_decode *dec_ctx = NULL; - struct encoder_ctx *enc_ctx = update_encoder_list(ctx); - - dec_ctx = update_decoder_list(ctx); - - if (enc_ctx) - enc_ctx->timing = dec_ctx->timing; - - memset(&dec_sub, 0, sizeof(dec_sub)); - mprint("Opening \'%s\': ", file); + int mp4_ret = 0; + GF_ISOFile *f; + u32 i, j, track_count, avc_track_count, cc_track_count; + struct cc_subtitle dec_sub; + struct lib_cc_decode *dec_ctx = NULL; + struct encoder_ctx *enc_ctx = update_encoder_list(ctx); + + dec_ctx = update_decoder_list(ctx); + + if (enc_ctx) + enc_ctx->timing = dec_ctx->timing; + + memset(&dec_sub, 0, sizeof(dec_sub)); + + // Only show opening message if not in list_tracks mode + if (!ccx_options.list_tracks_only) + mprint("Opening \'%s\': ", file); + #ifdef MP4_DEBUG - gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_DEBUG); + gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_DEBUG); #endif - if ((f = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL)) == NULL) - { - mprint("Failed to open input file (gf_isom_open() returned error)\n"); - free(dec_ctx->xds_ctx); - return -2; - } - - mprint("ok\n"); - - track_count = gf_isom_get_track_count(f); - - avc_track_count = 0; - cc_track_count = 0; - - for (i = 0; i < track_count; i++) - { - const u32 type = gf_isom_get_media_type(f, i + 1); - const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); - mprint("Track %d, type=%c%c%c%c subtype=%c%c%c%c\n", i + 1, (unsigned char)(type >> 24 % 0x100), - (unsigned char)((type >> 16) % 0x100), (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), - (unsigned char)(subtype >> 24 % 0x100), - (unsigned char)((subtype >> 16) % 0x100), (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); - if (type == GF_ISOM_MEDIA_CLOSED_CAPTION || type == GF_ISOM_MEDIA_SUBT || type == GF_ISOM_MEDIA_TEXT) - cc_track_count++; - if (type == GF_ISOM_MEDIA_VISUAL && subtype == GF_ISOM_SUBTYPE_AVC_H264) - avc_track_count++; - } - - mprint("MP4: found %u tracks: %u avc and %u cc\n", track_count, avc_track_count, cc_track_count); - - for (i = 0; i < track_count; i++) - { - const u32 type = gf_isom_get_media_type(f, i + 1); - const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); - mprint("Processing track %d, type=%c%c%c%c subtype=%c%c%c%c\n", i + 1, - (unsigned char)(type >> 24 % 0x100), (unsigned char)((type >> 16) % 0x100), - (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), - (unsigned char)(subtype >> 24 % 0x100), (unsigned char)((subtype >> 16) % 0x100), - (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); - - const u64 track_type = MEDIA_TYPE(type, subtype); - - switch (track_type) - { - case MEDIA_TYPE(GF_ISOM_MEDIA_VISUAL, GF_ISOM_SUBTYPE_XDVB): // vide:xdvb - if (cc_track_count && !cfg->mp4vidtrack) - continue; - // If there are multiple tracks, change fd for different tracks - if (avc_track_count > 1) - { - switch_output_file(ctx, enc_ctx, i); - } - if (process_xdvb_track(ctx, file, f, i + 1, &dec_sub) != 0) - { - mprint("Error on process_xdvb_track()\n"); - free(dec_ctx->xds_ctx); - return -3; - } - if (dec_sub.got_output) - { - mp4_ret = 1; - encode_sub(enc_ctx, &dec_sub); - dec_sub.got_output = 0; - } - break; - - case MEDIA_TYPE(GF_ISOM_MEDIA_VISUAL, GF_ISOM_SUBTYPE_AVC_H264): // vide:avc1 - if (cc_track_count && !cfg->mp4vidtrack) - continue; - // If there are multiple tracks, change fd for different tracks - if (avc_track_count > 1) - { - switch_output_file(ctx, enc_ctx, i); - } - GF_AVCConfig *cnf = gf_isom_avc_config_get(f, i + 1, 1); - if (cnf != NULL) - { - for (j = 0; j < gf_list_count(cnf->sequenceParameterSets); j++) - { - GF_AVCConfigSlot *seqcnf = (GF_AVCConfigSlot *)gf_list_get(cnf->sequenceParameterSets, j); - do_NAL(enc_ctx, dec_ctx, (unsigned char *)seqcnf->data, seqcnf->size, &dec_sub); - } - gf_odf_avc_cfg_del(cnf); - } - if (process_avc_track(ctx, file, f, i + 1, &dec_sub) != 0) - { - mprint("Error on process_avc_track()\n"); - free(dec_ctx->xds_ctx); - return -3; - } - if (dec_sub.got_output) - { - mp4_ret = 1; - encode_sub(enc_ctx, &dec_sub); - dec_sub.got_output = 0; - } - break; - - default: - if (type != GF_ISOM_MEDIA_CLOSED_CAPTION && type != GF_ISOM_MEDIA_SUBT && type != GF_ISOM_MEDIA_TEXT) - break; // ignore non cc track - - if (avc_track_count && cfg->mp4vidtrack) - continue; - // If there are multiple tracks, change fd for different tracks - if (cc_track_count > 1) - { - switch_output_file(ctx, enc_ctx, i); - } - unsigned num_samples = gf_isom_get_sample_count(f, i + 1); - - u32 ProcessingStreamDescriptionIndex = 0; // Current track we are processing, 0 = we don't know yet - u32 timescale = gf_isom_get_media_timescale(f, i + 1); + if ((f = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL)) == NULL) + { + mprint("Failed to open input file (gf_isom_open() returned error)\n"); + free(dec_ctx->xds_ctx); + return -2; + } + + if (!ccx_options.list_tracks_only) + mprint("ok\n"); + + track_count = gf_isom_get_track_count(f); + avc_track_count = 0; + cc_track_count = 0; + + // First pass: count tracks by type + for (i = 0; i < track_count; i++) + { + const u32 type = gf_isom_get_media_type(f, i + 1); + const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); + + if (type == GF_ISOM_MEDIA_CLOSED_CAPTION || type == GF_ISOM_MEDIA_SUBT || type == GF_ISOM_MEDIA_TEXT) + cc_track_count++; + if (type == GF_ISOM_MEDIA_VISUAL && subtype == GF_ISOM_SUBTYPE_AVC_H264) + avc_track_count++; + } + + // If in track listing mode, show track information and exit + if (ccx_options.list_tracks_only) + { + mprint("\n"); + mprint("Available tracks in input file:\n"); + mprint("------------------------------\n"); + + for (i = 0; i < track_count; i++) + { + const u32 type = gf_isom_get_media_type(f, i + 1); + const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); + + mprint("Track %d: ", i + 1); + + // Get descriptive track type + if (type == GF_ISOM_MEDIA_VISUAL) + { + if (subtype == GF_ISOM_SUBTYPE_AVC_H264) + mprint("Type: H.264 Video"); + else if (subtype == GF_ISOM_SUBTYPE_XDVB) + mprint("Type: XDVB Video"); + else + mprint("Type: Video"); + } + else if (type == GF_ISOM_MEDIA_AUDIO) + { + mprint("Type: Audio"); + } + else if (type == GF_ISOM_MEDIA_CLOSED_CAPTION) + { + if (subtype == GF_QT_SUBTYPE_C608) + mprint("Type: CEA-608 Closed Caption"); + else if (subtype == GF_ISOM_SUBTYPE_C708) + mprint("Type: CEA-708 Closed Caption"); + else + mprint("Type: Closed Caption"); + } + else if (type == GF_ISOM_MEDIA_SUBT || type == GF_ISOM_MEDIA_TEXT) + { + if (subtype == GF_ISOM_SUBTYPE_TX3G) + mprint("Type: TX3G Subtitle"); + else if (subtype == GF_ISOM_SUBTYPE_TEXT) + mprint("Type: Text Subtitle"); + else + mprint("Type: Subtitle"); + } + else + { + mprint("Type: Other (%c%c%c%c/%c%c%c%c)", + (unsigned char)(type >> 24 % 0x100), + (unsigned char)((type >> 16) % 0x100), + (unsigned char)((type >> 8) % 0x100), + (unsigned char)(type % 0x100), + (unsigned char)(subtype >> 24 % 0x100), + (unsigned char)((subtype >> 16) % 0x100), + (unsigned char)((subtype >> 8) % 0x100), + (unsigned char)(subtype % 0x100)); + } + + mprint("\n"); + } + + mprint("\nTrack listing completed. Exiting as requested.\n"); + gf_isom_close(f); + freep(&dec_ctx->xds_ctx); + return 0; + } + + // Regular processing for normal mode follows + + for (i = 0; i < track_count; i++) + { + const u32 type = gf_isom_get_media_type(f, i + 1); + const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); + mprint("Track %d, type=%c%c%c%c subtype=%c%c%c%c\n", i + 1, (unsigned char)(type >> 24 % 0x100), + (unsigned char)((type >> 16) % 0x100), (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), + (unsigned char)(subtype >> 24 % 0x100), + (unsigned char)((subtype >> 16) % 0x100), (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); + } + + mprint("MP4: found %u tracks: %u avc and %u cc\n", track_count, avc_track_count, cc_track_count); + + for (i = 0; i < track_count; i++) + { + const u32 type = gf_isom_get_media_type(f, i + 1); + const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); + mprint("Processing track %d, type=%c%c%c%c subtype=%c%c%c%c\n", i + 1, + (unsigned char)(type >> 24 % 0x100), (unsigned char)((type >> 16) % 0x100), + (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), + (unsigned char)(subtype >> 24 % 0x100), (unsigned char)((subtype >> 16) % 0x100), + (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); + + const u64 track_type = MEDIA_TYPE(type, subtype); + + switch (track_type) + { + case MEDIA_TYPE(GF_ISOM_MEDIA_VISUAL, GF_ISOM_SUBTYPE_XDVB): // vide:xdvb + if (cc_track_count && !cfg->mp4vidtrack) + continue; + // If there are multiple tracks, change fd for different tracks + if (avc_track_count > 1) + { + switch_output_file(ctx, enc_ctx, i); + } + if (process_xdvb_track(ctx, file, f, i + 1, &dec_sub) != 0) + { + mprint("Error on process_xdvb_track()\n"); + free(dec_ctx->xds_ctx); + return -3; + } + if (dec_sub.got_output) + { + mp4_ret = 1; + encode_sub(enc_ctx, &dec_sub); + dec_sub.got_output = 0; + } + break; + + case MEDIA_TYPE(GF_ISOM_MEDIA_VISUAL, GF_ISOM_SUBTYPE_AVC_H264): // vide:avc1 + if (cc_track_count && !cfg->mp4vidtrack) + continue; + // If there are multiple tracks, change fd for different tracks + if (avc_track_count > 1) + { + switch_output_file(ctx, enc_ctx, i); + } + GF_AVCConfig *cnf = gf_isom_avc_config_get(f, i + 1, 1); + if (cnf != NULL) + { + for (j = 0; j < gf_list_count(cnf->sequenceParameterSets); j++) + { + GF_AVCConfigSlot *seqcnf = (GF_AVCConfigSlot *)gf_list_get(cnf->sequenceParameterSets, j); + do_NAL(enc_ctx, dec_ctx, (unsigned char *)seqcnf->data, seqcnf->size, &dec_sub); + } + gf_odf_avc_cfg_del(cnf); + } + if (process_avc_track(ctx, file, f, i + 1, &dec_sub) != 0) + { + mprint("Error on process_avc_track()\n"); + free(dec_ctx->xds_ctx); + return -3; + } + if (dec_sub.got_output) + { + mp4_ret = 1; + encode_sub(enc_ctx, &dec_sub); + dec_sub.got_output = 0; + } + break; + + default: + if (type != GF_ISOM_MEDIA_CLOSED_CAPTION && type != GF_ISOM_MEDIA_SUBT && type != GF_ISOM_MEDIA_TEXT) + break; // ignore non cc track + + if (avc_track_count && cfg->mp4vidtrack) + continue; + // If there are multiple tracks, change fd for different tracks + if (cc_track_count > 1) + { + switch_output_file(ctx, enc_ctx, i); + } + unsigned num_samples = gf_isom_get_sample_count(f, i + 1); + + u32 ProcessingStreamDescriptionIndex = 0; // Current track we are processing, 0 = we don't know yet + u32 timescale = gf_isom_get_media_timescale(f, i + 1); #ifdef MP4_DEBUG - unsigned num_streams = gf_isom_get_sample_description_count(f, i + 1); - u64 duration = gf_isom_get_media_duration(f, i + 1); - mprint("%u streams\n", num_streams); - mprint("%u sample counts\n", num_samples); - mprint("%u timescale\n", (unsigned)timescale); - mprint("%u duration\n", (unsigned)duration); + unsigned num_streams = gf_isom_get_sample_description_count(f, i + 1); + u64 duration = gf_isom_get_media_duration(f, i + 1); + mprint("%u streams\n", num_streams); + mprint("%u sample counts\n", num_samples); + mprint("%u timescale\n", (unsigned)timescale); + mprint("%u duration\n", (unsigned)duration); #endif - for (unsigned k = 0; k < num_samples; k++) - { - u32 StreamDescriptionIndex; - GF_ISOSample *sample = gf_isom_get_sample(f, i + 1, k + 1, &StreamDescriptionIndex); - if (ProcessingStreamDescriptionIndex && ProcessingStreamDescriptionIndex != StreamDescriptionIndex) - { - mprint("This sample seems to have more than one description. This isn't supported yet.\n"); - mprint("Submitting this video file will help add support to this case.\n"); - break; - } - if (!ProcessingStreamDescriptionIndex) - ProcessingStreamDescriptionIndex = StreamDescriptionIndex; - if (sample == NULL) - continue; + for (unsigned k = 0; k < num_samples; k++) + { + u32 StreamDescriptionIndex; + GF_ISOSample *sample = gf_isom_get_sample(f, i + 1, k + 1, &StreamDescriptionIndex); + if (ProcessingStreamDescriptionIndex && ProcessingStreamDescriptionIndex != StreamDescriptionIndex) + { + mprint("This sample seems to have more than one description. This isn't supported yet.\n"); + mprint("Submitting this video file will help add support to this case.\n"); + break; + } + if (!ProcessingStreamDescriptionIndex) + ProcessingStreamDescriptionIndex = StreamDescriptionIndex; + if (sample == NULL) + continue; #ifdef MP4_DEBUG - mprint("Data length: %lu\n", sample->dataLength); - const LLONG timestamp = (LLONG)((sample->DTS + sample->CTS_Offset) * 1000) / timescale; + mprint("Data length: %lu\n", sample->dataLength); + const LLONG timestamp = (LLONG)((sample->DTS + sample->CTS_Offset) * 1000) / timescale; #endif - set_current_pts(dec_ctx->timing, (sample->DTS + sample->CTS_Offset) * MPEG_CLOCK_FREQ / timescale); - set_fts(dec_ctx->timing); - - int atomStart = 0; - // Process Atom by Atom - while (atomStart < sample->dataLength) - { - char *data = sample->data + atomStart; - size_t atom_length = -1; - switch (track_type) - { - case MEDIA_TYPE(GF_ISOM_MEDIA_TEXT, GF_ISOM_SUBTYPE_TX3G): // text:tx3g - case MEDIA_TYPE(GF_ISOM_MEDIA_SUBT, GF_ISOM_SUBTYPE_TX3G): // sbtl:tx3g (they're the same) - atom_length = process_tx3g(ctx, enc_ctx, dec_ctx, - &dec_sub, &mp4_ret, - data, sample->dataLength, 0); - break; - case MEDIA_TYPE(GF_ISOM_MEDIA_CLOSED_CAPTION, GF_QT_SUBTYPE_C608): // clcp:c608 - case MEDIA_TYPE(GF_ISOM_MEDIA_CLOSED_CAPTION, GF_ISOM_SUBTYPE_C708): // clcp:c708 - atom_length = process_clcp(ctx, enc_ctx, dec_ctx, - &dec_sub, &mp4_ret, subtype, - data, sample->dataLength); - break; - case MEDIA_TYPE(GF_ISOM_MEDIA_TEXT, GF_ISOM_SUBTYPE_TEXT): // text:text - { - static int unsupported_reported = 0; - if (!unsupported_reported) - { - mprint("\ntext:text not yet supported, see\n"); - mprint("https://developer.apple.com/library/mac/documentation/quicktime/qtff/QTFFChap3/qtff3.html\n"); - mprint("If you want to work on a PR.\n"); - unsupported_reported = 1; - } - break; - } - - default: - // See https://gpac.wp.imt.fr/2014/09/04/subtitling-with-gpac/ for more possible types - mprint("\nUnsupported track type \"%c%c%c%c:%c%c%c%c\". Please report.\n", - (unsigned char)(type >> 24 % 0x100), (unsigned char)((type >> 16) % 0x100), - (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), - (unsigned char)(subtype >> 24 % 0x100), (unsigned char)((subtype >> 16) % 0x100), - (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); - } - if (atom_length == -1) - break; // error happened or process of the sample is finished - atomStart += atom_length; - } - free(sample->data); - free(sample); - - // End of change - int progress = (int)((k * 100) / num_samples); - if (ctx->last_reported_progress != progress) - { - int cur_sec = (int)(get_fts(dec_ctx->timing, dec_ctx->current_field) / 1000); - activity_progress(progress, cur_sec / 60, cur_sec % 60); - ctx->last_reported_progress = progress; - } - } - - // Encode the last subtitle - if (subtype == GF_ISOM_SUBTYPE_TX3G) - { - process_tx3g(ctx, enc_ctx, dec_ctx, - &dec_sub, &mp4_ret, - NULL, 0, 1); - } - - int cur_sec = (int)(get_fts(dec_ctx->timing, dec_ctx->current_field) / 1000); - activity_progress(100, cur_sec / 60, cur_sec % 60); - } - } - - freep(&dec_ctx->xds_ctx); - - mprint("\nClosing media: "); - gf_isom_close(f); - f = NULL; - mprint("ok\n"); - - if (avc_track_count) - mprint("Found %d AVC track(s). ", avc_track_count); - else - mprint("Found no AVC track(s). "); - - if (cc_track_count) - mprint("Found %d CC track(s).\n", cc_track_count); - else - mprint("Found no dedicated CC track(s).\n"); - - ctx->freport.mp4_cc_track_cnt = cc_track_count; - - if ((dec_ctx->write_format == CCX_OF_MCC) && (dec_ctx->saw_caption_block == CCX_TRUE)) - { - mp4_ret = 1; - } - - return mp4_ret; + set_current_pts(dec_ctx->timing, (sample->DTS + sample->CTS_Offset) * MPEG_CLOCK_FREQ / timescale); + set_fts(dec_ctx->timing); + + int atomStart = 0; + // Process Atom by Atom + while (atomStart < sample->dataLength) + { + char *data = sample->data + atomStart; + size_t atom_length = -1; + switch (track_type) + { + case MEDIA_TYPE(GF_ISOM_MEDIA_TEXT, GF_ISOM_SUBTYPE_TX3G): // text:tx3g + case MEDIA_TYPE(GF_ISOM_MEDIA_SUBT, GF_ISOM_SUBTYPE_TX3G): // sbtl:tx3g (they're the same) + atom_length = process_tx3g(ctx, enc_ctx, dec_ctx, + &dec_sub, &mp4_ret, + data, sample->dataLength, 0); + break; + case MEDIA_TYPE(GF_ISOM_MEDIA_CLOSED_CAPTION, GF_QT_SUBTYPE_C608): // clcp:c608 + case MEDIA_TYPE(GF_ISOM_MEDIA_CLOSED_CAPTION, GF_ISOM_SUBTYPE_C708): // clcp:c708 + atom_length = process_clcp(ctx, enc_ctx, dec_ctx, + &dec_sub, &mp4_ret, subtype, + data, sample->dataLength); + break; + case MEDIA_TYPE(GF_ISOM_MEDIA_TEXT, GF_ISOM_SUBTYPE_TEXT): // text:text + { + static int unsupported_reported = 0; + if (!unsupported_reported) + { + mprint("\ntext:text not yet supported, see\n"); + mprint("https://developer.apple.com/library/mac/documentation/quicktime/qtff/QTFFChap3/qtff3.html\n"); + mprint("If you want to work on a PR.\n"); + unsupported_reported = 1; + } + break; + } + + default: + // See https://gpac.wp.imt.fr/2014/09/04/subtitling-with-gpac/ for more possible types + mprint("\nUnsupported track type \"%c%c%c%c:%c%c%c%c\". Please report.\n", + (unsigned char)(type >> 24 % 0x100), (unsigned char)((type >> 16) % 0x100), + (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), + (unsigned char)(subtype >> 24 % 0x100), (unsigned char)((subtype >> 16) % 0x100), + (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); + } + if (atom_length == -1) + break; // error happened or process of the sample is finished + atomStart += atom_length; + } + free(sample->data); + free(sample); + + // End of change + int progress = (int)((k * 100) / num_samples); + if (ctx->last_reported_progress != progress) + { + int cur_sec = (int)(get_fts(dec_ctx->timing, dec_ctx->current_field) / 1000); + activity_progress(progress, cur_sec / 60, cur_sec % 60); + ctx->last_reported_progress = progress; + } + } + + // Encode the last subtitle + if (subtype == GF_ISOM_SUBTYPE_TX3G) + { + process_tx3g(ctx, enc_ctx, dec_ctx, + &dec_sub, &mp4_ret, + NULL, 0, 1); + } + + int cur_sec = (int)(get_fts(dec_ctx->timing, dec_ctx->current_field) / 1000); + activity_progress(100, cur_sec / 60, cur_sec % 60); + } + } + + freep(&dec_ctx->xds_ctx); + + mprint("\nClosing media: "); + gf_isom_close(f); + f = NULL; + mprint("ok\n"); + + if (avc_track_count) + mprint("Found %d AVC track(s). ", avc_track_count); + else + mprint("Found no AVC track(s). "); + + if (cc_track_count) + mprint("Found %d CC track(s).\n", cc_track_count); + else + mprint("Found no dedicated CC track(s).\n"); + + ctx->freport.mp4_cc_track_cnt = cc_track_count; + + if ((dec_ctx->write_format == CCX_OF_MCC) && (dec_ctx->saw_caption_block == CCX_TRUE)) + { + mp4_ret = 1; + } + + return mp4_ret; } int dumpchapters(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) diff --git a/src/lib_ccx/ts_functions.c b/src/lib_ccx/ts_functions.c index 624093f48..0f62659d4 100644 --- a/src/lib_ccx/ts_functions.c +++ b/src/lib_ccx/ts_functions.c @@ -982,3 +982,60 @@ int ts_get_more_data(struct lib_ccx_ctx *ctx, struct demuxer_data **data) return ret; } + +void list_ts_tracks(struct lib_ccx_ctx *ctx) +{ + struct ccx_demuxer *ctx_demux = ctx->demux_ctx; + int i; + + mprint("\n"); + mprint("Available tracks in input file:\n"); + mprint("------------------------------\n"); + + // Display program information + for (i = 0; i < ctx_demux->nb_program; i++) + { + struct program_info *pinfo = &ctx_demux->pinfo[i]; + + if (pinfo->program_number == -1) + continue; + + mprint("Program: %d\n", pinfo->program_number); + + // PCR PID + if (pinfo->pcr_pid) + { + mprint(" PCR: PID: %u\n", pinfo->pcr_pid); + } + + // Find caption tracks by searching through PIDs + for (int j = 0; j <= MAX_PSI_PID; j++) + { + if (ctx_demux->PIDs_programs[j] && + ctx_demux->PIDs_programs[j]->program_number == pinfo->program_number) + { + struct cap_info *cinfo = get_cinfo(ctx_demux, j); + if (cinfo) + { + char *stream_type_name = get_buffer_type_str(cinfo); + + mprint(" Track %d: Type: %s, PID: %u\n", + j, + stream_type_name ? stream_type_name : "Subtitle/Caption", + j); + + if (stream_type_name) + free(stream_type_name); + } + } + } + } + + if (ctx_demux->nb_program == 0 || + (ctx_demux->nb_program == 1 && ctx_demux->pinfo[0].program_number == -1)) + { + mprint("No program information found in the stream.\n"); + } + + mprint("\nTrack listing completed. Exiting as requested.\n"); +} diff --git a/src/rust/lib_ccxr/src/common/options.rs b/src/rust/lib_ccxr/src/common/options.rs index 489605c44..d6cab03ed 100644 --- a/src/rust/lib_ccxr/src/common/options.rs +++ b/src/rust/lib_ccxr/src/common/options.rs @@ -384,6 +384,8 @@ pub struct Options { pub use_gop_as_pts: Option, /// Replace 0000 with 8080 in HDTV (needed for some cards) pub fix_padding: bool, + /// If true, only list tracks, don't process them + pub list_tracks_only: bool, /// If true, output in stderr progress updates so the GUI can grab them pub gui_mode_reports: bool, /// If true, suppress the output of the progress to stdout @@ -565,6 +567,7 @@ impl Default for Options { messages_target: Default::default(), timestamp_map: Default::default(), dolevdist: true, + list_tracks_only: false, levdistmincnt: 2, levdistmaxpct: 10, investigate_packets: Default::default(), diff --git a/src/rust/src/args.rs b/src/rust/src/args.rs index 92a656202..e9e777b0e 100644 --- a/src/rust/src/args.rs +++ b/src/rust/src/args.rs @@ -359,6 +359,9 @@ pub struct Args { /// the first one we find that contains a suitable stream. #[arg(long, verbatim_doc_comment, help_heading=OPTIONS_AFFECTING_INPUT_FILES)] pub autoprogram: bool, + /// List tracks in input file and exit + #[arg(long = "list-tracks", help_heading = OPTION_AFFECT_PROCESSED)] + pub list_tracks: bool, /// Uses multiple programs from the same input stream. #[arg(long, verbatim_doc_comment, help_heading=OPTIONS_AFFECTING_INPUT_FILES)] pub multiprogram: bool, diff --git a/src/rust/src/common.rs b/src/rust/src/common.rs index b182c5020..4f32b009d 100644 --- a/src/rust/src/common.rs +++ b/src/rust/src/common.rs @@ -62,6 +62,7 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options (*ccx_s_options).webvtt_create_css = options.webvtt_create_css as _; (*ccx_s_options).cc_channel = options.cc_channel as _; (*ccx_s_options).buffer_input = options.buffer_input as _; + (*ccx_s_options).list_tracks_only = options.list_tracks_only as _; (*ccx_s_options).nofontcolor = options.nofontcolor as _; (*ccx_s_options).write_format = options.write_format.to_ctype(); (*ccx_s_options).send_to_srv = options.send_to_srv as _; diff --git a/src/rust/src/parser.rs b/src/rust/src/parser.rs index d3833fbaa..c8b298400 100644 --- a/src/rust/src/parser.rs +++ b/src/rust/src/parser.rs @@ -674,6 +674,10 @@ impl OptionsExt for Options { if args.chapters { self.extract_chapters = true; } + + if args.list_tracks { + self.list_tracks_only = true; + } if args.bufferinput { self.buffer_input = true; From ab70c12064dd3cc3184c78ed6d6a7b54cf6b89d4 Mon Sep 17 00:00:00 2001 From: tank0nf Date: Sat, 1 Mar 2025 22:03:30 +0530 Subject: [PATCH 3/3] added fix for workflow --- src/ccextractor.c | 330 +++++++++--------- src/lib_ccx/matroska.c | 173 +++++----- src/lib_ccx/mp4.c | 662 ++++++++++++++++++------------------- src/lib_ccx/params.c | 4 +- src/lib_ccx/ts_functions.c | 106 +++--- src/rust/src/parser.rs | 2 +- 6 files changed, 638 insertions(+), 639 deletions(-) diff --git a/src/ccextractor.c b/src/ccextractor.c index ecbd7212e..c42ad37bb 100644 --- a/src/ccextractor.c +++ b/src/ccextractor.c @@ -40,214 +40,214 @@ void print_end_msg(void) int api_start(struct ccx_s_options api_options) { - struct lib_ccx_ctx *ctx = NULL; // Context for libs - struct lib_cc_decode *dec_ctx = NULL; // Context for decoder - int ret = 0, tmp = 0; - enum ccx_stream_mode_enum stream_mode = CCX_SM_ELEMENTARY_OR_NOT_FOUND; + struct lib_ccx_ctx *ctx = NULL; // Context for libs + struct lib_cc_decode *dec_ctx = NULL; // Context for decoder + int ret = 0, tmp = 0; + enum ccx_stream_mode_enum stream_mode = CCX_SM_ELEMENTARY_OR_NOT_FOUND; #if defined(ENABLE_OCR) && defined(_WIN32) - setMsgSeverity(LEPT_MSG_SEVERITY); + setMsgSeverity(LEPT_MSG_SEVERITY); #endif - // Initialize CCExtractor libraries - ctx = init_libraries(&api_options); - - if (!ctx) - { - if (errno == ENOMEM) - fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory, could not initialize libraries\n"); - else if (errno == EINVAL) - fatal(CCX_COMMON_EXIT_BUG_BUG, "Invalid option to CCextractor Library\n"); - else if (errno == EPERM) - fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Operation not permitted.\n"); - else if (errno == EACCES) - fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Permission denied\n"); - else - fatal(EXIT_NOT_CLASSIFIED, "Unable to create Library Context %d\n", errno); - } + // Initialize CCExtractor libraries + ctx = init_libraries(&api_options); + + if (!ctx) + { + if (errno == ENOMEM) + fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory, could not initialize libraries\n"); + else if (errno == EINVAL) + fatal(CCX_COMMON_EXIT_BUG_BUG, "Invalid option to CCextractor Library\n"); + else if (errno == EPERM) + fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Operation not permitted.\n"); + else if (errno == EACCES) + fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Permission denied\n"); + else + fatal(EXIT_NOT_CLASSIFIED, "Unable to create Library Context %d\n", errno); + } #ifdef ENABLE_HARDSUBX - if (api_options.hardsubx) - { - // Perform burned in subtitle extraction - hardsubx(&api_options, ctx); - return 0; - } + if (api_options.hardsubx) + { + // Perform burned in subtitle extraction + hardsubx(&api_options, ctx); + return 0; + } #endif #ifdef WITH_LIBCURL - curl_global_init(CURL_GLOBAL_ALL); - - /* get a curl handle */ - curl = curl_easy_init(); - if (!curl) - { - curl_global_cleanup(); // Must be done even on init fail - fatal(EXIT_NOT_CLASSIFIED, "Unable to init curl."); - } + curl_global_init(CURL_GLOBAL_ALL); + + /* get a curl handle */ + curl = curl_easy_init(); + if (!curl) + { + curl_global_cleanup(); // Must be done even on init fail + fatal(EXIT_NOT_CLASSIFIED, "Unable to init curl."); + } #endif - int show_myth_banner = 0; - - // Only show params dump if we're not just listing tracks - // if (!api_options.list_tracks_only) - params_dump(ctx); - - // default teletext page - if (tlt_config.page > 0) - { - // dec to BCD, magazine pages numbers are in BCD (ETSI 300 706) - tlt_config.page = ((tlt_config.page / 100) << 8) | (((tlt_config.page / 10) % 10) << 4) | (tlt_config.page % 10); - } - - if (api_options.transcript_settings.xds) - { - if (api_options.write_format != CCX_OF_TRANSCRIPT) - { - api_options.transcript_settings.xds = 0; - mprint("Warning: -xds ignored, XDS can only be exported to transcripts at this time.\n"); - } - } - - time_t start, final; - time(&start); - - if (api_options.binary_concat) - { - ctx->total_inputsize = get_total_file_size(ctx); - if (ctx->total_inputsize < 0) - { - switch (ctx->total_inputsize) - { - case -1 * ENOENT: - fatal(EXIT_NO_INPUT_FILES, "Failed to open one of the input file(s): File does not exist."); - case -1 * EACCES: - fatal(EXIT_READ_ERROR, "Failed to open input file: Unable to access"); - case -1 * EINVAL: - fatal(EXIT_READ_ERROR, "Failed to open input file: Invalid opening flag."); - case -1 * EMFILE: - fatal(EXIT_TOO_MANY_INPUT_FILES, "Failed to open input file: Too many files are open."); - default: - fatal(EXIT_READ_ERROR, "Failed to open input file: Reason unknown"); - } - } - } + int show_myth_banner = 0; + + // Only show params dump if we're not just listing tracks + // if (!api_options.list_tracks_only) + params_dump(ctx); + + // default teletext page + if (tlt_config.page > 0) + { + // dec to BCD, magazine pages numbers are in BCD (ETSI 300 706) + tlt_config.page = ((tlt_config.page / 100) << 8) | (((tlt_config.page / 10) % 10) << 4) | (tlt_config.page % 10); + } + + if (api_options.transcript_settings.xds) + { + if (api_options.write_format != CCX_OF_TRANSCRIPT) + { + api_options.transcript_settings.xds = 0; + mprint("Warning: -xds ignored, XDS can only be exported to transcripts at this time.\n"); + } + } + + time_t start, final; + time(&start); + + if (api_options.binary_concat) + { + ctx->total_inputsize = get_total_file_size(ctx); + if (ctx->total_inputsize < 0) + { + switch (ctx->total_inputsize) + { + case -1 * ENOENT: + fatal(EXIT_NO_INPUT_FILES, "Failed to open one of the input file(s): File does not exist."); + case -1 * EACCES: + fatal(EXIT_READ_ERROR, "Failed to open input file: Unable to access"); + case -1 * EINVAL: + fatal(EXIT_READ_ERROR, "Failed to open input file: Invalid opening flag."); + case -1 * EMFILE: + fatal(EXIT_TOO_MANY_INPUT_FILES, "Failed to open input file: Too many files are open."); + default: + fatal(EXIT_READ_ERROR, "Failed to open input file: Reason unknown"); + } + } + } #ifndef _WIN32 - signal_ctx = ctx; - m_signal(SIGINT, sigint_handler); - m_signal(SIGTERM, sigterm_handler); - m_signal(SIGUSR1, sigusr1_handler); + signal_ctx = ctx; + m_signal(SIGINT, sigint_handler); + m_signal(SIGTERM, sigterm_handler); + m_signal(SIGUSR1, sigusr1_handler); #endif - terminate_asap = 0; + terminate_asap = 0; #ifdef ENABLE_SHARING - if (api_options.translate_enabled && ctx->num_input_files > 1) - { - mprint("[share] WARNING: simultaneous translation of several input files is not supported yet\n"); - api_options.translate_enabled = 0; - api_options.sharing_enabled = 0; - } - if (api_options.translate_enabled) - { - mprint("[share] launching translate service\n"); - ccx_share_launch_translator(api_options.translate_langs, api_options.translate_key); - } + if (api_options.translate_enabled && ctx->num_input_files > 1) + { + mprint("[share] WARNING: simultaneous translation of several input files is not supported yet\n"); + api_options.translate_enabled = 0; + api_options.sharing_enabled = 0; + } + if (api_options.translate_enabled) + { + mprint("[share] launching translate service\n"); + ccx_share_launch_translator(api_options.translate_langs, api_options.translate_key); + } #endif // ENABLE_SHARING - ret = 0; - while (switch_to_next_file(ctx, 0)) - { - prepare_for_new_file(ctx); + ret = 0; + while (switch_to_next_file(ctx, 0)) + { + prepare_for_new_file(ctx); #ifdef ENABLE_SHARING - if (api_options.sharing_enabled) - ccx_share_start(ctx->basefilename); + if (api_options.sharing_enabled) + ccx_share_start(ctx->basefilename); #endif // ENABLE_SHARING - stream_mode = ctx->demux_ctx->get_stream_mode(ctx->demux_ctx); - - // Check if we're listing tracks and we have an MKV file + stream_mode = ctx->demux_ctx->get_stream_mode(ctx->demux_ctx); + + // Check if we're listing tracks and we have an MKV file if (api_options.list_tracks_only) { // Clear all output ccx_common_logging.debug_mask = 0; - + // Format-specific track listing switch (stream_mode) { case CCX_SM_MKV: ret = matroska_loop(ctx); return ret; - + case CCX_SM_MP4: // MP4 will be handled in its own case break; - + case CCX_SM_TRANSPORT: case CCX_SM_PROGRAM: - if (api_options.list_tracks_only) - { - mprint("\nCCExtractor Track Listing\n"); - mprint("------------------------\n"); - - // Process some packets to populate program information - struct demuxer_data *data = NULL; - int i, ret = 0; - - // Read enough packets to detect program info (PAT and PMT) - // Usually this information appears early in the stream - for (i = 0; i < 2000 && !ctx->demux_ctx->nb_program; i++) + if (api_options.list_tracks_only) { - ret = ts_readstream(ctx->demux_ctx, &data); - if (ret == CCX_EOF) - break; - if (data) - delete_demuxer_data(data); - data = NULL; + mprint("\nCCExtractor Track Listing\n"); + mprint("------------------------\n"); + + // Process some packets to populate program information + struct demuxer_data *data = NULL; + int i, ret = 0; + + // Read enough packets to detect program info (PAT and PMT) + // Usually this information appears early in the stream + for (i = 0; i < 2000 && !ctx->demux_ctx->nb_program; i++) + { + ret = ts_readstream(ctx->demux_ctx, &data); + if (ret == CCX_EOF) + break; + if (data) + delete_demuxer_data(data); + data = NULL; + } + + // Now list the tracks with the populated program information + list_ts_tracks(ctx); + return EXIT_OK; } - - // Now list the tracks with the populated program information - list_ts_tracks(ctx); - return EXIT_OK; - } - - if (!api_options.use_gop_as_pts) // If !0 then the user selected something - api_options.use_gop_as_pts = 0; - if (api_options.ignore_pts_jumps) - ccx_common_timing_settings.disable_sync_check = 1; - mprint("\rAnalyzing data in general mode\n"); - tmp = general_loop(ctx); - if (!ret) - ret = tmp; - break; - + + if (!api_options.use_gop_as_pts) // If !0 then the user selected something + api_options.use_gop_as_pts = 0; + if (api_options.ignore_pts_jumps) + ccx_common_timing_settings.disable_sync_check = 1; + mprint("\rAnalyzing data in general mode\n"); + tmp = general_loop(ctx); + if (!ret) + ret = tmp; + break; + default: mprint("\nTrack listing is only supported for MKV, MP4 and TS files.\n"); return EXIT_OK; } } - - // Disable sync check for raw formats - they have the right timeline. - // Also true for bin formats, but -nosync might have created a - // broken timeline for debug purposes. - // Disable too in MP4, specs doesn't say that there can't be a jump - switch (stream_mode) - { - case CCX_SM_MCPOODLESRAW: - case CCX_SM_RCWT: - case CCX_SM_MP4: + + // Disable sync check for raw formats - they have the right timeline. + // Also true for bin formats, but -nosync might have created a + // broken timeline for debug purposes. + // Disable too in MP4, specs doesn't say that there can't be a jump + switch (stream_mode) + { + case CCX_SM_MCPOODLESRAW: + case CCX_SM_RCWT: + case CCX_SM_MP4: #ifdef WTV_DEBUG - case CCX_SM_HEX_DUMP: + case CCX_SM_HEX_DUMP: #endif - ccx_common_timing_settings.disable_sync_check = 1; - break; - default: - break; - } - - /* ----------------------------------------------------------------- - MAIN LOOP - ----------------------------------------------------------------- */ - switch (stream_mode) - { + ccx_common_timing_settings.disable_sync_check = 1; + break; + default: + break; + } + + /* ----------------------------------------------------------------- + MAIN LOOP + ----------------------------------------------------------------- */ + switch (stream_mode) + { case CCX_SM_ELEMENTARY_OR_NOT_FOUND: if (!api_options.use_gop_as_pts) // If !0 then the user selected something api_options.use_gop_as_pts = 1; // Force GOP timing for ES diff --git a/src/lib_ccx/matroska.c b/src/lib_ccx/matroska.c index 766cbcbf5..b0517fa54 100644 --- a/src/lib_ccx/matroska.c +++ b/src/lib_ccx/matroska.c @@ -1359,95 +1359,94 @@ FILE *create_file(struct lib_ccx_ctx *ctx) void print_track_list(struct matroska_ctx *mkv_ctx) { - mprint("\n"); - mprint("Available tracks in input file:\n"); - mprint("------------------------------\n"); - - for (int i = 0; i < mkv_ctx->sub_tracks_count; i++) - { - struct matroska_sub_track *track = mkv_ctx->sub_tracks[i]; - - mprint("Track %d: Type: Subtitle, Language: %s, Codec: %s\n", - (int)track->track_number, - track->lang ? track->lang : "unknown", - track->codec_id_string ? track->codec_id_string : "unknown"); - } - - // Print video track if found - if (mkv_ctx->avc_track_number > -1) - { - mprint("Track %d: Type: Video\n", (int)mkv_ctx->avc_track_number); - } - - mprint("\n"); -} + mprint("\n"); + mprint("Available tracks in input file:\n"); + mprint("------------------------------\n"); + + for (int i = 0; i < mkv_ctx->sub_tracks_count; i++) + { + struct matroska_sub_track *track = mkv_ctx->sub_tracks[i]; + + mprint("Track %d: Type: Subtitle, Language: %s, Codec: %s\n", + (int)track->track_number, + track->lang ? track->lang : "unknown", + track->codec_id_string ? track->codec_id_string : "unknown"); + } + + // Print video track if found + if (mkv_ctx->avc_track_number > -1) + { + mprint("Track %d: Type: Video\n", (int)mkv_ctx->avc_track_number); + } + mprint("\n"); +} int matroska_loop(struct lib_ccx_ctx *ctx) { - // If we're just listing tracks, completely disable ALL output during parsing - int original_debug_mask = 0; - if (ccx_options.list_tracks_only) - { - original_debug_mask = ccx_common_logging.debug_mask; - ccx_common_logging.debug_mask = 0; - } - else if (ccx_options.write_format_rewritten) - { - mprint(MATROSKA_WARNING "You are using --out=, but Matroska parser extract subtitles in a recorded format\n"); - mprint("--out= will be ignored\n"); - } - - // Don't need generated input file - // Will read bytes by FILE* - close_input_file(ctx); - - struct matroska_ctx *mkv_ctx = malloc(sizeof(struct matroska_ctx)); - mkv_ctx->ctx = ctx; - mkv_ctx->sub_tracks_count = 0; - mkv_ctx->sentence_count = 0; - mkv_ctx->current_second = 0; - mkv_ctx->filename = ctx->inputfile[ctx->current_file]; - mkv_ctx->file = create_file(ctx); - mkv_ctx->sub_tracks = malloc(sizeof(struct matroska_sub_track **)); - // EIA-608 - memset(&mkv_ctx->dec_sub, 0, sizeof(mkv_ctx->dec_sub)); - mkv_ctx->avc_track_number = -1; - - matroska_parse(mkv_ctx); - - // Check if we're just listing tracks - if (ccx_options.list_tracks_only) - { - // Restore output capabilities for our specific output - ccx_common_logging.debug_mask = original_debug_mask; - - // Print tracks in a clean format - print_track_list(mkv_ctx); - - // Cleanup and exit early - matroska_free_all(mkv_ctx); - // mprint("\nTrack listing completed. Exiting as requested.\n"); - return 0; - } - - // 100% done - activity_progress(100, (int)(mkv_ctx->current_second / 60), - (int)(mkv_ctx->current_second % 60)); - - matroska_save_all(mkv_ctx, ccx_options.mkvlang); - int sentence_count = mkv_ctx->sentence_count; - matroska_free_all(mkv_ctx); - - mprint("\n\n"); - - // Support only one AVC track by now - if (mkv_ctx->avc_track_number > -1) - mprint("Found AVC track. "); - else - mprint("Found no AVC track. "); - - if (mkv_ctx->dec_sub.got_output) - return 1; - return sentence_count; + // If we're just listing tracks, completely disable ALL output during parsing + int original_debug_mask = 0; + if (ccx_options.list_tracks_only) + { + original_debug_mask = ccx_common_logging.debug_mask; + ccx_common_logging.debug_mask = 0; + } + else if (ccx_options.write_format_rewritten) + { + mprint(MATROSKA_WARNING "You are using --out=, but Matroska parser extract subtitles in a recorded format\n"); + mprint("--out= will be ignored\n"); + } + + // Don't need generated input file + // Will read bytes by FILE* + close_input_file(ctx); + + struct matroska_ctx *mkv_ctx = malloc(sizeof(struct matroska_ctx)); + mkv_ctx->ctx = ctx; + mkv_ctx->sub_tracks_count = 0; + mkv_ctx->sentence_count = 0; + mkv_ctx->current_second = 0; + mkv_ctx->filename = ctx->inputfile[ctx->current_file]; + mkv_ctx->file = create_file(ctx); + mkv_ctx->sub_tracks = malloc(sizeof(struct matroska_sub_track **)); + // EIA-608 + memset(&mkv_ctx->dec_sub, 0, sizeof(mkv_ctx->dec_sub)); + mkv_ctx->avc_track_number = -1; + + matroska_parse(mkv_ctx); + + // Check if we're just listing tracks + if (ccx_options.list_tracks_only) + { + // Restore output capabilities for our specific output + ccx_common_logging.debug_mask = original_debug_mask; + + // Print tracks in a clean format + print_track_list(mkv_ctx); + + // Cleanup and exit early + matroska_free_all(mkv_ctx); + // mprint("\nTrack listing completed. Exiting as requested.\n"); + return 0; + } + + // 100% done + activity_progress(100, (int)(mkv_ctx->current_second / 60), + (int)(mkv_ctx->current_second % 60)); + + matroska_save_all(mkv_ctx, ccx_options.mkvlang); + int sentence_count = mkv_ctx->sentence_count; + matroska_free_all(mkv_ctx); + + mprint("\n\n"); + + // Support only one AVC track by now + if (mkv_ctx->avc_track_number > -1) + mprint("Found AVC track. "); + else + mprint("Found no AVC track. "); + + if (mkv_ctx->dec_sub.got_output) + return 1; + return sentence_count; } diff --git a/src/lib_ccx/mp4.c b/src/lib_ccx/mp4.c index dc122962c..0ed70dc57 100644 --- a/src/lib_ccx/mp4.c +++ b/src/lib_ccx/mp4.c @@ -541,344 +541,344 @@ static int process_tx3g(struct lib_ccx_ctx *ctx, struct encoder_ctx *enc_ctx, int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) { - int mp4_ret = 0; - GF_ISOFile *f; - u32 i, j, track_count, avc_track_count, cc_track_count; - struct cc_subtitle dec_sub; - struct lib_cc_decode *dec_ctx = NULL; - struct encoder_ctx *enc_ctx = update_encoder_list(ctx); - - dec_ctx = update_decoder_list(ctx); - - if (enc_ctx) - enc_ctx->timing = dec_ctx->timing; - - memset(&dec_sub, 0, sizeof(dec_sub)); - - // Only show opening message if not in list_tracks mode - if (!ccx_options.list_tracks_only) - mprint("Opening \'%s\': ", file); - + int mp4_ret = 0; + GF_ISOFile *f; + u32 i, j, track_count, avc_track_count, cc_track_count; + struct cc_subtitle dec_sub; + struct lib_cc_decode *dec_ctx = NULL; + struct encoder_ctx *enc_ctx = update_encoder_list(ctx); + + dec_ctx = update_decoder_list(ctx); + + if (enc_ctx) + enc_ctx->timing = dec_ctx->timing; + + memset(&dec_sub, 0, sizeof(dec_sub)); + + // Only show opening message if not in list_tracks mode + if (!ccx_options.list_tracks_only) + mprint("Opening \'%s\': ", file); + #ifdef MP4_DEBUG - gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_DEBUG); + gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_DEBUG); #endif - if ((f = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL)) == NULL) - { - mprint("Failed to open input file (gf_isom_open() returned error)\n"); - free(dec_ctx->xds_ctx); - return -2; - } - - if (!ccx_options.list_tracks_only) - mprint("ok\n"); - - track_count = gf_isom_get_track_count(f); - avc_track_count = 0; - cc_track_count = 0; - - // First pass: count tracks by type - for (i = 0; i < track_count; i++) - { - const u32 type = gf_isom_get_media_type(f, i + 1); - const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); - - if (type == GF_ISOM_MEDIA_CLOSED_CAPTION || type == GF_ISOM_MEDIA_SUBT || type == GF_ISOM_MEDIA_TEXT) - cc_track_count++; - if (type == GF_ISOM_MEDIA_VISUAL && subtype == GF_ISOM_SUBTYPE_AVC_H264) - avc_track_count++; - } - - // If in track listing mode, show track information and exit - if (ccx_options.list_tracks_only) - { - mprint("\n"); - mprint("Available tracks in input file:\n"); - mprint("------------------------------\n"); - - for (i = 0; i < track_count; i++) - { - const u32 type = gf_isom_get_media_type(f, i + 1); - const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); - - mprint("Track %d: ", i + 1); - - // Get descriptive track type - if (type == GF_ISOM_MEDIA_VISUAL) - { - if (subtype == GF_ISOM_SUBTYPE_AVC_H264) - mprint("Type: H.264 Video"); - else if (subtype == GF_ISOM_SUBTYPE_XDVB) - mprint("Type: XDVB Video"); - else - mprint("Type: Video"); - } - else if (type == GF_ISOM_MEDIA_AUDIO) - { - mprint("Type: Audio"); - } - else if (type == GF_ISOM_MEDIA_CLOSED_CAPTION) - { - if (subtype == GF_QT_SUBTYPE_C608) - mprint("Type: CEA-608 Closed Caption"); - else if (subtype == GF_ISOM_SUBTYPE_C708) - mprint("Type: CEA-708 Closed Caption"); - else - mprint("Type: Closed Caption"); - } - else if (type == GF_ISOM_MEDIA_SUBT || type == GF_ISOM_MEDIA_TEXT) - { - if (subtype == GF_ISOM_SUBTYPE_TX3G) - mprint("Type: TX3G Subtitle"); - else if (subtype == GF_ISOM_SUBTYPE_TEXT) - mprint("Type: Text Subtitle"); - else - mprint("Type: Subtitle"); - } - else - { - mprint("Type: Other (%c%c%c%c/%c%c%c%c)", - (unsigned char)(type >> 24 % 0x100), - (unsigned char)((type >> 16) % 0x100), - (unsigned char)((type >> 8) % 0x100), - (unsigned char)(type % 0x100), - (unsigned char)(subtype >> 24 % 0x100), - (unsigned char)((subtype >> 16) % 0x100), - (unsigned char)((subtype >> 8) % 0x100), - (unsigned char)(subtype % 0x100)); - } - - mprint("\n"); - } - - mprint("\nTrack listing completed. Exiting as requested.\n"); - gf_isom_close(f); - freep(&dec_ctx->xds_ctx); - return 0; - } - - // Regular processing for normal mode follows - - for (i = 0; i < track_count; i++) - { - const u32 type = gf_isom_get_media_type(f, i + 1); - const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); - mprint("Track %d, type=%c%c%c%c subtype=%c%c%c%c\n", i + 1, (unsigned char)(type >> 24 % 0x100), - (unsigned char)((type >> 16) % 0x100), (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), - (unsigned char)(subtype >> 24 % 0x100), - (unsigned char)((subtype >> 16) % 0x100), (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); - } - - mprint("MP4: found %u tracks: %u avc and %u cc\n", track_count, avc_track_count, cc_track_count); - - for (i = 0; i < track_count; i++) - { - const u32 type = gf_isom_get_media_type(f, i + 1); - const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); - mprint("Processing track %d, type=%c%c%c%c subtype=%c%c%c%c\n", i + 1, - (unsigned char)(type >> 24 % 0x100), (unsigned char)((type >> 16) % 0x100), - (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), - (unsigned char)(subtype >> 24 % 0x100), (unsigned char)((subtype >> 16) % 0x100), - (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); - - const u64 track_type = MEDIA_TYPE(type, subtype); - - switch (track_type) - { - case MEDIA_TYPE(GF_ISOM_MEDIA_VISUAL, GF_ISOM_SUBTYPE_XDVB): // vide:xdvb - if (cc_track_count && !cfg->mp4vidtrack) - continue; - // If there are multiple tracks, change fd for different tracks - if (avc_track_count > 1) - { - switch_output_file(ctx, enc_ctx, i); - } - if (process_xdvb_track(ctx, file, f, i + 1, &dec_sub) != 0) - { - mprint("Error on process_xdvb_track()\n"); - free(dec_ctx->xds_ctx); - return -3; - } - if (dec_sub.got_output) - { - mp4_ret = 1; - encode_sub(enc_ctx, &dec_sub); - dec_sub.got_output = 0; - } - break; - - case MEDIA_TYPE(GF_ISOM_MEDIA_VISUAL, GF_ISOM_SUBTYPE_AVC_H264): // vide:avc1 - if (cc_track_count && !cfg->mp4vidtrack) - continue; - // If there are multiple tracks, change fd for different tracks - if (avc_track_count > 1) - { - switch_output_file(ctx, enc_ctx, i); - } - GF_AVCConfig *cnf = gf_isom_avc_config_get(f, i + 1, 1); - if (cnf != NULL) - { - for (j = 0; j < gf_list_count(cnf->sequenceParameterSets); j++) - { - GF_AVCConfigSlot *seqcnf = (GF_AVCConfigSlot *)gf_list_get(cnf->sequenceParameterSets, j); - do_NAL(enc_ctx, dec_ctx, (unsigned char *)seqcnf->data, seqcnf->size, &dec_sub); - } - gf_odf_avc_cfg_del(cnf); - } - if (process_avc_track(ctx, file, f, i + 1, &dec_sub) != 0) - { - mprint("Error on process_avc_track()\n"); - free(dec_ctx->xds_ctx); - return -3; - } - if (dec_sub.got_output) - { - mp4_ret = 1; - encode_sub(enc_ctx, &dec_sub); - dec_sub.got_output = 0; - } - break; - - default: - if (type != GF_ISOM_MEDIA_CLOSED_CAPTION && type != GF_ISOM_MEDIA_SUBT && type != GF_ISOM_MEDIA_TEXT) - break; // ignore non cc track - - if (avc_track_count && cfg->mp4vidtrack) - continue; - // If there are multiple tracks, change fd for different tracks - if (cc_track_count > 1) - { - switch_output_file(ctx, enc_ctx, i); - } - unsigned num_samples = gf_isom_get_sample_count(f, i + 1); - - u32 ProcessingStreamDescriptionIndex = 0; // Current track we are processing, 0 = we don't know yet - u32 timescale = gf_isom_get_media_timescale(f, i + 1); + if ((f = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL)) == NULL) + { + mprint("Failed to open input file (gf_isom_open() returned error)\n"); + free(dec_ctx->xds_ctx); + return -2; + } + + if (!ccx_options.list_tracks_only) + mprint("ok\n"); + + track_count = gf_isom_get_track_count(f); + avc_track_count = 0; + cc_track_count = 0; + + // First pass: count tracks by type + for (i = 0; i < track_count; i++) + { + const u32 type = gf_isom_get_media_type(f, i + 1); + const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); + + if (type == GF_ISOM_MEDIA_CLOSED_CAPTION || type == GF_ISOM_MEDIA_SUBT || type == GF_ISOM_MEDIA_TEXT) + cc_track_count++; + if (type == GF_ISOM_MEDIA_VISUAL && subtype == GF_ISOM_SUBTYPE_AVC_H264) + avc_track_count++; + } + + // If in track listing mode, show track information and exit + if (ccx_options.list_tracks_only) + { + mprint("\n"); + mprint("Available tracks in input file:\n"); + mprint("------------------------------\n"); + + for (i = 0; i < track_count; i++) + { + const u32 type = gf_isom_get_media_type(f, i + 1); + const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); + + mprint("Track %d: ", i + 1); + + // Get descriptive track type + if (type == GF_ISOM_MEDIA_VISUAL) + { + if (subtype == GF_ISOM_SUBTYPE_AVC_H264) + mprint("Type: H.264 Video"); + else if (subtype == GF_ISOM_SUBTYPE_XDVB) + mprint("Type: XDVB Video"); + else + mprint("Type: Video"); + } + else if (type == GF_ISOM_MEDIA_AUDIO) + { + mprint("Type: Audio"); + } + else if (type == GF_ISOM_MEDIA_CLOSED_CAPTION) + { + if (subtype == GF_QT_SUBTYPE_C608) + mprint("Type: CEA-608 Closed Caption"); + else if (subtype == GF_ISOM_SUBTYPE_C708) + mprint("Type: CEA-708 Closed Caption"); + else + mprint("Type: Closed Caption"); + } + else if (type == GF_ISOM_MEDIA_SUBT || type == GF_ISOM_MEDIA_TEXT) + { + if (subtype == GF_ISOM_SUBTYPE_TX3G) + mprint("Type: TX3G Subtitle"); + else if (subtype == GF_ISOM_SUBTYPE_TEXT) + mprint("Type: Text Subtitle"); + else + mprint("Type: Subtitle"); + } + else + { + mprint("Type: Other (%c%c%c%c/%c%c%c%c)", + (unsigned char)(type >> 24 % 0x100), + (unsigned char)((type >> 16) % 0x100), + (unsigned char)((type >> 8) % 0x100), + (unsigned char)(type % 0x100), + (unsigned char)(subtype >> 24 % 0x100), + (unsigned char)((subtype >> 16) % 0x100), + (unsigned char)((subtype >> 8) % 0x100), + (unsigned char)(subtype % 0x100)); + } + + mprint("\n"); + } + + mprint("\nTrack listing completed. Exiting as requested.\n"); + gf_isom_close(f); + freep(&dec_ctx->xds_ctx); + return 0; + } + + // Regular processing for normal mode follows + + for (i = 0; i < track_count; i++) + { + const u32 type = gf_isom_get_media_type(f, i + 1); + const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); + mprint("Track %d, type=%c%c%c%c subtype=%c%c%c%c\n", i + 1, (unsigned char)(type >> 24 % 0x100), + (unsigned char)((type >> 16) % 0x100), (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), + (unsigned char)(subtype >> 24 % 0x100), + (unsigned char)((subtype >> 16) % 0x100), (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); + } + + mprint("MP4: found %u tracks: %u avc and %u cc\n", track_count, avc_track_count, cc_track_count); + + for (i = 0; i < track_count; i++) + { + const u32 type = gf_isom_get_media_type(f, i + 1); + const u32 subtype = gf_isom_get_media_subtype(f, i + 1, 1); + mprint("Processing track %d, type=%c%c%c%c subtype=%c%c%c%c\n", i + 1, + (unsigned char)(type >> 24 % 0x100), (unsigned char)((type >> 16) % 0x100), + (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), + (unsigned char)(subtype >> 24 % 0x100), (unsigned char)((subtype >> 16) % 0x100), + (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); + + const u64 track_type = MEDIA_TYPE(type, subtype); + + switch (track_type) + { + case MEDIA_TYPE(GF_ISOM_MEDIA_VISUAL, GF_ISOM_SUBTYPE_XDVB): // vide:xdvb + if (cc_track_count && !cfg->mp4vidtrack) + continue; + // If there are multiple tracks, change fd for different tracks + if (avc_track_count > 1) + { + switch_output_file(ctx, enc_ctx, i); + } + if (process_xdvb_track(ctx, file, f, i + 1, &dec_sub) != 0) + { + mprint("Error on process_xdvb_track()\n"); + free(dec_ctx->xds_ctx); + return -3; + } + if (dec_sub.got_output) + { + mp4_ret = 1; + encode_sub(enc_ctx, &dec_sub); + dec_sub.got_output = 0; + } + break; + + case MEDIA_TYPE(GF_ISOM_MEDIA_VISUAL, GF_ISOM_SUBTYPE_AVC_H264): // vide:avc1 + if (cc_track_count && !cfg->mp4vidtrack) + continue; + // If there are multiple tracks, change fd for different tracks + if (avc_track_count > 1) + { + switch_output_file(ctx, enc_ctx, i); + } + GF_AVCConfig *cnf = gf_isom_avc_config_get(f, i + 1, 1); + if (cnf != NULL) + { + for (j = 0; j < gf_list_count(cnf->sequenceParameterSets); j++) + { + GF_AVCConfigSlot *seqcnf = (GF_AVCConfigSlot *)gf_list_get(cnf->sequenceParameterSets, j); + do_NAL(enc_ctx, dec_ctx, (unsigned char *)seqcnf->data, seqcnf->size, &dec_sub); + } + gf_odf_avc_cfg_del(cnf); + } + if (process_avc_track(ctx, file, f, i + 1, &dec_sub) != 0) + { + mprint("Error on process_avc_track()\n"); + free(dec_ctx->xds_ctx); + return -3; + } + if (dec_sub.got_output) + { + mp4_ret = 1; + encode_sub(enc_ctx, &dec_sub); + dec_sub.got_output = 0; + } + break; + + default: + if (type != GF_ISOM_MEDIA_CLOSED_CAPTION && type != GF_ISOM_MEDIA_SUBT && type != GF_ISOM_MEDIA_TEXT) + break; // ignore non cc track + + if (avc_track_count && cfg->mp4vidtrack) + continue; + // If there are multiple tracks, change fd for different tracks + if (cc_track_count > 1) + { + switch_output_file(ctx, enc_ctx, i); + } + unsigned num_samples = gf_isom_get_sample_count(f, i + 1); + + u32 ProcessingStreamDescriptionIndex = 0; // Current track we are processing, 0 = we don't know yet + u32 timescale = gf_isom_get_media_timescale(f, i + 1); #ifdef MP4_DEBUG - unsigned num_streams = gf_isom_get_sample_description_count(f, i + 1); - u64 duration = gf_isom_get_media_duration(f, i + 1); - mprint("%u streams\n", num_streams); - mprint("%u sample counts\n", num_samples); - mprint("%u timescale\n", (unsigned)timescale); - mprint("%u duration\n", (unsigned)duration); + unsigned num_streams = gf_isom_get_sample_description_count(f, i + 1); + u64 duration = gf_isom_get_media_duration(f, i + 1); + mprint("%u streams\n", num_streams); + mprint("%u sample counts\n", num_samples); + mprint("%u timescale\n", (unsigned)timescale); + mprint("%u duration\n", (unsigned)duration); #endif - for (unsigned k = 0; k < num_samples; k++) - { - u32 StreamDescriptionIndex; - GF_ISOSample *sample = gf_isom_get_sample(f, i + 1, k + 1, &StreamDescriptionIndex); - if (ProcessingStreamDescriptionIndex && ProcessingStreamDescriptionIndex != StreamDescriptionIndex) - { - mprint("This sample seems to have more than one description. This isn't supported yet.\n"); - mprint("Submitting this video file will help add support to this case.\n"); - break; - } - if (!ProcessingStreamDescriptionIndex) - ProcessingStreamDescriptionIndex = StreamDescriptionIndex; - if (sample == NULL) - continue; + for (unsigned k = 0; k < num_samples; k++) + { + u32 StreamDescriptionIndex; + GF_ISOSample *sample = gf_isom_get_sample(f, i + 1, k + 1, &StreamDescriptionIndex); + if (ProcessingStreamDescriptionIndex && ProcessingStreamDescriptionIndex != StreamDescriptionIndex) + { + mprint("This sample seems to have more than one description. This isn't supported yet.\n"); + mprint("Submitting this video file will help add support to this case.\n"); + break; + } + if (!ProcessingStreamDescriptionIndex) + ProcessingStreamDescriptionIndex = StreamDescriptionIndex; + if (sample == NULL) + continue; #ifdef MP4_DEBUG - mprint("Data length: %lu\n", sample->dataLength); - const LLONG timestamp = (LLONG)((sample->DTS + sample->CTS_Offset) * 1000) / timescale; + mprint("Data length: %lu\n", sample->dataLength); + const LLONG timestamp = (LLONG)((sample->DTS + sample->CTS_Offset) * 1000) / timescale; #endif - set_current_pts(dec_ctx->timing, (sample->DTS + sample->CTS_Offset) * MPEG_CLOCK_FREQ / timescale); - set_fts(dec_ctx->timing); - - int atomStart = 0; - // Process Atom by Atom - while (atomStart < sample->dataLength) - { - char *data = sample->data + atomStart; - size_t atom_length = -1; - switch (track_type) - { - case MEDIA_TYPE(GF_ISOM_MEDIA_TEXT, GF_ISOM_SUBTYPE_TX3G): // text:tx3g - case MEDIA_TYPE(GF_ISOM_MEDIA_SUBT, GF_ISOM_SUBTYPE_TX3G): // sbtl:tx3g (they're the same) - atom_length = process_tx3g(ctx, enc_ctx, dec_ctx, - &dec_sub, &mp4_ret, - data, sample->dataLength, 0); - break; - case MEDIA_TYPE(GF_ISOM_MEDIA_CLOSED_CAPTION, GF_QT_SUBTYPE_C608): // clcp:c608 - case MEDIA_TYPE(GF_ISOM_MEDIA_CLOSED_CAPTION, GF_ISOM_SUBTYPE_C708): // clcp:c708 - atom_length = process_clcp(ctx, enc_ctx, dec_ctx, - &dec_sub, &mp4_ret, subtype, - data, sample->dataLength); - break; - case MEDIA_TYPE(GF_ISOM_MEDIA_TEXT, GF_ISOM_SUBTYPE_TEXT): // text:text - { - static int unsupported_reported = 0; - if (!unsupported_reported) - { - mprint("\ntext:text not yet supported, see\n"); - mprint("https://developer.apple.com/library/mac/documentation/quicktime/qtff/QTFFChap3/qtff3.html\n"); - mprint("If you want to work on a PR.\n"); - unsupported_reported = 1; - } - break; - } - - default: - // See https://gpac.wp.imt.fr/2014/09/04/subtitling-with-gpac/ for more possible types - mprint("\nUnsupported track type \"%c%c%c%c:%c%c%c%c\". Please report.\n", - (unsigned char)(type >> 24 % 0x100), (unsigned char)((type >> 16) % 0x100), - (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), - (unsigned char)(subtype >> 24 % 0x100), (unsigned char)((subtype >> 16) % 0x100), - (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); - } - if (atom_length == -1) - break; // error happened or process of the sample is finished - atomStart += atom_length; - } - free(sample->data); - free(sample); - - // End of change - int progress = (int)((k * 100) / num_samples); - if (ctx->last_reported_progress != progress) - { - int cur_sec = (int)(get_fts(dec_ctx->timing, dec_ctx->current_field) / 1000); - activity_progress(progress, cur_sec / 60, cur_sec % 60); - ctx->last_reported_progress = progress; - } - } - - // Encode the last subtitle - if (subtype == GF_ISOM_SUBTYPE_TX3G) - { - process_tx3g(ctx, enc_ctx, dec_ctx, - &dec_sub, &mp4_ret, - NULL, 0, 1); - } - - int cur_sec = (int)(get_fts(dec_ctx->timing, dec_ctx->current_field) / 1000); - activity_progress(100, cur_sec / 60, cur_sec % 60); - } - } - - freep(&dec_ctx->xds_ctx); - - mprint("\nClosing media: "); - gf_isom_close(f); - f = NULL; - mprint("ok\n"); - - if (avc_track_count) - mprint("Found %d AVC track(s). ", avc_track_count); - else - mprint("Found no AVC track(s). "); - - if (cc_track_count) - mprint("Found %d CC track(s).\n", cc_track_count); - else - mprint("Found no dedicated CC track(s).\n"); - - ctx->freport.mp4_cc_track_cnt = cc_track_count; - - if ((dec_ctx->write_format == CCX_OF_MCC) && (dec_ctx->saw_caption_block == CCX_TRUE)) - { - mp4_ret = 1; - } - - return mp4_ret; + set_current_pts(dec_ctx->timing, (sample->DTS + sample->CTS_Offset) * MPEG_CLOCK_FREQ / timescale); + set_fts(dec_ctx->timing); + + int atomStart = 0; + // Process Atom by Atom + while (atomStart < sample->dataLength) + { + char *data = sample->data + atomStart; + size_t atom_length = -1; + switch (track_type) + { + case MEDIA_TYPE(GF_ISOM_MEDIA_TEXT, GF_ISOM_SUBTYPE_TX3G): // text:tx3g + case MEDIA_TYPE(GF_ISOM_MEDIA_SUBT, GF_ISOM_SUBTYPE_TX3G): // sbtl:tx3g (they're the same) + atom_length = process_tx3g(ctx, enc_ctx, dec_ctx, + &dec_sub, &mp4_ret, + data, sample->dataLength, 0); + break; + case MEDIA_TYPE(GF_ISOM_MEDIA_CLOSED_CAPTION, GF_QT_SUBTYPE_C608): // clcp:c608 + case MEDIA_TYPE(GF_ISOM_MEDIA_CLOSED_CAPTION, GF_ISOM_SUBTYPE_C708): // clcp:c708 + atom_length = process_clcp(ctx, enc_ctx, dec_ctx, + &dec_sub, &mp4_ret, subtype, + data, sample->dataLength); + break; + case MEDIA_TYPE(GF_ISOM_MEDIA_TEXT, GF_ISOM_SUBTYPE_TEXT): // text:text + { + static int unsupported_reported = 0; + if (!unsupported_reported) + { + mprint("\ntext:text not yet supported, see\n"); + mprint("https://developer.apple.com/library/mac/documentation/quicktime/qtff/QTFFChap3/qtff3.html\n"); + mprint("If you want to work on a PR.\n"); + unsupported_reported = 1; + } + break; + } + + default: + // See https://gpac.wp.imt.fr/2014/09/04/subtitling-with-gpac/ for more possible types + mprint("\nUnsupported track type \"%c%c%c%c:%c%c%c%c\". Please report.\n", + (unsigned char)(type >> 24 % 0x100), (unsigned char)((type >> 16) % 0x100), + (unsigned char)((type >> 8) % 0x100), (unsigned char)(type % 0x100), + (unsigned char)(subtype >> 24 % 0x100), (unsigned char)((subtype >> 16) % 0x100), + (unsigned char)((subtype >> 8) % 0x100), (unsigned char)(subtype % 0x100)); + } + if (atom_length == -1) + break; // error happened or process of the sample is finished + atomStart += atom_length; + } + free(sample->data); + free(sample); + + // End of change + int progress = (int)((k * 100) / num_samples); + if (ctx->last_reported_progress != progress) + { + int cur_sec = (int)(get_fts(dec_ctx->timing, dec_ctx->current_field) / 1000); + activity_progress(progress, cur_sec / 60, cur_sec % 60); + ctx->last_reported_progress = progress; + } + } + + // Encode the last subtitle + if (subtype == GF_ISOM_SUBTYPE_TX3G) + { + process_tx3g(ctx, enc_ctx, dec_ctx, + &dec_sub, &mp4_ret, + NULL, 0, 1); + } + + int cur_sec = (int)(get_fts(dec_ctx->timing, dec_ctx->current_field) / 1000); + activity_progress(100, cur_sec / 60, cur_sec % 60); + } + } + + freep(&dec_ctx->xds_ctx); + + mprint("\nClosing media: "); + gf_isom_close(f); + f = NULL; + mprint("ok\n"); + + if (avc_track_count) + mprint("Found %d AVC track(s). ", avc_track_count); + else + mprint("Found no AVC track(s). "); + + if (cc_track_count) + mprint("Found %d CC track(s).\n", cc_track_count); + else + mprint("Found no dedicated CC track(s).\n"); + + ctx->freport.mp4_cc_track_cnt = cc_track_count; + + if ((dec_ctx->write_format == CCX_OF_MCC) && (dec_ctx->saw_caption_block == CCX_TRUE)) + { + mp4_ret = 1; + } + + return mp4_ret; } int dumpchapters(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) diff --git a/src/lib_ccx/params.c b/src/lib_ccx/params.c index c8fe0a2a0..77d05c39f 100644 --- a/src/lib_ccx/params.c +++ b/src/lib_ccx/params.c @@ -1619,14 +1619,14 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) fatal(EXIT_MALFORMED_PARAMETER, "--codec has no argument.\n"); } } - //list tracks in the file + // list tracks in the file if (strcmp(argv[i], "--list-tracks") == 0 || strcmp(argv[i], "-lt") == 0) { opt->cc_to_stdout = 1; opt->list_tracks_only = 1; continue; } - + /*user specified subtitle to be selected */ if (strcmp(argv[i], "--no-codec") == 0) { diff --git a/src/lib_ccx/ts_functions.c b/src/lib_ccx/ts_functions.c index 0f62659d4..bd8e160d6 100644 --- a/src/lib_ccx/ts_functions.c +++ b/src/lib_ccx/ts_functions.c @@ -985,57 +985,57 @@ int ts_get_more_data(struct lib_ccx_ctx *ctx, struct demuxer_data **data) void list_ts_tracks(struct lib_ccx_ctx *ctx) { - struct ccx_demuxer *ctx_demux = ctx->demux_ctx; - int i; - - mprint("\n"); - mprint("Available tracks in input file:\n"); - mprint("------------------------------\n"); - - // Display program information - for (i = 0; i < ctx_demux->nb_program; i++) - { - struct program_info *pinfo = &ctx_demux->pinfo[i]; - - if (pinfo->program_number == -1) - continue; - - mprint("Program: %d\n", pinfo->program_number); - - // PCR PID - if (pinfo->pcr_pid) - { - mprint(" PCR: PID: %u\n", pinfo->pcr_pid); - } - - // Find caption tracks by searching through PIDs - for (int j = 0; j <= MAX_PSI_PID; j++) - { - if (ctx_demux->PIDs_programs[j] && - ctx_demux->PIDs_programs[j]->program_number == pinfo->program_number) - { - struct cap_info *cinfo = get_cinfo(ctx_demux, j); - if (cinfo) - { - char *stream_type_name = get_buffer_type_str(cinfo); - - mprint(" Track %d: Type: %s, PID: %u\n", - j, - stream_type_name ? stream_type_name : "Subtitle/Caption", - j); - - if (stream_type_name) - free(stream_type_name); - } - } - } - } - - if (ctx_demux->nb_program == 0 || - (ctx_demux->nb_program == 1 && ctx_demux->pinfo[0].program_number == -1)) - { - mprint("No program information found in the stream.\n"); - } - - mprint("\nTrack listing completed. Exiting as requested.\n"); + struct ccx_demuxer *ctx_demux = ctx->demux_ctx; + int i; + + mprint("\n"); + mprint("Available tracks in input file:\n"); + mprint("------------------------------\n"); + + // Display program information + for (i = 0; i < ctx_demux->nb_program; i++) + { + struct program_info *pinfo = &ctx_demux->pinfo[i]; + + if (pinfo->program_number == -1) + continue; + + mprint("Program: %d\n", pinfo->program_number); + + // PCR PID + if (pinfo->pcr_pid) + { + mprint(" PCR: PID: %u\n", pinfo->pcr_pid); + } + + // Find caption tracks by searching through PIDs + for (int j = 0; j <= MAX_PSI_PID; j++) + { + if (ctx_demux->PIDs_programs[j] && + ctx_demux->PIDs_programs[j]->program_number == pinfo->program_number) + { + struct cap_info *cinfo = get_cinfo(ctx_demux, j); + if (cinfo) + { + char *stream_type_name = get_buffer_type_str(cinfo); + + mprint(" Track %d: Type: %s, PID: %u\n", + j, + stream_type_name ? stream_type_name : "Subtitle/Caption", + j); + + if (stream_type_name) + free(stream_type_name); + } + } + } + } + + if (ctx_demux->nb_program == 0 || + (ctx_demux->nb_program == 1 && ctx_demux->pinfo[0].program_number == -1)) + { + mprint("No program information found in the stream.\n"); + } + + mprint("\nTrack listing completed. Exiting as requested.\n"); } diff --git a/src/rust/src/parser.rs b/src/rust/src/parser.rs index c8b298400..ee6392b74 100644 --- a/src/rust/src/parser.rs +++ b/src/rust/src/parser.rs @@ -674,7 +674,7 @@ impl OptionsExt for Options { if args.chapters { self.extract_chapters = true; } - + if args.list_tracks { self.list_tracks_only = true; }