diff --git a/Changelog b/Changelog index 2ff792137f..2f038022cc 100644 --- a/Changelog +++ b/Changelog @@ -27,6 +27,7 @@ version : - AST demuxer - new expansion syntax for drawtext - BRender PIX image decoder +- ffprobe -show_entries option version 1.0: diff --git a/doc/ffprobe.texi b/doc/ffprobe.texi index 7b47fbabae..aacb14cfa3 100644 --- a/doc/ffprobe.texi +++ b/doc/ffprobe.texi @@ -133,6 +133,59 @@ Like @option{-show_format}, but only prints the specified entry of the container format information, rather than all. This option may be given more than once, then all specified entries will be shown. +This option is deprecated, use @code{show_entries} instead. + +@item -show_entries @var{section_entries} +Set list of entries to show. + +Entries are specified according to the following +syntax. @var{section_entries} contains a list of section entries +separated by @code{:}. Each section entry is composed by a section +name (or unique name), optionally followed by a list of entries local +to that section, separated by @code{,}. + +If section name is specified but is followed by no @code{=}, all +entries are printed to output, together with all the contained +sections. Otherwise only the entries specified in the local section +entries list are printed. In particular, if @code{=} is specified but +the list of local entries is empty, then no entries will be shown for +that section. + +Note that the order of specification of the local section entries is +not honored in the output, and the usual display order will be +retained. + +The formal syntax is given by: +@example +@var{LOCAL_SECTION_ENTRIES} ::= @var{SECTION_ENTRY_NAME}[,@var{LOCAL_SECTION_ENTRIES}] +@var{SECTION_ENTRY} ::= @var{SECTION_NAME}[=[@var{LOCAL_SECTION_ENTRIES}]] +@var{SECTION_ENTRIES} ::= @var{SECTION_ENTRY}[:@var{SECTION_ENTRIES}] +@end example + +For example, to show only the index and type of each stream, and the PTS +time, duration time, and stream index of the packets, you can specify +the argument: +@example +packet=pts_time,duration_time,stream_index : stream=index,codec_type +@end example + +To show all the entries in the section "format", but only the codec +type in the section "stream", specify the argument: +@example +format : stream=codec_type +@end example + +To show all the tags in the stream and format sections: +@example +format_tags : format_tags +@end example + +To show only the @code{title} tag (if available) in the stream +sections: +@example +stream_tags=title +@end example + @item -show_packets Show information about each packet contained in the input multimedia stream. diff --git a/ffprobe.c b/ffprobe.c index 760a0ba6af..fc665a7a02 100644 --- a/ffprobe.c +++ b/ffprobe.c @@ -55,9 +55,9 @@ static int do_read_packets = 0; static int do_show_error = 0; static int do_show_format = 0; static int do_show_frames = 0; -static AVDictionary *fmt_entries_to_show = NULL; static int do_show_packets = 0; static int do_show_streams = 0; +static int do_show_stream_disposition = 0; static int do_show_data = 0; static int do_show_program_version = 0; static int do_show_library_versions = 0; @@ -73,6 +73,8 @@ static char *stream_specifier; /* section structure definition */ +#define SECTION_MAX_NB_CHILDREN 10 + struct section { int id; ///< unique id identifying a section const char *name; @@ -82,7 +84,11 @@ struct section { #define SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys. /// For these sections the element_name field is mandatory. int flags; + int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1 const char *element_name; ///< name of the contained element, if provided + const char *unique_name; ///< unique section name, in case the name is ambiguous + AVDictionary *entries_to_show; + int show_all_entries; }; typedef enum { @@ -106,24 +112,26 @@ typedef enum { SECTION_ID_STREAM_TAGS, } SectionID; -static const struct section sections[] = { - [SECTION_ID_ERROR] = { SECTION_ID_ERROR, "error" }, - [SECTION_ID_FORMAT] = { SECTION_ID_FORMAT, "format" }, - [SECTION_ID_FORMAT_TAGS] = { SECTION_ID_FORMAT_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, .element_name = "tag" }, - [SECTION_ID_FRAME] = { SECTION_ID_FRAME, "frame" }, - [SECTION_ID_FRAMES] = { SECTION_ID_FRAMES, "frames", SECTION_FLAG_IS_ARRAY }, - [SECTION_ID_FRAME_TAGS] = { SECTION_ID_FRAME_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, .element_name = "tag" }, - [SECTION_ID_LIBRARY_VERSION] = { SECTION_ID_LIBRARY_VERSION, "library_version" }, - [SECTION_ID_LIBRARY_VERSIONS] = { SECTION_ID_LIBRARY_VERSIONS, "library_versions", SECTION_FLAG_IS_ARRAY }, - [SECTION_ID_PACKET] = { SECTION_ID_PACKET, "packet" }, - [SECTION_ID_PACKETS] = { SECTION_ID_PACKETS, "packets", SECTION_FLAG_IS_ARRAY }, - [SECTION_ID_PACKETS_AND_FRAMES] = { SECTION_ID_PACKETS_AND_FRAMES, "packets_and_frames", SECTION_FLAG_IS_ARRAY }, - [SECTION_ID_PROGRAM_VERSION] = { SECTION_ID_PROGRAM_VERSION, "program_version" }, - [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "root", SECTION_FLAG_IS_WRAPPER }, - [SECTION_ID_STREAM] = { SECTION_ID_STREAM, "stream" }, - [SECTION_ID_STREAM_DISPOSITION] = { SECTION_ID_STREAM_DISPOSITION, "disposition" }, - [SECTION_ID_STREAMS] = { SECTION_ID_STREAMS, "streams", SECTION_FLAG_IS_ARRAY }, - [SECTION_ID_STREAM_TAGS] = { SECTION_ID_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, .element_name = "tag" }, +static struct section sections[] = { + [SECTION_ID_ERROR] = { SECTION_ID_ERROR, "error", 0, { -1 } }, + [SECTION_ID_FORMAT] = { SECTION_ID_FORMAT, "format", 0, { SECTION_ID_FORMAT_TAGS, -1 } }, + [SECTION_ID_FORMAT_TAGS] = { SECTION_ID_FORMAT_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "format_tags" }, + [SECTION_ID_FRAMES] = { SECTION_ID_FRAMES, "frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME, -1 } }, + [SECTION_ID_FRAME] = { SECTION_ID_FRAME, "frame", 0, { SECTION_ID_FRAME_TAGS, -1 } }, + [SECTION_ID_FRAME_TAGS] = { SECTION_ID_FRAME_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "frame_tags" }, + [SECTION_ID_LIBRARY_VERSIONS] = { SECTION_ID_LIBRARY_VERSIONS, "library_versions", SECTION_FLAG_IS_ARRAY, { SECTION_ID_LIBRARY_VERSION, -1 } }, + [SECTION_ID_LIBRARY_VERSION] = { SECTION_ID_LIBRARY_VERSION, "library_version", 0, { -1 } }, + [SECTION_ID_PACKETS] = { SECTION_ID_PACKETS, "packets", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, + [SECTION_ID_PACKETS_AND_FRAMES] = { SECTION_ID_PACKETS_AND_FRAMES, "packets_and_frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, + [SECTION_ID_PACKET] = { SECTION_ID_PACKET, "packet", 0, { -1 } }, + [SECTION_ID_PROGRAM_VERSION] = { SECTION_ID_PROGRAM_VERSION, "program_version", 0, { -1 } }, + [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "root", SECTION_FLAG_IS_WRAPPER, + { SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_STREAMS, SECTION_ID_PACKETS, + SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_LIBRARY_VERSIONS, -1} }, + [SECTION_ID_STREAMS] = { SECTION_ID_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM, -1 } }, + [SECTION_ID_STREAM] = { SECTION_ID_STREAM, "stream", 0, { SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAM_TAGS, -1 } }, + [SECTION_ID_STREAM_DISPOSITION] = { SECTION_ID_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_disposition" }, + [SECTION_ID_STREAM_TAGS] = { SECTION_ID_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_tags" }, }; static const OptionDef *options; @@ -146,7 +154,9 @@ static int *selected_streams; static void exit_program(void) { - av_dict_free(&fmt_entries_to_show); + int i; + for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) + av_dict_free(&(sections[i].entries_to_show)); } struct unit_value { @@ -375,9 +385,9 @@ static inline void writer_print_section_footer(WriterContext *wctx) static inline void writer_print_integer(WriterContext *wctx, const char *key, long long int val) { - if ((wctx->section[wctx->level]->id != SECTION_ID_FORMAT - && wctx->section[wctx->level]->id != SECTION_ID_FORMAT_TAGS) || - !fmt_entries_to_show || av_dict_get(fmt_entries_to_show, key, NULL, 0)) { + const struct section *section = wctx->section[wctx->level]; + + if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { wctx->writer->print_integer(wctx, key, val); wctx->nb_item[wctx->level]++; } @@ -386,11 +396,12 @@ static inline void writer_print_integer(WriterContext *wctx, static inline void writer_print_string(WriterContext *wctx, const char *key, const char *val, int opt) { + const struct section *section = wctx->section[wctx->level]; + if (opt && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS)) return; - if ((wctx->section[wctx->level]->id != SECTION_ID_FORMAT - && wctx->section[wctx->level]->id != SECTION_ID_FORMAT_TAGS) || - !fmt_entries_to_show || av_dict_get(fmt_entries_to_show, key, NULL, 0)) { + + if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { wctx->writer->print_string(wctx, key, val); wctx->nb_item[wctx->level]++; } @@ -1706,6 +1717,7 @@ static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_i print_int(name, !!(stream->disposition & AV_DISPOSITION_##flagname)); \ } while (0) + if (do_show_stream_disposition) { writer_print_section_header(w, SECTION_ID_STREAM_DISPOSITION); PRINT_DISPOSITION(DEFAULT, "default"); PRINT_DISPOSITION(DUB, "dub"); @@ -1719,6 +1731,7 @@ static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_i PRINT_DISPOSITION(CLEAN_EFFECTS, "clean_effects"); PRINT_DISPOSITION(ATTACHED_PIC, "attached_pic"); writer_print_section_footer(w); + } show_tags(w, stream->metadata, SECTION_ID_STREAM_TAGS); @@ -1958,11 +1971,100 @@ static int opt_format(void *optctx, const char *opt, const char *arg) return 0; } +static inline void mark_section_show_entries(SectionID section_id, + int show_all_entries, AVDictionary *entries) +{ + struct section *section = §ions[section_id]; + + section->show_all_entries = show_all_entries; + if (show_all_entries) { + SectionID *id; + for (id = section->children_ids; *id != -1; id++) + mark_section_show_entries(*id, show_all_entries, entries); + } else { + av_dict_copy(§ion->entries_to_show, entries, 0); + } +} + +static int match_section(const char *section_name, + int show_all_entries, AVDictionary *entries) +{ + int i, ret = 0; + + for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) { + const struct section *section = §ions[i]; + if (!strcmp(section_name, section->name) || + (section->unique_name && !strcmp(section_name, section->unique_name))) { + av_log(NULL, AV_LOG_DEBUG, + "'%s' matches section with unique name '%s'\n", section_name, + (char *)av_x_if_null(section->unique_name, section->name)); + ret++; + mark_section_show_entries(section->id, show_all_entries, entries); + } + } + return ret; +} + +static int opt_show_entries(void *optctx, const char *opt, const char *arg) +{ + const char *p = arg; + int ret = 0; + + while (*p) { + AVDictionary *entries = NULL; + char *section_name = av_get_token(&p, "=:"); + int show_all_entries = 0; + + if (!section_name) { + av_log(NULL, AV_LOG_ERROR, + "Missing section name for option '%s'\n", opt); + return AVERROR(EINVAL); + } + + if (*p == '=') { + p++; + while (*p && *p != ':') { + char *entry = av_get_token(&p, ",:"); + if (!entry) + break; + av_log(NULL, AV_LOG_VERBOSE, + "Adding '%s' to the entries to show in section '%s'\n", + entry, section_name); + av_dict_set(&entries, entry, "", AV_DICT_DONT_STRDUP_KEY); + if (*p == ',') + p++; + } + } else { + show_all_entries = 1; + } + + ret = match_section(section_name, show_all_entries, entries); + if (ret == 0) { + av_log(NULL, AV_LOG_ERROR, "No match for section '%s'\n", section_name); + ret = AVERROR(EINVAL); + } + av_free(section_name); + + if (ret <= 0) + break; + if (*p) + p++; + } + + return ret; +} + static int opt_show_format_entry(void *optctx, const char *opt, const char *arg) { - do_show_format = 1; - av_dict_set(&fmt_entries_to_show, arg, "", 0); - return 0; + char *buf = av_asprintf("format=%s", arg); + int ret; + + av_log(NULL, AV_LOG_WARNING, + "Option '%s' is deprecated, use '-show_entries format=%s' instead\n", + opt, arg); + ret = opt_show_entries(optctx, opt, buf); + av_free(buf); + return ret; } static void opt_input_file(void *optctx, const char *arg) @@ -2005,11 +2107,26 @@ static int opt_pretty(void *optctx, const char *opt, const char *arg) static int opt_show_versions(const char *opt, const char *arg) { - do_show_program_version = 1; - do_show_library_versions = 1; + mark_section_show_entries(SECTION_ID_PROGRAM_VERSION, 1, NULL); + mark_section_show_entries(SECTION_ID_LIBRARY_VERSION, 1, NULL); return 0; } +#define DEFINE_OPT_SHOW_SECTION(section, target_section_id) \ + static int opt_show_##section(const char *opt, const char *arg) \ + { \ + mark_section_show_entries(SECTION_ID_##target_section_id, 1, NULL); \ + return 0; \ + } + +DEFINE_OPT_SHOW_SECTION(error, ERROR); +DEFINE_OPT_SHOW_SECTION(format, FORMAT); +DEFINE_OPT_SHOW_SECTION(frames, FRAMES); +DEFINE_OPT_SHOW_SECTION(library_versions, LIBRARY_VERSIONS); +DEFINE_OPT_SHOW_SECTION(packets, PACKETS); +DEFINE_OPT_SHOW_SECTION(program_version, PROGRAM_VERSION); +DEFINE_OPT_SHOW_SECTION(streams, STREAMS); + static const OptionDef real_options[] = { #include "cmdutils_common_opts.h" { "f", HAS_ARG, {.func_arg = opt_format}, "force format", "format" }, @@ -2026,17 +2143,19 @@ static const OptionDef real_options[] = { { "of", OPT_STRING | HAS_ARG, {(void*)&print_format}, "alias for -print_format", "format" }, { "select_streams", OPT_STRING | HAS_ARG, {(void*)&stream_specifier}, "select the specified streams", "stream_specifier" }, { "show_data", OPT_BOOL, {(void*)&do_show_data}, "show packets data" }, - { "show_error", OPT_BOOL, {(void*)&do_show_error} , "show probing error" }, - { "show_format", OPT_BOOL, {&do_show_format} , "show format/container info" }, - { "show_frames", OPT_BOOL, {(void*)&do_show_frames} , "show frames info" }, + { "show_error", 0, {(void*)&opt_show_error}, "show probing error" }, + { "show_format", 0, {(void*)&opt_show_format}, "show format/container info" }, + { "show_frames", 0, {(void*)&opt_show_frames}, "show frames info" }, { "show_format_entry", HAS_ARG, {.func_arg = opt_show_format_entry}, "show a particular entry from the format/container info", "entry" }, - { "show_packets", OPT_BOOL, {&do_show_packets}, "show packets info" }, - { "show_streams", OPT_BOOL, {&do_show_streams}, "show streams info" }, + { "show_entries", HAS_ARG, {.func_arg = opt_show_entries}, + "show a set of specified entries", "entry_list" }, + { "show_packets", 0, {(void*)&opt_show_packets}, "show packets info" }, + { "show_streams", 0, {(void*)&opt_show_streams}, "show streams info" }, { "count_frames", OPT_BOOL, {(void*)&do_count_frames}, "count the number of frames per stream" }, { "count_packets", OPT_BOOL, {(void*)&do_count_packets}, "count the number of packets per stream" }, - { "show_program_version", OPT_BOOL, {(void*)&do_show_program_version}, "show ffprobe version" }, - { "show_library_versions", OPT_BOOL, {(void*)&do_show_library_versions}, "show library versions" }, + { "show_program_version", 0, {(void*)&opt_show_program_version}, "show ffprobe version" }, + { "show_library_versions", 0, {(void*)&opt_show_library_versions}, "show library versions" }, { "show_versions", 0, {(void*)&opt_show_versions}, "show program and library versions" }, { "show_private_data", OPT_BOOL, {(void*)&show_private_data}, "show private data" }, { "private", OPT_BOOL, {(void*)&show_private_data}, "same as show_private_data" }, @@ -2046,13 +2165,30 @@ static const OptionDef real_options[] = { { NULL, }, }; +static inline int check_section_show_entries(int section_id) +{ + int *id; + struct section *section = §ions[section_id]; + if (sections[section_id].show_all_entries || sections[section_id].entries_to_show) + return 1; + for (id = section->children_ids; *id != -1; id++) + if (check_section_show_entries(*id)) + return 1; + return 0; +} + +#define SET_DO_SHOW(id, varname) do { \ + if (check_section_show_entries(SECTION_ID_##id)) \ + do_show_##varname = 1; \ + } while (0) + int main(int argc, char **argv) { const Writer *w; WriterContext *wctx; char *buf; char *w_name = NULL, *w_args = NULL; - int ret; + int ret, i; av_log_set_flags(AV_LOG_SKIP_REPEATED); atexit(exit_program); @@ -2069,6 +2205,16 @@ int main(int argc, char **argv) show_banner(argc, argv, options); parse_options(NULL, argc, argv, options, opt_input_file); + /* mark things to show, based on -show_entries */ + SET_DO_SHOW(ERROR, error); + SET_DO_SHOW(FORMAT, format); + SET_DO_SHOW(FRAMES, frames); + SET_DO_SHOW(LIBRARY_VERSIONS, library_versions); + SET_DO_SHOW(PACKETS, packets); + SET_DO_SHOW(PROGRAM_VERSION, program_version); + SET_DO_SHOW(STREAMS, streams); + SET_DO_SHOW(STREAM_DISPOSITION, stream_disposition); + if (do_bitexact && (do_show_program_version || do_show_library_versions)) { av_log(NULL, AV_LOG_ERROR, "-bitexact and -show_program_version or -show_library_versions " @@ -2125,7 +2271,8 @@ end: av_freep(&print_format); uninit_opts(); - av_dict_free(&fmt_entries_to_show); + for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) + av_dict_free(&(sections[i].entries_to_show)); avformat_network_deinit();