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 |
