From: Ingo Oppermann <ingo@datarhei.com> To: ffmpeg-devel@ffmpeg.org Cc: Ingo Oppermann <ingo@datarhei.com> Subject: [FFmpeg-devel] [PATCH v1] ffmpeg: add optional JSON output of inputs, outputs, mapping, and progress Date: Thu, 9 Jun 2022 14:47:00 +0200 Message-ID: <20220609124700.81727-1-ingo@datarhei.com> (raw) 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@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. +@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) _______________________________________________ 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".
next reply other threads:[~2022-06-09 12:47 UTC|newest] Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top 2022-06-09 12:47 Ingo Oppermann [this message] 2022-07-25 17:47 ` Ingo Oppermann 2022-07-28 14:09 ` Anton Khirnov 2022-07-31 13:15 ` Bodecs Bela
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20220609124700.81727-1-ingo@datarhei.com \ --to=ingo@datarhei.com \ --cc=ffmpeg-devel@ffmpeg.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git