ffprobe: add -o option

This enables printing to a resource specified with -o OUTPUT.

In case the output is not specified, prints to stdout as usual.

Address issue: http://trac.ffmpeg.org/ticket/8024

Signed-off-by: Marton Balint <cus@passwd.hu>
This commit is contained in:
Stefano Sabatini 2021-04-18 23:11:01 +02:00 committed by Marton Balint
parent 7cae3d8b76
commit ff07492cd8
3 changed files with 156 additions and 61 deletions

View File

@ -19,6 +19,7 @@ version 5.1:
- blurdetect filter - blurdetect filter
- tiltshelf audio filter - tiltshelf audio filter
- QOI image format support - QOI image format support
- ffprobe -o option
version 5.0: version 5.0:

View File

@ -28,6 +28,9 @@ If a url is specified in input, ffprobe will try to open and
probe the url content. If the url cannot be opened or recognized as probe the url content. If the url cannot be opened or recognized as
a multimedia file, a positive exit code is returned. a multimedia file, a positive exit code is returned.
If no output is specified as output with @option{o} ffprobe will write
to stdout.
ffprobe may be employed both as a standalone application or in ffprobe may be employed both as a standalone application or in
combination with a textual filter, which may perform more combination with a textual filter, which may perform more
sophisticated processing, e.g. statistical processing or plotting. sophisticated processing, e.g. statistical processing or plotting.
@ -348,6 +351,10 @@ on the specific build.
@item -i @var{input_url} @item -i @var{input_url}
Read @var{input_url}. Read @var{input_url}.
@item -o @var{output_url}
Write output to @var{output_url}. If not specified, the output is sent
to stdout.
@end table @end table
@c man end @c man end

View File

@ -281,6 +281,7 @@ static const OptionDef *options;
static const char *input_filename; static const char *input_filename;
static const char *print_input_filename; static const char *print_input_filename;
static const AVInputFormat *iformat = NULL; static const AVInputFormat *iformat = NULL;
static const char *output_filename = NULL;
static struct AVHashContext *hash; static struct AVHashContext *hash;
@ -476,6 +477,12 @@ typedef struct Writer {
struct WriterContext { struct WriterContext {
const AVClass *class; ///< class of the writer const AVClass *class; ///< class of the writer
const Writer *writer; ///< the Writer of which this is an instance const Writer *writer; ///< the Writer of which this is an instance
AVIOContext *avio; ///< the I/O context used to write
void (* writer_w8)(WriterContext *wctx, int b);
void (* writer_put_str)(WriterContext *wctx, const char *str);
void (* writer_printf)(WriterContext *wctx, const char *fmt, ...);
char *name; ///< name of this writer instance char *name; ///< name of this writer instance
void *priv; ///< private data for use by the filter void *priv; ///< private data for use by the filter
@ -553,6 +560,10 @@ static void writer_close(WriterContext **wctx)
av_opt_free((*wctx)->priv); av_opt_free((*wctx)->priv);
av_freep(&((*wctx)->priv)); av_freep(&((*wctx)->priv));
av_opt_free(*wctx); av_opt_free(*wctx);
if ((*wctx)->avio) {
avio_flush((*wctx)->avio);
avio_close((*wctx)->avio);
}
av_freep(wctx); av_freep(wctx);
} }
@ -564,9 +575,46 @@ static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size)
av_bprintf(bp, "%02X", ubuf[i]); av_bprintf(bp, "%02X", ubuf[i]);
} }
static inline void writer_w8_avio(WriterContext *wctx, int b)
{
avio_w8(wctx->avio, b);
}
static inline void writer_put_str_avio(WriterContext *wctx, const char *str)
{
avio_write(wctx->avio, str, strlen(str));
}
static inline void writer_printf_avio(WriterContext *wctx, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
avio_vprintf(wctx->avio, fmt, ap);
va_end(ap);
}
static inline void writer_w8_printf(WriterContext *wctx, int b)
{
printf("%c", b);
}
static inline void writer_put_str_printf(WriterContext *wctx, const char *str)
{
printf("%s", str);
}
static inline void writer_printf_printf(WriterContext *wctx, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
static int writer_open(WriterContext **wctx, const Writer *writer, const char *args, static int writer_open(WriterContext **wctx, const Writer *writer, const char *args,
const struct section *sections, int nb_sections) const struct section *sections, int nb_sections, const char *output)
{ {
int i, ret = 0; int i, ret = 0;
@ -637,6 +685,21 @@ static int writer_open(WriterContext **wctx, const Writer *writer, const char *a
} }
} }
if (!output_filename) {
(*wctx)->writer_w8 = writer_w8_printf;
(*wctx)->writer_put_str = writer_put_str_printf;
(*wctx)->writer_printf = writer_printf_printf;
} else {
if ((ret = avio_open(&(*wctx)->avio, output, AVIO_FLAG_WRITE)) < 0) {
av_log(*wctx, AV_LOG_ERROR,
"Failed to open output '%s' with error: %s\n", output, av_err2str(ret));
goto fail;
}
(*wctx)->writer_w8 = writer_w8_avio;
(*wctx)->writer_put_str = writer_put_str_avio;
(*wctx)->writer_printf = writer_printf_avio;
}
for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
av_bprint_init(&(*wctx)->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED); av_bprint_init(&(*wctx)->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED);
@ -904,6 +967,10 @@ static void writer_print_integers(WriterContext *wctx, const char *name,
av_bprint_finalize(&bp, NULL); av_bprint_finalize(&bp, NULL);
} }
#define writer_w8(wctx_, b_) (wctx_)->writer_w8(wctx_, b_)
#define writer_put_str(wctx_, str_) (wctx_)->writer_put_str(wctx_, str_)
#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer_printf(wctx_, fmt_, __VA_ARGS__)
#define MAX_REGISTERED_WRITERS_NB 64 #define MAX_REGISTERED_WRITERS_NB 64
static const Writer *registered_writers[MAX_REGISTERED_WRITERS_NB + 1]; static const Writer *registered_writers[MAX_REGISTERED_WRITERS_NB + 1];
@ -998,7 +1065,7 @@ static void default_print_section_header(WriterContext *wctx)
return; return;
if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
printf("[%s]\n", upcase_string(buf, sizeof(buf), section->name)); writer_printf(wctx, "[%s]\n", upcase_string(buf, sizeof(buf), section->name));
} }
static void default_print_section_footer(WriterContext *wctx) static void default_print_section_footer(WriterContext *wctx)
@ -1011,7 +1078,7 @@ static void default_print_section_footer(WriterContext *wctx)
return; return;
if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
printf("[/%s]\n", upcase_string(buf, sizeof(buf), section->name)); writer_printf(wctx, "[/%s]\n", upcase_string(buf, sizeof(buf), section->name));
} }
static void default_print_str(WriterContext *wctx, const char *key, const char *value) static void default_print_str(WriterContext *wctx, const char *key, const char *value)
@ -1019,8 +1086,8 @@ static void default_print_str(WriterContext *wctx, const char *key, const char *
DefaultContext *def = wctx->priv; DefaultContext *def = wctx->priv;
if (!def->nokey) if (!def->nokey)
printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
printf("%s\n", value); writer_printf(wctx, "%s\n", value);
} }
static void default_print_int(WriterContext *wctx, const char *key, long long int value) static void default_print_int(WriterContext *wctx, const char *key, long long int value)
@ -1028,8 +1095,8 @@ static void default_print_int(WriterContext *wctx, const char *key, long long in
DefaultContext *def = wctx->priv; DefaultContext *def = wctx->priv;
if (!def->nokey) if (!def->nokey)
printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
printf("%lld\n", value); writer_printf(wctx, "%lld\n", value);
} }
static const Writer default_writer = { static const Writer default_writer = {
@ -1171,10 +1238,10 @@ static void compact_print_section_header(WriterContext *wctx)
} }
if (parent_section && !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)) && if (parent_section && !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)) &&
wctx->level && wctx->nb_item[wctx->level-1]) wctx->level && wctx->nb_item[wctx->level-1])
printf("%c", compact->item_sep); writer_w8(wctx, compact->item_sep);
if (compact->print_section && if (compact->print_section &&
!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) !(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
printf("%s%c", section->name, compact->item_sep); writer_printf(wctx, "%s%c", section->name, compact->item_sep);
} }
} }
@ -1185,7 +1252,7 @@ static void compact_print_section_footer(WriterContext *wctx)
if (!compact->nested_section[wctx->level] && if (!compact->nested_section[wctx->level] &&
compact->terminate_line[wctx->level] && compact->terminate_line[wctx->level] &&
!(wctx->section[wctx->level]->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) !(wctx->section[wctx->level]->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
printf("\n"); writer_w8(wctx, '\n');
} }
static void compact_print_str(WriterContext *wctx, const char *key, const char *value) static void compact_print_str(WriterContext *wctx, const char *key, const char *value)
@ -1193,11 +1260,11 @@ static void compact_print_str(WriterContext *wctx, const char *key, const char *
CompactContext *compact = wctx->priv; CompactContext *compact = wctx->priv;
AVBPrint buf; AVBPrint buf;
if (wctx->nb_item[wctx->level]) printf("%c", compact->item_sep); if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
if (!compact->nokey) if (!compact->nokey)
printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
printf("%s", compact->escape_str(&buf, value, compact->item_sep, wctx)); writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx));
av_bprint_finalize(&buf, NULL); av_bprint_finalize(&buf, NULL);
} }
@ -1205,10 +1272,10 @@ static void compact_print_int(WriterContext *wctx, const char *key, long long in
{ {
CompactContext *compact = wctx->priv; CompactContext *compact = wctx->priv;
if (wctx->nb_item[wctx->level]) printf("%c", compact->item_sep); if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
if (!compact->nokey) if (!compact->nokey)
printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
printf("%lld", value); writer_printf(wctx, "%lld", value);
} }
static const Writer compact_writer = { static const Writer compact_writer = {
@ -1351,7 +1418,7 @@ static void flat_print_section_header(WriterContext *wctx)
static void flat_print_int(WriterContext *wctx, const char *key, long long int value) static void flat_print_int(WriterContext *wctx, const char *key, long long int value)
{ {
printf("%s%s=%lld\n", wctx->section_pbuf[wctx->level].str, key, value); writer_printf(wctx, "%s%s=%lld\n", wctx->section_pbuf[wctx->level].str, key, value);
} }
static void flat_print_str(WriterContext *wctx, const char *key, const char *value) static void flat_print_str(WriterContext *wctx, const char *key, const char *value)
@ -1359,11 +1426,11 @@ static void flat_print_str(WriterContext *wctx, const char *key, const char *val
FlatContext *flat = wctx->priv; FlatContext *flat = wctx->priv;
AVBPrint buf; AVBPrint buf;
printf("%s", wctx->section_pbuf[wctx->level].str); writer_put_str(wctx, wctx->section_pbuf[wctx->level].str);
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
printf("%s=", flat_escape_key_str(&buf, key, flat->sep)); writer_printf(wctx, "%s=", flat_escape_key_str(&buf, key, flat->sep));
av_bprint_clear(&buf); av_bprint_clear(&buf);
printf("\"%s\"\n", flat_escape_value_str(&buf, value)); writer_printf(wctx, "\"%s\"\n", flat_escape_value_str(&buf, value));
av_bprint_finalize(&buf, NULL); av_bprint_finalize(&buf, NULL);
} }
@ -1433,12 +1500,12 @@ static void ini_print_section_header(WriterContext *wctx)
av_bprint_clear(buf); av_bprint_clear(buf);
if (!parent_section) { if (!parent_section) {
printf("# ffprobe output\n\n"); writer_put_str(wctx, "# ffprobe output\n\n");
return; return;
} }
if (wctx->nb_item[wctx->level-1]) if (wctx->nb_item[wctx->level-1])
printf("\n"); writer_w8(wctx, '\n');
av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str); av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
if (ini->hierarchical || if (ini->hierarchical ||
@ -1453,7 +1520,7 @@ static void ini_print_section_header(WriterContext *wctx)
} }
if (!(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) if (!(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER)))
printf("[%s]\n", buf->str); writer_printf(wctx, "[%s]\n", buf->str);
} }
static void ini_print_str(WriterContext *wctx, const char *key, const char *value) static void ini_print_str(WriterContext *wctx, const char *key, const char *value)
@ -1461,15 +1528,15 @@ static void ini_print_str(WriterContext *wctx, const char *key, const char *valu
AVBPrint buf; AVBPrint buf;
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
printf("%s=", ini_escape_str(&buf, key)); writer_printf(wctx, "%s=", ini_escape_str(&buf, key));
av_bprint_clear(&buf); av_bprint_clear(&buf);
printf("%s\n", ini_escape_str(&buf, value)); writer_printf(wctx, "%s\n", ini_escape_str(&buf, value));
av_bprint_finalize(&buf, NULL); av_bprint_finalize(&buf, NULL);
} }
static void ini_print_int(WriterContext *wctx, const char *key, long long int value) static void ini_print_int(WriterContext *wctx, const char *key, long long int value)
{ {
printf("%s=%lld\n", key, value); writer_printf(wctx, "%s=%lld\n", key, value);
} }
static const Writer ini_writer = { static const Writer ini_writer = {
@ -1532,7 +1599,7 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
return dst->str; return dst->str;
} }
#define JSON_INDENT() printf("%*c", json->indent_level * 4, ' ') #define JSON_INDENT() writer_printf(wctx, "%*c", json->indent_level * 4, ' ')
static void json_print_section_header(WriterContext *wctx) static void json_print_section_header(WriterContext *wctx)
{ {
@ -1543,10 +1610,10 @@ static void json_print_section_header(WriterContext *wctx)
wctx->section[wctx->level-1] : NULL; wctx->section[wctx->level-1] : NULL;
if (wctx->level && wctx->nb_item[wctx->level-1]) if (wctx->level && wctx->nb_item[wctx->level-1])
printf(",\n"); writer_put_str(wctx, ",\n");
if (section->flags & SECTION_FLAG_IS_WRAPPER) { if (section->flags & SECTION_FLAG_IS_WRAPPER) {
printf("{\n"); writer_put_str(wctx, "{\n");
json->indent_level++; json->indent_level++;
} else { } else {
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
@ -1555,17 +1622,17 @@ static void json_print_section_header(WriterContext *wctx)
json->indent_level++; json->indent_level++;
if (section->flags & SECTION_FLAG_IS_ARRAY) { if (section->flags & SECTION_FLAG_IS_ARRAY) {
printf("\"%s\": [\n", buf.str); writer_printf(wctx, "\"%s\": [\n", buf.str);
} else if (parent_section && !(parent_section->flags & SECTION_FLAG_IS_ARRAY)) { } else if (parent_section && !(parent_section->flags & SECTION_FLAG_IS_ARRAY)) {
printf("\"%s\": {%s", buf.str, json->item_start_end); writer_printf(wctx, "\"%s\": {%s", buf.str, json->item_start_end);
} else { } else {
printf("{%s", json->item_start_end); writer_printf(wctx, "{%s", json->item_start_end);
/* this is required so the parser can distinguish between packets and frames */ /* this is required so the parser can distinguish between packets and frames */
if (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES) { if (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES) {
if (!json->compact) if (!json->compact)
JSON_INDENT(); JSON_INDENT();
printf("\"type\": \"%s\"", section->name); writer_printf(wctx, "\"type\": \"%s\"", section->name);
wctx->nb_item[wctx->level]++; wctx->nb_item[wctx->level]++;
} }
} }
@ -1580,18 +1647,18 @@ static void json_print_section_footer(WriterContext *wctx)
if (wctx->level == 0) { if (wctx->level == 0) {
json->indent_level--; json->indent_level--;
printf("\n}\n"); writer_put_str(wctx, "\n}\n");
} else if (section->flags & SECTION_FLAG_IS_ARRAY) { } else if (section->flags & SECTION_FLAG_IS_ARRAY) {
printf("\n"); writer_w8(wctx, '\n');
json->indent_level--; json->indent_level--;
JSON_INDENT(); JSON_INDENT();
printf("]"); writer_w8(wctx, ']');
} else { } else {
printf("%s", json->item_start_end); writer_put_str(wctx, json->item_start_end);
json->indent_level--; json->indent_level--;
if (!json->compact) if (!json->compact)
JSON_INDENT(); JSON_INDENT();
printf("}"); writer_w8(wctx, '}');
} }
} }
@ -1601,9 +1668,9 @@ static inline void json_print_item_str(WriterContext *wctx,
AVBPrint buf; AVBPrint buf;
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
printf("\"%s\":", json_escape_str(&buf, key, wctx)); writer_printf(wctx, "\"%s\":", json_escape_str(&buf, key, wctx));
av_bprint_clear(&buf); av_bprint_clear(&buf);
printf(" \"%s\"", json_escape_str(&buf, value, wctx)); writer_printf(wctx, " \"%s\"", json_escape_str(&buf, value, wctx));
av_bprint_finalize(&buf, NULL); av_bprint_finalize(&buf, NULL);
} }
@ -1614,7 +1681,7 @@ static void json_print_str(WriterContext *wctx, const char *key, const char *val
wctx->section[wctx->level-1] : NULL; wctx->section[wctx->level-1] : NULL;
if (wctx->nb_item[wctx->level] || (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES)) if (wctx->nb_item[wctx->level] || (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES))
printf("%s", json->item_sep); writer_put_str(wctx, json->item_sep);
if (!json->compact) if (!json->compact)
JSON_INDENT(); JSON_INDENT();
json_print_item_str(wctx, key, value); json_print_item_str(wctx, key, value);
@ -1628,12 +1695,12 @@ static void json_print_int(WriterContext *wctx, const char *key, long long int v
AVBPrint buf; AVBPrint buf;
if (wctx->nb_item[wctx->level] || (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES)) if (wctx->nb_item[wctx->level] || (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES))
printf("%s", json->item_sep); writer_put_str(wctx, json->item_sep);
if (!json->compact) if (!json->compact)
JSON_INDENT(); JSON_INDENT();
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
printf("\"%s\": %lld", json_escape_str(&buf, key, wctx), value); writer_printf(wctx, "\"%s\": %lld", json_escape_str(&buf, key, wctx), value);
av_bprint_finalize(&buf, NULL); av_bprint_finalize(&buf, NULL);
} }
@ -1693,7 +1760,7 @@ static av_cold int xml_init(WriterContext *wctx)
return 0; return 0;
} }
#define XML_INDENT() printf("%*c", xml->indent_level * 4, ' ') #define XML_INDENT() writer_printf(wctx, "%*c", xml->indent_level * 4, ' ')
static void xml_print_section_header(WriterContext *wctx) static void xml_print_section_header(WriterContext *wctx)
{ {
@ -1707,8 +1774,8 @@ static void xml_print_section_header(WriterContext *wctx)
"xmlns:ffprobe=\"http://www.ffmpeg.org/schema/ffprobe\" " "xmlns:ffprobe=\"http://www.ffmpeg.org/schema/ffprobe\" "
"xsi:schemaLocation=\"http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd\""; "xsi:schemaLocation=\"http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd\"";
printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); writer_put_str(wctx, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
printf("<%sffprobe%s>\n", writer_printf(wctx, "<%sffprobe%s>\n",
xml->fully_qualified ? "ffprobe:" : "", xml->fully_qualified ? "ffprobe:" : "",
xml->fully_qualified ? qual : ""); xml->fully_qualified ? qual : "");
return; return;
@ -1716,20 +1783,20 @@ static void xml_print_section_header(WriterContext *wctx)
if (xml->within_tag) { if (xml->within_tag) {
xml->within_tag = 0; xml->within_tag = 0;
printf(">\n"); writer_put_str(wctx, ">\n");
} }
if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) { if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) {
xml->indent_level++; xml->indent_level++;
} else { } else {
if (parent_section && (parent_section->flags & SECTION_FLAG_IS_WRAPPER) && if (parent_section && (parent_section->flags & SECTION_FLAG_IS_WRAPPER) &&
wctx->level && wctx->nb_item[wctx->level-1]) wctx->level && wctx->nb_item[wctx->level-1])
printf("\n"); writer_w8(wctx, '\n');
xml->indent_level++; xml->indent_level++;
if (section->flags & SECTION_FLAG_IS_ARRAY) { if (section->flags & SECTION_FLAG_IS_ARRAY) {
XML_INDENT(); printf("<%s>\n", section->name); XML_INDENT(); writer_printf(wctx, "<%s>\n", section->name);
} else { } else {
XML_INDENT(); printf("<%s ", section->name); XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
xml->within_tag = 1; xml->within_tag = 1;
} }
} }
@ -1741,15 +1808,15 @@ static void xml_print_section_footer(WriterContext *wctx)
const struct section *section = wctx->section[wctx->level]; const struct section *section = wctx->section[wctx->level];
if (wctx->level == 0) { if (wctx->level == 0) {
printf("</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : ""); writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
} else if (xml->within_tag) { } else if (xml->within_tag) {
xml->within_tag = 0; xml->within_tag = 0;
printf("/>\n"); writer_put_str(wctx, "/>\n");
xml->indent_level--; xml->indent_level--;
} else if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) { } else if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) {
xml->indent_level--; xml->indent_level--;
} else { } else {
XML_INDENT(); printf("</%s>\n", section->name); XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
xml->indent_level--; xml->indent_level--;
} }
} }
@ -1766,20 +1833,20 @@ static void xml_print_str(WriterContext *wctx, const char *key, const char *valu
XML_INDENT(); XML_INDENT();
av_bprint_escape(&buf, key, NULL, av_bprint_escape(&buf, key, NULL,
AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
printf("<%s key=\"%s\"", writer_printf(wctx, "<%s key=\"%s\"",
section->element_name, buf.str); section->element_name, buf.str);
av_bprint_clear(&buf); av_bprint_clear(&buf);
av_bprint_escape(&buf, value, NULL, av_bprint_escape(&buf, value, NULL,
AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
printf(" value=\"%s\"/>\n", buf.str); writer_printf(wctx, " value=\"%s\"/>\n", buf.str);
} else { } else {
if (wctx->nb_item[wctx->level]) if (wctx->nb_item[wctx->level])
printf(" "); writer_w8(wctx, ' ');
av_bprint_escape(&buf, value, NULL, av_bprint_escape(&buf, value, NULL,
AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
printf("%s=\"%s\"", key, buf.str); writer_printf(wctx, "%s=\"%s\"", key, buf.str);
} }
av_bprint_finalize(&buf, NULL); av_bprint_finalize(&buf, NULL);
@ -1788,8 +1855,8 @@ static void xml_print_str(WriterContext *wctx, const char *key, const char *valu
static void xml_print_int(WriterContext *wctx, const char *key, long long int value) static void xml_print_int(WriterContext *wctx, const char *key, long long int value)
{ {
if (wctx->nb_item[wctx->level]) if (wctx->nb_item[wctx->level])
printf(" "); writer_w8(wctx, ' ');
printf("%s=\"%lld\"", key, value); writer_printf(wctx, "%s=\"%lld\"", key, value);
} }
static Writer xml_writer = { static Writer xml_writer = {
@ -3642,6 +3709,25 @@ static int opt_input_file_i(void *optctx, const char *opt, const char *arg)
return 0; return 0;
} }
static void opt_output_file(void *optctx, const char *arg)
{
if (output_filename) {
av_log(NULL, AV_LOG_ERROR,
"Argument '%s' provided as output filename, but '%s' was already specified.\n",
arg, output_filename);
exit_program(1);
}
if (!strcmp(arg, "-"))
arg = "pipe:";
output_filename = arg;
}
static int opt_output_file_o(void *optctx, const char *opt, const char *arg)
{
opt_output_file(optctx, arg);
return 0;
}
static int opt_print_filename(void *optctx, const char *opt, const char *arg) static int opt_print_filename(void *optctx, const char *opt, const char *arg)
{ {
print_input_filename = arg; print_input_filename = arg;
@ -3904,6 +3990,7 @@ static const OptionDef real_options[] = {
{ "bitexact", OPT_BOOL, {&do_bitexact}, "force bitexact output" }, { "bitexact", OPT_BOOL, {&do_bitexact}, "force bitexact output" },
{ "read_intervals", HAS_ARG, {.func_arg = opt_read_intervals}, "set read intervals", "read_intervals" }, { "read_intervals", HAS_ARG, {.func_arg = opt_read_intervals}, "set read intervals", "read_intervals" },
{ "i", HAS_ARG, {.func_arg = opt_input_file_i}, "read specified file", "input_file"}, { "i", HAS_ARG, {.func_arg = opt_input_file_i}, "read specified file", "input_file"},
{ "o", HAS_ARG, {.func_arg = opt_output_file_o}, "write to specified output", "output_file"},
{ "print_filename", HAS_ARG, {.func_arg = opt_print_filename}, "override the printed input filename", "print_file"}, { "print_filename", HAS_ARG, {.func_arg = opt_print_filename}, "override the printed input filename", "print_file"},
{ "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT, { &find_stream_info }, { "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT, { &find_stream_info },
"read and decode the streams to fill missing information with heuristics" }, "read and decode the streams to fill missing information with heuristics" },
@ -4031,7 +4118,7 @@ int main(int argc, char **argv)
} }
if ((ret = writer_open(&wctx, w, w_args, if ((ret = writer_open(&wctx, w, w_args,
sections, FF_ARRAY_ELEMS(sections))) >= 0) { sections, FF_ARRAY_ELEMS(sections), output_filename)) >= 0) {
if (w == &xml_writer) if (w == &xml_writer)
wctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES; wctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;