[FFmpeg-devel] [PATCH 2/2] ffprobe: add -o option
Stefano Sabatini
stefasab at gmail.com
Mon Apr 19 00:30:58 EEST 2021
This enables printing to a resource specified with -o OUTPUT.
Address issue: http://trac.ffmpeg.org/ticket/8024
---
doc/ffprobe.texi | 7 ++
fftools/ffprobe.c | 174 ++++++++++++++++++++++++++++++----------------
2 files changed, 120 insertions(+), 61 deletions(-)
diff --git a/doc/ffprobe.texi b/doc/ffprobe.texi
index d7fab4ff40..f57b46a8fd 100644
--- a/doc/ffprobe.texi
+++ b/doc/ffprobe.texi
@@ -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
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
combination with a textual filter, which may perform more
sophisticated processing, e.g. statistical processing or plotting.
@@ -342,6 +345,10 @@ on the specific build.
@item -i @var{input_url}
Read @var{input_url}.
+ at item -o @var{output_url}
+Write output to @var{output_url}. If not specified, the output is sent
+to stdout.
+
@end table
@c man end
diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index 38462e1ff3..cdec261f29 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -258,6 +258,7 @@ static const OptionDef *options;
static const char *input_filename;
static const char *print_input_filename;
static AVInputFormat *iformat = NULL;
+static const char *output_filename = NULL;
static struct AVHashContext *hash;
@@ -453,6 +454,7 @@ typedef struct Writer {
struct WriterContext {
const AVClass *class; ///< class of the writer
const Writer *writer; ///< the Writer of which this is an instance
+ AVIOContext *avio; /// the I/O context used to write
char *name; ///< name of this writer instance
void *priv; ///< private data for use by the filter
@@ -530,6 +532,10 @@ static void writer_close(WriterContext **wctx)
av_opt_free((*wctx)->priv);
av_freep(&((*wctx)->priv));
av_opt_free(*wctx);
+ if ((*wctx)->avio) {
+ avio_flush((*wctx)->avio);
+ avio_close((*wctx)->avio);
+ }
av_freep(wctx);
}
@@ -543,7 +549,7 @@ static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size)
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 *url)
{
int i, ret = 0;
@@ -614,6 +620,9 @@ static int writer_open(WriterContext **wctx, const Writer *writer, const char *a
}
}
+ if ((ret = avio_open(&(*wctx)->avio, url, AVIO_FLAG_WRITE)) < 0)
+ goto fail;
+
for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
av_bprint_init(&(*wctx)->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED);
@@ -879,6 +888,25 @@ static void writer_print_integers(WriterContext *wctx, const char *name,
av_bprint_finalize(&bp, NULL);
}
+static inline void writer_w8(WriterContext *wctx, int b)
+{
+ avio_w8(wctx->avio, b);
+}
+
+static inline void writer_put_str(WriterContext *wctx, const char *str)
+{
+ avio_put_str(wctx->avio, str);
+}
+
+static inline void writer_printf(WriterContext *wctx, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ avio_vprintf(wctx->avio, fmt, ap);
+ va_end(ap);
+}
+
#define MAX_REGISTERED_WRITERS_NB 64
static const Writer *registered_writers[MAX_REGISTERED_WRITERS_NB + 1];
@@ -973,7 +1001,7 @@ static void default_print_section_header(WriterContext *wctx)
return;
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)
@@ -986,7 +1014,7 @@ static void default_print_section_footer(WriterContext *wctx)
return;
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)
@@ -994,8 +1022,8 @@ static void default_print_str(WriterContext *wctx, const char *key, const char *
DefaultContext *def = wctx->priv;
if (!def->nokey)
- printf("%s%s=", wctx->section_pbuf[wctx->level].str, key);
- printf("%s\n", value);
+ writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+ writer_printf(wctx, "%s\n", value);
}
static void default_print_int(WriterContext *wctx, const char *key, long long int value)
@@ -1003,8 +1031,8 @@ static void default_print_int(WriterContext *wctx, const char *key, long long in
DefaultContext *def = wctx->priv;
if (!def->nokey)
- printf("%s%s=", wctx->section_pbuf[wctx->level].str, key);
- printf("%lld\n", value);
+ writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+ writer_printf(wctx, "%lld\n", value);
}
static const Writer default_writer = {
@@ -1143,11 +1171,11 @@ static void compact_print_section_header(WriterContext *wctx)
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");
+ writer_w8(wctx, '\n');
}
if (compact->print_section &&
!(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);
}
}
@@ -1158,7 +1186,7 @@ static void compact_print_section_footer(WriterContext *wctx)
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");
+ writer_w8(wctx, '\n');
}
static void compact_print_str(WriterContext *wctx, const char *key, const char *value)
@@ -1166,11 +1194,11 @@ static void compact_print_str(WriterContext *wctx, const char *key, const char *
CompactContext *compact = wctx->priv;
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)
- 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);
- 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);
}
@@ -1178,10 +1206,10 @@ static void compact_print_int(WriterContext *wctx, const char *key, long long in
{
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)
- printf("%s%s=", wctx->section_pbuf[wctx->level].str, key);
- printf("%lld", value);
+ writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+ writer_printf(wctx, "%lld", value);
}
static const Writer compact_writer = {
@@ -1324,7 +1352,7 @@ static void flat_print_section_header(WriterContext *wctx)
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)
@@ -1332,11 +1360,11 @@ static void flat_print_str(WriterContext *wctx, const char *key, const char *val
FlatContext *flat = wctx->priv;
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);
- 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);
- 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);
}
@@ -1406,12 +1434,12 @@ static void ini_print_section_header(WriterContext *wctx)
av_bprint_clear(buf);
if (!parent_section) {
- printf("# ffprobe output\n\n");
+ writer_put_str(wctx, "# ffprobe output\n\n");
return;
}
if (wctx->nb_item[wctx->level-1])
- printf("\n");
+ writer_w8(wctx, '\n');
av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
if (ini->hierarchical ||
@@ -1426,7 +1454,7 @@ static void ini_print_section_header(WriterContext *wctx)
}
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)
@@ -1434,15 +1462,15 @@ static void ini_print_str(WriterContext *wctx, const char *key, const char *valu
AVBPrint buf;
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);
- printf("%s\n", ini_escape_str(&buf, value));
+ writer_printf(wctx, "%s\n", ini_escape_str(&buf, value));
av_bprint_finalize(&buf, NULL);
}
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 = {
@@ -1505,7 +1533,7 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
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)
{
@@ -1516,10 +1544,10 @@ static void json_print_section_header(WriterContext *wctx)
wctx->section[wctx->level-1] : NULL;
if (wctx->level && wctx->nb_item[wctx->level-1])
- printf(",\n");
+ writer_put_str(wctx, ",\n");
if (section->flags & SECTION_FLAG_IS_WRAPPER) {
- printf("{\n");
+ writer_put_str(wctx, "{\n");
json->indent_level++;
} else {
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
@@ -1528,17 +1556,17 @@ static void json_print_section_header(WriterContext *wctx)
json->indent_level++;
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)) {
- printf("\"%s\": {%s", buf.str, json->item_start_end);
+ writer_printf(wctx, "\"%s\": {%s", buf.str, json->item_start_end);
} 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 */
if (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES) {
if (!json->compact)
JSON_INDENT();
- printf("\"type\": \"%s\"", section->name);
+ writer_printf(wctx, "\"type\": \"%s\"", section->name);
}
}
av_bprint_finalize(&buf, NULL);
@@ -1552,18 +1580,18 @@ static void json_print_section_footer(WriterContext *wctx)
if (wctx->level == 0) {
json->indent_level--;
- printf("\n}\n");
+ writer_put_str(wctx, "\n}\n");
} else if (section->flags & SECTION_FLAG_IS_ARRAY) {
- printf("\n");
+ writer_w8(wctx, '\n');
json->indent_level--;
JSON_INDENT();
- printf("]");
+ writer_w8(wctx, ']');
} else {
- printf("%s", json->item_start_end);
+ writer_put_str(wctx, json->item_start_end);
json->indent_level--;
if (!json->compact)
JSON_INDENT();
- printf("}");
+ writer_w8(wctx, '}');
}
}
@@ -1573,9 +1601,9 @@ static inline void json_print_item_str(WriterContext *wctx,
AVBPrint buf;
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);
- printf(" \"%s\"", json_escape_str(&buf, value, wctx));
+ writer_printf(wctx, " \"%s\"", json_escape_str(&buf, value, wctx));
av_bprint_finalize(&buf, NULL);
}
@@ -1586,7 +1614,7 @@ static void json_print_str(WriterContext *wctx, const char *key, const char *val
wctx->section[wctx->level-1] : NULL;
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)
JSON_INDENT();
json_print_item_str(wctx, key, value);
@@ -1600,12 +1628,12 @@ static void json_print_int(WriterContext *wctx, const char *key, long long int v
AVBPrint buf;
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)
JSON_INDENT();
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);
}
@@ -1672,7 +1700,7 @@ static av_cold int xml_init(WriterContext *wctx)
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)
{
@@ -1686,8 +1714,8 @@ static void xml_print_section_header(WriterContext *wctx)
"xmlns:ffprobe='http://www.ffmpeg.org/schema/ffprobe' "
"xsi:schemaLocation='http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd'";
- printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- printf("<%sffprobe%s>\n",
+ writer_put_str(wctx, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ writer_printf(wctx, "<%sffprobe%s>\n",
xml->fully_qualified ? "ffprobe:" : "",
xml->fully_qualified ? qual : "");
return;
@@ -1695,20 +1723,20 @@ static void xml_print_section_header(WriterContext *wctx)
if (xml->within_tag) {
xml->within_tag = 0;
- printf(">\n");
+ writer_put_str(wctx, ">\n");
}
if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) {
xml->indent_level++;
} else {
if (parent_section && (parent_section->flags & SECTION_FLAG_IS_WRAPPER) &&
wctx->level && wctx->nb_item[wctx->level-1])
- printf("\n");
+ writer_w8(wctx, '\n');
xml->indent_level++;
if (section->flags & SECTION_FLAG_IS_ARRAY) {
- XML_INDENT(); printf("<%s>\n", section->name);
+ XML_INDENT(); writer_printf(wctx, "<%s>\n", section->name);
} else {
- XML_INDENT(); printf("<%s ", section->name);
+ XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
xml->within_tag = 1;
}
}
@@ -1720,15 +1748,15 @@ static void xml_print_section_footer(WriterContext *wctx)
const struct section *section = wctx->section[wctx->level];
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) {
xml->within_tag = 0;
- printf("/>\n");
+ writer_put_str(wctx, "/>\n");
xml->indent_level--;
} else if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) {
xml->indent_level--;
} else {
- XML_INDENT(); printf("</%s>\n", section->name);
+ XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
xml->indent_level--;
}
}
@@ -1745,20 +1773,20 @@ static void xml_print_str(WriterContext *wctx, const char *key, const char *valu
XML_INDENT();
av_bprint_escape(&buf, key, NULL,
AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
- printf("<%s key=\"%s\"",
- section->element_name, buf.str);
+ writer_printf(wctx, "<%s key=\"%s\"",
+ section->element_name, buf.str);
av_bprint_clear(&buf);
av_bprint_escape(&buf, value, NULL,
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 {
if (wctx->nb_item[wctx->level])
- printf(" ");
+ writer_w8(wctx, ' ');
av_bprint_escape(&buf, value, NULL,
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);
@@ -1767,8 +1795,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)
{
if (wctx->nb_item[wctx->level])
- printf(" ");
- printf("%s=\"%lld\"", key, value);
+ writer_w8(wctx, ' ');
+ writer_printf(wctx, "%s=\"%lld\"", key, value);
}
static Writer xml_writer = {
@@ -3380,6 +3408,25 @@ static int opt_input_file_i(void *optctx, const char *opt, const char *arg)
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)
{
print_input_filename = arg;
@@ -3644,6 +3691,7 @@ static const OptionDef real_options[] = {
{ "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"},
+ { "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"},
{ "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT, { &find_stream_info },
"read and decode the streams to fill missing information with heuristics" },
@@ -3771,8 +3819,12 @@ int main(int argc, char **argv)
goto end;
}
+ if (!output_filename) {
+ output_filename = "pipe:";
+ }
+
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)
wctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;
--
2.25.1
More information about the ffmpeg-devel
mailing list