mirror of
https://github.com/datarhei/ffmpeg
synced 2025-12-24 13:08:07 +08:00
441 lines
15 KiB
Diff
441 lines
15 KiB
Diff
From df7dfb5351f77a672de5a6d613678888f560afcb Mon Sep 17 00:00:00 2001
|
|
From: Ingo Oppermann <ingo@oppermann.ch>
|
|
Date: Tue, 1 Jun 2021 08:53:52 +0200
|
|
Subject: [PATCH v20] JSON progress report (ffmpeg 4.4)
|
|
|
|
---
|
|
fftools/ffmpeg.c | 262 +++++++++++++++++++++++++++++++++++++++++--
|
|
fftools/ffmpeg.h | 1 +
|
|
fftools/ffmpeg_opt.c | 69 ++++++++++++
|
|
3 files changed, 322 insertions(+), 10 deletions(-)
|
|
|
|
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
|
|
index 46bb014..e5e00c9 100644
|
|
--- a/fftools/ffmpeg.c
|
|
+++ b/fftools/ffmpeg.c
|
|
@@ -1699,12 +1699,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 frame_number, vid, i;
|
|
double bitrate;
|
|
@@ -1733,13 +1732,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);
|
|
@@ -1822,6 +1814,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;
|
|
@@ -1909,6 +1904,251 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
|
|
print_final_stats(total_size);
|
|
}
|
|
|
|
+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, *dec;
|
|
+ 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];
|
|
+ dec = ist->dec_ctx;
|
|
+
|
|
+ av_bprintf(&buf, "{");
|
|
+ av_bprintf(&buf, "\"index\":%d,\"stream\":%d,", i, j);
|
|
+
|
|
+ av_bprintf(&buf, "\"frame\":%"PRIu64",\"packet\":%"PRIu64",", ist->frames_decoded == 0 ? ist->nb_packets : ist->frames_decoded, ist->nb_packets);
|
|
+
|
|
+ av_bprintf(&buf, "\"size_kb\":%.0f", ist->data_size / 1024.0);
|
|
+
|
|
+ if(i == (nb_input_files - 1) && j == (f->nb_streams - 1)) {
|
|
+ av_bprintf(&buf, "}");
|
|
+ }
|
|
+ else {
|
|
+ av_bprintf(&buf, "},");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ av_bprintf(&buf, "],");
|
|
+
|
|
+ // check libavcodec/utils.c:avcodec_string
|
|
+ // check libavformat/dump.c:av_dump_format
|
|
+ // check libavcodec/avcodec.h:struct AVCodec and struct AVCodecContext
|
|
+ // check libavformat/avformat.h:struct AVStream
|
|
+ // check fftools/ffmpeg.h:struct OutputStream
|
|
+
|
|
+ first_vid = 1;
|
|
+
|
|
+ av_bprintf(&buf, "\"outputs\":[");
|
|
+ for (i = 0; i < nb_output_streams; i++) {
|
|
+ OutputFile *f;
|
|
+ q = -1;
|
|
+ ost = output_streams[i];
|
|
+ f = output_files[ost->file_index];
|
|
+ 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 == 0 ? ost->packets_written : ost->frames_encoded, 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", stream_size / 1024.0);
|
|
+
|
|
+ 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,", total_size / 1024.0);
|
|
+
|
|
+ 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\":%d,\"drop\":%d", 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_json_outputs() {
|
|
+ static int ost_all_initialized = 0;
|
|
+ int i;
|
|
+ int nb_initialized = 0;
|
|
+
|
|
+ if(print_jsonstats != 1 && print_stats != -1) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if(ost_all_initialized == 1) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // check if all outputs are initialized
|
|
+ for (i = 0; i < nb_output_streams; i++) {
|
|
+ OutputStream *ost = output_streams[i];
|
|
+ if (ost->initialized) {
|
|
+ nb_initialized++;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // only if all outputs are initialized, dump the outputs
|
|
+ if (nb_initialized == nb_output_streams) {
|
|
+ ost_all_initialized = 1;
|
|
+
|
|
+ AVBPrint buf;
|
|
+
|
|
+ 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;
|
|
+
|
|
+ av_bprintf(&buf, "{");
|
|
+ av_bprintf(&buf, "\"url\":\"%s\",\"format\":\"%s\",\"index\":%d,\"stream\":%d,", ctx->url, ctx->oformat->name, ost->file_index, ost->index);
|
|
+ av_bprintf(&buf, "\"type\":\"%s\",\"codec\":\"%s\",\"coder\":\"%s\",\"bitrate_kbps\":%"PRId64",", 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");
|
|
+
|
|
+ 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_get_channel_layout_string(layout, sizeof(layout), enc->channels, enc->channel_layout);
|
|
+
|
|
+ av_bprintf(&buf, ",\"sampling_hz\":%d,\"layout\":\"%s\",\"channels\":%d", enc->sample_rate, layout, enc->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);
|
|
+ }
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+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_stats == 1) {
|
|
+ print_default_report(is_last_report, timer_start, cur_time);
|
|
+ }
|
|
+ else {
|
|
+ print_json_report(is_last_report, timer_start, cur_time);
|
|
+ }
|
|
+}
|
|
+
|
|
static void ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParameters *par)
|
|
{
|
|
// We never got any input. Set a fake format, which will
|
|
@@ -3048,6 +3288,8 @@ static int check_init_output_file(OutputFile *of, int file_index)
|
|
av_dump_format(of->ctx, file_index, of->ctx->url, 1);
|
|
nb_output_dumped++;
|
|
|
|
+ print_json_outputs();
|
|
+
|
|
if (sdp_filename || want_sdp)
|
|
print_sdp();
|
|
|
|
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
|
|
index 606f2af..e7e9a46 100644
|
|
--- a/fftools/ffmpeg.h
|
|
+++ b/fftools/ffmpeg.h
|
|
@@ -621,6 +621,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_opt.c b/fftools/ffmpeg_opt.c
|
|
index 807e783..d48c02a 100644
|
|
--- a/fftools/ffmpeg_opt.c
|
|
+++ b/fftools/ffmpeg_opt.c
|
|
@@ -41,6 +41,7 @@
|
|
#include "libavutil/parseutils.h"
|
|
#include "libavutil/pixdesc.h"
|
|
#include "libavutil/pixfmt.h"
|
|
+#include "libavutil/bprint.h"
|
|
|
|
#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass"
|
|
|
|
@@ -167,6 +168,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;
|
|
int frame_bits_per_raw_sample = 0;
|
|
@@ -3348,6 +3350,69 @@ static int open_files(OptionGroupList *l, const char *inout,
|
|
return 0;
|
|
}
|
|
|
|
+static void print_json_inputs() {
|
|
+ if(print_jsonstats != 1 && print_stats != -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);
|
|
+
|
|
+ av_bprintf(&buf, "{");
|
|
+ av_bprintf(&buf, "\"url\":\"%s\",\"format\":\"%s\",\"index\":%d,\"stream\":%d,", ctx->url, ctx->iformat->name, i, j);
|
|
+ av_bprintf(&buf, "\"type\":\"%s\",\"codec\":\"%s\",\"coder\":\"%s\",\"bitrate_kbps\":%"PRId64",", 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");
|
|
+
|
|
+ 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_get_channel_layout_string(layout, sizeof(layout), dec->channels, dec->channel_layout);
|
|
+
|
|
+ av_bprintf(&buf, ",\"sampling_hz\":%d,\"layout\":\"%s\",\"channels\":%d", dec->sample_rate, layout, dec->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);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
int ffmpeg_parse_options(int argc, char **argv)
|
|
{
|
|
OptionParseContext octx;
|
|
@@ -3381,6 +3446,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) {
|
|
@@ -3584,6 +3651,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: 9dcc10e319b3533e0cef975c013b5fdf02e67ea2
|
|
--
|
|
2.24.3 (Apple Git-128)
|
|
|