[FFmpeg-devel] [PATCH v1] ffmpeg: add optional JSON output of inputs, outputs, mapping, and progress

Ingo Oppermann ingo at datarhei.com
Thu Jun 9 15:47:00 EEST 2022


In order to make a running ffmpeg process easier to monitor and parse by
programs that call the ffmpeg binary and process its output, this patch adds
the command line option -jsonstats. This option is a modifier for the
(no)stats option which provides a more verbose output in JSON format than the
default output. It enables the additional output of the input streams,
their mapping to the outputs (including the filter graphs), and the output
streams as JSON. Each output is on a single line and is prefixed with
"json.inputs:", "json.mapping:", and "json.outputs:" respectively, followed by
the JSON data. The -jsonstats option is disabled by default.

The inputs and outputs are arrays and for each input and output stream, the
information in the JSON is similar to the default dump of the inputs and
outputs.

The stream mapping includes an array of the filter graphs and a mapping
representation similar to the output from to graph2dot.c program.

The current progress report is replaced by a JSON representation which is
prefixed with "json.progress:" followed by JSON data, and each report will be
on a new line. The progress data contains values similar to the default data
for each input and output stream and a summary.

Together with the -progress option, the described JSON data instead of the
default data will be written to the provided target.

Signed-off-by: Ingo Oppermann <ingo at datarhei.com>
---
 doc/ffmpeg.texi      |  10 ++
 fftools/ffmpeg.c     | 198 +++++++++++++++++++++++++++-
 fftools/ffmpeg.h     |   1 +
 fftools/ffmpeg_mux.c | 307 +++++++++++++++++++++++++++++++++++++++++++
 fftools/ffmpeg_opt.c | 115 ++++++++++++++++
 5 files changed, 629 insertions(+), 2 deletions(-)

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 0d7e1a479d..16fcd9970a 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -784,6 +784,13 @@ disable it you need to specify @code{-nostats}.
 @item -stats_period @var{time} (@emph{global})
 Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
 
+ at item -jsonstats (@emph{global})
+Print inputs, outputs, stream mapping, and encoding progress/statistics. It is off by
+default. It modifies the output of @code{-stats} to be JSON. The inputs, outputs,
+stream mapping, and progress information are written on one line and are prefixed
+with @var{json.inputs:}, @var{json.outputs:}, @var{json.mapping:}, and @var{json.progress:}
+respectively followed by the JSON data.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
@@ -792,6 +799,9 @@ the encoding process. It is made of "@var{key}=@var{value}" lines. @var{key}
 consists of only alphanumeric characters. The last key of a sequence of
 progress information is always "progress".
 
+If @code{-jsonstats} is enabled, the progress information is written as JSON with
+the prefixes and data 
+
 The update period is set using @code{-stats_period}.
 
 @anchor{stdin option}
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index 5ed287c522..eea1491ed1 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -1505,7 +1505,7 @@ static void print_final_stats(int64_t total_size)
     }
 }
 
-static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time)
+static void print_default_report(int is_last_report, int64_t timer_start, int64_t cur_time)
 {
     AVBPrint buf, buf_script;
     OutputStream *ost;
@@ -1695,7 +1695,7 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
     }
     av_bprint_finalize(&buf, NULL);
 
-    if (progress_avio) {
+    if (progress_avio && !print_jsonstats) {
         av_bprintf(&buf_script, "progress=%s\n",
                    is_last_report ? "end" : "continue");
         avio_write(progress_avio, buf_script.str,
@@ -1715,6 +1715,200 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
         print_final_stats(total_size);
 }
 
+/**
+ * Print progress report in JSON format
+ *
+ * @param is_last_report Whether this is the last report
+ * @param timer_start Time when the processing started
+ * @param cur_time Current processing time of the stream
+ */
+static void print_json_report(int is_last_report, int64_t timer_start, int64_t cur_time)
+{
+    AVBPrint buf;
+    InputStream *ist;
+    OutputStream *ost;
+    uint64_t stream_size, total_packets = 0, total_size = 0;
+    AVCodecContext *enc;
+    int i, j;
+    double speed;
+    int64_t pts = INT64_MIN + 1;
+    static int first_report = 1;
+    static int64_t last_time = -1;
+    int hours, mins, secs, us;
+    const char *hours_sign;
+    float t, q;
+
+    if (!is_last_report) {
+        if (last_time == -1) {
+            last_time = cur_time;
+        }
+        if (((cur_time - last_time) < stats_period && !first_report) ||
+            (first_report && nb_output_dumped < nb_output_files))
+            return;
+        last_time = cur_time;
+    }
+
+    t = (cur_time - timer_start) / 1000000.0;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    av_bprintf(&buf, "json.progress:{");
+    av_bprintf(&buf, "\"inputs\":[");
+    for (i = 0; i < nb_input_files; i++) {
+        InputFile *f = input_files[i];
+
+        for (j = 0; j < f->nb_streams; j++) {
+            ist = input_streams[f->ist_index + j];
+
+            av_bprintf(&buf, "{");
+            av_bprintf(&buf, "\"index\":%d,\"stream\":%d,", i, j);
+
+            av_bprintf(&buf,
+                "\"frame\":%" PRIu64 ",\"packet\":%" PRIu64 ",",
+                !ist->frames_decoded ? ist->nb_packets : ist->frames_decoded,
+                ist->nb_packets);
+
+            av_bprintf(&buf, "\"size_bytes\":%" PRIu64, ist->data_size);
+
+            if (i == (nb_input_files - 1) && j == (f->nb_streams - 1)) {
+                av_bprintf(&buf, "}");
+            } else {
+                av_bprintf(&buf, "},");
+            }
+        }
+    }
+
+    av_bprintf(&buf, "],");
+
+    av_bprintf(&buf, "\"outputs\":[");
+    for (i = 0; i < nb_output_streams; i++) {
+        q = -1;
+        ost = output_streams[i];
+        enc = ost->enc_ctx;
+        if (!ost->stream_copy) {
+            q = ost->quality / (float)FF_QP2LAMBDA;
+        }
+
+        av_bprintf(&buf, "{");
+        av_bprintf(
+            &buf, "\"index\":%d,\"stream\":%d,", ost->file_index, ost->index);
+
+        av_bprintf(&buf,
+            "\"frame\":%" PRIu64 ",\"packet\":%" PRIu64 ",",
+            !ost->frames_encoded ? ost->packets_written : ost->frames_encoded,
+            ost->packets_written);
+
+        if (enc->codec_type == AVMEDIA_TYPE_VIDEO) {
+            av_bprintf(&buf, "\"q\":%.1f,", q);
+        }
+
+        /* compute min output value */
+        if (av_stream_get_end_pts(ost->st) != AV_NOPTS_VALUE) {
+            pts = FFMAX(pts,
+                av_rescale_q(av_stream_get_end_pts(ost->st),
+                    ost->st->time_base,
+                    AV_TIME_BASE_Q));
+            if (copy_ts) {
+                if (copy_ts_first_pts == AV_NOPTS_VALUE && pts > 1)
+                    copy_ts_first_pts = pts;
+                if (copy_ts_first_pts != AV_NOPTS_VALUE)
+                    pts -= copy_ts_first_pts;
+            }
+        }
+
+        total_packets += ost->packets_written;
+
+        if (is_last_report) {
+            nb_frames_drop += ost->last_dropped;
+        }
+
+        stream_size = ost->data_size + ost->enc_ctx->extradata_size;
+        total_size += stream_size;
+
+        av_bprintf(&buf, "\"size_bytes\":%" PRIu64, stream_size);
+
+        if (i == (nb_output_streams - 1)) {
+            av_bprintf(&buf, "}");
+        } else {
+            av_bprintf(&buf, "},");
+        }
+    }
+
+    av_bprintf(&buf, "],");
+
+    av_bprintf(&buf,
+        "\"packet\":%" PRIu64 ",\"size_bytes\":%" PRIu64 ",",
+        total_packets,
+        total_size);
+
+    secs = FFABS(pts) / AV_TIME_BASE;
+    us = FFABS(pts) % AV_TIME_BASE;
+    mins = secs / 60;
+    secs %= 60;
+    hours = mins / 60;
+    mins %= 60;
+    hours_sign = (pts < 0) ? "-" : "";
+
+    if (pts != AV_NOPTS_VALUE) {
+        av_bprintf(&buf,
+            "\"time\":\"%s%dh%dm%d.%ds\",",
+            hours_sign,
+            hours,
+            mins,
+            secs,
+            (100 * us) / AV_TIME_BASE);
+    }
+
+    speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1;
+    av_bprintf(&buf, "\"speed\":%.3g,", speed);
+
+    av_bprintf(&buf, "\"dup\":%d,\"drop\":%d", nb_frames_dup, nb_frames_drop);
+    av_bprintf(&buf, "}\n");
+
+    if (print_stats || is_last_report) {
+        if (AV_LOG_INFO > av_log_get_level()) {
+            fprintf(stderr, "%s", buf.str);
+        } else {
+            av_log(NULL, AV_LOG_INFO, "%s", buf.str);
+        }
+
+        fflush(stderr);
+    }
+
+    if (progress_avio) {
+        avio_write(progress_avio, buf.str, FFMIN(buf.len, buf.size - 1));
+        avio_flush(progress_avio);
+        if (is_last_report) {
+            av_bprint_clear(&buf);
+            av_bprintf(&buf, "ffmpeg.progress:NULL\n");
+            avio_write(progress_avio, buf.str, FFMIN(buf.len, buf.size - 1));
+            int ret;
+            if ((ret = avio_closep(&progress_avio)) < 0) {
+                av_log(NULL,
+                    AV_LOG_ERROR,
+                    "Error closing progress log, loss of information possible: %s\n",
+                    av_err2str(ret));
+            }
+        }
+    }
+
+    first_report = 0;
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time)
+{
+    if (!print_stats && !is_last_report && !progress_avio)
+        return;
+
+    if (print_jsonstats == 1) {
+        print_json_report(is_last_report, timer_start, cur_time);
+    } else {
+        print_default_report(is_last_report, timer_start, cur_time);
+    }
+}
+
 static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParameters *par)
 {
     int ret;
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7326193caf..14968869d0 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -631,6 +631,7 @@ extern int debug_ts;
 extern int exit_on_error;
 extern int abort_on_flags;
 extern int print_stats;
+extern int print_jsonstats;
 extern int64_t stats_period;
 extern int qp_hist;
 extern int stdin_interaction;
diff --git a/fftools/ffmpeg_mux.c b/fftools/ffmpeg_mux.c
index 794d580635..c7771faad7 100644
--- a/fftools/ffmpeg_mux.c
+++ b/fftools/ffmpeg_mux.c
@@ -21,14 +21,20 @@
 
 #include "ffmpeg.h"
 
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
 #include "libavutil/fifo.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/log.h"
 #include "libavutil/mem.h"
+#include "libavutil/pixdesc.h"
 #include "libavutil/timestamp.h"
 
+#include "libavcodec/avcodec.h"
 #include "libavcodec/packet.h"
 
+#include "libavfilter/avfilter.h"
+
 #include "libavformat/avformat.h"
 #include "libavformat/avio.h"
 
@@ -226,6 +232,305 @@ fail:
     return ret;
 }
 
+/**
+ * Write a graph as JSON to an initialized buffer
+ *
+ * @param buf Pointer to an initialized AVBPrint buffer
+ * @param graph Pointer to a AVFilterGraph
+ */
+static void print_json_graph(AVBPrint *buf, AVFilterGraph *graph)
+{
+    int i, j;
+
+    if (!graph) {
+        av_bprintf(buf, "null\n");
+        return;
+    }
+
+    av_bprintf(buf, "[");
+
+    for (i = 0; i < graph->nb_filters; i++) {
+        const AVFilterContext *filter_ctx = graph->filters[i];
+
+        for (j = 0; j < filter_ctx->nb_outputs; j++) {
+            AVFilterLink *link = filter_ctx->outputs[j];
+            if (link) {
+                const AVFilterContext *dst_filter_ctx = link->dst;
+
+                av_bprintf(buf,
+                    "{\"src_name\":\"%s\",\"src_filter\":\"%s\",\"dst_name\":\"%s\",\"dst_filter\":\"%s\",",
+                    filter_ctx->name,
+                    filter_ctx->filter->name,
+                    dst_filter_ctx->name,
+                    dst_filter_ctx->filter->name);
+                av_bprintf(buf,
+                    "\"inpad\":\"%s\",\"outpad\":\"%s\",",
+                    avfilter_pad_get_name(link->srcpad, 0),
+                    avfilter_pad_get_name(link->dstpad, 0));
+                av_bprintf(buf,
+                    "\"timebase\":\"%d/%d\",",
+                    link->time_base.num,
+                    link->time_base.den);
+
+                if (link->type == AVMEDIA_TYPE_VIDEO) {
+                    const AVPixFmtDescriptor *desc =
+                        av_pix_fmt_desc_get(link->format);
+                    av_bprintf(buf,
+                        "\"type\":\"video\",\"format\":\"%s\",\"width\":%d,\"height\":%d",
+                        desc->name,
+                        link->w,
+                        link->h);
+                } else if (link->type == AVMEDIA_TYPE_AUDIO) {
+                    char layout[255];
+                    av_channel_layout_describe(
+                        &link->ch_layout, layout, sizeof(layout));
+                    av_bprintf(buf,
+                        "\"type\":\"audio\",\"format\":\"%s\",\"sampling_hz\":%d,\"layout\":\"%s\"",
+                        av_get_sample_fmt_name(link->format),
+                        link->sample_rate,
+                        layout);
+                }
+
+                if (i == (graph->nb_filters - 1)) {
+                    av_bprintf(buf, "}");
+                } else {
+                    av_bprintf(buf, "},");
+                }
+            }
+        }
+    }
+
+    av_bprintf(buf, "]");
+}
+
+/**
+ * Print all outputs in JSON format
+ */
+static void print_json_outputs()
+{
+    static int ost_all_initialized = 0;
+    int i, j, k;
+    int nb_initialized = 0;
+    AVBPrint buf;
+
+    if (!print_jsonstats) {
+        return;
+    }
+
+    if (ost_all_initialized == 1) {
+        return;
+    }
+
+    // count how many outputs are initialized
+    for (i = 0; i < nb_output_streams; i++) {
+        OutputStream *ost = output_streams[i];
+        if (ost->initialized) {
+            nb_initialized++;
+        }
+    }
+
+    // only when all outputs are initialized, dump the outputs
+    if (nb_initialized == nb_output_streams) {
+        ost_all_initialized = 1;
+    }
+
+    if (ost_all_initialized != 1) {
+        return;
+    }
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    av_bprintf(&buf, "json.outputs:[");
+    for (i = 0; i < nb_output_streams; i++) {
+        OutputStream *ost = output_streams[i];
+        OutputFile *f = output_files[ost->file_index];
+        AVFormatContext *ctx = f->ctx;
+        AVStream *st = ost->st;
+        AVDictionaryEntry *lang =
+            av_dict_get(st->metadata, "language", NULL, 0);
+        AVCodecContext *enc = ost->enc_ctx;
+        char *url = NULL;
+
+        if (av_escape(&url,
+                ctx->url,
+                "\\\"",
+                AV_ESCAPE_MODE_BACKSLASH,
+                AV_UTF8_FLAG_ACCEPT_ALL) < 0) {
+            url = av_strdup("-");
+        }
+
+        av_bprintf(&buf, "{");
+        av_bprintf(&buf,
+            "\"url\":\"%s\",\"format\":\"%s\",\"index\":%d,\"stream\":%d,",
+            url,
+            ctx->oformat->name,
+            ost->file_index,
+            ost->index);
+        av_bprintf(&buf,
+            "\"type\":\"%s\",\"codec\":\"%s\",\"coder\":\"%s\",\"bitrate_kbps\":%" PRId64
+            ",",
+            av_get_media_type_string(enc->codec_type),
+            avcodec_get_name(enc->codec_id),
+            ost->stream_copy ? "copy"
+                             : (enc->codec ? enc->codec->name : "unknown"),
+            enc->bit_rate / 1000);
+        av_bprintf(&buf,
+            "\"duration_sec\":%f,\"language\":\"%s\"",
+            0.0,
+            lang ? lang->value : "und");
+
+        av_free(url);
+
+        if (enc->codec_type == AVMEDIA_TYPE_VIDEO) {
+            float fps = 0;
+            if (st->avg_frame_rate.den && st->avg_frame_rate.num) {
+                fps = av_q2d(st->avg_frame_rate);
+            }
+
+            av_bprintf(&buf,
+                ",\"fps\":%f,\"pix_fmt\":\"%s\",\"width\":%d,\"height\":%d",
+                fps,
+                st->codecpar->format == AV_PIX_FMT_NONE
+                    ? "none"
+                    : av_get_pix_fmt_name(st->codecpar->format),
+                st->codecpar->width,
+                st->codecpar->height);
+        } else if (enc->codec_type == AVMEDIA_TYPE_AUDIO) {
+            char layout[128];
+            av_channel_layout_describe(&enc->ch_layout, layout, sizeof(layout));
+
+            av_bprintf(&buf,
+                ",\"sampling_hz\":%d,\"layout\":\"%s\",\"channels\":%d",
+                enc->sample_rate,
+                layout,
+                enc->ch_layout.nb_channels);
+        }
+
+        if (i == (nb_output_streams - 1)) {
+            av_bprintf(&buf, "}");
+        } else {
+            av_bprintf(&buf, "},");
+        }
+    }
+
+    av_bprintf(&buf, "]\n");
+
+    av_log(NULL, AV_LOG_INFO, "%s", buf.str);
+
+    if (progress_avio) {
+        avio_write(progress_avio, buf.str, FFMIN(buf.len, buf.size - 1));
+        avio_flush(progress_avio);
+    }
+
+    av_bprint_clear(&buf);
+
+    av_bprintf(&buf, "json.mapping:{");
+    av_bprintf(&buf, "\"graphs\":[");
+
+    for (i = 0; i < nb_filtergraphs; i++) {
+        av_bprintf(&buf, "{\"index\":%d,\"graph\":", i);
+        print_json_graph(&buf, filtergraphs[i]->graph);
+
+        if (i == (nb_filtergraphs - 1)) {
+            av_bprintf(&buf, "}");
+        } else {
+            av_bprintf(&buf, "},");
+        }
+    }
+
+    av_bprintf(&buf, "],");
+
+    // The following is inspired by tools/graph2dot.c
+
+    av_bprintf(&buf, "\"mapping\":[");
+
+    for (i = 0; i < nb_input_streams; i++) {
+        InputStream *ist = input_streams[i];
+
+        for (j = 0; j < ist->nb_filters; j++) {
+            if (ist->filters[j]->graph) {
+                char *name = NULL;
+                for (k = 0; k < ist->filters[j]->graph->nb_inputs; k++) {
+                    if (ist->filters[j]->graph->inputs[k]->ist == ist) {
+                        name = ist->filters[j]->graph->inputs[k]->filter->name;
+                        break;
+                    }
+                }
+
+                av_bprintf(&buf,
+                    "{\"input\":{\"index\":%d,\"stream\":%d},\"graph\":{\"index\":%d,\"name\":\"%s\"},\"output\":null},",
+                    ist->file_index,
+                    ist->st->index,
+                    ist->filters[j]->graph->index,
+                    name);
+            }
+        }
+    }
+
+    for (i = 0; i < nb_output_streams; i++) {
+        OutputStream *ost = output_streams[i];
+
+        if (ost->attachment_filename) {
+            av_bprintf(&buf,
+                "{\"input\":null,\"file\":\"%s\",\"output\":{\"index\":%d,\"stream\":%d}},",
+                ost->attachment_filename,
+                ost->file_index,
+                ost->index);
+            goto next_output;
+        }
+
+        if (ost->filter && ost->filter->graph) {
+            char *name = NULL;
+            for (j = 0; j < ost->filter->graph->nb_outputs; j++) {
+                if (ost->filter->graph->outputs[j]->ost == ost) {
+                    name = ost->filter->graph->outputs[j]->filter->name;
+                    break;
+                }
+            }
+            av_bprintf(&buf,
+                "{\"input\":null,\"graph\":{\"index\":%d,\"name\":\"%s\"},\"output\":{\"index\":%d,\"stream\":%d}}",
+                ost->filter->graph->index,
+                name,
+                ost->file_index,
+                ost->index);
+            goto next_output;
+        }
+
+        av_bprintf(&buf,
+            "{\"input\":{\"index\":%d,\"stream\":%d},\"output\":{\"index\":%d,\"stream\":%d}",
+            input_streams[ost->source_index]->file_index,
+            input_streams[ost->source_index]->st->index,
+            ost->file_index,
+            ost->index);
+        av_bprintf(&buf, ",\"copy\":%s", ost->stream_copy ? "true" : "false");
+
+        if (ost->sync_ist != input_streams[ost->source_index]) {
+            av_bprintf(&buf,
+                ",\"sync\":{\"index\":%d,\"stream\":%d}",
+                ost->sync_ist->file_index,
+                ost->sync_ist->st->index);
+        }
+
+        av_bprintf(&buf, "}");
+
+    next_output:
+        if (i != (nb_output_streams - 1)) {
+            av_bprintf(&buf, ",");
+        }
+    }
+
+    av_bprintf(&buf, "]}\n");
+
+    av_log(NULL, AV_LOG_INFO, "%s", buf.str);
+
+    if (progress_avio) {
+        avio_write(progress_avio, buf.str, FFMIN(buf.len, buf.size - 1));
+        avio_flush(progress_avio);
+    }
+
+    av_bprint_finalize(&buf, NULL);
+}
+
 /* open the muxer when all the streams are initialized */
 int of_check_init(OutputFile *of)
 {
@@ -251,6 +556,8 @@ int of_check_init(OutputFile *of)
     av_dump_format(of->ctx, of->index, of->ctx->url, 1);
     nb_output_dumped++;
 
+    print_json_outputs();
+
     if (sdp_filename || want_sdp) {
         ret = print_sdp();
         if (ret < 0) {
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 2c1b3bd0dd..20e991eca5 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -51,6 +51,7 @@
 #include "libavutil/parseutils.h"
 #include "libavutil/pixdesc.h"
 #include "libavutil/pixfmt.h"
+#include "libavutil/bprint.h"
 
 #define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass"
 
@@ -169,6 +170,7 @@ int debug_ts          = 0;
 int exit_on_error     = 0;
 int abort_on_flags    = 0;
 int print_stats       = -1;
+int print_jsonstats   = 0;
 int qp_hist           = 0;
 int stdin_interaction = 1;
 float max_error_rate  = 2.0/3;
@@ -3434,6 +3436,115 @@ static int open_files(OptionGroupList *l, const char *inout,
     return 0;
 }
 
+/**
+ * Print all inputs in JSON format
+ */
+static void print_json_inputs()
+{
+    if (!print_jsonstats) {
+        return;
+    }
+
+    AVBPrint buf;
+    int i, j;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    av_bprintf(&buf, "json.inputs:[");
+    for (i = 0; i < nb_input_files; i++) {
+        InputFile *f = input_files[i];
+        AVFormatContext *ctx = f->ctx;
+
+        float duration = 0;
+        if (ctx->duration != AV_NOPTS_VALUE) {
+            duration = (float)(ctx->duration +
+                           (ctx->duration <= INT64_MAX - 5000 ? 5000 : 0)) /
+                (float)AV_TIME_BASE;
+        }
+
+        for (j = 0; j < f->nb_streams; j++) {
+            InputStream *ist = input_streams[f->ist_index + j];
+            AVCodecContext *dec = ist->dec_ctx;
+            AVStream *st = ist->st;
+            AVDictionaryEntry *lang =
+                av_dict_get(st->metadata, "language", NULL, 0);
+            char *url = NULL;
+
+            if (av_escape(&url,
+                    ctx->url,
+                    "\\\"",
+                    AV_ESCAPE_MODE_BACKSLASH,
+                    AV_UTF8_FLAG_ACCEPT_ALL) < 0) {
+                url = av_strdup("-");
+            }
+
+            av_bprintf(&buf, "{");
+            av_bprintf(&buf,
+                "\"url\":\"%s\",\"format\":\"%s\",\"index\":%d,\"stream\":%d,",
+                url,
+                ctx->iformat->name,
+                i,
+                j);
+            av_bprintf(&buf,
+                "\"type\":\"%s\",\"codec\":\"%s\",\"coder\":\"%s\",\"bitrate_kbps\":%" PRId64
+                ",",
+                av_get_media_type_string(dec->codec_type),
+                avcodec_get_name(dec->codec_id),
+                dec->codec ? dec->codec->name : "unknown",
+                dec->bit_rate / 1000);
+            av_bprintf(&buf,
+                "\"duration_sec\":%f,\"language\":\"%s\"",
+                duration,
+                lang ? lang->value : "und");
+
+            av_free(url);
+
+            if (dec->codec_type == AVMEDIA_TYPE_VIDEO) {
+                float fps = 0;
+                if (st->avg_frame_rate.den && st->avg_frame_rate.num) {
+                    fps = av_q2d(st->avg_frame_rate);
+                }
+
+                av_bprintf(&buf,
+                    ",\"fps\":%f,\"pix_fmt\":\"%s\",\"width\":%d,\"height\":%d",
+                    fps,
+                    st->codecpar->format == AV_PIX_FMT_NONE
+                        ? "none"
+                        : av_get_pix_fmt_name(st->codecpar->format),
+                    st->codecpar->width,
+                    st->codecpar->height);
+            } else if (dec->codec_type == AVMEDIA_TYPE_AUDIO) {
+                char layout[128];
+                av_channel_layout_describe(
+                    &dec->ch_layout, layout, sizeof(layout));
+
+                av_bprintf(&buf,
+                    ",\"sampling_hz\":%d,\"layout\":\"%s\",\"channels\":%d",
+                    dec->sample_rate,
+                    layout,
+                    dec->ch_layout.nb_channels);
+            }
+
+            if (i == (nb_input_files - 1) && j == (f->nb_streams - 1)) {
+                av_bprintf(&buf, "}");
+            } else {
+                av_bprintf(&buf, "},");
+            }
+        }
+    }
+
+    av_bprintf(&buf, "]\n");
+
+    av_log(NULL, AV_LOG_INFO, "%s", buf.str);
+
+    if (progress_avio) {
+        avio_write(progress_avio, buf.str, FFMIN(buf.len, buf.size - 1));
+        avio_flush(progress_avio);
+    }
+
+    av_bprint_finalize(&buf, NULL);
+}
+
 int ffmpeg_parse_options(int argc, char **argv)
 {
     OptionParseContext octx;
@@ -3467,6 +3578,8 @@ int ffmpeg_parse_options(int argc, char **argv)
         goto fail;
     }
 
+    print_json_inputs();
+
     /* create the complex filtergraphs */
     ret = init_complex_filters();
     if (ret < 0) {
@@ -3688,6 +3801,8 @@ const OptionDef options[] = {
         "enable automatic conversion filters globally" },
     { "stats",          OPT_BOOL,                                    { &print_stats },
         "print progress report during encoding", },
+    { "jsonstats",      OPT_BOOL,                                    { &print_jsonstats },
+        "print JSON progress report during encoding", },
     { "stats_period",    HAS_ARG | OPT_EXPERT,                       { .func_arg = opt_stats_period },
         "set the period at which ffmpeg updates stats and -progress output", "time" },
     { "attach",         HAS_ARG | OPT_PERFILE | OPT_EXPERT |

base-commit: 5d5a01419928d0c00bae54f730eede150cd5b268
-- 
2.32.1 (Apple Git-133)



More information about the ffmpeg-devel mailing list