Files
ffmpeg_build/contrib/ffmpeg-jsonstats.patch

841 lines
27 KiB
Diff

From 614b3f1eb76a2fe40eda49232fee8e7a6418d4fd Mon Sep 17 00:00:00 2001
From: Ingo Oppermann <ingo@datarhei.com>
Date: Thu, 13 Apr 2023 14:38:05 +0200
Subject: [PATCH v26] JSON progress report (ffmpeg 5.1)
---
fftools/ffmpeg.c | 245 +++++++++++++++++++++++++++++++++--
fftools/ffmpeg.h | 8 ++
fftools/ffmpeg_mux.c | 299 +++++++++++++++++++++++++++++++++++++++++++
fftools/ffmpeg_opt.c | 110 ++++++++++++++++
4 files changed, 651 insertions(+), 11 deletions(-)
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index e7384f05..1c15fd96 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -1504,12 +1504,11 @@ 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;
- AVFormatContext *oc;
- int64_t total_size;
+ int64_t total_size = 0;
AVCodecContext *enc;
int vid, i;
double bitrate;
@@ -1538,13 +1537,6 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
t = (cur_time-timer_start) / 1000000.0;
-
- oc = output_files[0]->ctx;
-
- total_size = avio_size(oc->pb);
- if (total_size <= 0) // FIXME improve avio_size() so it works with non seekable output too
- total_size = avio_tell(oc->pb);
-
vid = 0;
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC);
@@ -1627,6 +1619,9 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
if (is_last_report)
nb_frames_drop += ost->last_dropped;
+
+ total_size += ost->data_size;
+ total_size += ost->enc_ctx->extradata_size;
}
secs = FFABS(pts) / AV_TIME_BASE;
@@ -1714,6 +1709,201 @@ 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_size = 0;
+ AVCodecContext *enc;
+ int i, j;
+ uint64_t first_vid, first_frame_number = 0, first_packet_number = 0;
+ double speed;
+ int64_t min_pts = INT64_MIN + 1, pts = INT64_MIN + 1;
+ static int64_t last_time = -1;
+ int hours, mins, secs, us;
+ const char *hours_sign;
+ float t, q;
+ float first_q = -1;
+
+ if(!print_jsonstats && !is_last_report && !progress_avio) {
+ return;
+ }
+
+ if(!is_last_report) {
+ if(last_time == -1) {
+ last_time = cur_time;
+ return;
+ }
+ if((cur_time - last_time) < 500000) {
+ return;
+ }
+ last_time = cur_time;
+ }
+
+ t = (cur_time - timer_start) / 1000000.0;
+
+ av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ av_bprintf(&buf, "ffmpeg.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, "\"framerate\":{\"min\":%.3f,\"max\":%.3f,\"avg\":%.3f},", ist->min_framerate, ist->max_framerate, ist->avg_framerate);
+
+ av_bprintf(&buf,
+ "\"frame\":%" PRIu64 ",\"keyframe\":%" PRIu64 ",\"packet\":%" PRIu64 ",",
+ ist->frames_decoded == 0 ? ist->nb_packets
+ : ist->frames_decoded,
+ ist->nb_keyframes,
+ ist->nb_packets);
+
+ av_bprintf(&buf, "\"size_kb\":%.0f,\"size_bytes\":%" PRIu64, ist->data_size / 1024.0, ist->data_size);
+
+ if(i == (nb_input_files - 1) && j == (f->nb_streams - 1)) {
+ av_bprintf(&buf, "}");
+ } else {
+ av_bprintf(&buf, "},");
+ }
+ }
+ }
+
+ av_bprintf(&buf, "],");
+
+ first_vid = 1;
+
+ 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 ",\"keyframe\":%" PRIu64 ",\"packet\":%" PRIu64 ",",
+ ost->frames_encoded == 0 ? ost->packets_written
+ : ost->frames_encoded,
+ ost->nb_keyframes,
+ ost->packets_written);
+
+ if(enc->codec_type == AVMEDIA_TYPE_VIDEO) {
+ av_bprintf(&buf, "\"q\":%.1f,", q);
+
+ if(first_vid == 1) {
+ first_frame_number = ost->frames_encoded == 0
+ ? ost->packets_written
+ : ost->frames_encoded;
+ first_packet_number = ost->packets_written;
+ first_q = q;
+
+ first_vid = 0;
+ }
+ }
+
+ /* compute min output value */
+ pts = INT64_MIN + 1;
+ 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));
+ min_pts = FFMAX(min_pts,
+ av_rescale_q(av_stream_get_end_pts(ost->st),
+ ost->st->time_base,
+ AV_TIME_BASE_Q));
+ }
+
+ 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_kb\":%.0f,\"size_bytes\":%" PRIu64 ",", stream_size / 1024.0, stream_size);
+ av_bprintf(&buf, "\"extradata_size_bytes\":%d", ost->enc_ctx->extradata_size);
+
+ if(i == (nb_output_streams - 1)) {
+ av_bprintf(&buf, "}");
+ } else {
+ av_bprintf(&buf, "},");
+ }
+ }
+
+ av_bprintf(&buf, "],");
+
+ av_bprintf(&buf,
+ "\"frame\":%" PRIu64 ",\"packet\":%" PRIu64 ",\"q\":%.1f,",
+ first_frame_number,
+ first_packet_number,
+ first_q);
+
+ av_bprintf(&buf, "\"size_kb\":%.0f,\"size_bytes\":%" PRIu64 ",", total_size / 1024.0, total_size);
+
+ secs = FFABS(min_pts) / AV_TIME_BASE;
+ us = FFABS(min_pts) % AV_TIME_BASE;
+ mins = secs / 60;
+ secs %= 60;
+ hours = mins / 60;
+ mins %= 60;
+ hours_sign = (min_pts < 0) ? "-" : "";
+
+ if(min_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)min_pts / AV_TIME_BASE / t : -1;
+ av_bprintf(&buf, "\"speed\":%.3g,", speed);
+
+ av_bprintf(&buf, "\"dup\":%"PRId64",\"drop\":%"PRId64, nb_frames_dup, nb_frames_drop);
+ av_bprintf(&buf, "}");
+
+ if(print_jsonstats || is_last_report) {
+ fprintf(stderr, "%s\n", buf.str);
+ fflush(stderr);
+ }
+
+ av_bprint_finalize(&buf, NULL);
+}
+
+static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time)
+{
+ if(!print_stats && !print_jsonstats && !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;
@@ -2660,6 +2850,9 @@ static int init_input_stream(int ist_index, char *error, int error_len)
ist->next_pts = AV_NOPTS_VALUE;
ist->next_dts = AV_NOPTS_VALUE;
+ ist->min_framerate = 0.0;
+ ist->max_framerate = 0.0;
+
return 0;
}
@@ -3988,6 +4181,10 @@ static int process_input(int file_index)
ist->data_size += pkt->size;
ist->nb_packets++;
+ if(pkt->flags & AV_PKT_FLAG_KEY) {
+ ist->nb_keyframes++;
+ }
+
if (ist->discard)
goto discard_packet;
@@ -4153,9 +4350,35 @@ static int process_input(int file_index)
}
}
- if (pkt->dts != AV_NOPTS_VALUE)
+ if (pkt->dts != AV_NOPTS_VALUE) {
ifile->last_ts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q);
+ if(pkt->flags & AV_PKT_FLAG_KEY) {
+ int64_t ts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q);
+
+ // fps estimation
+ if(ts - ist->last_keyframe_dts != 0) {
+ double framerate = (ist->nb_packets - ist->last_keyframe_pktnb) / (double)(ts - ist->last_keyframe_dts) * (double)AV_TIME_BASE;
+
+ if(ist->min_framerate == 0.0 || framerate < ist->min_framerate) {
+ ist->min_framerate = framerate;
+ }
+
+ if(ist->max_framerate == 0.0 || framerate > ist->max_framerate) {
+ ist->max_framerate = framerate;
+ }
+
+ ist->sum_framerate += framerate;
+ ist->nsamples_framerate++;
+
+ ist->avg_framerate = ((ist->nsamples_framerate-1)*ist->avg_framerate + framerate) / (double)ist->nsamples_framerate;
+ }
+
+ ist->last_keyframe_dts = ts;
+ ist->last_keyframe_pktnb = ist->nb_packets;
+ }
+ }
+
if (debug_ts) {
av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n",
ifile->ist_index + pkt->stream_index, av_get_media_type_string(ist->dec_ctx->codec_type),
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 391a35cf..302c159f 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -330,6 +330,11 @@ typedef struct InputStream {
int64_t pts; ///< current pts of the decoded frame (in AV_TIME_BASE units)
int wrap_correction_done;
+ int64_t last_keyframe_dts;
+ int64_t last_keyframe_pktnb;
+ int64_t nsamples_framerate;
+ double sum_framerate, min_framerate, max_framerate, avg_framerate;
+
int64_t filter_in_rescale_delta_last;
int64_t min_pts; /* pts with the smallest value in a current stream */
@@ -391,6 +396,7 @@ typedef struct InputStream {
uint64_t data_size;
/* number of packets successfully read for this stream */
uint64_t nb_packets;
+ uint64_t nb_keyframes;
// number of frames/samples retrieved from the decoder
uint64_t frames_decoded;
uint64_t samples_decoded;
@@ -553,6 +559,7 @@ typedef struct OutputStream {
uint64_t data_size;
// number of packets send to the muxer
uint64_t packets_written;
+ uint64_t nb_keyframes;
// number of frames/samples sent to the encoder
uint64_t frames_encoded;
uint64_t samples_encoded;
@@ -636,6 +643,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 a55fd18f..ae82baf3 100644
--- a/fftools/ffmpeg_mux.c
+++ b/fftools/ffmpeg_mux.c
@@ -26,6 +26,8 @@
#include "libavutil/log.h"
#include "libavutil/mem.h"
#include "libavutil/timestamp.h"
+#include "libavutil/bprint.h"
+#include "libavutil/pixdesc.h"
#include "libavcodec/packet.h"
@@ -152,6 +154,10 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost,
ost->data_size += pkt->size;
ost->packets_written++;
+ if(pkt->flags & AV_PKT_FLAG_KEY) {
+ ost->nb_keyframes++;
+ }
+
pkt->stream_index = ost->index;
if (debug_ts) {
@@ -226,6 +232,297 @@ 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 == NULL) {
+ 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 != 1) {
+ 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, "ffmpeg.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 != NULL ? 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, "]");
+
+ fprintf(stderr, "%s\n", buf.str);
+
+ av_bprint_clear(&buf);
+
+ av_bprintf(&buf, "ffmpeg.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, "]}");
+
+ fprintf(stderr, "%s\n", buf.str);
+ fflush(stderr);
+
+ av_bprint_finalize(&buf, NULL);
+
+ return;
+}
+
/* open the muxer when all the streams are initialized */
int of_check_init(OutputFile *of)
{
@@ -251,6 +548,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 6e18a4a2..572d5356 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -52,6 +52,7 @@
#include "libavutil/parseutils.h"
#include "libavutil/pixdesc.h"
#include "libavutil/pixfmt.h"
+#include "libavutil/bprint.h"
#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass"
@@ -171,6 +172,7 @@ int debug_ts = 0;
int exit_on_error = 0;
int abort_on_flags = 0;
int print_stats = -1;
+int print_jsonstats = 1;
int qp_hist = 0;
int stdin_interaction = 1;
float max_error_rate = 2.0/3;
@@ -3547,6 +3549,111 @@ 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 != 1) {
+ return;
+ }
+
+ AVBPrint buf;
+ int i, j;
+
+ av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ av_bprintf(&buf, "ffmpeg.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 != NULL ? 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, "]");
+
+ fprintf(stderr, "%s\n", buf.str);
+ fflush(stderr);
+
+ av_bprint_finalize(&buf, NULL);
+}
+
int ffmpeg_parse_options(int argc, char **argv)
{
OptionParseContext octx;
@@ -3580,6 +3687,7 @@ int ffmpeg_parse_options(int argc, char **argv)
goto fail;
}
+ print_json_inputs();
apply_sync_offsets();
/* create the complex filtergraphs */
@@ -3806,6 +3914,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: 2bca71f4986725d7cf0d441e2f82a790d0a0c717
--
2.37.1 (Apple Git-137.1)