From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTP id 0705A4323C for ; Mon, 25 Jul 2022 17:47:35 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id C34D568B7E8; Mon, 25 Jul 2022 20:47:32 +0300 (EEST) Received: from mail-wm1-f46.google.com (mail-wm1-f46.google.com [209.85.128.46]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id CD7D568B2C8 for ; Mon, 25 Jul 2022 20:47:26 +0300 (EEST) Received: by mail-wm1-f46.google.com with SMTP id c22so7310966wmr.2 for ; Mon, 25 Jul 2022 10:47:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=datarhei.com; s=google; h=from:content-transfer-encoding:mime-version:subject:date:references :to:in-reply-to:message-id; bh=9kfBNyWfxrCsym5td7wbyqHUD7laLJB2DaNHa9GbQZQ=; b=BQu0jASnA7/9rVqICqg/0PI0Fom4/muXC7pxBd7/n8233fC85A4sE/ZaOuuuvt8Fe5 ruvXIeGc00zn8xEhIYo2/sD9/plb7f/y234K/zp0dExXyuuq36Taa/SsJtHu6ETEubSb nQ/T+e9CKJdHHTd3lxgSxrSGlLpB9iTUjjD75lF7XNeieb0A4iNMcdH737N9bvMBj+BC WyHYrqr5SJCHaHK84JIjm3uodVq+WFDSF7b5JtfP1v4vXeheXaS8zPFFLRGvd5xcvfq1 avantouG8YlridDHzTidfH3cvisGaKyr/1O6z/68SjJ7PEbv6FPJZJosaj+lb3ZK0SH4 tICw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:content-transfer-encoding:mime-version :subject:date:references:to:in-reply-to:message-id; bh=9kfBNyWfxrCsym5td7wbyqHUD7laLJB2DaNHa9GbQZQ=; b=Pg3e8GRQIVFBom1PVCDCv67P38Y1PbX3qAhunkGLEDOKBrYdiH8cFkbPVHLcC67Kjs vpL7GWoItGXvLeKscls5R+rz6Hc6Fjtzx5X4etN6j3QBBdOmJNPcp0msw66Yd22WxGiK CpPLblom8lou4A3oKVD6mloRGNWcMBeT61rwklnNcvC3wNy0ENL5SxrVvlqzcetrMsbe 9xg3UJGCxB22VYt5vo6r4Tw6OBG4rqUChGU+sIg/ty7h4r2Ums6zXL+qR9Kg/0frtY7L T/71dZEqRMpQiZBJl3BvkgdUKMwW9M7+6D9YeMkIF5Toa4Fagj2uM0ALX/p56/6g5H+l fRuw== X-Gm-Message-State: AJIora+EWdxnlREI4pnh3IGtQcWkwShsa8Azt99lYO1oFhAAKv1zEb+f Dq8ezhdXH1uh2/7gIwC5hYqIt/sE0YDO X-Google-Smtp-Source: AGRyM1uK7aSu1H+VdQELIZExjX36Dx8i/bayiEz32n358qAdf9xzDHbTQPUVbqYpS3nc1TlyAFdwMQ== X-Received: by 2002:a05:600c:3b9a:b0:3a3:21c2:e289 with SMTP id n26-20020a05600c3b9a00b003a321c2e289mr9223777wms.77.1658771245523; Mon, 25 Jul 2022 10:47:25 -0700 (PDT) Received: from smtpclient.apple (adsl-178-38-92-185.adslplus.ch. [178.38.92.185]) by smtp.gmail.com with ESMTPSA id z21-20020a05600c0a1500b0039c454067ddsm15934593wmp.15.2022.07.25.10.47.24 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 25 Jul 2022 10:47:24 -0700 (PDT) From: Ingo Oppermann Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3696.100.31\)) Date: Mon, 25 Jul 2022 19:47:23 +0200 References: <20220609124700.81727-1-ingo@datarhei.com> To: ffmpeg-devel@ffmpeg.org In-Reply-To: <20220609124700.81727-1-ingo@datarhei.com> Message-Id: X-Mailer: Apple Mail (2.3696.100.31) Subject: Re: [FFmpeg-devel] [PATCH v1] ffmpeg: add optional JSON output of inputs, outputs, mapping, and progress X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Archived-At: List-Archive: List-Post: > On 9 Jun 2022, at 14:47, Ingo Oppermann 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 > --- > 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. > > +@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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".