From f7813a5324be39d13ab536c245d15dfc602a7849 Mon Sep 17 00:00:00 2001 From: Tim Redfern Date: Sun, 29 Dec 2013 12:19:38 +0000 Subject: basic type mechanism working --- ffmpeg/ffprobe.c | 855 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 756 insertions(+), 99 deletions(-) (limited to 'ffmpeg/ffprobe.c') diff --git a/ffmpeg/ffprobe.c b/ffmpeg/ffprobe.c index 4d2d3e1..0374d37 100644 --- a/ffmpeg/ffprobe.c +++ b/ffmpeg/ffprobe.c @@ -24,7 +24,7 @@ */ #include "config.h" -#include "version.h" +#include "libavutil/ffversion.h" #include @@ -37,7 +37,9 @@ #include "libavutil/pixdesc.h" #include "libavutil/dict.h" #include "libavutil/libm.h" +#include "libavutil/parseutils.h" #include "libavutil/timecode.h" +#include "libavutil/timestamp.h" #include "libavdevice/avdevice.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" @@ -52,16 +54,24 @@ static int do_count_frames = 0; static int do_count_packets = 0; static int do_read_frames = 0; static int do_read_packets = 0; +static int do_show_chapters = 0; static int do_show_error = 0; static int do_show_format = 0; static int do_show_frames = 0; static int do_show_packets = 0; +static int do_show_programs = 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; +static int do_show_chapter_tags = 0; +static int do_show_format_tags = 0; +static int do_show_frame_tags = 0; +static int do_show_program_tags = 0; +static int do_show_stream_tags = 0; + static int show_value_unit = 0; static int use_value_prefix = 0; static int use_byte_value_binary_prefix = 0; @@ -71,6 +81,17 @@ static int show_private_data = 1; static char *print_format; static char *stream_specifier; +typedef struct { + int id; ///< identifier + int64_t start, end; ///< start, end in second/AV_TIME_BASE units + int has_start, has_end; + int start_is_offset, end_is_offset; + int duration_frames; +} ReadInterval; + +static ReadInterval *read_intervals; +static int read_intervals_nb = 0; + /* section structure definition */ #define SECTION_MAX_NB_CHILDREN 10 @@ -93,6 +114,9 @@ struct section { typedef enum { SECTION_ID_NONE = -1, + SECTION_ID_CHAPTER, + SECTION_ID_CHAPTER_TAGS, + SECTION_ID_CHAPTERS, SECTION_ID_ERROR, SECTION_ID_FORMAT, SECTION_ID_FORMAT_TAGS, @@ -104,19 +128,30 @@ typedef enum { SECTION_ID_PACKET, SECTION_ID_PACKETS, SECTION_ID_PACKETS_AND_FRAMES, + SECTION_ID_PROGRAM_STREAM_DISPOSITION, + SECTION_ID_PROGRAM_STREAM_TAGS, + SECTION_ID_PROGRAM, + SECTION_ID_PROGRAM_STREAMS, + SECTION_ID_PROGRAM_STREAM, + SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_VERSION, + SECTION_ID_PROGRAMS, SECTION_ID_ROOT, SECTION_ID_STREAM, SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAMS, SECTION_ID_STREAM_TAGS, + SECTION_ID_SUBTITLE, } SectionID; static struct section sections[] = { + [SECTION_ID_CHAPTERS] = { SECTION_ID_CHAPTERS, "chapters", SECTION_FLAG_IS_ARRAY, { SECTION_ID_CHAPTER, -1 } }, + [SECTION_ID_CHAPTER] = { SECTION_ID_CHAPTER, "chapter", 0, { SECTION_ID_CHAPTER_TAGS, -1 } }, + [SECTION_ID_CHAPTER_TAGS] = { SECTION_ID_CHAPTER_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "chapter_tags" }, [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_FRAMES] = { SECTION_ID_FRAMES, "frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME, SECTION_ID_SUBTITLE, -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 } }, @@ -124,14 +159,22 @@ static struct section sections[] = { [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_STREAM_DISPOSITION] = { SECTION_ID_PROGRAM_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "program_stream_disposition" }, + [SECTION_ID_PROGRAM_STREAM_TAGS] = { SECTION_ID_PROGRAM_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_stream_tags" }, + [SECTION_ID_PROGRAM] = { SECTION_ID_PROGRAM, "program", 0, { SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_STREAMS, -1 } }, + [SECTION_ID_PROGRAM_STREAMS] = { SECTION_ID_PROGRAM_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM_STREAM, -1 }, .unique_name = "program_streams" }, + [SECTION_ID_PROGRAM_STREAM] = { SECTION_ID_PROGRAM_STREAM, "stream", 0, { SECTION_ID_PROGRAM_STREAM_DISPOSITION, SECTION_ID_PROGRAM_STREAM_TAGS, -1 }, .unique_name = "program_stream" }, + [SECTION_ID_PROGRAM_TAGS] = { SECTION_ID_PROGRAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_tags" }, [SECTION_ID_PROGRAM_VERSION] = { SECTION_ID_PROGRAM_VERSION, "program_version", 0, { -1 } }, + [SECTION_ID_PROGRAMS] = { SECTION_ID_PROGRAMS, "programs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM, -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_CHAPTERS, SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_PROGRAMS, 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" }, + [SECTION_ID_SUBTITLE] = { SECTION_ID_SUBTITLE, "subtitle", 0, { -1 } }, }; static const OptionDef *options; @@ -152,7 +195,7 @@ static uint64_t *nb_streams_packets; static uint64_t *nb_streams_frames; static int *selected_streams; -static void exit_program(void) +static void ffprobe_cleanup(int ret) { int i; for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) @@ -223,6 +266,13 @@ typedef struct WriterContext WriterContext; #define WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS 1 #define WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER 2 +typedef enum { + WRITER_STRING_VALIDATION_FAIL, + WRITER_STRING_VALIDATION_REPLACE, + WRITER_STRING_VALIDATION_IGNORE, + WRITER_STRING_VALIDATION_NB +} StringValidation; + typedef struct Writer { const AVClass *priv_class; ///< private class of the writer, if any int priv_size; ///< private size for the writer context @@ -263,6 +313,10 @@ struct WriterContext { unsigned int nb_section_packet; ///< number of the packet section in case we are in "packets_and_frames" section unsigned int nb_section_frame; ///< number of the frame section in case we are in "packets_and_frames" section unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame according if is_packets_and_frames + + StringValidation string_validation; + char *string_validation_replacement; + unsigned int string_validation_utf8_flags; }; static const char *writer_get_name(void *p) @@ -271,11 +325,35 @@ static const char *writer_get_name(void *p) return wctx->writer->name; } +#define OFFSET(x) offsetof(WriterContext, x) + +static const AVOption writer_options[] = { + { "string_validation", "set string validation mode", + OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, + { "sv", "set string validation mode", + OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, + { "ignore", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_IGNORE}, .unit = "sv" }, + { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_REPLACE}, .unit = "sv" }, + { "fail", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_FAIL}, .unit = "sv" }, + { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}}, + { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}}, + { NULL } +}; + +static void *writer_child_next(void *obj, void *prev) +{ + WriterContext *ctx = obj; + if (!prev && ctx->writer && ctx->writer->priv_class && ctx->priv) + return ctx->priv; + return NULL; +} + static const AVClass writer_class = { - "Writer", - writer_get_name, - NULL, - LIBAVUTIL_VERSION_INT, + .class_name = "Writer", + .item_name = writer_get_name, + .option = writer_options, + .version = LIBAVUTIL_VERSION_INT, + .child_next = writer_child_next, }; static void writer_close(WriterContext **wctx) @@ -292,9 +370,19 @@ static void writer_close(WriterContext **wctx) if ((*wctx)->writer->priv_class) av_opt_free((*wctx)->priv); av_freep(&((*wctx)->priv)); + av_opt_free(*wctx); av_freep(wctx); } +static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size) +{ + int i; + av_bprintf(bp, "0X"); + for (i = 0; i < ubuf_size; i++) + av_bprintf(bp, "%02X", ubuf[i]); +} + + static int writer_open(WriterContext **wctx, const Writer *writer, const char *args, const struct section *sections, int nb_sections) { @@ -316,14 +404,55 @@ static int writer_open(WriterContext **wctx, const Writer *writer, const char *a (*wctx)->sections = sections; (*wctx)->nb_sections = nb_sections; + av_opt_set_defaults(*wctx); + if (writer->priv_class) { void *priv_ctx = (*wctx)->priv; *((const AVClass **)priv_ctx) = writer->priv_class; av_opt_set_defaults(priv_ctx); + } + + /* convert options to dictionary */ + if (args) { + AVDictionary *opts = NULL; + AVDictionaryEntry *opt = NULL; - if (args && - (ret = av_set_options_string(priv_ctx, args, "=", ":")) < 0) + if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) { + av_log(*wctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to writer context\n", args); + av_dict_free(&opts); goto fail; + } + + while ((opt = av_dict_get(opts, "", opt, AV_DICT_IGNORE_SUFFIX))) { + if ((ret = av_opt_set(*wctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) { + av_log(*wctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to writer context\n", + opt->key, opt->value); + av_dict_free(&opts); + goto fail; + } + } + + av_dict_free(&opts); + } + + /* validate replace string */ + { + const uint8_t *p = (*wctx)->string_validation_replacement; + const uint8_t *endp = p + strlen(p); + while (*p) { + const uint8_t *p0 = p; + int32_t code; + ret = av_utf8_decode(&code, &p, endp, (*wctx)->string_validation_utf8_flags); + if (ret < 0) { + AVBPrint bp; + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + bprint_bytes(&bp, p0, p-p0), + av_log(wctx, AV_LOG_ERROR, + "Invalid UTF8 sequence %s found in string validation replace '%s'\n", + bp.str, (*wctx)->string_validation_replacement); + return ret; + } + } } for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) @@ -393,18 +522,98 @@ static inline void writer_print_integer(WriterContext *wctx, } } -static inline void writer_print_string(WriterContext *wctx, - const char *key, const char *val, int opt) +static inline int validate_string(WriterContext *wctx, char **dstp, const char *src) +{ + const uint8_t *p, *endp; + AVBPrint dstbuf; + int invalid_chars_nb = 0, ret = 0; + + av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED); + + endp = src + strlen(src); + for (p = (uint8_t *)src; *p;) { + uint32_t code; + int invalid = 0; + const uint8_t *p0 = p; + + if (av_utf8_decode(&code, &p, endp, wctx->string_validation_utf8_flags) < 0) { + AVBPrint bp; + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + bprint_bytes(&bp, p0, p-p0); + av_log(wctx, AV_LOG_DEBUG, + "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src); + invalid = 1; + } + + if (invalid) { + invalid_chars_nb++; + + switch (wctx->string_validation) { + case WRITER_STRING_VALIDATION_FAIL: + av_log(wctx, AV_LOG_ERROR, + "Invalid UTF-8 sequence found in string '%s'\n", src); + ret = AVERROR_INVALIDDATA; + goto end; + break; + + case WRITER_STRING_VALIDATION_REPLACE: + av_bprintf(&dstbuf, "%s", wctx->string_validation_replacement); + break; + } + } + + if (!invalid || wctx->string_validation == WRITER_STRING_VALIDATION_IGNORE) + av_bprint_append_data(&dstbuf, p0, p-p0); + } + + if (invalid_chars_nb && wctx->string_validation == WRITER_STRING_VALIDATION_REPLACE) { + av_log(wctx, AV_LOG_WARNING, + "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n", + invalid_chars_nb, src, wctx->string_validation_replacement); + } + +end: + av_bprint_finalize(&dstbuf, dstp); + return ret; +} + +#define PRINT_STRING_OPT 1 +#define PRINT_STRING_VALIDATE 2 + +static inline int writer_print_string(WriterContext *wctx, + const char *key, const char *val, int flags) { const struct section *section = wctx->section[wctx->level]; + int ret = 0; - if (opt && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS)) - return; + if ((flags & PRINT_STRING_OPT) + && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS)) + return 0; if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { - wctx->writer->print_string(wctx, key, val); + if (flags & PRINT_STRING_VALIDATE) { + char *key1 = NULL, *val1 = NULL; + ret = validate_string(wctx, &key1, key); + if (ret < 0) goto end; + ret = validate_string(wctx, &val1, val); + if (ret < 0) goto end; + wctx->writer->print_string(wctx, key1, val1); + end: + if (ret < 0) { + av_log(wctx, AV_LOG_ERROR, + "Invalid key=value string combination %s=%s in section %s\n", + key, val, section->unique_name); + } + av_free(key1); + av_free(val1); + } else { + wctx->writer->print_string(wctx, key, val); + } + wctx->nb_item[wctx->level]++; } + + return ret; } static inline void writer_print_rational(WriterContext *wctx, @@ -422,7 +631,7 @@ static void writer_print_time(WriterContext *wctx, const char *key, char buf[128]; if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { - writer_print_string(wctx, key, "N/A", 1); + writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT); } else { double d = ts * av_q2d(*time_base); struct unit_value uv; @@ -436,7 +645,7 @@ static void writer_print_time(WriterContext *wctx, const char *key, static void writer_print_ts(WriterContext *wctx, const char *key, int64_t ts, int is_duration) { if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { - writer_print_string(wctx, key, "N/A", 1); + writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT); } else { writer_print_integer(wctx, key, ts); } @@ -505,9 +714,9 @@ static const char *name##_get_name(void *ctx) \ return #name ; \ } \ static const AVClass name##_class = { \ - #name, \ - name##_get_name, \ - name##_options \ + .class_name = #name, \ + .item_name = name##_get_name, \ + .option = name##_options \ } /* Default output */ @@ -519,6 +728,7 @@ typedef struct DefaultContext { int nested_section[SECTION_MAX_NB_LEVELS]; } DefaultContext; +#undef OFFSET #define OFFSET(x) offsetof(DefaultContext, x) static const AVOption default_options[] = { @@ -668,6 +878,8 @@ typedef struct CompactContext { char *escape_mode_str; const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx); int nested_section[SECTION_MAX_NB_LEVELS]; + int has_nested_elems[SECTION_MAX_NB_LEVELS]; + int terminate_line[SECTION_MAX_NB_LEVELS]; } CompactContext; #undef OFFSET @@ -715,18 +927,28 @@ static void compact_print_section_header(WriterContext *wctx) const struct section *section = wctx->section[wctx->level]; const struct section *parent_section = wctx->level ? wctx->section[wctx->level-1] : NULL; + compact->terminate_line[wctx->level] = 1; + compact->has_nested_elems[wctx->level] = 0; av_bprint_clear(&wctx->section_pbuf[wctx->level]); - if (parent_section && + if (!(section->flags & SECTION_FLAG_IS_ARRAY) && parent_section && !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) { compact->nested_section[wctx->level] = 1; + compact->has_nested_elems[wctx->level-1] = 1; av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:", wctx->section_pbuf[wctx->level-1].str, (char *)av_x_if_null(section->element_name, section->name)); wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1]; - } else if (compact->print_section && - !(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) - printf("%s%c", section->name, compact->item_sep); + } else { + if (parent_section && compact->has_nested_elems[wctx->level-1] && + (section->flags & SECTION_FLAG_IS_ARRAY)) { + compact->terminate_line[wctx->level-1] = 0; + printf("\n"); + } + if (compact->print_section && + !(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) + printf("%s%c", section->name, compact->item_sep); + } } static void compact_print_section_footer(WriterContext *wctx) @@ -734,6 +956,7 @@ static void compact_print_section_footer(WriterContext *wctx) CompactContext *compact = wctx->priv; if (!compact->nested_section[wctx->level] && + compact->terminate_line[wctx->level] && !(wctx->section[wctx->level]->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) printf("\n"); } @@ -1392,7 +1615,8 @@ static void writer_register_all(void) #define print_int(k, v) writer_print_integer(w, k, v) #define print_q(k, v, s) writer_print_rational(w, k, v, s) #define print_str(k, v) writer_print_string(w, k, v, 0) -#define print_str_opt(k, v) writer_print_string(w, k, v, 1) +#define print_str_opt(k, v) writer_print_string(w, k, v, PRINT_STRING_OPT) +#define print_str_validate(k, v) writer_print_string(w, k, v, PRINT_STRING_VALIDATE) #define print_time(k, v, tb) writer_print_time(w, k, v, tb, 0) #define print_ts(k, v) writer_print_ts(w, k, v, 0) #define print_duration_time(k, v, tb) writer_print_time(w, k, v, tb, 1) @@ -1407,16 +1631,22 @@ static void writer_register_all(void) #define print_section_header(s) writer_print_section_header(w, s) #define print_section_footer(s) writer_print_section_footer(w, s) -static inline void show_tags(WriterContext *wctx, AVDictionary *tags, int section_id) +static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id) { AVDictionaryEntry *tag = NULL; + int ret = 0; if (!tags) - return; - writer_print_section_header(wctx, section_id); - while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) - writer_print_string(wctx, tag->key, tag->value, 0); - writer_print_section_footer(wctx); + return 0; + writer_print_section_header(w, section_id); + + while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) { + if ((ret = print_str_validate(tag->key, tag->value)) < 0) + break; + } + writer_print_section_footer(w); + + return ret; } static void show_packet(WriterContext *w, AVFormatContext *fmt_ctx, AVPacket *pkt, int packet_idx) @@ -1454,6 +1684,29 @@ static void show_packet(WriterContext *w, AVFormatContext *fmt_ctx, AVPacket *pk fflush(stdout); } +static void show_subtitle(WriterContext *w, AVSubtitle *sub, AVStream *stream, + AVFormatContext *fmt_ctx) +{ + AVBPrint pbuf; + + av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); + + writer_print_section_header(w, SECTION_ID_SUBTITLE); + + print_str ("media_type", "subtitle"); + print_ts ("pts", sub->pts); + print_time("pts_time", sub->pts, &AV_TIME_BASE_Q); + print_int ("format", sub->format); + print_int ("start_display_time", sub->start_display_time); + print_int ("end_display_time", sub->end_display_time); + print_int ("num_rects", sub->num_rects); + + writer_print_section_footer(w); + + av_bprint_finalize(&pbuf, NULL); + fflush(stdout); +} + static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, AVFormatContext *fmt_ctx) { @@ -1472,6 +1725,8 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, print_time("pkt_pts_time", frame->pkt_pts, &stream->time_base); print_ts ("pkt_dts", frame->pkt_dts); print_time("pkt_dts_time", frame->pkt_dts, &stream->time_base); + print_ts ("best_effort_timestamp", av_frame_get_best_effort_timestamp(frame)); + print_time("best_effort_timestamp_time", av_frame_get_best_effort_timestamp(frame), &stream->time_base); print_duration_ts ("pkt_duration", av_frame_get_pkt_duration(frame)); print_duration_time("pkt_duration_time", av_frame_get_pkt_duration(frame), &stream->time_base); if (av_frame_get_pkt_pos (frame) != -1) print_fmt ("pkt_pos", "%"PRId64, av_frame_get_pkt_pos(frame)); @@ -1517,7 +1772,8 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, print_str_opt("channel_layout", "unknown"); break; } - show_tags(w, av_frame_get_metadata(frame), SECTION_ID_FRAME_TAGS); + if (do_show_frame_tags) + show_tags(w, av_frame_get_metadata(frame), SECTION_ID_FRAME_TAGS); writer_print_section_footer(w); @@ -1530,9 +1786,9 @@ static av_always_inline int process_frame(WriterContext *w, AVFrame *frame, AVPacket *pkt) { AVCodecContext *dec_ctx = fmt_ctx->streams[pkt->stream_index]->codec; + AVSubtitle sub; int ret = 0, got_frame = 0; - avcodec_get_frame_defaults(frame); if (dec_ctx->codec) { switch (dec_ctx->codec_type) { case AVMEDIA_TYPE_VIDEO: @@ -1542,6 +1798,10 @@ static av_always_inline int process_frame(WriterContext *w, case AVMEDIA_TYPE_AUDIO: ret = avcodec_decode_audio4(dec_ctx, frame, &got_frame, pkt); break; + + case AVMEDIA_TYPE_SUBTITLE: + ret = avcodec_decode_subtitle2(dec_ctx, &sub, &got_frame, pkt); + break; } } @@ -1551,23 +1811,107 @@ static av_always_inline int process_frame(WriterContext *w, pkt->data += ret; pkt->size -= ret; if (got_frame) { + int is_sub = (dec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE); nb_streams_frames[pkt->stream_index]++; if (do_show_frames) - show_frame(w, frame, fmt_ctx->streams[pkt->stream_index], fmt_ctx); + if (is_sub) + show_subtitle(w, &sub, fmt_ctx->streams[pkt->stream_index], fmt_ctx); + else + show_frame(w, frame, fmt_ctx->streams[pkt->stream_index], fmt_ctx); + if (is_sub) + avsubtitle_free(&sub); } return got_frame; } -static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) +static void log_read_interval(const ReadInterval *interval, void *log_ctx, int log_level) +{ + av_log(log_ctx, log_level, "id:%d", interval->id); + + if (interval->has_start) { + av_log(log_ctx, log_level, " start:%s%s", interval->start_is_offset ? "+" : "", + av_ts2timestr(interval->start, &AV_TIME_BASE_Q)); + } else { + av_log(log_ctx, log_level, " start:N/A"); + } + + if (interval->has_end) { + av_log(log_ctx, log_level, " end:%s", interval->end_is_offset ? "+" : ""); + if (interval->duration_frames) + av_log(log_ctx, log_level, "#%"PRId64, interval->end); + else + av_log(log_ctx, log_level, "%s", av_ts2timestr(interval->end, &AV_TIME_BASE_Q)); + } else { + av_log(log_ctx, log_level, " end:N/A"); + } + + av_log(log_ctx, log_level, "\n"); +} + +static int read_interval_packets(WriterContext *w, AVFormatContext *fmt_ctx, + const ReadInterval *interval, int64_t *cur_ts) { AVPacket pkt, pkt1; - AVFrame frame; - int i = 0; + AVFrame *frame = NULL; + int ret = 0, i = 0, frame_count = 0; + int64_t start = -INT64_MAX, end = interval->end; + int has_start = 0, has_end = interval->has_end && !interval->end_is_offset; av_init_packet(&pkt); + av_log(NULL, AV_LOG_VERBOSE, "Processing read interval "); + log_read_interval(interval, NULL, AV_LOG_VERBOSE); + + if (interval->has_start) { + int64_t target; + if (interval->start_is_offset) { + if (*cur_ts == AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_ERROR, + "Could not seek to relative position since current " + "timestamp is not defined\n"); + ret = AVERROR(EINVAL); + goto end; + } + target = *cur_ts + interval->start; + } else { + target = interval->start; + } + + av_log(NULL, AV_LOG_VERBOSE, "Seeking to read interval start point %s\n", + av_ts2timestr(target, &AV_TIME_BASE_Q)); + if ((ret = avformat_seek_file(fmt_ctx, -1, -INT64_MAX, target, INT64_MAX, 0)) < 0) { + av_log(NULL, AV_LOG_ERROR, "Could not seek to position %"PRId64": %s\n", + interval->start, av_err2str(ret)); + goto end; + } + } + + frame = av_frame_alloc(); while (!av_read_frame(fmt_ctx, &pkt)) { if (selected_streams[pkt.stream_index]) { + AVRational tb = fmt_ctx->streams[pkt.stream_index]->time_base; + + if (pkt.pts != AV_NOPTS_VALUE) + *cur_ts = av_rescale_q(pkt.pts, tb, AV_TIME_BASE_Q); + + if (!has_start && *cur_ts != AV_NOPTS_VALUE) { + start = *cur_ts; + has_start = 1; + } + + if (has_start && !has_end && interval->end_is_offset) { + end = start + interval->end; + has_end = 1; + } + + if (interval->end_is_offset && interval->duration_frames) { + if (frame_count >= interval->end) + break; + } else if (has_end && *cur_ts != AV_NOPTS_VALUE && *cur_ts >= end) { + break; + } + + frame_count++; if (do_read_packets) { if (do_show_packets) show_packet(w, fmt_ctx, &pkt, i++); @@ -1575,7 +1919,7 @@ static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) } if (do_read_frames) { pkt1 = pkt; - while (pkt1.size && process_frame(w, fmt_ctx, &frame, &pkt1) > 0); + while (pkt1.size && process_frame(w, fmt_ctx, frame, &pkt1) > 0); } } av_free_packet(&pkt); @@ -1587,11 +1931,38 @@ static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) for (i = 0; i < fmt_ctx->nb_streams; i++) { pkt.stream_index = i; if (do_read_frames) - while (process_frame(w, fmt_ctx, &frame, &pkt) > 0); + while (process_frame(w, fmt_ctx, frame, &pkt) > 0); + } + +end: + av_frame_free(&frame); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Could not read packets in interval "); + log_read_interval(interval, NULL, AV_LOG_ERROR); + } + return ret; +} + +static int read_packets(WriterContext *w, AVFormatContext *fmt_ctx) +{ + int i, ret = 0; + int64_t cur_ts = fmt_ctx->start_time; + + if (read_intervals_nb == 0) { + ReadInterval interval = (ReadInterval) { .has_start = 0, .has_end = 0 }; + ret = read_interval_packets(w, fmt_ctx, &interval, &cur_ts); + } else { + for (i = 0; i < read_intervals_nb; i++) { + ret = read_interval_packets(w, fmt_ctx, &read_intervals[i], &cur_ts); + if (ret < 0) + break; + } } + + return ret; } -static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx) +static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, int in_program) { AVStream *stream = fmt_ctx->streams[stream_idx]; AVCodecContext *dec_ctx; @@ -1600,10 +1971,11 @@ static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_i const char *s; AVRational sar, dar; AVBPrint pbuf; + int ret = 0; av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - writer_print_section_header(w, SECTION_ID_STREAM); + writer_print_section_header(w, in_program ? SECTION_ID_PROGRAM_STREAM : SECTION_ID_STREAM); print_int("index", stream->index); @@ -1674,8 +2046,28 @@ static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_i else print_str_opt("sample_fmt", "unknown"); print_val("sample_rate", dec_ctx->sample_rate, unit_hertz_str); print_int("channels", dec_ctx->channels); + + if (dec_ctx->channel_layout) { + av_bprint_clear(&pbuf); + av_bprint_channel_layout(&pbuf, dec_ctx->channels, dec_ctx->channel_layout); + print_str ("channel_layout", pbuf.str); + } else { + print_str_opt("channel_layout", "unknown"); + } + print_int("bits_per_sample", av_get_bits_per_sample(dec_ctx->codec_id)); break; + + case AVMEDIA_TYPE_SUBTITLE: + if (dec_ctx->width) + print_int("width", dec_ctx->width); + else + print_str_opt("width", "N/A"); + if (dec_ctx->height) + print_int("height", dec_ctx->height); + else + print_str_opt("height", "N/A"); + break; } } else { print_str_opt("codec_type", "unknown"); @@ -1719,7 +2111,7 @@ static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_i } while (0) if (do_show_stream_disposition) { - writer_print_section_header(w, SECTION_ID_STREAM_DISPOSITION); + writer_print_section_header(w, in_program ? SECTION_ID_PROGRAM_STREAM_DISPOSITION : SECTION_ID_STREAM_DISPOSITION); PRINT_DISPOSITION(DEFAULT, "default"); PRINT_DISPOSITION(DUB, "dub"); PRINT_DISPOSITION(ORIGINAL, "original"); @@ -1734,31 +2126,117 @@ static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_i writer_print_section_footer(w); } - show_tags(w, stream->metadata, SECTION_ID_STREAM_TAGS); + if (do_show_stream_tags) + ret = show_tags(w, stream->metadata, in_program ? SECTION_ID_PROGRAM_STREAM_TAGS : SECTION_ID_STREAM_TAGS); writer_print_section_footer(w); av_bprint_finalize(&pbuf, NULL); fflush(stdout); + + return ret; } -static void show_streams(WriterContext *w, AVFormatContext *fmt_ctx) +static int show_streams(WriterContext *w, AVFormatContext *fmt_ctx) { - int i; + int i, ret = 0; + writer_print_section_header(w, SECTION_ID_STREAMS); for (i = 0; i < fmt_ctx->nb_streams; i++) - if (selected_streams[i]) - show_stream(w, fmt_ctx, i); + if (selected_streams[i]) { + ret = show_stream(w, fmt_ctx, i, 0); + if (ret < 0) + break; + } + writer_print_section_footer(w); + + return ret; +} + +static int show_program(WriterContext *w, AVFormatContext *fmt_ctx, AVProgram *program) +{ + int i, ret = 0; + + writer_print_section_header(w, SECTION_ID_PROGRAM); + print_int("program_id", program->id); + print_int("program_num", program->program_num); + print_int("nb_streams", program->nb_stream_indexes); + print_int("pmt_pid", program->pmt_pid); + print_int("pcr_pid", program->pcr_pid); + print_ts("start_pts", program->start_time); + print_time("start_time", program->start_time, &AV_TIME_BASE_Q); + print_ts("end_pts", program->end_time); + print_time("end_time", program->end_time, &AV_TIME_BASE_Q); + if (do_show_program_tags) + ret = show_tags(w, program->metadata, SECTION_ID_PROGRAM_TAGS); + if (ret < 0) + goto end; + + writer_print_section_header(w, SECTION_ID_PROGRAM_STREAMS); + for (i = 0; i < program->nb_stream_indexes; i++) { + if (selected_streams[program->stream_index[i]]) { + ret = show_stream(w, fmt_ctx, program->stream_index[i], 1); + if (ret < 0) + break; + } + } + writer_print_section_footer(w); + +end: + writer_print_section_footer(w); + return ret; +} + +static int show_programs(WriterContext *w, AVFormatContext *fmt_ctx) +{ + int i, ret = 0; + + writer_print_section_header(w, SECTION_ID_PROGRAMS); + for (i = 0; i < fmt_ctx->nb_programs; i++) { + AVProgram *program = fmt_ctx->programs[i]; + if (!program) + continue; + ret = show_program(w, fmt_ctx, program); + if (ret < 0) + break; + } writer_print_section_footer(w); + return ret; } -static void show_format(WriterContext *w, AVFormatContext *fmt_ctx) +static int show_chapters(WriterContext *w, AVFormatContext *fmt_ctx) +{ + int i, ret = 0; + + writer_print_section_header(w, SECTION_ID_CHAPTERS); + for (i = 0; i < fmt_ctx->nb_chapters; i++) { + AVChapter *chapter = fmt_ctx->chapters[i]; + + writer_print_section_header(w, SECTION_ID_CHAPTER); + print_int("id", chapter->id); + print_q ("time_base", chapter->time_base, '/'); + print_int("start", chapter->start); + print_time("start_time", chapter->start, &chapter->time_base); + print_int("end", chapter->end); + print_time("end_time", chapter->end, &chapter->time_base); + if (do_show_chapter_tags) + ret = show_tags(w, chapter->metadata, SECTION_ID_CHAPTER_TAGS); + writer_print_section_footer(w); + } + writer_print_section_footer(w); + + return ret; +} + +static int show_format(WriterContext *w, AVFormatContext *fmt_ctx) { char val_str[128]; int64_t size = fmt_ctx->pb ? avio_size(fmt_ctx->pb) : -1; + int ret = 0; writer_print_section_header(w, SECTION_ID_FORMAT); - print_str("filename", fmt_ctx->filename); + print_str_validate("filename", fmt_ctx->filename); print_int("nb_streams", fmt_ctx->nb_streams); + print_int("nb_programs", fmt_ctx->nb_programs); print_str("format_name", fmt_ctx->iformat->name); if (!do_bitexact) { if (fmt_ctx->iformat->long_name) print_str ("format_long_name", fmt_ctx->iformat->long_name); @@ -1770,10 +2248,13 @@ static void show_format(WriterContext *w, AVFormatContext *fmt_ctx) else print_str_opt("size", "N/A"); if (fmt_ctx->bit_rate > 0) print_val ("bit_rate", fmt_ctx->bit_rate, unit_bit_per_second_str); else print_str_opt("bit_rate", "N/A"); - show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); + print_int("probe_score", av_format_get_probe_score(fmt_ctx)); + if (do_show_format_tags) + ret = show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); writer_print_section_footer(w); fflush(stdout); + return ret; } static void show_error(WriterContext *w, int err) @@ -1827,18 +2308,18 @@ static int open_input_file(AVFormatContext **fmt_ctx_ptr, const char *filename) AVCodec *codec; if (stream->codec->codec_id == AV_CODEC_ID_PROBE) { - av_log(NULL, AV_LOG_ERROR, + av_log(NULL, AV_LOG_WARNING, "Failed to probe codec for input stream %d\n", stream->index); } else if (!(codec = avcodec_find_decoder(stream->codec->codec_id))) { - av_log(NULL, AV_LOG_ERROR, + av_log(NULL, AV_LOG_WARNING, "Unsupported codec with id %d for input stream %d\n", stream->codec->codec_id, stream->index); } else { AVDictionary *opts = filter_codec_opts(codec_opts, stream->codec->codec_id, fmt_ctx, stream, codec); if (avcodec_open2(stream->codec, codec, &opts) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error while opening codec for input stream %d\n", + av_log(NULL, AV_LOG_WARNING, "Could not open codec for input stream %d\n", stream->index); } if ((t = av_dict_get(opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { @@ -1876,50 +2357,69 @@ static int probe_file(WriterContext *wctx, const char *filename) do_read_packets = do_show_packets || do_count_packets; ret = open_input_file(&fmt_ctx, filename); - if (ret >= 0) { - nb_streams_frames = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_frames)); - nb_streams_packets = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_packets)); - selected_streams = av_calloc(fmt_ctx->nb_streams, sizeof(*selected_streams)); - - for (i = 0; i < fmt_ctx->nb_streams; i++) { - if (stream_specifier) { - ret = avformat_match_stream_specifier(fmt_ctx, - fmt_ctx->streams[i], - stream_specifier); - if (ret < 0) - goto end; - else - selected_streams[i] = ret; - } else { - selected_streams[i] = 1; - } - } + if (ret < 0) + return ret; + +#define CHECK_END if (ret < 0) goto end + + nb_streams_frames = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_frames)); + nb_streams_packets = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_packets)); + selected_streams = av_calloc(fmt_ctx->nb_streams, sizeof(*selected_streams)); - if (do_read_frames || do_read_packets) { - if (do_show_frames && do_show_packets && - wctx->writer->flags & WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER) - section_id = SECTION_ID_PACKETS_AND_FRAMES; - else if (do_show_packets && !do_show_frames) - section_id = SECTION_ID_PACKETS; - else // (!do_show_packets && do_show_frames) - section_id = SECTION_ID_FRAMES; - if (do_show_frames || do_show_packets) - writer_print_section_header(wctx, section_id); - read_packets(wctx, fmt_ctx); - if (do_show_frames || do_show_packets) - writer_print_section_footer(wctx); + for (i = 0; i < fmt_ctx->nb_streams; i++) { + if (stream_specifier) { + ret = avformat_match_stream_specifier(fmt_ctx, + fmt_ctx->streams[i], + stream_specifier); + CHECK_END; + else + selected_streams[i] = ret; + ret = 0; + } else { + selected_streams[i] = 1; } - if (do_show_streams) - show_streams(wctx, fmt_ctx); - if (do_show_format) - show_format(wctx, fmt_ctx); + } - end: - close_input_file(&fmt_ctx); - av_freep(&nb_streams_frames); - av_freep(&nb_streams_packets); - av_freep(&selected_streams); + if (do_read_frames || do_read_packets) { + if (do_show_frames && do_show_packets && + wctx->writer->flags & WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER) + section_id = SECTION_ID_PACKETS_AND_FRAMES; + else if (do_show_packets && !do_show_frames) + section_id = SECTION_ID_PACKETS; + else // (!do_show_packets && do_show_frames) + section_id = SECTION_ID_FRAMES; + if (do_show_frames || do_show_packets) + writer_print_section_header(wctx, section_id); + ret = read_packets(wctx, fmt_ctx); + if (do_show_frames || do_show_packets) + writer_print_section_footer(wctx); + CHECK_END; } + + if (do_show_programs) { + ret = show_programs(wctx, fmt_ctx); + CHECK_END; + } + + if (do_show_streams) { + ret = show_streams(wctx, fmt_ctx); + CHECK_END; + } + if (do_show_chapters) { + ret = show_chapters(wctx, fmt_ctx); + CHECK_END; + } + if (do_show_format) { + ret = show_format(wctx, fmt_ctx); + CHECK_END; + } + +end: + close_input_file(&fmt_ctx); + av_freep(&nb_streams_frames); + av_freep(&nb_streams_packets); + av_freep(&selected_streams); + return ret; } @@ -1938,7 +2438,7 @@ static void ffprobe_show_program_version(WriterContext *w) writer_print_section_header(w, SECTION_ID_PROGRAM_VERSION); print_str("version", FFMPEG_VERSION); print_fmt("copyright", "Copyright (c) %d-%d the FFmpeg developers", - program_birth_year, this_year); + program_birth_year, CONFIG_THIS_YEAR); print_str("build_date", __DATE__); print_str("build_time", __TIME__); print_str("compiler_ident", CC_IDENT); @@ -2090,7 +2590,7 @@ static void opt_input_file(void *optctx, const char *arg) av_log(NULL, AV_LOG_ERROR, "Argument '%s' provided as input filename, but '%s' was already specified.\n", arg, input_filename); - exit(1); + exit_program(1); } if (!strcmp(arg, "-")) arg = "pipe:"; @@ -2113,6 +2613,145 @@ void show_help_default(const char *opt, const char *arg) show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM); } +/** + * Parse interval specification, according to the format: + * INTERVAL ::= [START|+START_OFFSET][%[END|+END_OFFSET]] + * INTERVALS ::= INTERVAL[,INTERVALS] +*/ +static int parse_read_interval(const char *interval_spec, + ReadInterval *interval) +{ + int ret = 0; + char *next, *p, *spec = av_strdup(interval_spec); + if (!spec) + return AVERROR(ENOMEM); + + if (!*spec) { + av_log(NULL, AV_LOG_ERROR, "Invalid empty interval specification\n"); + ret = AVERROR(EINVAL); + goto end; + } + + p = spec; + next = strchr(spec, '%'); + if (next) + *next++ = 0; + + /* parse first part */ + if (*p) { + interval->has_start = 1; + + if (*p == '+') { + interval->start_is_offset = 1; + p++; + } else { + interval->start_is_offset = 0; + } + + ret = av_parse_time(&interval->start, p, 1); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Invalid interval start specification '%s'\n", p); + goto end; + } + } else { + interval->has_start = 0; + } + + /* parse second part */ + p = next; + if (p && *p) { + int64_t us; + interval->has_end = 1; + + if (*p == '+') { + interval->end_is_offset = 1; + p++; + } else { + interval->end_is_offset = 0; + } + + if (interval->end_is_offset && *p == '#') { + long long int lli; + char *tail; + interval->duration_frames = 1; + p++; + lli = strtoll(p, &tail, 10); + if (*tail || lli < 0) { + av_log(NULL, AV_LOG_ERROR, + "Invalid or negative value '%s' for duration number of frames\n", p); + goto end; + } + interval->end = lli; + } else { + ret = av_parse_time(&us, p, 1); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Invalid interval end/duration specification '%s'\n", p); + goto end; + } + interval->end = us; + } + } else { + interval->has_end = 0; + } + +end: + av_free(spec); + return ret; +} + +static int parse_read_intervals(const char *intervals_spec) +{ + int ret, n, i; + char *p, *spec = av_strdup(intervals_spec); + if (!spec) + return AVERROR(ENOMEM); + + /* preparse specification, get number of intervals */ + for (n = 0, p = spec; *p; p++) + if (*p == ',') + n++; + n++; + + read_intervals = av_malloc(n * sizeof(*read_intervals)); + if (!read_intervals) { + ret = AVERROR(ENOMEM); + goto end; + } + read_intervals_nb = n; + + /* parse intervals */ + p = spec; + for (i = 0; p; i++) { + char *next; + + av_assert0(i < read_intervals_nb); + next = strchr(p, ','); + if (next) + *next++ = 0; + + read_intervals[i].id = i; + ret = parse_read_interval(p, &read_intervals[i]); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing read interval #%d '%s'\n", + i, p); + goto end; + } + av_log(NULL, AV_LOG_VERBOSE, "Parsed log interval "); + log_read_interval(&read_intervals[i], NULL, AV_LOG_VERBOSE); + p = next; + } + av_assert0(i == read_intervals_nb); + +end: + av_free(spec); + return ret; +} + +static int opt_read_intervals(void *optctx, const char *opt, const char *arg) +{ + return parse_read_intervals(arg); +} + static int opt_pretty(void *optctx, const char *opt, const char *arg) { show_value_unit = 1; @@ -2165,6 +2804,7 @@ static int opt_show_versions(const char *opt, const char *arg) return 0; \ } +DEFINE_OPT_SHOW_SECTION(chapters, CHAPTERS); DEFINE_OPT_SHOW_SECTION(error, ERROR); DEFINE_OPT_SHOW_SECTION(format, FORMAT); DEFINE_OPT_SHOW_SECTION(frames, FRAMES); @@ -2172,6 +2812,7 @@ 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); +DEFINE_OPT_SHOW_SECTION(programs, PROGRAMS); static const OptionDef real_options[] = { #include "cmdutils_common_opts.h" @@ -2198,7 +2839,9 @@ static const OptionDef real_options[] = { { "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_programs", 0, {(void*)&opt_show_programs}, "show programs info" }, { "show_streams", 0, {(void*)&opt_show_streams}, "show streams info" }, + { "show_chapters", 0, {(void*)&opt_show_chapters}, "show chapters 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", 0, {(void*)&opt_show_program_version}, "show ffprobe version" }, @@ -2207,6 +2850,7 @@ static const OptionDef real_options[] = { { "show_private_data", OPT_BOOL, {(void*)&show_private_data}, "show private data" }, { "private", OPT_BOOL, {(void*)&show_private_data}, "same as show_private_data" }, { "bitexact", OPT_BOOL, {&do_bitexact}, "force bitexact output" }, + { "read_intervals", HAS_ARG, {.func_arg = opt_read_intervals}, "set read intervals", "read_intervals" }, { "default", HAS_ARG | OPT_AUDIO | OPT_VIDEO | OPT_EXPERT, {.func_arg = opt_default}, "generic catch all option", "" }, { "i", HAS_ARG, {.func_arg = opt_input_file_i}, "read specified file", "input_file"}, { NULL, }, @@ -2238,7 +2882,7 @@ int main(int argc, char **argv) int ret, i; av_log_set_flags(AV_LOG_SKIP_REPEATED); - atexit(exit_program); + register_exit(ffprobe_cleanup); options = real_options; parse_loglevel(argc, argv, options); @@ -2253,14 +2897,23 @@ int main(int argc, char **argv) parse_options(NULL, argc, argv, options, opt_input_file); /* mark things to show, based on -show_entries */ + SET_DO_SHOW(CHAPTERS, chapters); 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(PROGRAMS, programs); SET_DO_SHOW(STREAMS, streams); SET_DO_SHOW(STREAM_DISPOSITION, stream_disposition); + SET_DO_SHOW(PROGRAM_STREAM_DISPOSITION, stream_disposition); + + SET_DO_SHOW(CHAPTER_TAGS, chapter_tags); + SET_DO_SHOW(FORMAT_TAGS, format_tags); + SET_DO_SHOW(FRAME_TAGS, frame_tags); + SET_DO_SHOW(PROGRAM_TAGS, program_tags); + SET_DO_SHOW(STREAM_TAGS, stream_tags); if (do_bitexact && (do_show_program_version || do_show_library_versions)) { av_log(NULL, AV_LOG_ERROR, @@ -2290,6 +2943,9 @@ int main(int argc, char **argv) if ((ret = writer_open(&wctx, w, w_args, sections, FF_ARRAY_ELEMS(sections))) >= 0) { + if (w == &xml_writer) + wctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES; + writer_print_section_header(wctx, SECTION_ID_ROOT); if (do_show_program_version) @@ -2298,7 +2954,7 @@ int main(int argc, char **argv) ffprobe_show_library_versions(wctx); if (!input_filename && - ((do_show_format || do_show_streams || do_show_packets || do_show_error) || + ((do_show_format || do_show_programs || do_show_streams || do_show_chapters || do_show_packets || do_show_error) || (!do_show_program_version && !do_show_library_versions))) { show_usage(); av_log(NULL, AV_LOG_ERROR, "You have to specify one input file.\n"); @@ -2316,6 +2972,7 @@ int main(int argc, char **argv) end: av_freep(&print_format); + av_freep(&read_intervals); uninit_opts(); for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) @@ -2323,5 +2980,5 @@ end: avformat_network_deinit(); - return ret; + return ret < 0; } -- cgit v1.2.3