mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2025-01-08 13:22:53 +02:00
ffprobe: add compact writer
This commit is contained in:
parent
0491a2a07a
commit
1c43713e57
@ -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:
|
||||
|
@ -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.
|
||||
|
||||
|
229
ffprobe.c
229
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" },
|
||||
|
Loading…
Reference in New Issue
Block a user