[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