Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH 0/3] print_graphs: Complete Filtergraph Printing
@ 2025-02-19  9:59 ffmpegagent
  2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 1/3] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
                   ` (3 more replies)
  0 siblings, 4 replies; 15+ messages in thread
From: ffmpegagent @ 2025-02-19  9:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

The key benefits are:

 * Different to other graph printing methods, this is outputting:
   * all graphs with runtime state
     (including auto-inserted filters)
   * each graph with its inputs and outputs
   * all filters with their in- and output pads
   * all connections between all input- and output pads
   * for each connection:
     * the runtime-negotiated format and media type
     * the hw context
     * if video hw context, both: hw pixfmt + sw pixfmt
 * Output can either be printed to stdout or written to specified file
 * Output is machine-readable
 * Use the same output implementation as ffprobe, supporting multiple
   formats
   (this commit includes only the default and JSON writers)

Right after filtergraph configuration, the connection details are often not
complete yet. On the other side, when waiting too long or if an error occurs
somewhere, the graph info might never be printed. Experience has shown, that
the most suitable and realiable point in time for printing graph information
is right before cleanup.

Due to the changes for multi-threading, this is no longer doable as easy as
before, so the following method is used: Each filtergraph initiates its own
graph printing short before cleanup into a buffer. Before final cleanup in
ffmpeg.c, the outputs from the individual graphs are pieced together for the
actual output to file or console. (the structure according to the output
format remains valid)

Example output:
https://gist.github.com/softworkz/2a9e8699b288f5d40fa381c2a496e165

softworkz (3):
  fftools/ffmpeg_filter: Move some declaration to new header file
  fftools/ffmpeg_graphprint: Add options for filtergraph printing
  fftools: Enable filtergraph printing and update docs

 doc/ffmpeg.texi             |   10 +
 fftools/Makefile            |    1 +
 fftools/ffmpeg.c            |    4 +
 fftools/ffmpeg.h            |    3 +
 fftools/ffmpeg_filter.c     |  193 +-----
 fftools/ffmpeg_filter.h     |  232 +++++++
 fftools/ffmpeg_graphprint.c | 1152 +++++++++++++++++++++++++++++++++++
 fftools/ffmpeg_graphprint.h |  224 +++++++
 fftools/ffmpeg_opt.c        |   12 +
 9 files changed, 1644 insertions(+), 187 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h
 create mode 100644 fftools/ffmpeg_graphprint.c
 create mode 100644 fftools/ffmpeg_graphprint.h


base-commit: e18f87ed9f9f61c980420b315dc8ecb308831bc5
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-52%2Fsoftworkz%2Fsubmit_print_graphs5-v1
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-52/softworkz/submit_print_graphs5-v1
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/52
-- 
ffmpeg-codebot
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH 1/3] fftools/ffmpeg_filter: Move some declaration to new header file
  2025-02-19  9:59 [FFmpeg-devel] [PATCH 0/3] print_graphs: Complete Filtergraph Printing ffmpegagent
@ 2025-02-19  9:59 ` softworkz
  2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 15+ messages in thread
From: softworkz @ 2025-02-19  9:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

to allow print_graph to access the information.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_filter.c | 188 +-------------------------------
 fftools/ffmpeg_filter.h | 232 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 233 insertions(+), 187 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 800e2a3f06..6de4e87ade 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ffmpeg.h"
+#include "ffmpeg_filter.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -42,44 +43,6 @@
 // FIXME private header, used for mid_pred()
 #include "libavcodec/mathops.h"
 
-typedef struct FilterGraphPriv {
-    FilterGraph      fg;
-
-    // name used for logging
-    char             log_name[32];
-
-    int              is_simple;
-    // true when the filtergraph contains only meta filters
-    // that do not modify the frame data
-    int              is_meta;
-    // source filters are present in the graph
-    int              have_sources;
-    int              disable_conversions;
-
-    unsigned         nb_outputs_done;
-
-    const char      *graph_desc;
-
-    int              nb_threads;
-
-    // frame for temporarily holding output from the filtergraph
-    AVFrame         *frame;
-    // frame for sending output to the encoder
-    AVFrame         *frame_enc;
-
-    Scheduler       *sch;
-    unsigned         sch_idx;
-} FilterGraphPriv;
-
-static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
-{
-    return (FilterGraphPriv*)fg;
-}
-
-static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
-{
-    return (const FilterGraphPriv*)fg;
-}
 
 // data that is local to the filter thread and not visible outside of it
 typedef struct FilterGraphThread {
@@ -102,155 +65,6 @@ typedef struct FilterGraphThread {
     uint8_t         *eof_out;
 } FilterGraphThread;
 
-typedef struct InputFilterPriv {
-    InputFilter         ifilter;
-
-    InputFilterOptions  opts;
-
-    int                 index;
-
-    AVFilterContext    *filter;
-
-    // used to hold submitted input
-    AVFrame            *frame;
-
-    /* for filters that are not yet bound to an input stream,
-     * this stores the input linklabel, if any */
-    uint8_t            *linklabel;
-
-    // filter data type
-    enum AVMediaType    type;
-    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
-    // same as type otherwise
-    enum AVMediaType    type_src;
-
-    int                 eof;
-    int                 bound;
-
-    // parameters configured for this input
-    int                 format;
-
-    int                 width, height;
-    AVRational          sample_aspect_ratio;
-    enum AVColorSpace   color_space;
-    enum AVColorRange   color_range;
-
-    int                 sample_rate;
-    AVChannelLayout     ch_layout;
-
-    AVRational          time_base;
-
-    AVFrameSideData   **side_data;
-    int                 nb_side_data;
-
-    AVFifo             *frame_queue;
-
-    AVBufferRef        *hw_frames_ctx;
-
-    int                 displaymatrix_present;
-    int                 displaymatrix_applied;
-    int32_t             displaymatrix[9];
-
-    int                 downmixinfo_present;
-    AVDownmixInfo       downmixinfo;
-
-    struct {
-        AVFrame *frame;
-
-        int64_t last_pts;
-        int64_t end_pts;
-
-        ///< marks if sub2video_update should force an initialization
-        unsigned int initialize;
-    } sub2video;
-} InputFilterPriv;
-
-static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
-{
-    return (InputFilterPriv*)ifilter;
-}
-
-typedef struct FPSConvContext {
-    AVFrame          *last_frame;
-    /* number of frames emitted by the video-encoding sync code */
-    int64_t           frame_number;
-    /* history of nb_frames_prev, i.e. the number of times the
-     * previous frame was duplicated by vsync code in recent
-     * do_video_out() calls */
-    int64_t           frames_prev_hist[3];
-
-    uint64_t          dup_warning;
-
-    int               last_dropped;
-    int               dropped_keyframe;
-
-    enum VideoSyncMethod vsync_method;
-
-    AVRational        framerate;
-    AVRational        framerate_max;
-    const AVRational *framerate_supported;
-    int               framerate_clip;
-} FPSConvContext;
-
-typedef struct OutputFilterPriv {
-    OutputFilter            ofilter;
-
-    int                     index;
-
-    void                   *log_parent;
-    char                    log_name[32];
-
-    char                   *name;
-
-    AVFilterContext        *filter;
-
-    /* desired output stream properties */
-    int                     format;
-    int                     width, height;
-    int                     sample_rate;
-    AVChannelLayout         ch_layout;
-    enum AVColorSpace       color_space;
-    enum AVColorRange       color_range;
-
-    AVFrameSideData       **side_data;
-    int                     nb_side_data;
-
-    // time base in which the output is sent to our downstream
-    // does not need to match the filtersink's timebase
-    AVRational              tb_out;
-    // at least one frame with the above timebase was sent
-    // to our downstream, so it cannot change anymore
-    int                     tb_out_locked;
-
-    AVRational              sample_aspect_ratio;
-
-    AVDictionary           *sws_opts;
-    AVDictionary           *swr_opts;
-
-    // those are only set if no format is specified and the encoder gives us multiple options
-    // They point directly to the relevant lists of the encoder.
-    const int              *formats;
-    const AVChannelLayout  *ch_layouts;
-    const int              *sample_rates;
-    const enum AVColorSpace *color_spaces;
-    const enum AVColorRange *color_ranges;
-
-    AVRational              enc_timebase;
-    int64_t                 trim_start_us;
-    int64_t                 trim_duration_us;
-    // offset for output timestamps, in AV_TIME_BASE_Q
-    int64_t                 ts_offset;
-    int64_t                 next_pts;
-    FPSConvContext          fps;
-
-    unsigned                flags;
-} OutputFilterPriv;
-
-static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
-{
-    return (OutputFilterPriv*)ofilter;
-}
-
 typedef struct FilterCommand {
     char *target;
     char *command;
diff --git a/fftools/ffmpeg_filter.h b/fftools/ffmpeg_filter.h
new file mode 100644
index 0000000000..628d272bcd
--- /dev/null
+++ b/fftools/ffmpeg_filter.h
@@ -0,0 +1,232 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef FFTOOLS_FFMPEG_FILTER_H
+#define FFTOOLS_FFMPEG_FILTER_H
+
+#include "ffmpeg.h"
+
+#include <stdint.h>
+
+#include "ffmpeg_sched.h"
+#include "sync_queue.h"
+
+#include "libavfilter/avfilter.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/dict.h"
+#include "libavutil/fifo.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/downmix_info.h"
+
+typedef struct FilterGraphPriv {
+    FilterGraph      fg;
+
+    // name used for logging
+    char             log_name[32];
+
+    int              is_simple;
+    // true when the filtergraph contains only meta filters
+    // that do not modify the frame data
+    int              is_meta;
+    // source filters are present in the graph
+    int              have_sources;
+    int              disable_conversions;
+
+    unsigned         nb_outputs_done;
+
+    const char      *graph_desc;
+
+    int              nb_threads;
+
+    // frame for temporarily holding output from the filtergraph
+    AVFrame         *frame;
+    // frame for sending output to the encoder
+    AVFrame         *frame_enc;
+
+    Scheduler       *sch;
+    unsigned         sch_idx;
+
+    AVBPrint graph_print_buf;
+
+} FilterGraphPriv;
+
+static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
+{
+    return (FilterGraphPriv*)fg;
+}
+
+static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
+{
+    return (const FilterGraphPriv*)fg;
+}
+
+typedef struct InputFilterPriv {
+    InputFilter         ifilter;
+
+    InputFilterOptions  opts;
+
+    int                 index;
+
+    AVFilterContext    *filter;
+
+    // used to hold submitted input
+    AVFrame            *frame;
+
+    /* for filters that are not yet bound to an input stream,
+     * this stores the input linklabel, if any */
+    uint8_t            *linklabel;
+
+    // filter data type
+    enum AVMediaType    type;
+    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
+    // same as type otherwise
+    enum AVMediaType    type_src;
+
+    int                 eof;
+    int                 bound;
+
+    // parameters configured for this input
+    int                 format;
+
+    int                 width, height;
+    AVRational          sample_aspect_ratio;
+    enum AVColorSpace   color_space;
+    enum AVColorRange   color_range;
+
+    int                 sample_rate;
+    AVChannelLayout     ch_layout;
+
+    AVRational          time_base;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
+    AVFifo             *frame_queue;
+
+    AVBufferRef        *hw_frames_ctx;
+
+    int                 displaymatrix_present;
+    int                 displaymatrix_applied;
+    int32_t             displaymatrix[9];
+
+    int                 downmixinfo_present;
+    AVDownmixInfo       downmixinfo;
+
+    struct {
+        AVFrame *frame;
+
+        int64_t last_pts;
+        int64_t end_pts;
+
+        ///< marks if sub2video_update should force an initialization
+        unsigned int initialize;
+    } sub2video;
+} InputFilterPriv;
+
+static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
+{
+    return (InputFilterPriv*)ifilter;
+}
+
+typedef struct FPSConvContext {
+    AVFrame          *last_frame;
+    /* number of frames emitted by the video-encoding sync code */
+    int64_t           frame_number;
+    /* history of nb_frames_prev, i.e. the number of times the
+     * previous frame was duplicated by vsync code in recent
+     * do_video_out() calls */
+    int64_t           frames_prev_hist[3];
+
+    uint64_t          dup_warning;
+
+    int               last_dropped;
+    int               dropped_keyframe;
+
+    enum VideoSyncMethod vsync_method;
+
+    AVRational        framerate;
+    AVRational        framerate_max;
+    const AVRational *framerate_supported;
+    int               framerate_clip;
+} FPSConvContext;
+
+
+typedef struct OutputFilterPriv {
+    OutputFilter            ofilter;
+
+    int                     index;
+
+    void                   *log_parent;
+    char                    log_name[32];
+
+    char                   *name;
+
+    AVFilterContext        *filter;
+
+    /* desired output stream properties */
+    int                     format;
+    int                     width, height;
+    int                     sample_rate;
+    AVChannelLayout         ch_layout;
+    enum AVColorSpace       color_space;
+    enum AVColorRange       color_range;
+
+    AVFrameSideData       **side_data;
+    int                     nb_side_data;
+
+    // time base in which the output is sent to our downstream
+    // does not need to match the filtersink's timebase
+    AVRational              tb_out;
+    // at least one frame with the above timebase was sent
+    // to our downstream, so it cannot change anymore
+    int                     tb_out_locked;
+
+    AVRational              sample_aspect_ratio;
+
+    AVDictionary           *sws_opts;
+    AVDictionary           *swr_opts;
+
+    // those are only set if no format is specified and the encoder gives us multiple options
+    // They point directly to the relevant lists of the encoder.
+    const int              *formats;
+    const AVChannelLayout  *ch_layouts;
+    const int              *sample_rates;
+    const enum AVColorSpace *color_spaces;
+    const enum AVColorRange *color_ranges;
+
+    AVRational              enc_timebase;
+    int64_t                 trim_start_us;
+    int64_t                 trim_duration_us;
+    // offset for output timestamps, in AV_TIME_BASE_Q
+    int64_t                 ts_offset;
+    int64_t                 next_pts;
+    FPSConvContext          fps;
+
+    unsigned                flags;
+} OutputFilterPriv;
+
+static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
+{
+    return (OutputFilterPriv*)ofilter;
+}
+
+#endif /* FFTOOLS_FFMPEG_FILTER_H */
-- 
ffmpeg-codebot

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
  2025-02-19  9:59 [FFmpeg-devel] [PATCH 0/3] print_graphs: Complete Filtergraph Printing ffmpegagent
  2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 1/3] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-02-19  9:59 ` softworkz
  2025-02-21  9:22   ` Andreas Rheinhardt
  2025-02-21 13:09   ` Nicolas George
  2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 3/3] fftools: Enable filtergraph printing and update docs softworkz
  2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing ffmpegagent
  3 siblings, 2 replies; 15+ messages in thread
From: softworkz @ 2025-02-19  9:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

The key benefits are:

- Different to other graph printing methods, this is outputting:
  - all graphs with runtime state
    (including auto-inserted filters)
  - each graph with its inputs and outputs
  - all filters with their in- and output pads
  - all connections between all input- and output pads
  - for each connection:
    - the runtime-negotiated format and media type
    - the hw context
    - if video hw context, both: hw pixfmt + sw pixfmt
- Output can either be printed to stdout or written to specified file
- Output is machine-readable
- Use the same output implementation as ffprobe, supporting multiple
  formats

Note: This commit includes only the default and JSON writers.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/Makefile            |    1 +
 fftools/ffmpeg.h            |    3 +
 fftools/ffmpeg_graphprint.c | 1152 +++++++++++++++++++++++++++++++++++
 fftools/ffmpeg_graphprint.h |  224 +++++++
 fftools/ffmpeg_opt.c        |   12 +
 5 files changed, 1392 insertions(+)
 create mode 100644 fftools/ffmpeg_graphprint.c
 create mode 100644 fftools/ffmpeg_graphprint.h

diff --git a/fftools/Makefile b/fftools/Makefile
index 4499799818..189feb4e2a 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -19,6 +19,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
+    fftools/ffmpeg_graphprint.o \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
 
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 6cc0da05a0..432954b4cc 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -714,6 +714,9 @@ extern float max_error_rate;
 extern char *filter_nbthreads;
 extern int filter_complex_nbthreads;
 extern int vstats_version;
+extern int print_graphs;
+extern char* print_graphs_file;
+extern char* print_graphs_format;
 extern int auto_conversion_filters;
 
 extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_graphprint.c b/fftools/ffmpeg_graphprint.c
new file mode 100644
index 0000000000..77f143b8c2
--- /dev/null
+++ b/fftools/ffmpeg_graphprint.c
@@ -0,0 +1,1152 @@
+/*
+ * Copyright (c) 2018 - softworkz
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "ffmpeg_graphprint.h"
+#include "ffmpeg_filter.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavfilter/filters.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+
+static const char *writer_get_name(void *p)
+{
+    WriterContext *wctx = p;
+    return wctx->writer->name;
+}
+
+#define OFFSET(x) offsetof(WriterContext, x)
+
+static const AVOption writer_options[] = {
+    { "string_validation", "set string validation mode",
+      OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" },
+    { "sv", "set string validation mode",
+      OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" },
+    { "ignore",  NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_IGNORE},  .unit = "sv" },
+    { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_REPLACE}, .unit = "sv" },
+    { "fail",    NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_FAIL},    .unit = "sv" },
+    { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}},
+    { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str="\xEF\xBF\xBD"}},
+    { NULL }
+};
+
+static void *writer_child_next(void *obj, void *prev)
+{
+    WriterContext *ctx = obj;
+    if (!prev && ctx->writer && ctx->writer->priv_class && ctx->priv)
+        return ctx->priv;
+    return NULL;
+}
+
+static const AVClass writer_class = {
+    .class_name = "Writer",
+    .item_name  = writer_get_name,
+    .option     = writer_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+    .child_next = writer_child_next,
+};
+
+void writer_close(WriterContext **wctx)
+{
+    int i;
+
+    if (!*wctx)
+        return;
+
+    if ((*wctx)->writer->uninit)
+        (*wctx)->writer->uninit(*wctx);
+    for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
+        av_bprint_finalize(&(*wctx)->section_pbuf[i], NULL);
+    av_bprint_finalize(&(*wctx)->bpBuf, NULL);
+    if ((*wctx)->writer->priv_class)
+        av_opt_free((*wctx)->priv);
+    av_freep(&((*wctx)->priv));
+    av_opt_free(*wctx);
+    av_freep(wctx);
+}
+
+static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size)
+{
+    av_bprintf(bp, "0X");
+    for (size_t i = 0; i < ubuf_size; i++)
+        av_bprintf(bp, "%02X", ubuf[i]);
+}
+
+int writer_open(WriterContext **wctx, const Writer *writer, const char *args,
+                       const struct section *sections1, int nb_sections)
+{
+    int i, ret = 0;
+
+    if (!(*wctx = av_mallocz(sizeof(WriterContext)))) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    if (!((*wctx)->priv = av_mallocz(writer->priv_size))) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    (*wctx)->class = &writer_class;
+    (*wctx)->writer = writer;
+    (*wctx)->level = -1;
+    (*wctx)->sections = sections;
+    (*wctx)->nb_sections = nb_sections;
+
+    av_opt_set_defaults(*wctx);
+
+    if (writer->priv_class) {
+        void *priv_ctx = (*wctx)->priv;
+        *((const AVClass **)priv_ctx) = writer->priv_class;
+        av_opt_set_defaults(priv_ctx);
+    }
+
+    /* convert options to dictionary */
+    if (args) {
+        AVDictionary *opts = NULL;
+        AVDictionaryEntry *opt = NULL;
+
+        if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) {
+            av_log(*wctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to writer context\n", args);
+            av_dict_free(&opts);
+            goto fail;
+        }
+
+        while ((opt = av_dict_get(opts, "", opt, AV_DICT_IGNORE_SUFFIX))) {
+            if ((ret = av_opt_set(*wctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) {
+                av_log(*wctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to writer context\n",
+                       opt->key, opt->value);
+                av_dict_free(&opts);
+                goto fail;
+            }
+        }
+
+        av_dict_free(&opts);
+    }
+
+    /* validate replace string */
+    {
+        const uint8_t *p = (*wctx)->string_validation_replacement;
+        const uint8_t *endp = p + strlen(p);
+        while (*p) {
+            const uint8_t *p0 = p;
+            int32_t code;
+            ret = av_utf8_decode(&code, &p, endp, (*wctx)->string_validation_utf8_flags);
+            if (ret < 0) {
+                AVBPrint bp;
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+                bprint_bytes(&bp, p0, p-p0),
+                    av_log(wctx, AV_LOG_ERROR,
+                           "Invalid UTF8 sequence %s found in string validation replace '%s'\n",
+                           bp.str, (*wctx)->string_validation_replacement);
+                return ret;
+            }
+        }
+    }
+
+    for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
+        av_bprint_init(&(*wctx)->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED);
+
+    av_bprint_init(&(*wctx)->bpBuf, 500000, AV_BPRINT_SIZE_UNLIMITED);
+
+    if ((*wctx)->writer->init)
+        ret = (*wctx)->writer->init(*wctx);
+    if (ret < 0)
+        goto fail;
+
+    return 0;
+
+fail:
+    writer_close(wctx);
+    return ret;
+}
+
+void writer_print_section_header(WriterContext *wctx, int section_id)
+{
+    //int parent_section_id;
+    wctx->level++;
+    av_assert0(wctx->level < SECTION_MAX_NB_LEVELS);
+    //parent_section_id = wctx->level ?
+    //    (wctx->section[wctx->level-1])->id : SECTION_ID_NONE;
+
+    wctx->nb_item[wctx->level] = 0;
+    wctx->section[wctx->level] = &wctx->sections[section_id];
+
+    if (wctx->writer->print_section_header)
+        wctx->writer->print_section_header(wctx);
+}
+
+void writer_print_section_footer(WriterContext *wctx)
+{
+    //int section_id = wctx->section[wctx->level]->id;
+    int parent_section_id = wctx->level ?
+        wctx->section[wctx->level-1]->id : SECTION_ID_NONE;
+
+    if (parent_section_id != SECTION_ID_NONE)
+        wctx->nb_item[wctx->level-1]++;
+
+    if (wctx->writer->print_section_footer)
+        wctx->writer->print_section_footer(wctx);
+    wctx->level--;
+}
+
+static inline int validate_string(WriterContext *wctx, char **dstp, const char *src)
+{
+    const uint8_t *p, *endp;
+    AVBPrint dstbuf;
+    int invalid_chars_nb = 0, ret = 0;
+
+    av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    endp = src + strlen(src);
+    for (p = (uint8_t *)src; *p;) {
+        uint32_t code;
+        int invalid = 0;
+        const uint8_t *p0 = p;
+
+        if (av_utf8_decode(&code, &p, endp, wctx->string_validation_utf8_flags) < 0) {
+            AVBPrint bp;
+            av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+            bprint_bytes(&bp, p0, p-p0);
+            av_log(wctx, AV_LOG_DEBUG,
+                   "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src);
+            invalid = 1;
+        }
+
+        if (invalid) {
+            invalid_chars_nb++;
+
+            switch (wctx->string_validation) {
+            case WRITER_STRING_VALIDATION_FAIL:
+                av_log(wctx, AV_LOG_ERROR,
+                       "Invalid UTF-8 sequence found in string '%s'\n", src);
+                ret = AVERROR_INVALIDDATA;
+                goto end;
+
+            case WRITER_STRING_VALIDATION_REPLACE:
+                av_bprintf(&dstbuf, "%s", wctx->string_validation_replacement);
+                break;
+            }
+        }
+
+        if (!invalid || wctx->string_validation == WRITER_STRING_VALIDATION_IGNORE)
+            av_bprint_append_data(&dstbuf, p0, p-p0);
+    }
+
+    if (invalid_chars_nb && wctx->string_validation == WRITER_STRING_VALIDATION_REPLACE) {
+        av_log(wctx, AV_LOG_WARNING,
+               "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n",
+               invalid_chars_nb, src, wctx->string_validation_replacement);
+    }
+
+end:
+    av_bprint_finalize(&dstbuf, dstp);
+    return ret;
+}
+
+int writer_print_string(WriterContext *wctx, const char *key, const char *val, int flags)
+{
+    const struct section *section = wctx->section[wctx->level];
+    int ret = 0;
+
+    if ((flags & PRINT_STRING_OPT)
+        && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS))
+        return 0;
+
+    if (val == NULL)
+        return 0;
+
+    if (flags & PRINT_STRING_VALIDATE) {
+        char *key1 = NULL, *val1 = NULL;
+        ret = validate_string(wctx, &key1, key);
+        if (ret < 0) goto end;
+        ret = validate_string(wctx, &val1, val);
+        if (ret < 0) goto end;
+        wctx->writer->print_string(wctx, key1, val1);
+    end:
+        if (ret < 0) {
+            av_log(wctx, AV_LOG_ERROR,
+                    "Invalid key=value string combination %s=%s in section %s\n",
+                    key, val, section->unique_name);
+        }
+        av_free(key1);
+        av_free(val1);
+    } else {
+        wctx->writer->print_string(wctx, key, val);
+    }
+
+    wctx->nb_item[wctx->level]++;
+
+    return ret;
+}
+
+void writer_print_integer(WriterContext *wctx, const char *key, long long int val)
+{
+    //const struct section *section = wctx->section[wctx->level];
+
+    wctx->writer->print_integer(wctx, key, val);
+    wctx->nb_item[wctx->level]++;
+}
+
+void writer_print_rational(WriterContext *wctx, const char *key, AVRational q, char sep)
+{
+    AVBPrint buf;
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+    av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
+    writer_print_string(wctx, key, buf.str, 0);
+}
+
+void writer_print_guid(WriterContext *wctx, const char *key, GUID *guid)
+{
+    AVBPrint buf;
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+    av_bprintf(&buf, "{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}",
+             (unsigned) guid->Data1, guid->Data2, guid->Data3,
+             guid->Data4[0], guid->Data4[1],
+             guid->Data4[2], guid->Data4[3],
+             guid->Data4[4], guid->Data4[5],
+             guid->Data4[6], guid->Data4[7]);
+
+    writer_print_string(wctx, key, buf.str, 0);
+}
+
+//writer_print_time(WriterContext *wctx, const char *key, int64_t ts, const AVRational *time_base, int is_duration)
+//{
+//    char buf[128];
+//
+//    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
+//        writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT);
+//    } else {
+//        double d = ts * av_q2d(*time_base);
+//        struct unit_value uv;
+//        uv.val.d = d;
+//        uv.unit = unit_second_str;
+//        value_string(buf, sizeof(buf), uv);
+//        writer_print_string(wctx, key, buf, 0);
+//    }
+//}
+
+void writer_print_ts(WriterContext *wctx, const char *key, int64_t ts, int is_duration)
+{
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
+        writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT);
+    } else {
+        writer_print_integer(wctx, key, ts);
+    }
+}
+
+
+static const Writer *registered_writers[MAX_REGISTERED_WRITERS_NB + 1];
+
+static int writer_register(const Writer *writer)
+{
+    static int next_registered_writer_idx = 0;
+
+    if (next_registered_writer_idx == MAX_REGISTERED_WRITERS_NB)
+        return AVERROR(ENOMEM);
+
+    registered_writers[next_registered_writer_idx++] = writer;
+    return 0;
+}
+
+const Writer *writer_get_by_name(const char *name)
+{
+    int i;
+
+    for (i = 0; registered_writers[i]; i++)
+        if (!strcmp(registered_writers[i]->name, name))
+            return registered_writers[i];
+
+    return NULL;
+}
+
+/* WRITERS */
+
+#define DEFINE_WRITER_CLASS(name)                   \
+static const char *name##_get_name(void *ctx)       \
+{                                                   \
+    return #name ;                                  \
+}                                                   \
+static const AVClass name##_class = {               \
+    .class_name = #name,                            \
+    .item_name  = name##_get_name,                  \
+    .option     = name##_options                    \
+}
+
+/* Default output */
+
+typedef struct DefaultContext {
+    const AVClass *class;
+    int nokey;
+    int noprint_wrappers;
+    int nested_section[SECTION_MAX_NB_LEVELS];
+} DefaultContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(DefaultContext, x)
+
+static const AVOption default_options[] = {
+    { "noprint_wrappers", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "nw",               "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "nokey",          "force no key printing",     OFFSET(nokey),          AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "nk",             "force no key printing",     OFFSET(nokey),          AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    {NULL},
+};
+
+DEFINE_WRITER_CLASS(default);
+
+/* lame uppercasing routine, assumes the string is lower case ASCII */
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    size_t i;
+    for (i = 0; src[i] && i < dst_size-1; i++)
+        dst[i] = av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static void default_print_section_header(WriterContext *wctx)
+{
+    DefaultContext *def = wctx->priv;
+    char buf[32];
+    const struct section *section = wctx->section[wctx->level];
+    const struct section *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    av_bprint_clear(&wctx->section_pbuf[wctx->level]);
+    if (parent_section &&
+        !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) {
+        def->nested_section[wctx->level] = 1;
+        av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:",
+                   wctx->section_pbuf[wctx->level-1].str,
+                   upcase_string(buf, sizeof(buf),
+                                 av_x_if_null(section->element_name, section->name)));
+    }
+
+    if (def->noprint_wrappers || def->nested_section[wctx->level])
+        return;
+
+    if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
+        av_bprintf(&wctx->bpBuf, "[%s]\n", upcase_string(buf, sizeof(buf), section->name));
+}
+
+static void default_print_section_footer(WriterContext *wctx)
+{
+    DefaultContext *def = wctx->priv;
+    const struct section *section = wctx->section[wctx->level];
+    char buf[32];
+
+    if (def->noprint_wrappers || def->nested_section[wctx->level])
+        return;
+
+    if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
+        av_bprintf(&wctx->bpBuf, "[/%s]\n", upcase_string(buf, sizeof(buf), section->name));
+}
+
+static void default_print_str(WriterContext *wctx, const char *key, const char *value)
+{
+    DefaultContext *def = wctx->priv;
+
+    if (!def->nokey)
+        av_bprintf(&wctx->bpBuf, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+    av_bprintf(&wctx->bpBuf, "%s\n", value);
+}
+
+static void default_print_int(WriterContext *wctx, const char *key, long long int value)
+{
+    DefaultContext *def = wctx->priv;
+
+    if (!def->nokey)
+        av_bprintf(&wctx->bpBuf, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+    av_bprintf(&wctx->bpBuf, "%lld\n", value);
+}
+
+static const Writer default_writer = {
+    .name                  = "default",
+    .priv_size             = sizeof(DefaultContext),
+    .print_section_header  = default_print_section_header,
+    .print_section_footer  = default_print_section_footer,
+    .print_integer         = default_print_int,
+    .print_string          = default_print_str,
+    .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS,
+    .priv_class            = &default_class,
+};
+
+/* JSON output */
+
+typedef struct JSONContext {
+    const AVClass *class;
+    int indent_level;
+    int compact;
+    const char *item_sep, *item_start_end;
+} JSONContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(JSONContext, x)
+
+static const AVOption json_options[]= {
+    { "compact", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "c",       "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { NULL }
+};
+
+DEFINE_WRITER_CLASS(json);
+
+static av_cold int json_init(WriterContext *wctx)
+{
+    JSONContext *json = wctx->priv;
+
+    json->item_sep       = json->compact ? ", " : ",\n";
+    json->item_start_end = json->compact ? " "  : "\n";
+
+    return 0;
+}
+
+static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx)
+{
+    static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0};
+    static const char json_subst[]  = {'"', '\\',  'b',  'f',  'n',  'r',  't', 0};
+    const char *p;
+
+    for (p = src; *p; p++) {
+        char *s = strchr(json_escape, *p);
+        if (s) {
+            av_bprint_chars(dst, '\\', 1);
+            av_bprint_chars(dst, json_subst[s - json_escape], 1);
+        } else if ((unsigned char)*p < 32) {
+            av_bprintf(dst, "\\u00%02x", *p & 0xff);
+        } else {
+            av_bprint_chars(dst, *p, 1);
+        }
+    }
+    return dst->str;
+}
+
+#define JSON_INDENT() av_bprintf(&wctx->bpBuf, "%*c", json->indent_level * 4, ' ')
+
+static void json_print_section_header(WriterContext *wctx)
+{
+    JSONContext *json = wctx->priv;
+    AVBPrint buf;
+    const struct section *section = wctx->section[wctx->level];
+    const struct section *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    if (wctx->level && wctx->nb_item[wctx->level-1])
+        av_bprintf(&wctx->bpBuf, ",\n");
+
+    if (section->flags & SECTION_FLAG_IS_WRAPPER) {
+        av_bprintf(&wctx->bpBuf, "{\n");
+        json->indent_level++;
+    } else {
+        av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+        json_escape_str(&buf, section->name, wctx);
+        JSON_INDENT();
+
+        json->indent_level++;
+        if (section->flags & SECTION_FLAG_IS_ARRAY) {
+            av_bprintf(&wctx->bpBuf, "\"%s\": [\n", buf.str);
+        } else if (parent_section && !(parent_section->flags & SECTION_FLAG_IS_ARRAY)) {
+            av_bprintf(&wctx->bpBuf, "\"%s\": {%s", buf.str, json->item_start_end);
+        } else {
+            av_bprintf(&wctx->bpBuf, "{%s", json->item_start_end);
+        }
+        av_bprint_finalize(&buf, NULL);
+    }
+}
+
+static void json_print_section_footer(WriterContext *wctx)
+{
+    JSONContext *json = wctx->priv;
+    const struct section *section = wctx->section[wctx->level];
+
+    if (wctx->level == 0) {
+        json->indent_level--;
+        av_bprintf(&wctx->bpBuf, "\n}\n");
+    } else if (section->flags & SECTION_FLAG_IS_ARRAY) {
+        av_bprintf(&wctx->bpBuf, "\n");
+        json->indent_level--;
+        JSON_INDENT();
+        av_bprintf(&wctx->bpBuf, "]");
+    } else {
+        av_bprintf(&wctx->bpBuf, "%s", json->item_start_end);
+        json->indent_level--;
+        if (!json->compact)
+            JSON_INDENT();
+        av_bprintf(&wctx->bpBuf, "}");
+    }
+}
+
+static inline void json_print_item_str(WriterContext *wctx,
+                                       const char *key, const char *value)
+{
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&wctx->bpBuf, "\"%s\":", json_escape_str(&buf, key,   wctx));
+    av_bprint_clear(&buf);
+    av_bprintf(&wctx->bpBuf, " \"%s\"", json_escape_str(&buf, value, wctx));
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void json_print_str(WriterContext *wctx, const char *key, const char *value)
+{
+    JSONContext *json = wctx->priv;
+
+    if (wctx->nb_item[wctx->level])
+        av_bprintf(&wctx->bpBuf, "%s", json->item_sep);
+    if (!json->compact)
+        JSON_INDENT();
+    json_print_item_str(wctx, key, value);
+}
+
+static void json_print_int(WriterContext *wctx, const char *key, long long int value)
+{
+    JSONContext *json = wctx->priv;
+    AVBPrint buf;
+
+    if (wctx->nb_item[wctx->level])
+        av_bprintf(&wctx->bpBuf, "%s", json->item_sep);
+    if (!json->compact)
+        JSON_INDENT();
+
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&wctx->bpBuf, "\"%s\": %lld", json_escape_str(&buf, key, wctx), value);
+    av_bprint_finalize(&buf, NULL);
+}
+
+static const Writer json_writer = {
+    .name                 = "json",
+    .priv_size            = sizeof(JSONContext),
+    .init                 = json_init,
+    .print_section_header = json_print_section_header,
+    .print_section_footer = json_print_section_footer,
+    .print_integer        = json_print_int,
+    .print_string         = json_print_str,
+    .flags = WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
+    .priv_class           = &json_class,
+};
+
+static void print_hwdevicecontext(WriterContext *w, AVHWDeviceContext *hw_device_context)
+{
+    writer_print_section_header(w, SECTION_ID_HWDEViCECONTEXT);
+
+    print_int("HasHwDeviceContext", 1);
+    print_str("DeviceType", av_hwdevice_get_type_name(hw_device_context->type));
+
+    writer_print_section_footer(w); // SECTION_ID_HWDEViCECONTEXT
+}
+
+static void print_hwframescontext(WriterContext *w, AVHWFramesContext *hw_frames_context)
+{
+    const AVPixFmtDescriptor* pixdescHw;
+    const AVPixFmtDescriptor* pixdescSw;
+
+    writer_print_section_header(w, SECTION_ID_HWFRAMESCONTEXT);
+
+    print_int("HasHwFramesContext", 1);
+
+    pixdescHw = av_pix_fmt_desc_get(hw_frames_context->format);
+    if (pixdescHw != NULL) {
+        print_str("HwPixelFormat", pixdescHw->name);
+        print_str("HwPixelFormatAlias", pixdescHw->alias);
+    }
+
+    pixdescSw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
+    if (pixdescSw != NULL) {
+        print_str("SwPixelFormat", pixdescSw->name);
+        print_str("SwPixelFormatAlias", pixdescSw->alias);
+    }
+
+    print_int("Width", hw_frames_context->width);
+    print_int("Height", hw_frames_context->height);
+
+    print_hwdevicecontext(w, hw_frames_context->device_ctx);
+
+    writer_print_section_footer(w); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(WriterContext *w, AVFilterLink *link)
+{
+    char layoutString[64];
+
+    switch (link->type) {
+        case AVMEDIA_TYPE_VIDEO:
+            print_str("Format",  av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
+            print_int("Width", link->w);
+            print_int("Height", link->h);
+            print_q("SAR", link->sample_aspect_ratio, ':');
+            print_q("TimeBase", link->time_base, '/');
+            break;
+
+        ////case AVMEDIA_TYPE_SUBTITLE:
+        ////    print_str("Format",  av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
+        ////    print_int("Width", link->w);
+        ////    print_int("Height", link->h);
+        ////    print_q("TimeBase", link->time_base, '/');
+        ////    break;
+
+        case AVMEDIA_TYPE_AUDIO:
+            av_channel_layout_describe(&link->ch_layout, layoutString, sizeof(layoutString));
+            print_str("ChannelString", layoutString);
+            print_int("Channels", link->ch_layout.nb_channels);
+            ////print_int("ChannelLayout", link->ch_layout);
+            print_int("SampleRate", link->sample_rate);
+            break;
+    }
+
+    FilterLink* plink = ff_filter_link(link);
+
+    if (plink->hw_frames_ctx != NULL && plink->hw_frames_ctx->buffer != NULL)
+        print_hwframescontext(w, (AVHWFramesContext*)plink->hw_frames_ctx->data);
+}
+
+static void print_filter(WriterContext *w, AVFilterContext* filter)
+{
+    writer_print_section_header(w, SECTION_ID_FILTER);
+
+    print_str("Name", filter->name);
+
+    if (filter->filter != NULL) {
+        print_str("Name2", filter->filter->name);
+        print_str("Description", filter->filter->description);
+    }
+
+    if (filter->hw_device_ctx != NULL) {
+        AVHWDeviceContext* decCtx = (AVHWDeviceContext*)filter->hw_device_ctx->data;
+        print_hwdevicecontext(w, decCtx);
+    }
+
+    writer_print_section_header(w, SECTION_ID_INPUTS);
+
+    for (unsigned i = 0; i < filter->nb_inputs; i++) {
+        AVFilterLink *link = filter->inputs[i];
+        writer_print_section_header(w, SECTION_ID_INPUT);
+
+        print_str("SourceName", link->src->name);
+        print_str("SourcePadName", link->srcpad->name);
+        print_str("DestPadName", link->dstpad->name);
+
+        print_link(w, link);
+
+        writer_print_section_footer(w); // SECTION_ID_INPUT
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_INPUTS
+
+    // --------------------------------------------------
+
+    writer_print_section_header(w, SECTION_ID_OUTPUTS);
+
+    for (unsigned i = 0; i < filter->nb_outputs; i++) {
+        AVFilterLink *link = filter->outputs[i];
+        writer_print_section_header(w, SECTION_ID_OUTPUT);
+
+        print_str("DestName", link->dst->name);
+        print_str("DestPadName", link->dstpad->name);
+        print_str("SourceName", link->src->name);
+
+        print_link(w, link);
+
+        writer_print_section_footer(w); // SECTION_ID_OUTPUT
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_OUTPUTS
+
+    writer_print_section_footer(w); // SECTION_ID_FILTER
+}
+
+static void print_filtergraph_single(WriterContext *w, FilterGraph* fg)
+{
+    char layoutString[64];
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVFilterGraph* graph = NULL;
+
+    print_int("GraphIndex", fg->index);
+    print_str("Description", fgp->graph_desc);
+
+    writer_print_section_header(w, SECTION_ID_INPUTS);
+
+    for (int i = 0; i < fg->nb_inputs; i++) {
+        InputFilterPriv* ifilter = ifp_from_ifilter(fg->inputs[i]);
+        enum AVMediaType mediaType = ifilter->type;
+
+        writer_print_section_header(w, SECTION_ID_INPUT);
+
+        print_str("Name1", (char*)ifilter->ifilter.name);
+
+        if (ifilter->filter != NULL) {
+            print_str("Name2", ifilter->filter->name);
+            print_str("Name3", ifilter->filter->filter->name);
+            print_str("Description", ifilter->filter->filter->description);
+        }
+
+        print_str("MediaType", av_get_media_type_string(mediaType));
+        print_int("MediaTypeId", mediaType);
+
+        switch (ifilter->type) {
+        case AVMEDIA_TYPE_VIDEO:
+        case AVMEDIA_TYPE_SUBTITLE:
+            print_str("Format",  av_x_if_null(av_get_pix_fmt_name(ifilter->format), "?"));
+            print_int("Width", ifilter->width);
+            print_int("Height", ifilter->height);
+            print_q("SAR", ifilter->sample_aspect_ratio, ':');
+            break;
+        case AVMEDIA_TYPE_AUDIO:
+
+            av_channel_layout_describe(&ifilter->ch_layout, layoutString, sizeof(layoutString));
+            print_str("ChannelString", layoutString);
+            ////print_int("Channels", ifilter->channels);
+            ////print_int("ChannelLayout", ifilter->channel_layout);
+            print_int("SampleRate", ifilter->sample_rate);
+            break;
+        case AVMEDIA_TYPE_ATTACHMENT:
+        case AVMEDIA_TYPE_DATA:
+            break;
+        }
+
+        if (ifilter->hw_frames_ctx != NULL)
+            print_hwframescontext(w, (AVHWFramesContext*)ifilter->hw_frames_ctx->data);
+        else if (ifilter->filter != NULL && ifilter->filter->hw_device_ctx != NULL) {
+            AVHWDeviceContext* devCtx = (AVHWDeviceContext*)ifilter->filter->hw_device_ctx->data;
+            print_hwdevicecontext(w, devCtx);
+        }
+
+        writer_print_section_footer(w); // SECTION_ID_INPUT
+
+        if (!graph && ifilter->filter != NULL && ifilter->filter->nb_outputs > 0) {
+            FilterLink* link = ff_filter_link(ifilter->filter->outputs[0]);
+            graph = link->graph;
+        }
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_INPUTS
+
+
+    writer_print_section_header(w, SECTION_ID_OUTPUTS);
+
+    for (int i = 0; i < fg->nb_outputs; i++) {
+        OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
+        enum AVMediaType mediaType = AVMEDIA_TYPE_UNKNOWN;
+
+        writer_print_section_header(w, SECTION_ID_OUTPUT);
+        print_str("Name1", ofilter->name);
+
+        if (ofilter->filter != NULL) {
+            print_str("Name2", ofilter->filter->name);
+            print_str("Name3", ofilter->filter->filter->name);
+            print_str("Description", ofilter->filter->filter->description);
+
+            if (ofilter->filter->nb_inputs > 0)
+                mediaType = ofilter->filter->inputs[0]->type;
+        }
+
+        print_str("MediaType", av_get_media_type_string(mediaType));
+        print_int("MediaTypeId", mediaType);
+
+        switch (ofilter->ofilter.type) {
+        case AVMEDIA_TYPE_VIDEO:
+        case AVMEDIA_TYPE_SUBTITLE:
+            print_str("Format",  av_x_if_null(av_get_pix_fmt_name(ofilter->format), "?"));
+            print_int("Width", ofilter->width);
+            print_int("Height", ofilter->height);
+            break;
+        case AVMEDIA_TYPE_AUDIO:
+
+            av_channel_layout_describe(&ofilter->ch_layout, layoutString, sizeof(layoutString));
+            print_str("ChannelString", layoutString);
+            ////print_int("Channels", ofilter->channels);
+            ////print_int("ChannelLayout", ofilter->channel_layout);
+            print_int("SampleRate", ofilter->sample_rate);
+            break;
+        case AVMEDIA_TYPE_ATTACHMENT:
+        case AVMEDIA_TYPE_DATA:
+            break;
+        }
+
+        if (ofilter->filter != NULL && ofilter->filter->hw_device_ctx != NULL) {
+            AVHWDeviceContext* devCtx = (AVHWDeviceContext*)ofilter->filter->hw_device_ctx->data;
+            print_hwdevicecontext(w, devCtx);
+        }
+
+        writer_print_section_footer(w); // SECTION_ID_OUTPUT
+
+        if (!graph && ofilter->filter != NULL && ofilter->filter->nb_inputs > 0) {
+            FilterLink* link = ff_filter_link(ofilter->filter->inputs[0]);
+            graph = link->graph;
+        }
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_OUTPUTS
+
+
+    writer_print_section_header(w, SECTION_ID_FILTERS);
+
+    if (graph != NULL) {
+        for (unsigned i = 0; i < graph->nb_filters; i++) {
+            AVFilterContext *filter = graph->filters[i];
+            writer_print_section_header(w, SECTION_ID_FILTER);
+
+            print_filter(w, filter);
+
+            writer_print_section_footer(w); // SECTION_ID_FILTER
+        }
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_FILTERS
+}
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+    const Writer *writer;
+    WriterContext *w;
+    char *buf, *w_name, *w_args;
+    int ret;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVBPrint *targetBuf = &fgp->graph_print_buf;
+
+    writer_register_all();
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("default");
+    if (!print_graphs_format) {
+        return AVERROR(ENOMEM);
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &buf);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        return AVERROR(EINVAL);
+    }
+    w_args = buf;
+
+    writer = writer_get_by_name(w_name);
+    if (writer == NULL) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph  output format with name '%s'\n", w_name);
+        return AVERROR(EINVAL);
+    }
+
+    if (targetBuf->len) {
+        av_bprint_finalize(targetBuf, NULL);
+    }
+
+    if ((ret = writer_open(&w, writer, w_args, sections, FF_ARRAY_ELEMS(sections))) >= 0) {
+        writer_print_section_header(w, SECTION_ID_ROOT);
+        writer_print_section_header(w, SECTION_ID_FILTERGRAPHS);
+        writer_print_section_header(w, SECTION_ID_FILTERGRAPH);
+
+        av_bprint_clear(&w->bpBuf);
+
+        print_filtergraph_single(w, fg);
+
+        av_bprint_finalize(&w->bpBuf, &targetBuf->str);
+        targetBuf->len = w->bpBuf.len;
+        targetBuf->size = w->bpBuf.len + 1;
+
+        writer_close(&w);
+    } else
+        return ret;
+
+    return 0;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, OutputFile **ofiles, int nb_ofiles)
+{
+    const Writer *writer;
+    WriterContext *w;
+    char *buf, *w_name, *w_args;
+    int ret;
+
+    writer_register_all();
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("default");
+    if (!print_graphs_format) {
+        return AVERROR(ENOMEM);
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &buf);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        return AVERROR(EINVAL);
+    }
+    w_args = buf;
+
+    writer = writer_get_by_name(w_name);
+    if (writer == NULL) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph  output format with name '%s'\n", w_name);
+        return AVERROR(EINVAL);
+    }
+
+    if ((ret = writer_open(&w, writer, w_args, sections, FF_ARRAY_ELEMS(sections))) >= 0) {
+        writer_print_section_header(w, SECTION_ID_ROOT);
+
+        writer_print_section_header(w, SECTION_ID_FILTERGRAPHS);
+
+        for (int i = 0; i < nb_graphs; i++) {
+
+            FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
+            AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+            if (graph_buf->len > 0) {
+                writer_print_section_header(w, SECTION_ID_FILTERGRAPH);
+
+                av_bprint_append_data(&w->bpBuf, graph_buf->str, graph_buf->len);
+                av_bprint_finalize(graph_buf, NULL);
+
+                writer_print_section_footer(w); // SECTION_ID_FILTERGRAPH
+            }
+       }
+
+        for (int n = 0; n < nb_ofiles; n++) {
+
+            OutputFile *of = ofiles[n];
+
+            for (int i = 0; i < of->nb_streams; i++) {
+
+                OutputStream *ost = of->streams[i];
+
+                if (ost->fg_simple) {
+
+                    FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
+                    AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+                    if (graph_buf->len > 0) {
+                        writer_print_section_header(w, SECTION_ID_FILTERGRAPH);
+
+                        av_bprint_append_data(&w->bpBuf, graph_buf->str,
+                                            graph_buf->len);
+                        av_bprint_finalize(graph_buf, NULL);
+
+                        writer_print_section_footer(w); // SECTION_ID_FILTERGRAPH
+                    }
+                }
+            }
+        }
+
+        writer_print_section_footer(w); // SECTION_ID_FILTERGRAPHS
+
+        writer_print_section_footer(w); // SECTION_ID_ROOT
+
+        if (print_graphs_file) {
+
+            AVIOContext *avio = NULL;
+
+            ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n",
+                       print_graphs_file, av_err2str(ret));
+                return ret;
+            }
+
+            avio_write(avio, (const unsigned char*)w->bpBuf.str, FFMIN(w->bpBuf.len, w->bpBuf.size - 1));
+            avio_flush(avio);
+
+            if ((ret = avio_closep(&avio)) < 0)
+                av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
+        }
+
+        if (print_graphs)
+            av_log(NULL, AV_LOG_INFO, "%s    %c", w->bpBuf.str, '\n');
+
+        writer_close(&w);
+    }
+
+    return 0;
+}
+
+void writer_register_all(void)
+{
+    static int initialized;
+
+    if (initialized)
+        return;
+    initialized = 1;
+
+    writer_register(&default_writer);
+    //writer_register(&compact_writer);
+    //writer_register(&csv_writer);
+    //writer_register(&flat_writer);
+    //writer_register(&ini_writer);
+    writer_register(&json_writer);
+    //writer_register(&xml_writer);
+}
+
+void write_error(WriterContext *w, int err)
+{
+    char errbuf[128];
+    const char *errbuf_ptr = errbuf;
+
+    if (av_strerror(err, errbuf, sizeof(errbuf)) < 0)
+        errbuf_ptr = strerror(AVUNERROR(err));
+
+    writer_print_section_header(w, SECTION_ID_ERROR);
+    print_int("Number", err);
+    print_str("Message", errbuf_ptr);
+    writer_print_section_footer(w);
+}
+
+void write_error_msg(WriterContext *w, int err, const char *msg)
+{
+    writer_print_section_header(w, SECTION_ID_ERROR);
+    print_int("Number", err);
+    print_str("Message", msg);
+    writer_print_section_footer(w);
+}
+
+void write_error_fmt(WriterContext *w, int err, const char *fmt,...)
+{
+    AVBPrint pbuf;
+    va_list vl;
+    va_start(vl, fmt);
+
+    writer_print_section_header(w, SECTION_ID_ERROR);
+    print_int("Number", err);
+
+    av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
+
+    av_bprint_clear(&pbuf);
+
+    av_vbprintf(&pbuf, fmt, vl);
+    va_end(vl);
+
+    print_str("Message", pbuf.str);
+
+    av_bprint_finalize(&pbuf, NULL);
+
+    writer_print_section_footer(w);
+}
+
diff --git a/fftools/ffmpeg_graphprint.h b/fftools/ffmpeg_graphprint.h
new file mode 100644
index 0000000000..5fa4a2585b
--- /dev/null
+++ b/fftools/ffmpeg_graphprint.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2018 - softworkz
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef FFTOOLS_FFMPEG_GRAPHPRINT_H
+#define FFTOOLS_FFMPEG_GRAPHPRINT_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+
+#define SECTION_MAX_NB_CHILDREN 11
+#define PRINT_STRING_OPT      1
+#define PRINT_STRING_VALIDATE 2
+#define MAX_REGISTERED_WRITERS_NB 64
+
+#ifndef GUID_DEFINED
+#define GUID_DEFINED
+typedef struct _GUID {
+    uint32_t Data1;
+    uint16_t Data2;
+    uint16_t Data3;
+    uint8_t  Data4[ 8 ];
+} GUID;
+
+#define IsEqualGUID(rguid1, rguid2) (!memcmp(rguid1, rguid2, sizeof(GUID)))
+
+#endif
+
+struct section {
+    int id;             ///< unique id identifying a section
+    const char *name;
+
+#define SECTION_FLAG_IS_WRAPPER      1 ///< the section only contains other sections, but has no data at its own level
+#define SECTION_FLAG_IS_ARRAY        2 ///< the section contains an array of elements of the same type
+#define SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys.
+                                           ///  For these sections the element_name field is mandatory.
+    int flags;
+    int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
+    const char *element_name; ///< name of the contained element, if provided
+    const char *unique_name;  ///< unique section name, in case the name is ambiguous
+};
+
+typedef enum {
+    SECTION_ID_NONE = -1,
+    SECTION_ID_ROOT,
+    SECTION_ID_PROGRAM_VERSION,
+    SECTION_ID_FILTERGRAPHS,
+    SECTION_ID_FILTERGRAPH,
+    SECTION_ID_INPUTS,
+    SECTION_ID_INPUT,
+    SECTION_ID_OUTPUTS,
+    SECTION_ID_OUTPUT,
+    SECTION_ID_FILTERS,
+    SECTION_ID_FILTER,
+    SECTION_ID_HWDEViCECONTEXT,
+    SECTION_ID_HWFRAMESCONTEXT,
+    SECTION_ID_ERROR,
+    SECTION_ID_LOG,
+    SECTION_ID_LOGS,
+
+} SectionID;
+
+static const struct section sections[] = {
+    [SECTION_ID_ROOT] =               { SECTION_ID_ROOT, "GraphDescription", SECTION_FLAG_IS_WRAPPER,
+                                      { SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_FILTERGRAPHS, SECTION_ID_LOGS, -1} },
+    [SECTION_ID_PROGRAM_VERSION] =    { SECTION_ID_PROGRAM_VERSION, "ProgramVersion", 0, { -1 } },
+
+    [SECTION_ID_FILTERGRAPHS] =       { SECTION_ID_FILTERGRAPHS, "Graphs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
+    [SECTION_ID_FILTERGRAPH] =        { SECTION_ID_FILTERGRAPH, "Graph", 0, { SECTION_ID_INPUTS, SECTION_ID_OUTPUTS, SECTION_ID_FILTERS, -1 },  },
+
+    [SECTION_ID_INPUTS] =             { SECTION_ID_INPUTS, "Inputs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_INPUT, SECTION_ID_ERROR, -1 } },
+    [SECTION_ID_INPUT] =              { SECTION_ID_INPUT, "Input", 0, { SECTION_ID_HWFRAMESCONTEXT, SECTION_ID_ERROR, -1 },  },
+
+    [SECTION_ID_OUTPUTS] =            { SECTION_ID_OUTPUTS, "Outputs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_OUTPUT, SECTION_ID_ERROR, -1 } },
+    [SECTION_ID_OUTPUT] =             { SECTION_ID_OUTPUT, "Output", 0, { SECTION_ID_HWFRAMESCONTEXT, SECTION_ID_ERROR, -1 },  },
+
+    [SECTION_ID_FILTERS] =            { SECTION_ID_FILTERS, "Filters", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER, SECTION_ID_ERROR, -1 } },
+    [SECTION_ID_FILTER] =             { SECTION_ID_FILTER, "Filter", 0, { SECTION_ID_HWDEViCECONTEXT, SECTION_ID_ERROR, -1 },  },
+
+    [SECTION_ID_HWDEViCECONTEXT] =    { SECTION_ID_HWDEViCECONTEXT, "HwDeviceContext", 0, { SECTION_ID_ERROR, -1 },  },
+    [SECTION_ID_HWFRAMESCONTEXT] =    { SECTION_ID_HWFRAMESCONTEXT, "HwFramesContext", 0, { SECTION_ID_ERROR, -1 },  },
+
+    [SECTION_ID_ERROR] =              { SECTION_ID_ERROR, "Error", 0, { -1 } },
+    [SECTION_ID_LOGS] =               { SECTION_ID_LOGS, "Log", SECTION_FLAG_IS_ARRAY, { SECTION_ID_LOG, -1 } },
+    [SECTION_ID_LOG] =                { SECTION_ID_LOG, "LogEntry", 0, { -1 },  },
+};
+
+struct unit_value {
+    union { double d; long long int i; } val;
+    const char *unit;
+};
+
+static const char unit_second_str[]         = "s"    ;
+static const char unit_hertz_str[]          = "Hz"   ;
+static const char unit_byte_str[]           = "byte" ;
+static const char unit_bit_per_second_str[] = "bit/s";
+
+/* WRITERS API */
+
+typedef struct WriterContext WriterContext;
+
+#define WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS 1
+#define WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER 2
+
+typedef enum {
+    WRITER_STRING_VALIDATION_FAIL,
+    WRITER_STRING_VALIDATION_REPLACE,
+    WRITER_STRING_VALIDATION_IGNORE,
+    WRITER_STRING_VALIDATION_NB
+} StringValidation;
+
+typedef struct Writer {
+    const AVClass *priv_class;      ///< private class of the writer, if any
+    int priv_size;                  ///< private size for the writer context
+    const char *name;
+
+    int  (*init)  (WriterContext *wctx);
+    void (*uninit)(WriterContext *wctx);
+
+    void (*print_section_header)(WriterContext *wctx);
+    void (*print_section_footer)(WriterContext *wctx);
+    void (*print_integer)       (WriterContext *wctx, const char *, long long int);
+    void (*print_rational)      (WriterContext *wctx, AVRational *q, char *sep);
+    void (*print_string)        (WriterContext *wctx, const char *, const char *);
+    int flags;                  ///< a combination or WRITER_FLAG_*
+} Writer;
+
+#define SECTION_MAX_NB_LEVELS 10
+
+struct WriterContext {
+    const AVClass *class;           ///< class of the writer
+    const Writer *writer;           ///< the Writer of which this is an instance
+    char *name;                     ///< name of this writer instance
+    void *priv;                     ///< private data for use by the filter
+
+    const struct section *sections; ///< array containing all sections
+    int nb_sections;                ///< number of sections
+
+    int level;                      ///< current level, starting from 0
+
+    /** number of the item printed in the given section, starting from 0 */
+    unsigned int nb_item[SECTION_MAX_NB_LEVELS];
+
+    /** section per each level */
+    const struct section *section[SECTION_MAX_NB_LEVELS];
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+                                                  ///  used by various writers
+    AVBPrint bpBuf;
+    unsigned int nb_section_packet; ///< number of the packet section in case we are in "packets_and_frames" section
+    unsigned int nb_section_frame;  ///< number of the frame  section in case we are in "packets_and_frames" section
+    unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame according if is_packets_and_frames
+
+    int string_validation;
+    char *string_validation_replacement;
+    unsigned int string_validation_utf8_flags;
+};
+
+#define print_fmt(k, f, ...) do {              \
+    av_bprint_clear(&pbuf);                    \
+    av_bprintf(&pbuf, f, __VA_ARGS__);         \
+    writer_print_string(w, k, pbuf.str, 0);    \
+} while (0)
+
+#define print_int(k, v)         writer_print_integer(w, k, v)
+#define print_q(k, v, s)        writer_print_rational(w, k, v, s)
+#define print_guid(k, v)        writer_print_guid(w, k, v)
+#define print_str(k, v)         writer_print_string(w, k, v, 0)
+#define print_str_opt(k, v)     writer_print_string(w, k, v, PRINT_STRING_OPT)
+#define print_str_validate(k, v) writer_print_string(w, k, v, PRINT_STRING_VALIDATE)
+#define print_time(k, v, tb)    writer_print_time(w, k, v, tb, 0)
+#define print_ts(k, v)          writer_print_ts(w, k, v, 0)
+#define print_duration_time(k, v, tb) writer_print_time(w, k, v, tb, 1)
+#define print_duration_ts(k, v)       writer_print_ts(w, k, v, 1)
+#define print_val(k, v, u) do {                                     \
+    struct unit_value uv;                                           \
+    uv.val.i = v;                                                   \
+    uv.unit = u;                                                    \
+    writer_print_string(w, k, value_string(val_str, sizeof(val_str), uv), 0); \
+} while (0)
+
+void writer_register_all(void);
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, OutputFile **output_files, int nb_output_files);
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+const Writer *writer_get_by_name(const char *name);
+
+int writer_open(WriterContext **wctx, const Writer *writer, const char *args,
+                       const struct section *sections, int nb_sections);
+
+void writer_print_section_header(WriterContext *wctx, int section_id);
+void writer_print_section_footer(WriterContext *wctx);
+
+int writer_print_string(WriterContext *wctx, const char *key, const char *val, int flags);
+void writer_print_integer(WriterContext *wctx, const char *key, long long int val);
+void writer_print_rational(WriterContext *wctx, const char *key, AVRational q, char sep);
+void writer_print_guid(WriterContext *wctx, const char *key, GUID *guid);
+void writer_print_ts(WriterContext *wctx, const char *key, int64_t ts, int is_duration);
+
+void writer_close(WriterContext **wctx);
+void write_error(WriterContext *w, int err);
+void write_error_msg(WriterContext *w, int err, const char *msg);
+void write_error_fmt(WriterContext *w, int err, const char *fmt,...);
+
+#endif /* FFTOOLS_FFMPEG_GRAPHPRINT_H */
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3c0c682594..8530ff6a66 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -75,6 +75,9 @@ float max_error_rate  = 2.0/3;
 char *filter_nbthreads;
 int filter_complex_nbthreads = 0;
 int vstats_version = 2;
+int print_graphs = 0;
+char* print_graphs_file = NULL;
+char* print_graphs_format = NULL;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1730,6 +1733,15 @@ const OptionDef options[] = {
         { .func_arg = opt_filter_complex_script },
         "deprecated, use -/filter_complex instead", "filename" },
 #endif
+    { "print_graphs",   OPT_TYPE_BOOL, 0,
+        { &print_graphs },
+        "prints filtergraph details to stderr" },
+    { "print_graphs_file", OPT_TYPE_STRING, 0,
+        { &print_graphs_file },
+        "writes graph details to a file", "filename" },
+    { "print_graphs_format", OPT_TYPE_STRING, 0,
+        { &print_graphs_format },
+      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml)", "format" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
-- 
ffmpeg-codebot

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH 3/3] fftools: Enable filtergraph printing and update docs
  2025-02-19  9:59 [FFmpeg-devel] [PATCH 0/3] print_graphs: Complete Filtergraph Printing ffmpegagent
  2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 1/3] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
  2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
@ 2025-02-19  9:59 ` softworkz
  2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing ffmpegagent
  3 siblings, 0 replies; 15+ messages in thread
From: softworkz @ 2025-02-19  9:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Enables filtergraph printing and adds the options to the docs

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi         | 10 ++++++++++
 fftools/ffmpeg.c        |  4 ++++
 fftools/ffmpeg_filter.c |  5 +++++
 3 files changed, 19 insertions(+)

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index da6549f043..6398b4ed92 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1388,6 +1388,16 @@ It is on by default, to explicitly 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 -print_graphs (@emph{global})
+Prints filtergraph details to stderr in the format set via -print_graphs_format.
+
+@item -print_graphs_file @var{filename} (@emph{global})
+Writes filtergraph details to the specified file in the format set via -print_graphs_format.
+
+@item -print_graphs_format @var{format} (@emph{global})
+Sets the output printing format (available formats are: default, compact, csv, flat, ini, json, xml)
+The default format is json.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..b4bc76d788 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -81,6 +81,7 @@
 #include "ffmpeg.h"
 #include "ffmpeg_sched.h"
 #include "ffmpeg_utils.h"
+#include "ffmpeg_graphprint.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
+    if (print_graphs || print_graphs_file)
+        print_filtergraphs(filtergraphs, nb_filtergraphs, output_files, nb_output_files);
+
     if (do_benchmark) {
         int64_t maxrss = getmaxrss() / 1024;
         av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 6de4e87ade..7198416ae9 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -22,6 +22,7 @@
 
 #include "ffmpeg.h"
 #include "ffmpeg_filter.h"
+#include "ffmpeg_graphprint.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -2970,6 +2971,10 @@ read_frames:
     }
 
 finish:
+
+    if (print_graphs || print_graphs_file)
+        print_filtergraph(fg, fgt.graph);
+
     // EOF is normal termination
     if (ret == AVERROR_EOF)
         ret = 0;
-- 
ffmpeg-codebot
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
  2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
@ 2025-02-21  9:22   ` Andreas Rheinhardt
  2025-02-21  9:42     ` Soft Works
  2025-02-21 13:09   ` Nicolas George
  1 sibling, 1 reply; 15+ messages in thread
From: Andreas Rheinhardt @ 2025-02-21  9:22 UTC (permalink / raw)
  To: ffmpeg-devel

softworkz:
> From: softworkz <softworkz@hotmail.com>
> 
> The key benefits are:
> 
> - Different to other graph printing methods, this is outputting:
>   - all graphs with runtime state
>     (including auto-inserted filters)
>   - each graph with its inputs and outputs
>   - all filters with their in- and output pads
>   - all connections between all input- and output pads
>   - for each connection:
>     - the runtime-negotiated format and media type
>     - the hw context
>     - if video hw context, both: hw pixfmt + sw pixfmt
> - Output can either be printed to stdout or written to specified file
> - Output is machine-readable
> - Use the same output implementation as ffprobe, supporting multiple
>   formats
> 
> Note: This commit includes only the default and JSON writers.
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/Makefile            |    1 +
>  fftools/ffmpeg.h            |    3 +
>  fftools/ffmpeg_graphprint.c | 1152 +++++++++++++++++++++++++++++++++++
>  fftools/ffmpeg_graphprint.h |  224 +++++++
>  fftools/ffmpeg_opt.c        |   12 +
>  5 files changed, 1392 insertions(+)
>  create mode 100644 fftools/ffmpeg_graphprint.c
>  create mode 100644 fftools/ffmpeg_graphprint.h
> 
> diff --git a/fftools/Makefile b/fftools/Makefile
> index 4499799818..189feb4e2a 100644
> --- a/fftools/Makefile
> +++ b/fftools/Makefile
> @@ -19,6 +19,7 @@ OBJS-ffmpeg +=                  \
>      fftools/ffmpeg_mux_init.o   \
>      fftools/ffmpeg_opt.o        \
>      fftools/ffmpeg_sched.o      \
> +    fftools/ffmpeg_graphprint.o \
>      fftools/sync_queue.o        \
>      fftools/thread_queue.o      \
>  
> diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
> index 6cc0da05a0..432954b4cc 100644
> --- a/fftools/ffmpeg.h
> +++ b/fftools/ffmpeg.h
> @@ -714,6 +714,9 @@ extern float max_error_rate;
>  extern char *filter_nbthreads;
>  extern int filter_complex_nbthreads;
>  extern int vstats_version;
> +extern int print_graphs;
> +extern char* print_graphs_file;
> +extern char* print_graphs_format;
>  extern int auto_conversion_filters;
>  
>  extern const AVIOInterruptCB int_cb;
> diff --git a/fftools/ffmpeg_graphprint.c b/fftools/ffmpeg_graphprint.c
> new file mode 100644
> index 0000000000..77f143b8c2
> --- /dev/null
> +++ b/fftools/ffmpeg_graphprint.c
> @@ -0,0 +1,1152 @@
> +/*
> + * Copyright (c) 2018 - softworkz
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> + * @file
> + * output writers for filtergraph details
> + */
> +
> +#include "config.h"
> +
> +#include <string.h>
> +
> +#include "ffmpeg_graphprint.h"
> +#include "ffmpeg_filter.h"
> +
> +#include "libavutil/avassert.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/pixdesc.h"
> +#include "libavutil/dict.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/common.h"
> +#include "libavfilter/avfilter.h"
> +#include "libavfilter/filters.h"

That's an internal header which must not be used by fftools.

> +#include "libavutil/buffer.h"
> +#include "libavutil/hwcontext.h"
> +
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
  2025-02-21  9:22   ` Andreas Rheinhardt
@ 2025-02-21  9:42     ` Soft Works
  2025-02-21 11:11       ` Andreas Rheinhardt
  0 siblings, 1 reply; 15+ messages in thread
From: Soft Works @ 2025-02-21  9:42 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Andreas Rheinhardt
> Sent: Freitag, 21. Februar 2025 10:23
> To: ffmpeg-devel@ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add
> options for filtergraph printing
> 
> softworkz:
> > From: softworkz <softworkz@hotmail.com>
> >
> > The key benefits are:
> >
> > - Different to other graph printing methods, this is outputting:
> >   - all graphs with runtime state
> >     (including auto-inserted filters)
> >   - each graph with its inputs and outputs
> >   - all filters with their in- and output pads
> >   - all connections between all input- and output pads
> >   - for each connection:
> >     - the runtime-negotiated format and media type
> >     - the hw context
> >     - if video hw context, both: hw pixfmt + sw pixfmt
> > - Output can either be printed to stdout or written to specified file
> > - Output is machine-readable
> > - Use the same output implementation as ffprobe, supporting multiple
> >   formats
> >
> > Note: This commit includes only the default and JSON writers.
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  fftools/Makefile            |    1 +
> >  fftools/ffmpeg.h            |    3 +
> >  fftools/ffmpeg_graphprint.c | 1152
> +++++++++++++++++++++++++++++++++++
> >  fftools/ffmpeg_graphprint.h |  224 +++++++
> >  fftools/ffmpeg_opt.c        |   12 +
> >  5 files changed, 1392 insertions(+)
> >  create mode 100644 fftools/ffmpeg_graphprint.c
> >  create mode 100644 fftools/ffmpeg_graphprint.h
> >
> > diff --git a/fftools/Makefile b/fftools/Makefile
> > index 4499799818..189feb4e2a 100644
> > --- a/fftools/Makefile
> > +++ b/fftools/Makefile
> > @@ -19,6 +19,7 @@ OBJS-ffmpeg +=                  \
> >      fftools/ffmpeg_mux_init.o   \
> >      fftools/ffmpeg_opt.o        \
> >      fftools/ffmpeg_sched.o      \
> > +    fftools/ffmpeg_graphprint.o \
> >      fftools/sync_queue.o        \
> >      fftools/thread_queue.o      \
> >
> > diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
> > index 6cc0da05a0..432954b4cc 100644
> > --- a/fftools/ffmpeg.h
> > +++ b/fftools/ffmpeg.h
> > @@ -714,6 +714,9 @@ extern float max_error_rate;
> >  extern char *filter_nbthreads;
> >  extern int filter_complex_nbthreads;
> >  extern int vstats_version;
> > +extern int print_graphs;
> > +extern char* print_graphs_file;
> > +extern char* print_graphs_format;
> >  extern int auto_conversion_filters;
> >
> >  extern const AVIOInterruptCB int_cb;
> > diff --git a/fftools/ffmpeg_graphprint.c b/fftools/ffmpeg_graphprint.c
> > new file mode 100644
> > index 0000000000..77f143b8c2
> > --- /dev/null
> > +++ b/fftools/ffmpeg_graphprint.c
> > @@ -0,0 +1,1152 @@
> > +/*
> > + * Copyright (c) 2018 - softworkz
> > + *
> > + * This file is part of FFmpeg.
> > + *
> > + * FFmpeg is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU Lesser General Public
> > + * License as published by the Free Software Foundation; either
> > + * version 2.1 of the License, or (at your option) any later version.
> > + *
> > + * FFmpeg is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> GNU
> > + * Lesser General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU Lesser General Public
> > + * License along with FFmpeg; if not, write to the Free Software
> > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
> USA
> > + */
> > +
> > +/**
> > + * @file
> > + * output writers for filtergraph details
> > + */
> > +
> > +#include "config.h"
> > +
> > +#include <string.h>
> > +
> > +#include "ffmpeg_graphprint.h"
> > +#include "ffmpeg_filter.h"
> > +
> > +#include "libavutil/avassert.h"
> > +#include "libavutil/avstring.h"
> > +#include "libavutil/opt.h"
> > +#include "libavutil/pixdesc.h"
> > +#include "libavutil/dict.h"
> > +#include "libavutil/intreadwrite.h"
> > +#include "libavutil/common.h"
> > +#include "libavfilter/avfilter.h"
> > +#include "libavfilter/filters.h"
> 
> That's an internal header which must not be used by fftools.

Thanks Andreas,

I know, but isn't fftools itself "internal"?

What's the alternative? I could move AVFilterPad to avfilter.h.
It's prefixed with 'AV', so isn't it meant to be public anyway?

And then there's the hw_frames_ctx does it make sense to 
move it to AVFilterLink? Or rather add a function to access it?

Thanks,
sw 
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
  2025-02-21  9:42     ` Soft Works
@ 2025-02-21 11:11       ` Andreas Rheinhardt
  2025-02-21 11:25         ` Soft Works
  0 siblings, 1 reply; 15+ messages in thread
From: Andreas Rheinhardt @ 2025-02-21 11:11 UTC (permalink / raw)
  To: ffmpeg-devel

Soft Works:
> 
> 
>> -----Original Message-----
>> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
>> Andreas Rheinhardt
>> Sent: Freitag, 21. Februar 2025 10:23
>> To: ffmpeg-devel@ffmpeg.org
>> Subject: Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add
>> options for filtergraph printing
>>
>> softworkz:
>>> From: softworkz <softworkz@hotmail.com>
>>>
>>> The key benefits are:
>>>
>>> - Different to other graph printing methods, this is outputting:
>>>   - all graphs with runtime state
>>>     (including auto-inserted filters)
>>>   - each graph with its inputs and outputs
>>>   - all filters with their in- and output pads
>>>   - all connections between all input- and output pads
>>>   - for each connection:
>>>     - the runtime-negotiated format and media type
>>>     - the hw context
>>>     - if video hw context, both: hw pixfmt + sw pixfmt
>>> - Output can either be printed to stdout or written to specified file
>>> - Output is machine-readable
>>> - Use the same output implementation as ffprobe, supporting multiple
>>>   formats
>>>
>>> Note: This commit includes only the default and JSON writers.
>>>
>>> Signed-off-by: softworkz <softworkz@hotmail.com>
>>> ---
>>>  fftools/Makefile            |    1 +
>>>  fftools/ffmpeg.h            |    3 +
>>>  fftools/ffmpeg_graphprint.c | 1152
>> +++++++++++++++++++++++++++++++++++
>>>  fftools/ffmpeg_graphprint.h |  224 +++++++
>>>  fftools/ffmpeg_opt.c        |   12 +
>>>  5 files changed, 1392 insertions(+)
>>>  create mode 100644 fftools/ffmpeg_graphprint.c
>>>  create mode 100644 fftools/ffmpeg_graphprint.h
>>>
>>> diff --git a/fftools/Makefile b/fftools/Makefile
>>> index 4499799818..189feb4e2a 100644
>>> --- a/fftools/Makefile
>>> +++ b/fftools/Makefile
>>> @@ -19,6 +19,7 @@ OBJS-ffmpeg +=                  \
>>>      fftools/ffmpeg_mux_init.o   \
>>>      fftools/ffmpeg_opt.o        \
>>>      fftools/ffmpeg_sched.o      \
>>> +    fftools/ffmpeg_graphprint.o \
>>>      fftools/sync_queue.o        \
>>>      fftools/thread_queue.o      \
>>>
>>> diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
>>> index 6cc0da05a0..432954b4cc 100644
>>> --- a/fftools/ffmpeg.h
>>> +++ b/fftools/ffmpeg.h
>>> @@ -714,6 +714,9 @@ extern float max_error_rate;
>>>  extern char *filter_nbthreads;
>>>  extern int filter_complex_nbthreads;
>>>  extern int vstats_version;
>>> +extern int print_graphs;
>>> +extern char* print_graphs_file;
>>> +extern char* print_graphs_format;
>>>  extern int auto_conversion_filters;
>>>
>>>  extern const AVIOInterruptCB int_cb;
>>> diff --git a/fftools/ffmpeg_graphprint.c b/fftools/ffmpeg_graphprint.c
>>> new file mode 100644
>>> index 0000000000..77f143b8c2
>>> --- /dev/null
>>> +++ b/fftools/ffmpeg_graphprint.c
>>> @@ -0,0 +1,1152 @@
>>> +/*
>>> + * Copyright (c) 2018 - softworkz
>>> + *
>>> + * This file is part of FFmpeg.
>>> + *
>>> + * FFmpeg is free software; you can redistribute it and/or
>>> + * modify it under the terms of the GNU Lesser General Public
>>> + * License as published by the Free Software Foundation; either
>>> + * version 2.1 of the License, or (at your option) any later version.
>>> + *
>>> + * FFmpeg is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> GNU
>>> + * Lesser General Public License for more details.
>>> + *
>>> + * You should have received a copy of the GNU Lesser General Public
>>> + * License along with FFmpeg; if not, write to the Free Software
>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
>> USA
>>> + */
>>> +
>>> +/**
>>> + * @file
>>> + * output writers for filtergraph details
>>> + */
>>> +
>>> +#include "config.h"
>>> +
>>> +#include <string.h>
>>> +
>>> +#include "ffmpeg_graphprint.h"
>>> +#include "ffmpeg_filter.h"
>>> +
>>> +#include "libavutil/avassert.h"
>>> +#include "libavutil/avstring.h"
>>> +#include "libavutil/opt.h"
>>> +#include "libavutil/pixdesc.h"
>>> +#include "libavutil/dict.h"
>>> +#include "libavutil/intreadwrite.h"
>>> +#include "libavutil/common.h"
>>> +#include "libavfilter/avfilter.h"
>>> +#include "libavfilter/filters.h"
>>
>> That's an internal header which must not be used by fftools.
> 
> Thanks Andreas,
> 
> I know, but isn't fftools itself "internal"?

fftools is just an ordinary user of the libraries; it is not special in
any way. filters.h is internal to libavfilter and must not be used
anywhere else.

> 
> What's the alternative? I could move AVFilterPad to avfilter.h.
> It's prefixed with 'AV', so isn't it meant to be public anyway?
> 
> And then there's the hw_frames_ctx does it make sense to 
> move it to AVFilterLink? Or rather add a function to access it?
> 
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
  2025-02-21 11:11       ` Andreas Rheinhardt
@ 2025-02-21 11:25         ` Soft Works
  0 siblings, 0 replies; 15+ messages in thread
From: Soft Works @ 2025-02-21 11:25 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Andreas Rheinhardt
> Sent: Freitag, 21. Februar 2025 12:11
> To: ffmpeg-devel@ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add
> options for filtergraph printing
> 
> Soft Works:
> >
> >
> >> -----Original Message-----
> >> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> >> Andreas Rheinhardt
> >> Sent: Freitag, 21. Februar 2025 10:23
> >> To: ffmpeg-devel@ffmpeg.org
> >> Subject: Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add
> >> options for filtergraph printing
> >>
> >> softworkz:
> >>> From: softworkz <softworkz@hotmail.com>
> >>>
> >>> The key benefits are:
> >>>
> >>> - Different to other graph printing methods, this is outputting:
> >>>   - all graphs with runtime state
> >>>     (including auto-inserted filters)
> >>>   - each graph with its inputs and outputs
> >>>   - all filters with their in- and output pads
> >>>   - all connections between all input- and output pads
> >>>   - for each connection:
> >>>     - the runtime-negotiated format and media type
> >>>     - the hw context
> >>>     - if video hw context, both: hw pixfmt + sw pixfmt
> >>> - Output can either be printed to stdout or written to specified file
> >>> - Output is machine-readable
> >>> - Use the same output implementation as ffprobe, supporting multiple
> >>>   formats
> >>>
> >>> Note: This commit includes only the default and JSON writers.
> >>>
> >>> Signed-off-by: softworkz <softworkz@hotmail.com>
> >>> ---
> >>>  fftools/Makefile            |    1 +
> >>>  fftools/ffmpeg.h            |    3 +
> >>>  fftools/ffmpeg_graphprint.c | 1152
> >> +++++++++++++++++++++++++++++++++++
> >>>  fftools/ffmpeg_graphprint.h |  224 +++++++
> >>>  fftools/ffmpeg_opt.c        |   12 +
> >>>  5 files changed, 1392 insertions(+)
> >>>  create mode 100644 fftools/ffmpeg_graphprint.c
> >>>  create mode 100644 fftools/ffmpeg_graphprint.h
> >>>
> >>> diff --git a/fftools/Makefile b/fftools/Makefile
> >>> index 4499799818..189feb4e2a 100644
> >>> --- a/fftools/Makefile
> >>> +++ b/fftools/Makefile
> >>> @@ -19,6 +19,7 @@ OBJS-ffmpeg +=                  \
> >>>      fftools/ffmpeg_mux_init.o   \
> >>>      fftools/ffmpeg_opt.o        \
> >>>      fftools/ffmpeg_sched.o      \
> >>> +    fftools/ffmpeg_graphprint.o \
> >>>      fftools/sync_queue.o        \
> >>>      fftools/thread_queue.o      \
> >>>
> >>> diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
> >>> index 6cc0da05a0..432954b4cc 100644
> >>> --- a/fftools/ffmpeg.h
> >>> +++ b/fftools/ffmpeg.h
> >>> @@ -714,6 +714,9 @@ extern float max_error_rate;
> >>>  extern char *filter_nbthreads;
> >>>  extern int filter_complex_nbthreads;
> >>>  extern int vstats_version;
> >>> +extern int print_graphs;
> >>> +extern char* print_graphs_file;
> >>> +extern char* print_graphs_format;
> >>>  extern int auto_conversion_filters;
> >>>
> >>>  extern const AVIOInterruptCB int_cb;
> >>> diff --git a/fftools/ffmpeg_graphprint.c b/fftools/ffmpeg_graphprint.c
> >>> new file mode 100644
> >>> index 0000000000..77f143b8c2
> >>> --- /dev/null
> >>> +++ b/fftools/ffmpeg_graphprint.c
> >>> @@ -0,0 +1,1152 @@
> >>> +/*
> >>> + * Copyright (c) 2018 - softworkz
> >>> + *
> >>> + * This file is part of FFmpeg.
> >>> + *
> >>> + * FFmpeg is free software; you can redistribute it and/or
> >>> + * modify it under the terms of the GNU Lesser General Public
> >>> + * License as published by the Free Software Foundation; either
> >>> + * version 2.1 of the License, or (at your option) any later version.
> >>> + *
> >>> + * FFmpeg is distributed in the hope that it will be useful,
> >>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> >>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> >> GNU
> >>> + * Lesser General Public License for more details.
> >>> + *
> >>> + * You should have received a copy of the GNU Lesser General Public
> >>> + * License along with FFmpeg; if not, write to the Free Software
> >>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-
> 1301
> >> USA
> >>> + */
> >>> +
> >>> +/**
> >>> + * @file
> >>> + * output writers for filtergraph details
> >>> + */
> >>> +
> >>> +#include "config.h"
> >>> +
> >>> +#include <string.h>
> >>> +
> >>> +#include "ffmpeg_graphprint.h"
> >>> +#include "ffmpeg_filter.h"
> >>> +
> >>> +#include "libavutil/avassert.h"
> >>> +#include "libavutil/avstring.h"
> >>> +#include "libavutil/opt.h"
> >>> +#include "libavutil/pixdesc.h"
> >>> +#include "libavutil/dict.h"
> >>> +#include "libavutil/intreadwrite.h"
> >>> +#include "libavutil/common.h"
> >>> +#include "libavfilter/avfilter.h"
> >>> +#include "libavfilter/filters.h"
> >>
> >> That's an internal header which must not be used by fftools.
> >
> > Thanks Andreas,
> >
> > I know, but isn't fftools itself "internal"?
> 
> fftools is just an ordinary user of the libraries; it is not special in
> any way. filters.h is internal to libavfilter and must not be used
> anywhere else.

Ok, thanks. I think I found a way around.

sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing
  2025-02-19  9:59 [FFmpeg-devel] [PATCH 0/3] print_graphs: Complete Filtergraph Printing ffmpegagent
                   ` (2 preceding siblings ...)
  2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 3/3] fftools: Enable filtergraph printing and update docs softworkz
@ 2025-02-21 11:27 ` ffmpegagent
  2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 1/4] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
                     ` (3 more replies)
  3 siblings, 4 replies; 15+ messages in thread
From: ffmpegagent @ 2025-02-21 11:27 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Soft Works, softworkz, Andreas Rheinhardt

The key benefits are:

 * Different to other graph printing methods, this is outputting:
   * all graphs with runtime state
     (including auto-inserted filters)
   * each graph with its inputs and outputs
   * all filters with their in- and output pads
   * all connections between all input- and output pads
   * for each connection:
     * the runtime-negotiated format and media type
     * the hw context
     * if video hw context, both: hw pixfmt + sw pixfmt
 * Output can either be printed to stdout or written to specified file
 * Output is machine-readable
 * Use the same output implementation as ffprobe, supporting multiple
   formats
   (this commit includes only the default and JSON writers)

Right after filtergraph configuration, the connection details are often not
complete yet. On the other side, when waiting too long or if an error occurs
somewhere, the graph info might never be printed. Experience has shown, that
the most suitable and realiable point in time for printing graph information
is right before cleanup.

Due to the changes for multi-threading, this is no longer doable as easy as
before, so the following method is used: Each filtergraph initiates its own
graph printing short before cleanup into a buffer. Before final cleanup in
ffmpeg.c, the outputs from the individual graphs are pieced together for the
actual output to file or console. (the structure according to the output
format remains valid)

Example output:
https://gist.github.com/softworkz/2a9e8699b288f5d40fa381c2a496e165

Update V2

 * Change NULL checks to match common code style
 * Add Add avfilter_link_get_hw_frames_ctx() function to avoid importing
   filters.h
 * Do the same without including avfilter/filters.h (as per note from
   Andreas Reinhardt)

softworkz (4):
  fftools/ffmpeg_filter: Move some declaration to new header file
  avfilter/avfilter Add avfilter_link_get_hw_frames_ctx()
  fftools/ffmpeg_graphprint: Add options for filtergraph printing
  fftools: Enable filtergraph printing and update docs

 doc/APIchanges              |    3 +
 doc/ffmpeg.texi             |   10 +
 fftools/Makefile            |    1 +
 fftools/ffmpeg.c            |    4 +
 fftools/ffmpeg.h            |    3 +
 fftools/ffmpeg_filter.c     |  193 +-----
 fftools/ffmpeg_filter.h     |  232 +++++++
 fftools/ffmpeg_graphprint.c | 1141 +++++++++++++++++++++++++++++++++++
 fftools/ffmpeg_graphprint.h |  224 +++++++
 fftools/ffmpeg_opt.c        |   12 +
 libavfilter/avfilter.c      |    9 +
 libavfilter/avfilter.h      |   12 +
 libavfilter/version.h       |    2 +-
 13 files changed, 1658 insertions(+), 188 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h
 create mode 100644 fftools/ffmpeg_graphprint.c
 create mode 100644 fftools/ffmpeg_graphprint.h


base-commit: e18f87ed9f9f61c980420b315dc8ecb308831bc5
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-52%2Fsoftworkz%2Fsubmit_print_graphs5-v2
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-52/softworkz/submit_print_graphs5-v2
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/52

Range-diff vs v1:

 1:  c843eb0741 = 1:  c843eb0741 fftools/ffmpeg_filter: Move some declaration to new header file
 -:  ---------- > 2:  5a7b45ab09 avfilter/avfilter Add avfilter_link_get_hw_frames_ctx()
 2:  0750b971f9 ! 3:  1f87cfae77 fftools/ffmpeg_graphprint: Add options for filtergraph printing
     @@ fftools/ffmpeg_graphprint.c (new)
      +#include "libavutil/intreadwrite.h"
      +#include "libavutil/common.h"
      +#include "libavfilter/avfilter.h"
     -+#include "libavfilter/filters.h"
      +#include "libavutil/buffer.h"
      +#include "libavutil/hwcontext.h"
      +
     @@ fftools/ffmpeg_graphprint.c (new)
      +    .priv_class           = &json_class,
      +};
      +
     -+static void print_hwdevicecontext(WriterContext *w, AVHWDeviceContext *hw_device_context)
     ++static void print_hwdevicecontext(WriterContext *w, const AVHWDeviceContext *hw_device_context)
      +{
      +    writer_print_section_header(w, SECTION_ID_HWDEViCECONTEXT);
      +
     @@ fftools/ffmpeg_graphprint.c (new)
      +    writer_print_section_footer(w); // SECTION_ID_HWDEViCECONTEXT
      +}
      +
     -+static void print_hwframescontext(WriterContext *w, AVHWFramesContext *hw_frames_context)
     ++static void print_hwframescontext(WriterContext *w, const AVHWFramesContext *hw_frames_context)
      +{
      +    const AVPixFmtDescriptor* pixdescHw;
      +    const AVPixFmtDescriptor* pixdescSw;
     @@ fftools/ffmpeg_graphprint.c (new)
      +    print_int("HasHwFramesContext", 1);
      +
      +    pixdescHw = av_pix_fmt_desc_get(hw_frames_context->format);
     -+    if (pixdescHw != NULL) {
     ++    if (pixdescHw) {
      +        print_str("HwPixelFormat", pixdescHw->name);
      +        print_str("HwPixelFormatAlias", pixdescHw->alias);
      +    }
      +
      +    pixdescSw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
     -+    if (pixdescSw != NULL) {
     ++    if (pixdescSw) {
      +        print_str("SwPixelFormat", pixdescSw->name);
      +        print_str("SwPixelFormatAlias", pixdescSw->alias);
      +    }
     @@ fftools/ffmpeg_graphprint.c (new)
      +            break;
      +    }
      +
     -+    FilterLink* plink = ff_filter_link(link);
     ++    AVBufferRef *hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
      +
     -+    if (plink->hw_frames_ctx != NULL && plink->hw_frames_ctx->buffer != NULL)
     -+        print_hwframescontext(w, (AVHWFramesContext*)plink->hw_frames_ctx->data);
     ++    if (hw_frames_ctx && hw_frames_ctx->buffer) {
     ++      print_hwframescontext(w, (AVHWFramesContext *)hw_frames_ctx->data);
     ++    }
      +}
      +
     -+static void print_filter(WriterContext *w, AVFilterContext* filter)
     ++static void print_filter(WriterContext *w, const AVFilterContext* filter)
      +{
      +    writer_print_section_header(w, SECTION_ID_FILTER);
      +
      +    print_str("Name", filter->name);
      +
     -+    if (filter->filter != NULL) {
     ++    if (filter->filter) {
      +        print_str("Name2", filter->filter->name);
      +        print_str("Description", filter->filter->description);
      +    }
      +
     -+    if (filter->hw_device_ctx != NULL) {
     ++    if (filter->hw_device_ctx) {
      +        AVHWDeviceContext* decCtx = (AVHWDeviceContext*)filter->hw_device_ctx->data;
      +        print_hwdevicecontext(w, decCtx);
      +    }
     @@ fftools/ffmpeg_graphprint.c (new)
      +        writer_print_section_header(w, SECTION_ID_INPUT);
      +
      +        print_str("SourceName", link->src->name);
     -+        print_str("SourcePadName", link->srcpad->name);
     -+        print_str("DestPadName", link->dstpad->name);
     ++        print_str("SourcePadName", avfilter_pad_get_name(link->srcpad, 0));
     ++        print_str("DestPadName", avfilter_pad_get_name(link->dstpad, 0));
      +
      +        print_link(w, link);
      +
     @@ fftools/ffmpeg_graphprint.c (new)
      +        writer_print_section_header(w, SECTION_ID_OUTPUT);
      +
      +        print_str("DestName", link->dst->name);
     -+        print_str("DestPadName", link->dstpad->name);
     ++        print_str("DestPadName", avfilter_pad_get_name(link->dstpad, 0));
      +        print_str("SourceName", link->src->name);
      +
      +        print_link(w, link);
     @@ fftools/ffmpeg_graphprint.c (new)
      +    writer_print_section_footer(w); // SECTION_ID_FILTER
      +}
      +
     -+static void print_filtergraph_single(WriterContext *w, FilterGraph* fg)
     ++static void print_filtergraph_single(WriterContext *w, FilterGraph* fg, AVFilterGraph *graph)
      +{
      +    char layoutString[64];
      +    FilterGraphPriv *fgp = fgp_from_fg(fg);
     -+    AVFilterGraph* graph = NULL;
      +
      +    print_int("GraphIndex", fg->index);
      +    print_str("Description", fgp->graph_desc);
     @@ fftools/ffmpeg_graphprint.c (new)
      +
      +        print_str("Name1", (char*)ifilter->ifilter.name);
      +
     -+        if (ifilter->filter != NULL) {
     ++        if (ifilter->filter) {
      +            print_str("Name2", ifilter->filter->name);
      +            print_str("Name3", ifilter->filter->filter->name);
      +            print_str("Description", ifilter->filter->filter->description);
     @@ fftools/ffmpeg_graphprint.c (new)
      +            break;
      +        }
      +
     -+        if (ifilter->hw_frames_ctx != NULL)
     ++        if (ifilter->hw_frames_ctx)
      +            print_hwframescontext(w, (AVHWFramesContext*)ifilter->hw_frames_ctx->data);
     -+        else if (ifilter->filter != NULL && ifilter->filter->hw_device_ctx != NULL) {
     ++        else if (ifilter->filter && ifilter->filter->hw_device_ctx) {
      +            AVHWDeviceContext* devCtx = (AVHWDeviceContext*)ifilter->filter->hw_device_ctx->data;
      +            print_hwdevicecontext(w, devCtx);
      +        }
      +
      +        writer_print_section_footer(w); // SECTION_ID_INPUT
     -+
     -+        if (!graph && ifilter->filter != NULL && ifilter->filter->nb_outputs > 0) {
     -+            FilterLink* link = ff_filter_link(ifilter->filter->outputs[0]);
     -+            graph = link->graph;
     -+        }
      +    }
      +
      +    writer_print_section_footer(w); // SECTION_ID_INPUTS
     @@ fftools/ffmpeg_graphprint.c (new)
      +        writer_print_section_header(w, SECTION_ID_OUTPUT);
      +        print_str("Name1", ofilter->name);
      +
     -+        if (ofilter->filter != NULL) {
     ++        if (ofilter->filter) {
      +            print_str("Name2", ofilter->filter->name);
      +            print_str("Name3", ofilter->filter->filter->name);
      +            print_str("Description", ofilter->filter->filter->description);
     @@ fftools/ffmpeg_graphprint.c (new)
      +            break;
      +        }
      +
     -+        if (ofilter->filter != NULL && ofilter->filter->hw_device_ctx != NULL) {
     ++        if (ofilter->filter && ofilter->filter->hw_device_ctx) {
      +            AVHWDeviceContext* devCtx = (AVHWDeviceContext*)ofilter->filter->hw_device_ctx->data;
      +            print_hwdevicecontext(w, devCtx);
      +        }
      +
      +        writer_print_section_footer(w); // SECTION_ID_OUTPUT
     -+
     -+        if (!graph && ofilter->filter != NULL && ofilter->filter->nb_inputs > 0) {
     -+            FilterLink* link = ff_filter_link(ofilter->filter->inputs[0]);
     -+            graph = link->graph;
     -+        }
      +    }
      +
      +    writer_print_section_footer(w); // SECTION_ID_OUTPUTS
     @@ fftools/ffmpeg_graphprint.c (new)
      +
      +    writer_print_section_header(w, SECTION_ID_FILTERS);
      +
     -+    if (graph != NULL) {
     ++    if (graph) {
      +        for (unsigned i = 0; i < graph->nb_filters; i++) {
      +            AVFilterContext *filter = graph->filters[i];
      +            writer_print_section_header(w, SECTION_ID_FILTER);
     @@ fftools/ffmpeg_graphprint.c (new)
      +
      +        av_bprint_clear(&w->bpBuf);
      +
     -+        print_filtergraph_single(w, fg);
     ++        print_filtergraph_single(w, fg, graph);
      +
      +        av_bprint_finalize(&w->bpBuf, &targetBuf->str);
      +        targetBuf->len = w->bpBuf.len;
 3:  7eea20993c = 4:  1bf975fa0e fftools: Enable filtergraph printing and update docs

-- 
ffmpeg-codebot
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 1/4] fftools/ffmpeg_filter: Move some declaration to new header file
  2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing ffmpegagent
@ 2025-02-21 11:27   ` softworkz
  2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 2/4] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() softworkz
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 15+ messages in thread
From: softworkz @ 2025-02-21 11:27 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Soft Works, softworkz, Andreas Rheinhardt

From: softworkz <softworkz@hotmail.com>

to allow print_graph to access the information.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_filter.c | 188 +-------------------------------
 fftools/ffmpeg_filter.h | 232 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 233 insertions(+), 187 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 800e2a3f06..6de4e87ade 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ffmpeg.h"
+#include "ffmpeg_filter.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -42,44 +43,6 @@
 // FIXME private header, used for mid_pred()
 #include "libavcodec/mathops.h"
 
-typedef struct FilterGraphPriv {
-    FilterGraph      fg;
-
-    // name used for logging
-    char             log_name[32];
-
-    int              is_simple;
-    // true when the filtergraph contains only meta filters
-    // that do not modify the frame data
-    int              is_meta;
-    // source filters are present in the graph
-    int              have_sources;
-    int              disable_conversions;
-
-    unsigned         nb_outputs_done;
-
-    const char      *graph_desc;
-
-    int              nb_threads;
-
-    // frame for temporarily holding output from the filtergraph
-    AVFrame         *frame;
-    // frame for sending output to the encoder
-    AVFrame         *frame_enc;
-
-    Scheduler       *sch;
-    unsigned         sch_idx;
-} FilterGraphPriv;
-
-static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
-{
-    return (FilterGraphPriv*)fg;
-}
-
-static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
-{
-    return (const FilterGraphPriv*)fg;
-}
 
 // data that is local to the filter thread and not visible outside of it
 typedef struct FilterGraphThread {
@@ -102,155 +65,6 @@ typedef struct FilterGraphThread {
     uint8_t         *eof_out;
 } FilterGraphThread;
 
-typedef struct InputFilterPriv {
-    InputFilter         ifilter;
-
-    InputFilterOptions  opts;
-
-    int                 index;
-
-    AVFilterContext    *filter;
-
-    // used to hold submitted input
-    AVFrame            *frame;
-
-    /* for filters that are not yet bound to an input stream,
-     * this stores the input linklabel, if any */
-    uint8_t            *linklabel;
-
-    // filter data type
-    enum AVMediaType    type;
-    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
-    // same as type otherwise
-    enum AVMediaType    type_src;
-
-    int                 eof;
-    int                 bound;
-
-    // parameters configured for this input
-    int                 format;
-
-    int                 width, height;
-    AVRational          sample_aspect_ratio;
-    enum AVColorSpace   color_space;
-    enum AVColorRange   color_range;
-
-    int                 sample_rate;
-    AVChannelLayout     ch_layout;
-
-    AVRational          time_base;
-
-    AVFrameSideData   **side_data;
-    int                 nb_side_data;
-
-    AVFifo             *frame_queue;
-
-    AVBufferRef        *hw_frames_ctx;
-
-    int                 displaymatrix_present;
-    int                 displaymatrix_applied;
-    int32_t             displaymatrix[9];
-
-    int                 downmixinfo_present;
-    AVDownmixInfo       downmixinfo;
-
-    struct {
-        AVFrame *frame;
-
-        int64_t last_pts;
-        int64_t end_pts;
-
-        ///< marks if sub2video_update should force an initialization
-        unsigned int initialize;
-    } sub2video;
-} InputFilterPriv;
-
-static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
-{
-    return (InputFilterPriv*)ifilter;
-}
-
-typedef struct FPSConvContext {
-    AVFrame          *last_frame;
-    /* number of frames emitted by the video-encoding sync code */
-    int64_t           frame_number;
-    /* history of nb_frames_prev, i.e. the number of times the
-     * previous frame was duplicated by vsync code in recent
-     * do_video_out() calls */
-    int64_t           frames_prev_hist[3];
-
-    uint64_t          dup_warning;
-
-    int               last_dropped;
-    int               dropped_keyframe;
-
-    enum VideoSyncMethod vsync_method;
-
-    AVRational        framerate;
-    AVRational        framerate_max;
-    const AVRational *framerate_supported;
-    int               framerate_clip;
-} FPSConvContext;
-
-typedef struct OutputFilterPriv {
-    OutputFilter            ofilter;
-
-    int                     index;
-
-    void                   *log_parent;
-    char                    log_name[32];
-
-    char                   *name;
-
-    AVFilterContext        *filter;
-
-    /* desired output stream properties */
-    int                     format;
-    int                     width, height;
-    int                     sample_rate;
-    AVChannelLayout         ch_layout;
-    enum AVColorSpace       color_space;
-    enum AVColorRange       color_range;
-
-    AVFrameSideData       **side_data;
-    int                     nb_side_data;
-
-    // time base in which the output is sent to our downstream
-    // does not need to match the filtersink's timebase
-    AVRational              tb_out;
-    // at least one frame with the above timebase was sent
-    // to our downstream, so it cannot change anymore
-    int                     tb_out_locked;
-
-    AVRational              sample_aspect_ratio;
-
-    AVDictionary           *sws_opts;
-    AVDictionary           *swr_opts;
-
-    // those are only set if no format is specified and the encoder gives us multiple options
-    // They point directly to the relevant lists of the encoder.
-    const int              *formats;
-    const AVChannelLayout  *ch_layouts;
-    const int              *sample_rates;
-    const enum AVColorSpace *color_spaces;
-    const enum AVColorRange *color_ranges;
-
-    AVRational              enc_timebase;
-    int64_t                 trim_start_us;
-    int64_t                 trim_duration_us;
-    // offset for output timestamps, in AV_TIME_BASE_Q
-    int64_t                 ts_offset;
-    int64_t                 next_pts;
-    FPSConvContext          fps;
-
-    unsigned                flags;
-} OutputFilterPriv;
-
-static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
-{
-    return (OutputFilterPriv*)ofilter;
-}
-
 typedef struct FilterCommand {
     char *target;
     char *command;
diff --git a/fftools/ffmpeg_filter.h b/fftools/ffmpeg_filter.h
new file mode 100644
index 0000000000..628d272bcd
--- /dev/null
+++ b/fftools/ffmpeg_filter.h
@@ -0,0 +1,232 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef FFTOOLS_FFMPEG_FILTER_H
+#define FFTOOLS_FFMPEG_FILTER_H
+
+#include "ffmpeg.h"
+
+#include <stdint.h>
+
+#include "ffmpeg_sched.h"
+#include "sync_queue.h"
+
+#include "libavfilter/avfilter.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/dict.h"
+#include "libavutil/fifo.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/downmix_info.h"
+
+typedef struct FilterGraphPriv {
+    FilterGraph      fg;
+
+    // name used for logging
+    char             log_name[32];
+
+    int              is_simple;
+    // true when the filtergraph contains only meta filters
+    // that do not modify the frame data
+    int              is_meta;
+    // source filters are present in the graph
+    int              have_sources;
+    int              disable_conversions;
+
+    unsigned         nb_outputs_done;
+
+    const char      *graph_desc;
+
+    int              nb_threads;
+
+    // frame for temporarily holding output from the filtergraph
+    AVFrame         *frame;
+    // frame for sending output to the encoder
+    AVFrame         *frame_enc;
+
+    Scheduler       *sch;
+    unsigned         sch_idx;
+
+    AVBPrint graph_print_buf;
+
+} FilterGraphPriv;
+
+static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
+{
+    return (FilterGraphPriv*)fg;
+}
+
+static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
+{
+    return (const FilterGraphPriv*)fg;
+}
+
+typedef struct InputFilterPriv {
+    InputFilter         ifilter;
+
+    InputFilterOptions  opts;
+
+    int                 index;
+
+    AVFilterContext    *filter;
+
+    // used to hold submitted input
+    AVFrame            *frame;
+
+    /* for filters that are not yet bound to an input stream,
+     * this stores the input linklabel, if any */
+    uint8_t            *linklabel;
+
+    // filter data type
+    enum AVMediaType    type;
+    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
+    // same as type otherwise
+    enum AVMediaType    type_src;
+
+    int                 eof;
+    int                 bound;
+
+    // parameters configured for this input
+    int                 format;
+
+    int                 width, height;
+    AVRational          sample_aspect_ratio;
+    enum AVColorSpace   color_space;
+    enum AVColorRange   color_range;
+
+    int                 sample_rate;
+    AVChannelLayout     ch_layout;
+
+    AVRational          time_base;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
+    AVFifo             *frame_queue;
+
+    AVBufferRef        *hw_frames_ctx;
+
+    int                 displaymatrix_present;
+    int                 displaymatrix_applied;
+    int32_t             displaymatrix[9];
+
+    int                 downmixinfo_present;
+    AVDownmixInfo       downmixinfo;
+
+    struct {
+        AVFrame *frame;
+
+        int64_t last_pts;
+        int64_t end_pts;
+
+        ///< marks if sub2video_update should force an initialization
+        unsigned int initialize;
+    } sub2video;
+} InputFilterPriv;
+
+static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
+{
+    return (InputFilterPriv*)ifilter;
+}
+
+typedef struct FPSConvContext {
+    AVFrame          *last_frame;
+    /* number of frames emitted by the video-encoding sync code */
+    int64_t           frame_number;
+    /* history of nb_frames_prev, i.e. the number of times the
+     * previous frame was duplicated by vsync code in recent
+     * do_video_out() calls */
+    int64_t           frames_prev_hist[3];
+
+    uint64_t          dup_warning;
+
+    int               last_dropped;
+    int               dropped_keyframe;
+
+    enum VideoSyncMethod vsync_method;
+
+    AVRational        framerate;
+    AVRational        framerate_max;
+    const AVRational *framerate_supported;
+    int               framerate_clip;
+} FPSConvContext;
+
+
+typedef struct OutputFilterPriv {
+    OutputFilter            ofilter;
+
+    int                     index;
+
+    void                   *log_parent;
+    char                    log_name[32];
+
+    char                   *name;
+
+    AVFilterContext        *filter;
+
+    /* desired output stream properties */
+    int                     format;
+    int                     width, height;
+    int                     sample_rate;
+    AVChannelLayout         ch_layout;
+    enum AVColorSpace       color_space;
+    enum AVColorRange       color_range;
+
+    AVFrameSideData       **side_data;
+    int                     nb_side_data;
+
+    // time base in which the output is sent to our downstream
+    // does not need to match the filtersink's timebase
+    AVRational              tb_out;
+    // at least one frame with the above timebase was sent
+    // to our downstream, so it cannot change anymore
+    int                     tb_out_locked;
+
+    AVRational              sample_aspect_ratio;
+
+    AVDictionary           *sws_opts;
+    AVDictionary           *swr_opts;
+
+    // those are only set if no format is specified and the encoder gives us multiple options
+    // They point directly to the relevant lists of the encoder.
+    const int              *formats;
+    const AVChannelLayout  *ch_layouts;
+    const int              *sample_rates;
+    const enum AVColorSpace *color_spaces;
+    const enum AVColorRange *color_ranges;
+
+    AVRational              enc_timebase;
+    int64_t                 trim_start_us;
+    int64_t                 trim_duration_us;
+    // offset for output timestamps, in AV_TIME_BASE_Q
+    int64_t                 ts_offset;
+    int64_t                 next_pts;
+    FPSConvContext          fps;
+
+    unsigned                flags;
+} OutputFilterPriv;
+
+static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
+{
+    return (OutputFilterPriv*)ofilter;
+}
+
+#endif /* FFTOOLS_FFMPEG_FILTER_H */
-- 
ffmpeg-codebot

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 2/4] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx()
  2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing ffmpegagent
  2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 1/4] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-02-21 11:27   ` softworkz
  2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 3/4] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
  2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 4/4] fftools: Enable filtergraph printing and update docs softworkz
  3 siblings, 0 replies; 15+ messages in thread
From: softworkz @ 2025-02-21 11:27 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Soft Works, softworkz, Andreas Rheinhardt

From: softworkz <softworkz@hotmail.com>

---
 doc/APIchanges         |  3 +++
 libavfilter/avfilter.c |  9 +++++++++
 libavfilter/avfilter.h | 12 ++++++++++++
 libavfilter/version.h  |  2 +-
 4 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/doc/APIchanges b/doc/APIchanges
index ac506f4b56..acdd473a67 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2024-03-07
 
 API changes, most recent first:
 
+2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
+  Add avfilter_link_get_hw_frames_ctx().
+
 2025-02-xx - xxxxxxxxxx - lavu 59.57.100 - log.h
   Add flags AV_LOG_PRINT_TIME and AV_LOG_PRINT_DATETIME.
 
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index e732556ffa..13abd7e8ad 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -1006,6 +1006,15 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx)
     return pads[pad_idx].type;
 }
 
+AVBufferRef *avfilter_link_get_hw_frames_ctx(AVFilterLink *link)
+{
+    FilterLink* plink = ff_filter_link(link);
+    if (plink->hw_frames_ctx)
+        return av_buffer_ref(plink->hw_frames_ctx);
+
+    return NULL;
+}
+
 static int default_filter_frame(AVFilterLink *link, AVFrame *frame)
 {
     return ff_filter_frame(link->dst->outputs[0], frame);
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index 4520d5f978..27c50520b3 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -96,6 +96,18 @@ const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
  */
 enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
 
+/**
+ * Get the hardware frames context of a filter link.
+ *
+ * @param link an AVFilterLink
+ *
+ * @return a ref-counted copy of the link's hw_frames_ctx if there's a hardware
+ *         frames context associated with the link or NULL otherwise.
+ *         The returned AVBufferRef needs to be released with av_buffer_unref()
+ *         when it's no longer used.
+ */
+AVBufferRef* avfilter_link_get_hw_frames_ctx(AVFilterLink *link);
+
 /**
  * Lists of formats / etc. supported by an end of a link.
  *
diff --git a/libavfilter/version.h b/libavfilter/version.h
index 77f38cb9b4..4a69d6be98 100644
--- a/libavfilter/version.h
+++ b/libavfilter/version.h
@@ -31,7 +31,7 @@
 
 #include "version_major.h"
 
-#define LIBAVFILTER_VERSION_MINOR   9
+#define LIBAVFILTER_VERSION_MINOR  10
 #define LIBAVFILTER_VERSION_MICRO 100
 
 
-- 
ffmpeg-codebot

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 3/4] fftools/ffmpeg_graphprint: Add options for filtergraph printing
  2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing ffmpegagent
  2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 1/4] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
  2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 2/4] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() softworkz
@ 2025-02-21 11:27   ` softworkz
  2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 4/4] fftools: Enable filtergraph printing and update docs softworkz
  3 siblings, 0 replies; 15+ messages in thread
From: softworkz @ 2025-02-21 11:27 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Soft Works, softworkz, Andreas Rheinhardt

From: softworkz <softworkz@hotmail.com>

The key benefits are:

- Different to other graph printing methods, this is outputting:
  - all graphs with runtime state
    (including auto-inserted filters)
  - each graph with its inputs and outputs
  - all filters with their in- and output pads
  - all connections between all input- and output pads
  - for each connection:
    - the runtime-negotiated format and media type
    - the hw context
    - if video hw context, both: hw pixfmt + sw pixfmt
- Output can either be printed to stdout or written to specified file
- Output is machine-readable
- Use the same output implementation as ffprobe, supporting multiple
  formats

Note: This commit includes only the default and JSON writers.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/Makefile            |    1 +
 fftools/ffmpeg.h            |    3 +
 fftools/ffmpeg_graphprint.c | 1141 +++++++++++++++++++++++++++++++++++
 fftools/ffmpeg_graphprint.h |  224 +++++++
 fftools/ffmpeg_opt.c        |   12 +
 5 files changed, 1381 insertions(+)
 create mode 100644 fftools/ffmpeg_graphprint.c
 create mode 100644 fftools/ffmpeg_graphprint.h

diff --git a/fftools/Makefile b/fftools/Makefile
index 4499799818..189feb4e2a 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -19,6 +19,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
+    fftools/ffmpeg_graphprint.o \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
 
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 6cc0da05a0..432954b4cc 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -714,6 +714,9 @@ extern float max_error_rate;
 extern char *filter_nbthreads;
 extern int filter_complex_nbthreads;
 extern int vstats_version;
+extern int print_graphs;
+extern char* print_graphs_file;
+extern char* print_graphs_format;
 extern int auto_conversion_filters;
 
 extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_graphprint.c b/fftools/ffmpeg_graphprint.c
new file mode 100644
index 0000000000..57bfeda5e9
--- /dev/null
+++ b/fftools/ffmpeg_graphprint.c
@@ -0,0 +1,1141 @@
+/*
+ * Copyright (c) 2018 - softworkz
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "ffmpeg_graphprint.h"
+#include "ffmpeg_filter.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+
+static const char *writer_get_name(void *p)
+{
+    WriterContext *wctx = p;
+    return wctx->writer->name;
+}
+
+#define OFFSET(x) offsetof(WriterContext, x)
+
+static const AVOption writer_options[] = {
+    { "string_validation", "set string validation mode",
+      OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" },
+    { "sv", "set string validation mode",
+      OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" },
+    { "ignore",  NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_IGNORE},  .unit = "sv" },
+    { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_REPLACE}, .unit = "sv" },
+    { "fail",    NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_FAIL},    .unit = "sv" },
+    { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}},
+    { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str="\xEF\xBF\xBD"}},
+    { NULL }
+};
+
+static void *writer_child_next(void *obj, void *prev)
+{
+    WriterContext *ctx = obj;
+    if (!prev && ctx->writer && ctx->writer->priv_class && ctx->priv)
+        return ctx->priv;
+    return NULL;
+}
+
+static const AVClass writer_class = {
+    .class_name = "Writer",
+    .item_name  = writer_get_name,
+    .option     = writer_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+    .child_next = writer_child_next,
+};
+
+void writer_close(WriterContext **wctx)
+{
+    int i;
+
+    if (!*wctx)
+        return;
+
+    if ((*wctx)->writer->uninit)
+        (*wctx)->writer->uninit(*wctx);
+    for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
+        av_bprint_finalize(&(*wctx)->section_pbuf[i], NULL);
+    av_bprint_finalize(&(*wctx)->bpBuf, NULL);
+    if ((*wctx)->writer->priv_class)
+        av_opt_free((*wctx)->priv);
+    av_freep(&((*wctx)->priv));
+    av_opt_free(*wctx);
+    av_freep(wctx);
+}
+
+static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size)
+{
+    av_bprintf(bp, "0X");
+    for (size_t i = 0; i < ubuf_size; i++)
+        av_bprintf(bp, "%02X", ubuf[i]);
+}
+
+int writer_open(WriterContext **wctx, const Writer *writer, const char *args,
+                       const struct section *sections1, int nb_sections)
+{
+    int i, ret = 0;
+
+    if (!(*wctx = av_mallocz(sizeof(WriterContext)))) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    if (!((*wctx)->priv = av_mallocz(writer->priv_size))) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    (*wctx)->class = &writer_class;
+    (*wctx)->writer = writer;
+    (*wctx)->level = -1;
+    (*wctx)->sections = sections;
+    (*wctx)->nb_sections = nb_sections;
+
+    av_opt_set_defaults(*wctx);
+
+    if (writer->priv_class) {
+        void *priv_ctx = (*wctx)->priv;
+        *((const AVClass **)priv_ctx) = writer->priv_class;
+        av_opt_set_defaults(priv_ctx);
+    }
+
+    /* convert options to dictionary */
+    if (args) {
+        AVDictionary *opts = NULL;
+        AVDictionaryEntry *opt = NULL;
+
+        if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) {
+            av_log(*wctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to writer context\n", args);
+            av_dict_free(&opts);
+            goto fail;
+        }
+
+        while ((opt = av_dict_get(opts, "", opt, AV_DICT_IGNORE_SUFFIX))) {
+            if ((ret = av_opt_set(*wctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) {
+                av_log(*wctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to writer context\n",
+                       opt->key, opt->value);
+                av_dict_free(&opts);
+                goto fail;
+            }
+        }
+
+        av_dict_free(&opts);
+    }
+
+    /* validate replace string */
+    {
+        const uint8_t *p = (*wctx)->string_validation_replacement;
+        const uint8_t *endp = p + strlen(p);
+        while (*p) {
+            const uint8_t *p0 = p;
+            int32_t code;
+            ret = av_utf8_decode(&code, &p, endp, (*wctx)->string_validation_utf8_flags);
+            if (ret < 0) {
+                AVBPrint bp;
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+                bprint_bytes(&bp, p0, p-p0),
+                    av_log(wctx, AV_LOG_ERROR,
+                           "Invalid UTF8 sequence %s found in string validation replace '%s'\n",
+                           bp.str, (*wctx)->string_validation_replacement);
+                return ret;
+            }
+        }
+    }
+
+    for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
+        av_bprint_init(&(*wctx)->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED);
+
+    av_bprint_init(&(*wctx)->bpBuf, 500000, AV_BPRINT_SIZE_UNLIMITED);
+
+    if ((*wctx)->writer->init)
+        ret = (*wctx)->writer->init(*wctx);
+    if (ret < 0)
+        goto fail;
+
+    return 0;
+
+fail:
+    writer_close(wctx);
+    return ret;
+}
+
+void writer_print_section_header(WriterContext *wctx, int section_id)
+{
+    //int parent_section_id;
+    wctx->level++;
+    av_assert0(wctx->level < SECTION_MAX_NB_LEVELS);
+    //parent_section_id = wctx->level ?
+    //    (wctx->section[wctx->level-1])->id : SECTION_ID_NONE;
+
+    wctx->nb_item[wctx->level] = 0;
+    wctx->section[wctx->level] = &wctx->sections[section_id];
+
+    if (wctx->writer->print_section_header)
+        wctx->writer->print_section_header(wctx);
+}
+
+void writer_print_section_footer(WriterContext *wctx)
+{
+    //int section_id = wctx->section[wctx->level]->id;
+    int parent_section_id = wctx->level ?
+        wctx->section[wctx->level-1]->id : SECTION_ID_NONE;
+
+    if (parent_section_id != SECTION_ID_NONE)
+        wctx->nb_item[wctx->level-1]++;
+
+    if (wctx->writer->print_section_footer)
+        wctx->writer->print_section_footer(wctx);
+    wctx->level--;
+}
+
+static inline int validate_string(WriterContext *wctx, char **dstp, const char *src)
+{
+    const uint8_t *p, *endp;
+    AVBPrint dstbuf;
+    int invalid_chars_nb = 0, ret = 0;
+
+    av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    endp = src + strlen(src);
+    for (p = (uint8_t *)src; *p;) {
+        uint32_t code;
+        int invalid = 0;
+        const uint8_t *p0 = p;
+
+        if (av_utf8_decode(&code, &p, endp, wctx->string_validation_utf8_flags) < 0) {
+            AVBPrint bp;
+            av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+            bprint_bytes(&bp, p0, p-p0);
+            av_log(wctx, AV_LOG_DEBUG,
+                   "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src);
+            invalid = 1;
+        }
+
+        if (invalid) {
+            invalid_chars_nb++;
+
+            switch (wctx->string_validation) {
+            case WRITER_STRING_VALIDATION_FAIL:
+                av_log(wctx, AV_LOG_ERROR,
+                       "Invalid UTF-8 sequence found in string '%s'\n", src);
+                ret = AVERROR_INVALIDDATA;
+                goto end;
+
+            case WRITER_STRING_VALIDATION_REPLACE:
+                av_bprintf(&dstbuf, "%s", wctx->string_validation_replacement);
+                break;
+            }
+        }
+
+        if (!invalid || wctx->string_validation == WRITER_STRING_VALIDATION_IGNORE)
+            av_bprint_append_data(&dstbuf, p0, p-p0);
+    }
+
+    if (invalid_chars_nb && wctx->string_validation == WRITER_STRING_VALIDATION_REPLACE) {
+        av_log(wctx, AV_LOG_WARNING,
+               "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n",
+               invalid_chars_nb, src, wctx->string_validation_replacement);
+    }
+
+end:
+    av_bprint_finalize(&dstbuf, dstp);
+    return ret;
+}
+
+int writer_print_string(WriterContext *wctx, const char *key, const char *val, int flags)
+{
+    const struct section *section = wctx->section[wctx->level];
+    int ret = 0;
+
+    if ((flags & PRINT_STRING_OPT)
+        && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS))
+        return 0;
+
+    if (val == NULL)
+        return 0;
+
+    if (flags & PRINT_STRING_VALIDATE) {
+        char *key1 = NULL, *val1 = NULL;
+        ret = validate_string(wctx, &key1, key);
+        if (ret < 0) goto end;
+        ret = validate_string(wctx, &val1, val);
+        if (ret < 0) goto end;
+        wctx->writer->print_string(wctx, key1, val1);
+    end:
+        if (ret < 0) {
+            av_log(wctx, AV_LOG_ERROR,
+                    "Invalid key=value string combination %s=%s in section %s\n",
+                    key, val, section->unique_name);
+        }
+        av_free(key1);
+        av_free(val1);
+    } else {
+        wctx->writer->print_string(wctx, key, val);
+    }
+
+    wctx->nb_item[wctx->level]++;
+
+    return ret;
+}
+
+void writer_print_integer(WriterContext *wctx, const char *key, long long int val)
+{
+    //const struct section *section = wctx->section[wctx->level];
+
+    wctx->writer->print_integer(wctx, key, val);
+    wctx->nb_item[wctx->level]++;
+}
+
+void writer_print_rational(WriterContext *wctx, const char *key, AVRational q, char sep)
+{
+    AVBPrint buf;
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+    av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
+    writer_print_string(wctx, key, buf.str, 0);
+}
+
+void writer_print_guid(WriterContext *wctx, const char *key, GUID *guid)
+{
+    AVBPrint buf;
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+    av_bprintf(&buf, "{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}",
+             (unsigned) guid->Data1, guid->Data2, guid->Data3,
+             guid->Data4[0], guid->Data4[1],
+             guid->Data4[2], guid->Data4[3],
+             guid->Data4[4], guid->Data4[5],
+             guid->Data4[6], guid->Data4[7]);
+
+    writer_print_string(wctx, key, buf.str, 0);
+}
+
+//writer_print_time(WriterContext *wctx, const char *key, int64_t ts, const AVRational *time_base, int is_duration)
+//{
+//    char buf[128];
+//
+//    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
+//        writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT);
+//    } else {
+//        double d = ts * av_q2d(*time_base);
+//        struct unit_value uv;
+//        uv.val.d = d;
+//        uv.unit = unit_second_str;
+//        value_string(buf, sizeof(buf), uv);
+//        writer_print_string(wctx, key, buf, 0);
+//    }
+//}
+
+void writer_print_ts(WriterContext *wctx, const char *key, int64_t ts, int is_duration)
+{
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
+        writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT);
+    } else {
+        writer_print_integer(wctx, key, ts);
+    }
+}
+
+
+static const Writer *registered_writers[MAX_REGISTERED_WRITERS_NB + 1];
+
+static int writer_register(const Writer *writer)
+{
+    static int next_registered_writer_idx = 0;
+
+    if (next_registered_writer_idx == MAX_REGISTERED_WRITERS_NB)
+        return AVERROR(ENOMEM);
+
+    registered_writers[next_registered_writer_idx++] = writer;
+    return 0;
+}
+
+const Writer *writer_get_by_name(const char *name)
+{
+    int i;
+
+    for (i = 0; registered_writers[i]; i++)
+        if (!strcmp(registered_writers[i]->name, name))
+            return registered_writers[i];
+
+    return NULL;
+}
+
+/* WRITERS */
+
+#define DEFINE_WRITER_CLASS(name)                   \
+static const char *name##_get_name(void *ctx)       \
+{                                                   \
+    return #name ;                                  \
+}                                                   \
+static const AVClass name##_class = {               \
+    .class_name = #name,                            \
+    .item_name  = name##_get_name,                  \
+    .option     = name##_options                    \
+}
+
+/* Default output */
+
+typedef struct DefaultContext {
+    const AVClass *class;
+    int nokey;
+    int noprint_wrappers;
+    int nested_section[SECTION_MAX_NB_LEVELS];
+} DefaultContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(DefaultContext, x)
+
+static const AVOption default_options[] = {
+    { "noprint_wrappers", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "nw",               "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "nokey",          "force no key printing",     OFFSET(nokey),          AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "nk",             "force no key printing",     OFFSET(nokey),          AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    {NULL},
+};
+
+DEFINE_WRITER_CLASS(default);
+
+/* lame uppercasing routine, assumes the string is lower case ASCII */
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    size_t i;
+    for (i = 0; src[i] && i < dst_size-1; i++)
+        dst[i] = av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static void default_print_section_header(WriterContext *wctx)
+{
+    DefaultContext *def = wctx->priv;
+    char buf[32];
+    const struct section *section = wctx->section[wctx->level];
+    const struct section *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    av_bprint_clear(&wctx->section_pbuf[wctx->level]);
+    if (parent_section &&
+        !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) {
+        def->nested_section[wctx->level] = 1;
+        av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:",
+                   wctx->section_pbuf[wctx->level-1].str,
+                   upcase_string(buf, sizeof(buf),
+                                 av_x_if_null(section->element_name, section->name)));
+    }
+
+    if (def->noprint_wrappers || def->nested_section[wctx->level])
+        return;
+
+    if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
+        av_bprintf(&wctx->bpBuf, "[%s]\n", upcase_string(buf, sizeof(buf), section->name));
+}
+
+static void default_print_section_footer(WriterContext *wctx)
+{
+    DefaultContext *def = wctx->priv;
+    const struct section *section = wctx->section[wctx->level];
+    char buf[32];
+
+    if (def->noprint_wrappers || def->nested_section[wctx->level])
+        return;
+
+    if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
+        av_bprintf(&wctx->bpBuf, "[/%s]\n", upcase_string(buf, sizeof(buf), section->name));
+}
+
+static void default_print_str(WriterContext *wctx, const char *key, const char *value)
+{
+    DefaultContext *def = wctx->priv;
+
+    if (!def->nokey)
+        av_bprintf(&wctx->bpBuf, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+    av_bprintf(&wctx->bpBuf, "%s\n", value);
+}
+
+static void default_print_int(WriterContext *wctx, const char *key, long long int value)
+{
+    DefaultContext *def = wctx->priv;
+
+    if (!def->nokey)
+        av_bprintf(&wctx->bpBuf, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+    av_bprintf(&wctx->bpBuf, "%lld\n", value);
+}
+
+static const Writer default_writer = {
+    .name                  = "default",
+    .priv_size             = sizeof(DefaultContext),
+    .print_section_header  = default_print_section_header,
+    .print_section_footer  = default_print_section_footer,
+    .print_integer         = default_print_int,
+    .print_string          = default_print_str,
+    .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS,
+    .priv_class            = &default_class,
+};
+
+/* JSON output */
+
+typedef struct JSONContext {
+    const AVClass *class;
+    int indent_level;
+    int compact;
+    const char *item_sep, *item_start_end;
+} JSONContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(JSONContext, x)
+
+static const AVOption json_options[]= {
+    { "compact", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "c",       "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { NULL }
+};
+
+DEFINE_WRITER_CLASS(json);
+
+static av_cold int json_init(WriterContext *wctx)
+{
+    JSONContext *json = wctx->priv;
+
+    json->item_sep       = json->compact ? ", " : ",\n";
+    json->item_start_end = json->compact ? " "  : "\n";
+
+    return 0;
+}
+
+static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx)
+{
+    static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0};
+    static const char json_subst[]  = {'"', '\\',  'b',  'f',  'n',  'r',  't', 0};
+    const char *p;
+
+    for (p = src; *p; p++) {
+        char *s = strchr(json_escape, *p);
+        if (s) {
+            av_bprint_chars(dst, '\\', 1);
+            av_bprint_chars(dst, json_subst[s - json_escape], 1);
+        } else if ((unsigned char)*p < 32) {
+            av_bprintf(dst, "\\u00%02x", *p & 0xff);
+        } else {
+            av_bprint_chars(dst, *p, 1);
+        }
+    }
+    return dst->str;
+}
+
+#define JSON_INDENT() av_bprintf(&wctx->bpBuf, "%*c", json->indent_level * 4, ' ')
+
+static void json_print_section_header(WriterContext *wctx)
+{
+    JSONContext *json = wctx->priv;
+    AVBPrint buf;
+    const struct section *section = wctx->section[wctx->level];
+    const struct section *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    if (wctx->level && wctx->nb_item[wctx->level-1])
+        av_bprintf(&wctx->bpBuf, ",\n");
+
+    if (section->flags & SECTION_FLAG_IS_WRAPPER) {
+        av_bprintf(&wctx->bpBuf, "{\n");
+        json->indent_level++;
+    } else {
+        av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+        json_escape_str(&buf, section->name, wctx);
+        JSON_INDENT();
+
+        json->indent_level++;
+        if (section->flags & SECTION_FLAG_IS_ARRAY) {
+            av_bprintf(&wctx->bpBuf, "\"%s\": [\n", buf.str);
+        } else if (parent_section && !(parent_section->flags & SECTION_FLAG_IS_ARRAY)) {
+            av_bprintf(&wctx->bpBuf, "\"%s\": {%s", buf.str, json->item_start_end);
+        } else {
+            av_bprintf(&wctx->bpBuf, "{%s", json->item_start_end);
+        }
+        av_bprint_finalize(&buf, NULL);
+    }
+}
+
+static void json_print_section_footer(WriterContext *wctx)
+{
+    JSONContext *json = wctx->priv;
+    const struct section *section = wctx->section[wctx->level];
+
+    if (wctx->level == 0) {
+        json->indent_level--;
+        av_bprintf(&wctx->bpBuf, "\n}\n");
+    } else if (section->flags & SECTION_FLAG_IS_ARRAY) {
+        av_bprintf(&wctx->bpBuf, "\n");
+        json->indent_level--;
+        JSON_INDENT();
+        av_bprintf(&wctx->bpBuf, "]");
+    } else {
+        av_bprintf(&wctx->bpBuf, "%s", json->item_start_end);
+        json->indent_level--;
+        if (!json->compact)
+            JSON_INDENT();
+        av_bprintf(&wctx->bpBuf, "}");
+    }
+}
+
+static inline void json_print_item_str(WriterContext *wctx,
+                                       const char *key, const char *value)
+{
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&wctx->bpBuf, "\"%s\":", json_escape_str(&buf, key,   wctx));
+    av_bprint_clear(&buf);
+    av_bprintf(&wctx->bpBuf, " \"%s\"", json_escape_str(&buf, value, wctx));
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void json_print_str(WriterContext *wctx, const char *key, const char *value)
+{
+    JSONContext *json = wctx->priv;
+
+    if (wctx->nb_item[wctx->level])
+        av_bprintf(&wctx->bpBuf, "%s", json->item_sep);
+    if (!json->compact)
+        JSON_INDENT();
+    json_print_item_str(wctx, key, value);
+}
+
+static void json_print_int(WriterContext *wctx, const char *key, long long int value)
+{
+    JSONContext *json = wctx->priv;
+    AVBPrint buf;
+
+    if (wctx->nb_item[wctx->level])
+        av_bprintf(&wctx->bpBuf, "%s", json->item_sep);
+    if (!json->compact)
+        JSON_INDENT();
+
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&wctx->bpBuf, "\"%s\": %lld", json_escape_str(&buf, key, wctx), value);
+    av_bprint_finalize(&buf, NULL);
+}
+
+static const Writer json_writer = {
+    .name                 = "json",
+    .priv_size            = sizeof(JSONContext),
+    .init                 = json_init,
+    .print_section_header = json_print_section_header,
+    .print_section_footer = json_print_section_footer,
+    .print_integer        = json_print_int,
+    .print_string         = json_print_str,
+    .flags = WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
+    .priv_class           = &json_class,
+};
+
+static void print_hwdevicecontext(WriterContext *w, const AVHWDeviceContext *hw_device_context)
+{
+    writer_print_section_header(w, SECTION_ID_HWDEViCECONTEXT);
+
+    print_int("HasHwDeviceContext", 1);
+    print_str("DeviceType", av_hwdevice_get_type_name(hw_device_context->type));
+
+    writer_print_section_footer(w); // SECTION_ID_HWDEViCECONTEXT
+}
+
+static void print_hwframescontext(WriterContext *w, const AVHWFramesContext *hw_frames_context)
+{
+    const AVPixFmtDescriptor* pixdescHw;
+    const AVPixFmtDescriptor* pixdescSw;
+
+    writer_print_section_header(w, SECTION_ID_HWFRAMESCONTEXT);
+
+    print_int("HasHwFramesContext", 1);
+
+    pixdescHw = av_pix_fmt_desc_get(hw_frames_context->format);
+    if (pixdescHw) {
+        print_str("HwPixelFormat", pixdescHw->name);
+        print_str("HwPixelFormatAlias", pixdescHw->alias);
+    }
+
+    pixdescSw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
+    if (pixdescSw) {
+        print_str("SwPixelFormat", pixdescSw->name);
+        print_str("SwPixelFormatAlias", pixdescSw->alias);
+    }
+
+    print_int("Width", hw_frames_context->width);
+    print_int("Height", hw_frames_context->height);
+
+    print_hwdevicecontext(w, hw_frames_context->device_ctx);
+
+    writer_print_section_footer(w); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(WriterContext *w, AVFilterLink *link)
+{
+    char layoutString[64];
+
+    switch (link->type) {
+        case AVMEDIA_TYPE_VIDEO:
+            print_str("Format",  av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
+            print_int("Width", link->w);
+            print_int("Height", link->h);
+            print_q("SAR", link->sample_aspect_ratio, ':');
+            print_q("TimeBase", link->time_base, '/');
+            break;
+
+        ////case AVMEDIA_TYPE_SUBTITLE:
+        ////    print_str("Format",  av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
+        ////    print_int("Width", link->w);
+        ////    print_int("Height", link->h);
+        ////    print_q("TimeBase", link->time_base, '/');
+        ////    break;
+
+        case AVMEDIA_TYPE_AUDIO:
+            av_channel_layout_describe(&link->ch_layout, layoutString, sizeof(layoutString));
+            print_str("ChannelString", layoutString);
+            print_int("Channels", link->ch_layout.nb_channels);
+            ////print_int("ChannelLayout", link->ch_layout);
+            print_int("SampleRate", link->sample_rate);
+            break;
+    }
+
+    AVBufferRef *hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
+
+    if (hw_frames_ctx && hw_frames_ctx->buffer) {
+      print_hwframescontext(w, (AVHWFramesContext *)hw_frames_ctx->data);
+    }
+}
+
+static void print_filter(WriterContext *w, const AVFilterContext* filter)
+{
+    writer_print_section_header(w, SECTION_ID_FILTER);
+
+    print_str("Name", filter->name);
+
+    if (filter->filter) {
+        print_str("Name2", filter->filter->name);
+        print_str("Description", filter->filter->description);
+    }
+
+    if (filter->hw_device_ctx) {
+        AVHWDeviceContext* decCtx = (AVHWDeviceContext*)filter->hw_device_ctx->data;
+        print_hwdevicecontext(w, decCtx);
+    }
+
+    writer_print_section_header(w, SECTION_ID_INPUTS);
+
+    for (unsigned i = 0; i < filter->nb_inputs; i++) {
+        AVFilterLink *link = filter->inputs[i];
+        writer_print_section_header(w, SECTION_ID_INPUT);
+
+        print_str("SourceName", link->src->name);
+        print_str("SourcePadName", avfilter_pad_get_name(link->srcpad, 0));
+        print_str("DestPadName", avfilter_pad_get_name(link->dstpad, 0));
+
+        print_link(w, link);
+
+        writer_print_section_footer(w); // SECTION_ID_INPUT
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_INPUTS
+
+    // --------------------------------------------------
+
+    writer_print_section_header(w, SECTION_ID_OUTPUTS);
+
+    for (unsigned i = 0; i < filter->nb_outputs; i++) {
+        AVFilterLink *link = filter->outputs[i];
+        writer_print_section_header(w, SECTION_ID_OUTPUT);
+
+        print_str("DestName", link->dst->name);
+        print_str("DestPadName", avfilter_pad_get_name(link->dstpad, 0));
+        print_str("SourceName", link->src->name);
+
+        print_link(w, link);
+
+        writer_print_section_footer(w); // SECTION_ID_OUTPUT
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_OUTPUTS
+
+    writer_print_section_footer(w); // SECTION_ID_FILTER
+}
+
+static void print_filtergraph_single(WriterContext *w, FilterGraph* fg, AVFilterGraph *graph)
+{
+    char layoutString[64];
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+
+    print_int("GraphIndex", fg->index);
+    print_str("Description", fgp->graph_desc);
+
+    writer_print_section_header(w, SECTION_ID_INPUTS);
+
+    for (int i = 0; i < fg->nb_inputs; i++) {
+        InputFilterPriv* ifilter = ifp_from_ifilter(fg->inputs[i]);
+        enum AVMediaType mediaType = ifilter->type;
+
+        writer_print_section_header(w, SECTION_ID_INPUT);
+
+        print_str("Name1", (char*)ifilter->ifilter.name);
+
+        if (ifilter->filter) {
+            print_str("Name2", ifilter->filter->name);
+            print_str("Name3", ifilter->filter->filter->name);
+            print_str("Description", ifilter->filter->filter->description);
+        }
+
+        print_str("MediaType", av_get_media_type_string(mediaType));
+        print_int("MediaTypeId", mediaType);
+
+        switch (ifilter->type) {
+        case AVMEDIA_TYPE_VIDEO:
+        case AVMEDIA_TYPE_SUBTITLE:
+            print_str("Format",  av_x_if_null(av_get_pix_fmt_name(ifilter->format), "?"));
+            print_int("Width", ifilter->width);
+            print_int("Height", ifilter->height);
+            print_q("SAR", ifilter->sample_aspect_ratio, ':');
+            break;
+        case AVMEDIA_TYPE_AUDIO:
+
+            av_channel_layout_describe(&ifilter->ch_layout, layoutString, sizeof(layoutString));
+            print_str("ChannelString", layoutString);
+            ////print_int("Channels", ifilter->channels);
+            ////print_int("ChannelLayout", ifilter->channel_layout);
+            print_int("SampleRate", ifilter->sample_rate);
+            break;
+        case AVMEDIA_TYPE_ATTACHMENT:
+        case AVMEDIA_TYPE_DATA:
+            break;
+        }
+
+        if (ifilter->hw_frames_ctx)
+            print_hwframescontext(w, (AVHWFramesContext*)ifilter->hw_frames_ctx->data);
+        else if (ifilter->filter && ifilter->filter->hw_device_ctx) {
+            AVHWDeviceContext* devCtx = (AVHWDeviceContext*)ifilter->filter->hw_device_ctx->data;
+            print_hwdevicecontext(w, devCtx);
+        }
+
+        writer_print_section_footer(w); // SECTION_ID_INPUT
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_INPUTS
+
+
+    writer_print_section_header(w, SECTION_ID_OUTPUTS);
+
+    for (int i = 0; i < fg->nb_outputs; i++) {
+        OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
+        enum AVMediaType mediaType = AVMEDIA_TYPE_UNKNOWN;
+
+        writer_print_section_header(w, SECTION_ID_OUTPUT);
+        print_str("Name1", ofilter->name);
+
+        if (ofilter->filter) {
+            print_str("Name2", ofilter->filter->name);
+            print_str("Name3", ofilter->filter->filter->name);
+            print_str("Description", ofilter->filter->filter->description);
+
+            if (ofilter->filter->nb_inputs > 0)
+                mediaType = ofilter->filter->inputs[0]->type;
+        }
+
+        print_str("MediaType", av_get_media_type_string(mediaType));
+        print_int("MediaTypeId", mediaType);
+
+        switch (ofilter->ofilter.type) {
+        case AVMEDIA_TYPE_VIDEO:
+        case AVMEDIA_TYPE_SUBTITLE:
+            print_str("Format",  av_x_if_null(av_get_pix_fmt_name(ofilter->format), "?"));
+            print_int("Width", ofilter->width);
+            print_int("Height", ofilter->height);
+            break;
+        case AVMEDIA_TYPE_AUDIO:
+
+            av_channel_layout_describe(&ofilter->ch_layout, layoutString, sizeof(layoutString));
+            print_str("ChannelString", layoutString);
+            ////print_int("Channels", ofilter->channels);
+            ////print_int("ChannelLayout", ofilter->channel_layout);
+            print_int("SampleRate", ofilter->sample_rate);
+            break;
+        case AVMEDIA_TYPE_ATTACHMENT:
+        case AVMEDIA_TYPE_DATA:
+            break;
+        }
+
+        if (ofilter->filter && ofilter->filter->hw_device_ctx) {
+            AVHWDeviceContext* devCtx = (AVHWDeviceContext*)ofilter->filter->hw_device_ctx->data;
+            print_hwdevicecontext(w, devCtx);
+        }
+
+        writer_print_section_footer(w); // SECTION_ID_OUTPUT
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_OUTPUTS
+
+
+    writer_print_section_header(w, SECTION_ID_FILTERS);
+
+    if (graph) {
+        for (unsigned i = 0; i < graph->nb_filters; i++) {
+            AVFilterContext *filter = graph->filters[i];
+            writer_print_section_header(w, SECTION_ID_FILTER);
+
+            print_filter(w, filter);
+
+            writer_print_section_footer(w); // SECTION_ID_FILTER
+        }
+    }
+
+    writer_print_section_footer(w); // SECTION_ID_FILTERS
+}
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+    const Writer *writer;
+    WriterContext *w;
+    char *buf, *w_name, *w_args;
+    int ret;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVBPrint *targetBuf = &fgp->graph_print_buf;
+
+    writer_register_all();
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("default");
+    if (!print_graphs_format) {
+        return AVERROR(ENOMEM);
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &buf);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        return AVERROR(EINVAL);
+    }
+    w_args = buf;
+
+    writer = writer_get_by_name(w_name);
+    if (writer == NULL) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph  output format with name '%s'\n", w_name);
+        return AVERROR(EINVAL);
+    }
+
+    if (targetBuf->len) {
+        av_bprint_finalize(targetBuf, NULL);
+    }
+
+    if ((ret = writer_open(&w, writer, w_args, sections, FF_ARRAY_ELEMS(sections))) >= 0) {
+        writer_print_section_header(w, SECTION_ID_ROOT);
+        writer_print_section_header(w, SECTION_ID_FILTERGRAPHS);
+        writer_print_section_header(w, SECTION_ID_FILTERGRAPH);
+
+        av_bprint_clear(&w->bpBuf);
+
+        print_filtergraph_single(w, fg, graph);
+
+        av_bprint_finalize(&w->bpBuf, &targetBuf->str);
+        targetBuf->len = w->bpBuf.len;
+        targetBuf->size = w->bpBuf.len + 1;
+
+        writer_close(&w);
+    } else
+        return ret;
+
+    return 0;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, OutputFile **ofiles, int nb_ofiles)
+{
+    const Writer *writer;
+    WriterContext *w;
+    char *buf, *w_name, *w_args;
+    int ret;
+
+    writer_register_all();
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("default");
+    if (!print_graphs_format) {
+        return AVERROR(ENOMEM);
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &buf);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        return AVERROR(EINVAL);
+    }
+    w_args = buf;
+
+    writer = writer_get_by_name(w_name);
+    if (writer == NULL) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph  output format with name '%s'\n", w_name);
+        return AVERROR(EINVAL);
+    }
+
+    if ((ret = writer_open(&w, writer, w_args, sections, FF_ARRAY_ELEMS(sections))) >= 0) {
+        writer_print_section_header(w, SECTION_ID_ROOT);
+
+        writer_print_section_header(w, SECTION_ID_FILTERGRAPHS);
+
+        for (int i = 0; i < nb_graphs; i++) {
+
+            FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
+            AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+            if (graph_buf->len > 0) {
+                writer_print_section_header(w, SECTION_ID_FILTERGRAPH);
+
+                av_bprint_append_data(&w->bpBuf, graph_buf->str, graph_buf->len);
+                av_bprint_finalize(graph_buf, NULL);
+
+                writer_print_section_footer(w); // SECTION_ID_FILTERGRAPH
+            }
+       }
+
+        for (int n = 0; n < nb_ofiles; n++) {
+
+            OutputFile *of = ofiles[n];
+
+            for (int i = 0; i < of->nb_streams; i++) {
+
+                OutputStream *ost = of->streams[i];
+
+                if (ost->fg_simple) {
+
+                    FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
+                    AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+                    if (graph_buf->len > 0) {
+                        writer_print_section_header(w, SECTION_ID_FILTERGRAPH);
+
+                        av_bprint_append_data(&w->bpBuf, graph_buf->str,
+                                            graph_buf->len);
+                        av_bprint_finalize(graph_buf, NULL);
+
+                        writer_print_section_footer(w); // SECTION_ID_FILTERGRAPH
+                    }
+                }
+            }
+        }
+
+        writer_print_section_footer(w); // SECTION_ID_FILTERGRAPHS
+
+        writer_print_section_footer(w); // SECTION_ID_ROOT
+
+        if (print_graphs_file) {
+
+            AVIOContext *avio = NULL;
+
+            ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n",
+                       print_graphs_file, av_err2str(ret));
+                return ret;
+            }
+
+            avio_write(avio, (const unsigned char*)w->bpBuf.str, FFMIN(w->bpBuf.len, w->bpBuf.size - 1));
+            avio_flush(avio);
+
+            if ((ret = avio_closep(&avio)) < 0)
+                av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
+        }
+
+        if (print_graphs)
+            av_log(NULL, AV_LOG_INFO, "%s    %c", w->bpBuf.str, '\n');
+
+        writer_close(&w);
+    }
+
+    return 0;
+}
+
+void writer_register_all(void)
+{
+    static int initialized;
+
+    if (initialized)
+        return;
+    initialized = 1;
+
+    writer_register(&default_writer);
+    //writer_register(&compact_writer);
+    //writer_register(&csv_writer);
+    //writer_register(&flat_writer);
+    //writer_register(&ini_writer);
+    writer_register(&json_writer);
+    //writer_register(&xml_writer);
+}
+
+void write_error(WriterContext *w, int err)
+{
+    char errbuf[128];
+    const char *errbuf_ptr = errbuf;
+
+    if (av_strerror(err, errbuf, sizeof(errbuf)) < 0)
+        errbuf_ptr = strerror(AVUNERROR(err));
+
+    writer_print_section_header(w, SECTION_ID_ERROR);
+    print_int("Number", err);
+    print_str("Message", errbuf_ptr);
+    writer_print_section_footer(w);
+}
+
+void write_error_msg(WriterContext *w, int err, const char *msg)
+{
+    writer_print_section_header(w, SECTION_ID_ERROR);
+    print_int("Number", err);
+    print_str("Message", msg);
+    writer_print_section_footer(w);
+}
+
+void write_error_fmt(WriterContext *w, int err, const char *fmt,...)
+{
+    AVBPrint pbuf;
+    va_list vl;
+    va_start(vl, fmt);
+
+    writer_print_section_header(w, SECTION_ID_ERROR);
+    print_int("Number", err);
+
+    av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
+
+    av_bprint_clear(&pbuf);
+
+    av_vbprintf(&pbuf, fmt, vl);
+    va_end(vl);
+
+    print_str("Message", pbuf.str);
+
+    av_bprint_finalize(&pbuf, NULL);
+
+    writer_print_section_footer(w);
+}
+
diff --git a/fftools/ffmpeg_graphprint.h b/fftools/ffmpeg_graphprint.h
new file mode 100644
index 0000000000..5fa4a2585b
--- /dev/null
+++ b/fftools/ffmpeg_graphprint.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2018 - softworkz
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef FFTOOLS_FFMPEG_GRAPHPRINT_H
+#define FFTOOLS_FFMPEG_GRAPHPRINT_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+
+#define SECTION_MAX_NB_CHILDREN 11
+#define PRINT_STRING_OPT      1
+#define PRINT_STRING_VALIDATE 2
+#define MAX_REGISTERED_WRITERS_NB 64
+
+#ifndef GUID_DEFINED
+#define GUID_DEFINED
+typedef struct _GUID {
+    uint32_t Data1;
+    uint16_t Data2;
+    uint16_t Data3;
+    uint8_t  Data4[ 8 ];
+} GUID;
+
+#define IsEqualGUID(rguid1, rguid2) (!memcmp(rguid1, rguid2, sizeof(GUID)))
+
+#endif
+
+struct section {
+    int id;             ///< unique id identifying a section
+    const char *name;
+
+#define SECTION_FLAG_IS_WRAPPER      1 ///< the section only contains other sections, but has no data at its own level
+#define SECTION_FLAG_IS_ARRAY        2 ///< the section contains an array of elements of the same type
+#define SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys.
+                                           ///  For these sections the element_name field is mandatory.
+    int flags;
+    int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
+    const char *element_name; ///< name of the contained element, if provided
+    const char *unique_name;  ///< unique section name, in case the name is ambiguous
+};
+
+typedef enum {
+    SECTION_ID_NONE = -1,
+    SECTION_ID_ROOT,
+    SECTION_ID_PROGRAM_VERSION,
+    SECTION_ID_FILTERGRAPHS,
+    SECTION_ID_FILTERGRAPH,
+    SECTION_ID_INPUTS,
+    SECTION_ID_INPUT,
+    SECTION_ID_OUTPUTS,
+    SECTION_ID_OUTPUT,
+    SECTION_ID_FILTERS,
+    SECTION_ID_FILTER,
+    SECTION_ID_HWDEViCECONTEXT,
+    SECTION_ID_HWFRAMESCONTEXT,
+    SECTION_ID_ERROR,
+    SECTION_ID_LOG,
+    SECTION_ID_LOGS,
+
+} SectionID;
+
+static const struct section sections[] = {
+    [SECTION_ID_ROOT] =               { SECTION_ID_ROOT, "GraphDescription", SECTION_FLAG_IS_WRAPPER,
+                                      { SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_FILTERGRAPHS, SECTION_ID_LOGS, -1} },
+    [SECTION_ID_PROGRAM_VERSION] =    { SECTION_ID_PROGRAM_VERSION, "ProgramVersion", 0, { -1 } },
+
+    [SECTION_ID_FILTERGRAPHS] =       { SECTION_ID_FILTERGRAPHS, "Graphs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
+    [SECTION_ID_FILTERGRAPH] =        { SECTION_ID_FILTERGRAPH, "Graph", 0, { SECTION_ID_INPUTS, SECTION_ID_OUTPUTS, SECTION_ID_FILTERS, -1 },  },
+
+    [SECTION_ID_INPUTS] =             { SECTION_ID_INPUTS, "Inputs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_INPUT, SECTION_ID_ERROR, -1 } },
+    [SECTION_ID_INPUT] =              { SECTION_ID_INPUT, "Input", 0, { SECTION_ID_HWFRAMESCONTEXT, SECTION_ID_ERROR, -1 },  },
+
+    [SECTION_ID_OUTPUTS] =            { SECTION_ID_OUTPUTS, "Outputs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_OUTPUT, SECTION_ID_ERROR, -1 } },
+    [SECTION_ID_OUTPUT] =             { SECTION_ID_OUTPUT, "Output", 0, { SECTION_ID_HWFRAMESCONTEXT, SECTION_ID_ERROR, -1 },  },
+
+    [SECTION_ID_FILTERS] =            { SECTION_ID_FILTERS, "Filters", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER, SECTION_ID_ERROR, -1 } },
+    [SECTION_ID_FILTER] =             { SECTION_ID_FILTER, "Filter", 0, { SECTION_ID_HWDEViCECONTEXT, SECTION_ID_ERROR, -1 },  },
+
+    [SECTION_ID_HWDEViCECONTEXT] =    { SECTION_ID_HWDEViCECONTEXT, "HwDeviceContext", 0, { SECTION_ID_ERROR, -1 },  },
+    [SECTION_ID_HWFRAMESCONTEXT] =    { SECTION_ID_HWFRAMESCONTEXT, "HwFramesContext", 0, { SECTION_ID_ERROR, -1 },  },
+
+    [SECTION_ID_ERROR] =              { SECTION_ID_ERROR, "Error", 0, { -1 } },
+    [SECTION_ID_LOGS] =               { SECTION_ID_LOGS, "Log", SECTION_FLAG_IS_ARRAY, { SECTION_ID_LOG, -1 } },
+    [SECTION_ID_LOG] =                { SECTION_ID_LOG, "LogEntry", 0, { -1 },  },
+};
+
+struct unit_value {
+    union { double d; long long int i; } val;
+    const char *unit;
+};
+
+static const char unit_second_str[]         = "s"    ;
+static const char unit_hertz_str[]          = "Hz"   ;
+static const char unit_byte_str[]           = "byte" ;
+static const char unit_bit_per_second_str[] = "bit/s";
+
+/* WRITERS API */
+
+typedef struct WriterContext WriterContext;
+
+#define WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS 1
+#define WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER 2
+
+typedef enum {
+    WRITER_STRING_VALIDATION_FAIL,
+    WRITER_STRING_VALIDATION_REPLACE,
+    WRITER_STRING_VALIDATION_IGNORE,
+    WRITER_STRING_VALIDATION_NB
+} StringValidation;
+
+typedef struct Writer {
+    const AVClass *priv_class;      ///< private class of the writer, if any
+    int priv_size;                  ///< private size for the writer context
+    const char *name;
+
+    int  (*init)  (WriterContext *wctx);
+    void (*uninit)(WriterContext *wctx);
+
+    void (*print_section_header)(WriterContext *wctx);
+    void (*print_section_footer)(WriterContext *wctx);
+    void (*print_integer)       (WriterContext *wctx, const char *, long long int);
+    void (*print_rational)      (WriterContext *wctx, AVRational *q, char *sep);
+    void (*print_string)        (WriterContext *wctx, const char *, const char *);
+    int flags;                  ///< a combination or WRITER_FLAG_*
+} Writer;
+
+#define SECTION_MAX_NB_LEVELS 10
+
+struct WriterContext {
+    const AVClass *class;           ///< class of the writer
+    const Writer *writer;           ///< the Writer of which this is an instance
+    char *name;                     ///< name of this writer instance
+    void *priv;                     ///< private data for use by the filter
+
+    const struct section *sections; ///< array containing all sections
+    int nb_sections;                ///< number of sections
+
+    int level;                      ///< current level, starting from 0
+
+    /** number of the item printed in the given section, starting from 0 */
+    unsigned int nb_item[SECTION_MAX_NB_LEVELS];
+
+    /** section per each level */
+    const struct section *section[SECTION_MAX_NB_LEVELS];
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+                                                  ///  used by various writers
+    AVBPrint bpBuf;
+    unsigned int nb_section_packet; ///< number of the packet section in case we are in "packets_and_frames" section
+    unsigned int nb_section_frame;  ///< number of the frame  section in case we are in "packets_and_frames" section
+    unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame according if is_packets_and_frames
+
+    int string_validation;
+    char *string_validation_replacement;
+    unsigned int string_validation_utf8_flags;
+};
+
+#define print_fmt(k, f, ...) do {              \
+    av_bprint_clear(&pbuf);                    \
+    av_bprintf(&pbuf, f, __VA_ARGS__);         \
+    writer_print_string(w, k, pbuf.str, 0);    \
+} while (0)
+
+#define print_int(k, v)         writer_print_integer(w, k, v)
+#define print_q(k, v, s)        writer_print_rational(w, k, v, s)
+#define print_guid(k, v)        writer_print_guid(w, k, v)
+#define print_str(k, v)         writer_print_string(w, k, v, 0)
+#define print_str_opt(k, v)     writer_print_string(w, k, v, PRINT_STRING_OPT)
+#define print_str_validate(k, v) writer_print_string(w, k, v, PRINT_STRING_VALIDATE)
+#define print_time(k, v, tb)    writer_print_time(w, k, v, tb, 0)
+#define print_ts(k, v)          writer_print_ts(w, k, v, 0)
+#define print_duration_time(k, v, tb) writer_print_time(w, k, v, tb, 1)
+#define print_duration_ts(k, v)       writer_print_ts(w, k, v, 1)
+#define print_val(k, v, u) do {                                     \
+    struct unit_value uv;                                           \
+    uv.val.i = v;                                                   \
+    uv.unit = u;                                                    \
+    writer_print_string(w, k, value_string(val_str, sizeof(val_str), uv), 0); \
+} while (0)
+
+void writer_register_all(void);
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, OutputFile **output_files, int nb_output_files);
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+const Writer *writer_get_by_name(const char *name);
+
+int writer_open(WriterContext **wctx, const Writer *writer, const char *args,
+                       const struct section *sections, int nb_sections);
+
+void writer_print_section_header(WriterContext *wctx, int section_id);
+void writer_print_section_footer(WriterContext *wctx);
+
+int writer_print_string(WriterContext *wctx, const char *key, const char *val, int flags);
+void writer_print_integer(WriterContext *wctx, const char *key, long long int val);
+void writer_print_rational(WriterContext *wctx, const char *key, AVRational q, char sep);
+void writer_print_guid(WriterContext *wctx, const char *key, GUID *guid);
+void writer_print_ts(WriterContext *wctx, const char *key, int64_t ts, int is_duration);
+
+void writer_close(WriterContext **wctx);
+void write_error(WriterContext *w, int err);
+void write_error_msg(WriterContext *w, int err, const char *msg);
+void write_error_fmt(WriterContext *w, int err, const char *fmt,...);
+
+#endif /* FFTOOLS_FFMPEG_GRAPHPRINT_H */
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3c0c682594..8530ff6a66 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -75,6 +75,9 @@ float max_error_rate  = 2.0/3;
 char *filter_nbthreads;
 int filter_complex_nbthreads = 0;
 int vstats_version = 2;
+int print_graphs = 0;
+char* print_graphs_file = NULL;
+char* print_graphs_format = NULL;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1730,6 +1733,15 @@ const OptionDef options[] = {
         { .func_arg = opt_filter_complex_script },
         "deprecated, use -/filter_complex instead", "filename" },
 #endif
+    { "print_graphs",   OPT_TYPE_BOOL, 0,
+        { &print_graphs },
+        "prints filtergraph details to stderr" },
+    { "print_graphs_file", OPT_TYPE_STRING, 0,
+        { &print_graphs_file },
+        "writes graph details to a file", "filename" },
+    { "print_graphs_format", OPT_TYPE_STRING, 0,
+        { &print_graphs_format },
+      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml)", "format" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
-- 
ffmpeg-codebot

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 4/4] fftools: Enable filtergraph printing and update docs
  2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing ffmpegagent
                     ` (2 preceding siblings ...)
  2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 3/4] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
@ 2025-02-21 11:27   ` softworkz
  3 siblings, 0 replies; 15+ messages in thread
From: softworkz @ 2025-02-21 11:27 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Soft Works, softworkz, Andreas Rheinhardt

From: softworkz <softworkz@hotmail.com>

Enables filtergraph printing and adds the options to the docs

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi         | 10 ++++++++++
 fftools/ffmpeg.c        |  4 ++++
 fftools/ffmpeg_filter.c |  5 +++++
 3 files changed, 19 insertions(+)

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index da6549f043..6398b4ed92 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1388,6 +1388,16 @@ It is on by default, to explicitly 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 -print_graphs (@emph{global})
+Prints filtergraph details to stderr in the format set via -print_graphs_format.
+
+@item -print_graphs_file @var{filename} (@emph{global})
+Writes filtergraph details to the specified file in the format set via -print_graphs_format.
+
+@item -print_graphs_format @var{format} (@emph{global})
+Sets the output printing format (available formats are: default, compact, csv, flat, ini, json, xml)
+The default format is json.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..b4bc76d788 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -81,6 +81,7 @@
 #include "ffmpeg.h"
 #include "ffmpeg_sched.h"
 #include "ffmpeg_utils.h"
+#include "ffmpeg_graphprint.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
+    if (print_graphs || print_graphs_file)
+        print_filtergraphs(filtergraphs, nb_filtergraphs, output_files, nb_output_files);
+
     if (do_benchmark) {
         int64_t maxrss = getmaxrss() / 1024;
         av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 6de4e87ade..7198416ae9 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -22,6 +22,7 @@
 
 #include "ffmpeg.h"
 #include "ffmpeg_filter.h"
+#include "ffmpeg_graphprint.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -2970,6 +2971,10 @@ read_frames:
     }
 
 finish:
+
+    if (print_graphs || print_graphs_file)
+        print_filtergraph(fg, fgt.graph);
+
     // EOF is normal termination
     if (ret == AVERROR_EOF)
         ret = 0;
-- 
ffmpeg-codebot
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
  2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
  2025-02-21  9:22   ` Andreas Rheinhardt
@ 2025-02-21 13:09   ` Nicolas George
  2025-02-21 13:49     ` Soft Works
  1 sibling, 1 reply; 15+ messages in thread
From: Nicolas George @ 2025-02-21 13:09 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

softworkz (HE12025-02-19):
> From: softworkz <softworkz@hotmail.com>
> 
> The key benefits are:
> 
> - Different to other graph printing methods, this is outputting:
>   - all graphs with runtime state
>     (including auto-inserted filters)
>   - each graph with its inputs and outputs
>   - all filters with their in- and output pads
>   - all connections between all input- and output pads
>   - for each connection:
>     - the runtime-negotiated format and media type
>     - the hw context
>     - if video hw context, both: hw pixfmt + sw pixfmt
> - Output can either be printed to stdout or written to specified file
> - Output is machine-readable
> - Use the same output implementation as ffprobe, supporting multiple
>   formats
> 
> Note: This commit includes only the default and JSON writers.

This patch contains a lot of code copy-pasted from ffprobe. Moreover, it
is copy-pasted from 2018 ffprobe, with seven years of bugfixes omitted.
Copy-pasting non-trivial amounts of code is a big no, experienced
developers should know better. It is a big no among other reasons
precisely because bug fixes on one copy will likely be skipped on the
other copies.

When the same code is needed in multiple parts of the project, it needs
to be moved into a library with a proper API.

For JSON, and more generally ffprobe's writers, since it was quite
obvious we need structured output at other places than ffprobe, I had
started working on it. IIRC, I had the JSON output done, with no dynamic
allocations if the structure is not too deep. But of course, since it
uses strings, it requires a good strings API, which is being blocked.

Regards,

-- 
  Nicolas George
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
  2025-02-21 13:09   ` Nicolas George
@ 2025-02-21 13:49     ` Soft Works
  0 siblings, 0 replies; 15+ messages in thread
From: Soft Works @ 2025-02-21 13:49 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Nicolas George
> Sent: Freitag, 21. Februar 2025 14:10
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add
> options for filtergraph printing
> 
> softworkz (HE12025-02-19):
> > From: softworkz <softworkz@hotmail.com>
> >
> > The key benefits are:
> >
> > - Different to other graph printing methods, this is outputting:
> >   - all graphs with runtime state
> >     (including auto-inserted filters)
> >   - each graph with its inputs and outputs
> >   - all filters with their in- and output pads
> >   - all connections between all input- and output pads
> >   - for each connection:
> >     - the runtime-negotiated format and media type
> >     - the hw context
> >     - if video hw context, both: hw pixfmt + sw pixfmt
> > - Output can either be printed to stdout or written to specified file
> > - Output is machine-readable
> > - Use the same output implementation as ffprobe, supporting multiple
> >   formats
> >
> > Note: This commit includes only the default and JSON writers.
> 
> This patch contains a lot of code copy-pasted from ffprobe. Moreover, it
> is copy-pasted from 2018 ffprobe, with seven years of bugfixes omitted.

Hello Nicolas,

Yes, this is all true, but of course I did a diff to current ffprobe code and
the number of bugfixes is exactly zero.
Probably it's not seven but 4 years, as I've likely done the same when
I had submitted it initially, in 2021.

> When the same code is needed in multiple parts of the project, it needs
> to be moved into a library with a proper API.

Strictly speaking, it's not a duplication because one part lives only 
In ffprobe and the other only in ffmpeg. Also, the submitted code
prints to a buffer and the ffprobe code prints directly to stdout or
a file.
Nonetheless, I agree of course that it would be great to unify it.
When I had submitted this patchset in 2021, you had said the same
thing, that you want to work on it, but now it's 4 years later and
it hasn't happened.

I understand that you would like to get in your new strings API into
the code base and my only reservation was that I find it a little bit
too clever/tricky which requires thinking around three corners
each time when trying to follow what's happening. Probably also,
your earlier string API (AVBPrint) is too good already to consider 
it urgent for replacement. Overall, I'm not against it, though.

But this can't be a blocker. I can stub out the writers to a separate
and shared code file if this is OK for you. I don't want to get in your
way of things you have already started, but it wouldn't be much 
more than a move, not the kind of rewrite you are aiming for.

I think you'll agree that this patchset cannot wait for something
which might or might not happen in the future.

Thank you
sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2025-02-21 13:49 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-02-19  9:59 [FFmpeg-devel] [PATCH 0/3] print_graphs: Complete Filtergraph Printing ffmpegagent
2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 1/3] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
2025-02-21  9:22   ` Andreas Rheinhardt
2025-02-21  9:42     ` Soft Works
2025-02-21 11:11       ` Andreas Rheinhardt
2025-02-21 11:25         ` Soft Works
2025-02-21 13:09   ` Nicolas George
2025-02-21 13:49     ` Soft Works
2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 3/3] fftools: Enable filtergraph printing and update docs softworkz
2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing ffmpegagent
2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 1/4] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 2/4] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() softworkz
2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 3/4] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 4/4] fftools: Enable filtergraph printing and update docs softworkz

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