diff --git a/Changelog b/Changelog index 0b2d13bc76..f635e889cf 100644 --- a/Changelog +++ b/Changelog @@ -121,6 +121,7 @@ easier to use. The changes are: - Discworld II BMV decoding support - VBLE Decoder - OS X Video Decoder Acceleration (VDA) support +- compact output in ffprobe version 0.8: diff --git a/doc/ffprobe.texi b/doc/ffprobe.texi index 9496fc95be..172e35dc4e 100644 --- a/doc/ffprobe.texi +++ b/doc/ffprobe.texi @@ -147,6 +147,58 @@ keyN=valN Metadata tags are printed as a line in the corresponding FORMAT or STREAM section, and are prefixed by the string "TAG:". +@section compact +Compact format. + +Each section is printed on a single line. +If no option is specifid, the output has the form: +@example +section|key1=val1| ... |keyN=valN +@end example + +Metadata tags are printed in the corresponding "format" or "stream" +section. A metadata tag key, if printed, is prefixed by the string +"tag:". + +This writer accepts options as a list of @var{key}=@var{value} pairs, +separated by ":". + +The description of the accepted options follows. + +@table @option + +@item item_sep, s +Specify the character to use for separating fields in the output line. +It must be a single printable character, it is "|" by default. + +@item nokey, nk +If set to 1 specify not to print the key of each field. Its default +value is 0. + +@item escape, e +Set the escape mode to use, default to "c". + +It can assume one of the following values: +@table @option +@item c +Perform C-like escaping. Strings containing a newline ('\n') or +carriage return ('\r'), the escaping character ('\') or the item +separator character @var{SEP} are escaped using C-like fashioned +escaping, so that a newline is converted to the sequence "\n", a +carriage return to "\r", '\' to "\\" and the separator @var{SEP} is +converted to "\@var{SEP}". + +@item csv +Perform CSV-like escaping, as described in RFC4180. Strings +containing a newline ('\n'), a carriage return ('\r'), a double quote +('"'), or @var{SEP} are enclosed in double-quotes. + +@item none +Perform no escaping. +@end table + +@end table + @section json JSON based format. diff --git a/ffprobe.c b/ffprobe.c index a8c2ff0c9c..c15cf7bd2b 100644 --- a/ffprobe.c +++ b/ffprobe.c @@ -462,6 +462,232 @@ static Writer default_writer = { .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS, }; +/* Compact output */ + +/** + * Escape \n, \r, \\ and sep characters contained in s, and print the + * resulting string. + */ +static const char *c_escape_str(char **dst, size_t *dst_size, + const char *src, const char sep, void *log_ctx) +{ + const char *p; + char *q; + size_t size = 1; + + /* precompute size */ + for (p = src; *p; p++, size++) { + ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-2); + if (*p == '\n' || *p == '\r' || *p == '\\') + size++; + } + + ESCAPE_REALLOC_BUF(dst_size, dst, src, size); + + q = *dst; + for (p = src; *p; p++) { + switch (*src) { + case '\n': *q++ = '\\'; *q++ = 'n'; break; + case '\r': *q++ = '\\'; *q++ = 'r'; break; + case '\\': *q++ = '\\'; *q++ = '\\'; break; + default: + if (*p == sep) + *q++ = '\\'; + *q++ = *p; + } + } + *q = 0; + return *dst; +} + +/** + * Quote fields containing special characters, check RFC4180. + */ +static const char *csv_escape_str(char **dst, size_t *dst_size, + const char *src, const char sep, void *log_ctx) +{ + const char *p; + char *q; + size_t size = 1; + int quote = 0; + + /* precompute size */ + for (p = src; *p; p++, size++) { + ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-4); + if (*p == '"' || *p == sep || *p == '\n' || *p == '\r') + if (!quote) { + quote = 1; + size += 2; + } + if (*p == '"') + size++; + } + + ESCAPE_REALLOC_BUF(dst_size, dst, src, size); + + q = *dst; + p = src; + if (quote) + *q++ = '\"'; + while (*p) { + if (*p == '"') + *q++ = '\"'; + *q++ = *p++; + } + if (quote) + *q++ = '\"'; + *q = 0; + + return *dst; +} + +static const char *none_escape_str(char **dst, size_t *dst_size, + const char *src, const char sep, void *log_ctx) +{ + return src; +} + +typedef struct CompactContext { + const AVClass *class; + char *item_sep_str; + char item_sep; + int nokey; + char *buf; + size_t buf_size; + char *escape_mode_str; + const char * (*escape_str)(char **dst, size_t *dst_size, + const char *src, const char sep, void *log_ctx); +} CompactContext; + +#define OFFSET(x) offsetof(CompactContext, x) + +static const AVOption compact_options[]= { + {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, CHAR_MIN, CHAR_MAX }, + {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, CHAR_MIN, CHAR_MAX }, + {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1 }, + {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1 }, + {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, CHAR_MIN, CHAR_MAX }, + {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, CHAR_MIN, CHAR_MAX }, + {NULL}, +}; + +static const char *compact_get_name(void *ctx) +{ + return "compact"; +} + +static const AVClass compact_class = { + "CompactContext", + compact_get_name, + compact_options +}; + +static av_cold int compact_init(WriterContext *wctx, const char *args, void *opaque) +{ + CompactContext *compact = wctx->priv; + int err; + + compact->class = &compact_class; + av_opt_set_defaults(compact); + + if (args && + (err = (av_set_options_string(compact, args, "=", ":"))) < 0) { + av_log(wctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args); + return err; + } + if (strlen(compact->item_sep_str) != 1) { + av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", + compact->item_sep_str); + return AVERROR(EINVAL); + } + compact->item_sep = compact->item_sep_str[0]; + + compact->buf_size = ESCAPE_INIT_BUF_SIZE; + if (!(compact->buf = av_malloc(compact->buf_size))) + return AVERROR(ENOMEM); + + if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str; + else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str; + else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str; + else { + av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str); + return AVERROR(EINVAL); + } + + return 0; +} + +static av_cold void compact_uninit(WriterContext *wctx) +{ + CompactContext *compact = wctx->priv; + + av_freep(&compact->item_sep_str); + av_freep(&compact->buf); + av_freep(&compact->escape_mode_str); +} + +static void compact_print_section_header(WriterContext *wctx, const char *section) +{ + CompactContext *compact = wctx->priv; + + printf("%s%c", section, compact->item_sep); +} + +static void compact_print_section_footer(WriterContext *wctx, const char *section) +{ + printf("\n"); +} + +static void compact_print_str(WriterContext *wctx, const char *key, const char *value) +{ + CompactContext *compact = wctx->priv; + + if (wctx->nb_item) printf("%c", compact->item_sep); + if (!compact->nokey) + printf("%s=", key); + printf("%s", compact->escape_str(&compact->buf, &compact->buf_size, + value, compact->item_sep, wctx)); +} + +static void compact_print_int(WriterContext *wctx, const char *key, int value) +{ + CompactContext *compact = wctx->priv; + + if (wctx->nb_item) printf("%c", compact->item_sep); + if (!compact->nokey) + printf("%s=", key); + printf("%d", value); +} + +static void compact_show_tags(WriterContext *wctx, AVDictionary *dict) +{ + CompactContext *compact = wctx->priv; + AVDictionaryEntry *tag = NULL; + + while ((tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX))) { + if (wctx->nb_item) printf("%c", compact->item_sep); + if (!compact->nokey) + printf("tag:%s=", compact->escape_str(&compact->buf, &compact->buf_size, + tag->key, compact->item_sep, wctx)); + printf("%s", compact->escape_str(&compact->buf, &compact->buf_size, + tag->value, compact->item_sep, wctx)); + } +} + +static Writer compact_writer = { + .name = "compact", + .priv_size = sizeof(CompactContext), + + .init = compact_init, + .uninit = compact_uninit, + .print_section_header = compact_print_section_header, + .print_section_footer = compact_print_section_footer, + .print_integer = compact_print_int, + .print_string = compact_print_str, + .show_tags = compact_show_tags, + .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS +}; + /* JSON output */ typedef struct { @@ -630,6 +856,7 @@ static void writer_register_all(void) initialized = 1; writer_register(&default_writer); + writer_register(&compact_writer); writer_register(&json_writer); } @@ -976,7 +1203,7 @@ static const OptionDef options[] = { "use sexagesimal format HOURS:MM:SS.MICROSECONDS for time units" }, { "pretty", 0, {(void*)&opt_pretty}, "prettify the format of displayed values, make it more human readable" }, - { "print_format", OPT_STRING | HAS_ARG, {(void*)&print_format}, "set the output printing format (available formats are: default, json)", "format" }, + { "print_format", OPT_STRING | HAS_ARG, {(void*)&print_format}, "set the output printing format (available formats are: default, compact, json)", "format" }, { "show_format", OPT_BOOL, {(void*)&do_show_format} , "show format/container info" }, { "show_packets", OPT_BOOL, {(void*)&do_show_packets}, "show packets info" }, { "show_streams", OPT_BOOL, {(void*)&do_show_streams}, "show streams info" },