mirror of
https://github.com/datarhei/ffmpeg
synced 2025-12-24 13:08:07 +08:00
841 lines
27 KiB
Diff
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)
|
|
|