Skip to content

[FEAT] Added --list-tracks option to display available tracks in media files #1669

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGES.TXT
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
70 changes: 70 additions & 0 deletions src/ccextractor.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ int api_start(struct ccx_s_options api_options)

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
Expand Down Expand Up @@ -161,6 +163,68 @@ int api_start(struct ccx_s_options api_options)
#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
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
// broken timeline for debug purposes.
Expand All @@ -178,6 +242,7 @@ int api_start(struct ccx_s_options api_options)
default:
break;
}

/* -----------------------------------------------------------------
MAIN LOOP
----------------------------------------------------------------- */
Expand Down Expand Up @@ -471,6 +536,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;
Expand Down
1 change: 1 addition & 0 deletions src/lib_ccx/ccx_common_option.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/lib_ccx/ccx_encoders_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion src/lib_ccx/file_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/lib_ccx/lib_ccx.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
50 changes: 48 additions & 2 deletions src/lib_ccx/matroska.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1358,9 +1357,41 @@ FILE *create_file(struct lib_ccx_ctx *ctx)
return file;
}

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");
}

int matroska_loop(struct lib_ccx_ctx *ctx)
{
if (ccx_options.write_format_rewritten)
// 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=<format>, but Matroska parser extract subtitles in a recorded format\n");
mprint("--out=<format> will be ignored\n");
Expand All @@ -1384,6 +1415,21 @@ int matroska_loop(struct lib_ccx_ctx *ctx)

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));
Expand Down
1 change: 1 addition & 0 deletions src/lib_ccx/matroska.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
97 changes: 90 additions & 7 deletions src/lib_ccx/mp4.c
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ 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;
Expand All @@ -553,7 +554,11 @@ int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file)
enc_ctx->timing = dec_ctx->timing;

memset(&dec_sub, 0, sizeof(dec_sub));
mprint("Opening \'%s\': ", file);

// 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);
#endif
Expand All @@ -565,27 +570,105 @@ int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file)
return -2;
}

mprint("ok\n");
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);
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++;
}

// 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++)
Expand Down
8 changes: 8 additions & 0 deletions src/lib_ccx/params.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -1618,6 +1619,13 @@ 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)
Expand Down
Loading
Loading