* [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; 27+ 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] 27+ 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; 27+ 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] 27+ 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; 27+ 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] 27+ 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; 27+ 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] 27+ 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; 27+ 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] 27+ 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; 27+ 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] 27+ 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; 27+ 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] 27+ 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; 27+ 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] 27+ 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
` (4 more replies)
3 siblings, 5 replies; 27+ 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] 27+ 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
` (3 subsequent siblings)
4 siblings, 0 replies; 27+ 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] 27+ 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
` (2 subsequent siblings)
4 siblings, 0 replies; 27+ 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] 27+ 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
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
4 siblings, 0 replies; 27+ 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] 27+ 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
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
4 siblings, 0 replies; 27+ 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] 27+ 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; 27+ 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] 27+ 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
2025-02-24 10:41 ` Nicolas George
0 siblings, 1 reply; 27+ 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] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
2025-02-21 13:49 ` Soft Works
@ 2025-02-24 10:41 ` Nicolas George
2025-02-24 13:19 ` Soft Works
0 siblings, 1 reply; 27+ messages in thread
From: Nicolas George @ 2025-02-24 10:41 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Soft Works (HE12025-02-21):
> Yes, this is all true, but of course I did a diff to current ffprobe code and
> the number of bugfixes is exactly zero.
I did a diff to see if what I suspected was duplicated code was indeed,
and I found it was, and I found failures of synchronization too.
> Strictly speaking, it's not a duplication because one part lives only
> In ffprobe and the other only in ffmpeg.
That is irrelevant, you copy-pasted code, that is code duplication.
> When I had submitted this patchset in 2021, you had said the same
> thing,
Well, unless I was not paying attention, the same flaws lead to the same
comments.
> that you want to work on it, but now it's 4 years later and
> it hasn't happened.
Indeed. “It hasn't happened” is kind of what you should expect when a
prerequisite is being blocked.
> But this can't be a blocker.
You cannot be blocked until the code I had baking is included, but your
code duplication is still a big no, and a big no is blocking.
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] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
2025-02-24 10:41 ` Nicolas George
@ 2025-02-24 13:19 ` Soft Works
2025-02-26 14:42 ` Nicolas George
0 siblings, 1 reply; 27+ messages in thread
From: Soft Works @ 2025-02-24 13:19 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: Montag, 24. Februar 2025 11:41
> 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
>
> Soft Works (HE12025-02-21):
[..]
> > that you want to work on it, but now it's 4 years later and
> > it hasn't happened.
>
> Indeed. “It hasn't happened” is kind of what you should expect when a
> prerequisite is being blocked.
>
> > But this can't be a blocker.
>
> You cannot be blocked until the code I had baking is included,
Okay, thanks.
> but your
> code duplication is still a big no, and a big no is blocking.
Accepted. I'll outfactor the writers first. It's a valid point of course;
I just wasn't sure whether you would dislike me doing it.
Should each writer go into a separate code file, or all in one?
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] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
2025-02-24 13:19 ` Soft Works
@ 2025-02-26 14:42 ` Nicolas George
2025-02-27 13:11 ` Soft Works
0 siblings, 1 reply; 27+ messages in thread
From: Nicolas George @ 2025-02-26 14:42 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Soft Works (HE12025-02-24):
> Accepted. I'll outfactor the writers first. It's a valid point of course;
> I just wasn't sure whether you would dislike me doing it.
I dislike the fact that it will be done on top of a pedestrian or clumsy
strings API, but I know precisely where to lay the blame for that
absurdity and it is not you.
> Should each writer go into a separate code file, or all in one?
The writers in ffprobe are an ad-hoc construction to turn the shallow
data structures produced by ffprobe into a common denominator of JSON,
XML, CSV and a few custom formats. It is not a good candidate to be
turned into an API.
The writes make arbitrary decisions about how the data will be
represented. Not much in JSON, but for XML the choices of what is an
attribute and what is an element are very arbitrary. The XML writer in
ffprobe cannot output any (basic) XML file.
That part of the code is not duplicated in your patch anyway. What is
duplicated is the low-level code. So this is where you would be better
off starting.
That would mean something like this:
Move the low-level JSON writing code into lavu, with a low-level API
specific to JSON. Bonus points if the API can be used without dynamic
allocations when the data structure is not too deep.
Use the newly introduced av_json_write API in ffprobe; the JSON writer
becomes a trivial wrapper around it.
Also use it in your patch.
Later, possibly somebody else: do the same with XML. The low-level API
will need to be a little more complex, because XML has more room for
tweaks.
At some point in the future: unify the JSON, XML and other writers in
libavutil under a common API, but one that can be tweaked for any kind
of input data structure, not just a list of streams/packets/frames with
flat attributes.
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] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing
2025-02-26 14:42 ` Nicolas George
@ 2025-02-27 13:11 ` Soft Works
0 siblings, 0 replies; 27+ messages in thread
From: Soft Works @ 2025-02-27 13:11 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: Mittwoch, 26. Februar 2025 15:43
> 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
>
> Soft Works (HE12025-02-24):
> > Accepted. I'll outfactor the writers first. It's a valid point of course;
> > I just wasn't sure whether you would dislike me doing it.
>
> I dislike the fact that it will be done on top of a pedestrian or clumsy
> strings API, but I know precisely where to lay the blame for that
> absurdity and it is not you.
>
> > Should each writer go into a separate code file, or all in one?
>
> The writers in ffprobe are an ad-hoc construction to turn the shallow
> data structures produced by ffprobe into a common denominator of JSON,
> XML, CSV and a few custom formats. It is not a good candidate to be
> turned into an API.
>
> The writes make arbitrary decisions about how the data will be
> represented. Not much in JSON, but for XML the choices of what is an
> attribute and what is an element are very arbitrary. The XML writer in
> ffprobe cannot output any (basic) XML file.
>
> That part of the code is not duplicated in your patch anyway. What is
> duplicated is the low-level code. So this is where you would be better
> off starting.
>
> That would mean something like this:
>
> Move the low-level JSON writing code into lavu, with a low-level API
> specific to JSON. Bonus points if the API can be used without dynamic
> allocations when the data structure is not too deep.
>
> Use the newly introduced av_json_write API in ffprobe; the JSON writer
> becomes a trivial wrapper around it.
>
> Also use it in your patch.
>
> Later, possibly somebody else: do the same with XML. The low-level API
> will need to be a little more complex, because XML has more room for
> tweaks.
>
> At some point in the future: unify the JSON, XML and other writers in
> libavutil under a common API, but one that can be tweaked for any kind
> of input data structure, not just a list of streams/packets/frames with
> flat attributes.
HI Nicolas,
thanks a lot for your advice. I've looked into extracting only the JSON
part, but it has a number of things which are specific to the usage in
ffprobe and I felt that it doesn't allow for a clean solution when trying
to keep the behavior and create a clean API in avutil at the same time.
Also, the JSON code is just a small part. The big elephant is the core
code, like you said and I so I chose to grab this thing at the root and
fully generalize that code.
I have done it in a way that it's a public API in avutil, foremost in order
to be sure that it's fully clean and independent.
I'm not insisting that it has to be public. It's surely not there where
you like to have it, but I think it might serve as better basis for that.
Maybe the code can just live in fftools for the time being and serve
both, ffprobe and ffmpeg (graph printing)?
Please let me know what you think.
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] 27+ messages in thread
* [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing
2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing ffmpegagent
` (3 preceding siblings ...)
2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 4/4] fftools: Enable filtergraph printing and update docs softworkz
@ 2025-03-01 10:01 ` ffmpegagent
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz
` (6 more replies)
4 siblings, 7 replies; 27+ messages in thread
From: ffmpegagent @ 2025-03-01 10:01 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Soft Works, softworkz, Andreas Rheinhardt
This new version of the patchset starts by extracting the text formatting
and writing APIs from ffprobe.c into a subfolder under fftools. The type
naming follows public API naming style, ramping up for making it a public
API in the future without another big renaming.
The extraction of the text formatting APIs can be followed in smaller steps
in the recent patchset "[RFC] avtextformat: Transform text writing into an
independent API". To make this more review-friendly, the ffprobe changes are
done in two steps. The 2nd commit includes all essential changes while the
large part of renamings is deferred to the 3rd commit (containing renamings
only).
The graph-printing uses the extracted APIs. It supports all ffprobe output
formats now. Otherwise it's functional equivalent to the previous version:
* Different to other graph printing methods, this is outputting:
* both, simple and complex filtergraphs
* 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
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)
Update V3
* Includes extraction and generalization of the text formatting APIs
* All output formats supported now
softworkz (7):
fftools/textformat: Extract and generalize textformat api from
ffprobe.c
fftools/ffprobe: Change to use textformat api
fftools/ffprobe: Rename writer_print_section_* and WriterContext
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 | 23 +
fftools/ffmpeg.c | 4 +
fftools/ffmpeg.h | 3 +
fftools/ffmpeg_filter.c | 193 +--
fftools/ffmpeg_filter.h | 232 +++
fftools/ffmpeg_graphprint.c | 474 ++++++
fftools/ffmpeg_graphprint.h | 79 +
fftools/ffmpeg_opt.c | 12 +
fftools/ffprobe.c | 2190 ++++------------------------
fftools/textformat/avtextformat.c | 671 +++++++++
fftools/textformat/avtextformat.h | 171 +++
fftools/textformat/avtextwriters.h | 68 +
fftools/textformat/tf_compact.c | 282 ++++
fftools/textformat/tf_default.c | 145 ++
fftools/textformat/tf_flat.c | 174 +++
fftools/textformat/tf_ini.c | 160 ++
fftools/textformat/tf_json.c | 215 +++
fftools/textformat/tf_xml.c | 221 +++
fftools/textformat/tw_avio.c | 129 ++
fftools/textformat/tw_buffer.c | 92 ++
fftools/textformat/tw_stdout.c | 82 ++
libavfilter/avfilter.c | 9 +
libavfilter/avfilter.h | 12 +
libavfilter/version.h | 2 +-
26 files changed, 3576 insertions(+), 2080 deletions(-)
create mode 100644 fftools/ffmpeg_filter.h
create mode 100644 fftools/ffmpeg_graphprint.c
create mode 100644 fftools/ffmpeg_graphprint.h
create mode 100644 fftools/textformat/avtextformat.c
create mode 100644 fftools/textformat/avtextformat.h
create mode 100644 fftools/textformat/avtextwriters.h
create mode 100644 fftools/textformat/tf_compact.c
create mode 100644 fftools/textformat/tf_default.c
create mode 100644 fftools/textformat/tf_flat.c
create mode 100644 fftools/textformat/tf_ini.c
create mode 100644 fftools/textformat/tf_json.c
create mode 100644 fftools/textformat/tf_xml.c
create mode 100644 fftools/textformat/tw_avio.c
create mode 100644 fftools/textformat/tw_buffer.c
create mode 100644 fftools/textformat/tw_stdout.c
base-commit: 99e2af4e7837ca09b97d93a562dc12947179fc48
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-52%2Fsoftworkz%2Fsubmit_print_graphs5-v3
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-52/softworkz/submit_print_graphs5-v3
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/52
Range-diff vs v2:
-: ---------- > 1: 6239813ba0 fftools/textformat: Extract and generalize textformat api from ffprobe.c
-: ---------- > 2: 805f66cd92 fftools/ffprobe: Change to use textformat api
-: ---------- > 3: 8dcc17112d fftools/ffprobe: Rename writer_print_section_* and WriterContext
1: c843eb0741 = 4: 06174ae7ef fftools/ffmpeg_filter: Move some declaration to new header file
2: 5a7b45ab09 = 5: d93c23872f avfilter/avfilter Add avfilter_link_get_hw_frames_ctx()
3: 1f87cfae77 ! 6: 9d76b4df1b fftools/ffmpeg_graphprint: Add options for filtergraph printing
@@ Commit message
- 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 ##
@@ fftools/Makefile: OBJS-ffmpeg += \
+ fftools/ffmpeg_graphprint.o \
fftools/sync_queue.o \
fftools/thread_queue.o \
++ fftools/textformat/avtextformat.o \
++ fftools/textformat/tf_compact.o \
++ fftools/textformat/tf_default.o \
++ fftools/textformat/tf_flat.o \
++ fftools/textformat/tf_ini.o \
++ fftools/textformat/tf_json.o \
++ fftools/textformat/tf_xml.o \
++ fftools/textformat/tw_avio.o \
++ fftools/textformat/tw_buffer.o \
++ fftools/textformat/tw_stdout.o \
+ OBJS-ffprobe += \
+ fftools/textformat/avtextformat.o \
## fftools/ffmpeg.h ##
@@ fftools/ffmpeg.h: extern float max_error_rate;
@@ fftools/ffmpeg_graphprint.c (new)
+
+#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"
++#include "textformat/avtextformat.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;
++/* Text Format API Shortcuts */
++#define print_int(k, v) avtext_print_integer(w, k, v)
++#define print_q(k, v, s) avtext_print_rational(w, k, v, s)
++#define print_str(k, v) avtext_print_string(w, k, v, 0)
+
-+ 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)
++static void print_hwdevicecontext(AVTextFormatContext *w, const AVHWDeviceContext *hw_device_context)
+{
-+ 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);
++ avtext_print_section_header(w, NULL, 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
++ avtext_print_section_footer(w); // SECTION_ID_HWDEViCECONTEXT
+}
+
-+static void print_hwframescontext(WriterContext *w, const AVHWFramesContext *hw_frames_context)
++static void print_hwframescontext(AVTextFormatContext *w, const AVHWFramesContext *hw_frames_context)
+{
+ const AVPixFmtDescriptor* pixdescHw;
+ const AVPixFmtDescriptor* pixdescSw;
+
-+ writer_print_section_header(w, SECTION_ID_HWFRAMESCONTEXT);
++ avtext_print_section_header(w, NULL, SECTION_ID_HWFRAMESCONTEXT);
+
+ print_int("HasHwFramesContext", 1);
+
@@ fftools/ffmpeg_graphprint.c (new)
+
+ print_hwdevicecontext(w, hw_frames_context->device_ctx);
+
-+ writer_print_section_footer(w); // SECTION_ID_HWFRAMESCONTEXT
++ avtext_print_section_footer(w); // SECTION_ID_HWFRAMESCONTEXT
+}
+
-+static void print_link(WriterContext *w, AVFilterLink *link)
++static void print_link(AVTextFormatContext *w, AVFilterLink *link)
+{
+ char layoutString[64];
+
@@ fftools/ffmpeg_graphprint.c (new)
+ }
+}
+
-+static void print_filter(WriterContext *w, const AVFilterContext* filter)
++static void print_filter(AVTextFormatContext *w, const AVFilterContext* filter)
+{
-+ writer_print_section_header(w, SECTION_ID_FILTER);
++ avtext_print_section_header(w, NULL, SECTION_ID_FILTER);
+
+ print_str("Name", filter->name);
+
@@ fftools/ffmpeg_graphprint.c (new)
+ print_hwdevicecontext(w, decCtx);
+ }
+
-+ writer_print_section_header(w, SECTION_ID_INPUTS);
++ avtext_print_section_header(w, NULL, 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);
++ avtext_print_section_header(w, NULL, SECTION_ID_INPUT);
+
+ print_str("SourceName", link->src->name);
+ print_str("SourcePadName", avfilter_pad_get_name(link->srcpad, 0));
@@ fftools/ffmpeg_graphprint.c (new)
+
+ print_link(w, link);
+
-+ writer_print_section_footer(w); // SECTION_ID_INPUT
++ avtext_print_section_footer(w); // SECTION_ID_INPUT
+ }
+
-+ writer_print_section_footer(w); // SECTION_ID_INPUTS
-+
-+ // --------------------------------------------------
++ avtext_print_section_footer(w); // SECTION_ID_INPUTS
+
-+ writer_print_section_header(w, SECTION_ID_OUTPUTS);
++ avtext_print_section_header(w, NULL, 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);
++ avtext_print_section_header(w, NULL, SECTION_ID_OUTPUT);
+
+ print_str("DestName", link->dst->name);
+ print_str("DestPadName", avfilter_pad_get_name(link->dstpad, 0));
@@ fftools/ffmpeg_graphprint.c (new)
+
+ print_link(w, link);
+
-+ writer_print_section_footer(w); // SECTION_ID_OUTPUT
++ avtext_print_section_footer(w); // SECTION_ID_OUTPUT
+ }
+
-+ writer_print_section_footer(w); // SECTION_ID_OUTPUTS
++ avtext_print_section_footer(w); // SECTION_ID_OUTPUTS
+
-+ writer_print_section_footer(w); // SECTION_ID_FILTER
++ avtext_print_section_footer(w); // SECTION_ID_FILTER
+}
+
-+static void print_filtergraph_single(WriterContext *w, FilterGraph* fg, AVFilterGraph *graph)
++static void init_sections(void)
++{
++ for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
++ sections[i].show_all_entries = 1;
++}
++
++static void print_filtergraph_single(AVTextFormatContext *w, FilterGraph* fg, AVFilterGraph *graph)
+{
+ char layoutString[64];
+ FilterGraphPriv *fgp = fgp_from_fg(fg);
@@ fftools/ffmpeg_graphprint.c (new)
+ print_int("GraphIndex", fg->index);
+ print_str("Description", fgp->graph_desc);
+
-+ writer_print_section_header(w, SECTION_ID_INPUTS);
++ avtext_print_section_header(w, NULL, 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);
++ avtext_print_section_header(w, NULL, SECTION_ID_INPUT);
+
+ print_str("Name1", (char*)ifilter->ifilter.name);
+
@@ fftools/ffmpeg_graphprint.c (new)
+
+ 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:
@@ fftools/ffmpeg_graphprint.c (new)
+ print_hwdevicecontext(w, devCtx);
+ }
+
-+ writer_print_section_footer(w); // SECTION_ID_INPUT
++ avtext_print_section_footer(w); // SECTION_ID_INPUT
+ }
+
-+ writer_print_section_footer(w); // SECTION_ID_INPUTS
++ avtext_print_section_footer(w); // SECTION_ID_INPUTS
+
+
-+ writer_print_section_header(w, SECTION_ID_OUTPUTS);
++ avtext_print_section_header(w, NULL, 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);
++ avtext_print_section_header(w, NULL, SECTION_ID_OUTPUT);
+ print_str("Name1", ofilter->name);
+
+ if (ofilter->filter) {
@@ fftools/ffmpeg_graphprint.c (new)
+
+ 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:
@@ fftools/ffmpeg_graphprint.c (new)
+ print_hwdevicecontext(w, devCtx);
+ }
+
-+ writer_print_section_footer(w); // SECTION_ID_OUTPUT
++ avtext_print_section_footer(w); // SECTION_ID_OUTPUT
+ }
+
-+ writer_print_section_footer(w); // SECTION_ID_OUTPUTS
++ avtext_print_section_footer(w); // SECTION_ID_OUTPUTS
+
+
-+ writer_print_section_header(w, SECTION_ID_FILTERS);
++ avtext_print_section_header(w, NULL, 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);
++ avtext_print_section_header(w, NULL, SECTION_ID_FILTER);
+
+ print_filter(w, filter);
+
-+ writer_print_section_footer(w); // SECTION_ID_FILTER
++ avtext_print_section_footer(w); // SECTION_ID_FILTER
+ }
+ }
+
-+ writer_print_section_footer(w); // SECTION_ID_FILTERS
++ avtext_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;
++ const AVTextFormatter *text_formatter;
++ AVTextFormatContext *tctx;
++ AVTextWriterContext *wctx;
++ char *w_name, *w_args;
+ int ret;
+ FilterGraphPriv *fgp = fgp_from_fg(fg);
-+ AVBPrint *targetBuf = &fgp->graph_print_buf;
++ AVBPrint *target_buf = &fgp->graph_print_buf;
++
++ init_sections();
+
-+ writer_register_all();
++ if (target_buf->len)
++ av_bprint_finalize(target_buf, NULL);
++
++ av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ if (!print_graphs_format)
+ print_graphs_format = av_strdup("default");
-+ if (!print_graphs_format) {
++ if (!print_graphs_format)
+ return AVERROR(ENOMEM);
-+ }
+
-+ w_name = av_strtok(print_graphs_format, "=", &buf);
++ w_name = av_strtok(print_graphs_format, "=", &w_args);
+ 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) {
++ text_formatter = avtext_get_formatter_by_name(w_name);
++ if (text_formatter == 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);
++ ret = avtextwriter_create_buffer(&wctx, target_buf);
++ if (ret < 0) {
++ av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
++ 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);
-+ writer_print_section_header(w, SECTION_ID_FILTERGRAPH);
-+
-+ av_bprint_clear(&w->bpBuf);
++ if ((ret = avtext_context_open(&tctx, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), 0, 0, 0, 0, -1, NULL)) >= 0) {
++ avtext_print_section_header(tctx, NULL, SECTION_ID_ROOT);
++ avtext_print_section_header(tctx, NULL, SECTION_ID_FILTERGRAPHS);
++ avtext_print_section_header(tctx, NULL, SECTION_ID_FILTERGRAPH);
+
-+ print_filtergraph_single(w, fg, graph);
++ av_bprint_clear(target_buf);
+
-+ av_bprint_finalize(&w->bpBuf, &targetBuf->str);
-+ targetBuf->len = w->bpBuf.len;
-+ targetBuf->size = w->bpBuf.len + 1;
++ print_filtergraph_single(tctx, fg, graph);
+
-+ writer_close(&w);
++ avtext_context_close(&tctx);
++ avtextwriter_context_close(&wctx);
+ } else
+ return ret;
+
@@ fftools/ffmpeg_graphprint.c (new)
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, OutputFile **ofiles, int nb_ofiles)
+{
-+ const Writer *writer;
-+ WriterContext *w;
++ const AVTextFormatter *text_formatter;
++ AVTextFormatContext *tctx;
++ AVTextWriterContext *wctx;
++ AVBPrint target_buf;
+ char *buf, *w_name, *w_args;
+ int ret;
+
-+ writer_register_all();
++ init_sections();
+
+ if (!print_graphs_format)
+ print_graphs_format = av_strdup("default");
@@ fftools/ffmpeg_graphprint.c (new)
+ }
+ w_args = buf;
+
-+ writer = writer_get_by_name(w_name);
-+ if (writer == NULL) {
++ text_formatter = avtext_get_formatter_by_name(w_name);
++ if (text_formatter == 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);
++ av_bprint_init(&target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
++
++ ret = avtextwriter_create_buffer(&wctx, &target_buf);
++ if (ret < 0) {
++ av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
++ return AVERROR(EINVAL);
++ }
++
++ if ((ret = avtext_context_open(&tctx, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), 0, 0, 0, 0, -1, NULL)) >= 0) {
++ avtext_print_section_header(tctx, NULL, SECTION_ID_ROOT);
+
-+ writer_print_section_header(w, SECTION_ID_FILTERGRAPHS);
++ avtext_print_section_header(tctx, NULL, SECTION_ID_FILTERGRAPHS);
+
+ for (int i = 0; i < nb_graphs; i++) {
+
@@ fftools/ffmpeg_graphprint.c (new)
+ AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+ if (graph_buf->len > 0) {
-+ writer_print_section_header(w, SECTION_ID_FILTERGRAPH);
++ avtext_print_section_header(tctx, NULL, SECTION_ID_FILTERGRAPH);
+
-+ av_bprint_append_data(&w->bpBuf, graph_buf->str, graph_buf->len);
++ av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+ av_bprint_finalize(graph_buf, NULL);
+
-+ writer_print_section_footer(w); // SECTION_ID_FILTERGRAPH
++ avtext_print_section_footer(tctx); // 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);
++ avtext_print_section_header(tctx, NULL, SECTION_ID_FILTERGRAPH);
+
-+ av_bprint_append_data(&w->bpBuf, graph_buf->str,
-+ graph_buf->len);
++ av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+ av_bprint_finalize(graph_buf, NULL);
+
-+ writer_print_section_footer(w); // SECTION_ID_FILTERGRAPH
++ avtext_print_section_footer(tctx); // SECTION_ID_FILTERGRAPH
+ }
+ }
+ }
+ }
+
-+ writer_print_section_footer(w); // SECTION_ID_FILTERGRAPHS
-+
-+ writer_print_section_footer(w); // SECTION_ID_ROOT
++ avtext_print_section_footer(tctx); // SECTION_ID_FILTERGRAPHS
++ avtext_print_section_footer(tctx); // SECTION_ID_ROOT
+
+ if (print_graphs_file) {
-+
+ AVIOContext *avio = NULL;
+
+ ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
@@ fftools/ffmpeg_graphprint.c (new)
+ return ret;
+ }
+
-+ avio_write(avio, (const unsigned char*)w->bpBuf.str, FFMIN(w->bpBuf.len, w->bpBuf.size - 1));
++ avio_write(avio, (const unsigned char*)target_buf.str, FFMIN(target_buf.len, target_buf.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');
++ if (print_graphs) {
++ printf("%s", target_buf.str);
++ av_log(NULL, AV_LOG_INFO, "%s %c", target_buf.str, '\n');
++ }
+
-+ writer_close(&w);
++ avtext_context_close(&tctx);
++ avtextwriter_context_close(&wctx);
+ }
+
+ 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);
-+}
-+
## fftools/ffmpeg_graphprint.h (new) ##
@@
@@ fftools/ffmpeg_graphprint.h (new)
+#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
-+};
++#include "textformat/avtextformat.h"
+
+typedef enum {
-+ SECTION_ID_NONE = -1,
+ SECTION_ID_ROOT,
+ SECTION_ID_PROGRAM_VERSION,
+ SECTION_ID_FILTERGRAPHS,
@@ fftools/ffmpeg_graphprint.h (new)
+
+} SectionID;
+
-+static const struct section sections[] = {
-+ [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "GraphDescription", SECTION_FLAG_IS_WRAPPER,
++static struct AVTextFormatSection sections[] = {
++ [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "GraphDescription", AV_TEXTFORMAT_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_FILTERGRAPHS] = { SECTION_ID_FILTERGRAPHS, "Graphs", AV_TEXTFORMAT_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_INPUTS] = { SECTION_ID_INPUTS, "Inputs", AV_TEXTFORMAT_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_OUTPUTS] = { SECTION_ID_OUTPUTS, "Outputs", AV_TEXTFORMAT_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_FILTERS] = { SECTION_ID_FILTERS, "Filters", AV_TEXTFORMAT_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_LOGS] = { SECTION_ID_LOGS, "Log", AV_TEXTFORMAT_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 */
## fftools/ffmpeg_opt.c ##
4: 1bf975fa0e = 7: 049f720ae7 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] 27+ messages in thread
* [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
@ 2025-03-01 10:01 ` softworkz
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 2/7] fftools/ffprobe: Change to use textformat api softworkz
` (5 subsequent siblings)
6 siblings, 0 replies; 27+ messages in thread
From: softworkz @ 2025-03-01 10:01 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Soft Works, softworkz, Andreas Rheinhardt
From: softworkz <softworkz@hotmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
fftools/textformat/avtextformat.c | 671 +++++++++++++++++++++++++++++
fftools/textformat/avtextformat.h | 171 ++++++++
fftools/textformat/avtextwriters.h | 68 +++
fftools/textformat/tf_compact.c | 282 ++++++++++++
fftools/textformat/tf_default.c | 145 +++++++
fftools/textformat/tf_flat.c | 174 ++++++++
fftools/textformat/tf_ini.c | 160 +++++++
fftools/textformat/tf_json.c | 215 +++++++++
fftools/textformat/tf_xml.c | 221 ++++++++++
fftools/textformat/tw_avio.c | 129 ++++++
fftools/textformat/tw_buffer.c | 92 ++++
fftools/textformat/tw_stdout.c | 82 ++++
12 files changed, 2410 insertions(+)
create mode 100644 fftools/textformat/avtextformat.c
create mode 100644 fftools/textformat/avtextformat.h
create mode 100644 fftools/textformat/avtextwriters.h
create mode 100644 fftools/textformat/tf_compact.c
create mode 100644 fftools/textformat/tf_default.c
create mode 100644 fftools/textformat/tf_flat.c
create mode 100644 fftools/textformat/tf_ini.c
create mode 100644 fftools/textformat/tf_json.c
create mode 100644 fftools/textformat/tf_xml.c
create mode 100644 fftools/textformat/tw_avio.c
create mode 100644 fftools/textformat/tw_buffer.c
create mode 100644 fftools/textformat/tw_stdout.c
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
new file mode 100644
index 0000000000..1fba78b103
--- /dev/null
+++ b/fftools/textformat/avtextformat.c
@@ -0,0 +1,671 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libavutil/mem.h"
+#include "libavutil/avassert.h"
+#include "libavutil/bprint.h"
+#include "libavutil/error.h"
+#include "libavutil/hash.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/macros.h"
+#include "libavutil/opt.h"
+#include "avtextformat.h"
+
+#define SECTION_ID_NONE -1
+
+#define SHOW_OPTIONAL_FIELDS_AUTO -1
+#define SHOW_OPTIONAL_FIELDS_NEVER 0
+#define SHOW_OPTIONAL_FIELDS_ALWAYS 1
+
+static const struct {
+ double bin_val;
+ double dec_val;
+ const char *bin_str;
+ const char *dec_str;
+} si_prefixes[] = {
+ { 1.0, 1.0, "", "" },
+ { 1.024e3, 1e3, "Ki", "K" },
+ { 1.048576e6, 1e6, "Mi", "M" },
+ { 1.073741824e9, 1e9, "Gi", "G" },
+ { 1.099511627776e12, 1e12, "Ti", "T" },
+ { 1.125899906842624e15, 1e15, "Pi", "P" },
+};
+
+static const char *avtext_context_get_formatter_name(void *p)
+{
+ AVTextFormatContext *tctx = p;
+ return tctx->formatter->name;
+}
+
+#define OFFSET(x) offsetof(AVTextFormatContext, x)
+
+static const AVOption textcontext_options[] = {
+ { "string_validation", "set string validation mode",
+ OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, 0, AV_TEXTFORMAT_STRING_VALIDATION_NB-1, .unit = "sv" },
+ { "sv", "set string validation mode",
+ OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, 0, AV_TEXTFORMAT_STRING_VALIDATION_NB-1, .unit = "sv" },
+ { "ignore", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_IGNORE}, .unit = "sv" },
+ { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, .unit = "sv" },
+ { "fail", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_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 *trextcontext_child_next(void *obj, void *prev)
+{
+ AVTextFormatContext *ctx = obj;
+ if (!prev && ctx->formatter && ctx->formatter->priv_class && ctx->priv)
+ return ctx->priv;
+ return NULL;
+}
+
+static const AVClass textcontext_class = {
+ .class_name = "AVTextContext",
+ .item_name = avtext_context_get_formatter_name,
+ .option = textcontext_options,
+ .version = LIBAVUTIL_VERSION_INT,
+ .child_next = trextcontext_child_next,
+};
+
+static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size)
+{
+ int i;
+ av_bprintf(bp, "0X");
+ for (i = 0; i < ubuf_size; i++)
+ av_bprintf(bp, "%02X", ubuf[i]);
+}
+
+int avtext_context_close(AVTextFormatContext **ptctx)
+{
+ AVTextFormatContext *tctx = *ptctx;
+ int i;
+ int ret = 0;
+
+ if (!tctx)
+ return -1;
+
+ av_hash_freep(&tctx->hash);
+
+ av_hash_freep(&tctx->hash);
+
+ if (tctx->formatter->uninit)
+ tctx->formatter->uninit(tctx);
+ for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
+ av_bprint_finalize(&tctx->section_pbuf[i], NULL);
+ if (tctx->formatter->priv_class)
+ av_opt_free(tctx->priv);
+ av_freep(&tctx->priv);
+ av_opt_free(tctx);
+ av_freep(ptctx);
+ return ret;
+}
+
+
+int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
+ const struct AVTextFormatSection *sections, int nb_sections,
+ int show_value_unit,
+ int use_value_prefix,
+ int use_byte_value_binary_prefix,
+ int use_value_sexagesimal_format,
+ int show_optional_fields,
+ char *show_data_hash)
+{
+ AVTextFormatContext *tctx;
+ int i, ret = 0;
+
+ if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ tctx->show_value_unit = show_value_unit;
+ tctx->use_value_prefix = use_value_prefix;
+ tctx->use_byte_value_binary_prefix = use_byte_value_binary_prefix;
+ tctx->use_value_sexagesimal_format = use_value_sexagesimal_format;
+ tctx->show_optional_fields = show_optional_fields;
+
+ if (nb_sections > SECTION_MAX_NB_SECTIONS) {
+ av_log(tctx, AV_LOG_ERROR, "The number of section definitions (%d) is larger than the maximum allowed (%d)\n", nb_sections, SECTION_MAX_NB_SECTIONS);
+ goto fail;
+ }
+
+ tctx->class = &textcontext_class;
+ tctx->formatter = formatter;
+ tctx->level = -1;
+ tctx->sections = sections;
+ tctx->nb_sections = nb_sections;
+ tctx->writer = writer_context;
+
+ av_opt_set_defaults(tctx);
+
+ if (formatter->priv_class) {
+ void *priv_ctx = tctx->priv;
+ *(const AVClass **)priv_ctx = formatter->priv_class;
+ av_opt_set_defaults(priv_ctx);
+ }
+
+ /* convert options to dictionary */
+ if (args) {
+ AVDictionary *opts = NULL;
+ const AVDictionaryEntry *opt = NULL;
+
+ if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) {
+ av_log(tctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to textformat context\n", args);
+ av_dict_free(&opts);
+ goto fail;
+ }
+
+ while ((opt = av_dict_iterate(opts, opt))) {
+ if ((ret = av_opt_set(tctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) {
+ av_log(tctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to textformat context\n",
+ opt->key, opt->value);
+ av_dict_free(&opts);
+ goto fail;
+ }
+ }
+
+ av_dict_free(&opts);
+ }
+
+ if (show_data_hash) {
+ if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
+ if (ret == AVERROR(EINVAL)) {
+ const char *n;
+ av_log(NULL, AV_LOG_ERROR, "Unknown hash algorithm '%s'\nKnown algorithms:", show_data_hash);
+ for (i = 0; (n = av_hash_names(i)); i++)
+ av_log(NULL, AV_LOG_ERROR, " %s", n);
+ av_log(NULL, AV_LOG_ERROR, "\n");
+ }
+ return ret;
+ }
+ }
+
+ /* validate replace string */
+ {
+ const uint8_t *p = tctx->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, tctx->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(tctx, AV_LOG_ERROR,
+ "Invalid UTF8 sequence %s found in string validation replace '%s'\n",
+ bp.str, tctx->string_validation_replacement);
+ return ret;
+ }
+ }
+ }
+
+ for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
+ av_bprint_init(&tctx->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED);
+
+ if (tctx->formatter->init)
+ ret = tctx->formatter->init(tctx);
+ if (ret < 0)
+ goto fail;
+
+ *ptctx = tctx;
+
+ return 0;
+
+fail:
+ avtext_context_close(&tctx);
+ return ret;
+}
+
+/* Temporary definitions during refactoring */
+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";
+
+
+void avtext_print_section_header(AVTextFormatContext *tctx,
+ const void *data,
+ int section_id)
+{
+ tctx->level++;
+ av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
+
+ tctx->nb_item[tctx->level] = 0;
+ memset(tctx->nb_item_type[tctx->level], 0, sizeof(tctx->nb_item_type[tctx->level]));
+ tctx->section[tctx->level] = &tctx->sections[section_id];
+
+ if (tctx->formatter->print_section_header)
+ tctx->formatter->print_section_header(tctx, data);
+}
+
+void avtext_print_section_footer(AVTextFormatContext *tctx)
+{
+ int section_id = tctx->section[tctx->level]->id;
+ int parent_section_id = tctx->level ?
+ tctx->section[tctx->level-1]->id : SECTION_ID_NONE;
+
+ if (parent_section_id != SECTION_ID_NONE) {
+ tctx->nb_item[tctx->level - 1]++;
+ tctx->nb_item_type[tctx->level - 1][section_id]++;
+ }
+
+ if (tctx->formatter->print_section_footer)
+ tctx->formatter->print_section_footer(tctx);
+ tctx->level--;
+}
+
+void avtext_print_integer(AVTextFormatContext *tctx,
+ const char *key, int64_t val)
+{
+ const struct AVTextFormatSection *section = tctx->section[tctx->level];
+
+ if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) {
+ tctx->formatter->print_integer(tctx, key, val);
+ tctx->nb_item[tctx->level]++;
+ }
+}
+
+static inline int validate_string(AVTextFormatContext *tctx, 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 = src; *p;) {
+ uint32_t code;
+ int invalid = 0;
+ const uint8_t *p0 = p;
+
+ if (av_utf8_decode(&code, &p, endp, tctx->string_validation_utf8_flags) < 0) {
+ AVBPrint bp;
+ av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+ bprint_bytes(&bp, p0, p-p0);
+ av_log(tctx, AV_LOG_DEBUG,
+ "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src);
+ invalid = 1;
+ }
+
+ if (invalid) {
+ invalid_chars_nb++;
+
+ switch (tctx->string_validation) {
+ case AV_TEXTFORMAT_STRING_VALIDATION_FAIL:
+ av_log(tctx, AV_LOG_ERROR,
+ "Invalid UTF-8 sequence found in string '%s'\n", src);
+ ret = AVERROR_INVALIDDATA;
+ goto end;
+ break;
+
+ case AV_TEXTFORMAT_STRING_VALIDATION_REPLACE:
+ av_bprintf(&dstbuf, "%s", tctx->string_validation_replacement);
+ break;
+ }
+ }
+
+ if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
+ av_bprint_append_data(&dstbuf, p0, p-p0);
+ }
+
+ if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
+ av_log(tctx, AV_LOG_WARNING,
+ "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n",
+ invalid_chars_nb, src, tctx->string_validation_replacement);
+ }
+
+end:
+ av_bprint_finalize(&dstbuf, dstp);
+ return ret;
+}
+
+struct unit_value {
+ union { double d; int64_t i; } val;
+ const char *unit;
+};
+
+static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
+{
+ double vald;
+ int64_t vali;
+ int show_float = 0;
+
+ if (uv.unit == unit_second_str) {
+ vald = uv.val.d;
+ show_float = 1;
+ } else {
+ vald = vali = uv.val.i;
+ }
+
+ if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
+ double secs;
+ int hours, mins;
+ secs = vald;
+ mins = (int)secs / 60;
+ secs = secs - mins * 60;
+ hours = mins / 60;
+ mins %= 60;
+ snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs);
+ } else {
+ const char *prefix_string = "";
+
+ if (tctx->use_value_prefix && vald > 1) {
+ int64_t index;
+
+ if (uv.unit == unit_byte_str && tctx->use_byte_value_binary_prefix) {
+ index = (int64_t) (log2(vald)) / 10;
+ index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
+ vald /= si_prefixes[index].bin_val;
+ prefix_string = si_prefixes[index].bin_str;
+ } else {
+ index = (int64_t) (log10(vald)) / 3;
+ index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
+ vald /= si_prefixes[index].dec_val;
+ prefix_string = si_prefixes[index].dec_str;
+ }
+ vali = vald;
+ }
+
+ if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
+ snprintf(buf, buf_size, "%f", vald);
+ else
+ snprintf(buf, buf_size, "%"PRId64, vali);
+ av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || tctx->show_value_unit ? " " : "",
+ prefix_string, tctx->show_value_unit ? uv.unit : "");
+ }
+
+ return buf;
+}
+
+
+void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit)
+{
+ char val_str[128];
+ struct unit_value uv;
+ uv.val.i = value;
+ uv.unit = unit;
+ avtext_print_string(tctx, key, value_string(tctx, val_str, sizeof(val_str), uv), 0);
+}
+
+
+int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
+{
+ const struct AVTextFormatSection *section = tctx->section[tctx->level];
+ int ret = 0;
+
+ if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
+ (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
+ && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
+ && !(tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS)))
+ return 0;
+
+ if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) {
+ if (flags & AV_TEXTFORMAT_PRINT_STRING_VALIDATE) {
+ char *key1 = NULL, *val1 = NULL;
+ ret = validate_string(tctx, &key1, key);
+ if (ret < 0) goto end;
+ ret = validate_string(tctx, &val1, val);
+ if (ret < 0) goto end;
+ tctx->formatter->print_string(tctx, key1, val1);
+ end:
+ if (ret < 0) {
+ av_log(tctx, 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 {
+ tctx->formatter->print_string(tctx, key, val);
+ }
+
+ tctx->nb_item[tctx->level]++;
+ }
+
+ return ret;
+}
+
+void avtext_print_rational(AVTextFormatContext *tctx,
+ 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);
+ avtext_print_string(tctx, key, buf.str, 0);
+}
+
+void avtext_print_time(AVTextFormatContext *tctx, 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)) {
+ avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
+ } else {
+ double d = ts * av_q2d(*time_base);
+ struct unit_value uv;
+ uv.val.d = d;
+ uv.unit = unit_second_str;
+ value_string(tctx, buf, sizeof(buf), uv);
+ avtext_print_string(tctx, key, buf, 0);
+ }
+}
+
+void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration)
+{
+ if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
+ avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
+ } else {
+ avtext_print_integer(tctx, key, ts);
+ }
+}
+
+void avtext_print_data(AVTextFormatContext *tctx, const char *name,
+ const uint8_t *data, int size)
+{
+ AVBPrint bp;
+ int offset = 0, l, i;
+
+ av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+ av_bprintf(&bp, "\n");
+ while (size) {
+ av_bprintf(&bp, "%08x: ", offset);
+ l = FFMIN(size, 16);
+ for (i = 0; i < l; i++) {
+ av_bprintf(&bp, "%02x", data[i]);
+ if (i & 1)
+ av_bprintf(&bp, " ");
+ }
+ av_bprint_chars(&bp, ' ', 41 - 2 * i - i / 2);
+ for (i = 0; i < l; i++)
+ av_bprint_chars(&bp, data[i] - 32U < 95 ? data[i] : '.', 1);
+ av_bprintf(&bp, "\n");
+ offset += l;
+ data += l;
+ size -= l;
+ }
+ avtext_print_string(tctx, name, bp.str, 0);
+ av_bprint_finalize(&bp, NULL);
+}
+
+void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
+ const uint8_t *data, int size)
+{
+ char *p, buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
+
+ if (!tctx->hash)
+ return;
+ av_hash_init(tctx->hash);
+ av_hash_update(tctx->hash, data, size);
+ snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
+ p = buf + strlen(buf);
+ av_hash_final_hex(tctx->hash, p, buf + sizeof(buf) - p);
+ avtext_print_string(tctx, name, buf, 0);
+}
+
+void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
+ uint8_t *data, int size, const char *format,
+ int columns, int bytes, int offset_add)
+{
+ AVBPrint bp;
+ int offset = 0, l, i;
+
+ av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+ av_bprintf(&bp, "\n");
+ while (size) {
+ av_bprintf(&bp, "%08x: ", offset);
+ l = FFMIN(size, columns);
+ for (i = 0; i < l; i++) {
+ if (bytes == 1) av_bprintf(&bp, format, *data);
+ else if (bytes == 2) av_bprintf(&bp, format, AV_RN16(data));
+ else if (bytes == 4) av_bprintf(&bp, format, AV_RN32(data));
+ data += bytes;
+ size --;
+ }
+ av_bprintf(&bp, "\n");
+ offset += offset_add;
+ }
+ avtext_print_string(tctx, name, bp.str, 0);
+ av_bprint_finalize(&bp, NULL);
+}
+
+static const char *avtextwriter_context_get_writer_name(void *p)
+{
+ AVTextWriterContext *wctx = p;
+ return wctx->writer->name;
+}
+
+static void *writercontext_child_next(void *obj, void *prev)
+{
+ AVTextFormatContext *ctx = obj;
+ if (!prev && ctx->formatter && ctx->formatter->priv_class && ctx->priv)
+ return ctx->priv;
+ return NULL;
+}
+
+static const AVClass textwriter_class = {
+ .class_name = "AVTextWriterContext",
+ .item_name = avtextwriter_context_get_writer_name,
+ .version = LIBAVUTIL_VERSION_INT,
+ .child_next = writercontext_child_next,
+};
+
+
+int avtextwriter_context_close(AVTextWriterContext **pwctx)
+{
+ AVTextWriterContext *wctx = *pwctx;
+ int ret = 0;
+
+ if (!wctx)
+ return -1;
+
+ if (wctx->writer->uninit)
+ wctx->writer->uninit(wctx);
+ if (wctx->writer->priv_class)
+ av_opt_free(wctx->priv);
+ av_freep(&wctx->priv);
+ av_freep(pwctx);
+ return ret;
+}
+
+
+int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer)
+{
+ AVTextWriterContext *wctx;
+ int ret = 0;
+
+ if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ if (!(wctx->priv = av_mallocz(writer->priv_size))) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ if (writer->priv_class) {
+ void *priv_ctx = wctx->priv;
+ *(const AVClass **)priv_ctx = writer->priv_class;
+ av_opt_set_defaults(priv_ctx);
+ }
+
+ wctx->class = &textwriter_class;
+ wctx->writer = writer;
+
+ av_opt_set_defaults(wctx);
+
+
+ if (wctx->writer->init)
+ ret = wctx->writer->init(wctx);
+ if (ret < 0)
+ goto fail;
+
+ *pwctx = wctx;
+
+ return 0;
+
+fail:
+ avtextwriter_context_close(&wctx);
+ return ret;
+}
+
+static const AVTextFormatter *registered_formatters[7+1];
+static void formatters_register_all(void)
+{
+ static int initialized;
+
+ if (initialized)
+ return;
+ initialized = 1;
+
+ registered_formatters[0] = &avtextformatter_default;
+ registered_formatters[1] = &avtextformatter_compact;
+ registered_formatters[2] = &avtextformatter_csv;
+ registered_formatters[3] = &avtextformatter_flat;
+ registered_formatters[4] = &avtextformatter_ini;
+ registered_formatters[5] = &avtextformatter_json;
+ registered_formatters[6] = &avtextformatter_xml;
+}
+
+const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
+{
+ formatters_register_all();
+
+ for (int i = 0; registered_formatters[i]; i++)
+ if (!strcmp(registered_formatters[i]->name, name))
+ return registered_formatters[i];
+
+ return NULL;
+}
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
new file mode 100644
index 0000000000..b7b6e0eea0
--- /dev/null
+++ b/fftools/textformat/avtextformat.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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_TEXTFORMAT_AVTEXTFORMAT_H
+#define FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include "libavutil/attributes.h"
+#include "libavutil/dict.h"
+#include "libavformat/avio.h"
+#include "libavutil/bprint.h"
+#include "libavutil/rational.h"
+#include "libavutil/hash.h"
+#include "avtextwriters.h"
+
+#define SECTION_MAX_NB_CHILDREN 11
+
+
+struct AVTextFormatSection {
+ int id; ///< unique id identifying a section
+ const char *name;
+
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER 1 ///< the section only contains other sections, but has no data at its own level
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY 2 ///< the section contains an array of elements of the same type
+#define AV_TEXTFORMAT_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.
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE 8 ///< the section contains a type to distinguish multiple nested elements
+#define AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE 16 ///< the items in this array section should be numbered individually by type
+
+ int flags;
+ const 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
+ AVDictionary *entries_to_show;
+ const char *(* get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined
+ int show_all_entries;
+} AVTextFormatSection;
+
+typedef struct AVTextFormatContext AVTextFormatContext;
+
+#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
+#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
+
+typedef enum {
+ AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
+ AV_TEXTFORMAT_STRING_VALIDATION_REPLACE,
+ AV_TEXTFORMAT_STRING_VALIDATION_IGNORE,
+ AV_TEXTFORMAT_STRING_VALIDATION_NB
+} StringValidation;
+
+typedef struct AVTextFormatter {
+ const AVClass *priv_class; ///< private class of the formatter, if any
+ int priv_size; ///< private size for the formatter context
+ const char *name;
+
+ int (*init) (AVTextFormatContext *tctx);
+ void (*uninit)(AVTextFormatContext *tctx);
+
+ void (*print_section_header)(AVTextFormatContext *tctx, const void *data);
+ void (*print_section_footer)(AVTextFormatContext *tctx);
+ void (*print_integer) (AVTextFormatContext *tctx, const char *, int64_t);
+ void (*print_rational) (AVTextFormatContext *tctx, AVRational *q, char *sep);
+ void (*print_string) (AVTextFormatContext *tctx, const char *, const char *);
+ int flags; ///< a combination or AV_TEXTFORMAT__FLAG_*
+} AVTextFormatter;
+
+#define SECTION_MAX_NB_LEVELS 12
+#define SECTION_MAX_NB_SECTIONS 100
+
+struct AVTextFormatContext {
+ const AVClass *class; ///< class of the formatter
+ const AVTextFormatter *formatter; ///< the AVTextFormatter of which this is an instance
+ AVTextWriterContext *writer; ///< the AVTextWriterContext
+
+ char *name; ///< name of this formatter instance
+ void *priv; ///< private data for use by the filter
+
+ const struct AVTextFormatSection *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];
+ unsigned int nb_item_type[SECTION_MAX_NB_LEVELS][SECTION_MAX_NB_SECTIONS];
+
+ /** section per each level */
+ const struct AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
+ AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+ /// used by various formatters
+
+ int show_optional_fields;
+ int show_value_unit;
+ int use_value_prefix;
+ int use_byte_value_binary_prefix;
+ int use_value_sexagesimal_format;
+
+ struct AVHashContext *hash;
+
+ int string_validation;
+ char *string_validation_replacement;
+ unsigned int string_validation_utf8_flags;
+};
+
+#define AV_TEXTFORMAT_PRINT_STRING_OPTIONAL 1
+#define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
+
+int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer, const char *args,
+ const struct AVTextFormatSection *sections, int nb_sections,
+ int show_value_unit,
+ int use_value_prefix,
+ int use_byte_value_binary_prefix,
+ int use_value_sexagesimal_format,
+ int show_optional_fields,
+ char *show_data_hash);
+
+int avtext_context_close(AVTextFormatContext **tctx);
+
+
+void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id);
+
+void avtext_print_section_footer(AVTextFormatContext *tctx);
+
+void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
+
+int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags);
+
+void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit);
+
+void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep);
+
+void avtext_print_time(AVTextFormatContext *tctx, const char *key, int64_t ts, const AVRational *time_base, int is_duration);
+
+void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration);
+
+void avtext_print_data(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size);
+
+void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size);
+
+void avtext_print_integers(AVTextFormatContext *tctx, const char *name, uint8_t *data, int size,
+ const char *format, int columns, int bytes, int offset_add);
+
+const AVTextFormatter *avtext_get_formatter_by_name(const char *name);
+
+extern const AVTextFormatter avtextformatter_default;
+extern const AVTextFormatter avtextformatter_compact;
+extern const AVTextFormatter avtextformatter_csv;
+extern const AVTextFormatter avtextformatter_flat;
+extern const AVTextFormatter avtextformatter_ini;
+extern const AVTextFormatter avtextformatter_json;
+extern const AVTextFormatter avtextformatter_xml;
+
+#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
new file mode 100644
index 0000000000..b344881d05
--- /dev/null
+++ b/fftools/textformat/avtextwriters.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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_TEXTFORMAT_AVTEXTWRITERS_H
+#define FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include "libavutil/attributes.h"
+#include "libavutil/dict.h"
+#include "libavformat/avio.h"
+#include "libavutil/bprint.h"
+#include "libavutil/rational.h"
+#include "libavutil/hash.h"
+
+typedef struct AVTextWriterContext AVTextWriterContext;
+
+typedef struct AVTextWriter {
+ const AVClass *priv_class; ///< private class of the writer, if any
+ int priv_size; ///< private size for the writer private class
+ const char *name;
+
+ int (* init)(AVTextWriterContext *wctx);
+ void (* uninit)(AVTextWriterContext *wctx);
+ void (* writer_w8)(AVTextWriterContext *wctx, int b);
+ void (* writer_put_str)(AVTextWriterContext *wctx, const char *str);
+ void (* writer_printf)(AVTextWriterContext *wctx, const char *fmt, ...);
+} AVTextWriter;
+
+typedef struct AVTextWriterContext {
+ const AVClass *class; ///< class of the writer
+ const AVTextWriter *writer;
+ const char *name;
+ void *priv; ///< private data for use by the writer
+
+} AVTextWriterContext;
+
+
+int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer);
+
+int avtextwriter_context_close(AVTextWriterContext **pwctx);
+
+int avtextwriter_create_stdout(AVTextWriterContext **pwctx);
+
+int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit);
+
+int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit);
+
+int avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer);
+
+#endif /* FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H */
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
new file mode 100644
index 0000000000..ad07ca4bd0
--- /dev/null
+++ b/fftools/textformat/tf_compact.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/error.h>
+#include <libavutil/macros.h>
+#include <libavutil/opt.h>
+
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+
+#define DEFINE_FORMATTER_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 \
+}
+
+
+/* Compact output */
+
+/**
+ * Apply C-language-like string escaping.
+ */
+static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
+{
+ const char *p;
+
+ for (p = src; *p; p++) {
+ switch (*p) {
+ case '\b': av_bprintf(dst, "%s", "\\b"); break;
+ case '\f': av_bprintf(dst, "%s", "\\f"); break;
+ case '\n': av_bprintf(dst, "%s", "\\n"); break;
+ case '\r': av_bprintf(dst, "%s", "\\r"); break;
+ case '\\': av_bprintf(dst, "%s", "\\\\"); break;
+ default:
+ if (*p == sep)
+ av_bprint_chars(dst, '\\', 1);
+ av_bprint_chars(dst, *p, 1);
+ }
+ }
+ return dst->str;
+}
+
+/**
+ * Quote fields containing special characters, check RFC4180.
+ */
+static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
+{
+ char meta_chars[] = { sep, '"', '\n', '\r', '\0' };
+ int needs_quoting = !!src[strcspn(src, meta_chars)];
+
+ if (needs_quoting)
+ av_bprint_chars(dst, '"', 1);
+
+ for (; *src; src++) {
+ if (*src == '"')
+ av_bprint_chars(dst, '"', 1);
+ av_bprint_chars(dst, *src, 1);
+ }
+ if (needs_quoting)
+ av_bprint_chars(dst, '"', 1);
+ return dst->str;
+}
+
+static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
+{
+ return src;
+}
+
+typedef struct CompactContext {
+ const AVClass *class;
+ char *item_sep_str;
+ char item_sep;
+ int nokey;
+ int print_section;
+ char *escape_mode_str;
+ const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx);
+ int nested_section[SECTION_MAX_NB_LEVELS];
+ int has_nested_elems[SECTION_MAX_NB_LEVELS];
+ int terminate_line[SECTION_MAX_NB_LEVELS];
+} CompactContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(CompactContext, x)
+
+static const AVOption compact_options[]= {
+ {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 },
+ {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 },
+ {"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 },
+ {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 },
+ {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 },
+ {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(compact);
+
+static av_cold int compact_init(AVTextFormatContext *wctx)
+{
+ CompactContext *compact = wctx->priv;
+
+ if (strlen(compact->item_sep_str) != 1) {
+ av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
+ compact->item_sep_str);
+ return AVERROR(EINVAL);
+ }
+ compact->item_sep = compact->item_sep_str[0];
+
+ if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str;
+ else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str;
+ else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str;
+ else {
+ av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str);
+ return AVERROR(EINVAL);
+ }
+
+ return 0;
+}
+
+static void compact_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+ CompactContext *compact = wctx->priv;
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+ const struct AVTextFormatSection *parent_section = wctx->level ?
+ wctx->section[wctx->level-1] : NULL;
+ compact->terminate_line[wctx->level] = 1;
+ compact->has_nested_elems[wctx->level] = 0;
+
+ av_bprint_clear(&wctx->section_pbuf[wctx->level]);
+ if (parent_section &&
+ (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ||
+ (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) &&
+ !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) {
+
+ /* define a prefix for elements not contained in an array or
+ in a wrapper, or for array elements with a type */
+ const char *element_name = (char *)av_x_if_null(section->element_name, section->name);
+ AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
+
+ compact->nested_section[wctx->level] = 1;
+ compact->has_nested_elems[wctx->level-1] = 1;
+
+ av_bprintf(section_pbuf, "%s%s",
+ wctx->section_pbuf[wctx->level-1].str, element_name);
+
+ if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
+ // add /TYPE to prefix
+ av_bprint_chars(section_pbuf, '/', 1);
+
+ // normalize section type, replace special characters and lower case
+ for (const char *p = section->get_type(data); *p; p++) {
+ char c =
+ (*p >= '0' && *p <= '9') ||
+ (*p >= 'a' && *p <= 'z') ||
+ (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
+ av_bprint_chars(section_pbuf, c, 1);
+ }
+ }
+ av_bprint_chars(section_pbuf, ':', 1);
+
+ wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1];
+ } else {
+ if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) &&
+ wctx->level && wctx->nb_item[wctx->level-1])
+ writer_w8(wctx, compact->item_sep);
+ if (compact->print_section &&
+ !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
+ writer_printf(wctx, "%s%c", section->name, compact->item_sep);
+ }
+}
+
+static void compact_print_section_footer(AVTextFormatContext *wctx)
+{
+ CompactContext *compact = wctx->priv;
+
+ if (!compact->nested_section[wctx->level] &&
+ compact->terminate_line[wctx->level] &&
+ !(wctx->section[wctx->level]->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
+ writer_w8(wctx, '\n');
+}
+
+static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+ CompactContext *compact = wctx->priv;
+ AVBPrint buf;
+
+ if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+ if (!compact->nokey)
+ writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+ av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+ writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx));
+ av_bprint_finalize(&buf, NULL);
+}
+
+static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+ CompactContext *compact = wctx->priv;
+
+ if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+ if (!compact->nokey)
+ writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+ writer_printf(wctx, "%"PRId64, value);
+}
+
+const AVTextFormatter avtextformatter_compact = {
+ .name = "compact",
+ .priv_size = sizeof(CompactContext),
+ .init = compact_init,
+ .print_section_header = compact_print_section_header,
+ .print_section_footer = compact_print_section_footer,
+ .print_integer = compact_print_int,
+ .print_string = compact_print_str,
+ .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
+ .priv_class = &compact_class,
+};
+
+/* CSV output */
+
+#undef OFFSET
+#define OFFSET(x) offsetof(CompactContext, x)
+
+static const AVOption csv_options[] = {
+ {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 },
+ {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 },
+ {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
+ {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
+ {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(csv);
+
+const AVTextFormatter avtextformatter_csv = {
+ .name = "csv",
+ .priv_size = sizeof(CompactContext),
+ .init = compact_init,
+ .print_section_header = compact_print_section_header,
+ .print_section_footer = compact_print_section_footer,
+ .print_integer = compact_print_int,
+ .print_string = compact_print_str,
+ .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
+ .priv_class = &csv_class,
+};
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
new file mode 100644
index 0000000000..9625dd813b
--- /dev/null
+++ b/fftools/textformat/tf_default.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_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_FORMATTER_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)
+{
+ int 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(AVTextFormatContext *wctx, const void *data)
+{
+ DefaultContext *def = wctx->priv;
+ char buf[32];
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+ const struct AVTextFormatSection *parent_section = wctx->level ?
+ wctx->section[wctx->level-1] : NULL;
+
+ av_bprint_clear(&wctx->section_pbuf[wctx->level]);
+ if (parent_section &&
+ !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_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 & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
+ writer_printf(wctx, "[%s]\n", upcase_string(buf, sizeof(buf), section->name));
+}
+
+static void default_print_section_footer(AVTextFormatContext *wctx)
+{
+ DefaultContext *def = wctx->priv;
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+ char buf[32];
+
+ if (def->noprint_wrappers || def->nested_section[wctx->level])
+ return;
+
+ if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
+ writer_printf(wctx, "[/%s]\n", upcase_string(buf, sizeof(buf), section->name));
+}
+
+static void default_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+ DefaultContext *def = wctx->priv;
+
+ if (!def->nokey)
+ writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+ writer_printf(wctx, "%s\n", value);
+}
+
+static void default_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+ DefaultContext *def = wctx->priv;
+
+ if (!def->nokey)
+ writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+ writer_printf(wctx, "%"PRId64"\n", value);
+}
+
+const AVTextFormatter avtextformatter_default = {
+ .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 = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
+ .priv_class = &default_class,
+};
\ No newline at end of file
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
new file mode 100644
index 0000000000..afdc494aee
--- /dev/null
+++ b/fftools/textformat/tf_flat.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/error.h>
+#include <libavutil/macros.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_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 \
+}
+
+
+/* Flat output */
+
+typedef struct FlatContext {
+ const AVClass *class;
+ const char *sep_str;
+ char sep;
+ int hierarchical;
+} FlatContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(FlatContext, x)
+
+static const AVOption flat_options[]= {
+ {"sep_char", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, 0, 0 },
+ {"s", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, 0, 0 },
+ {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(flat);
+
+static av_cold int flat_init(AVTextFormatContext *wctx)
+{
+ FlatContext *flat = wctx->priv;
+
+ if (strlen(flat->sep_str) != 1) {
+ av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
+ flat->sep_str);
+ return AVERROR(EINVAL);
+ }
+ flat->sep = flat->sep_str[0];
+
+ return 0;
+}
+
+static const char *flat_escape_key_str(AVBPrint *dst, const char *src, const char sep)
+{
+ const char *p;
+
+ for (p = src; *p; p++) {
+ if (!((*p >= '0' && *p <= '9') ||
+ (*p >= 'a' && *p <= 'z') ||
+ (*p >= 'A' && *p <= 'Z')))
+ av_bprint_chars(dst, '_', 1);
+ else
+ av_bprint_chars(dst, *p, 1);
+ }
+ return dst->str;
+}
+
+static const char *flat_escape_value_str(AVBPrint *dst, const char *src)
+{
+ const char *p;
+
+ for (p = src; *p; p++) {
+ switch (*p) {
+ case '\n': av_bprintf(dst, "%s", "\\n"); break;
+ case '\r': av_bprintf(dst, "%s", "\\r"); break;
+ case '\\': av_bprintf(dst, "%s", "\\\\"); break;
+ case '"': av_bprintf(dst, "%s", "\\\""); break;
+ case '`': av_bprintf(dst, "%s", "\\`"); break;
+ case '$': av_bprintf(dst, "%s", "\\$"); break;
+ default: av_bprint_chars(dst, *p, 1); break;
+ }
+ }
+ return dst->str;
+}
+
+static void flat_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+ FlatContext *flat = wctx->priv;
+ AVBPrint *buf = &wctx->section_pbuf[wctx->level];
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+ const struct AVTextFormatSection *parent_section = wctx->level ?
+ wctx->section[wctx->level-1] : NULL;
+
+ /* build section header */
+ av_bprint_clear(buf);
+ if (!parent_section)
+ return;
+ av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+
+ if (flat->hierarchical ||
+ !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) {
+ av_bprintf(buf, "%s%s", wctx->section[wctx->level]->name, flat->sep_str);
+
+ if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
+ int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ?
+ wctx->nb_item_type[wctx->level-1][section->id] :
+ wctx->nb_item[wctx->level-1];
+ av_bprintf(buf, "%d%s", n, flat->sep_str);
+ }
+ }
+}
+
+static void flat_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+ writer_printf(wctx, "%s%s=%"PRId64"\n", wctx->section_pbuf[wctx->level].str, key, value);
+}
+
+static void flat_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+ FlatContext *flat = wctx->priv;
+ AVBPrint buf;
+
+ writer_put_str(wctx, wctx->section_pbuf[wctx->level].str);
+ av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+ writer_printf(wctx, "%s=", flat_escape_key_str(&buf, key, flat->sep));
+ av_bprint_clear(&buf);
+ writer_printf(wctx, "\"%s\"\n", flat_escape_value_str(&buf, value));
+ av_bprint_finalize(&buf, NULL);
+}
+
+const AVTextFormatter avtextformatter_flat = {
+ .name = "flat",
+ .priv_size = sizeof(FlatContext),
+ .init = flat_init,
+ .print_section_header = flat_print_section_header,
+ .print_integer = flat_print_int,
+ .print_string = flat_print_str,
+ .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
+ .priv_class = &flat_class,
+};
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
new file mode 100644
index 0000000000..99a9af5690
--- /dev/null
+++ b/fftools/textformat/tf_ini.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_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;
+
+/* INI format output */
+
+typedef struct INIContext {
+ const AVClass *class;
+ int hierarchical;
+} INIContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(INIContext, x)
+
+static const AVOption ini_options[] = {
+ {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+ {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(ini);
+
+static char *ini_escape_str(AVBPrint *dst, const char *src)
+{
+ int i = 0;
+ char c = 0;
+
+ while (c = src[i++]) {
+ switch (c) {
+ case '\b': av_bprintf(dst, "%s", "\\b"); break;
+ case '\f': av_bprintf(dst, "%s", "\\f"); break;
+ case '\n': av_bprintf(dst, "%s", "\\n"); break;
+ case '\r': av_bprintf(dst, "%s", "\\r"); break;
+ case '\t': av_bprintf(dst, "%s", "\\t"); break;
+ case '\\':
+ case '#' :
+ case '=' :
+ case ':' : av_bprint_chars(dst, '\\', 1);
+ default:
+ if ((unsigned char)c < 32)
+ av_bprintf(dst, "\\x00%02x", c & 0xff);
+ else
+ av_bprint_chars(dst, c, 1);
+ break;
+ }
+ }
+ return dst->str;
+}
+
+static void ini_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+ INIContext *ini = wctx->priv;
+ AVBPrint *buf = &wctx->section_pbuf[wctx->level];
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+ const struct AVTextFormatSection *parent_section = wctx->level ?
+ wctx->section[wctx->level-1] : NULL;
+
+ av_bprint_clear(buf);
+ if (!parent_section) {
+ writer_put_str(wctx, "# ffprobe output\n\n");
+ return;
+ }
+
+ if (wctx->nb_item[wctx->level-1])
+ writer_w8(wctx, '\n');
+
+ av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+ if (ini->hierarchical ||
+ !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) {
+ av_bprintf(buf, "%s%s", buf->str[0] ? "." : "", wctx->section[wctx->level]->name);
+
+ if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
+ int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ?
+ wctx->nb_item_type[wctx->level-1][section->id] :
+ wctx->nb_item[wctx->level-1];
+ av_bprintf(buf, ".%d", n);
+ }
+ }
+
+ if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
+ writer_printf(wctx, "[%s]\n", buf->str);
+}
+
+static void ini_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+ AVBPrint buf;
+
+ av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+ writer_printf(wctx, "%s=", ini_escape_str(&buf, key));
+ av_bprint_clear(&buf);
+ writer_printf(wctx, "%s\n", ini_escape_str(&buf, value));
+ av_bprint_finalize(&buf, NULL);
+}
+
+static void ini_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+ writer_printf(wctx, "%s=%"PRId64"\n", key, value);
+}
+
+const AVTextFormatter avtextformatter_ini = {
+ .name = "ini",
+ .priv_size = sizeof(INIContext),
+ .print_section_header = ini_print_section_header,
+ .print_integer = ini_print_int,
+ .print_string = ini_print_str,
+ .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
+ .priv_class = &ini_class,
+};
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
new file mode 100644
index 0000000000..4579c8a3d9
--- /dev/null
+++ b/fftools/textformat/tf_json.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_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 \
+}
+
+
+/* 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_FORMATTER_CLASS(json);
+
+static av_cold int json_init(AVTextFormatContext *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() writer_printf(wctx, "%*c", json->indent_level * 4, ' ')
+
+static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+ JSONContext *json = wctx->priv;
+ AVBPrint buf;
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+ const struct AVTextFormatSection *parent_section = wctx->level ?
+ wctx->section[wctx->level-1] : NULL;
+
+ if (wctx->level && wctx->nb_item[wctx->level-1])
+ writer_put_str(wctx, ",\n");
+
+ if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
+ writer_put_str(wctx, "{\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 & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
+ writer_printf(wctx, "\"%s\": [\n", buf.str);
+ } else if (parent_section && !(parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) {
+ writer_printf(wctx, "\"%s\": {%s", buf.str, json->item_start_end);
+ } else {
+ writer_printf(wctx, "{%s", json->item_start_end);
+
+ /* this is required so the parser can distinguish between packets and frames */
+ if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE) {
+ if (!json->compact)
+ JSON_INDENT();
+ writer_printf(wctx, "\"type\": \"%s\"", section->name);
+ wctx->nb_item[wctx->level]++;
+ }
+ }
+ av_bprint_finalize(&buf, NULL);
+ }
+}
+
+static void json_print_section_footer(AVTextFormatContext *wctx)
+{
+ JSONContext *json = wctx->priv;
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+ if (wctx->level == 0) {
+ json->indent_level--;
+ writer_put_str(wctx, "\n}\n");
+ } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
+ writer_w8(wctx, '\n');
+ json->indent_level--;
+ JSON_INDENT();
+ writer_w8(wctx, ']');
+ } else {
+ writer_put_str(wctx, json->item_start_end);
+ json->indent_level--;
+ if (!json->compact)
+ JSON_INDENT();
+ writer_w8(wctx, '}');
+ }
+}
+
+static inline void json_print_item_str(AVTextFormatContext *wctx,
+ const char *key, const char *value)
+{
+ AVBPrint buf;
+
+ av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+ writer_printf(wctx, "\"%s\":", json_escape_str(&buf, key, wctx));
+ av_bprint_clear(&buf);
+ writer_printf(wctx, " \"%s\"", json_escape_str(&buf, value, wctx));
+ av_bprint_finalize(&buf, NULL);
+}
+
+static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+ JSONContext *json = wctx->priv;
+ const struct AVTextFormatSection *parent_section = wctx->level ?
+ wctx->section[wctx->level-1] : NULL;
+
+ if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE))
+ writer_put_str(wctx, json->item_sep);
+ if (!json->compact)
+ JSON_INDENT();
+ json_print_item_str(wctx, key, value);
+}
+
+static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+ JSONContext *json = wctx->priv;
+ const struct AVTextFormatSection *parent_section = wctx->level ?
+ wctx->section[wctx->level-1] : NULL;
+ AVBPrint buf;
+
+ if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE))
+ writer_put_str(wctx, json->item_sep);
+ if (!json->compact)
+ JSON_INDENT();
+
+ av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+ writer_printf(wctx, "\"%s\": %"PRId64, json_escape_str(&buf, key, wctx), value);
+ av_bprint_finalize(&buf, NULL);
+}
+
+const AVTextFormatter avtextformatter_json = {
+ .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 = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
+ .priv_class = &json_class,
+};
+
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
new file mode 100644
index 0000000000..04c43fb85d
--- /dev/null
+++ b/fftools/textformat/tf_xml.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/error.h>
+#include <libavutil/macros.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_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 \
+}
+
+/* XML output */
+
+typedef struct XMLContext {
+ const AVClass *class;
+ int within_tag;
+ int indent_level;
+ int fully_qualified;
+ int xsd_strict;
+} XMLContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(XMLContext, x)
+
+static const AVOption xml_options[] = {
+ {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+ {"q", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+ {"xsd_strict", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+ {"x", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+ {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(xml);
+
+static av_cold int xml_init(AVTextFormatContext *wctx)
+{
+ XMLContext *xml = wctx->priv;
+
+ if (xml->xsd_strict) {
+ xml->fully_qualified = 1;
+#define CHECK_COMPLIANCE(opt, opt_name) \
+ if (opt) { \
+ av_log(wctx, AV_LOG_ERROR, \
+ "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \
+ "You need to disable such option with '-no%s'\n", opt_name, opt_name); \
+ return AVERROR(EINVAL); \
+ }
+ ////CHECK_COMPLIANCE(show_private_data, "private");
+ CHECK_COMPLIANCE(wctx->show_value_unit, "unit");
+ CHECK_COMPLIANCE(wctx->use_value_prefix, "prefix");
+ }
+
+ return 0;
+}
+
+#define XML_INDENT() writer_printf(wctx, "%*c", xml->indent_level * 4, ' ')
+
+static void xml_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+ XMLContext *xml = wctx->priv;
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+ const struct AVTextFormatSection *parent_section = wctx->level ?
+ wctx->section[wctx->level-1] : NULL;
+
+ if (wctx->level == 0) {
+ const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ "xmlns:ffprobe=\"http://www.ffmpeg.org/schema/ffprobe\" "
+ "xsi:schemaLocation=\"http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd\"";
+
+ writer_put_str(wctx, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ writer_printf(wctx, "<%sffprobe%s>\n",
+ xml->fully_qualified ? "ffprobe:" : "",
+ xml->fully_qualified ? qual : "");
+ return;
+ }
+
+ if (xml->within_tag) {
+ xml->within_tag = 0;
+ writer_put_str(wctx, ">\n");
+ }
+
+ if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
+ wctx->level && wctx->nb_item[wctx->level-1])
+ writer_w8(wctx, '\n');
+ xml->indent_level++;
+
+ if (section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS)) {
+ XML_INDENT(); writer_printf(wctx, "<%s", section->name);
+
+ if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
+ AVBPrint buf;
+ av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+ av_bprint_escape(&buf, section->get_type(data), NULL,
+ AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
+ writer_printf(wctx, " type=\"%s\"", buf.str);
+ }
+ writer_printf(wctx, ">\n", section->name);
+ } else {
+ XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
+ xml->within_tag = 1;
+ }
+}
+
+static void xml_print_section_footer(AVTextFormatContext *wctx)
+{
+ XMLContext *xml = wctx->priv;
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+ if (wctx->level == 0) {
+ writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
+ } else if (xml->within_tag) {
+ xml->within_tag = 0;
+ writer_put_str(wctx, "/>\n");
+ xml->indent_level--;
+ } else {
+ XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
+ xml->indent_level--;
+ }
+}
+
+static void xml_print_value(AVTextFormatContext *wctx, const char *key,
+ const char *str, int64_t num, const int is_int)
+{
+ AVBPrint buf;
+ XMLContext *xml = wctx->priv;
+ const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+ av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+
+ if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS) {
+ xml->indent_level++;
+ XML_INDENT();
+ av_bprint_escape(&buf, key, NULL,
+ AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
+ writer_printf(wctx, "<%s key=\"%s\"",
+ section->element_name, buf.str);
+ av_bprint_clear(&buf);
+
+ if (is_int) {
+ writer_printf(wctx, " value=\"%"PRId64"\"/>\n", num);
+ } else {
+ av_bprint_escape(&buf, str, NULL,
+ AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
+ writer_printf(wctx, " value=\"%s\"/>\n", buf.str);
+ }
+ xml->indent_level--;
+ } else {
+ if (wctx->nb_item[wctx->level])
+ writer_w8(wctx, ' ');
+
+ if (is_int) {
+ writer_printf(wctx, "%s=\"%"PRId64"\"", key, num);
+ } else {
+ av_bprint_escape(&buf, str, NULL,
+ AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
+ writer_printf(wctx, "%s=\"%s\"", key, buf.str);
+ }
+ }
+
+ av_bprint_finalize(&buf, NULL);
+}
+
+static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
+ xml_print_value(wctx, key, value, 0, 0);
+}
+
+static void xml_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+ xml_print_value(wctx, key, NULL, value, 1);
+}
+
+const AVTextFormatter avtextformatter_xml = {
+ .name = "xml",
+ .priv_size = sizeof(XMLContext),
+ .init = xml_init,
+ .print_section_header = xml_print_section_header,
+ .print_section_footer = xml_print_section_footer,
+ .print_integer = xml_print_int,
+ .print_string = xml_print_str,
+ .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
+ .priv_class = &xml_class,
+};
+
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
new file mode 100644
index 0000000000..e1605a1272
--- /dev/null
+++ b/fftools/textformat/tw_avio.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+
+#include "avtextwriters.h"
+#include "libavutil/opt.h"
+
+/* AVIO Writer */
+
+# define WRITER_NAME "aviowriter"
+
+typedef struct IOWriterContext {
+ const AVClass *class;
+ AVIOContext *avio_context;
+ int close_on_uninit;
+} IOWriterContext;
+
+static const char *iowriter_get_name(void *ctx)
+{
+ return WRITER_NAME;
+}
+
+static const AVClass iowriter_class = {
+ .class_name = WRITER_NAME,
+ .item_name = iowriter_get_name,
+};
+
+static av_cold void iowriter_uninit(AVTextWriterContext *wctx)
+{
+ IOWriterContext *ctx = wctx->priv;
+
+ if (ctx->close_on_uninit && ctx->avio_context) {
+ avio_flush(ctx->avio_context);
+ avio_close(ctx->avio_context);
+ }
+}
+
+static void io_w8(AVTextWriterContext *wctx, int b)
+{
+ IOWriterContext *ctx = wctx->priv;
+ avio_w8(ctx->avio_context, b);
+}
+
+static void io_put_str(AVTextWriterContext *wctx, const char *str)
+{
+ IOWriterContext *ctx = wctx->priv;
+ avio_write(ctx->avio_context, str, strlen(str));
+}
+
+static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+{
+ IOWriterContext *ctx = wctx->priv;
+ va_list ap;
+
+ va_start(ap, fmt);
+ avio_vprintf(ctx->avio_context, fmt, ap);
+ va_end(ap);
+}
+
+
+const AVTextWriter avtextwriter_avio = {
+ .name = WRITER_NAME,
+ .priv_size = sizeof(IOWriterContext),
+ .uninit = iowriter_uninit,
+ .priv_class = &iowriter_class,
+ .writer_put_str = io_put_str,
+ .writer_printf = io_printf,
+ .writer_w8 = io_w8
+};
+
+int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit)
+{
+ IOWriterContext *ctx;
+ int ret;
+
+
+ ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
+ if (ret < 0)
+ return ret;
+
+ ctx = (*pwctx)->priv;
+
+ if ((ret = avio_open(&ctx->avio_context, output_filename, AVIO_FLAG_WRITE)) < 0) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Failed to open output '%s' with error: %s\n", output_filename, av_err2str(ret));
+ avtextwriter_context_close(pwctx);
+ return ret;
+ }
+
+ ctx->close_on_uninit = close_on_uninit;
+
+ return ret;
+}
+
+
+int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
+{
+ IOWriterContext *ctx;
+ int ret;
+
+ ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
+ if (ret < 0)
+ return ret;
+
+ ctx = (*pwctx)->priv;
+ ctx->avio_context = avio_ctx;
+ ctx->close_on_uninit = close_on_uninit;
+
+ return ret;
+}
diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
new file mode 100644
index 0000000000..4aeec4afa0
--- /dev/null
+++ b/fftools/textformat/tw_buffer.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+
+#include "avtextwriters.h"
+#include "libavutil/opt.h"
+#include "libavutil/bprint.h"
+
+/* Buffer Writer */
+
+# define WRITER_NAME "bufferwriter"
+
+typedef struct BufferWriterContext {
+ const AVClass *class;
+ AVBPrint *buffer;
+} BufferWriterContext;
+
+static const char *bufferwriter_get_name(void *ctx)
+{
+ return WRITER_NAME;
+}
+
+static const AVClass bufferwriter_class = {
+ .class_name = WRITER_NAME,
+ .item_name = bufferwriter_get_name,
+};
+
+static void buffer_w8(AVTextWriterContext *wctx, int b)
+{
+ BufferWriterContext *ctx = wctx->priv;
+ av_bprintf(ctx->buffer, "%c", b);
+}
+
+static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
+{
+ BufferWriterContext *ctx = wctx->priv;
+ av_bprintf(ctx->buffer, "%s", str);
+}
+
+static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+{
+ BufferWriterContext *ctx = wctx->priv;
+
+ va_list vargs;
+ va_start(vargs, fmt);
+ av_vbprintf(ctx->buffer, fmt, vargs);
+ va_end(vargs);
+}
+
+
+const AVTextWriter avtextwriter_buffer = {
+ .name = WRITER_NAME,
+ .priv_size = sizeof(BufferWriterContext),
+ .priv_class = &bufferwriter_class,
+ .writer_put_str = buffer_put_str,
+ .writer_printf = buffer_printf,
+ .writer_w8 = buffer_w8
+};
+
+int avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer)
+{
+ BufferWriterContext *ctx;
+ int ret;
+
+ ret = avtextwriter_context_open(pwctx, &avtextwriter_buffer);
+ if (ret < 0)
+ return ret;
+
+ ctx = (*pwctx)->priv;
+ ctx->buffer = buffer;
+
+ return ret;
+}
diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
new file mode 100644
index 0000000000..b9a613ad61
--- /dev/null
+++ b/fftools/textformat/tw_stdout.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * 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
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextwriters.h"
+#include "libavutil/opt.h"
+
+/* STDOUT Writer */
+
+# define WRITER_NAME "stdoutwriter"
+
+typedef struct StdOutWriterContext {
+ const AVClass *class;
+} StdOutWriterContext;
+
+static const char *stdoutwriter_get_name(void *ctx)
+{
+ return WRITER_NAME;
+}
+
+static const AVClass stdoutwriter_class = {
+ .class_name = WRITER_NAME,
+ .item_name = stdoutwriter_get_name,
+};
+
+static inline void stdout_w8(AVTextWriterContext *wctx, int b)
+{
+ printf("%c", b);
+}
+
+static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
+{
+ printf("%s", str);
+}
+
+static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+}
+
+
+static const AVTextWriter avtextwriter_stdout = {
+ .name = WRITER_NAME,
+ .priv_size = sizeof(StdOutWriterContext),
+ .priv_class = &stdoutwriter_class,
+ .writer_put_str = stdout_put_str,
+ .writer_printf = stdout_printf,
+ .writer_w8 = stdout_w8
+};
+
+int avtextwriter_create_stdout(AVTextWriterContext **pwctx)
+{
+ int ret;
+
+ ret = avtextwriter_context_open(pwctx, &avtextwriter_stdout);
+
+ return ret;
+}
--
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] 27+ messages in thread
* [FFmpeg-devel] [PATCH v3 2/7] fftools/ffprobe: Change to use textformat api
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz
@ 2025-03-01 10:01 ` softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz
` (4 subsequent siblings)
6 siblings, 0 replies; 27+ messages in thread
From: softworkz @ 2025-03-01 10:01 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Soft Works, softworkz, Andreas Rheinhardt
From: softworkz <softworkz@hotmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
fftools/Makefile | 12 +
fftools/ffprobe.c | 1849 ++++-----------------------------------------
2 files changed, 142 insertions(+), 1719 deletions(-)
diff --git a/fftools/Makefile b/fftools/Makefile
index 4499799818..664b73b161 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -22,6 +22,18 @@ OBJS-ffmpeg += \
fftools/sync_queue.o \
fftools/thread_queue.o \
+OBJS-ffprobe += \
+ fftools/textformat/avtextformat.o \
+ fftools/textformat/tf_compact.o \
+ fftools/textformat/tf_default.o \
+ fftools/textformat/tf_flat.o \
+ fftools/textformat/tf_ini.o \
+ fftools/textformat/tf_json.o \
+ fftools/textformat/tf_xml.o \
+ fftools/textformat/tw_avio.o \
+ fftools/textformat/tw_buffer.o \
+ fftools/textformat/tw_stdout.o \
+
OBJS-ffplay += fftools/ffplay_renderer.o
define DOFFTOOL
diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index 7341731d2f..f398057df7 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -40,7 +40,6 @@
#include "libavutil/channel_layout.h"
#include "libavutil/display.h"
#include "libavutil/film_grain_params.h"
-#include "libavutil/hash.h"
#include "libavutil/hdr_dynamic_metadata.h"
#include "libavutil/iamf.h"
#include "libavutil/mastering_display_metadata.h"
@@ -66,11 +65,17 @@
#include "libpostproc/postprocess.h"
#include "libpostproc/version.h"
#include "libavfilter/version.h"
+#include "textformat/avtextformat.h"
#include "cmdutils.h"
#include "opt_common.h"
#include "libavutil/thread.h"
+// TEMPORARY DEFINES
+#define writer_print_section_header(w, d, s) avtext_print_section_header(w, d, s)
+#define writer_print_section_footer(w) avtext_print_section_footer(w)
+#define WriterContext AVTextFormatContext
+
// attached as opaque_ref to packets/frames
typedef struct FrameData {
int64_t pkt_pos;
@@ -156,10 +161,7 @@ static int find_stream_info = 1;
/* section structure definition */
-#define SECTION_MAX_NB_CHILDREN 11
-
typedef enum {
- SECTION_ID_NONE = -1,
SECTION_ID_CHAPTER,
SECTION_ID_CHAPTER_TAGS,
SECTION_ID_CHAPTERS,
@@ -228,25 +230,6 @@ typedef enum {
SECTION_ID_SUBTITLE,
} SectionID;
-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.
-#define SECTION_FLAG_HAS_TYPE 8 ///< the section contains a type to distinguish multiple nested elements
-
- int flags;
- const SectionID 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
- AVDictionary *entries_to_show;
- const char *(* get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined
- int show_all_entries;
-};
-
static const char *get_packet_side_data_type(const void *data)
{
const AVPacketSideData *sd = (const AVPacketSideData *)data;
@@ -270,75 +253,75 @@ static const char *get_stream_group_type(const void *data)
return av_x_if_null(avformat_stream_group_name(stg->type), "unknown");
}
-static struct section sections[] = {
- [SECTION_ID_CHAPTERS] = { SECTION_ID_CHAPTERS, "chapters", SECTION_FLAG_IS_ARRAY, { SECTION_ID_CHAPTER, -1 } },
+static struct AVTextFormatSection sections[] = {
+ [SECTION_ID_CHAPTERS] = { SECTION_ID_CHAPTERS, "chapters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_CHAPTER, -1 } },
[SECTION_ID_CHAPTER] = { SECTION_ID_CHAPTER, "chapter", 0, { SECTION_ID_CHAPTER_TAGS, -1 } },
- [SECTION_ID_CHAPTER_TAGS] = { SECTION_ID_CHAPTER_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "chapter_tags" },
+ [SECTION_ID_CHAPTER_TAGS] = { SECTION_ID_CHAPTER_TAGS, "tags", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "chapter_tags" },
[SECTION_ID_ERROR] = { SECTION_ID_ERROR, "error", 0, { -1 } },
[SECTION_ID_FORMAT] = { SECTION_ID_FORMAT, "format", 0, { SECTION_ID_FORMAT_TAGS, -1 } },
- [SECTION_ID_FORMAT_TAGS] = { SECTION_ID_FORMAT_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "format_tags" },
- [SECTION_ID_FRAMES] = { SECTION_ID_FRAMES, "frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME, SECTION_ID_SUBTITLE, -1 } },
+ [SECTION_ID_FORMAT_TAGS] = { SECTION_ID_FORMAT_TAGS, "tags", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "format_tags" },
+ [SECTION_ID_FRAMES] = { SECTION_ID_FRAMES, "frames", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME, SECTION_ID_SUBTITLE, -1 } },
[SECTION_ID_FRAME] = { SECTION_ID_FRAME, "frame", 0, { SECTION_ID_FRAME_TAGS, SECTION_ID_FRAME_SIDE_DATA_LIST, SECTION_ID_FRAME_LOGS, -1 } },
- [SECTION_ID_FRAME_TAGS] = { SECTION_ID_FRAME_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "frame_tags" },
- [SECTION_ID_FRAME_SIDE_DATA_LIST] ={ SECTION_ID_FRAME_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "frame_side_data_list" },
- [SECTION_ID_FRAME_SIDE_DATA] = { SECTION_ID_FRAME_SIDE_DATA, "side_data", SECTION_FLAG_HAS_VARIABLE_FIELDS|SECTION_FLAG_HAS_TYPE, { SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST, -1 }, .unique_name = "frame_side_data", .element_name = "side_datum", .get_type = get_frame_side_data_type },
- [SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST] = { SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST, "timecodes", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA_TIMECODE, -1 } },
+ [SECTION_ID_FRAME_TAGS] = { SECTION_ID_FRAME_TAGS, "tags", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "frame_tags" },
+ [SECTION_ID_FRAME_SIDE_DATA_LIST] ={ SECTION_ID_FRAME_SIDE_DATA_LIST, "side_data_list", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "frame_side_data_list" },
+ [SECTION_ID_FRAME_SIDE_DATA] = { SECTION_ID_FRAME_SIDE_DATA, "side_data", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS|AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE, { SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST, -1 }, .unique_name = "frame_side_data", .element_name = "side_datum", .get_type = get_frame_side_data_type },
+ [SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST] = { SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST, "timecodes", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA_TIMECODE, -1 } },
[SECTION_ID_FRAME_SIDE_DATA_TIMECODE] = { SECTION_ID_FRAME_SIDE_DATA_TIMECODE, "timecode", 0, { -1 } },
- [SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST] = { SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST, "components", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA_COMPONENT, -1 }, .element_name = "component", .unique_name = "frame_side_data_components" },
- [SECTION_ID_FRAME_SIDE_DATA_COMPONENT] = { SECTION_ID_FRAME_SIDE_DATA_COMPONENT, "component", SECTION_FLAG_HAS_VARIABLE_FIELDS|SECTION_FLAG_HAS_TYPE, { SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST, -1 }, .unique_name = "frame_side_data_component", .element_name = "component_entry", .get_type = get_raw_string_type },
- [SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST] = { SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST, "pieces", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA_PIECE, -1 }, .element_name = "piece", .unique_name = "frame_side_data_pieces" },
- [SECTION_ID_FRAME_SIDE_DATA_PIECE] = { SECTION_ID_FRAME_SIDE_DATA_PIECE, "piece", SECTION_FLAG_HAS_VARIABLE_FIELDS|SECTION_FLAG_HAS_TYPE, { -1 }, .element_name = "piece_entry", .unique_name = "frame_side_data_piece", .get_type = get_raw_string_type },
- [SECTION_ID_FRAME_LOGS] = { SECTION_ID_FRAME_LOGS, "logs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_LOG, -1 } },
+ [SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST] = { SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST, "components", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA_COMPONENT, -1 }, .element_name = "component", .unique_name = "frame_side_data_components" },
+ [SECTION_ID_FRAME_SIDE_DATA_COMPONENT] = { SECTION_ID_FRAME_SIDE_DATA_COMPONENT, "component", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS|AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE, { SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST, -1 }, .unique_name = "frame_side_data_component", .element_name = "component_entry", .get_type = get_raw_string_type },
+ [SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST] = { SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST, "pieces", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA_PIECE, -1 }, .element_name = "piece", .unique_name = "frame_side_data_pieces" },
+ [SECTION_ID_FRAME_SIDE_DATA_PIECE] = { SECTION_ID_FRAME_SIDE_DATA_PIECE, "piece", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS|AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE, { -1 }, .element_name = "piece_entry", .unique_name = "frame_side_data_piece", .get_type = get_raw_string_type },
+ [SECTION_ID_FRAME_LOGS] = { SECTION_ID_FRAME_LOGS, "logs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_LOG, -1 } },
[SECTION_ID_FRAME_LOG] = { SECTION_ID_FRAME_LOG, "log", 0, { -1 }, },
- [SECTION_ID_LIBRARY_VERSIONS] = { SECTION_ID_LIBRARY_VERSIONS, "library_versions", SECTION_FLAG_IS_ARRAY, { SECTION_ID_LIBRARY_VERSION, -1 } },
+ [SECTION_ID_LIBRARY_VERSIONS] = { SECTION_ID_LIBRARY_VERSIONS, "library_versions", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_LIBRARY_VERSION, -1 } },
[SECTION_ID_LIBRARY_VERSION] = { SECTION_ID_LIBRARY_VERSION, "library_version", 0, { -1 } },
- [SECTION_ID_PACKETS] = { SECTION_ID_PACKETS, "packets", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} },
- [SECTION_ID_PACKETS_AND_FRAMES] = { SECTION_ID_PACKETS_AND_FRAMES, "packets_and_frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} },
+ [SECTION_ID_PACKETS] = { SECTION_ID_PACKETS, "packets", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} },
+ [SECTION_ID_PACKETS_AND_FRAMES] = { SECTION_ID_PACKETS_AND_FRAMES, "packets_and_frames", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE, { SECTION_ID_PACKET, -1} },
[SECTION_ID_PACKET] = { SECTION_ID_PACKET, "packet", 0, { SECTION_ID_PACKET_TAGS, SECTION_ID_PACKET_SIDE_DATA_LIST, -1 } },
- [SECTION_ID_PACKET_TAGS] = { SECTION_ID_PACKET_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "packet_tags" },
- [SECTION_ID_PACKET_SIDE_DATA_LIST] ={ SECTION_ID_PACKET_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "packet_side_data_list" },
- [SECTION_ID_PACKET_SIDE_DATA] = { SECTION_ID_PACKET_SIDE_DATA, "side_data", SECTION_FLAG_HAS_VARIABLE_FIELDS|SECTION_FLAG_HAS_TYPE, { -1 }, .unique_name = "packet_side_data", .element_name = "side_datum", .get_type = get_packet_side_data_type },
- [SECTION_ID_PIXEL_FORMATS] = { SECTION_ID_PIXEL_FORMATS, "pixel_formats", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PIXEL_FORMAT, -1 } },
+ [SECTION_ID_PACKET_TAGS] = { SECTION_ID_PACKET_TAGS, "tags", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "packet_tags" },
+ [SECTION_ID_PACKET_SIDE_DATA_LIST] ={ SECTION_ID_PACKET_SIDE_DATA_LIST, "side_data_list", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "packet_side_data_list" },
+ [SECTION_ID_PACKET_SIDE_DATA] = { SECTION_ID_PACKET_SIDE_DATA, "side_data", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS|AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE, { -1 }, .unique_name = "packet_side_data", .element_name = "side_datum", .get_type = get_packet_side_data_type },
+ [SECTION_ID_PIXEL_FORMATS] = { SECTION_ID_PIXEL_FORMATS, "pixel_formats", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_PIXEL_FORMAT, -1 } },
[SECTION_ID_PIXEL_FORMAT] = { SECTION_ID_PIXEL_FORMAT, "pixel_format", 0, { SECTION_ID_PIXEL_FORMAT_FLAGS, SECTION_ID_PIXEL_FORMAT_COMPONENTS, -1 } },
[SECTION_ID_PIXEL_FORMAT_FLAGS] = { SECTION_ID_PIXEL_FORMAT_FLAGS, "flags", 0, { -1 }, .unique_name = "pixel_format_flags" },
- [SECTION_ID_PIXEL_FORMAT_COMPONENTS] = { SECTION_ID_PIXEL_FORMAT_COMPONENTS, "components", SECTION_FLAG_IS_ARRAY, {SECTION_ID_PIXEL_FORMAT_COMPONENT, -1 }, .unique_name = "pixel_format_components" },
+ [SECTION_ID_PIXEL_FORMAT_COMPONENTS] = { SECTION_ID_PIXEL_FORMAT_COMPONENTS, "components", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, {SECTION_ID_PIXEL_FORMAT_COMPONENT, -1 }, .unique_name = "pixel_format_components" },
[SECTION_ID_PIXEL_FORMAT_COMPONENT] = { SECTION_ID_PIXEL_FORMAT_COMPONENT, "component", 0, { -1 } },
[SECTION_ID_PROGRAM_STREAM_DISPOSITION] = { SECTION_ID_PROGRAM_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "program_stream_disposition" },
- [SECTION_ID_PROGRAM_STREAM_TAGS] = { SECTION_ID_PROGRAM_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_stream_tags" },
+ [SECTION_ID_PROGRAM_STREAM_TAGS] = { SECTION_ID_PROGRAM_STREAM_TAGS, "tags", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_stream_tags" },
[SECTION_ID_PROGRAM] = { SECTION_ID_PROGRAM, "program", 0, { SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_STREAMS, -1 } },
- [SECTION_ID_PROGRAM_STREAMS] = { SECTION_ID_PROGRAM_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM_STREAM, -1 }, .unique_name = "program_streams" },
+ [SECTION_ID_PROGRAM_STREAMS] = { SECTION_ID_PROGRAM_STREAMS, "streams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM_STREAM, -1 }, .unique_name = "program_streams" },
[SECTION_ID_PROGRAM_STREAM] = { SECTION_ID_PROGRAM_STREAM, "stream", 0, { SECTION_ID_PROGRAM_STREAM_DISPOSITION, SECTION_ID_PROGRAM_STREAM_TAGS, -1 }, .unique_name = "program_stream" },
- [SECTION_ID_PROGRAM_TAGS] = { SECTION_ID_PROGRAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_tags" },
+ [SECTION_ID_PROGRAM_TAGS] = { SECTION_ID_PROGRAM_TAGS, "tags", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_tags" },
[SECTION_ID_PROGRAM_VERSION] = { SECTION_ID_PROGRAM_VERSION, "program_version", 0, { -1 } },
- [SECTION_ID_PROGRAMS] = { SECTION_ID_PROGRAMS, "programs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM, -1 } },
+ [SECTION_ID_PROGRAMS] = { SECTION_ID_PROGRAMS, "programs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM, -1 } },
[SECTION_ID_STREAM_GROUP_STREAM_DISPOSITION] = { SECTION_ID_STREAM_GROUP_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_group_stream_disposition" },
- [SECTION_ID_STREAM_GROUP_STREAM_TAGS] = { SECTION_ID_STREAM_GROUP_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_group_stream_tags" },
+ [SECTION_ID_STREAM_GROUP_STREAM_TAGS] = { SECTION_ID_STREAM_GROUP_STREAM_TAGS, "tags", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_group_stream_tags" },
[SECTION_ID_STREAM_GROUP] = { SECTION_ID_STREAM_GROUP, "stream_group", 0, { SECTION_ID_STREAM_GROUP_TAGS, SECTION_ID_STREAM_GROUP_DISPOSITION, SECTION_ID_STREAM_GROUP_COMPONENTS, SECTION_ID_STREAM_GROUP_STREAMS, -1 } },
- [SECTION_ID_STREAM_GROUP_COMPONENTS] = { SECTION_ID_STREAM_GROUP_COMPONENTS, "components", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_COMPONENT, -1 }, .element_name = "component", .unique_name = "stream_group_components" },
- [SECTION_ID_STREAM_GROUP_COMPONENT] = { SECTION_ID_STREAM_GROUP_COMPONENT, "component", SECTION_FLAG_HAS_VARIABLE_FIELDS|SECTION_FLAG_HAS_TYPE, { SECTION_ID_STREAM_GROUP_SUBCOMPONENTS, -1 }, .unique_name = "stream_group_component", .element_name = "component_entry", .get_type = get_stream_group_type },
- [SECTION_ID_STREAM_GROUP_SUBCOMPONENTS] = { SECTION_ID_STREAM_GROUP_SUBCOMPONENTS, "subcomponents", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_SUBCOMPONENT, -1 }, .element_name = "component" },
- [SECTION_ID_STREAM_GROUP_SUBCOMPONENT] = { SECTION_ID_STREAM_GROUP_SUBCOMPONENT, "subcomponent", SECTION_FLAG_HAS_VARIABLE_FIELDS|SECTION_FLAG_HAS_TYPE, { SECTION_ID_STREAM_GROUP_PIECES, -1 }, .element_name = "subcomponent_entry", .get_type = get_raw_string_type },
- [SECTION_ID_STREAM_GROUP_PIECES] = { SECTION_ID_STREAM_GROUP_PIECES, "pieces", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_PIECE, -1 }, .element_name = "piece", .unique_name = "stream_group_pieces" },
- [SECTION_ID_STREAM_GROUP_PIECE] = { SECTION_ID_STREAM_GROUP_PIECE, "piece", SECTION_FLAG_HAS_VARIABLE_FIELDS|SECTION_FLAG_HAS_TYPE, { SECTION_ID_STREAM_GROUP_SUBPIECES, -1 }, .unique_name = "stream_group_piece", .element_name = "piece_entry", .get_type = get_raw_string_type },
- [SECTION_ID_STREAM_GROUP_SUBPIECES] = { SECTION_ID_STREAM_GROUP_SUBPIECES, "subpieces", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_SUBPIECE, -1 }, .element_name = "subpiece" },
- [SECTION_ID_STREAM_GROUP_SUBPIECE] = { SECTION_ID_STREAM_GROUP_SUBPIECE, "subpiece", SECTION_FLAG_HAS_VARIABLE_FIELDS|SECTION_FLAG_HAS_TYPE, { SECTION_ID_STREAM_GROUP_BLOCKS, -1 }, .element_name = "subpiece_entry", .get_type = get_raw_string_type },
- [SECTION_ID_STREAM_GROUP_BLOCKS] = { SECTION_ID_STREAM_GROUP_BLOCKS, "blocks", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_BLOCK, -1 }, .element_name = "block" },
- [SECTION_ID_STREAM_GROUP_BLOCK] = { SECTION_ID_STREAM_GROUP_BLOCK, "block", SECTION_FLAG_HAS_VARIABLE_FIELDS|SECTION_FLAG_HAS_TYPE, { -1 }, .element_name = "block_entry", .get_type = get_raw_string_type },
- [SECTION_ID_STREAM_GROUP_STREAMS] = { SECTION_ID_STREAM_GROUP_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_STREAM, -1 }, .unique_name = "stream_group_streams" },
+ [SECTION_ID_STREAM_GROUP_COMPONENTS] = { SECTION_ID_STREAM_GROUP_COMPONENTS, "components", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_COMPONENT, -1 }, .element_name = "component", .unique_name = "stream_group_components" },
+ [SECTION_ID_STREAM_GROUP_COMPONENT] = { SECTION_ID_STREAM_GROUP_COMPONENT, "component", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS|AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE, { SECTION_ID_STREAM_GROUP_SUBCOMPONENTS, -1 }, .unique_name = "stream_group_component", .element_name = "component_entry", .get_type = get_stream_group_type },
+ [SECTION_ID_STREAM_GROUP_SUBCOMPONENTS] = { SECTION_ID_STREAM_GROUP_SUBCOMPONENTS, "subcomponents", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_SUBCOMPONENT, -1 }, .element_name = "component" },
+ [SECTION_ID_STREAM_GROUP_SUBCOMPONENT] = { SECTION_ID_STREAM_GROUP_SUBCOMPONENT, "subcomponent", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS|AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE, { SECTION_ID_STREAM_GROUP_PIECES, -1 }, .element_name = "subcomponent_entry", .get_type = get_raw_string_type },
+ [SECTION_ID_STREAM_GROUP_PIECES] = { SECTION_ID_STREAM_GROUP_PIECES, "pieces", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_PIECE, -1 }, .element_name = "piece", .unique_name = "stream_group_pieces" },
+ [SECTION_ID_STREAM_GROUP_PIECE] = { SECTION_ID_STREAM_GROUP_PIECE, "piece", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS|AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE, { SECTION_ID_STREAM_GROUP_SUBPIECES, -1 }, .unique_name = "stream_group_piece", .element_name = "piece_entry", .get_type = get_raw_string_type },
+ [SECTION_ID_STREAM_GROUP_SUBPIECES] = { SECTION_ID_STREAM_GROUP_SUBPIECES, "subpieces", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_SUBPIECE, -1 }, .element_name = "subpiece" },
+ [SECTION_ID_STREAM_GROUP_SUBPIECE] = { SECTION_ID_STREAM_GROUP_SUBPIECE, "subpiece", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS|AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE, { SECTION_ID_STREAM_GROUP_BLOCKS, -1 }, .element_name = "subpiece_entry", .get_type = get_raw_string_type },
+ [SECTION_ID_STREAM_GROUP_BLOCKS] = { SECTION_ID_STREAM_GROUP_BLOCKS, "blocks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_BLOCK, -1 }, .element_name = "block" },
+ [SECTION_ID_STREAM_GROUP_BLOCK] = { SECTION_ID_STREAM_GROUP_BLOCK, "block", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS|AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE, { -1 }, .element_name = "block_entry", .get_type = get_raw_string_type },
+ [SECTION_ID_STREAM_GROUP_STREAMS] = { SECTION_ID_STREAM_GROUP_STREAMS, "streams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_STREAM, -1 }, .unique_name = "stream_group_streams" },
[SECTION_ID_STREAM_GROUP_STREAM] = { SECTION_ID_STREAM_GROUP_STREAM, "stream", 0, { SECTION_ID_STREAM_GROUP_STREAM_DISPOSITION, SECTION_ID_STREAM_GROUP_STREAM_TAGS, -1 }, .unique_name = "stream_group_stream" },
[SECTION_ID_STREAM_GROUP_DISPOSITION] = { SECTION_ID_STREAM_GROUP_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_group_disposition" },
- [SECTION_ID_STREAM_GROUP_TAGS] = { SECTION_ID_STREAM_GROUP_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_group_tags" },
- [SECTION_ID_STREAM_GROUPS] = { SECTION_ID_STREAM_GROUPS, "stream_groups", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP, -1 } },
- [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "root", SECTION_FLAG_IS_WRAPPER,
+ [SECTION_ID_STREAM_GROUP_TAGS] = { SECTION_ID_STREAM_GROUP_TAGS, "tags", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_group_tags" },
+ [SECTION_ID_STREAM_GROUPS] = { SECTION_ID_STREAM_GROUPS, "stream_groups", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP, -1 } },
+ [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "root", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER,
{ SECTION_ID_CHAPTERS, SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_PROGRAMS, SECTION_ID_STREAM_GROUPS, SECTION_ID_STREAMS,
SECTION_ID_PACKETS, SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_LIBRARY_VERSIONS,
SECTION_ID_PIXEL_FORMATS, -1} },
- [SECTION_ID_STREAMS] = { SECTION_ID_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM, -1 } },
+ [SECTION_ID_STREAMS] = { SECTION_ID_STREAMS, "streams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM, -1 } },
[SECTION_ID_STREAM] = { SECTION_ID_STREAM, "stream", 0, { SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAM_TAGS, SECTION_ID_STREAM_SIDE_DATA_LIST, -1 } },
[SECTION_ID_STREAM_DISPOSITION] = { SECTION_ID_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_disposition" },
- [SECTION_ID_STREAM_TAGS] = { SECTION_ID_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_tags" },
- [SECTION_ID_STREAM_SIDE_DATA_LIST] ={ SECTION_ID_STREAM_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "stream_side_data_list" },
- [SECTION_ID_STREAM_SIDE_DATA] = { SECTION_ID_STREAM_SIDE_DATA, "side_data", SECTION_FLAG_HAS_TYPE|SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .unique_name = "stream_side_data", .element_name = "side_datum", .get_type = get_packet_side_data_type },
+ [SECTION_ID_STREAM_TAGS] = { SECTION_ID_STREAM_TAGS, "tags", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_tags" },
+ [SECTION_ID_STREAM_SIDE_DATA_LIST] ={ SECTION_ID_STREAM_SIDE_DATA_LIST, "side_data_list", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "stream_side_data_list" },
+ [SECTION_ID_STREAM_SIDE_DATA] = { SECTION_ID_STREAM_SIDE_DATA, "side_data", AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE|AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .unique_name = "stream_side_data", .element_name = "side_datum", .get_type = get_packet_side_data_type },
[SECTION_ID_SUBTITLE] = { SECTION_ID_SUBTITLE, "subtitle", 0, { -1 } },
};
@@ -350,22 +333,6 @@ static const char *print_input_filename;
static const AVInputFormat *iformat = NULL;
static const char *output_filename = NULL;
-static struct AVHashContext *hash;
-
-static const struct {
- double bin_val;
- double dec_val;
- const char *bin_str;
- const char *dec_str;
-} si_prefixes[] = {
- { 1.0, 1.0, "", "" },
- { 1.024e3, 1e3, "Ki", "K" },
- { 1.048576e6, 1e6, "Mi", "M" },
- { 1.073741824e9, 1e9, "Gi", "G" },
- { 1.099511627776e12, 1e12, "Ti", "T" },
- { 1.125899906842624e15, 1e15, "Pi", "P" },
-};
-
static const char unit_second_str[] = "s" ;
static const char unit_hertz_str[] = "Hz" ;
static const char unit_byte_str[] = "byte" ;
@@ -441,1554 +408,11 @@ static void log_callback(void *ptr, int level, const char *fmt, va_list vl)
#endif
}
-struct unit_value {
- union { double d; int64_t i; } val;
- const char *unit;
-};
-
-static char *value_string(char *buf, int buf_size, struct unit_value uv)
-{
- double vald;
- int64_t vali;
- int show_float = 0;
-
- if (uv.unit == unit_second_str) {
- vald = uv.val.d;
- show_float = 1;
- } else {
- vald = vali = uv.val.i;
- }
-
- if (uv.unit == unit_second_str && use_value_sexagesimal_format) {
- double secs;
- int hours, mins;
- secs = vald;
- mins = (int)secs / 60;
- secs = secs - mins * 60;
- hours = mins / 60;
- mins %= 60;
- snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs);
- } else {
- const char *prefix_string = "";
-
- if (use_value_prefix && vald > 1) {
- int64_t index;
-
- if (uv.unit == unit_byte_str && use_byte_value_binary_prefix) {
- index = (int64_t) (log2(vald)) / 10;
- index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
- vald /= si_prefixes[index].bin_val;
- prefix_string = si_prefixes[index].bin_str;
- } else {
- index = (int64_t) (log10(vald)) / 3;
- index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
- vald /= si_prefixes[index].dec_val;
- prefix_string = si_prefixes[index].dec_str;
- }
- vali = vald;
- }
-
- if (show_float || (use_value_prefix && vald != (int64_t)vald))
- snprintf(buf, buf_size, "%f", vald);
- else
- snprintf(buf, buf_size, "%"PRId64, vali);
- av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || show_value_unit ? " " : "",
- prefix_string, show_value_unit ? uv.unit : "");
- }
-
- return buf;
-}
-
-/* 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, const void *data);
- void (*print_section_footer)(WriterContext *wctx);
- void (*print_integer) (WriterContext *wctx, const char *, int64_t);
- 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 12
-
-struct WriterContext {
- const AVClass *class; ///< class of the writer
- const Writer *writer; ///< the Writer of which this is an instance
- AVIOContext *avio; ///< the I/O context used to write
-
- void (* writer_w8)(WriterContext *wctx, int b);
- void (* writer_put_str)(WriterContext *wctx, const char *str);
- void (* writer_printf)(WriterContext *wctx, const char *fmt, ...);
-
- 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
-
- 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;
-};
-
-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,
-};
-
-static int writer_close(WriterContext **wctx)
-{
- int i;
- int ret = 0;
-
- if (!*wctx)
- return -1;
-
- 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);
- if ((*wctx)->writer->priv_class)
- av_opt_free((*wctx)->priv);
- av_freep(&((*wctx)->priv));
- av_opt_free(*wctx);
- if ((*wctx)->avio) {
- avio_flush((*wctx)->avio);
- ret = avio_close((*wctx)->avio);
- }
- av_freep(wctx);
- return ret;
-}
-
-static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size)
-{
- int i;
- av_bprintf(bp, "0X");
- for (i = 0; i < ubuf_size; i++)
- av_bprintf(bp, "%02X", ubuf[i]);
-}
-
-static inline void writer_w8_avio(WriterContext *wctx, int b)
-{
- avio_w8(wctx->avio, b);
-}
-
-static inline void writer_put_str_avio(WriterContext *wctx, const char *str)
-{
- avio_write(wctx->avio, str, strlen(str));
-}
-
-static inline void writer_printf_avio(WriterContext *wctx, const char *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- avio_vprintf(wctx->avio, fmt, ap);
- va_end(ap);
-}
-
-static inline void writer_w8_printf(WriterContext *wctx, int b)
-{
- printf("%c", b);
-}
-
-static inline void writer_put_str_printf(WriterContext *wctx, const char *str)
-{
- printf("%s", str);
-}
-
-static inline void writer_printf_printf(WriterContext *wctx, const char *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- vprintf(fmt, ap);
- va_end(ap);
-}
-
-static int writer_open(WriterContext **wctx, const Writer *writer, const char *args,
- const struct section *sections, int nb_sections, const char *output)
-{
- 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;
- const 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_iterate(opts, opt))) {
- 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;
- }
- }
- }
-
- if (!output_filename) {
- (*wctx)->writer_w8 = writer_w8_printf;
- (*wctx)->writer_put_str = writer_put_str_printf;
- (*wctx)->writer_printf = writer_printf_printf;
- } else {
- if ((ret = avio_open(&(*wctx)->avio, output, AVIO_FLAG_WRITE)) < 0) {
- av_log(*wctx, AV_LOG_ERROR,
- "Failed to open output '%s' with error: %s\n", output, av_err2str(ret));
- goto fail;
- }
- (*wctx)->writer_w8 = writer_w8_avio;
- (*wctx)->writer_put_str = writer_put_str_avio;
- (*wctx)->writer_printf = writer_printf_avio;
- }
-
- for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
- av_bprint_init(&(*wctx)->section_pbuf[i], 1, 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;
-}
-
-static inline void writer_print_section_header(WriterContext *wctx,
- const void *data,
- 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 (section_id == SECTION_ID_PACKETS_AND_FRAMES) {
- wctx->nb_section_packet = wctx->nb_section_frame =
- wctx->nb_section_packet_frame = 0;
- } else if (parent_section_id == SECTION_ID_PACKETS_AND_FRAMES) {
- wctx->nb_section_packet_frame = section_id == SECTION_ID_PACKET ?
- wctx->nb_section_packet : wctx->nb_section_frame;
- }
-
- if (wctx->writer->print_section_header)
- wctx->writer->print_section_header(wctx, data);
-}
-
-static inline 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 (parent_section_id == SECTION_ID_PACKETS_AND_FRAMES) {
- if (section_id == SECTION_ID_PACKET) wctx->nb_section_packet++;
- else wctx->nb_section_frame++;
- }
- if (wctx->writer->print_section_footer)
- wctx->writer->print_section_footer(wctx);
- wctx->level--;
-}
-
-static inline void writer_print_integer(WriterContext *wctx,
- const char *key, int64_t val)
-{
- const struct section *section = wctx->section[wctx->level];
-
- if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) {
- wctx->writer->print_integer(wctx, key, val);
- wctx->nb_item[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 = 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;
- break;
-
- 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;
-}
-
-#define PRINT_STRING_OPT 1
-#define PRINT_STRING_VALIDATE 2
-
-static inline 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 (show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
- (show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
- && (flags & PRINT_STRING_OPT)
- && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS)))
- return 0;
-
- if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 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;
-}
-
-static inline 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);
-}
-
-static void 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);
- }
-}
-
-static 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 void writer_print_data(WriterContext *wctx, const char *name,
- const uint8_t *data, int size)
-{
- AVBPrint bp;
- int offset = 0, l, i;
-
- av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
- av_bprintf(&bp, "\n");
- while (size) {
- av_bprintf(&bp, "%08x: ", offset);
- l = FFMIN(size, 16);
- for (i = 0; i < l; i++) {
- av_bprintf(&bp, "%02x", data[i]);
- if (i & 1)
- av_bprintf(&bp, " ");
- }
- av_bprint_chars(&bp, ' ', 41 - 2 * i - i / 2);
- for (i = 0; i < l; i++)
- av_bprint_chars(&bp, data[i] - 32U < 95 ? data[i] : '.', 1);
- av_bprintf(&bp, "\n");
- offset += l;
- data += l;
- size -= l;
- }
- writer_print_string(wctx, name, bp.str, 0);
- av_bprint_finalize(&bp, NULL);
-}
-
-static void writer_print_data_hash(WriterContext *wctx, const char *name,
- const uint8_t *data, int size)
-{
- char *p, buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
-
- if (!hash)
- return;
- av_hash_init(hash);
- av_hash_update(hash, data, size);
- snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(hash));
- p = buf + strlen(buf);
- av_hash_final_hex(hash, p, buf + sizeof(buf) - p);
- writer_print_string(wctx, name, buf, 0);
-}
-
-static void writer_print_integers(WriterContext *wctx, const char *name,
- uint8_t *data, int size, const char *format,
- int columns, int bytes, int offset_add)
-{
- AVBPrint bp;
- int offset = 0, l, i;
-
- av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
- av_bprintf(&bp, "\n");
- while (size) {
- av_bprintf(&bp, "%08x: ", offset);
- l = FFMIN(size, columns);
- for (i = 0; i < l; i++) {
- if (bytes == 1) av_bprintf(&bp, format, *data);
- else if (bytes == 2) av_bprintf(&bp, format, AV_RN16(data));
- else if (bytes == 4) av_bprintf(&bp, format, AV_RN32(data));
- data += bytes;
- size --;
- }
- av_bprintf(&bp, "\n");
- offset += offset_add;
- }
- writer_print_string(wctx, name, bp.str, 0);
- av_bprint_finalize(&bp, NULL);
-}
-
-#define writer_w8(wctx_, b_) (wctx_)->writer_w8(wctx_, b_)
-#define writer_put_str(wctx_, str_) (wctx_)->writer_put_str(wctx_, str_)
-#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer_printf(wctx_, fmt_, __VA_ARGS__)
-
-#define MAX_REGISTERED_WRITERS_NB 64
-
-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;
-}
-
-static 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)
-{
- int 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, const void *data)
-{
- 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)))
- writer_printf(wctx, "[%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)))
- writer_printf(wctx, "[/%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)
- writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
- writer_printf(wctx, "%s\n", value);
-}
-
-static void default_print_int(WriterContext *wctx, const char *key, int64_t value)
-{
- DefaultContext *def = wctx->priv;
-
- if (!def->nokey)
- writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
- writer_printf(wctx, "%"PRId64"\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,
-};
-
-/* Compact output */
-
-/**
- * Apply C-language-like string escaping.
- */
-static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
-{
- const char *p;
-
- for (p = src; *p; p++) {
- switch (*p) {
- case '\b': av_bprintf(dst, "%s", "\\b"); break;
- case '\f': av_bprintf(dst, "%s", "\\f"); break;
- case '\n': av_bprintf(dst, "%s", "\\n"); break;
- case '\r': av_bprintf(dst, "%s", "\\r"); break;
- case '\\': av_bprintf(dst, "%s", "\\\\"); break;
- default:
- if (*p == sep)
- av_bprint_chars(dst, '\\', 1);
- av_bprint_chars(dst, *p, 1);
- }
- }
- return dst->str;
-}
-
-/**
- * Quote fields containing special characters, check RFC4180.
- */
-static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
-{
- char meta_chars[] = { sep, '"', '\n', '\r', '\0' };
- int needs_quoting = !!src[strcspn(src, meta_chars)];
-
- if (needs_quoting)
- av_bprint_chars(dst, '"', 1);
-
- for (; *src; src++) {
- if (*src == '"')
- av_bprint_chars(dst, '"', 1);
- av_bprint_chars(dst, *src, 1);
- }
- if (needs_quoting)
- av_bprint_chars(dst, '"', 1);
- return dst->str;
-}
-
-static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
-{
- return src;
-}
-
-typedef struct CompactContext {
- const AVClass *class;
- char *item_sep_str;
- char item_sep;
- int nokey;
- int print_section;
- char *escape_mode_str;
- const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx);
- int nested_section[SECTION_MAX_NB_LEVELS];
- int has_nested_elems[SECTION_MAX_NB_LEVELS];
- int terminate_line[SECTION_MAX_NB_LEVELS];
-} CompactContext;
-
-#undef OFFSET
-#define OFFSET(x) offsetof(CompactContext, x)
-
-static const AVOption compact_options[]= {
- {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 },
- {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 },
- {"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 },
- {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 },
- {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 },
- {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {NULL},
-};
-
-DEFINE_WRITER_CLASS(compact);
-
-static av_cold int compact_init(WriterContext *wctx)
-{
- CompactContext *compact = wctx->priv;
-
- if (strlen(compact->item_sep_str) != 1) {
- av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
- compact->item_sep_str);
- return AVERROR(EINVAL);
- }
- compact->item_sep = compact->item_sep_str[0];
-
- if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str;
- else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str;
- else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str;
- else {
- av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str);
- return AVERROR(EINVAL);
- }
-
- return 0;
-}
-
-static void compact_print_section_header(WriterContext *wctx, const void *data)
-{
- CompactContext *compact = wctx->priv;
- const struct section *section = wctx->section[wctx->level];
- const struct section *parent_section = wctx->level ?
- wctx->section[wctx->level-1] : NULL;
- compact->terminate_line[wctx->level] = 1;
- compact->has_nested_elems[wctx->level] = 0;
-
- av_bprint_clear(&wctx->section_pbuf[wctx->level]);
- if (parent_section &&
- (section->flags & SECTION_FLAG_HAS_TYPE ||
- (!(section->flags & SECTION_FLAG_IS_ARRAY) &&
- !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))))) {
-
- /* define a prefix for elements not contained in an array or
- in a wrapper, or for array elements with a type */
- const char *element_name = (char *)av_x_if_null(section->element_name, section->name);
- AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
-
- compact->nested_section[wctx->level] = 1;
- compact->has_nested_elems[wctx->level-1] = 1;
-
- av_bprintf(section_pbuf, "%s%s",
- wctx->section_pbuf[wctx->level-1].str, element_name);
-
- if (section->flags & SECTION_FLAG_HAS_TYPE) {
- // add /TYPE to prefix
- av_bprint_chars(section_pbuf, '/', 1);
-
- // normalize section type, replace special characters and lower case
- for (const char *p = section->get_type(data); *p; p++) {
- char c =
- (*p >= '0' && *p <= '9') ||
- (*p >= 'a' && *p <= 'z') ||
- (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
- av_bprint_chars(section_pbuf, c, 1);
- }
- }
- av_bprint_chars(section_pbuf, ':', 1);
-
- wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1];
- } else {
- if (parent_section && !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)) &&
- wctx->level && wctx->nb_item[wctx->level-1])
- writer_w8(wctx, compact->item_sep);
- if (compact->print_section &&
- !(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
- writer_printf(wctx, "%s%c", section->name, compact->item_sep);
- }
-}
-
-static void compact_print_section_footer(WriterContext *wctx)
-{
- CompactContext *compact = wctx->priv;
-
- if (!compact->nested_section[wctx->level] &&
- compact->terminate_line[wctx->level] &&
- !(wctx->section[wctx->level]->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
- writer_w8(wctx, '\n');
-}
-
-static void compact_print_str(WriterContext *wctx, const char *key, const char *value)
-{
- CompactContext *compact = wctx->priv;
- AVBPrint buf;
-
- if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
- if (!compact->nokey)
- writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
- av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx));
- av_bprint_finalize(&buf, NULL);
-}
-
-static void compact_print_int(WriterContext *wctx, const char *key, int64_t value)
-{
- CompactContext *compact = wctx->priv;
-
- if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
- if (!compact->nokey)
- writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
- writer_printf(wctx, "%"PRId64, value);
-}
-
-static const Writer compact_writer = {
- .name = "compact",
- .priv_size = sizeof(CompactContext),
- .init = compact_init,
- .print_section_header = compact_print_section_header,
- .print_section_footer = compact_print_section_footer,
- .print_integer = compact_print_int,
- .print_string = compact_print_str,
- .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS,
- .priv_class = &compact_class,
-};
-
-/* CSV output */
-
-#undef OFFSET
-#define OFFSET(x) offsetof(CompactContext, x)
-
-static const AVOption csv_options[] = {
- {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 },
- {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 },
- {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
- {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
- {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {NULL},
-};
-
-DEFINE_WRITER_CLASS(csv);
-
-static const Writer csv_writer = {
- .name = "csv",
- .priv_size = sizeof(CompactContext),
- .init = compact_init,
- .print_section_header = compact_print_section_header,
- .print_section_footer = compact_print_section_footer,
- .print_integer = compact_print_int,
- .print_string = compact_print_str,
- .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS,
- .priv_class = &csv_class,
-};
-
-/* Flat output */
-
-typedef struct FlatContext {
- const AVClass *class;
- const char *sep_str;
- char sep;
- int hierarchical;
-} FlatContext;
-
-#undef OFFSET
-#define OFFSET(x) offsetof(FlatContext, x)
-
-static const AVOption flat_options[]= {
- {"sep_char", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, 0, 0 },
- {"s", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, 0, 0 },
- {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {NULL},
-};
-
-DEFINE_WRITER_CLASS(flat);
-
-static av_cold int flat_init(WriterContext *wctx)
-{
- FlatContext *flat = wctx->priv;
-
- if (strlen(flat->sep_str) != 1) {
- av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
- flat->sep_str);
- return AVERROR(EINVAL);
- }
- flat->sep = flat->sep_str[0];
-
- return 0;
-}
-
-static const char *flat_escape_key_str(AVBPrint *dst, const char *src, const char sep)
-{
- const char *p;
-
- for (p = src; *p; p++) {
- if (!((*p >= '0' && *p <= '9') ||
- (*p >= 'a' && *p <= 'z') ||
- (*p >= 'A' && *p <= 'Z')))
- av_bprint_chars(dst, '_', 1);
- else
- av_bprint_chars(dst, *p, 1);
- }
- return dst->str;
-}
-
-static const char *flat_escape_value_str(AVBPrint *dst, const char *src)
-{
- const char *p;
-
- for (p = src; *p; p++) {
- switch (*p) {
- case '\n': av_bprintf(dst, "%s", "\\n"); break;
- case '\r': av_bprintf(dst, "%s", "\\r"); break;
- case '\\': av_bprintf(dst, "%s", "\\\\"); break;
- case '"': av_bprintf(dst, "%s", "\\\""); break;
- case '`': av_bprintf(dst, "%s", "\\`"); break;
- case '$': av_bprintf(dst, "%s", "\\$"); break;
- default: av_bprint_chars(dst, *p, 1); break;
- }
- }
- return dst->str;
-}
-
-static void flat_print_section_header(WriterContext *wctx, const void *data)
-{
- FlatContext *flat = wctx->priv;
- AVBPrint *buf = &wctx->section_pbuf[wctx->level];
- const struct section *section = wctx->section[wctx->level];
- const struct section *parent_section = wctx->level ?
- wctx->section[wctx->level-1] : NULL;
-
- /* build section header */
- av_bprint_clear(buf);
- if (!parent_section)
- return;
- av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
-
- if (flat->hierarchical ||
- !(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) {
- av_bprintf(buf, "%s%s", wctx->section[wctx->level]->name, flat->sep_str);
-
- if (parent_section->flags & SECTION_FLAG_IS_ARRAY) {
- int n = parent_section->id == SECTION_ID_PACKETS_AND_FRAMES ?
- wctx->nb_section_packet_frame : wctx->nb_item[wctx->level-1];
- av_bprintf(buf, "%d%s", n, flat->sep_str);
- }
- }
-}
-
-static void flat_print_int(WriterContext *wctx, const char *key, int64_t value)
-{
- writer_printf(wctx, "%s%s=%"PRId64"\n", wctx->section_pbuf[wctx->level].str, key, value);
-}
-
-static void flat_print_str(WriterContext *wctx, const char *key, const char *value)
-{
- FlatContext *flat = wctx->priv;
- AVBPrint buf;
-
- writer_put_str(wctx, wctx->section_pbuf[wctx->level].str);
- av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_printf(wctx, "%s=", flat_escape_key_str(&buf, key, flat->sep));
- av_bprint_clear(&buf);
- writer_printf(wctx, "\"%s\"\n", flat_escape_value_str(&buf, value));
- av_bprint_finalize(&buf, NULL);
-}
-
-static const Writer flat_writer = {
- .name = "flat",
- .priv_size = sizeof(FlatContext),
- .init = flat_init,
- .print_section_header = flat_print_section_header,
- .print_integer = flat_print_int,
- .print_string = flat_print_str,
- .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS|WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
- .priv_class = &flat_class,
-};
-
-/* INI format output */
-
-typedef struct INIContext {
- const AVClass *class;
- int hierarchical;
-} INIContext;
-
-#undef OFFSET
-#define OFFSET(x) offsetof(INIContext, x)
-
-static const AVOption ini_options[] = {
- {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
- {NULL},
-};
-
-DEFINE_WRITER_CLASS(ini);
-
-static char *ini_escape_str(AVBPrint *dst, const char *src)
-{
- int i = 0;
- char c = 0;
-
- while (c = src[i++]) {
- switch (c) {
- case '\b': av_bprintf(dst, "%s", "\\b"); break;
- case '\f': av_bprintf(dst, "%s", "\\f"); break;
- case '\n': av_bprintf(dst, "%s", "\\n"); break;
- case '\r': av_bprintf(dst, "%s", "\\r"); break;
- case '\t': av_bprintf(dst, "%s", "\\t"); break;
- case '\\':
- case '#' :
- case '=' :
- case ':' : av_bprint_chars(dst, '\\', 1);
- default:
- if ((unsigned char)c < 32)
- av_bprintf(dst, "\\x00%02x", c & 0xff);
- else
- av_bprint_chars(dst, c, 1);
- break;
- }
- }
- return dst->str;
-}
-
-static void ini_print_section_header(WriterContext *wctx, const void *data)
-{
- INIContext *ini = wctx->priv;
- AVBPrint *buf = &wctx->section_pbuf[wctx->level];
- const struct section *section = wctx->section[wctx->level];
- const struct section *parent_section = wctx->level ?
- wctx->section[wctx->level-1] : NULL;
-
- av_bprint_clear(buf);
- if (!parent_section) {
- writer_put_str(wctx, "# ffprobe output\n\n");
- return;
- }
-
- if (wctx->nb_item[wctx->level-1])
- writer_w8(wctx, '\n');
-
- av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
- if (ini->hierarchical ||
- !(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) {
- av_bprintf(buf, "%s%s", buf->str[0] ? "." : "", wctx->section[wctx->level]->name);
-
- if (parent_section->flags & SECTION_FLAG_IS_ARRAY) {
- int n = parent_section->id == SECTION_ID_PACKETS_AND_FRAMES ?
- wctx->nb_section_packet_frame : wctx->nb_item[wctx->level-1];
- av_bprintf(buf, ".%d", n);
- }
- }
-
- if (!(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER)))
- writer_printf(wctx, "[%s]\n", buf->str);
-}
-
-static void ini_print_str(WriterContext *wctx, const char *key, const char *value)
-{
- AVBPrint buf;
-
- av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_printf(wctx, "%s=", ini_escape_str(&buf, key));
- av_bprint_clear(&buf);
- writer_printf(wctx, "%s\n", ini_escape_str(&buf, value));
- av_bprint_finalize(&buf, NULL);
-}
-
-static void ini_print_int(WriterContext *wctx, const char *key, int64_t value)
-{
- writer_printf(wctx, "%s=%"PRId64"\n", key, value);
-}
-
-static const Writer ini_writer = {
- .name = "ini",
- .priv_size = sizeof(INIContext),
- .print_section_header = ini_print_section_header,
- .print_integer = ini_print_int,
- .print_string = ini_print_str,
- .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS|WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
- .priv_class = &ini_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() writer_printf(wctx, "%*c", json->indent_level * 4, ' ')
-
-static void json_print_section_header(WriterContext *wctx, const void *data)
-{
- 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])
- writer_put_str(wctx, ",\n");
-
- if (section->flags & SECTION_FLAG_IS_WRAPPER) {
- writer_put_str(wctx, "{\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) {
- writer_printf(wctx, "\"%s\": [\n", buf.str);
- } else if (parent_section && !(parent_section->flags & SECTION_FLAG_IS_ARRAY)) {
- writer_printf(wctx, "\"%s\": {%s", buf.str, json->item_start_end);
- } else {
- writer_printf(wctx, "{%s", json->item_start_end);
-
- /* this is required so the parser can distinguish between packets and frames */
- if (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES) {
- if (!json->compact)
- JSON_INDENT();
- writer_printf(wctx, "\"type\": \"%s\"", section->name);
- wctx->nb_item[wctx->level]++;
- }
- }
- 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--;
- writer_put_str(wctx, "\n}\n");
- } else if (section->flags & SECTION_FLAG_IS_ARRAY) {
- writer_w8(wctx, '\n');
- json->indent_level--;
- JSON_INDENT();
- writer_w8(wctx, ']');
- } else {
- writer_put_str(wctx, json->item_start_end);
- json->indent_level--;
- if (!json->compact)
- JSON_INDENT();
- writer_w8(wctx, '}');
- }
-}
-
-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);
- writer_printf(wctx, "\"%s\":", json_escape_str(&buf, key, wctx));
- av_bprint_clear(&buf);
- writer_printf(wctx, " \"%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;
- const struct section *parent_section = wctx->level ?
- wctx->section[wctx->level-1] : NULL;
-
- if (wctx->nb_item[wctx->level] || (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES))
- writer_put_str(wctx, 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, int64_t value)
-{
- JSONContext *json = wctx->priv;
- const struct section *parent_section = wctx->level ?
- wctx->section[wctx->level-1] : NULL;
- AVBPrint buf;
-
- if (wctx->nb_item[wctx->level] || (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES))
- writer_put_str(wctx, json->item_sep);
- if (!json->compact)
- JSON_INDENT();
-
- av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_printf(wctx, "\"%s\": %"PRId64, 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,
-};
-
-/* XML output */
-
-typedef struct XMLContext {
- const AVClass *class;
- int within_tag;
- int indent_level;
- int fully_qualified;
- int xsd_strict;
-} XMLContext;
-
-#undef OFFSET
-#define OFFSET(x) offsetof(XMLContext, x)
-
-static const AVOption xml_options[] = {
- {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
- {"q", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
- {"xsd_strict", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
- {"x", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
- {NULL},
-};
-
-DEFINE_WRITER_CLASS(xml);
-
-static av_cold int xml_init(WriterContext *wctx)
-{
- XMLContext *xml = wctx->priv;
-
- if (xml->xsd_strict) {
- xml->fully_qualified = 1;
-#define CHECK_COMPLIANCE(opt, opt_name) \
- if (opt) { \
- av_log(wctx, AV_LOG_ERROR, \
- "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \
- "You need to disable such option with '-no%s'\n", opt_name, opt_name); \
- return AVERROR(EINVAL); \
- }
- CHECK_COMPLIANCE(show_private_data, "private");
- CHECK_COMPLIANCE(show_value_unit, "unit");
- CHECK_COMPLIANCE(use_value_prefix, "prefix");
- }
-
- return 0;
-}
-
-#define XML_INDENT() writer_printf(wctx, "%*c", xml->indent_level * 4, ' ')
-
-static void xml_print_section_header(WriterContext *wctx, const void *data)
-{
- XMLContext *xml = wctx->priv;
- const struct section *section = wctx->section[wctx->level];
- const struct section *parent_section = wctx->level ?
- wctx->section[wctx->level-1] : NULL;
-
- if (wctx->level == 0) {
- const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
- "xmlns:ffprobe=\"http://www.ffmpeg.org/schema/ffprobe\" "
- "xsi:schemaLocation=\"http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd\"";
-
- writer_put_str(wctx, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- writer_printf(wctx, "<%sffprobe%s>\n",
- xml->fully_qualified ? "ffprobe:" : "",
- xml->fully_qualified ? qual : "");
- return;
- }
-
- if (xml->within_tag) {
- xml->within_tag = 0;
- writer_put_str(wctx, ">\n");
- }
-
- if (parent_section && (parent_section->flags & SECTION_FLAG_IS_WRAPPER) &&
- wctx->level && wctx->nb_item[wctx->level-1])
- writer_w8(wctx, '\n');
- xml->indent_level++;
-
- if (section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_HAS_VARIABLE_FIELDS)) {
- XML_INDENT(); writer_printf(wctx, "<%s", section->name);
-
- if (section->flags & SECTION_FLAG_HAS_TYPE) {
- AVBPrint buf;
- av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
- av_bprint_escape(&buf, section->get_type(data), NULL,
- AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
- writer_printf(wctx, " type=\"%s\"", buf.str);
- }
- writer_printf(wctx, ">\n", section->name);
- } else {
- XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
- xml->within_tag = 1;
- }
-}
-
-static void xml_print_section_footer(WriterContext *wctx)
-{
- XMLContext *xml = wctx->priv;
- const struct section *section = wctx->section[wctx->level];
-
- if (wctx->level == 0) {
- writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
- } else if (xml->within_tag) {
- xml->within_tag = 0;
- writer_put_str(wctx, "/>\n");
- xml->indent_level--;
- } else {
- XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
- xml->indent_level--;
- }
-}
-
-static void xml_print_value(WriterContext *wctx, const char *key,
- const char *str, int64_t num, const int is_int)
-{
- AVBPrint buf;
- XMLContext *xml = wctx->priv;
- const struct section *section = wctx->section[wctx->level];
-
- av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
-
- if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) {
- xml->indent_level++;
- XML_INDENT();
- av_bprint_escape(&buf, key, NULL,
- AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
- writer_printf(wctx, "<%s key=\"%s\"",
- section->element_name, buf.str);
- av_bprint_clear(&buf);
-
- if (is_int) {
- writer_printf(wctx, " value=\"%"PRId64"\"/>\n", num);
- } else {
- av_bprint_escape(&buf, str, NULL,
- AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
- writer_printf(wctx, " value=\"%s\"/>\n", buf.str);
- }
- xml->indent_level--;
- } else {
- if (wctx->nb_item[wctx->level])
- writer_w8(wctx, ' ');
-
- if (is_int) {
- writer_printf(wctx, "%s=\"%"PRId64"\"", key, num);
- } else {
- av_bprint_escape(&buf, str, NULL,
- AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
- writer_printf(wctx, "%s=\"%s\"", key, buf.str);
- }
- }
-
- av_bprint_finalize(&buf, NULL);
-}
-
-static inline void xml_print_str(WriterContext *wctx, const char *key, const char *value) {
- xml_print_value(wctx, key, value, 0, 0);
-}
-
-static void xml_print_int(WriterContext *wctx, const char *key, int64_t value)
-{
- xml_print_value(wctx, key, NULL, value, 1);
-}
-
-static Writer xml_writer = {
- .name = "xml",
- .priv_size = sizeof(XMLContext),
- .init = xml_init,
- .print_section_header = xml_print_section_header,
- .print_section_footer = xml_print_section_footer,
- .print_integer = xml_print_int,
- .print_string = xml_print_str,
- .flags = WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
- .priv_class = &xml_class,
-};
-
-static 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);
-}
#define print_fmt(k, f, ...) do { \
av_bprint_clear(&pbuf); \
av_bprintf(&pbuf, f, __VA_ARGS__); \
- writer_print_string(w, k, pbuf.str, 0); \
+ avtext_print_string(w, k, pbuf.str, 0); \
} while (0)
#define print_list_fmt(k, f, n, m, ...) do { \
@@ -2000,28 +424,19 @@ static void writer_register_all(void)
av_bprintf(&pbuf, f, __VA_ARGS__); \
} \
} \
- writer_print_string(w, k, pbuf.str, 0); \
+ avtext_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_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)
-
-#define print_section_header(s) writer_print_section_header(w, NULL, s)
-#define print_section_header_data(s, d) writer_print_section_header(w, d, s)
-#define print_section_footer(s) writer_print_section_footer(w, s)
+#define print_int(k, v) avtext_print_integer(w, k, v)
+#define print_q(k, v, s) avtext_print_rational(w, k, v, s)
+#define print_str(k, v) avtext_print_string(w, k, v, 0)
+#define print_str_opt(k, v) avtext_print_string(w, k, v, AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
+#define print_str_validate(k, v) avtext_print_string(w, k, v, AV_TEXTFORMAT_PRINT_STRING_VALIDATE)
+#define print_time(k, v, tb) avtext_print_time(w, k, v, tb, 0)
+#define print_ts(k, v) avtext_print_ts(w, k, v, 0)
+#define print_duration_time(k, v, tb) avtext_print_time(w, k, v, tb, 1)
+#define print_duration_ts(k, v) avtext_print_ts(w, k, v, 1)
+#define print_val(k, v, u) avtext_print_unit_int(w, k, v, u)
#define REALLOCZ_ARRAY_STREAM(ptr, cur_n, new_n) \
{ \
@@ -2529,7 +944,7 @@ static void print_pkt_side_data(WriterContext *w,
double rotation = av_display_rotation_get((int32_t *)sd->data);
if (isnan(rotation))
rotation = 0;
- writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1);
+ avtext_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1);
print_int("rotation", rotation);
} else if (sd->type == AV_PKT_DATA_STEREO3D) {
const AVStereo3D *stereo = (AVStereo3D *)sd->data;
@@ -2626,8 +1041,8 @@ static void print_pkt_side_data(WriterContext *w,
} else if (sd->type == AV_PKT_DATA_WEBVTT_IDENTIFIER ||
sd->type == AV_PKT_DATA_WEBVTT_SETTINGS) {
if (do_show_data)
- writer_print_data(w, "data", sd->data, sd->size);
- writer_print_data_hash(w, "data_hash", sd->data, sd->size);
+ avtext_print_data(w, "data", sd->data, sd->size);
+ avtext_print_data_hash(w, "data_hash", sd->data, sd->size);
} else if (sd->type == AV_PKT_DATA_FRAME_CROPPING && sd->size >= sizeof(uint32_t) * 4) {
print_int("crop_top", AV_RL32(sd->data));
print_int("crop_bottom", AV_RL32(sd->data + 4));
@@ -2754,7 +1169,6 @@ static int show_log(WriterContext *w, int section_ids, int section_id, int log_l
static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
{
- char val_str[128];
AVStream *st = ifile->streams[pkt->stream_index].st;
AVBPrint pbuf;
const char *s;
@@ -2780,8 +1194,8 @@ static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int p
pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_',
pkt->flags & AV_PKT_FLAG_CORRUPT ? 'C' : '_');
if (do_show_data)
- writer_print_data(w, "data", pkt->data, pkt->size);
- writer_print_data_hash(w, "data_hash", pkt->data, pkt->size);
+ avtext_print_data(w, "data", pkt->data, pkt->size);
+ avtext_print_data_hash(w, "data_hash", pkt->data, pkt->size);
if (pkt->side_data_elems) {
size_t size;
@@ -2850,7 +1264,7 @@ static void print_frame_side_data(WriterContext *w,
double rotation = av_display_rotation_get((int32_t *)sd->data);
if (isnan(rotation))
rotation = 0;
- writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1);
+ avtext_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1);
print_int("rotation", rotation);
} else if (sd->type == AV_FRAME_DATA_AFD && sd->size > 0) {
print_int("active_format", *sd->data);
@@ -3450,12 +1864,12 @@ static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_id
if (nb_streams_packets[stream_idx]) print_fmt ("nb_read_packets", "%"PRIu64, nb_streams_packets[stream_idx]);
else print_str_opt("nb_read_packets", "N/A");
if (do_show_data)
- writer_print_data(w, "extradata", par->extradata,
+ avtext_print_data(w, "extradata", par->extradata,
par->extradata_size);
if (par->extradata_size > 0) {
print_int("extradata_size", par->extradata_size);
- writer_print_data_hash(w, "extradata_hash", par->extradata,
+ avtext_print_data_hash(w, "extradata_hash", par->extradata,
par->extradata_size);
}
@@ -3829,7 +2243,6 @@ static int show_chapters(WriterContext *w, InputFile *ifile)
static int show_format(WriterContext *w, InputFile *ifile)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
- char val_str[128];
int64_t size = fmt_ctx->pb ? avio_size(fmt_ctx->pb) : -1;
int ret = 0;
@@ -4005,7 +2418,7 @@ static void close_input_file(InputFile *ifile)
avformat_close_input(&ifile->fmt_ctx);
}
-static int probe_file(WriterContext *wctx, const char *filename,
+static int probe_file(WriterContext *tctx, const char *filename,
const char *print_filename)
{
InputFile ifile = { 0 };
@@ -4047,40 +2460,40 @@ static int probe_file(WriterContext *wctx, const char *filename,
if (do_read_frames || do_read_packets) {
if (do_show_frames && do_show_packets &&
- wctx->writer->flags & WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER)
+ tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT)
section_id = SECTION_ID_PACKETS_AND_FRAMES;
else if (do_show_packets && !do_show_frames)
section_id = SECTION_ID_PACKETS;
else // (!do_show_packets && do_show_frames)
section_id = SECTION_ID_FRAMES;
if (do_show_frames || do_show_packets)
- writer_print_section_header(wctx, NULL, section_id);
- ret = read_packets(wctx, &ifile);
+ writer_print_section_header(tctx, NULL, section_id);
+ ret = read_packets(tctx, &ifile);
if (do_show_frames || do_show_packets)
- writer_print_section_footer(wctx);
+ writer_print_section_footer(tctx);
CHECK_END;
}
if (do_show_programs) {
- ret = show_programs(wctx, &ifile);
+ ret = show_programs(tctx, &ifile);
CHECK_END;
}
if (do_show_stream_groups) {
- ret = show_stream_groups(wctx, &ifile);
+ ret = show_stream_groups(tctx, &ifile);
CHECK_END;
}
if (do_show_streams) {
- ret = show_streams(wctx, &ifile);
+ ret = show_streams(tctx, &ifile);
CHECK_END;
}
if (do_show_chapters) {
- ret = show_chapters(wctx, &ifile);
+ ret = show_chapters(tctx, &ifile);
CHECK_END;
}
if (do_show_format) {
- ret = show_format(wctx, &ifile);
+ ret = show_format(tctx, &ifile);
CHECK_END;
}
@@ -4229,11 +2642,11 @@ static int opt_format(void *optctx, const char *opt, const char *arg)
static inline void mark_section_show_entries(SectionID section_id,
int show_all_entries, AVDictionary *entries)
{
- struct section *section = §ions[section_id];
+ struct AVTextFormatSection *section = §ions[section_id];
section->show_all_entries = show_all_entries;
if (show_all_entries) {
- for (const SectionID *id = section->children_ids; *id != -1; id++)
+ for (const int *id = section->children_ids; *id != -1; id++)
mark_section_show_entries(*id, show_all_entries, entries);
} else {
av_dict_copy(§ion->entries_to_show, entries, 0);
@@ -4246,7 +2659,7 @@ static int match_section(const char *section_name,
int i, ret = 0;
for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) {
- const struct section *section = §ions[i];
+ const struct AVTextFormatSection *section = §ions[i];
if (!strcmp(section_name, section->name) ||
(section->unique_name && !strcmp(section_name, section->unique_name))) {
av_log(NULL, AV_LOG_DEBUG,
@@ -4518,13 +2931,13 @@ static int opt_pretty(void *optctx, const char *opt, const char *arg)
static void print_section(SectionID id, int level)
{
- const SectionID *pid;
- const struct section *section = §ions[id];
+ const int *pid;
+ const struct AVTextFormatSection *section = §ions[id];
printf("%c%c%c%c",
- section->flags & SECTION_FLAG_IS_WRAPPER ? 'W' : '.',
- section->flags & SECTION_FLAG_IS_ARRAY ? 'A' : '.',
- section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS ? 'V' : '.',
- section->flags & SECTION_FLAG_HAS_TYPE ? 'T' : '.');
+ section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER ? 'W' : '.',
+ section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY ? 'A' : '.',
+ section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS ? 'V' : '.',
+ section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ? 'T' : '.');
printf("%*c %s", level * 4, ' ', section->name);
if (section->unique_name)
printf("/%s", section->unique_name);
@@ -4627,10 +3040,10 @@ static const OptionDef real_options[] = {
static inline int check_section_show_entries(int section_id)
{
- struct section *section = §ions[section_id];
+ struct AVTextFormatSection *section = §ions[section_id];
if (sections[section_id].show_all_entries || sections[section_id].entries_to_show)
return 1;
- for (const SectionID *id = section->children_ids; *id != -1; id++)
+ for (const int *id = section->children_ids; *id != -1; id++)
if (check_section_show_entries(*id))
return 1;
return 0;
@@ -4643,10 +3056,11 @@ static inline int check_section_show_entries(int section_id)
int main(int argc, char **argv)
{
- const Writer *w;
- WriterContext *wctx;
+ const AVTextFormatter *f;
+ WriterContext *tctx;
+ AVTextWriterContext *wctx;
char *buf;
- char *w_name = NULL, *w_args = NULL;
+ char *f_name = NULL, *f_args = NULL;
int ret, input_ret, i;
init_dynload();
@@ -4708,58 +3122,51 @@ int main(int argc, char **argv)
goto end;
}
- writer_register_all();
-
if (!output_format)
output_format = av_strdup("default");
if (!output_format) {
ret = AVERROR(ENOMEM);
goto end;
}
- w_name = av_strtok(output_format, "=", &buf);
- if (!w_name) {
+ f_name = av_strtok(output_format, "=", &buf);
+ if (!f_name) {
av_log(NULL, AV_LOG_ERROR,
"No name specified for the output format\n");
ret = AVERROR(EINVAL);
goto end;
}
- w_args = buf;
-
- if (show_data_hash) {
- if ((ret = av_hash_alloc(&hash, show_data_hash)) < 0) {
- if (ret == AVERROR(EINVAL)) {
- const char *n;
- av_log(NULL, AV_LOG_ERROR,
- "Unknown hash algorithm '%s'\nKnown algorithms:",
- show_data_hash);
- for (i = 0; (n = av_hash_names(i)); i++)
- av_log(NULL, AV_LOG_ERROR, " %s", n);
- av_log(NULL, AV_LOG_ERROR, "\n");
- }
- goto end;
- }
- }
+ f_args = buf;
- w = writer_get_by_name(w_name);
- if (!w) {
- av_log(NULL, AV_LOG_ERROR, "Unknown output format with name '%s'\n", w_name);
+ f = avtext_get_formatter_by_name(f_name);
+ if (!f) {
+ av_log(NULL, AV_LOG_ERROR, "Unknown output format with name '%s'\n", f_name);
ret = AVERROR(EINVAL);
goto end;
}
- if ((ret = writer_open(&wctx, w, w_args,
- sections, FF_ARRAY_ELEMS(sections), output_filename)) >= 0) {
- if (w == &xml_writer)
- wctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;
+ if (output_filename) {
+ ret = avtextwriter_create_file(&wctx, output_filename, 1);
+ } else
+ ret = avtextwriter_create_stdout(&wctx);
- writer_print_section_header(wctx, NULL, SECTION_ID_ROOT);
+ if (ret < 0)
+ goto end;
+
+ if ((ret = avtext_context_open(&tctx, f, wctx, f_args,
+ sections, FF_ARRAY_ELEMS(sections), show_value_unit,
+ use_value_prefix, use_byte_value_binary_prefix, use_value_sexagesimal_format,
+ show_optional_fields, show_data_hash)) >= 0) {
+ if (f == &avtextformatter_xml)
+ tctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;
+
+ writer_print_section_header(tctx, NULL, SECTION_ID_ROOT);
if (do_show_program_version)
- ffprobe_show_program_version(wctx);
+ ffprobe_show_program_version(tctx);
if (do_show_library_versions)
- ffprobe_show_library_versions(wctx);
+ ffprobe_show_library_versions(tctx);
if (do_show_pixel_formats)
- ffprobe_show_pixel_formats(wctx);
+ ffprobe_show_pixel_formats(tctx);
if (!input_filename &&
((do_show_format || do_show_programs || do_show_stream_groups || do_show_streams || do_show_chapters || do_show_packets || do_show_error) ||
@@ -4769,17 +3176,22 @@ int main(int argc, char **argv)
av_log(NULL, AV_LOG_ERROR, "Use -h to get full help or, even better, run 'man %s'.\n", program_name);
ret = AVERROR(EINVAL);
} else if (input_filename) {
- ret = probe_file(wctx, input_filename, print_input_filename);
+ ret = probe_file(tctx, input_filename, print_input_filename);
if (ret < 0 && do_show_error)
- show_error(wctx, ret);
+ show_error(tctx, ret);
}
input_ret = ret;
- writer_print_section_footer(wctx);
- ret = writer_close(&wctx);
+ avtext_print_section_footer(tctx);
+
+ ret = avtextwriter_context_close(&wctx);
+ if (ret < 0)
+ av_log(NULL, AV_LOG_ERROR, "Writing output failed (closing writer): %s\n", av_err2str(ret));
+
+ ret = avtext_context_close(&tctx);
if (ret < 0)
- av_log(NULL, AV_LOG_ERROR, "Writing output failed: %s\n", av_err2str(ret));
+ av_log(NULL, AV_LOG_ERROR, "Writing output failed (closing formatter): %s\n", av_err2str(ret));
ret = FFMIN(ret, input_ret);
}
@@ -4790,7 +3202,6 @@ end:
av_freep(&input_filename);
av_freep(&print_input_filename);
av_freep(&read_intervals);
- av_hash_freep(&hash);
uninit_opts();
for (i = 0; i < FF_ARRAY_ELEMS(sections); i++)
--
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] 27+ messages in thread
* [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 2/7] fftools/ffprobe: Change to use textformat api softworkz
@ 2025-03-01 10:02 ` softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 4/7] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
` (3 subsequent siblings)
6 siblings, 0 replies; 27+ messages in thread
From: softworkz @ 2025-03-01 10:02 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Soft Works, softworkz, Andreas Rheinhardt
From: softworkz <softworkz@hotmail.com>
separated for better clarity of the preceding commit
Signed-off-by: softworkz <softworkz@hotmail.com>
ren
---
fftools/ffprobe.c | 361 +++++++++++++++++++++++-----------------------
1 file changed, 178 insertions(+), 183 deletions(-)
diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index f398057df7..4a90bc4824 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -71,11 +71,6 @@
#include "libavutil/thread.h"
-// TEMPORARY DEFINES
-#define writer_print_section_header(w, d, s) avtext_print_section_header(w, d, s)
-#define writer_print_section_footer(w) avtext_print_section_footer(w)
-#define WriterContext AVTextFormatContext
-
// attached as opaque_ref to packets/frames
typedef struct FrameData {
int64_t pkt_pos;
@@ -446,25 +441,25 @@ static void log_callback(void *ptr, int level, const char *fmt, va_list vl)
memset( (ptr) + (cur_n), 0, ((new_n) - (cur_n)) * sizeof(*(ptr)) ); \
}
-static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id)
+static inline int show_tags(AVTextFormatContext *w, AVDictionary *tags, int section_id)
{
const AVDictionaryEntry *tag = NULL;
int ret = 0;
if (!tags)
return 0;
- writer_print_section_header(w, NULL, section_id);
+ avtext_print_section_header(w, NULL, section_id);
while ((tag = av_dict_iterate(tags, tag))) {
if ((ret = print_str_validate(tag->key, tag->value)) < 0)
break;
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
return ret;
}
-static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi)
+static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *dovi)
{
if (!dovi)
return;
@@ -519,15 +514,15 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi)
print_int("num_x_partitions", mapping->num_x_partitions);
print_int("num_y_partitions", mapping->num_y_partitions);
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST);
for (int c = 0; c < 3; c++) {
const AVDOVIReshapingCurve *curve = &mapping->curves[c];
- writer_print_section_header(w, "Reshaping curve", SECTION_ID_FRAME_SIDE_DATA_COMPONENT);
+ avtext_print_section_header(w, "Reshaping curve", SECTION_ID_FRAME_SIDE_DATA_COMPONENT);
print_list_fmt("pivots", "%"PRIu16, curve->num_pivots, 1, curve->pivots[idx]);
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST);
for (int i = 0; i < curve->num_pivots - 1; i++) {
AVBPrint piece_buf;
@@ -545,7 +540,7 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi)
}
av_bprintf(&piece_buf, " mapping");
- writer_print_section_header(w, piece_buf.str, SECTION_ID_FRAME_SIDE_DATA_PIECE);
+ avtext_print_section_header(w, piece_buf.str, SECTION_ID_FRAME_SIDE_DATA_PIECE);
print_int("mapping_idc", curve->mapping_idc[i]);
switch (curve->mapping_idc[i]) {
case AV_DOVI_MAPPING_POLYNOMIAL:
@@ -569,11 +564,11 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi)
}
// SECTION_ID_FRAME_SIDE_DATA_PIECE
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
// SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
if (mapping->nlq_method_idc != AV_DOVI_NLQ_NONE) {
const AVDOVINLQParams *nlq = &mapping->nlq[c];
@@ -589,11 +584,11 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi)
}
// SECTION_ID_FRAME_SIDE_DATA_COMPONENT
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
// SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
// color metadata
print_int("dm_metadata_id", color->dm_metadata_id);
@@ -626,7 +621,7 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi)
}
}
-static void print_dynamic_hdr10_plus(WriterContext *w, const AVDynamicHDRPlus *metadata)
+static void print_dynamic_hdr10_plus(AVTextFormatContext *w, const AVDynamicHDRPlus *metadata)
{
if (!metadata)
return;
@@ -725,7 +720,7 @@ static void print_dynamic_hdr10_plus(WriterContext *w, const AVDynamicHDRPlus *m
}
}
-static void print_dynamic_hdr_vivid(WriterContext *w, const AVDynamicHDRVivid *metadata)
+static void print_dynamic_hdr_vivid(AVTextFormatContext *w, const AVDynamicHDRVivid *metadata)
{
if (!metadata)
return;
@@ -795,7 +790,7 @@ static void print_dynamic_hdr_vivid(WriterContext *w, const AVDynamicHDRVivid *m
}
}
-static void print_ambient_viewing_environment(WriterContext *w,
+static void print_ambient_viewing_environment(AVTextFormatContext *w,
const AVAmbientViewingEnvironment *env)
{
if (!env)
@@ -806,7 +801,7 @@ static void print_ambient_viewing_environment(WriterContext *w,
print_q("ambient_light_y", env->ambient_light_y, '/');
}
-static void print_film_grain_params(WriterContext *w,
+static void print_film_grain_params(AVTextFormatContext *w,
const AVFilmGrainParams *fgp)
{
const char *color_range, *color_primaries, *color_trc, *color_space;
@@ -852,10 +847,10 @@ static void print_film_grain_params(WriterContext *w,
print_int("overlap_flag", aom->overlap_flag);
print_int("limit_output_range", aom->limit_output_range);
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST);
if (aom->num_y_points) {
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT);
print_int("bit_depth_luma", fgp->bit_depth_luma);
print_list_fmt("y_points_value", "%"PRIu8, aom->num_y_points, 1, aom->y_points[idx][0]);
@@ -863,14 +858,14 @@ static void print_film_grain_params(WriterContext *w,
print_list_fmt("ar_coeffs_y", "%"PRId8, num_ar_coeffs_y, 1, aom->ar_coeffs_y[idx]);
// SECTION_ID_FRAME_SIDE_DATA_COMPONENT
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
for (int uv = 0; uv < 2; uv++) {
if (!aom->num_uv_points[uv] && !aom->chroma_scaling_from_luma)
continue;
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT);
print_int("bit_depth_chroma", fgp->bit_depth_chroma);
print_list_fmt("uv_points_value", "%"PRIu8, aom->num_uv_points[uv], 1, aom->uv_points[uv][idx][0]);
@@ -881,11 +876,11 @@ static void print_film_grain_params(WriterContext *w,
print_int("uv_offset", aom->uv_offset[uv]);
// SECTION_ID_FRAME_SIDE_DATA_COMPONENT
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
// SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
break;
}
case AV_FILM_GRAIN_PARAMS_H274: {
@@ -894,36 +889,36 @@ static void print_film_grain_params(WriterContext *w,
print_int("blending_mode_id", h274->blending_mode_id);
print_int("log2_scale_factor", h274->log2_scale_factor);
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST);
for (int c = 0; c < 3; c++) {
if (!h274->component_model_present[c])
continue;
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT);
print_int(c ? "bit_depth_chroma" : "bit_depth_luma", c ? fgp->bit_depth_chroma : fgp->bit_depth_luma);
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST);
for (int i = 0; i < h274->num_intensity_intervals[c]; i++) {
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE);
print_int("intensity_interval_lower_bound", h274->intensity_interval_lower_bound[c][i]);
print_int("intensity_interval_upper_bound", h274->intensity_interval_upper_bound[c][i]);
print_list_fmt("comp_model_value", "%"PRId16, h274->num_model_values[c], 1, h274->comp_model_value[c][i][idx]);
// SECTION_ID_FRAME_SIDE_DATA_PIECE
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
// SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
// SECTION_ID_FRAME_SIDE_DATA_COMPONENT
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
// SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
break;
}
}
@@ -931,14 +926,14 @@ static void print_film_grain_params(WriterContext *w,
av_bprint_finalize(&pbuf, NULL);
}
-static void print_pkt_side_data(WriterContext *w,
+static void print_pkt_side_data(AVTextFormatContext *w,
AVCodecParameters *par,
const AVPacketSideData *sd,
SectionID id_data)
{
const char *name = av_packet_side_data_name(sd->type);
- writer_print_section_header(w, sd, id_data);
+ avtext_print_section_header(w, sd, id_data);
print_str("side_data_type", name ? name : "unknown");
if (sd->type == AV_PKT_DATA_DISPLAYMATRIX && sd->size >= 9*4) {
double rotation = av_display_rotation_get((int32_t *)sd->data);
@@ -1053,7 +1048,7 @@ static void print_pkt_side_data(WriterContext *w,
}
}
-static void print_private_data(WriterContext *w, void *priv_data)
+static void print_private_data(AVTextFormatContext *w, void *priv_data)
{
const AVOption *opt = NULL;
while (opt = av_opt_next(priv_data, opt)) {
@@ -1066,7 +1061,7 @@ static void print_private_data(WriterContext *w, void *priv_data)
}
}
-static void print_color_range(WriterContext *w, enum AVColorRange color_range)
+static void print_color_range(AVTextFormatContext *w, enum AVColorRange color_range)
{
const char *val = av_color_range_name(color_range);
if (!val || color_range == AVCOL_RANGE_UNSPECIFIED) {
@@ -1076,7 +1071,7 @@ static void print_color_range(WriterContext *w, enum AVColorRange color_range)
}
}
-static void print_color_space(WriterContext *w, enum AVColorSpace color_space)
+static void print_color_space(AVTextFormatContext *w, enum AVColorSpace color_space)
{
const char *val = av_color_space_name(color_space);
if (!val || color_space == AVCOL_SPC_UNSPECIFIED) {
@@ -1086,7 +1081,7 @@ static void print_color_space(WriterContext *w, enum AVColorSpace color_space)
}
}
-static void print_primaries(WriterContext *w, enum AVColorPrimaries color_primaries)
+static void print_primaries(AVTextFormatContext *w, enum AVColorPrimaries color_primaries)
{
const char *val = av_color_primaries_name(color_primaries);
if (!val || color_primaries == AVCOL_PRI_UNSPECIFIED) {
@@ -1096,7 +1091,7 @@ static void print_primaries(WriterContext *w, enum AVColorPrimaries color_primar
}
}
-static void print_color_trc(WriterContext *w, enum AVColorTransferCharacteristic color_trc)
+static void print_color_trc(AVTextFormatContext *w, enum AVColorTransferCharacteristic color_trc)
{
const char *val = av_color_transfer_name(color_trc);
if (!val || color_trc == AVCOL_TRC_UNSPECIFIED) {
@@ -1106,7 +1101,7 @@ static void print_color_trc(WriterContext *w, enum AVColorTransferCharacteristic
}
}
-static void print_chroma_location(WriterContext *w, enum AVChromaLocation chroma_location)
+static void print_chroma_location(AVTextFormatContext *w, enum AVChromaLocation chroma_location)
{
const char *val = av_chroma_location_name(chroma_location);
if (!val || chroma_location == AVCHROMA_LOC_UNSPECIFIED) {
@@ -1132,7 +1127,7 @@ static void clear_log(int need_lock)
ff_mutex_unlock(&log_mutex);
}
-static int show_log(WriterContext *w, int section_ids, int section_id, int log_level)
+static int show_log(AVTextFormatContext *w, int section_ids, int section_id, int log_level)
{
int i;
ff_mutex_lock(&log_mutex);
@@ -1140,11 +1135,11 @@ static int show_log(WriterContext *w, int section_ids, int section_id, int log_l
ff_mutex_unlock(&log_mutex);
return 0;
}
- writer_print_section_header(w, NULL, section_ids);
+ avtext_print_section_header(w, NULL, section_ids);
for (i=0; i<log_buffer_size; i++) {
if (log_buffer[i].log_level <= log_level) {
- writer_print_section_header(w, NULL, section_id);
+ avtext_print_section_header(w, NULL, section_id);
print_str("context", log_buffer[i].context_name);
print_int("level", log_buffer[i].log_level);
print_int("category", log_buffer[i].category);
@@ -1156,18 +1151,18 @@ static int show_log(WriterContext *w, int section_ids, int section_id, int log_l
print_str_opt("parent_category", "N/A");
}
print_str("message", log_buffer[i].log_message);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
}
clear_log(0);
ff_mutex_unlock(&log_mutex);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
return 0;
}
-static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
+static void show_packet(AVTextFormatContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
{
AVStream *st = ifile->streams[pkt->stream_index].st;
AVBPrint pbuf;
@@ -1175,7 +1170,7 @@ static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int p
av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_print_section_header(w, NULL, SECTION_ID_PACKET);
+ avtext_print_section_header(w, NULL, SECTION_ID_PACKET);
s = av_get_media_type_string(st->codecpar->codec_type);
if (s) print_str ("codec_type", s);
@@ -1209,29 +1204,29 @@ static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int p
av_dict_free(&dict);
}
- writer_print_section_header(w, NULL, SECTION_ID_PACKET_SIDE_DATA_LIST);
+ avtext_print_section_header(w, NULL, SECTION_ID_PACKET_SIDE_DATA_LIST);
for (int i = 0; i < pkt->side_data_elems; i++) {
print_pkt_side_data(w, st->codecpar, &pkt->side_data[i],
SECTION_ID_PACKET_SIDE_DATA);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
av_bprint_finalize(&pbuf, NULL);
fflush(stdout);
}
-static void show_subtitle(WriterContext *w, AVSubtitle *sub, AVStream *stream,
+static void show_subtitle(AVTextFormatContext *w, AVSubtitle *sub, AVStream *stream,
AVFormatContext *fmt_ctx)
{
AVBPrint pbuf;
av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_print_section_header(w, NULL, SECTION_ID_SUBTITLE);
+ avtext_print_section_header(w, NULL, SECTION_ID_SUBTITLE);
print_str ("media_type", "subtitle");
print_ts ("pts", sub->pts);
@@ -1241,23 +1236,23 @@ static void show_subtitle(WriterContext *w, AVSubtitle *sub, AVStream *stream,
print_int ("end_display_time", sub->end_display_time);
print_int ("num_rects", sub->num_rects);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
av_bprint_finalize(&pbuf, NULL);
fflush(stdout);
}
-static void print_frame_side_data(WriterContext *w,
+static void print_frame_side_data(AVTextFormatContext *w,
const AVFrame *frame,
const AVStream *stream)
{
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_LIST);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_LIST);
for (int i = 0; i < frame->nb_side_data; i++) {
const AVFrameSideData *sd = frame->side_data[i];
const char *name;
- writer_print_section_header(w, sd, SECTION_ID_FRAME_SIDE_DATA);
+ avtext_print_section_header(w, sd, SECTION_ID_FRAME_SIDE_DATA);
name = av_frame_side_data_name(sd->type);
print_str("side_data_type", name ? name : "unknown");
if (sd->type == AV_FRAME_DATA_DISPLAYMATRIX && sd->size >= 9*4) {
@@ -1275,15 +1270,15 @@ static void print_frame_side_data(WriterContext *w,
} else if (sd->type == AV_FRAME_DATA_S12M_TIMECODE && sd->size == 16) {
uint32_t *tc = (uint32_t*)sd->data;
int m = FFMIN(tc[0],3);
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST);
for (int j = 1; j <= m ; j++) {
char tcbuf[AV_TIMECODE_STR_SIZE];
av_timecode_make_smpte_tc_string2(tcbuf, stream->avg_frame_rate, tc[j], 0, 0);
- writer_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE);
print_str("value", tcbuf);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
} else if (sd->type == AV_FRAME_DATA_MASTERING_DISPLAY_METADATA) {
AVMasteringDisplayMetadata *metadata = (AVMasteringDisplayMetadata *)sd->data;
@@ -1328,12 +1323,12 @@ static void print_frame_side_data(WriterContext *w,
} else if (sd->type == AV_FRAME_DATA_VIEW_ID) {
print_int("view_id", *(int*)sd->data);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
-static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream,
+static void show_frame(AVTextFormatContext *w, AVFrame *frame, AVStream *stream,
AVFormatContext *fmt_ctx)
{
FrameData *fd = frame->opaque_ref ? (FrameData*)frame->opaque_ref->data : NULL;
@@ -1343,7 +1338,7 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream,
av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_print_section_header(w, NULL, SECTION_ID_FRAME);
+ avtext_print_section_header(w, NULL, SECTION_ID_FRAME);
s = av_get_media_type_string(stream->codecpar->codec_type);
if (s) print_str ("media_type", s);
@@ -1415,13 +1410,13 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream,
if (frame->nb_side_data)
print_frame_side_data(w, frame, stream);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
av_bprint_finalize(&pbuf, NULL);
fflush(stdout);
}
-static av_always_inline int process_frame(WriterContext *w,
+static av_always_inline int process_frame(AVTextFormatContext *w,
InputFile *ifile,
AVFrame *frame, const AVPacket *pkt,
int *packet_new)
@@ -1518,7 +1513,7 @@ static void log_read_interval(const ReadInterval *interval, void *log_ctx, int l
av_log(log_ctx, log_level, "\n");
}
-static int read_interval_packets(WriterContext *w, InputFile *ifile,
+static int read_interval_packets(AVTextFormatContext *w, InputFile *ifile,
const ReadInterval *interval, int64_t *cur_ts)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
@@ -1643,7 +1638,7 @@ end:
return ret;
}
-static int read_packets(WriterContext *w, InputFile *ifile)
+static int read_packets(AVTextFormatContext *w, InputFile *ifile)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
int i, ret = 0;
@@ -1663,22 +1658,22 @@ static int read_packets(WriterContext *w, InputFile *ifile)
return ret;
}
-static void print_dispositions(WriterContext *w, uint32_t disposition, SectionID section_id)
+static void print_dispositions(AVTextFormatContext *w, uint32_t disposition, SectionID section_id)
{
- writer_print_section_header(w, NULL, section_id);
+ avtext_print_section_header(w, NULL, section_id);
for (int i = 0; i < sizeof(disposition) * CHAR_BIT; i++) {
const char *disposition_str = av_disposition_to_string(1U << i);
if (disposition_str)
print_int(disposition_str, !!(disposition & (1U << i)));
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
#define IN_PROGRAM 1
#define IN_STREAM_GROUP 2
-static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int container)
+static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int container)
{
AVStream *stream = ist->st;
AVCodecParameters *par;
@@ -1710,7 +1705,7 @@ static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_id
av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_print_section_header(w, NULL, section_header[container]);
+ avtext_print_section_header(w, NULL, section_header[container]);
print_int("index", stream->index);
@@ -1885,45 +1880,45 @@ static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_id
}
if (stream->codecpar->nb_coded_side_data) {
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_SIDE_DATA_LIST);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_SIDE_DATA_LIST);
for (int i = 0; i < stream->codecpar->nb_coded_side_data; i++) {
print_pkt_side_data(w, stream->codecpar, &stream->codecpar->coded_side_data[i],
SECTION_ID_STREAM_SIDE_DATA);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
av_bprint_finalize(&pbuf, NULL);
fflush(stdout);
return ret;
}
-static int show_streams(WriterContext *w, InputFile *ifile)
+static int show_streams(AVTextFormatContext *w, InputFile *ifile)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
int i, ret = 0;
- writer_print_section_header(w, NULL, SECTION_ID_STREAMS);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAMS);
for (i = 0; i < ifile->nb_streams; i++)
if (selected_streams[i]) {
ret = show_stream(w, fmt_ctx, i, &ifile->streams[i], 0);
if (ret < 0)
break;
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
return ret;
}
-static int show_program(WriterContext *w, InputFile *ifile, AVProgram *program)
+static int show_program(AVTextFormatContext *w, InputFile *ifile, AVProgram *program)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
int i, ret = 0;
- writer_print_section_header(w, NULL, SECTION_ID_PROGRAM);
+ avtext_print_section_header(w, NULL, SECTION_ID_PROGRAM);
print_int("program_id", program->id);
print_int("program_num", program->program_num);
print_int("nb_streams", program->nb_stream_indexes);
@@ -1934,7 +1929,7 @@ static int show_program(WriterContext *w, InputFile *ifile, AVProgram *program)
if (ret < 0)
goto end;
- writer_print_section_header(w, NULL, SECTION_ID_PROGRAM_STREAMS);
+ avtext_print_section_header(w, NULL, SECTION_ID_PROGRAM_STREAMS);
for (i = 0; i < program->nb_stream_indexes; i++) {
if (selected_streams[program->stream_index[i]]) {
ret = show_stream(w, fmt_ctx, program->stream_index[i], &ifile->streams[program->stream_index[i]], IN_PROGRAM);
@@ -1942,19 +1937,19 @@ static int show_program(WriterContext *w, InputFile *ifile, AVProgram *program)
break;
}
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
end:
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
return ret;
}
-static int show_programs(WriterContext *w, InputFile *ifile)
+static int show_programs(AVTextFormatContext *w, InputFile *ifile)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
int i, ret = 0;
- writer_print_section_header(w, NULL, SECTION_ID_PROGRAMS);
+ avtext_print_section_header(w, NULL, SECTION_ID_PROGRAMS);
for (i = 0; i < fmt_ctx->nb_programs; i++) {
AVProgram *program = fmt_ctx->programs[i];
if (!program)
@@ -1963,14 +1958,14 @@ static int show_programs(WriterContext *w, InputFile *ifile)
if (ret < 0)
break;
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
return ret;
}
-static void print_tile_grid_params(WriterContext *w, const AVStreamGroup *stg,
+static void print_tile_grid_params(AVTextFormatContext *w, const AVStreamGroup *stg,
const AVStreamGroupTileGrid *tile_grid)
{
- writer_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT);
+ avtext_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT);
print_int("nb_tiles", tile_grid->nb_tiles);
print_int("coded_width", tile_grid->coded_width);
print_int("coded_height", tile_grid->coded_height);
@@ -1978,19 +1973,19 @@ static void print_tile_grid_params(WriterContext *w, const AVStreamGroup *stg,
print_int("vertical_offset", tile_grid->vertical_offset);
print_int("width", tile_grid->width);
print_int("height", tile_grid->height);
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS);
for (int i = 0; i < tile_grid->nb_tiles; i++) {
- writer_print_section_header(w, "tile_offset", SECTION_ID_STREAM_GROUP_SUBCOMPONENT);
+ avtext_print_section_header(w, "tile_offset", SECTION_ID_STREAM_GROUP_SUBCOMPONENT);
print_int("stream_index", tile_grid->offsets[i].idx);
print_int("tile_horizontal_offset", tile_grid->offsets[i].horizontal);
print_int("tile_vertical_offset", tile_grid->offsets[i].vertical);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
+ avtext_print_section_footer(w);
}
-static void print_iamf_param_definition(WriterContext *w, const char *name,
+static void print_iamf_param_definition(AVTextFormatContext *w, const char *name,
const AVIAMFParamDefinition *param, SectionID section_id)
{
SectionID subsection_id, parameter_section_id;
@@ -1998,7 +1993,7 @@ static void print_iamf_param_definition(WriterContext *w, const char *name,
av_assert0(subsection_id != -1);
parameter_section_id = sections[subsection_id].children_ids[0];
av_assert0(parameter_section_id != -1);
- writer_print_section_header(w, "IAMF Param Definition", section_id);
+ avtext_print_section_header(w, "IAMF Param Definition", section_id);
print_str("name", name);
print_int("nb_subblocks", param->nb_subblocks);
print_int("type", param->type);
@@ -2007,56 +2002,56 @@ static void print_iamf_param_definition(WriterContext *w, const char *name,
print_int("duration", param->duration);
print_int("constant_subblock_duration", param->constant_subblock_duration);
if (param->nb_subblocks > 0)
- writer_print_section_header(w, NULL, subsection_id);
+ avtext_print_section_header(w, NULL, subsection_id);
for (int i = 0; i < param->nb_subblocks; i++) {
const void *subblock = av_iamf_param_definition_get_subblock(param, i);
switch(param->type) {
case AV_IAMF_PARAMETER_DEFINITION_MIX_GAIN: {
const AVIAMFMixGain *mix = subblock;
- writer_print_section_header(w, "IAMF Mix Gain Parameters", parameter_section_id);
+ avtext_print_section_header(w, "IAMF Mix Gain Parameters", parameter_section_id);
print_int("subblock_duration", mix->subblock_duration);
print_int("animation_type", mix->animation_type);
print_q("start_point_value", mix->start_point_value, '/');
print_q("end_point_value", mix->end_point_value, '/');
print_q("control_point_value", mix->control_point_value, '/');
print_q("control_point_relative_time", mix->control_point_relative_time, '/');
- writer_print_section_footer(w); // parameter_section_id
+ avtext_print_section_footer(w); // parameter_section_id
break;
}
case AV_IAMF_PARAMETER_DEFINITION_DEMIXING: {
const AVIAMFDemixingInfo *demix = subblock;
- writer_print_section_header(w, "IAMF Demixing Info", parameter_section_id);
+ avtext_print_section_header(w, "IAMF Demixing Info", parameter_section_id);
print_int("subblock_duration", demix->subblock_duration);
print_int("dmixp_mode", demix->dmixp_mode);
- writer_print_section_footer(w); // parameter_section_id
+ avtext_print_section_footer(w); // parameter_section_id
break;
}
case AV_IAMF_PARAMETER_DEFINITION_RECON_GAIN: {
const AVIAMFReconGain *recon = subblock;
- writer_print_section_header(w, "IAMF Recon Gain", parameter_section_id);
+ avtext_print_section_header(w, "IAMF Recon Gain", parameter_section_id);
print_int("subblock_duration", recon->subblock_duration);
- writer_print_section_footer(w); // parameter_section_id
+ avtext_print_section_footer(w); // parameter_section_id
break;
}
}
}
if (param->nb_subblocks > 0)
- writer_print_section_footer(w); // subsection_id
- writer_print_section_footer(w); // section_id
+ avtext_print_section_footer(w); // subsection_id
+ avtext_print_section_footer(w); // section_id
}
-static void print_iamf_audio_element_params(WriterContext *w, const AVStreamGroup *stg,
+static void print_iamf_audio_element_params(AVTextFormatContext *w, const AVStreamGroup *stg,
const AVIAMFAudioElement *audio_element)
{
- writer_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT);
+ avtext_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT);
print_int("nb_layers", audio_element->nb_layers);
print_int("audio_element_type", audio_element->audio_element_type);
print_int("default_w", audio_element->default_w);
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS);
for (int i = 0; i < audio_element->nb_layers; i++) {
const AVIAMFLayer *layer = audio_element->layers[i];
char val_str[128];
- writer_print_section_header(w, "IAMF Audio Layer", SECTION_ID_STREAM_GROUP_SUBCOMPONENT);
+ avtext_print_section_header(w, "IAMF Audio Layer", SECTION_ID_STREAM_GROUP_SUBCOMPONENT);
av_channel_layout_describe(&layer->ch_layout, val_str, sizeof(val_str));
print_str("channel_layout", val_str);
if (audio_element->audio_element_type == AV_IAMF_AUDIO_ELEMENT_TYPE_CHANNEL) {
@@ -2064,7 +2059,7 @@ static void print_iamf_audio_element_params(WriterContext *w, const AVStreamGrou
print_q("output_gain", layer->output_gain, '/');
} else if (audio_element->audio_element_type == AV_IAMF_AUDIO_ELEMENT_TYPE_SCENE)
print_int("ambisonics_mode", layer->ambisonics_mode);
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT
}
if (audio_element->demixing_info)
print_iamf_param_definition(w, "demixing_info", audio_element->demixing_info,
@@ -2072,36 +2067,36 @@ static void print_iamf_audio_element_params(WriterContext *w, const AVStreamGrou
if (audio_element->recon_gain_info)
print_iamf_param_definition(w, "recon_gain_info", audio_element->recon_gain_info,
SECTION_ID_STREAM_GROUP_SUBCOMPONENT);
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENT
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENT
}
-static void print_iamf_submix_params(WriterContext *w, const AVIAMFSubmix *submix)
+static void print_iamf_submix_params(AVTextFormatContext *w, const AVIAMFSubmix *submix)
{
- writer_print_section_header(w, "IAMF Submix", SECTION_ID_STREAM_GROUP_SUBCOMPONENT);
+ avtext_print_section_header(w, "IAMF Submix", SECTION_ID_STREAM_GROUP_SUBCOMPONENT);
print_int("nb_elements", submix->nb_elements);
print_int("nb_layouts", submix->nb_layouts);
print_q("default_mix_gain", submix->default_mix_gain, '/');
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_PIECES);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_PIECES);
for (int i = 0; i < submix->nb_elements; i++) {
const AVIAMFSubmixElement *element = submix->elements[i];
- writer_print_section_header(w, "IAMF Submix Element", SECTION_ID_STREAM_GROUP_PIECE);
+ avtext_print_section_header(w, "IAMF Submix Element", SECTION_ID_STREAM_GROUP_PIECE);
print_int("stream_id", element->audio_element_id);
print_q("default_mix_gain", element->default_mix_gain, '/');
print_int("headphones_rendering_mode", element->headphones_rendering_mode);
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBPIECES);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBPIECES);
if (element->annotations) {
const AVDictionaryEntry *annotation = NULL;
- writer_print_section_header(w, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBPIECE);
+ avtext_print_section_header(w, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBPIECE);
while (annotation = av_dict_iterate(element->annotations, annotation))
print_str(annotation->key, annotation->value);
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBPIECE
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBPIECE
}
if (element->element_mix_config)
print_iamf_param_definition(w, "element_mix_config", element->element_mix_config,
SECTION_ID_STREAM_GROUP_SUBPIECE);
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBPIECES
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECE
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBPIECES
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECE
}
if (submix->output_mix_config)
print_iamf_param_definition(w, "output_mix_config", submix->output_mix_config,
@@ -2109,7 +2104,7 @@ static void print_iamf_submix_params(WriterContext *w, const AVIAMFSubmix *submi
for (int i = 0; i < submix->nb_layouts; i++) {
const AVIAMFSubmixLayout *layout = submix->layouts[i];
char val_str[128];
- writer_print_section_header(w, "IAMF Submix Layout", SECTION_ID_STREAM_GROUP_PIECE);
+ avtext_print_section_header(w, "IAMF Submix Layout", SECTION_ID_STREAM_GROUP_PIECE);
av_channel_layout_describe(&layout->sound_system, val_str, sizeof(val_str));
print_str("sound_system", val_str);
print_q("integrated_loudness", layout->integrated_loudness, '/');
@@ -2117,51 +2112,51 @@ static void print_iamf_submix_params(WriterContext *w, const AVIAMFSubmix *submi
print_q("true_peak", layout->true_peak, '/');
print_q("dialogue_anchored_loudness", layout->dialogue_anchored_loudness, '/');
print_q("album_anchored_loudness", layout->album_anchored_loudness, '/');
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECE
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECE
}
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECES
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECES
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT
}
-static void print_iamf_mix_presentation_params(WriterContext *w, const AVStreamGroup *stg,
+static void print_iamf_mix_presentation_params(AVTextFormatContext *w, const AVStreamGroup *stg,
const AVIAMFMixPresentation *mix_presentation)
{
- writer_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT);
+ avtext_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT);
print_int("nb_submixes", mix_presentation->nb_submixes);
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS);
if (mix_presentation->annotations) {
const AVDictionaryEntry *annotation = NULL;
- writer_print_section_header(w, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBCOMPONENT);
+ avtext_print_section_header(w, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBCOMPONENT);
while (annotation = av_dict_iterate(mix_presentation->annotations, annotation))
print_str(annotation->key, annotation->value);
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT
}
for (int i = 0; i < mix_presentation->nb_submixes; i++)
print_iamf_submix_params(w, mix_presentation->submixes[i]);
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENT
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENT
}
-static void print_stream_group_params(WriterContext *w, AVStreamGroup *stg)
+static void print_stream_group_params(AVTextFormatContext *w, AVStreamGroup *stg)
{
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_COMPONENTS);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_COMPONENTS);
if (stg->type == AV_STREAM_GROUP_PARAMS_TILE_GRID)
print_tile_grid_params(w, stg, stg->params.tile_grid);
else if (stg->type == AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT)
print_iamf_audio_element_params(w, stg, stg->params.iamf_audio_element);
else if (stg->type == AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION)
print_iamf_mix_presentation_params(w, stg, stg->params.iamf_mix_presentation);
- writer_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENTS
+ avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENTS
}
-static int show_stream_group(WriterContext *w, InputFile *ifile, AVStreamGroup *stg)
+static int show_stream_group(AVTextFormatContext *w, InputFile *ifile, AVStreamGroup *stg)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
AVBPrint pbuf;
int i, ret = 0;
av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP);
print_int("index", stg->index);
if (fmt_ctx->iformat->flags & AVFMT_SHOW_IDS) print_fmt ("id", "0x%"PRIx64, stg->id);
else print_str_opt("id", "N/A");
@@ -2182,7 +2177,7 @@ static int show_stream_group(WriterContext *w, InputFile *ifile, AVStreamGroup *
if (ret < 0)
goto end;
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_STREAMS);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_STREAMS);
for (i = 0; i < stg->nb_streams; i++) {
if (selected_streams[stg->streams[i]->index]) {
ret = show_stream(w, fmt_ctx, stg->streams[i]->index, &ifile->streams[stg->streams[i]->index], IN_STREAM_GROUP);
@@ -2190,20 +2185,20 @@ static int show_stream_group(WriterContext *w, InputFile *ifile, AVStreamGroup *
break;
}
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
end:
av_bprint_finalize(&pbuf, NULL);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
return ret;
}
-static int show_stream_groups(WriterContext *w, InputFile *ifile)
+static int show_stream_groups(AVTextFormatContext *w, InputFile *ifile)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
int i, ret = 0;
- writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUPS);
+ avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUPS);
for (i = 0; i < fmt_ctx->nb_stream_groups; i++) {
AVStreamGroup *stg = fmt_ctx->stream_groups[i];
@@ -2211,20 +2206,20 @@ static int show_stream_groups(WriterContext *w, InputFile *ifile)
if (ret < 0)
break;
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
return ret;
}
-static int show_chapters(WriterContext *w, InputFile *ifile)
+static int show_chapters(AVTextFormatContext *w, InputFile *ifile)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
int i, ret = 0;
- writer_print_section_header(w, NULL, SECTION_ID_CHAPTERS);
+ avtext_print_section_header(w, NULL, SECTION_ID_CHAPTERS);
for (i = 0; i < fmt_ctx->nb_chapters; i++) {
AVChapter *chapter = fmt_ctx->chapters[i];
- writer_print_section_header(w, NULL, SECTION_ID_CHAPTER);
+ avtext_print_section_header(w, NULL, SECTION_ID_CHAPTER);
print_int("id", chapter->id);
print_q ("time_base", chapter->time_base, '/');
print_int("start", chapter->start);
@@ -2233,20 +2228,20 @@ static int show_chapters(WriterContext *w, InputFile *ifile)
print_time("end_time", chapter->end, &chapter->time_base);
if (do_show_chapter_tags)
ret = show_tags(w, chapter->metadata, SECTION_ID_CHAPTER_TAGS);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
return ret;
}
-static int show_format(WriterContext *w, InputFile *ifile)
+static int show_format(AVTextFormatContext *w, InputFile *ifile)
{
AVFormatContext *fmt_ctx = ifile->fmt_ctx;
int64_t size = fmt_ctx->pb ? avio_size(fmt_ctx->pb) : -1;
int ret = 0;
- writer_print_section_header(w, NULL, SECTION_ID_FORMAT);
+ avtext_print_section_header(w, NULL, SECTION_ID_FORMAT);
print_str_validate("filename", fmt_ctx->url);
print_int("nb_streams", fmt_ctx->nb_streams);
print_int("nb_programs", fmt_ctx->nb_programs);
@@ -2266,17 +2261,17 @@ static int show_format(WriterContext *w, InputFile *ifile)
if (do_show_format_tags)
ret = show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
fflush(stdout);
return ret;
}
-static void show_error(WriterContext *w, int err)
+static void show_error(AVTextFormatContext *w, int err)
{
- writer_print_section_header(w, NULL, SECTION_ID_ERROR);
+ avtext_print_section_header(w, NULL, SECTION_ID_ERROR);
print_int("code", err);
print_str("string", av_err2str(err));
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
static int open_input_file(InputFile *ifile, const char *filename,
@@ -2418,7 +2413,7 @@ static void close_input_file(InputFile *ifile)
avformat_close_input(&ifile->fmt_ctx);
}
-static int probe_file(WriterContext *tctx, const char *filename,
+static int probe_file(AVTextFormatContext *tctx, const char *filename,
const char *print_filename)
{
InputFile ifile = { 0 };
@@ -2467,10 +2462,10 @@ static int probe_file(WriterContext *tctx, const char *filename,
else // (!do_show_packets && do_show_frames)
section_id = SECTION_ID_FRAMES;
if (do_show_frames || do_show_packets)
- writer_print_section_header(tctx, NULL, section_id);
+ avtext_print_section_header(tctx, NULL, section_id);
ret = read_packets(tctx, &ifile);
if (do_show_frames || do_show_packets)
- writer_print_section_footer(tctx);
+ avtext_print_section_footer(tctx);
CHECK_END;
}
@@ -2516,18 +2511,18 @@ static void show_usage(void)
av_log(NULL, AV_LOG_INFO, "\n");
}
-static void ffprobe_show_program_version(WriterContext *w)
+static void ffprobe_show_program_version(AVTextFormatContext *w)
{
AVBPrint pbuf;
av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
- writer_print_section_header(w, NULL, SECTION_ID_PROGRAM_VERSION);
+ avtext_print_section_header(w, NULL, SECTION_ID_PROGRAM_VERSION);
print_str("version", FFMPEG_VERSION);
print_fmt("copyright", "Copyright (c) %d-%d the FFmpeg developers",
program_birth_year, CONFIG_THIS_YEAR);
print_str("compiler_ident", CC_IDENT);
print_str("configuration", FFMPEG_CONFIGURATION);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
av_bprint_finalize(&pbuf, NULL);
}
@@ -2536,20 +2531,20 @@ static void ffprobe_show_program_version(WriterContext *w)
do { \
if (CONFIG_##LIBNAME) { \
unsigned int version = libname##_version(); \
- writer_print_section_header(w, NULL, SECTION_ID_LIBRARY_VERSION); \
+ avtext_print_section_header(w, NULL, SECTION_ID_LIBRARY_VERSION); \
print_str("name", "lib" #libname); \
print_int("major", LIB##LIBNAME##_VERSION_MAJOR); \
print_int("minor", LIB##LIBNAME##_VERSION_MINOR); \
print_int("micro", LIB##LIBNAME##_VERSION_MICRO); \
print_int("version", version); \
print_str("ident", LIB##LIBNAME##_IDENT); \
- writer_print_section_footer(w); \
+ avtext_print_section_footer(w); \
} \
} while (0)
-static void ffprobe_show_library_versions(WriterContext *w)
+static void ffprobe_show_library_versions(AVTextFormatContext *w)
{
- writer_print_section_header(w, NULL, SECTION_ID_LIBRARY_VERSIONS);
+ avtext_print_section_header(w, NULL, SECTION_ID_LIBRARY_VERSIONS);
SHOW_LIB_VERSION(avutil, AVUTIL);
SHOW_LIB_VERSION(avcodec, AVCODEC);
SHOW_LIB_VERSION(avformat, AVFORMAT);
@@ -2558,7 +2553,7 @@ static void ffprobe_show_library_versions(WriterContext *w)
SHOW_LIB_VERSION(swscale, SWSCALE);
SHOW_LIB_VERSION(swresample, SWRESAMPLE);
SHOW_LIB_VERSION(postproc, POSTPROC);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
#define PRINT_PIX_FMT_FLAG(flagname, name) \
@@ -2566,14 +2561,14 @@ static void ffprobe_show_library_versions(WriterContext *w)
print_int(name, !!(pixdesc->flags & AV_PIX_FMT_FLAG_##flagname)); \
} while (0)
-static void ffprobe_show_pixel_formats(WriterContext *w)
+static void ffprobe_show_pixel_formats(AVTextFormatContext *w)
{
const AVPixFmtDescriptor *pixdesc = NULL;
int i, n;
- writer_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMATS);
+ avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMATS);
while (pixdesc = av_pix_fmt_desc_next(pixdesc)) {
- writer_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT);
+ avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT);
print_str("name", pixdesc->name);
print_int("nb_components", pixdesc->nb_components);
if ((pixdesc->nb_components >= 3) && !(pixdesc->flags & AV_PIX_FMT_FLAG_RGB)) {
@@ -2587,7 +2582,7 @@ static void ffprobe_show_pixel_formats(WriterContext *w)
if (n) print_int ("bits_per_pixel", n);
else print_str_opt("bits_per_pixel", "N/A");
if (do_show_pixel_format_flags) {
- writer_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_FLAGS);
+ avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_FLAGS);
PRINT_PIX_FMT_FLAG(BE, "big_endian");
PRINT_PIX_FMT_FLAG(PAL, "palette");
PRINT_PIX_FMT_FLAG(BITSTREAM, "bitstream");
@@ -2595,21 +2590,21 @@ static void ffprobe_show_pixel_formats(WriterContext *w)
PRINT_PIX_FMT_FLAG(PLANAR, "planar");
PRINT_PIX_FMT_FLAG(RGB, "rgb");
PRINT_PIX_FMT_FLAG(ALPHA, "alpha");
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
if (do_show_pixel_format_components && (pixdesc->nb_components > 0)) {
- writer_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENTS);
+ avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENTS);
for (i = 0; i < pixdesc->nb_components; i++) {
- writer_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENT);
+ avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENT);
print_int("index", i + 1);
print_int("bit_depth", pixdesc->comp[i].depth);
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
- writer_print_section_footer(w);
+ avtext_print_section_footer(w);
}
static int opt_show_optional_fields(void *optctx, const char *opt, const char *arg)
@@ -3057,7 +3052,7 @@ static inline int check_section_show_entries(int section_id)
int main(int argc, char **argv)
{
const AVTextFormatter *f;
- WriterContext *tctx;
+ AVTextFormatContext *tctx;
AVTextWriterContext *wctx;
char *buf;
char *f_name = NULL, *f_args = NULL;
@@ -3159,7 +3154,7 @@ int main(int argc, char **argv)
if (f == &avtextformatter_xml)
tctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;
- writer_print_section_header(tctx, NULL, SECTION_ID_ROOT);
+ avtext_print_section_header(tctx, NULL, SECTION_ID_ROOT);
if (do_show_program_version)
ffprobe_show_program_version(tctx);
--
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] 27+ messages in thread
* [FFmpeg-devel] [PATCH v3 4/7] fftools/ffmpeg_filter: Move some declaration to new header file
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
` (2 preceding siblings ...)
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz
@ 2025-03-01 10:02 ` softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 5/7] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() softworkz
` (2 subsequent siblings)
6 siblings, 0 replies; 27+ messages in thread
From: softworkz @ 2025-03-01 10:02 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] 27+ messages in thread
* [FFmpeg-devel] [PATCH v3 5/7] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx()
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
` (3 preceding siblings ...)
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 4/7] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-03-01 10:02 ` softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 6/7] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 7/7] fftools: Enable filtergraph printing and update docs softworkz
6 siblings, 0 replies; 27+ messages in thread
From: softworkz @ 2025-03-01 10:02 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] 27+ messages in thread
* [FFmpeg-devel] [PATCH v3 6/7] fftools/ffmpeg_graphprint: Add options for filtergraph printing
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
` (4 preceding siblings ...)
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 5/7] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() softworkz
@ 2025-03-01 10:02 ` softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 7/7] fftools: Enable filtergraph printing and update docs softworkz
6 siblings, 0 replies; 27+ messages in thread
From: softworkz @ 2025-03-01 10:02 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
Signed-off-by: softworkz <softworkz@hotmail.com>
---
fftools/Makefile | 11 +
fftools/ffmpeg.h | 3 +
fftools/ffmpeg_graphprint.c | 474 ++++++++++++++++++++++++++++++++++++
fftools/ffmpeg_graphprint.h | 79 ++++++
fftools/ffmpeg_opt.c | 12 +
5 files changed, 579 insertions(+)
create mode 100644 fftools/ffmpeg_graphprint.c
create mode 100644 fftools/ffmpeg_graphprint.h
diff --git a/fftools/Makefile b/fftools/Makefile
index 664b73b161..03cdbd4b6e 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -19,8 +19,19 @@ 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 \
+ fftools/textformat/avtextformat.o \
+ fftools/textformat/tf_compact.o \
+ fftools/textformat/tf_default.o \
+ fftools/textformat/tf_flat.o \
+ fftools/textformat/tf_ini.o \
+ fftools/textformat/tf_json.o \
+ fftools/textformat/tf_xml.o \
+ fftools/textformat/tw_avio.o \
+ fftools/textformat/tw_buffer.o \
+ fftools/textformat/tw_stdout.o \
OBJS-ffprobe += \
fftools/textformat/avtextformat.o \
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 86a3e10c6b..9880236162 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -715,6 +715,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..b374c92743
--- /dev/null
+++ b/fftools/ffmpeg_graphprint.c
@@ -0,0 +1,474 @@
+/*
+ * 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/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+#include "textformat/avtextformat.h"
+
+/* Text Format API Shortcuts */
+#define print_int(k, v) avtext_print_integer(w, k, v)
+#define print_q(k, v, s) avtext_print_rational(w, k, v, s)
+#define print_str(k, v) avtext_print_string(w, k, v, 0)
+
+static void print_hwdevicecontext(AVTextFormatContext *w, const AVHWDeviceContext *hw_device_context)
+{
+ avtext_print_section_header(w, NULL, SECTION_ID_HWDEViCECONTEXT);
+
+ print_int("HasHwDeviceContext", 1);
+ print_str("DeviceType", av_hwdevice_get_type_name(hw_device_context->type));
+
+ avtext_print_section_footer(w); // SECTION_ID_HWDEViCECONTEXT
+}
+
+static void print_hwframescontext(AVTextFormatContext *w, const AVHWFramesContext *hw_frames_context)
+{
+ const AVPixFmtDescriptor* pixdescHw;
+ const AVPixFmtDescriptor* pixdescSw;
+
+ avtext_print_section_header(w, NULL, 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);
+
+ avtext_print_section_footer(w); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(AVTextFormatContext *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(AVTextFormatContext *w, const AVFilterContext* filter)
+{
+ avtext_print_section_header(w, NULL, 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);
+ }
+
+ avtext_print_section_header(w, NULL, SECTION_ID_INPUTS);
+
+ for (unsigned i = 0; i < filter->nb_inputs; i++) {
+ AVFilterLink *link = filter->inputs[i];
+ avtext_print_section_header(w, NULL, 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);
+
+ avtext_print_section_footer(w); // SECTION_ID_INPUT
+ }
+
+ avtext_print_section_footer(w); // SECTION_ID_INPUTS
+
+ avtext_print_section_header(w, NULL, SECTION_ID_OUTPUTS);
+
+ for (unsigned i = 0; i < filter->nb_outputs; i++) {
+ AVFilterLink *link = filter->outputs[i];
+ avtext_print_section_header(w, NULL, 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);
+
+ avtext_print_section_footer(w); // SECTION_ID_OUTPUT
+ }
+
+ avtext_print_section_footer(w); // SECTION_ID_OUTPUTS
+
+ avtext_print_section_footer(w); // SECTION_ID_FILTER
+}
+
+static void init_sections(void)
+{
+ for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
+ sections[i].show_all_entries = 1;
+}
+
+static void print_filtergraph_single(AVTextFormatContext *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);
+
+ avtext_print_section_header(w, NULL, 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;
+
+ avtext_print_section_header(w, NULL, 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("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);
+ }
+
+ avtext_print_section_footer(w); // SECTION_ID_INPUT
+ }
+
+ avtext_print_section_footer(w); // SECTION_ID_INPUTS
+
+
+ avtext_print_section_header(w, NULL, 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;
+
+ avtext_print_section_header(w, NULL, 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("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);
+ }
+
+ avtext_print_section_footer(w); // SECTION_ID_OUTPUT
+ }
+
+ avtext_print_section_footer(w); // SECTION_ID_OUTPUTS
+
+
+ avtext_print_section_header(w, NULL, SECTION_ID_FILTERS);
+
+ if (graph) {
+ for (unsigned i = 0; i < graph->nb_filters; i++) {
+ AVFilterContext *filter = graph->filters[i];
+ avtext_print_section_header(w, NULL, SECTION_ID_FILTER);
+
+ print_filter(w, filter);
+
+ avtext_print_section_footer(w); // SECTION_ID_FILTER
+ }
+ }
+
+ avtext_print_section_footer(w); // SECTION_ID_FILTERS
+}
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+ const AVTextFormatter *text_formatter;
+ AVTextFormatContext *tctx;
+ AVTextWriterContext *wctx;
+ char *w_name, *w_args;
+ int ret;
+ FilterGraphPriv *fgp = fgp_from_fg(fg);
+ AVBPrint *target_buf = &fgp->graph_print_buf;
+
+ init_sections();
+
+ if (target_buf->len)
+ av_bprint_finalize(target_buf, NULL);
+
+ av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ if (!print_graphs_format)
+ print_graphs_format = av_strdup("default");
+ if (!print_graphs_format)
+ return AVERROR(ENOMEM);
+
+ w_name = av_strtok(print_graphs_format, "=", &w_args);
+ if (!w_name) {
+ av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+ return AVERROR(EINVAL);
+ }
+
+ text_formatter = avtext_get_formatter_by_name(w_name);
+ if (text_formatter == NULL) {
+ av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
+ return AVERROR(EINVAL);
+ }
+
+ ret = avtextwriter_create_buffer(&wctx, target_buf);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
+ return AVERROR(EINVAL);
+ }
+
+ if ((ret = avtext_context_open(&tctx, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), 0, 0, 0, 0, -1, NULL)) >= 0) {
+ avtext_print_section_header(tctx, NULL, SECTION_ID_ROOT);
+ avtext_print_section_header(tctx, NULL, SECTION_ID_FILTERGRAPHS);
+ avtext_print_section_header(tctx, NULL, SECTION_ID_FILTERGRAPH);
+
+ av_bprint_clear(target_buf);
+
+ print_filtergraph_single(tctx, fg, graph);
+
+ avtext_context_close(&tctx);
+ avtextwriter_context_close(&wctx);
+ } else
+ return ret;
+
+ return 0;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, OutputFile **ofiles, int nb_ofiles)
+{
+ const AVTextFormatter *text_formatter;
+ AVTextFormatContext *tctx;
+ AVTextWriterContext *wctx;
+ AVBPrint target_buf;
+ char *buf, *w_name, *w_args;
+ int ret;
+
+ init_sections();
+
+ 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;
+
+ text_formatter = avtext_get_formatter_by_name(w_name);
+ if (text_formatter == NULL) {
+ av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
+ return AVERROR(EINVAL);
+ }
+
+ av_bprint_init(&target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ ret = avtextwriter_create_buffer(&wctx, &target_buf);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
+ return AVERROR(EINVAL);
+ }
+
+ if ((ret = avtext_context_open(&tctx, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), 0, 0, 0, 0, -1, NULL)) >= 0) {
+ avtext_print_section_header(tctx, NULL, SECTION_ID_ROOT);
+
+ avtext_print_section_header(tctx, NULL, 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) {
+ avtext_print_section_header(tctx, NULL, SECTION_ID_FILTERGRAPH);
+
+ av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+ av_bprint_finalize(graph_buf, NULL);
+
+ avtext_print_section_footer(tctx); // 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) {
+ avtext_print_section_header(tctx, NULL, SECTION_ID_FILTERGRAPH);
+
+ av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+ av_bprint_finalize(graph_buf, NULL);
+
+ avtext_print_section_footer(tctx); // SECTION_ID_FILTERGRAPH
+ }
+ }
+ }
+ }
+
+ avtext_print_section_footer(tctx); // SECTION_ID_FILTERGRAPHS
+ avtext_print_section_footer(tctx); // 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*)target_buf.str, FFMIN(target_buf.len, target_buf.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) {
+ printf("%s", target_buf.str);
+ av_log(NULL, AV_LOG_INFO, "%s %c", target_buf.str, '\n');
+ }
+
+ avtext_context_close(&tctx);
+ avtextwriter_context_close(&wctx);
+ }
+
+ return 0;
+}
diff --git a/fftools/ffmpeg_graphprint.h b/fftools/ffmpeg_graphprint.h
new file mode 100644
index 0000000000..5c2635765c
--- /dev/null
+++ b/fftools/ffmpeg_graphprint.h
@@ -0,0 +1,79 @@
+/*
+ * 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"
+#include "textformat/avtextformat.h"
+
+typedef enum {
+ 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 struct AVTextFormatSection sections[] = {
+ [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "GraphDescription", AV_TEXTFORMAT_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", AV_TEXTFORMAT_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", AV_TEXTFORMAT_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", AV_TEXTFORMAT_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", AV_TEXTFORMAT_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", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_LOG, -1 } },
+ [SECTION_ID_LOG] = { SECTION_ID_LOG, "LogEntry", 0, { -1 }, },
+};
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, OutputFile **output_files, int nb_output_files);
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+#endif /* FFTOOLS_FFMPEG_GRAPHPRINT_H */
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 27a9fc9e42..f63d253f53 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;
@@ -1733,6 +1736,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] 27+ messages in thread
* [FFmpeg-devel] [PATCH v3 7/7] fftools: Enable filtergraph printing and update docs
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
` (5 preceding siblings ...)
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 6/7] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
@ 2025-03-01 10:02 ` softworkz
6 siblings, 0 replies; 27+ messages in thread
From: softworkz @ 2025-03-01 10:02 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 fca220a334..0f1a253183 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] 27+ messages in thread
end of thread, other threads:[~2025-03-01 10:04 UTC | newest]
Thread overview: 27+ 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-24 10:41 ` Nicolas George
2025-02-24 13:19 ` Soft Works
2025-02-26 14:42 ` Nicolas George
2025-02-27 13:11 ` 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
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz
2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 2/7] fftools/ffprobe: Change to use textformat api softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 4/7] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 5/7] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 6/7] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 7/7] 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