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

Ingo Oppermann ingo at datarhei.com
Mon Jul 25 20:47:23 EEST 2022


> On 9 Jun 2022, at 14:47, Ingo Oppermann <ingo at datarhei.com> wrote:
> 
> 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)
> 

ping

Any comments on this patch? Thanks

--
Ingo



More information about the ffmpeg-devel mailing list