* [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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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; 73+ 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] 73+ 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 ` (7 more replies) 4 siblings, 8 replies; 73+ 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] 73+ 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-02 17:54 ` Stefano Sabatini ` (2 more replies) 2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 2/7] fftools/ffprobe: Change to use textformat api softworkz ` (6 subsequent siblings) 7 siblings, 3 replies; 73+ 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] 73+ messages in thread
* Re: [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 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz @ 2025-03-02 17:54 ` Stefano Sabatini 2025-03-02 19:44 ` Soft Works 2025-03-08 14:00 ` Stefano Sabatini 2025-03-08 14:36 ` Stefano Sabatini 2 siblings, 1 reply; 73+ messages in thread From: Stefano Sabatini @ 2025-03-02 17:54 UTC (permalink / raw) To: FFmpeg development discussions and patches Cc: Soft Works, softworkz, Andreas Rheinhardt Hi, I'll have a look at this in the week. There are a few things which I want to doublecheck (there is some ad-hoc application logic which might be broken when generalizing this), but I like the overall direction of this changeset. On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: > 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 ++++ Not sure if this is ffmpeg-ish but we might move the formats within a dedicated subdirectory to make more apparent the core/components distinction - anyway this is not a blocker. [...] _______________________________________________ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-02 17:54 ` Stefano Sabatini @ 2025-03-02 19:44 ` Soft Works 2025-03-05 20:20 ` Stefano Sabatini 0 siblings, 1 reply; 73+ messages in thread From: Soft Works @ 2025-03-02 19:44 UTC (permalink / raw) To: Stefano Sabatini, FFmpeg development discussions and patches Cc: Soft Works, Andreas Rheinhardt > -----Original Message----- > From: Stefano Sabatini <stefasab@gmail.com> > Sent: Sonntag, 2. März 2025 18:55 > To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org> > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > <softworkz@hotmail.com>; Andreas Rheinhardt > <andreas.rheinhardt@outlook.com> > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract > and generalize textformat api from ffprobe.c > > Hi, > > I'll have a look at this in the week. There are a few things which I > want to doublecheck (there is some ad-hoc application logic which > might be broken when generalizing this), but I like the overall > direction of this changeset. Hi Stefano, thanks a lot for your interest in this changeset. I think I know what you are talking about with regards to possible breakage 😊. In this changeset, I wanted to keep the changes compact to reduce the number of lines which needs to be reviewed. But there's a preceding patchset "[FFmpeg-devel,v2,0/8,RFC] avtextformat: Transform text writing into an independent API" which allows to follow the changes step-by-step. The part you are probably talking about is done here: https://patchwork.ffmpeg.org/project/ffmpeg/patch/01413dfbc0c60ee17c79370086abb88d78552929.1740718936.git.ffmpegagent@gmail.com/ I have removed the specific fields.. unsigned int nb_section_packet; ///< number of the packet section unsigned int nb_section_frame; ///< number of the frame section unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame ..and replaced it with a 2-dimensional array: unsigned int nb_item[SECTION_MAX_NB_LEVELS]; unsigned int nb_item_type[SECTION_MAX_NB_LEVELS][SECTION_MAX_NB_SECTIONS]; <= new While nb_item counts the total number of current items at a certain level, nb_item_type counts the number of items for each type separately. Then, WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER is replaced with TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT and instead of checking for the specific ID (SECTION_ID_PACKETS_AND_FRAMES), this is done with a new flag (SECTION_FLAG_NUMBERING_BY_TYPE). All that combined allows to achieve the same result as before (separate numbering of mixed array content sections). Generally, it was done in a refactoring way (not a rewrite), so nothing should have gotten lost, only the output writers are new (aviowriter, stdoutwriter, bufferwriter). > > On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: > > 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 ++++ > > Not sure if this is ffmpeg-ish but we might move the formats within a > dedicated subdirectory to make more apparent the core/components > distinction - anyway this is not a blocker. I'll do that, but it's not quite clear how you mean it, as they _are_ in a subdirectory already (textformat). Or do you mean two separate subdirectories (textformat and textwriters)? Another question: shall I add your name/copyright line in the header of the writer files? It's still your code (I assume), I've just transformed it a bit. 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-02 19:44 ` Soft Works @ 2025-03-05 20:20 ` Stefano Sabatini 2025-03-05 20:58 ` Soft Works 0 siblings, 1 reply; 73+ messages in thread From: Stefano Sabatini @ 2025-03-05 20:20 UTC (permalink / raw) To: Soft Works Cc: Soft Works, FFmpeg development discussions and patches, Andreas Rheinhardt On date Sunday 2025-03-02 19:44:34 +0000, Soft Works wrote: > > > > -----Original Message----- > > From: Stefano Sabatini <stefasab@gmail.com> > > Sent: Sonntag, 2. März 2025 18:55 > > To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org> > > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > > <softworkz@hotmail.com>; Andreas Rheinhardt > > <andreas.rheinhardt@outlook.com> > > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract > > and generalize textformat api from ffprobe.c > > > > Hi, > > > > I'll have a look at this in the week. There are a few things which I > > want to doublecheck (there is some ad-hoc application logic which > > might be broken when generalizing this), but I like the overall > > direction of this changeset. > > Hi Stefano, > > thanks a lot for your interest in this changeset. > > I think I know what you are talking about with regards to possible breakage 😊. > > In this changeset, I wanted to keep the changes compact to reduce the number of lines which needs to be reviewed. But there's a preceding patchset > > "[FFmpeg-devel,v2,0/8,RFC] avtextformat: Transform text writing into an independent API" > > which allows to follow the changes step-by-step. > > The part you are probably talking about is done here: > > https://patchwork.ffmpeg.org/project/ffmpeg/patch/01413dfbc0c60ee17c79370086abb88d78552929.1740718936.git.ffmpegagent@gmail.com/ > > > I have removed the specific fields.. > > unsigned int nb_section_packet; ///< number of the packet section > unsigned int nb_section_frame; ///< number of the frame section > unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame > > ..and replaced it with a 2-dimensional array: > > unsigned int nb_item[SECTION_MAX_NB_LEVELS]; > unsigned int nb_item_type[SECTION_MAX_NB_LEVELS][SECTION_MAX_NB_SECTIONS]; <= new > > While nb_item counts the total number of current items at a certain level, nb_item_type counts the number of items for each type separately. > > Then, > > WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER > > is replaced with > > TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT > > and instead of checking for the specific ID > (SECTION_ID_PACKETS_AND_FRAMES), this is done with a new flag > (SECTION_FLAG_NUMBERING_BY_TYPE). All that combined allows to > achieve the same result as before (separate numbering of mixed array > content sections). > > > Generally, it was done in a refactoring way (not a rewrite), so > nothing should have gotten lost, only the output writers are new > (aviowriter, stdoutwriter, bufferwriter). Cool, I think this should work indeed. > > > > On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: > > > 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 ++++ > > > > Not sure if this is ffmpeg-ish but we might move the formats within a > > dedicated subdirectory to make more apparent the core/components > > distinction - anyway this is not a blocker. > > I'll do that, but it's not quite clear how you mean it, as they _are_ in a subdirectory already (textformat). > Or do you mean two separate subdirectories (textformat and textwriters)? What I mean is that we might move the text formats to a dedicated directory (e.g. fftools/textformat/formats/compact.c) but it's not blocking at all. > Another question: shall I add your name/copyright line in the header > of the writer files? It's still your code (I assume), I've just > transformed it a bit. Feel free to skip it, git blame does a better job at tracking the authorship and I don't mind a lot about copyright anyway. _______________________________________________ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-05 20:20 ` Stefano Sabatini @ 2025-03-05 20:58 ` Soft Works 0 siblings, 0 replies; 73+ messages in thread From: Soft Works @ 2025-03-05 20:58 UTC (permalink / raw) To: Stefano Sabatini Cc: Soft Works, FFmpeg development discussions and patches, Andreas Rheinhardt > -----Original Message----- > From: Stefano Sabatini <stefasab@gmail.com> > Sent: Mittwoch, 5. März 2025 21:21 > To: Soft Works <softworkz@hotmail.com> > Cc: FFmpeg development discussions and patches <ffmpeg- > devel@ffmpeg.org>; Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; > Andreas Rheinhardt <andreas.rheinhardt@outlook.com> > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract > and generalize textformat api from ffprobe.c > > On date Sunday 2025-03-02 19:44:34 +0000, Soft Works wrote: > > > > > > > -----Original Message----- > > > From: Stefano Sabatini <stefasab@gmail.com> > > > Sent: Sonntag, 2. März 2025 18:55 > > > To: FFmpeg development discussions and patches <ffmpeg- > devel@ffmpeg.org> > > > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > > > <softworkz@hotmail.com>; Andreas Rheinhardt > > > <andreas.rheinhardt@outlook.com> > > > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: > Extract > > > and generalize textformat api from ffprobe.c > > > > > > Hi, > > > > > > I'll have a look at this in the week. There are a few things which I > > > want to doublecheck (there is some ad-hoc application logic which > > > might be broken when generalizing this), but I like the overall > > > direction of this changeset. > > > > Hi Stefano, > > > > thanks a lot for your interest in this changeset. > > > > I think I know what you are talking about with regards to possible > breakage 😊. > > > > In this changeset, I wanted to keep the changes compact to reduce the > number of lines which needs to be reviewed. But there's a preceding > patchset > > > > "[FFmpeg-devel,v2,0/8,RFC] avtextformat: Transform text writing into > an independent API" > > > > which allows to follow the changes step-by-step. > > > > The part you are probably talking about is done here: > > > > > https://patchwork.ffmpeg.org/project/ffmpeg/patch/01413dfbc0c60ee17c7937 > 0086abb88d78552929.1740718936.git.ffmpegagent@gmail.com/ > > > > > > I have removed the specific fields.. > > > > unsigned int nb_section_packet; ///< number of the packet section > > unsigned int nb_section_frame; ///< number of the frame section > > unsigned int nb_section_packet_frame; ///< nb_section_packet or > nb_section_frame > > > > ..and replaced it with a 2-dimensional array: > > > > unsigned int nb_item[SECTION_MAX_NB_LEVELS]; > > unsigned int > nb_item_type[SECTION_MAX_NB_LEVELS][SECTION_MAX_NB_SECTIONS]; <= new > > > > While nb_item counts the total number of current items at a certain > level, nb_item_type counts the number of items for each type separately. > > > > Then, > > > > WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER > > > > is replaced with > > > > TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT > > > > > and instead of checking for the specific ID > > (SECTION_ID_PACKETS_AND_FRAMES), this is done with a new flag > > (SECTION_FLAG_NUMBERING_BY_TYPE). All that combined allows to > > achieve the same result as before (separate numbering of mixed array > > content sections). > > > > > > Generally, it was done in a refactoring way (not a rewrite), so > > nothing should have gotten lost, only the output writers are new > > (aviowriter, stdoutwriter, bufferwriter). > > Cool, I think this should work indeed. Thanks for looking at it! > > > Not sure if this is ffmpeg-ish but we might move the formats within > a > > > dedicated subdirectory to make more apparent the core/components > > > distinction - anyway this is not a blocker. > > > > I'll do that, but it's not quite clear how you mean it, as they _are_ > in a subdirectory already (textformat). > > Or do you mean two separate subdirectories (textformat and > textwriters)? > > What I mean is that we might move the text formats to a dedicated > directory (e.g. fftools/textformat/formats/compact.c) but it's not > blocking at all. Originally, I had thought the similar in a way that it might not be "ffmpeg-ish"; actually I've even be afraid that somebody might object the textformat subfolder. The above (fftools/textformat/formats/) is how I'd personally organize it as well, but of course everybody needs to adapt in a community project. Some more opinions would be helpful.. > > > Another question: shall I add your name/copyright line in the header > > of the writer files? It's still your code (I assume), I've just > > transformed it a bit. > > Feel free to skip it, git blame does a better job at tracking the > authorship and I don't mind a lot about copyright anyway. Neither do I. (Well, there's not much rights to get anyway with a stupid pseudonym 😃). 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] 73+ messages in thread
* Re: [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 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz 2025-03-02 17:54 ` Stefano Sabatini @ 2025-03-08 14:00 ` Stefano Sabatini 2025-03-08 15:01 ` Soft Works 2025-03-08 14:36 ` Stefano Sabatini 2 siblings, 1 reply; 73+ messages in thread From: Stefano Sabatini @ 2025-03-08 14:00 UTC (permalink / raw) To: FFmpeg development discussions and patches Cc: Soft Works, softworkz, Andreas Rheinhardt Sorry for delayed review, due to a sickness on my side for the past three days. On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: > 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 nit: FFmpeg here and in the other headers > + * > + * 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) why the prefix for a static const? > +{ > + 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) trext -> text typo? > +{ > + 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; let's aid the programmer with a message error code, return EINVAL? > + > + 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); set ret = AVERROR(EINVAL) or this will return 0 > + 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) { non blocking, but probably we want to provide a more programmer friendly interface, so there is no need to serialize and deserialiaze the data here > + 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) you can also drop the public av prefix here > +{ > + 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; AVERROR(EINVAL) > + > + 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]; maybe use a const here, also I'd be more happy if we had a more dynamic registration system to avoid the hardcoded bits > +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, this might be later changed to flags to simplify the interface > + 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. > + */ totally unrelated fun fact, I later discovered this is not supported by MS Excel: https://answers.microsoft.com/en-us/msoffice/forum/all/why-excel-does-not-support-rfc-4180-standard-for/d0e379b1-ec3e-40d0-ad4f-33b20693c030 > +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, > +}; I didn't review the formatters code assuming this was copied and adapted from the ffprobe.c file. [...] The rest looks good to me. _______________________________________________ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-08 14:00 ` Stefano Sabatini @ 2025-03-08 15:01 ` Soft Works 0 siblings, 0 replies; 73+ messages in thread From: Soft Works @ 2025-03-08 15:01 UTC (permalink / raw) To: Stefano Sabatini, FFmpeg development discussions and patches; +Cc: Soft Works > -----Original Message----- > From: Stefano Sabatini <stefasab@gmail.com> > Sent: Samstag, 8. März 2025 15:01 > To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org> > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > <softworkz@hotmail.com>; Andreas Rheinhardt > <andreas.rheinhardt@outlook.com> > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract > and generalize textformat api from ffprobe.c > > Sorry for delayed review, due to a sickness on my side for the past > three days. No worries, I think it was pretty quick and this is waiting for long anyway 😊 I've done all the changes except of type "future improvements" and this: > > +static const AVTextFormatter *registered_formatters[7+1]; > > maybe use a const here, also I'd be more happy if we had a more > dynamic registration system to avoid the hardcoded bits While trying this, I remembered that I had tried this before already, but it causes trouble with static initialization order. Probably that's the reason why it has been like this before already, I'm not sure? > I didn't review the formatters code assuming this was copied and > adapted from the ffprobe.c file. Yup. > The rest looks good to me. Thanks a lot for your review! (I'll respond to the other ones separately) 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] 73+ messages in thread
* Re: [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 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz 2025-03-02 17:54 ` Stefano Sabatini 2025-03-08 14:00 ` Stefano Sabatini @ 2025-03-08 14:36 ` Stefano Sabatini 2025-03-08 15:30 ` Soft Works 2025-03-09 18:55 ` Soft Works 2 siblings, 2 replies; 73+ messages in thread From: Stefano Sabatini @ 2025-03-08 14:36 UTC (permalink / raw) To: FFmpeg development discussions and patches Cc: Soft Works, softworkz, Andreas Rheinhardt On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: [...] > +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); writer -> writer_ctx? I'm fine with changing this later to avoid massive rebase edits. Also I notice there is some of the usual inconsistencies here: av_X_Y against avXY and avX_Y that we have in the rest of the code. Maybe let's stick to avX_Y or to av_X_Y. Also this might be: av_text_format_open(...) av_text_format_close(...) av_text_format_print_X(...) Or to simplify we can just call the structure AVTextContext (I see text as an evolution of a string, meant for structured formatted data, which is implied by the fact that we need a formatter) and simplify related functions naming to: av_text_open(...) av_text_close(...) av_text_print_X(...) av_text_formatter_... av_text_writer_open... av_text_writer_close... In fact I don't think there is much gain in keeping "context" in the name of the functions. What do you think? Again, since this is not public API (yet?) this should not be considered a blocker (also I've been out of touch with FFmpeg and I might be not aware of API conventions evolution). _______________________________________________ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-08 14:36 ` Stefano Sabatini @ 2025-03-08 15:30 ` Soft Works 2025-03-08 18:12 ` Stefano Sabatini 2025-03-09 18:55 ` Soft Works 1 sibling, 1 reply; 73+ messages in thread From: Soft Works @ 2025-03-08 15:30 UTC (permalink / raw) To: Stefano Sabatini, FFmpeg development discussions and patches; +Cc: Soft Works > -----Original Message----- > From: Stefano Sabatini <stefasab@gmail.com> > Sent: Samstag, 8. März 2025 15:37 > To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org> > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > <softworkz@hotmail.com>; Andreas Rheinhardt > <andreas.rheinhardt@outlook.com> > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract > and generalize textformat api from ffprobe.c > > On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: > [...] > > > +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); > > writer -> writer_ctx? > > I'm fine with changing this later to avoid massive rebase edits. No problem, I realized that it's just a matter of tooling after getting annoyed by these things for years 😊 (done already) > Also I notice there is some of the usual inconsistencies here: > av_X_Y against avXY and avX_Y that we have in the rest of the code. > > Maybe let's stick to avX_Y or to av_X_Y. > > Also this might be: > av_text_format_open(...) > av_text_format_close(...) > av_text_format_print_X(...) > > Or to simplify we can just call the structure AVTextContext (I see > text as an evolution of a string, meant for structured formatted data, > which is implied by the fact that we need a formatter) and simplify > related functions naming to: > av_text_open(...) > av_text_close(...) > av_text_print_X(...) > > av_text_formatter_... > av_text_writer_open... > av_text_writer_close... > > In fact I don't think there is much gain in keeping "context" in the > name of the functions. > > What do you think? > > Again, since this is not public API (yet?) this should not be > considered a blocker (also I've been out of touch with FFmpeg and I > might be not aware of API conventions evolution). From bottom to top: Regarding public API, I tried to name everything as if it would be public in order to make it easier and less invasive in case that would happen. I also set this as a goal (and originally submitted it, being in avutil) to make it fully independent. Nicolas meant that it's not ready in the current form to become public and he's probably right about it. But in the new form and location it will be easy to work on it as the big refactoring change is out of the way now. Naming: I think the word context is helpful to indicate what it is - like Codec and Codec-Context there's AVTextFormatter and AVTextFormatContext - imo it is good to understand the relation between the too. For everything else I don't mind. I had changed the namings myself a number of times but each time it ended with another inconsistency - I was just kind of moving the inconsistency around. So, I'll happily rename everything in whatever way is desired, I'd just say that before renaming we should make a full plan in advance which covers the full range. 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-08 15:30 ` Soft Works @ 2025-03-08 18:12 ` Stefano Sabatini 2025-03-08 19:25 ` Soft Works 0 siblings, 1 reply; 73+ messages in thread From: Stefano Sabatini @ 2025-03-08 18:12 UTC (permalink / raw) To: Soft Works; +Cc: Soft Works, FFmpeg development discussions and patches On date Saturday 2025-03-08 15:30:28 +0000, Soft Works wrote: > > > > -----Original Message----- > > From: Stefano Sabatini <stefasab@gmail.com> > > Sent: Samstag, 8. März 2025 15:37 > > To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org> > > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > > <softworkz@hotmail.com>; Andreas Rheinhardt > > <andreas.rheinhardt@outlook.com> > > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract > > and generalize textformat api from ffprobe.c > > > > On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: > > [...] > > > > > +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); > > > > writer -> writer_ctx? > > > > I'm fine with changing this later to avoid massive rebase edits. > > No problem, I realized that it's just a matter of tooling after getting annoyed by these things for years 😊 > (done already) I'm curious what tooling are you using in this case? [...] > Naming: > > I think the word context is helpful to indicate what it is - like Codec and Codec-Context there's AVTextFormatter and AVTextFormatContext - imo it is good to understand the relation between the too. > > For everything else I don't mind. I had changed the namings myself a number of times but each time it ended with another inconsistency - I was just kind of moving the inconsistency around. > So, I'll happily rename everything in whatever way is desired, I'd just say that before renaming we should make a full plan in advance which covers the full range. OK, anyway this can be done in a second step so it's not blocking. Just to elaborate a bit more: int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options); note that in avcodec_open2 does not refer to context, although we provide both a context and a codec. In general adding that word is providing no added information. _______________________________________________ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-08 18:12 ` Stefano Sabatini @ 2025-03-08 19:25 ` Soft Works 0 siblings, 0 replies; 73+ messages in thread From: Soft Works @ 2025-03-08 19:25 UTC (permalink / raw) To: Stefano Sabatini; +Cc: Soft Works, FFmpeg development discussions and patches > -----Original Message----- > From: Stefano Sabatini <stefasab@gmail.com> > Sent: Samstag, 8. März 2025 19:12 > To: Soft Works <softworkz@hotmail.com> > Cc: FFmpeg development discussions and patches <ffmpeg- > devel@ffmpeg.org>; Soft Works <softworkz-at-hotmail.com@ffmpeg.org> > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract > and generalize textformat api from ffprobe.c > > On date Saturday 2025-03-08 15:30:28 +0000, Soft Works wrote: > > > > > > > -----Original Message----- > > > From: Stefano Sabatini <stefasab@gmail.com> > > > Sent: Samstag, 8. März 2025 15:37 > > > To: FFmpeg development discussions and patches <ffmpeg- > devel@ffmpeg.org> > > > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > > > <softworkz@hotmail.com>; Andreas Rheinhardt > > > <andreas.rheinhardt@outlook.com> > > > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: > Extract > > > and generalize textformat api from ffprobe.c > > > > > > On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: > > > [...] > > > > > > > +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); > > > > > > writer -> writer_ctx? > > > > > > I'm fine with changing this later to avoid massive rebase edits. > > > > > No problem, I realized that it's just a matter of tooling after > getting annoyed by these things for years 😊 > > (done already) > > I'm curious what tooling are you using in this case? SmartGit. It's from a small German company, Win/Linux/Mac. Window > Show Log Window - this is what I'm always using. The 'Standard window' is a recent addition but it doesn't provide that workflow where you're really feeling in charge. At the left side bottom, there's a tree with all branches, local and remote with checkboxes. Those checkboxes are magic. All checked branches are mixed together in the log list. In that list when you select two entries, you get the diff between those shown like normal commits, while the two commits can be from anywhere no matter whether local or remote or even disjoint. Another key point is the way how it allows to reshape a range of commits in no time. You can select files in past commits and split them off in a second, drag-drop re-order commits, modify past commits, revert changes from a single file in a past commit on top of the HEAD with a single click. So, when you have a range of commits and someone says change A needs to be in commit X and B moved to Y and all these kinds of things, you can get it done even before you're clear what your first CLI command would need to be. It's well thought out in so many details and allows me to work like I had always thought it should be. Sorry for the commercial, but such great tool deserves it imo. > [...] > > > Naming: > > > > I think the word context is helpful to indicate what it is - like > Codec and Codec-Context there's AVTextFormatter and AVTextFormatContext > - imo it is good to understand the relation between the too. > > > > For everything else I don't mind. I had changed the namings myself a > number of times but each time it ended with another inconsistency - I > was just kind of moving the inconsistency around. > > So, I'll happily rename everything in whatever way is desired, I'd > just say that before renaming we should make a full plan in advance > which covers the full range. > > OK, anyway this can be done in a second step so it's not blocking. > > Just to elaborate a bit more: > int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, > AVDictionary **options); > > note that in avcodec_open2 does not refer to context, although we > provide both a context and a codec. In general adding that word is > providing no added information. Okay fine, no need to argue about that bit! _______________________________________________ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-08 14:36 ` Stefano Sabatini 2025-03-08 15:30 ` Soft Works @ 2025-03-09 18:55 ` Soft Works 1 sibling, 0 replies; 73+ messages in thread From: Soft Works @ 2025-03-09 18:55 UTC (permalink / raw) To: Stefano Sabatini, FFmpeg development discussions and patches; +Cc: Soft Works > -----Original Message----- > From: Stefano Sabatini <stefasab@gmail.com> > Sent: Samstag, 8. März 2025 15:37 > To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org> > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > <softworkz@hotmail.com>; Andreas Rheinhardt > <andreas.rheinhardt@outlook.com> > Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract > and generalize textformat api from ffprobe.c > > On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: > [...] > > > +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); > > writer -> writer_ctx? > > I'm fine with changing this later to avoid massive rebase edits. > > Also I notice there is some of the usual inconsistencies here: > av_X_Y against avXY and avX_Y that we have in the rest of the code. > > Maybe let's stick to avX_Y or to av_X_Y. > > Also this might be: > av_text_format_open(...) > av_text_format_close(...) > av_text_format_print_X(...) > > Or to simplify we can just call the structure AVTextContext (I see > text as an evolution of a string, meant for structured formatted data, > which is implied by the fact that we need a formatter) and simplify > related functions naming to: > av_text_open(...) > av_text_close(...) > av_text_print_X(...) > > av_text_formatter_... > av_text_writer_open... > av_text_writer_close... > > In fact I don't think there is much gain in keeping "context" in the > name of the functions. > > What do you think? > > Again, since this is not public API (yet?) this should not be > considered a blocker (also I've been out of touch with FFmpeg and I > might be not aware of API conventions evolution). Hi Stefano, regarding the API naming I thought that maybe it's better to get it straight right away. To make it fully consistent as mentioned, we should look at all "public" API names. Here's a complete list: # "avtexformat.h" ## Defines AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS => AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT => AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT AV_TEXTFORMAT_PRINT_STRING_OPTIONAL => AV_TEXTFORMAT_PRINT_STRING_OPTIONAL AV_TEXTFORMAT_PRINT_STRING_VALIDATE => AV_TEXTFORMAT_PRINT_STRING_VALIDATE ## Enum StringValidation => StringValidation AV_TEXTFORMAT_STRING_VALIDATION_FAIL => AV_TEXTFORMAT_STRING_VALIDATION_FAIL AV_TEXTFORMAT_STRING_VALIDATION_REPLACE => AV_TEXTFORMAT_STRING_VALIDATION_REPLACE AV_TEXTFORMAT_STRING_VALIDATION_IGNORE => AV_TEXTFORMAT_STRING_VALIDATION_IGNORE AV_TEXTFORMAT_STRING_VALIDATION_NB => AV_TEXTFORMAT_STRING_VALIDATION_NB ## Structs AVTextFormatSection => AVTextFormatSection AVTextFormatter => AVTextFormatter AVTextFormatContext => AVTextFormatContext ## Functions avtext_context_open() => avtext_context_open() avtext_context_close() => avtext_context_close() avtext_print_section_header() => avtext_print_section_header() avtext_print_section_footer() => avtext_print_section_footer() avtext_print_integer() => avtext_print_integer() avtext_print_string() => avtext_print_string() avtext_print_unit_int() => avtext_print_unit_int() avtext_print_rational() => avtext_print_rational() avtext_print_time() => avtext_print_time() avtext_print_ts() => avtext_print_ts() avtext_print_data() => avtext_print_data() avtext_print_data_hash() => avtext_print_data_hash() avtext_print_integers() => avtext_print_integers() avtext_get_formatter_by_name() => avtext_get_formatter_by_name() # "avtextwriters.h" ## Structs AVTextWriter => AVTextWriter AVTextWriterContext => AVTextWriterContext ## Functions avtextwriter_context_open() => avtextwriter_context_open() avtextwriter_context_close() => avtextwriter_context_close() avtextwriter_create_stdout() => avtextwriter_create_stdout() avtextwriter_create_avio() => avtextwriter_create_avio() avtextwriter_create_file() => avtextwriter_create_file() avtextwriter_create_buffer() => avtextwriter_create_buffer() If you could edit the right sides in the way you think it should be, I'll make the changes accordingly. 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] 73+ 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-08 14:18 ` Stefano Sabatini 2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz ` (5 subsequent siblings) 7 siblings, 1 reply; 73+ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 2/7] fftools/ffprobe: Change to use textformat api 2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 2/7] fftools/ffprobe: Change to use textformat api softworkz @ 2025-03-08 14:18 ` Stefano Sabatini 0 siblings, 0 replies; 73+ messages in thread From: Stefano Sabatini @ 2025-03-08 14:18 UTC (permalink / raw) To: FFmpeg development discussions and patches Cc: Soft Works, softworkz, Andreas Rheinhardt On date Saturday 2025-03-01 10:01:59 +0000, softworkz wrote: > 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(-) [...] Overall makes sense. I spotted a few weird indents and possibly a few patch un-related API inconsistencies I will treat in a separate thread, but overall I think this is fine and welcome. _______________________________________________ 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] 73+ 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-08 14:46 ` Stefano Sabatini 2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 4/7] fftools/ffmpeg_filter: Move some declaration to new header file softworkz ` (4 subsequent siblings) 7 siblings, 1 reply; 73+ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext 2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz @ 2025-03-08 14:46 ` Stefano Sabatini 2025-03-08 15:46 ` Soft Works 2025-03-08 17:54 ` Soft Works 0 siblings, 2 replies; 73+ messages in thread From: Stefano Sabatini @ 2025-03-08 14:46 UTC (permalink / raw) To: FFmpeg development discussions and patches Cc: Soft Works, softworkz, Andreas Rheinhardt On date Saturday 2025-03-01 10:02:00 +0000, softworkz wrote: > 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) maybe while at it also change w to f or t? again this might be done as a patch on top, or there be will wondering readers [...] Should be good otherwise. _______________________________________________ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext 2025-03-08 14:46 ` Stefano Sabatini @ 2025-03-08 15:46 ` Soft Works 2025-03-08 17:54 ` Soft Works 1 sibling, 0 replies; 73+ messages in thread From: Soft Works @ 2025-03-08 15:46 UTC (permalink / raw) To: Stefano Sabatini, FFmpeg development discussions and patches Cc: Soft Works, Andreas Rheinhardt > -----Original Message----- > From: Stefano Sabatini <stefasab@gmail.com> > Sent: Samstag, 8. März 2025 15:46 > To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org> > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > <softworkz@hotmail.com>; Andreas Rheinhardt > <andreas.rheinhardt@outlook.com> > Subject: Re: [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename > writer_print_section_* and WriterContext > > On date Saturday 2025-03-01 10:02:00 +0000, softworkz wrote: > > 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) > > maybe while at it also change w to f or t? > > again this might be done as a patch on top, or there be will wondering > readers Yea, I thought when I'd do this, then there'd nobody ever want to review this 😊 (Done) _______________________________________________ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext 2025-03-08 14:46 ` Stefano Sabatini 2025-03-08 15:46 ` Soft Works @ 2025-03-08 17:54 ` Soft Works 1 sibling, 0 replies; 73+ messages in thread From: Soft Works @ 2025-03-08 17:54 UTC (permalink / raw) To: Stefano Sabatini, FFmpeg development discussions and patches; +Cc: Soft Works > -----Original Message----- > From: Stefano Sabatini <stefasab@gmail.com> > Sent: Samstag, 8. März 2025 15:46 > To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org> > Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>; softworkz > <softworkz@hotmail.com>; Andreas Rheinhardt > <andreas.rheinhardt@outlook.com> > Subject: Re: [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename > writer_print_section_* and WriterContext > > On date Saturday 2025-03-01 10:02:00 +0000, softworkz wrote: > > 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) > > maybe while at it also change w to f or t? > So I tried that (t) and it compiles, but FATE test are failing. I wasn't able to identify cause without going deeper, I don't know how exactly FATE tests are making use of ffprobe (or its code?). Most likely there's a naming collision. I've renamed it to 'tfc'. It has two more letters but it has the advantage that it's a unique token in the file. It's isolated in the last commit, to you can also skip that last one if you prefer. Thanks again for your effort 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] 73+ 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 ` (3 subsequent siblings) 7 siblings, 0 replies; 73+ 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] 73+ 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 ` (2 subsequent siblings) 7 siblings, 0 replies; 73+ 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] 73+ 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 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent 7 siblings, 0 replies; 73+ 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] 73+ 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 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent 7 siblings, 0 replies; 73+ 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing 2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent ` (6 preceding siblings ...) 2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 7/7] fftools: Enable filtergraph printing and update docs softworkz @ 2025-03-01 22:54 ` ffmpegagent 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz ` (7 more replies) 7 siblings, 8 replies; 73+ messages in thread From: ffmpegagent @ 2025-03-01 22:54 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 Update V4 * Fix "new warnings" were generated * Fix missing colon in commit message * Rebase due to changesin ApiChanges 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 | 518 +++++++ fftools/ffmpeg_graphprint.h | 35 + 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: 0245e9382c748eba91645b65a377c4c9c4a44849 Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-52%2Fsoftworkz%2Fsubmit_print_graphs5-v4 Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-52/softworkz/submit_print_graphs5-v4 Pull-Request: https://github.com/ffstaging/FFmpeg/pull/52 Range-diff vs v3: 1: 6239813ba0 = 1: 55bfc6c6a6 fftools/textformat: Extract and generalize textformat api from ffprobe.c 2: 805f66cd92 = 2: 1950903610 fftools/ffprobe: Change to use textformat api 3: 8dcc17112d = 3: 01cc022c3b fftools/ffprobe: Rename writer_print_section_* and WriterContext 4: 06174ae7ef ! 4: aed98ad79d fftools/ffmpeg_filter: Move some declaration to new header file @@ fftools/ffmpeg_filter.h (new) + +} FilterGraphPriv; + -+static FilterGraphPriv *fgp_from_fg(FilterGraph *fg) ++static inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg) +{ + return (FilterGraphPriv*)fg; +} + -+static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg) ++static inline const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg) +{ + return (const FilterGraphPriv*)fg; +} @@ fftools/ffmpeg_filter.h (new) + } sub2video; +} InputFilterPriv; + -+static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter) ++static inline InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter) +{ + return (InputFilterPriv*)ifilter; +} @@ fftools/ffmpeg_filter.h (new) + unsigned flags; +} OutputFilterPriv; + -+static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter) ++static inline OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter) +{ + return (OutputFilterPriv*)ofilter; +} 5: d93c23872f ! 5: 4b8d7ee51b avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() @@ Metadata Author: softworkz <softworkz@hotmail.com> ## Commit message ## - avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() + avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() ## doc/APIchanges ## @@ doc/APIchanges: The last version increases of all libraries were on 2024-03-07 @@ doc/APIchanges: The last version increases of all libraries were on 2024-03-07 +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. + 2025-03-01 - xxxxxxxxxx - lavu 59.58.100 - pixfmt.h + Add AV_PIX_FMT_GRAY32BE and AV_PIX_FMT_GRAY32LE. ## libavfilter/avfilter.c ## 6: 9d76b4df1b ! 6: f43eb56f39 fftools/ffmpeg_graphprint: Add options for filtergraph printing @@ fftools/ffmpeg_graphprint.c (new) +#include "libavutil/hwcontext.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 }, }, ++}; ++ +/* 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) @@ fftools/ffmpeg_graphprint.c (new) + +static void print_link(AVTextFormatContext *w, AVFilterLink *link) +{ ++ AVBufferRef *hw_frames_ctx; + char layoutString[64]; + + switch (link->type) { @@ fftools/ffmpeg_graphprint.c (new) + break; + } + -+ AVBufferRef *hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link); ++ 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); @@ fftools/ffmpeg_graphprint.h (new) +#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); + 7: 049f720ae7 = 7: c0cf4adcd9 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v4 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent @ 2025-03-01 22:54 ` softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 2/7] fftools/ffprobe: Change to use textformat api softworkz ` (6 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-01 22:54 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v4 2/7] fftools/ffprobe: Change to use textformat api 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz @ 2025-03-01 22:54 ` softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz ` (5 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-01 22:54 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v4 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 2/7] fftools/ffprobe: Change to use textformat api softworkz @ 2025-03-01 22:54 ` softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 4/7] fftools/ffmpeg_filter: Move some declaration to new header file softworkz ` (4 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-01 22:54 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v4 4/7] fftools/ffmpeg_filter: Move some declaration to new header file 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent ` (2 preceding siblings ...) 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz @ 2025-03-01 22:54 ` softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 5/7] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz ` (3 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-01 22:54 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..94296c5eac --- /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 inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg) +{ + return (FilterGraphPriv*)fg; +} + +static inline 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 inline 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 inline 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v4 5/7] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent ` (3 preceding siblings ...) 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 4/7] fftools/ffmpeg_filter: Move some declaration to new header file softworkz @ 2025-03-01 22:54 ` softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 6/7] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz ` (2 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-01 22:54 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 5a64836e25..354716399d 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-03-01 - xxxxxxxxxx - lavu 59.58.100 - pixfmt.h Add AV_PIX_FMT_GRAY32BE and AV_PIX_FMT_GRAY32LE. 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v4 6/7] fftools/ffmpeg_graphprint: Add options for filtergraph printing 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent ` (4 preceding siblings ...) 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 5/7] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz @ 2025-03-01 22:54 ` softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 7/7] fftools: Enable filtergraph printing and update docs softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-01 22:54 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 | 518 ++++++++++++++++++++++++++++++++++++ fftools/ffmpeg_graphprint.h | 35 +++ 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..dd6862679e --- /dev/null +++ b/fftools/ffmpeg_graphprint.c @@ -0,0 +1,518 @@ +/* + * 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" + +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 }, }, +}; + +/* 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) +{ + AVBufferRef *hw_frames_ctx; + 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; + } + + 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..e95a0773ba --- /dev/null +++ b/fftools/ffmpeg_graphprint.h @@ -0,0 +1,35 @@ +/* + * 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" + +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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v4 7/7] fftools: Enable filtergraph printing and update docs 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent ` (5 preceding siblings ...) 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 6/7] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz @ 2025-03-01 22:54 ` softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-01 22:54 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent ` (6 preceding siblings ...) 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 7/7] fftools: Enable filtergraph printing and update docs softworkz @ 2025-03-08 17:55 ` ffmpegagent 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz ` (8 more replies) 7 siblings, 9 replies; 73+ messages in thread From: ffmpegagent @ 2025-03-08 17:55 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz 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 Update V4 * Fix "new warnings" were generated * Fix missing colon in commit message * Rebase due to changesin ApiChanges Update V5 * Fix copyright headers * Return proper error codes rather than -1 * Fix misnamed local functions * Fix typo * Rename 'w' in ffprobe.c to 'tfc' softworkz (8): 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 fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) 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 | 518 +++++++ fftools/ffmpeg_graphprint.h | 35 + fftools/ffmpeg_opt.c | 12 + fftools/ffprobe.c | 2296 +++++----------------------- fftools/textformat/avtextformat.c | 672 ++++++++ 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, 3630 insertions(+), 2133 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: 0245e9382c748eba91645b65a377c4c9c4a44849 Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-52%2Fsoftworkz%2Fsubmit_print_graphs5-v5 Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-52/softworkz/submit_print_graphs5-v5 Pull-Request: https://github.com/ffstaging/FFmpeg/pull/52 Range-diff vs v4: 1: 55bfc6c6a6 ! 1: b26a45ea0e fftools/textformat: Extract and generalize textformat api from ffprobe.c @@ Commit message ## fftools/textformat/avtextformat.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/avtextformat.c (new) + { 1.125899906842624e15, 1e15, "Pi", "P" }, +}; + -+static const char *avtext_context_get_formatter_name(void *p) ++static const char *textcontext_get_formatter_name(void *p) +{ + AVTextFormatContext *tctx = p; + return tctx->formatter->name; @@ fftools/textformat/avtextformat.c (new) + { NULL } +}; + -+static void *trextcontext_child_next(void *obj, void *prev) ++static void *textcontext_child_next(void *obj, void *prev) +{ + AVTextFormatContext *ctx = obj; + if (!prev && ctx->formatter && ctx->formatter->priv_class && ctx->priv) @@ fftools/textformat/avtextformat.c (new) + +static const AVClass textcontext_class = { + .class_name = "AVTextContext", -+ .item_name = avtext_context_get_formatter_name, ++ .item_name = textcontext_get_formatter_name, + .option = textcontext_options, + .version = LIBAVUTIL_VERSION_INT, -+ .child_next = trextcontext_child_next, ++ .child_next = textcontext_child_next, +}; + +static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size) @@ fftools/textformat/avtextformat.c (new) + int ret = 0; + + if (!tctx) -+ return -1; ++ return EINVAL; + + av_hash_freep(&tctx->hash); + @@ fftools/textformat/avtextformat.c (new) + + 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); ++ ret = AVERROR(EINVAL); + goto fail; + } + @@ fftools/textformat/avtextformat.c (new) + av_bprint_finalize(&bp, NULL); +} + -+static const char *avtextwriter_context_get_writer_name(void *p) ++static const char *writercontext_get_writer_name(void *p) +{ + AVTextWriterContext *wctx = p; + return wctx->writer->name; @@ fftools/textformat/avtextformat.c (new) + +static const AVClass textwriter_class = { + .class_name = "AVTextWriterContext", -+ .item_name = avtextwriter_context_get_writer_name, ++ .item_name = writercontext_get_writer_name, + .version = LIBAVUTIL_VERSION_INT, + .child_next = writercontext_child_next, +}; @@ fftools/textformat/avtextformat.c (new) + int ret = 0; + + if (!wctx) -+ return -1; ++ return EINVAL; + + if (wctx->writer->uninit) + wctx->writer->uninit(wctx); @@ fftools/textformat/avtextformat.c (new) ## fftools/textformat/avtextformat.h (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/avtextformat.h (new) +#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, ++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, @@ fftools/textformat/avtextformat.h (new) ## fftools/textformat/avtextwriters.h (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/avtextwriters.h (new) ## fftools/textformat/tf_compact.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/tf_compact.c (new) ## fftools/textformat/tf_default.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/tf_default.c (new) ## fftools/textformat/tf_flat.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/tf_flat.c (new) ## fftools/textformat/tf_ini.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/tf_ini.c (new) ## fftools/textformat/tf_json.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/tf_json.c (new) ## fftools/textformat/tf_xml.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/tf_xml.c (new) ## fftools/textformat/tw_avio.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/tw_avio.c (new) ## fftools/textformat/tw_buffer.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * @@ fftools/textformat/tw_buffer.c (new) ## fftools/textformat/tw_stdout.c (new) ## @@ +/* -+ * Copyright (c) The ffmpeg developers ++ * Copyright (c) The FFmpeg developers + * + * This file is part of FFmpeg. + * 2: 1950903610 = 2: 2eb2b4ded5 fftools/ffprobe: Change to use textformat api 3: 01cc022c3b = 3: 5ab9ac2a28 fftools/ffprobe: Rename writer_print_section_* and WriterContext 4: aed98ad79d = 4: 85735035e4 fftools/ffmpeg_filter: Move some declaration to new header file 5: 4b8d7ee51b = 5: 811c5ab35c avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() 6: f43eb56f39 = 6: 8ae08a7006 fftools/ffmpeg_graphprint: Add options for filtergraph printing 7: c0cf4adcd9 = 7: 03663768dd fftools: Enable filtergraph printing and update docs -: ---------- > 8: 61dc171fa1 fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) -- 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v5 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent @ 2025-03-08 17:55 ` softworkz 2025-03-08 19:08 ` Stefano Sabatini 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 2/8] fftools/ffprobe: Change to use textformat api softworkz ` (7 subsequent siblings) 8 siblings, 1 reply; 73+ messages in thread From: softworkz @ 2025-03-08 17:55 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz From: softworkz <softworkz@hotmail.com> Signed-off-by: softworkz <softworkz@hotmail.com> --- fftools/textformat/avtextformat.c | 672 +++++++++++++++++++++++++++++ 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, 2411 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..6c09f9d2cd --- /dev/null +++ b/fftools/textformat/avtextformat.c @@ -0,0 +1,672 @@ +/* + * 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 *textcontext_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 *textcontext_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 = textcontext_get_formatter_name, + .option = textcontext_options, + .version = LIBAVUTIL_VERSION_INT, + .child_next = textcontext_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 EINVAL; + + 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); + ret = AVERROR(EINVAL); + 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 *writercontext_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 = writercontext_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 EINVAL; + + 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..4689499246 --- /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_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); + +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..a62f2c8906 --- /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..825b67bc6e --- /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..7369992f01 --- /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..6971593c77 --- /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..1f4216069f --- /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..de27a36e7e --- /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..57171c4cb3 --- /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..d335d35a56 --- /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..f8b38414a6 --- /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..23de6f671f --- /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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v5 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz @ 2025-03-08 19:08 ` Stefano Sabatini 2025-03-08 19:49 ` Soft Works 0 siblings, 1 reply; 73+ messages in thread From: Stefano Sabatini @ 2025-03-08 19:08 UTC (permalink / raw) To: FFmpeg development discussions and patches; +Cc: softworkz On date Saturday 2025-03-08 17:55:28 +0000, softworkz wrote: > From: softworkz <softworkz@hotmail.com> > > Signed-off-by: softworkz <softworkz@hotmail.com> > --- > fftools/textformat/avtextformat.c | 672 +++++++++++++++++++++++++++++ > 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, 2411 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 Looks good to me. As I said, there are a few API tweaks we might want to apply to improve overall consistency and usability, but it is not blocking from my side given this is internal API. _______________________________________________ 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v5 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-08 19:08 ` Stefano Sabatini @ 2025-03-08 19:49 ` Soft Works 0 siblings, 0 replies; 73+ messages in thread From: Soft Works @ 2025-03-08 19:49 UTC (permalink / raw) To: Stefano Sabatini, FFmpeg development discussions and patches > -----Original Message----- > From: Stefano Sabatini <stefasab@gmail.com> > Sent: Samstag, 8. März 2025 20:09 > To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org> > Cc: softworkz <softworkz@hotmail.com> > Subject: Re: [FFmpeg-devel] [PATCH v5 1/8] fftools/textformat: Extract > and generalize textformat api from ffprobe.c > > On date Saturday 2025-03-08 17:55:28 +0000, softworkz wrote: > > From: softworkz <softworkz@hotmail.com> > > > > Signed-off-by: softworkz <softworkz@hotmail.com> > > --- > > fftools/textformat/avtextformat.c | 672 > +++++++++++++++++++++++++++++ > > 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, 2411 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 > > Looks good to me. > > As I said, there are a few API tweaks we might want to apply to > improve overall consistency and usability, Yea, probably you mean things like the optional field logic where you need to enable it initially for all sections recursively to even see any output. I left it as is for the moment to keep it more focused, even though it's always tempting to get everything right at once, 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v5 2/8] fftools/ffprobe: Change to use textformat api 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz @ 2025-03-08 17:55 ` softworkz 2025-03-08 19:23 ` Stefano Sabatini 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz ` (6 subsequent siblings) 8 siblings, 1 reply; 73+ messages in thread From: softworkz @ 2025-03-08 17:55 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v5 2/8] fftools/ffprobe: Change to use textformat api 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 2/8] fftools/ffprobe: Change to use textformat api softworkz @ 2025-03-08 19:23 ` Stefano Sabatini 0 siblings, 0 replies; 73+ messages in thread From: Stefano Sabatini @ 2025-03-08 19:23 UTC (permalink / raw) To: FFmpeg development discussions and patches; +Cc: softworkz On date Saturday 2025-03-08 17:55:29 +0000, softworkz wrote: > 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(-) Should still be good, assuming tests pass after the change. Thanks. _______________________________________________ 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v5 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 2/8] fftools/ffprobe: Change to use textformat api softworkz @ 2025-03-08 17:55 ` softworkz 2025-03-08 19:24 ` Stefano Sabatini 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 4/8] fftools/ffmpeg_filter: Move some declaration to new header file softworkz ` (5 subsequent siblings) 8 siblings, 1 reply; 73+ messages in thread From: softworkz @ 2025-03-08 17:55 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz 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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v5 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz @ 2025-03-08 19:24 ` Stefano Sabatini 0 siblings, 0 replies; 73+ messages in thread From: Stefano Sabatini @ 2025-03-08 19:24 UTC (permalink / raw) To: FFmpeg development discussions and patches; +Cc: softworkz On date Saturday 2025-03-08 17:55:30 +0000, softworkz wrote: > 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(-) Should be good. _______________________________________________ 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v5 4/8] fftools/ffmpeg_filter: Move some declaration to new header file 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (2 preceding siblings ...) 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz @ 2025-03-08 17:55 ` softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 5/8] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz ` (4 subsequent siblings) 8 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 17:55 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..94296c5eac --- /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 inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg) +{ + return (FilterGraphPriv*)fg; +} + +static inline 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 inline 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 inline 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v5 5/8] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (3 preceding siblings ...) 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 4/8] fftools/ffmpeg_filter: Move some declaration to new header file softworkz @ 2025-03-08 17:55 ` softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz ` (3 subsequent siblings) 8 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 17:55 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz 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 5a64836e25..354716399d 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-03-01 - xxxxxxxxxx - lavu 59.58.100 - pixfmt.h Add AV_PIX_FMT_GRAY32BE and AV_PIX_FMT_GRAY32LE. 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v5 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (4 preceding siblings ...) 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 5/8] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz @ 2025-03-08 17:55 ` softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 7/8] fftools: Enable filtergraph printing and update docs softworkz ` (2 subsequent siblings) 8 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 17:55 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 Signed-off-by: softworkz <softworkz@hotmail.com> --- fftools/Makefile | 11 + fftools/ffmpeg.h | 3 + fftools/ffmpeg_graphprint.c | 518 ++++++++++++++++++++++++++++++++++++ fftools/ffmpeg_graphprint.h | 35 +++ 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..dd6862679e --- /dev/null +++ b/fftools/ffmpeg_graphprint.c @@ -0,0 +1,518 @@ +/* + * 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" + +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 }, }, +}; + +/* 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) +{ + AVBufferRef *hw_frames_ctx; + 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; + } + + 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..e95a0773ba --- /dev/null +++ b/fftools/ffmpeg_graphprint.h @@ -0,0 +1,35 @@ +/* + * 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" + +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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v5 7/8] fftools: Enable filtergraph printing and update docs 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (5 preceding siblings ...) 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz @ 2025-03-08 17:55 ` softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 8 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 17:55 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 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v5 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (6 preceding siblings ...) 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 7/8] fftools: Enable filtergraph printing and update docs softworkz @ 2025-03-08 17:55 ` softworkz 2025-03-08 19:30 ` Stefano Sabatini 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 8 siblings, 1 reply; 73+ messages in thread From: softworkz @ 2025-03-08 17:55 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz From: softworkz <softworkz@hotmail.com> Signed-off-by: softworkz <softworkz@hotmail.com> --- fftools/ffprobe.c | 534 +++++++++++++++++++++++----------------------- 1 file changed, 267 insertions(+), 267 deletions(-) diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c index 4a90bc4824..406bef4d2f 100644 --- a/fftools/ffprobe.c +++ b/fftools/ffprobe.c @@ -407,7 +407,7 @@ static void log_callback(void *ptr, int level, const char *fmt, va_list vl) #define print_fmt(k, f, ...) do { \ av_bprint_clear(&pbuf); \ av_bprintf(&pbuf, f, __VA_ARGS__); \ - avtext_print_string(w, k, pbuf.str, 0); \ + avtext_print_string(tfc, k, pbuf.str, 0); \ } while (0) #define print_list_fmt(k, f, n, m, ...) do { \ @@ -419,19 +419,19 @@ static void log_callback(void *ptr, int level, const char *fmt, va_list vl) av_bprintf(&pbuf, f, __VA_ARGS__); \ } \ } \ - avtext_print_string(w, k, pbuf.str, 0); \ + avtext_print_string(tfc, k, pbuf.str, 0); \ } while (0) -#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 print_int(k, v) avtext_print_integer(tfc, k, v) +#define print_q(k, v, s) avtext_print_rational(tfc, k, v, s) +#define print_str(k, v) avtext_print_string(tfc, k, v, 0) +#define print_str_opt(k, v) avtext_print_string(tfc, k, v, AV_TEXTFORMAT_PRINT_STRING_OPTIONAL) +#define print_str_validate(k, v) avtext_print_string(tfc, k, v, AV_TEXTFORMAT_PRINT_STRING_VALIDATE) +#define print_time(k, v, tb) avtext_print_time(tfc, k, v, tb, 0) +#define print_ts(k, v) avtext_print_ts(tfc, k, v, 0) +#define print_duration_time(k, v, tb) avtext_print_time(tfc, k, v, tb, 1) +#define print_duration_ts(k, v) avtext_print_ts(tfc, k, v, 1) +#define print_val(k, v, u) avtext_print_unit_int(tfc, k, v, u) #define REALLOCZ_ARRAY_STREAM(ptr, cur_n, new_n) \ { \ @@ -441,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(AVTextFormatContext *w, AVDictionary *tags, int section_id) +static inline int show_tags(AVTextFormatContext *tfc, AVDictionary *tags, int section_id) { const AVDictionaryEntry *tag = NULL; int ret = 0; if (!tags) return 0; - avtext_print_section_header(w, NULL, section_id); + avtext_print_section_header(tfc, NULL, section_id); while ((tag = av_dict_iterate(tags, tag))) { if ((ret = print_str_validate(tag->key, tag->value)) < 0) break; } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *dovi) +static void print_dovi_metadata(AVTextFormatContext *tfc, const AVDOVIMetadata *dovi) { if (!dovi) return; @@ -514,15 +514,15 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do print_int("num_x_partitions", mapping->num_x_partitions); print_int("num_y_partitions", mapping->num_y_partitions); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); for (int c = 0; c < 3; c++) { const AVDOVIReshapingCurve *curve = &mapping->curves[c]; - avtext_print_section_header(w, "Reshaping curve", SECTION_ID_FRAME_SIDE_DATA_COMPONENT); + avtext_print_section_header(tfc, "Reshaping curve", SECTION_ID_FRAME_SIDE_DATA_COMPONENT); print_list_fmt("pivots", "%"PRIu16, curve->num_pivots, 1, curve->pivots[idx]); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); for (int i = 0; i < curve->num_pivots - 1; i++) { AVBPrint piece_buf; @@ -540,7 +540,7 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do } av_bprintf(&piece_buf, " mapping"); - avtext_print_section_header(w, piece_buf.str, SECTION_ID_FRAME_SIDE_DATA_PIECE); + avtext_print_section_header(tfc, 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: @@ -564,11 +564,11 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do } // SECTION_ID_FRAME_SIDE_DATA_PIECE - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); if (mapping->nlq_method_idc != AV_DOVI_NLQ_NONE) { const AVDOVINLQParams *nlq = &mapping->nlq[c]; @@ -584,11 +584,11 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do } // SECTION_ID_FRAME_SIDE_DATA_COMPONENT - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); // color metadata print_int("dm_metadata_id", color->dm_metadata_id); @@ -621,7 +621,7 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do } } -static void print_dynamic_hdr10_plus(AVTextFormatContext *w, const AVDynamicHDRPlus *metadata) +static void print_dynamic_hdr10_plus(AVTextFormatContext *tfc, const AVDynamicHDRPlus *metadata) { if (!metadata) return; @@ -720,7 +720,7 @@ static void print_dynamic_hdr10_plus(AVTextFormatContext *w, const AVDynamicHDRP } } -static void print_dynamic_hdr_vivid(AVTextFormatContext *w, const AVDynamicHDRVivid *metadata) +static void print_dynamic_hdr_vivid(AVTextFormatContext *tfc, const AVDynamicHDRVivid *metadata) { if (!metadata) return; @@ -790,7 +790,7 @@ static void print_dynamic_hdr_vivid(AVTextFormatContext *w, const AVDynamicHDRVi } } -static void print_ambient_viewing_environment(AVTextFormatContext *w, +static void print_ambient_viewing_environment(AVTextFormatContext *tfc, const AVAmbientViewingEnvironment *env) { if (!env) @@ -801,7 +801,7 @@ static void print_ambient_viewing_environment(AVTextFormatContext *w, print_q("ambient_light_y", env->ambient_light_y, '/'); } -static void print_film_grain_params(AVTextFormatContext *w, +static void print_film_grain_params(AVTextFormatContext *tfc, const AVFilmGrainParams *fgp) { const char *color_range, *color_primaries, *color_trc, *color_space; @@ -847,10 +847,10 @@ static void print_film_grain_params(AVTextFormatContext *w, print_int("overlap_flag", aom->overlap_flag); print_int("limit_output_range", aom->limit_output_range); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); if (aom->num_y_points) { - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT); + avtext_print_section_header(tfc, 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]); @@ -858,14 +858,14 @@ static void print_film_grain_params(AVTextFormatContext *w, print_list_fmt("ar_coeffs_y", "%"PRId8, num_ar_coeffs_y, 1, aom->ar_coeffs_y[idx]); // SECTION_ID_FRAME_SIDE_DATA_COMPONENT - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } for (int uv = 0; uv < 2; uv++) { if (!aom->num_uv_points[uv] && !aom->chroma_scaling_from_luma) continue; - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT); + avtext_print_section_header(tfc, 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]); @@ -876,11 +876,11 @@ static void print_film_grain_params(AVTextFormatContext *w, print_int("uv_offset", aom->uv_offset[uv]); // SECTION_ID_FRAME_SIDE_DATA_COMPONENT - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); break; } case AV_FILM_GRAIN_PARAMS_H274: { @@ -889,36 +889,36 @@ static void print_film_grain_params(AVTextFormatContext *w, print_int("blending_mode_id", h274->blending_mode_id); print_int("log2_scale_factor", h274->log2_scale_factor); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); for (int c = 0; c < 3; c++) { if (!h274->component_model_present[c]) continue; - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT); + avtext_print_section_header(tfc, 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); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); for (int i = 0; i < h274->num_intensity_intervals[c]; i++) { - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE); + avtext_print_section_header(tfc, 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 - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); // SECTION_ID_FRAME_SIDE_DATA_COMPONENT - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); break; } } @@ -926,20 +926,20 @@ static void print_film_grain_params(AVTextFormatContext *w, av_bprint_finalize(&pbuf, NULL); } -static void print_pkt_side_data(AVTextFormatContext *w, +static void print_pkt_side_data(AVTextFormatContext *tfc, AVCodecParameters *par, const AVPacketSideData *sd, SectionID id_data) { const char *name = av_packet_side_data_name(sd->type); - avtext_print_section_header(w, sd, id_data); + avtext_print_section_header(tfc, 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); if (isnan(rotation)) rotation = 0; - avtext_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); + avtext_print_integers(tfc, "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; @@ -998,10 +998,10 @@ static void print_pkt_side_data(AVTextFormatContext *w, print_int("max_average", metadata->MaxFALL); } else if (sd->type == AV_PKT_DATA_AMBIENT_VIEWING_ENVIRONMENT) { print_ambient_viewing_environment( - w, (const AVAmbientViewingEnvironment *)sd->data); + tfc, (const AVAmbientViewingEnvironment *)sd->data); } else if (sd->type == AV_PKT_DATA_DYNAMIC_HDR10_PLUS) { AVDynamicHDRPlus *metadata = (AVDynamicHDRPlus *)sd->data; - print_dynamic_hdr10_plus(w, metadata); + print_dynamic_hdr10_plus(tfc, metadata); } else if (sd->type == AV_PKT_DATA_DOVI_CONF) { AVDOVIDecoderConfigurationRecord *dovi = (AVDOVIDecoderConfigurationRecord *)sd->data; const char *comp = "unknown"; @@ -1036,8 +1036,8 @@ static void print_pkt_side_data(AVTextFormatContext *w, } else if (sd->type == AV_PKT_DATA_WEBVTT_IDENTIFIER || sd->type == AV_PKT_DATA_WEBVTT_SETTINGS) { if (do_show_data) - avtext_print_data(w, "data", sd->data, sd->size); - avtext_print_data_hash(w, "data_hash", sd->data, sd->size); + avtext_print_data(tfc, "data", sd->data, sd->size); + avtext_print_data_hash(tfc, "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)); @@ -1048,7 +1048,7 @@ static void print_pkt_side_data(AVTextFormatContext *w, } } -static void print_private_data(AVTextFormatContext *w, void *priv_data) +static void print_private_data(AVTextFormatContext *tfc, void *priv_data) { const AVOption *opt = NULL; while (opt = av_opt_next(priv_data, opt)) { @@ -1061,7 +1061,7 @@ static void print_private_data(AVTextFormatContext *w, void *priv_data) } } -static void print_color_range(AVTextFormatContext *w, enum AVColorRange color_range) +static void print_color_range(AVTextFormatContext *tfc, enum AVColorRange color_range) { const char *val = av_color_range_name(color_range); if (!val || color_range == AVCOL_RANGE_UNSPECIFIED) { @@ -1071,7 +1071,7 @@ static void print_color_range(AVTextFormatContext *w, enum AVColorRange color_ra } } -static void print_color_space(AVTextFormatContext *w, enum AVColorSpace color_space) +static void print_color_space(AVTextFormatContext *tfc, enum AVColorSpace color_space) { const char *val = av_color_space_name(color_space); if (!val || color_space == AVCOL_SPC_UNSPECIFIED) { @@ -1081,7 +1081,7 @@ static void print_color_space(AVTextFormatContext *w, enum AVColorSpace color_sp } } -static void print_primaries(AVTextFormatContext *w, enum AVColorPrimaries color_primaries) +static void print_primaries(AVTextFormatContext *tfc, enum AVColorPrimaries color_primaries) { const char *val = av_color_primaries_name(color_primaries); if (!val || color_primaries == AVCOL_PRI_UNSPECIFIED) { @@ -1091,7 +1091,7 @@ static void print_primaries(AVTextFormatContext *w, enum AVColorPrimaries color_ } } -static void print_color_trc(AVTextFormatContext *w, enum AVColorTransferCharacteristic color_trc) +static void print_color_trc(AVTextFormatContext *tfc, enum AVColorTransferCharacteristic color_trc) { const char *val = av_color_transfer_name(color_trc); if (!val || color_trc == AVCOL_TRC_UNSPECIFIED) { @@ -1101,7 +1101,7 @@ static void print_color_trc(AVTextFormatContext *w, enum AVColorTransferCharacte } } -static void print_chroma_location(AVTextFormatContext *w, enum AVChromaLocation chroma_location) +static void print_chroma_location(AVTextFormatContext *tfc, enum AVChromaLocation chroma_location) { const char *val = av_chroma_location_name(chroma_location); if (!val || chroma_location == AVCHROMA_LOC_UNSPECIFIED) { @@ -1127,7 +1127,7 @@ static void clear_log(int need_lock) ff_mutex_unlock(&log_mutex); } -static int show_log(AVTextFormatContext *w, int section_ids, int section_id, int log_level) +static int show_log(AVTextFormatContext *tfc, int section_ids, int section_id, int log_level) { int i; ff_mutex_lock(&log_mutex); @@ -1135,11 +1135,11 @@ static int show_log(AVTextFormatContext *w, int section_ids, int section_id, int ff_mutex_unlock(&log_mutex); return 0; } - avtext_print_section_header(w, NULL, section_ids); + avtext_print_section_header(tfc, NULL, section_ids); for (i=0; i<log_buffer_size; i++) { if (log_buffer[i].log_level <= log_level) { - avtext_print_section_header(w, NULL, section_id); + avtext_print_section_header(tfc, 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); @@ -1151,18 +1151,18 @@ static int show_log(AVTextFormatContext *w, int section_ids, int section_id, int print_str_opt("parent_category", "N/A"); } print_str("message", log_buffer[i].log_message); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } } clear_log(0); ff_mutex_unlock(&log_mutex); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return 0; } -static void show_packet(AVTextFormatContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx) +static void show_packet(AVTextFormatContext *tfc, InputFile *ifile, AVPacket *pkt, int packet_idx) { AVStream *st = ifile->streams[pkt->stream_index].st; AVBPrint pbuf; @@ -1170,7 +1170,7 @@ static void show_packet(AVTextFormatContext *w, InputFile *ifile, AVPacket *pkt, av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, SECTION_ID_PACKET); + avtext_print_section_header(tfc, NULL, SECTION_ID_PACKET); s = av_get_media_type_string(st->codecpar->codec_type); if (s) print_str ("codec_type", s); @@ -1189,8 +1189,8 @@ static void show_packet(AVTextFormatContext *w, InputFile *ifile, AVPacket *pkt, pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_', pkt->flags & AV_PKT_FLAG_CORRUPT ? 'C' : '_'); if (do_show_data) - avtext_print_data(w, "data", pkt->data, pkt->size); - avtext_print_data_hash(w, "data_hash", pkt->data, pkt->size); + avtext_print_data(tfc, "data", pkt->data, pkt->size); + avtext_print_data_hash(tfc, "data_hash", pkt->data, pkt->size); if (pkt->side_data_elems) { size_t size; @@ -1200,33 +1200,33 @@ static void show_packet(AVTextFormatContext *w, InputFile *ifile, AVPacket *pkt, if (side_metadata && size && do_show_packet_tags) { AVDictionary *dict = NULL; if (av_packet_unpack_dictionary(side_metadata, size, &dict) >= 0) - show_tags(w, dict, SECTION_ID_PACKET_TAGS); + show_tags(tfc, dict, SECTION_ID_PACKET_TAGS); av_dict_free(&dict); } - avtext_print_section_header(w, NULL, SECTION_ID_PACKET_SIDE_DATA_LIST); + avtext_print_section_header(tfc, 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], + print_pkt_side_data(tfc, st->codecpar, &pkt->side_data[i], SECTION_ID_PACKET_SIDE_DATA); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); fflush(stdout); } -static void show_subtitle(AVTextFormatContext *w, AVSubtitle *sub, AVStream *stream, +static void show_subtitle(AVTextFormatContext *tfc, AVSubtitle *sub, AVStream *stream, AVFormatContext *fmt_ctx) { AVBPrint pbuf; av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, SECTION_ID_SUBTITLE); + avtext_print_section_header(tfc, NULL, SECTION_ID_SUBTITLE); print_str ("media_type", "subtitle"); print_ts ("pts", sub->pts); @@ -1236,30 +1236,30 @@ static void show_subtitle(AVTextFormatContext *w, AVSubtitle *sub, AVStream *str print_int ("end_display_time", sub->end_display_time); print_int ("num_rects", sub->num_rects); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); fflush(stdout); } -static void print_frame_side_data(AVTextFormatContext *w, +static void print_frame_side_data(AVTextFormatContext *tfc, const AVFrame *frame, const AVStream *stream) { - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_LIST); + avtext_print_section_header(tfc, 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; - avtext_print_section_header(w, sd, SECTION_ID_FRAME_SIDE_DATA); + avtext_print_section_header(tfc, 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) { double rotation = av_display_rotation_get((int32_t *)sd->data); if (isnan(rotation)) rotation = 0; - avtext_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); + avtext_print_integers(tfc, "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); @@ -1270,15 +1270,15 @@ static void print_frame_side_data(AVTextFormatContext *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); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST); + avtext_print_section_header(tfc, 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); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE); print_str("value", tcbuf); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } else if (sd->type == AV_FRAME_DATA_MASTERING_DISPLAY_METADATA) { AVMasteringDisplayMetadata *metadata = (AVMasteringDisplayMetadata *)sd->data; @@ -1300,7 +1300,7 @@ static void print_frame_side_data(AVTextFormatContext *w, } } else if (sd->type == AV_FRAME_DATA_DYNAMIC_HDR_PLUS) { AVDynamicHDRPlus *metadata = (AVDynamicHDRPlus *)sd->data; - print_dynamic_hdr10_plus(w, metadata); + print_dynamic_hdr10_plus(tfc, metadata); } else if (sd->type == AV_FRAME_DATA_CONTENT_LIGHT_LEVEL) { AVContentLightMetadata *metadata = (AVContentLightMetadata *)sd->data; print_int("max_content", metadata->MaxCLL); @@ -1311,24 +1311,24 @@ static void print_frame_side_data(AVTextFormatContext *w, print_str(tag->key, tag->value); print_int("size", sd->size); } else if (sd->type == AV_FRAME_DATA_DOVI_METADATA) { - print_dovi_metadata(w, (const AVDOVIMetadata *)sd->data); + print_dovi_metadata(tfc, (const AVDOVIMetadata *)sd->data); } else if (sd->type == AV_FRAME_DATA_DYNAMIC_HDR_VIVID) { AVDynamicHDRVivid *metadata = (AVDynamicHDRVivid *)sd->data; - print_dynamic_hdr_vivid(w, metadata); + print_dynamic_hdr_vivid(tfc, metadata); } else if (sd->type == AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT) { - print_ambient_viewing_environment(w, (const AVAmbientViewingEnvironment *)sd->data); + print_ambient_viewing_environment(tfc, (const AVAmbientViewingEnvironment *)sd->data); } else if (sd->type == AV_FRAME_DATA_FILM_GRAIN_PARAMS) { AVFilmGrainParams *fgp = (AVFilmGrainParams *)sd->data; - print_film_grain_params(w, fgp); + print_film_grain_params(tfc, fgp); } else if (sd->type == AV_FRAME_DATA_VIEW_ID) { print_int("view_id", *(int*)sd->data); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } -static void show_frame(AVTextFormatContext *w, AVFrame *frame, AVStream *stream, +static void show_frame(AVTextFormatContext *tfc, AVFrame *frame, AVStream *stream, AVFormatContext *fmt_ctx) { FrameData *fd = frame->opaque_ref ? (FrameData*)frame->opaque_ref->data : NULL; @@ -1338,7 +1338,7 @@ static void show_frame(AVTextFormatContext *w, AVFrame *frame, AVStream *stream, av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME); s = av_get_media_type_string(stream->codecpar->codec_type); if (s) print_str ("media_type", s); @@ -1383,11 +1383,11 @@ static void show_frame(AVTextFormatContext *w, AVFrame *frame, AVStream *stream, print_int("lossless", !!(frame->flags & AV_FRAME_FLAG_LOSSLESS)); print_int("repeat_pict", frame->repeat_pict); - print_color_range(w, frame->color_range); - print_color_space(w, frame->colorspace); - print_primaries(w, frame->color_primaries); - print_color_trc(w, frame->color_trc); - print_chroma_location(w, frame->chroma_location); + print_color_range(tfc, frame->color_range); + print_color_space(tfc, frame->colorspace); + print_primaries(tfc, frame->color_primaries); + print_color_trc(tfc, frame->color_trc); + print_chroma_location(tfc, frame->chroma_location); break; case AVMEDIA_TYPE_AUDIO: @@ -1404,19 +1404,19 @@ static void show_frame(AVTextFormatContext *w, AVFrame *frame, AVStream *stream, break; } if (do_show_frame_tags) - show_tags(w, frame->metadata, SECTION_ID_FRAME_TAGS); + show_tags(tfc, frame->metadata, SECTION_ID_FRAME_TAGS); if (do_show_log) - show_log(w, SECTION_ID_FRAME_LOGS, SECTION_ID_FRAME_LOG, do_show_log); + show_log(tfc, SECTION_ID_FRAME_LOGS, SECTION_ID_FRAME_LOG, do_show_log); if (frame->nb_side_data) - print_frame_side_data(w, frame, stream); + print_frame_side_data(tfc, frame, stream); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); fflush(stdout); } -static av_always_inline int process_frame(AVTextFormatContext *w, +static av_always_inline int process_frame(AVTextFormatContext *tfc, InputFile *ifile, AVFrame *frame, const AVPacket *pkt, int *packet_new) @@ -1470,9 +1470,9 @@ static av_always_inline int process_frame(AVTextFormatContext *w, nb_streams_frames[pkt->stream_index]++; if (do_show_frames) if (is_sub) - show_subtitle(w, &sub, ifile->streams[pkt->stream_index].st, fmt_ctx); + show_subtitle(tfc, &sub, ifile->streams[pkt->stream_index].st, fmt_ctx); else - show_frame(w, frame, ifile->streams[pkt->stream_index].st, fmt_ctx); + show_frame(tfc, frame, ifile->streams[pkt->stream_index].st, fmt_ctx); if (!is_sub && do_analyze_frames) { for (int i = 0; i < frame->nb_side_data; i++) { @@ -1513,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(AVTextFormatContext *w, InputFile *ifile, +static int read_interval_packets(AVTextFormatContext *tfc, InputFile *ifile, const ReadInterval *interval, int64_t *cur_ts) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; @@ -1596,7 +1596,7 @@ static int read_interval_packets(AVTextFormatContext *w, InputFile *ifile, frame_count++; if (do_read_packets) { if (do_show_packets) - show_packet(w, ifile, pkt, i++); + show_packet(tfc, ifile, pkt, i++); nb_streams_packets[pkt->stream_index]++; } if (do_read_frames) { @@ -1612,7 +1612,7 @@ static int read_interval_packets(AVTextFormatContext *w, InputFile *ifile, fd->pkt_pos = pkt->pos; fd->pkt_size = pkt->size; - while (process_frame(w, ifile, frame, pkt, &packet_new) > 0); + while (process_frame(tfc, ifile, frame, pkt, &packet_new) > 0); } } av_packet_unref(pkt); @@ -1622,7 +1622,7 @@ static int read_interval_packets(AVTextFormatContext *w, InputFile *ifile, for (i = 0; i < ifile->nb_streams; i++) { pkt->stream_index = i; if (do_read_frames) { - while (process_frame(w, ifile, frame, pkt, &(int){1}) > 0); + while (process_frame(tfc, ifile, frame, pkt, &(int){1}) > 0); if (ifile->streams[i].dec_ctx) avcodec_flush_buffers(ifile->streams[i].dec_ctx); } @@ -1638,7 +1638,7 @@ end: return ret; } -static int read_packets(AVTextFormatContext *w, InputFile *ifile) +static int read_packets(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; @@ -1646,10 +1646,10 @@ static int read_packets(AVTextFormatContext *w, InputFile *ifile) if (read_intervals_nb == 0) { ReadInterval interval = (ReadInterval) { .has_start = 0, .has_end = 0 }; - ret = read_interval_packets(w, ifile, &interval, &cur_ts); + ret = read_interval_packets(tfc, ifile, &interval, &cur_ts); } else { for (i = 0; i < read_intervals_nb; i++) { - ret = read_interval_packets(w, ifile, &read_intervals[i], &cur_ts); + ret = read_interval_packets(tfc, ifile, &read_intervals[i], &cur_ts); if (ret < 0) break; } @@ -1658,22 +1658,22 @@ static int read_packets(AVTextFormatContext *w, InputFile *ifile) return ret; } -static void print_dispositions(AVTextFormatContext *w, uint32_t disposition, SectionID section_id) +static void print_dispositions(AVTextFormatContext *tfc, uint32_t disposition, SectionID section_id) { - avtext_print_section_header(w, NULL, section_id); + avtext_print_section_header(tfc, 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))); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } #define IN_PROGRAM 1 #define IN_STREAM_GROUP 2 -static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int container) +static int show_stream(AVTextFormatContext *tfc, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int container) { AVStream *stream = ist->st; AVCodecParameters *par; @@ -1705,7 +1705,7 @@ static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int str av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, section_header[container]); + avtext_print_section_header(tfc, NULL, section_header[container]); print_int("index", stream->index); @@ -1774,11 +1774,11 @@ static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int str else print_str_opt("pix_fmt", "unknown"); print_int("level", par->level); - print_color_range(w, par->color_range); - print_color_space(w, par->color_space); - print_color_trc(w, par->color_trc); - print_primaries(w, par->color_primaries); - print_chroma_location(w, par->chroma_location); + print_color_range(tfc, par->color_range); + print_color_space(tfc, par->color_space); + print_color_trc(tfc, par->color_trc); + print_primaries(tfc, par->color_primaries); + print_chroma_location(tfc, par->chroma_location); if (par->field_order == AV_FIELD_PROGRESSIVE) print_str("field_order", "progressive"); @@ -1830,9 +1830,9 @@ static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int str if (show_private_data) { if (dec_ctx && dec_ctx->codec->priv_class) - print_private_data(w, dec_ctx->priv_data); + print_private_data(tfc, dec_ctx->priv_data); if (fmt_ctx->iformat->priv_class) - print_private_data(w, fmt_ctx->priv_data); + print_private_data(tfc, fmt_ctx->priv_data); } if (fmt_ctx->iformat->flags & AVFMT_SHOW_IDS) print_fmt ("id", "0x%x", stream->id); @@ -1859,113 +1859,113 @@ static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int str 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) - avtext_print_data(w, "extradata", par->extradata, + avtext_print_data(tfc, "extradata", par->extradata, par->extradata_size); if (par->extradata_size > 0) { print_int("extradata_size", par->extradata_size); - avtext_print_data_hash(w, "extradata_hash", par->extradata, + avtext_print_data_hash(tfc, "extradata_hash", par->extradata, par->extradata_size); } /* Print disposition information */ if (do_show_stream_disposition) { av_assert0(container < FF_ARRAY_ELEMS(section_disposition)); - print_dispositions(w, stream->disposition, section_disposition[container]); + print_dispositions(tfc, stream->disposition, section_disposition[container]); } if (do_show_stream_tags) { av_assert0(container < FF_ARRAY_ELEMS(section_tags)); - ret = show_tags(w, stream->metadata, section_tags[container]); + ret = show_tags(tfc, stream->metadata, section_tags[container]); } if (stream->codecpar->nb_coded_side_data) { - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_SIDE_DATA_LIST); + avtext_print_section_header(tfc, 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], + print_pkt_side_data(tfc, stream->codecpar, &stream->codecpar->coded_side_data[i], SECTION_ID_STREAM_SIDE_DATA); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); fflush(stdout); return ret; } -static int show_streams(AVTextFormatContext *w, InputFile *ifile) +static int show_streams(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_STREAMS); + avtext_print_section_header(tfc, 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); + ret = show_stream(tfc, fmt_ctx, i, &ifile->streams[i], 0); if (ret < 0) break; } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_program(AVTextFormatContext *w, InputFile *ifile, AVProgram *program) +static int show_program(AVTextFormatContext *tfc, InputFile *ifile, AVProgram *program) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_PROGRAM); + avtext_print_section_header(tfc, 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); print_int("pmt_pid", program->pmt_pid); print_int("pcr_pid", program->pcr_pid); if (do_show_program_tags) - ret = show_tags(w, program->metadata, SECTION_ID_PROGRAM_TAGS); + ret = show_tags(tfc, program->metadata, SECTION_ID_PROGRAM_TAGS); if (ret < 0) goto end; - avtext_print_section_header(w, NULL, SECTION_ID_PROGRAM_STREAMS); + avtext_print_section_header(tfc, 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); + ret = show_stream(tfc, fmt_ctx, program->stream_index[i], &ifile->streams[program->stream_index[i]], IN_PROGRAM); if (ret < 0) break; } } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); end: - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_programs(AVTextFormatContext *w, InputFile *ifile) +static int show_programs(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_PROGRAMS); + avtext_print_section_header(tfc, NULL, SECTION_ID_PROGRAMS); for (i = 0; i < fmt_ctx->nb_programs; i++) { AVProgram *program = fmt_ctx->programs[i]; if (!program) continue; - ret = show_program(w, ifile, program); + ret = show_program(tfc, ifile, program); if (ret < 0) break; } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static void print_tile_grid_params(AVTextFormatContext *w, const AVStreamGroup *stg, +static void print_tile_grid_params(AVTextFormatContext *tfc, const AVStreamGroup *stg, const AVStreamGroupTileGrid *tile_grid) { - avtext_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT); + avtext_print_section_header(tfc, 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); @@ -1973,19 +1973,19 @@ static void print_tile_grid_params(AVTextFormatContext *w, const AVStreamGroup * print_int("vertical_offset", tile_grid->vertical_offset); print_int("width", tile_grid->width); print_int("height", tile_grid->height); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); for (int i = 0; i < tile_grid->nb_tiles; i++) { - avtext_print_section_header(w, "tile_offset", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); + avtext_print_section_header(tfc, "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); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); + avtext_print_section_footer(tfc); } -static void print_iamf_param_definition(AVTextFormatContext *w, const char *name, +static void print_iamf_param_definition(AVTextFormatContext *tfc, const char *name, const AVIAMFParamDefinition *param, SectionID section_id) { SectionID subsection_id, parameter_section_id; @@ -1993,7 +1993,7 @@ static void print_iamf_param_definition(AVTextFormatContext *w, const char *name av_assert0(subsection_id != -1); parameter_section_id = sections[subsection_id].children_ids[0]; av_assert0(parameter_section_id != -1); - avtext_print_section_header(w, "IAMF Param Definition", section_id); + avtext_print_section_header(tfc, "IAMF Param Definition", section_id); print_str("name", name); print_int("nb_subblocks", param->nb_subblocks); print_int("type", param->type); @@ -2002,56 +2002,56 @@ static void print_iamf_param_definition(AVTextFormatContext *w, const char *name print_int("duration", param->duration); print_int("constant_subblock_duration", param->constant_subblock_duration); if (param->nb_subblocks > 0) - avtext_print_section_header(w, NULL, subsection_id); + avtext_print_section_header(tfc, 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; - avtext_print_section_header(w, "IAMF Mix Gain Parameters", parameter_section_id); + avtext_print_section_header(tfc, "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, '/'); - avtext_print_section_footer(w); // parameter_section_id + avtext_print_section_footer(tfc); // parameter_section_id break; } case AV_IAMF_PARAMETER_DEFINITION_DEMIXING: { const AVIAMFDemixingInfo *demix = subblock; - avtext_print_section_header(w, "IAMF Demixing Info", parameter_section_id); + avtext_print_section_header(tfc, "IAMF Demixing Info", parameter_section_id); print_int("subblock_duration", demix->subblock_duration); print_int("dmixp_mode", demix->dmixp_mode); - avtext_print_section_footer(w); // parameter_section_id + avtext_print_section_footer(tfc); // parameter_section_id break; } case AV_IAMF_PARAMETER_DEFINITION_RECON_GAIN: { const AVIAMFReconGain *recon = subblock; - avtext_print_section_header(w, "IAMF Recon Gain", parameter_section_id); + avtext_print_section_header(tfc, "IAMF Recon Gain", parameter_section_id); print_int("subblock_duration", recon->subblock_duration); - avtext_print_section_footer(w); // parameter_section_id + avtext_print_section_footer(tfc); // parameter_section_id break; } } } if (param->nb_subblocks > 0) - avtext_print_section_footer(w); // subsection_id - avtext_print_section_footer(w); // section_id + avtext_print_section_footer(tfc); // subsection_id + avtext_print_section_footer(tfc); // section_id } -static void print_iamf_audio_element_params(AVTextFormatContext *w, const AVStreamGroup *stg, +static void print_iamf_audio_element_params(AVTextFormatContext *tfc, const AVStreamGroup *stg, const AVIAMFAudioElement *audio_element) { - avtext_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT); + avtext_print_section_header(tfc, 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); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); + avtext_print_section_header(tfc, 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]; - avtext_print_section_header(w, "IAMF Audio Layer", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); + avtext_print_section_header(tfc, "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) { @@ -2059,52 +2059,52 @@ static void print_iamf_audio_element_params(AVTextFormatContext *w, const AVStre 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); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT } if (audio_element->demixing_info) - print_iamf_param_definition(w, "demixing_info", audio_element->demixing_info, + print_iamf_param_definition(tfc, "demixing_info", audio_element->demixing_info, SECTION_ID_STREAM_GROUP_SUBCOMPONENT); if (audio_element->recon_gain_info) - print_iamf_param_definition(w, "recon_gain_info", audio_element->recon_gain_info, + print_iamf_param_definition(tfc, "recon_gain_info", audio_element->recon_gain_info, SECTION_ID_STREAM_GROUP_SUBCOMPONENT); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENT + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_COMPONENT } -static void print_iamf_submix_params(AVTextFormatContext *w, const AVIAMFSubmix *submix) +static void print_iamf_submix_params(AVTextFormatContext *tfc, const AVIAMFSubmix *submix) { - avtext_print_section_header(w, "IAMF Submix", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); + avtext_print_section_header(tfc, "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, '/'); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_PIECES); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUP_PIECES); for (int i = 0; i < submix->nb_elements; i++) { const AVIAMFSubmixElement *element = submix->elements[i]; - avtext_print_section_header(w, "IAMF Submix Element", SECTION_ID_STREAM_GROUP_PIECE); + avtext_print_section_header(tfc, "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); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBPIECES); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUP_SUBPIECES); if (element->annotations) { const AVDictionaryEntry *annotation = NULL; - avtext_print_section_header(w, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBPIECE); + avtext_print_section_header(tfc, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBPIECE); while (annotation = av_dict_iterate(element->annotations, annotation)) print_str(annotation->key, annotation->value); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBPIECE + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBPIECE } if (element->element_mix_config) - print_iamf_param_definition(w, "element_mix_config", element->element_mix_config, + print_iamf_param_definition(tfc, "element_mix_config", element->element_mix_config, SECTION_ID_STREAM_GROUP_SUBPIECE); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBPIECES - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECE + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBPIECES + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_PIECE } if (submix->output_mix_config) - print_iamf_param_definition(w, "output_mix_config", submix->output_mix_config, + print_iamf_param_definition(tfc, "output_mix_config", submix->output_mix_config, SECTION_ID_STREAM_GROUP_PIECE); for (int i = 0; i < submix->nb_layouts; i++) { const AVIAMFSubmixLayout *layout = submix->layouts[i]; char val_str[128]; - avtext_print_section_header(w, "IAMF Submix Layout", SECTION_ID_STREAM_GROUP_PIECE); + avtext_print_section_header(tfc, "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, '/'); @@ -2112,51 +2112,51 @@ static void print_iamf_submix_params(AVTextFormatContext *w, const AVIAMFSubmix 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, '/'); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECE + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_PIECE } - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECES - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_PIECES + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT } -static void print_iamf_mix_presentation_params(AVTextFormatContext *w, const AVStreamGroup *stg, +static void print_iamf_mix_presentation_params(AVTextFormatContext *tfc, const AVStreamGroup *stg, const AVIAMFMixPresentation *mix_presentation) { - avtext_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT); + avtext_print_section_header(tfc, stg, SECTION_ID_STREAM_GROUP_COMPONENT); print_int("nb_submixes", mix_presentation->nb_submixes); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); if (mix_presentation->annotations) { const AVDictionaryEntry *annotation = NULL; - avtext_print_section_header(w, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); + avtext_print_section_header(tfc, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); while (annotation = av_dict_iterate(mix_presentation->annotations, annotation)) print_str(annotation->key, annotation->value); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT } for (int i = 0; i < mix_presentation->nb_submixes; i++) - print_iamf_submix_params(w, mix_presentation->submixes[i]); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENT + print_iamf_submix_params(tfc, mix_presentation->submixes[i]); + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_COMPONENT } -static void print_stream_group_params(AVTextFormatContext *w, AVStreamGroup *stg) +static void print_stream_group_params(AVTextFormatContext *tfc, AVStreamGroup *stg) { - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_COMPONENTS); + avtext_print_section_header(tfc, 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); + print_tile_grid_params(tfc, 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); + print_iamf_audio_element_params(tfc, 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); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENTS + print_iamf_mix_presentation_params(tfc, stg, stg->params.iamf_mix_presentation); + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_COMPONENTS } -static int show_stream_group(AVTextFormatContext *w, InputFile *ifile, AVStreamGroup *stg) +static int show_stream_group(AVTextFormatContext *tfc, 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); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP); + avtext_print_section_header(tfc, 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"); @@ -2166,60 +2166,60 @@ static int show_stream_group(AVTextFormatContext *w, InputFile *ifile, AVStreamG else print_str_opt("type", "unknown"); if (do_show_stream_group_components) - print_stream_group_params(w, stg); + print_stream_group_params(tfc, stg); /* Print disposition information */ if (do_show_stream_group_disposition) - print_dispositions(w, stg->disposition, SECTION_ID_STREAM_GROUP_DISPOSITION); + print_dispositions(tfc, stg->disposition, SECTION_ID_STREAM_GROUP_DISPOSITION); if (do_show_stream_group_tags) - ret = show_tags(w, stg->metadata, SECTION_ID_STREAM_GROUP_TAGS); + ret = show_tags(tfc, stg->metadata, SECTION_ID_STREAM_GROUP_TAGS); if (ret < 0) goto end; - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_STREAMS); + avtext_print_section_header(tfc, 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); + ret = show_stream(tfc, fmt_ctx, stg->streams[i]->index, &ifile->streams[stg->streams[i]->index], IN_STREAM_GROUP); if (ret < 0) break; } } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); end: av_bprint_finalize(&pbuf, NULL); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_stream_groups(AVTextFormatContext *w, InputFile *ifile) +static int show_stream_groups(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUPS); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUPS); for (i = 0; i < fmt_ctx->nb_stream_groups; i++) { AVStreamGroup *stg = fmt_ctx->stream_groups[i]; - ret = show_stream_group(w, ifile, stg); + ret = show_stream_group(tfc, ifile, stg); if (ret < 0) break; } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_chapters(AVTextFormatContext *w, InputFile *ifile) +static int show_chapters(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_CHAPTERS); + avtext_print_section_header(tfc, NULL, SECTION_ID_CHAPTERS); for (i = 0; i < fmt_ctx->nb_chapters; i++) { AVChapter *chapter = fmt_ctx->chapters[i]; - avtext_print_section_header(w, NULL, SECTION_ID_CHAPTER); + avtext_print_section_header(tfc, NULL, SECTION_ID_CHAPTER); print_int("id", chapter->id); print_q ("time_base", chapter->time_base, '/'); print_int("start", chapter->start); @@ -2227,21 +2227,21 @@ static int show_chapters(AVTextFormatContext *w, InputFile *ifile) print_int("end", chapter->end); print_time("end_time", chapter->end, &chapter->time_base); if (do_show_chapter_tags) - ret = show_tags(w, chapter->metadata, SECTION_ID_CHAPTER_TAGS); - avtext_print_section_footer(w); + ret = show_tags(tfc, chapter->metadata, SECTION_ID_CHAPTER_TAGS); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_format(AVTextFormatContext *w, InputFile *ifile) +static int show_format(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int64_t size = fmt_ctx->pb ? avio_size(fmt_ctx->pb) : -1; int ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_FORMAT); + avtext_print_section_header(tfc, 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); @@ -2259,19 +2259,19 @@ static int show_format(AVTextFormatContext *w, InputFile *ifile) else print_str_opt("bit_rate", "N/A"); print_int("probe_score", fmt_ctx->probe_score); if (do_show_format_tags) - ret = show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); + ret = show_tags(tfc, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); fflush(stdout); return ret; } -static void show_error(AVTextFormatContext *w, int err) +static void show_error(AVTextFormatContext *tfc, int err) { - avtext_print_section_header(w, NULL, SECTION_ID_ERROR); + avtext_print_section_header(tfc, NULL, SECTION_ID_ERROR); print_int("code", err); print_str("string", av_err2str(err)); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } static int open_input_file(InputFile *ifile, const char *filename, @@ -2413,7 +2413,7 @@ static void close_input_file(InputFile *ifile) avformat_close_input(&ifile->fmt_ctx); } -static int probe_file(AVTextFormatContext *tctx, const char *filename, +static int probe_file(AVTextFormatContext *tfc, const char *filename, const char *print_filename) { InputFile ifile = { 0 }; @@ -2455,40 +2455,40 @@ static int probe_file(AVTextFormatContext *tctx, const char *filename, if (do_read_frames || do_read_packets) { if (do_show_frames && do_show_packets && - tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT) + tfc->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) - avtext_print_section_header(tctx, NULL, section_id); - ret = read_packets(tctx, &ifile); + avtext_print_section_header(tfc, NULL, section_id); + ret = read_packets(tfc, &ifile); if (do_show_frames || do_show_packets) - avtext_print_section_footer(tctx); + avtext_print_section_footer(tfc); CHECK_END; } if (do_show_programs) { - ret = show_programs(tctx, &ifile); + ret = show_programs(tfc, &ifile); CHECK_END; } if (do_show_stream_groups) { - ret = show_stream_groups(tctx, &ifile); + ret = show_stream_groups(tfc, &ifile); CHECK_END; } if (do_show_streams) { - ret = show_streams(tctx, &ifile); + ret = show_streams(tfc, &ifile); CHECK_END; } if (do_show_chapters) { - ret = show_chapters(tctx, &ifile); + ret = show_chapters(tfc, &ifile); CHECK_END; } if (do_show_format) { - ret = show_format(tctx, &ifile); + ret = show_format(tfc, &ifile); CHECK_END; } @@ -2511,18 +2511,18 @@ static void show_usage(void) av_log(NULL, AV_LOG_INFO, "\n"); } -static void ffprobe_show_program_version(AVTextFormatContext *w) +static void ffprobe_show_program_version(AVTextFormatContext *tfc) { AVBPrint pbuf; av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, SECTION_ID_PROGRAM_VERSION); + avtext_print_section_header(tfc, 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); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); } @@ -2531,20 +2531,20 @@ static void ffprobe_show_program_version(AVTextFormatContext *w) do { \ if (CONFIG_##LIBNAME) { \ unsigned int version = libname##_version(); \ - avtext_print_section_header(w, NULL, SECTION_ID_LIBRARY_VERSION); \ + avtext_print_section_header(tfc, 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); \ - avtext_print_section_footer(w); \ + avtext_print_section_footer(tfc); \ } \ } while (0) -static void ffprobe_show_library_versions(AVTextFormatContext *w) +static void ffprobe_show_library_versions(AVTextFormatContext *tfc) { - avtext_print_section_header(w, NULL, SECTION_ID_LIBRARY_VERSIONS); + avtext_print_section_header(tfc, NULL, SECTION_ID_LIBRARY_VERSIONS); SHOW_LIB_VERSION(avutil, AVUTIL); SHOW_LIB_VERSION(avcodec, AVCODEC); SHOW_LIB_VERSION(avformat, AVFORMAT); @@ -2553,7 +2553,7 @@ static void ffprobe_show_library_versions(AVTextFormatContext *w) SHOW_LIB_VERSION(swscale, SWSCALE); SHOW_LIB_VERSION(swresample, SWRESAMPLE); SHOW_LIB_VERSION(postproc, POSTPROC); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } #define PRINT_PIX_FMT_FLAG(flagname, name) \ @@ -2561,14 +2561,14 @@ static void ffprobe_show_library_versions(AVTextFormatContext *w) print_int(name, !!(pixdesc->flags & AV_PIX_FMT_FLAG_##flagname)); \ } while (0) -static void ffprobe_show_pixel_formats(AVTextFormatContext *w) +static void ffprobe_show_pixel_formats(AVTextFormatContext *tfc) { const AVPixFmtDescriptor *pixdesc = NULL; int i, n; - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMATS); + avtext_print_section_header(tfc, NULL, SECTION_ID_PIXEL_FORMATS); while (pixdesc = av_pix_fmt_desc_next(pixdesc)) { - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT); + avtext_print_section_header(tfc, 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)) { @@ -2582,7 +2582,7 @@ static void ffprobe_show_pixel_formats(AVTextFormatContext *w) if (n) print_int ("bits_per_pixel", n); else print_str_opt("bits_per_pixel", "N/A"); if (do_show_pixel_format_flags) { - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_FLAGS); + avtext_print_section_header(tfc, 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"); @@ -2590,21 +2590,21 @@ static void ffprobe_show_pixel_formats(AVTextFormatContext *w) PRINT_PIX_FMT_FLAG(PLANAR, "planar"); PRINT_PIX_FMT_FLAG(RGB, "rgb"); PRINT_PIX_FMT_FLAG(ALPHA, "alpha"); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } if (do_show_pixel_format_components && (pixdesc->nb_components > 0)) { - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENTS); + avtext_print_section_header(tfc, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENTS); for (i = 0; i < pixdesc->nb_components; i++) { - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENT); + avtext_print_section_header(tfc, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENT); print_int("index", i + 1); print_int("bit_depth", pixdesc->comp[i].depth); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } static int opt_show_optional_fields(void *optctx, const char *opt, const char *arg) @@ -3052,7 +3052,7 @@ static inline int check_section_show_entries(int section_id) int main(int argc, char **argv) { const AVTextFormatter *f; - AVTextFormatContext *tctx; + AVTextFormatContext *tfc; AVTextWriterContext *wctx; char *buf; char *f_name = NULL, *f_args = NULL; @@ -3147,21 +3147,21 @@ int main(int argc, char **argv) if (ret < 0) goto end; - if ((ret = avtext_context_open(&tctx, f, wctx, f_args, + if ((ret = avtext_context_open(&tfc, 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; + tfc->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES; - avtext_print_section_header(tctx, NULL, SECTION_ID_ROOT); + avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT); if (do_show_program_version) - ffprobe_show_program_version(tctx); + ffprobe_show_program_version(tfc); if (do_show_library_versions) - ffprobe_show_library_versions(tctx); + ffprobe_show_library_versions(tfc); if (do_show_pixel_formats) - ffprobe_show_pixel_formats(tctx); + ffprobe_show_pixel_formats(tfc); 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) || @@ -3171,20 +3171,20 @@ 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(tctx, input_filename, print_input_filename); + ret = probe_file(tfc, input_filename, print_input_filename); if (ret < 0 && do_show_error) - show_error(tctx, ret); + show_error(tfc, ret); } input_ret = ret; - avtext_print_section_footer(tctx); + avtext_print_section_footer(tfc); 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); + ret = avtext_context_close(&tfc); if (ret < 0) av_log(NULL, AV_LOG_ERROR, "Writing output failed (closing formatter): %s\n", av_err2str(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] 73+ messages in thread
* Re: [FFmpeg-devel] [PATCH v5 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) softworkz @ 2025-03-08 19:30 ` Stefano Sabatini 0 siblings, 0 replies; 73+ messages in thread From: Stefano Sabatini @ 2025-03-08 19:30 UTC (permalink / raw) To: FFmpeg development discussions and patches; +Cc: softworkz Typo in the subject line (should be "tfc"). On date Saturday 2025-03-08 17:55:35 +0000, softworkz wrote: > From: softworkz <softworkz@hotmail.com> > > Signed-off-by: softworkz <softworkz@hotmail.com> > --- > fftools/ffprobe.c | 534 +++++++++++++++++++++++----------------------- > 1 file changed, 267 insertions(+), 267 deletions(-) > > static int opt_show_optional_fields(void *optctx, const char *opt, const char *arg) > @@ -3052,7 +3052,7 @@ static inline int check_section_show_entries(int section_id) > int main(int argc, char **argv) > { > const AVTextFormatter *f; > - AVTextFormatContext *tctx; > + AVTextFormatContext *tfc; I'd drop this rename since it introduces a greater asymmetry. > AVTextWriterContext *wctx; > char *buf; > char *f_name = NULL, *f_args = NULL; > @@ -3147,21 +3147,21 @@ int main(int argc, char **argv) > if (ret < 0) > goto end; > > - if ((ret = avtext_context_open(&tctx, f, wctx, f_args, > + if ((ret = avtext_context_open(&tfc, 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; > + tfc->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES; > > - avtext_print_section_header(tctx, NULL, SECTION_ID_ROOT); > + avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT); > > if (do_show_program_version) > - ffprobe_show_program_version(tctx); > + ffprobe_show_program_version(tfc); > if (do_show_library_versions) > - ffprobe_show_library_versions(tctx); > + ffprobe_show_library_versions(tfc); > if (do_show_pixel_formats) > - ffprobe_show_pixel_formats(tctx); > + ffprobe_show_pixel_formats(tfc); > > 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) || > @@ -3171,20 +3171,20 @@ 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(tctx, input_filename, print_input_filename); > + ret = probe_file(tfc, input_filename, print_input_filename); > if (ret < 0 && do_show_error) > - show_error(tctx, ret); > + show_error(tfc, ret); > } > > input_ret = ret; > > - avtext_print_section_footer(tctx); > + avtext_print_section_footer(tfc); > > 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); > + ret = avtext_context_close(&tfc); > if (ret < 0) > av_log(NULL, AV_LOG_ERROR, "Writing output failed (closing formatter): %s\n", av_err2str(ret)); Should be good otherwise, thanks. _______________________________________________ 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (7 preceding siblings ...) 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) softworkz @ 2025-03-08 20:16 ` ffmpegagent 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz ` (7 more replies) 8 siblings, 8 replies; 73+ messages in thread From: ffmpegagent @ 2025-03-08 20:16 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz 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 Update V4 * Fix "new warnings" were generated * Fix missing colon in commit message * Rebase due to changesin ApiChanges Update V5 * Fix copyright headers * Return proper error codes rather than -1 * Fix misnamed local functions * Fix typo * Rename 'w' in ffprobe.c to 'tfc' Update V6 * Fix commit message (8) * Revert renaming of context in main() softworkz (8): 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 fftools/ffprobe: Rename AVTextFormatContext variables (w => tfc) 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 | 518 +++++++ fftools/ffmpeg_graphprint.h | 35 + fftools/ffmpeg_opt.c | 12 + fftools/ffprobe.c | 2296 +++++----------------------- fftools/textformat/avtextformat.c | 672 ++++++++ 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, 3630 insertions(+), 2133 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: 0245e9382c748eba91645b65a377c4c9c4a44849 Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-52%2Fsoftworkz%2Fsubmit_print_graphs5-v6 Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-52/softworkz/submit_print_graphs5-v6 Pull-Request: https://github.com/ffstaging/FFmpeg/pull/52 Range-diff vs v5: 1: b26a45ea0e = 1: b26a45ea0e fftools/textformat: Extract and generalize textformat api from ffprobe.c 2: 2eb2b4ded5 = 2: 2eb2b4ded5 fftools/ffprobe: Change to use textformat api 3: 5ab9ac2a28 = 3: 5ab9ac2a28 fftools/ffprobe: Rename writer_print_section_* and WriterContext 4: 85735035e4 = 4: 85735035e4 fftools/ffmpeg_filter: Move some declaration to new header file 5: 811c5ab35c = 5: 811c5ab35c avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() 6: 8ae08a7006 = 6: 8ae08a7006 fftools/ffmpeg_graphprint: Add options for filtergraph printing 7: 03663768dd = 7: 03663768dd fftools: Enable filtergraph printing and update docs 8: 61dc171fa1 ! 8: 0caab91adc fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) @@ Metadata Author: softworkz <softworkz@hotmail.com> ## Commit message ## - fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) + fftools/ffprobe: Rename AVTextFormatContext variables (w => tfc) Signed-off-by: softworkz <softworkz@hotmail.com> @@ fftools/ffprobe.c: static void ffprobe_show_pixel_formats(AVTextFormatContext *w } static int opt_show_optional_fields(void *optctx, const char *opt, const char *arg) -@@ fftools/ffprobe.c: static inline int check_section_show_entries(int section_id) - int main(int argc, char **argv) - { - const AVTextFormatter *f; -- AVTextFormatContext *tctx; -+ AVTextFormatContext *tfc; - AVTextWriterContext *wctx; - char *buf; - char *f_name = NULL, *f_args = NULL; -@@ fftools/ffprobe.c: int main(int argc, char **argv) - if (ret < 0) - goto end; - -- if ((ret = avtext_context_open(&tctx, f, wctx, f_args, -+ if ((ret = avtext_context_open(&tfc, 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; -+ tfc->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES; - -- avtext_print_section_header(tctx, NULL, SECTION_ID_ROOT); -+ avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT); - - if (do_show_program_version) -- ffprobe_show_program_version(tctx); -+ ffprobe_show_program_version(tfc); - if (do_show_library_versions) -- ffprobe_show_library_versions(tctx); -+ ffprobe_show_library_versions(tfc); - if (do_show_pixel_formats) -- ffprobe_show_pixel_formats(tctx); -+ ffprobe_show_pixel_formats(tfc); - - 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) || -@@ fftools/ffprobe.c: 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(tctx, input_filename, print_input_filename); -+ ret = probe_file(tfc, input_filename, print_input_filename); - if (ret < 0 && do_show_error) -- show_error(tctx, ret); -+ show_error(tfc, ret); - } - - input_ret = ret; - -- avtext_print_section_footer(tctx); -+ avtext_print_section_footer(tfc); - - 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); -+ ret = avtext_context_close(&tfc); - if (ret < 0) - av_log(NULL, AV_LOG_ERROR, "Writing output failed (closing formatter): %s\n", av_err2str(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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v6 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent @ 2025-03-08 20:16 ` softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 2/8] fftools/ffprobe: Change to use textformat api softworkz ` (6 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 20:16 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz From: softworkz <softworkz@hotmail.com> Signed-off-by: softworkz <softworkz@hotmail.com> --- fftools/textformat/avtextformat.c | 672 +++++++++++++++++++++++++++++ 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, 2411 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..6c09f9d2cd --- /dev/null +++ b/fftools/textformat/avtextformat.c @@ -0,0 +1,672 @@ +/* + * 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 *textcontext_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 *textcontext_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 = textcontext_get_formatter_name, + .option = textcontext_options, + .version = LIBAVUTIL_VERSION_INT, + .child_next = textcontext_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 EINVAL; + + 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); + ret = AVERROR(EINVAL); + 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 *writercontext_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 = writercontext_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 EINVAL; + + 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..4689499246 --- /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_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); + +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..a62f2c8906 --- /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..825b67bc6e --- /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..7369992f01 --- /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..6971593c77 --- /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..1f4216069f --- /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..de27a36e7e --- /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..57171c4cb3 --- /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..d335d35a56 --- /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..f8b38414a6 --- /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..23de6f671f --- /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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v6 2/8] fftools/ffprobe: Change to use textformat api 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz @ 2025-03-08 20:16 ` softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz ` (5 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 20:16 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v6 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 2/8] fftools/ffprobe: Change to use textformat api softworkz @ 2025-03-08 20:16 ` softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 4/8] fftools/ffmpeg_filter: Move some declaration to new header file softworkz ` (4 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 20:16 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v6 4/8] fftools/ffmpeg_filter: Move some declaration to new header file 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (2 preceding siblings ...) 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz @ 2025-03-08 20:16 ` softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 5/8] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz ` (3 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 20:16 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..94296c5eac --- /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 inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg) +{ + return (FilterGraphPriv*)fg; +} + +static inline 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 inline 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 inline 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v6 5/8] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (3 preceding siblings ...) 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 4/8] fftools/ffmpeg_filter: Move some declaration to new header file softworkz @ 2025-03-08 20:16 ` softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz ` (2 subsequent siblings) 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 20:16 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz 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 5a64836e25..354716399d 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-03-01 - xxxxxxxxxx - lavu 59.58.100 - pixfmt.h Add AV_PIX_FMT_GRAY32BE and AV_PIX_FMT_GRAY32LE. 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v6 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (4 preceding siblings ...) 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 5/8] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz @ 2025-03-08 20:16 ` softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 7/8] fftools: Enable filtergraph printing and update docs softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tfc) softworkz 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 20:16 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 Signed-off-by: softworkz <softworkz@hotmail.com> --- fftools/Makefile | 11 + fftools/ffmpeg.h | 3 + fftools/ffmpeg_graphprint.c | 518 ++++++++++++++++++++++++++++++++++++ fftools/ffmpeg_graphprint.h | 35 +++ 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..dd6862679e --- /dev/null +++ b/fftools/ffmpeg_graphprint.c @@ -0,0 +1,518 @@ +/* + * 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" + +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 }, }, +}; + +/* 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) +{ + AVBufferRef *hw_frames_ctx; + 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; + } + + 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..e95a0773ba --- /dev/null +++ b/fftools/ffmpeg_graphprint.h @@ -0,0 +1,35 @@ +/* + * 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" + +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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v6 7/8] fftools: Enable filtergraph printing and update docs 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (5 preceding siblings ...) 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz @ 2025-03-08 20:16 ` softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tfc) softworkz 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 20:16 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 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] 73+ messages in thread
* [FFmpeg-devel] [PATCH v6 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tfc) 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent ` (6 preceding siblings ...) 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 7/8] fftools: Enable filtergraph printing and update docs softworkz @ 2025-03-08 20:16 ` softworkz 7 siblings, 0 replies; 73+ messages in thread From: softworkz @ 2025-03-08 20:16 UTC (permalink / raw) To: ffmpeg-devel; +Cc: softworkz From: softworkz <softworkz@hotmail.com> Signed-off-by: softworkz <softworkz@hotmail.com> --- fftools/ffprobe.c | 512 +++++++++++++++++++++++----------------------- 1 file changed, 256 insertions(+), 256 deletions(-) diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c index 4a90bc4824..9f869b41b3 100644 --- a/fftools/ffprobe.c +++ b/fftools/ffprobe.c @@ -407,7 +407,7 @@ static void log_callback(void *ptr, int level, const char *fmt, va_list vl) #define print_fmt(k, f, ...) do { \ av_bprint_clear(&pbuf); \ av_bprintf(&pbuf, f, __VA_ARGS__); \ - avtext_print_string(w, k, pbuf.str, 0); \ + avtext_print_string(tfc, k, pbuf.str, 0); \ } while (0) #define print_list_fmt(k, f, n, m, ...) do { \ @@ -419,19 +419,19 @@ static void log_callback(void *ptr, int level, const char *fmt, va_list vl) av_bprintf(&pbuf, f, __VA_ARGS__); \ } \ } \ - avtext_print_string(w, k, pbuf.str, 0); \ + avtext_print_string(tfc, k, pbuf.str, 0); \ } while (0) -#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 print_int(k, v) avtext_print_integer(tfc, k, v) +#define print_q(k, v, s) avtext_print_rational(tfc, k, v, s) +#define print_str(k, v) avtext_print_string(tfc, k, v, 0) +#define print_str_opt(k, v) avtext_print_string(tfc, k, v, AV_TEXTFORMAT_PRINT_STRING_OPTIONAL) +#define print_str_validate(k, v) avtext_print_string(tfc, k, v, AV_TEXTFORMAT_PRINT_STRING_VALIDATE) +#define print_time(k, v, tb) avtext_print_time(tfc, k, v, tb, 0) +#define print_ts(k, v) avtext_print_ts(tfc, k, v, 0) +#define print_duration_time(k, v, tb) avtext_print_time(tfc, k, v, tb, 1) +#define print_duration_ts(k, v) avtext_print_ts(tfc, k, v, 1) +#define print_val(k, v, u) avtext_print_unit_int(tfc, k, v, u) #define REALLOCZ_ARRAY_STREAM(ptr, cur_n, new_n) \ { \ @@ -441,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(AVTextFormatContext *w, AVDictionary *tags, int section_id) +static inline int show_tags(AVTextFormatContext *tfc, AVDictionary *tags, int section_id) { const AVDictionaryEntry *tag = NULL; int ret = 0; if (!tags) return 0; - avtext_print_section_header(w, NULL, section_id); + avtext_print_section_header(tfc, NULL, section_id); while ((tag = av_dict_iterate(tags, tag))) { if ((ret = print_str_validate(tag->key, tag->value)) < 0) break; } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *dovi) +static void print_dovi_metadata(AVTextFormatContext *tfc, const AVDOVIMetadata *dovi) { if (!dovi) return; @@ -514,15 +514,15 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do print_int("num_x_partitions", mapping->num_x_partitions); print_int("num_y_partitions", mapping->num_y_partitions); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); for (int c = 0; c < 3; c++) { const AVDOVIReshapingCurve *curve = &mapping->curves[c]; - avtext_print_section_header(w, "Reshaping curve", SECTION_ID_FRAME_SIDE_DATA_COMPONENT); + avtext_print_section_header(tfc, "Reshaping curve", SECTION_ID_FRAME_SIDE_DATA_COMPONENT); print_list_fmt("pivots", "%"PRIu16, curve->num_pivots, 1, curve->pivots[idx]); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); for (int i = 0; i < curve->num_pivots - 1; i++) { AVBPrint piece_buf; @@ -540,7 +540,7 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do } av_bprintf(&piece_buf, " mapping"); - avtext_print_section_header(w, piece_buf.str, SECTION_ID_FRAME_SIDE_DATA_PIECE); + avtext_print_section_header(tfc, 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: @@ -564,11 +564,11 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do } // SECTION_ID_FRAME_SIDE_DATA_PIECE - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); if (mapping->nlq_method_idc != AV_DOVI_NLQ_NONE) { const AVDOVINLQParams *nlq = &mapping->nlq[c]; @@ -584,11 +584,11 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do } // SECTION_ID_FRAME_SIDE_DATA_COMPONENT - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); // color metadata print_int("dm_metadata_id", color->dm_metadata_id); @@ -621,7 +621,7 @@ static void print_dovi_metadata(AVTextFormatContext *w, const AVDOVIMetadata *do } } -static void print_dynamic_hdr10_plus(AVTextFormatContext *w, const AVDynamicHDRPlus *metadata) +static void print_dynamic_hdr10_plus(AVTextFormatContext *tfc, const AVDynamicHDRPlus *metadata) { if (!metadata) return; @@ -720,7 +720,7 @@ static void print_dynamic_hdr10_plus(AVTextFormatContext *w, const AVDynamicHDRP } } -static void print_dynamic_hdr_vivid(AVTextFormatContext *w, const AVDynamicHDRVivid *metadata) +static void print_dynamic_hdr_vivid(AVTextFormatContext *tfc, const AVDynamicHDRVivid *metadata) { if (!metadata) return; @@ -790,7 +790,7 @@ static void print_dynamic_hdr_vivid(AVTextFormatContext *w, const AVDynamicHDRVi } } -static void print_ambient_viewing_environment(AVTextFormatContext *w, +static void print_ambient_viewing_environment(AVTextFormatContext *tfc, const AVAmbientViewingEnvironment *env) { if (!env) @@ -801,7 +801,7 @@ static void print_ambient_viewing_environment(AVTextFormatContext *w, print_q("ambient_light_y", env->ambient_light_y, '/'); } -static void print_film_grain_params(AVTextFormatContext *w, +static void print_film_grain_params(AVTextFormatContext *tfc, const AVFilmGrainParams *fgp) { const char *color_range, *color_primaries, *color_trc, *color_space; @@ -847,10 +847,10 @@ static void print_film_grain_params(AVTextFormatContext *w, print_int("overlap_flag", aom->overlap_flag); print_int("limit_output_range", aom->limit_output_range); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); if (aom->num_y_points) { - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT); + avtext_print_section_header(tfc, 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]); @@ -858,14 +858,14 @@ static void print_film_grain_params(AVTextFormatContext *w, print_list_fmt("ar_coeffs_y", "%"PRId8, num_ar_coeffs_y, 1, aom->ar_coeffs_y[idx]); // SECTION_ID_FRAME_SIDE_DATA_COMPONENT - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } for (int uv = 0; uv < 2; uv++) { if (!aom->num_uv_points[uv] && !aom->chroma_scaling_from_luma) continue; - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT); + avtext_print_section_header(tfc, 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]); @@ -876,11 +876,11 @@ static void print_film_grain_params(AVTextFormatContext *w, print_int("uv_offset", aom->uv_offset[uv]); // SECTION_ID_FRAME_SIDE_DATA_COMPONENT - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); break; } case AV_FILM_GRAIN_PARAMS_H274: { @@ -889,36 +889,36 @@ static void print_film_grain_params(AVTextFormatContext *w, print_int("blending_mode_id", h274->blending_mode_id); print_int("log2_scale_factor", h274->log2_scale_factor); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST); for (int c = 0; c < 3; c++) { if (!h274->component_model_present[c]) continue; - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_COMPONENT); + avtext_print_section_header(tfc, 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); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); for (int i = 0; i < h274->num_intensity_intervals[c]; i++) { - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_PIECE); + avtext_print_section_header(tfc, 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 - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); // SECTION_ID_FRAME_SIDE_DATA_COMPONENT - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } // SECTION_ID_FRAME_SIDE_DATA_COMPONENT_LIST - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); break; } } @@ -926,20 +926,20 @@ static void print_film_grain_params(AVTextFormatContext *w, av_bprint_finalize(&pbuf, NULL); } -static void print_pkt_side_data(AVTextFormatContext *w, +static void print_pkt_side_data(AVTextFormatContext *tfc, AVCodecParameters *par, const AVPacketSideData *sd, SectionID id_data) { const char *name = av_packet_side_data_name(sd->type); - avtext_print_section_header(w, sd, id_data); + avtext_print_section_header(tfc, 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); if (isnan(rotation)) rotation = 0; - avtext_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); + avtext_print_integers(tfc, "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; @@ -998,10 +998,10 @@ static void print_pkt_side_data(AVTextFormatContext *w, print_int("max_average", metadata->MaxFALL); } else if (sd->type == AV_PKT_DATA_AMBIENT_VIEWING_ENVIRONMENT) { print_ambient_viewing_environment( - w, (const AVAmbientViewingEnvironment *)sd->data); + tfc, (const AVAmbientViewingEnvironment *)sd->data); } else if (sd->type == AV_PKT_DATA_DYNAMIC_HDR10_PLUS) { AVDynamicHDRPlus *metadata = (AVDynamicHDRPlus *)sd->data; - print_dynamic_hdr10_plus(w, metadata); + print_dynamic_hdr10_plus(tfc, metadata); } else if (sd->type == AV_PKT_DATA_DOVI_CONF) { AVDOVIDecoderConfigurationRecord *dovi = (AVDOVIDecoderConfigurationRecord *)sd->data; const char *comp = "unknown"; @@ -1036,8 +1036,8 @@ static void print_pkt_side_data(AVTextFormatContext *w, } else if (sd->type == AV_PKT_DATA_WEBVTT_IDENTIFIER || sd->type == AV_PKT_DATA_WEBVTT_SETTINGS) { if (do_show_data) - avtext_print_data(w, "data", sd->data, sd->size); - avtext_print_data_hash(w, "data_hash", sd->data, sd->size); + avtext_print_data(tfc, "data", sd->data, sd->size); + avtext_print_data_hash(tfc, "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)); @@ -1048,7 +1048,7 @@ static void print_pkt_side_data(AVTextFormatContext *w, } } -static void print_private_data(AVTextFormatContext *w, void *priv_data) +static void print_private_data(AVTextFormatContext *tfc, void *priv_data) { const AVOption *opt = NULL; while (opt = av_opt_next(priv_data, opt)) { @@ -1061,7 +1061,7 @@ static void print_private_data(AVTextFormatContext *w, void *priv_data) } } -static void print_color_range(AVTextFormatContext *w, enum AVColorRange color_range) +static void print_color_range(AVTextFormatContext *tfc, enum AVColorRange color_range) { const char *val = av_color_range_name(color_range); if (!val || color_range == AVCOL_RANGE_UNSPECIFIED) { @@ -1071,7 +1071,7 @@ static void print_color_range(AVTextFormatContext *w, enum AVColorRange color_ra } } -static void print_color_space(AVTextFormatContext *w, enum AVColorSpace color_space) +static void print_color_space(AVTextFormatContext *tfc, enum AVColorSpace color_space) { const char *val = av_color_space_name(color_space); if (!val || color_space == AVCOL_SPC_UNSPECIFIED) { @@ -1081,7 +1081,7 @@ static void print_color_space(AVTextFormatContext *w, enum AVColorSpace color_sp } } -static void print_primaries(AVTextFormatContext *w, enum AVColorPrimaries color_primaries) +static void print_primaries(AVTextFormatContext *tfc, enum AVColorPrimaries color_primaries) { const char *val = av_color_primaries_name(color_primaries); if (!val || color_primaries == AVCOL_PRI_UNSPECIFIED) { @@ -1091,7 +1091,7 @@ static void print_primaries(AVTextFormatContext *w, enum AVColorPrimaries color_ } } -static void print_color_trc(AVTextFormatContext *w, enum AVColorTransferCharacteristic color_trc) +static void print_color_trc(AVTextFormatContext *tfc, enum AVColorTransferCharacteristic color_trc) { const char *val = av_color_transfer_name(color_trc); if (!val || color_trc == AVCOL_TRC_UNSPECIFIED) { @@ -1101,7 +1101,7 @@ static void print_color_trc(AVTextFormatContext *w, enum AVColorTransferCharacte } } -static void print_chroma_location(AVTextFormatContext *w, enum AVChromaLocation chroma_location) +static void print_chroma_location(AVTextFormatContext *tfc, enum AVChromaLocation chroma_location) { const char *val = av_chroma_location_name(chroma_location); if (!val || chroma_location == AVCHROMA_LOC_UNSPECIFIED) { @@ -1127,7 +1127,7 @@ static void clear_log(int need_lock) ff_mutex_unlock(&log_mutex); } -static int show_log(AVTextFormatContext *w, int section_ids, int section_id, int log_level) +static int show_log(AVTextFormatContext *tfc, int section_ids, int section_id, int log_level) { int i; ff_mutex_lock(&log_mutex); @@ -1135,11 +1135,11 @@ static int show_log(AVTextFormatContext *w, int section_ids, int section_id, int ff_mutex_unlock(&log_mutex); return 0; } - avtext_print_section_header(w, NULL, section_ids); + avtext_print_section_header(tfc, NULL, section_ids); for (i=0; i<log_buffer_size; i++) { if (log_buffer[i].log_level <= log_level) { - avtext_print_section_header(w, NULL, section_id); + avtext_print_section_header(tfc, 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); @@ -1151,18 +1151,18 @@ static int show_log(AVTextFormatContext *w, int section_ids, int section_id, int print_str_opt("parent_category", "N/A"); } print_str("message", log_buffer[i].log_message); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } } clear_log(0); ff_mutex_unlock(&log_mutex); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return 0; } -static void show_packet(AVTextFormatContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx) +static void show_packet(AVTextFormatContext *tfc, InputFile *ifile, AVPacket *pkt, int packet_idx) { AVStream *st = ifile->streams[pkt->stream_index].st; AVBPrint pbuf; @@ -1170,7 +1170,7 @@ static void show_packet(AVTextFormatContext *w, InputFile *ifile, AVPacket *pkt, av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, SECTION_ID_PACKET); + avtext_print_section_header(tfc, NULL, SECTION_ID_PACKET); s = av_get_media_type_string(st->codecpar->codec_type); if (s) print_str ("codec_type", s); @@ -1189,8 +1189,8 @@ static void show_packet(AVTextFormatContext *w, InputFile *ifile, AVPacket *pkt, pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_', pkt->flags & AV_PKT_FLAG_CORRUPT ? 'C' : '_'); if (do_show_data) - avtext_print_data(w, "data", pkt->data, pkt->size); - avtext_print_data_hash(w, "data_hash", pkt->data, pkt->size); + avtext_print_data(tfc, "data", pkt->data, pkt->size); + avtext_print_data_hash(tfc, "data_hash", pkt->data, pkt->size); if (pkt->side_data_elems) { size_t size; @@ -1200,33 +1200,33 @@ static void show_packet(AVTextFormatContext *w, InputFile *ifile, AVPacket *pkt, if (side_metadata && size && do_show_packet_tags) { AVDictionary *dict = NULL; if (av_packet_unpack_dictionary(side_metadata, size, &dict) >= 0) - show_tags(w, dict, SECTION_ID_PACKET_TAGS); + show_tags(tfc, dict, SECTION_ID_PACKET_TAGS); av_dict_free(&dict); } - avtext_print_section_header(w, NULL, SECTION_ID_PACKET_SIDE_DATA_LIST); + avtext_print_section_header(tfc, 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], + print_pkt_side_data(tfc, st->codecpar, &pkt->side_data[i], SECTION_ID_PACKET_SIDE_DATA); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); fflush(stdout); } -static void show_subtitle(AVTextFormatContext *w, AVSubtitle *sub, AVStream *stream, +static void show_subtitle(AVTextFormatContext *tfc, AVSubtitle *sub, AVStream *stream, AVFormatContext *fmt_ctx) { AVBPrint pbuf; av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, SECTION_ID_SUBTITLE); + avtext_print_section_header(tfc, NULL, SECTION_ID_SUBTITLE); print_str ("media_type", "subtitle"); print_ts ("pts", sub->pts); @@ -1236,30 +1236,30 @@ static void show_subtitle(AVTextFormatContext *w, AVSubtitle *sub, AVStream *str print_int ("end_display_time", sub->end_display_time); print_int ("num_rects", sub->num_rects); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); fflush(stdout); } -static void print_frame_side_data(AVTextFormatContext *w, +static void print_frame_side_data(AVTextFormatContext *tfc, const AVFrame *frame, const AVStream *stream) { - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_LIST); + avtext_print_section_header(tfc, 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; - avtext_print_section_header(w, sd, SECTION_ID_FRAME_SIDE_DATA); + avtext_print_section_header(tfc, 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) { double rotation = av_display_rotation_get((int32_t *)sd->data); if (isnan(rotation)) rotation = 0; - avtext_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); + avtext_print_integers(tfc, "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); @@ -1270,15 +1270,15 @@ static void print_frame_side_data(AVTextFormatContext *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); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST); + avtext_print_section_header(tfc, 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); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE); print_str("value", tcbuf); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } else if (sd->type == AV_FRAME_DATA_MASTERING_DISPLAY_METADATA) { AVMasteringDisplayMetadata *metadata = (AVMasteringDisplayMetadata *)sd->data; @@ -1300,7 +1300,7 @@ static void print_frame_side_data(AVTextFormatContext *w, } } else if (sd->type == AV_FRAME_DATA_DYNAMIC_HDR_PLUS) { AVDynamicHDRPlus *metadata = (AVDynamicHDRPlus *)sd->data; - print_dynamic_hdr10_plus(w, metadata); + print_dynamic_hdr10_plus(tfc, metadata); } else if (sd->type == AV_FRAME_DATA_CONTENT_LIGHT_LEVEL) { AVContentLightMetadata *metadata = (AVContentLightMetadata *)sd->data; print_int("max_content", metadata->MaxCLL); @@ -1311,24 +1311,24 @@ static void print_frame_side_data(AVTextFormatContext *w, print_str(tag->key, tag->value); print_int("size", sd->size); } else if (sd->type == AV_FRAME_DATA_DOVI_METADATA) { - print_dovi_metadata(w, (const AVDOVIMetadata *)sd->data); + print_dovi_metadata(tfc, (const AVDOVIMetadata *)sd->data); } else if (sd->type == AV_FRAME_DATA_DYNAMIC_HDR_VIVID) { AVDynamicHDRVivid *metadata = (AVDynamicHDRVivid *)sd->data; - print_dynamic_hdr_vivid(w, metadata); + print_dynamic_hdr_vivid(tfc, metadata); } else if (sd->type == AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT) { - print_ambient_viewing_environment(w, (const AVAmbientViewingEnvironment *)sd->data); + print_ambient_viewing_environment(tfc, (const AVAmbientViewingEnvironment *)sd->data); } else if (sd->type == AV_FRAME_DATA_FILM_GRAIN_PARAMS) { AVFilmGrainParams *fgp = (AVFilmGrainParams *)sd->data; - print_film_grain_params(w, fgp); + print_film_grain_params(tfc, fgp); } else if (sd->type == AV_FRAME_DATA_VIEW_ID) { print_int("view_id", *(int*)sd->data); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } -static void show_frame(AVTextFormatContext *w, AVFrame *frame, AVStream *stream, +static void show_frame(AVTextFormatContext *tfc, AVFrame *frame, AVStream *stream, AVFormatContext *fmt_ctx) { FrameData *fd = frame->opaque_ref ? (FrameData*)frame->opaque_ref->data : NULL; @@ -1338,7 +1338,7 @@ static void show_frame(AVTextFormatContext *w, AVFrame *frame, AVStream *stream, av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, SECTION_ID_FRAME); + avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME); s = av_get_media_type_string(stream->codecpar->codec_type); if (s) print_str ("media_type", s); @@ -1383,11 +1383,11 @@ static void show_frame(AVTextFormatContext *w, AVFrame *frame, AVStream *stream, print_int("lossless", !!(frame->flags & AV_FRAME_FLAG_LOSSLESS)); print_int("repeat_pict", frame->repeat_pict); - print_color_range(w, frame->color_range); - print_color_space(w, frame->colorspace); - print_primaries(w, frame->color_primaries); - print_color_trc(w, frame->color_trc); - print_chroma_location(w, frame->chroma_location); + print_color_range(tfc, frame->color_range); + print_color_space(tfc, frame->colorspace); + print_primaries(tfc, frame->color_primaries); + print_color_trc(tfc, frame->color_trc); + print_chroma_location(tfc, frame->chroma_location); break; case AVMEDIA_TYPE_AUDIO: @@ -1404,19 +1404,19 @@ static void show_frame(AVTextFormatContext *w, AVFrame *frame, AVStream *stream, break; } if (do_show_frame_tags) - show_tags(w, frame->metadata, SECTION_ID_FRAME_TAGS); + show_tags(tfc, frame->metadata, SECTION_ID_FRAME_TAGS); if (do_show_log) - show_log(w, SECTION_ID_FRAME_LOGS, SECTION_ID_FRAME_LOG, do_show_log); + show_log(tfc, SECTION_ID_FRAME_LOGS, SECTION_ID_FRAME_LOG, do_show_log); if (frame->nb_side_data) - print_frame_side_data(w, frame, stream); + print_frame_side_data(tfc, frame, stream); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); fflush(stdout); } -static av_always_inline int process_frame(AVTextFormatContext *w, +static av_always_inline int process_frame(AVTextFormatContext *tfc, InputFile *ifile, AVFrame *frame, const AVPacket *pkt, int *packet_new) @@ -1470,9 +1470,9 @@ static av_always_inline int process_frame(AVTextFormatContext *w, nb_streams_frames[pkt->stream_index]++; if (do_show_frames) if (is_sub) - show_subtitle(w, &sub, ifile->streams[pkt->stream_index].st, fmt_ctx); + show_subtitle(tfc, &sub, ifile->streams[pkt->stream_index].st, fmt_ctx); else - show_frame(w, frame, ifile->streams[pkt->stream_index].st, fmt_ctx); + show_frame(tfc, frame, ifile->streams[pkt->stream_index].st, fmt_ctx); if (!is_sub && do_analyze_frames) { for (int i = 0; i < frame->nb_side_data; i++) { @@ -1513,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(AVTextFormatContext *w, InputFile *ifile, +static int read_interval_packets(AVTextFormatContext *tfc, InputFile *ifile, const ReadInterval *interval, int64_t *cur_ts) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; @@ -1596,7 +1596,7 @@ static int read_interval_packets(AVTextFormatContext *w, InputFile *ifile, frame_count++; if (do_read_packets) { if (do_show_packets) - show_packet(w, ifile, pkt, i++); + show_packet(tfc, ifile, pkt, i++); nb_streams_packets[pkt->stream_index]++; } if (do_read_frames) { @@ -1612,7 +1612,7 @@ static int read_interval_packets(AVTextFormatContext *w, InputFile *ifile, fd->pkt_pos = pkt->pos; fd->pkt_size = pkt->size; - while (process_frame(w, ifile, frame, pkt, &packet_new) > 0); + while (process_frame(tfc, ifile, frame, pkt, &packet_new) > 0); } } av_packet_unref(pkt); @@ -1622,7 +1622,7 @@ static int read_interval_packets(AVTextFormatContext *w, InputFile *ifile, for (i = 0; i < ifile->nb_streams; i++) { pkt->stream_index = i; if (do_read_frames) { - while (process_frame(w, ifile, frame, pkt, &(int){1}) > 0); + while (process_frame(tfc, ifile, frame, pkt, &(int){1}) > 0); if (ifile->streams[i].dec_ctx) avcodec_flush_buffers(ifile->streams[i].dec_ctx); } @@ -1638,7 +1638,7 @@ end: return ret; } -static int read_packets(AVTextFormatContext *w, InputFile *ifile) +static int read_packets(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; @@ -1646,10 +1646,10 @@ static int read_packets(AVTextFormatContext *w, InputFile *ifile) if (read_intervals_nb == 0) { ReadInterval interval = (ReadInterval) { .has_start = 0, .has_end = 0 }; - ret = read_interval_packets(w, ifile, &interval, &cur_ts); + ret = read_interval_packets(tfc, ifile, &interval, &cur_ts); } else { for (i = 0; i < read_intervals_nb; i++) { - ret = read_interval_packets(w, ifile, &read_intervals[i], &cur_ts); + ret = read_interval_packets(tfc, ifile, &read_intervals[i], &cur_ts); if (ret < 0) break; } @@ -1658,22 +1658,22 @@ static int read_packets(AVTextFormatContext *w, InputFile *ifile) return ret; } -static void print_dispositions(AVTextFormatContext *w, uint32_t disposition, SectionID section_id) +static void print_dispositions(AVTextFormatContext *tfc, uint32_t disposition, SectionID section_id) { - avtext_print_section_header(w, NULL, section_id); + avtext_print_section_header(tfc, 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))); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } #define IN_PROGRAM 1 #define IN_STREAM_GROUP 2 -static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int container) +static int show_stream(AVTextFormatContext *tfc, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int container) { AVStream *stream = ist->st; AVCodecParameters *par; @@ -1705,7 +1705,7 @@ static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int str av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, section_header[container]); + avtext_print_section_header(tfc, NULL, section_header[container]); print_int("index", stream->index); @@ -1774,11 +1774,11 @@ static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int str else print_str_opt("pix_fmt", "unknown"); print_int("level", par->level); - print_color_range(w, par->color_range); - print_color_space(w, par->color_space); - print_color_trc(w, par->color_trc); - print_primaries(w, par->color_primaries); - print_chroma_location(w, par->chroma_location); + print_color_range(tfc, par->color_range); + print_color_space(tfc, par->color_space); + print_color_trc(tfc, par->color_trc); + print_primaries(tfc, par->color_primaries); + print_chroma_location(tfc, par->chroma_location); if (par->field_order == AV_FIELD_PROGRESSIVE) print_str("field_order", "progressive"); @@ -1830,9 +1830,9 @@ static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int str if (show_private_data) { if (dec_ctx && dec_ctx->codec->priv_class) - print_private_data(w, dec_ctx->priv_data); + print_private_data(tfc, dec_ctx->priv_data); if (fmt_ctx->iformat->priv_class) - print_private_data(w, fmt_ctx->priv_data); + print_private_data(tfc, fmt_ctx->priv_data); } if (fmt_ctx->iformat->flags & AVFMT_SHOW_IDS) print_fmt ("id", "0x%x", stream->id); @@ -1859,113 +1859,113 @@ static int show_stream(AVTextFormatContext *w, AVFormatContext *fmt_ctx, int str 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) - avtext_print_data(w, "extradata", par->extradata, + avtext_print_data(tfc, "extradata", par->extradata, par->extradata_size); if (par->extradata_size > 0) { print_int("extradata_size", par->extradata_size); - avtext_print_data_hash(w, "extradata_hash", par->extradata, + avtext_print_data_hash(tfc, "extradata_hash", par->extradata, par->extradata_size); } /* Print disposition information */ if (do_show_stream_disposition) { av_assert0(container < FF_ARRAY_ELEMS(section_disposition)); - print_dispositions(w, stream->disposition, section_disposition[container]); + print_dispositions(tfc, stream->disposition, section_disposition[container]); } if (do_show_stream_tags) { av_assert0(container < FF_ARRAY_ELEMS(section_tags)); - ret = show_tags(w, stream->metadata, section_tags[container]); + ret = show_tags(tfc, stream->metadata, section_tags[container]); } if (stream->codecpar->nb_coded_side_data) { - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_SIDE_DATA_LIST); + avtext_print_section_header(tfc, 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], + print_pkt_side_data(tfc, stream->codecpar, &stream->codecpar->coded_side_data[i], SECTION_ID_STREAM_SIDE_DATA); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); fflush(stdout); return ret; } -static int show_streams(AVTextFormatContext *w, InputFile *ifile) +static int show_streams(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_STREAMS); + avtext_print_section_header(tfc, 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); + ret = show_stream(tfc, fmt_ctx, i, &ifile->streams[i], 0); if (ret < 0) break; } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_program(AVTextFormatContext *w, InputFile *ifile, AVProgram *program) +static int show_program(AVTextFormatContext *tfc, InputFile *ifile, AVProgram *program) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_PROGRAM); + avtext_print_section_header(tfc, 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); print_int("pmt_pid", program->pmt_pid); print_int("pcr_pid", program->pcr_pid); if (do_show_program_tags) - ret = show_tags(w, program->metadata, SECTION_ID_PROGRAM_TAGS); + ret = show_tags(tfc, program->metadata, SECTION_ID_PROGRAM_TAGS); if (ret < 0) goto end; - avtext_print_section_header(w, NULL, SECTION_ID_PROGRAM_STREAMS); + avtext_print_section_header(tfc, 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); + ret = show_stream(tfc, fmt_ctx, program->stream_index[i], &ifile->streams[program->stream_index[i]], IN_PROGRAM); if (ret < 0) break; } } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); end: - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_programs(AVTextFormatContext *w, InputFile *ifile) +static int show_programs(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_PROGRAMS); + avtext_print_section_header(tfc, NULL, SECTION_ID_PROGRAMS); for (i = 0; i < fmt_ctx->nb_programs; i++) { AVProgram *program = fmt_ctx->programs[i]; if (!program) continue; - ret = show_program(w, ifile, program); + ret = show_program(tfc, ifile, program); if (ret < 0) break; } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static void print_tile_grid_params(AVTextFormatContext *w, const AVStreamGroup *stg, +static void print_tile_grid_params(AVTextFormatContext *tfc, const AVStreamGroup *stg, const AVStreamGroupTileGrid *tile_grid) { - avtext_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT); + avtext_print_section_header(tfc, 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); @@ -1973,19 +1973,19 @@ static void print_tile_grid_params(AVTextFormatContext *w, const AVStreamGroup * print_int("vertical_offset", tile_grid->vertical_offset); print_int("width", tile_grid->width); print_int("height", tile_grid->height); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); for (int i = 0; i < tile_grid->nb_tiles; i++) { - avtext_print_section_header(w, "tile_offset", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); + avtext_print_section_header(tfc, "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); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); + avtext_print_section_footer(tfc); } -static void print_iamf_param_definition(AVTextFormatContext *w, const char *name, +static void print_iamf_param_definition(AVTextFormatContext *tfc, const char *name, const AVIAMFParamDefinition *param, SectionID section_id) { SectionID subsection_id, parameter_section_id; @@ -1993,7 +1993,7 @@ static void print_iamf_param_definition(AVTextFormatContext *w, const char *name av_assert0(subsection_id != -1); parameter_section_id = sections[subsection_id].children_ids[0]; av_assert0(parameter_section_id != -1); - avtext_print_section_header(w, "IAMF Param Definition", section_id); + avtext_print_section_header(tfc, "IAMF Param Definition", section_id); print_str("name", name); print_int("nb_subblocks", param->nb_subblocks); print_int("type", param->type); @@ -2002,56 +2002,56 @@ static void print_iamf_param_definition(AVTextFormatContext *w, const char *name print_int("duration", param->duration); print_int("constant_subblock_duration", param->constant_subblock_duration); if (param->nb_subblocks > 0) - avtext_print_section_header(w, NULL, subsection_id); + avtext_print_section_header(tfc, 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; - avtext_print_section_header(w, "IAMF Mix Gain Parameters", parameter_section_id); + avtext_print_section_header(tfc, "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, '/'); - avtext_print_section_footer(w); // parameter_section_id + avtext_print_section_footer(tfc); // parameter_section_id break; } case AV_IAMF_PARAMETER_DEFINITION_DEMIXING: { const AVIAMFDemixingInfo *demix = subblock; - avtext_print_section_header(w, "IAMF Demixing Info", parameter_section_id); + avtext_print_section_header(tfc, "IAMF Demixing Info", parameter_section_id); print_int("subblock_duration", demix->subblock_duration); print_int("dmixp_mode", demix->dmixp_mode); - avtext_print_section_footer(w); // parameter_section_id + avtext_print_section_footer(tfc); // parameter_section_id break; } case AV_IAMF_PARAMETER_DEFINITION_RECON_GAIN: { const AVIAMFReconGain *recon = subblock; - avtext_print_section_header(w, "IAMF Recon Gain", parameter_section_id); + avtext_print_section_header(tfc, "IAMF Recon Gain", parameter_section_id); print_int("subblock_duration", recon->subblock_duration); - avtext_print_section_footer(w); // parameter_section_id + avtext_print_section_footer(tfc); // parameter_section_id break; } } } if (param->nb_subblocks > 0) - avtext_print_section_footer(w); // subsection_id - avtext_print_section_footer(w); // section_id + avtext_print_section_footer(tfc); // subsection_id + avtext_print_section_footer(tfc); // section_id } -static void print_iamf_audio_element_params(AVTextFormatContext *w, const AVStreamGroup *stg, +static void print_iamf_audio_element_params(AVTextFormatContext *tfc, const AVStreamGroup *stg, const AVIAMFAudioElement *audio_element) { - avtext_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT); + avtext_print_section_header(tfc, 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); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); + avtext_print_section_header(tfc, 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]; - avtext_print_section_header(w, "IAMF Audio Layer", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); + avtext_print_section_header(tfc, "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) { @@ -2059,52 +2059,52 @@ static void print_iamf_audio_element_params(AVTextFormatContext *w, const AVStre 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); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT } if (audio_element->demixing_info) - print_iamf_param_definition(w, "demixing_info", audio_element->demixing_info, + print_iamf_param_definition(tfc, "demixing_info", audio_element->demixing_info, SECTION_ID_STREAM_GROUP_SUBCOMPONENT); if (audio_element->recon_gain_info) - print_iamf_param_definition(w, "recon_gain_info", audio_element->recon_gain_info, + print_iamf_param_definition(tfc, "recon_gain_info", audio_element->recon_gain_info, SECTION_ID_STREAM_GROUP_SUBCOMPONENT); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENT + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_COMPONENT } -static void print_iamf_submix_params(AVTextFormatContext *w, const AVIAMFSubmix *submix) +static void print_iamf_submix_params(AVTextFormatContext *tfc, const AVIAMFSubmix *submix) { - avtext_print_section_header(w, "IAMF Submix", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); + avtext_print_section_header(tfc, "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, '/'); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_PIECES); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUP_PIECES); for (int i = 0; i < submix->nb_elements; i++) { const AVIAMFSubmixElement *element = submix->elements[i]; - avtext_print_section_header(w, "IAMF Submix Element", SECTION_ID_STREAM_GROUP_PIECE); + avtext_print_section_header(tfc, "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); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBPIECES); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUP_SUBPIECES); if (element->annotations) { const AVDictionaryEntry *annotation = NULL; - avtext_print_section_header(w, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBPIECE); + avtext_print_section_header(tfc, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBPIECE); while (annotation = av_dict_iterate(element->annotations, annotation)) print_str(annotation->key, annotation->value); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBPIECE + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBPIECE } if (element->element_mix_config) - print_iamf_param_definition(w, "element_mix_config", element->element_mix_config, + print_iamf_param_definition(tfc, "element_mix_config", element->element_mix_config, SECTION_ID_STREAM_GROUP_SUBPIECE); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBPIECES - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECE + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBPIECES + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_PIECE } if (submix->output_mix_config) - print_iamf_param_definition(w, "output_mix_config", submix->output_mix_config, + print_iamf_param_definition(tfc, "output_mix_config", submix->output_mix_config, SECTION_ID_STREAM_GROUP_PIECE); for (int i = 0; i < submix->nb_layouts; i++) { const AVIAMFSubmixLayout *layout = submix->layouts[i]; char val_str[128]; - avtext_print_section_header(w, "IAMF Submix Layout", SECTION_ID_STREAM_GROUP_PIECE); + avtext_print_section_header(tfc, "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, '/'); @@ -2112,51 +2112,51 @@ static void print_iamf_submix_params(AVTextFormatContext *w, const AVIAMFSubmix 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, '/'); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECE + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_PIECE } - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_PIECES - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_PIECES + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT } -static void print_iamf_mix_presentation_params(AVTextFormatContext *w, const AVStreamGroup *stg, +static void print_iamf_mix_presentation_params(AVTextFormatContext *tfc, const AVStreamGroup *stg, const AVIAMFMixPresentation *mix_presentation) { - avtext_print_section_header(w, stg, SECTION_ID_STREAM_GROUP_COMPONENT); + avtext_print_section_header(tfc, stg, SECTION_ID_STREAM_GROUP_COMPONENT); print_int("nb_submixes", mix_presentation->nb_submixes); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUP_SUBCOMPONENTS); if (mix_presentation->annotations) { const AVDictionaryEntry *annotation = NULL; - avtext_print_section_header(w, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); + avtext_print_section_header(tfc, "IAMF Annotations", SECTION_ID_STREAM_GROUP_SUBCOMPONENT); while (annotation = av_dict_iterate(mix_presentation->annotations, annotation)) print_str(annotation->key, annotation->value); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENT } for (int i = 0; i < mix_presentation->nb_submixes; i++) - print_iamf_submix_params(w, mix_presentation->submixes[i]); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENT + print_iamf_submix_params(tfc, mix_presentation->submixes[i]); + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_SUBCOMPONENTS + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_COMPONENT } -static void print_stream_group_params(AVTextFormatContext *w, AVStreamGroup *stg) +static void print_stream_group_params(AVTextFormatContext *tfc, AVStreamGroup *stg) { - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_COMPONENTS); + avtext_print_section_header(tfc, 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); + print_tile_grid_params(tfc, 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); + print_iamf_audio_element_params(tfc, 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); - avtext_print_section_footer(w); // SECTION_ID_STREAM_GROUP_COMPONENTS + print_iamf_mix_presentation_params(tfc, stg, stg->params.iamf_mix_presentation); + avtext_print_section_footer(tfc); // SECTION_ID_STREAM_GROUP_COMPONENTS } -static int show_stream_group(AVTextFormatContext *w, InputFile *ifile, AVStreamGroup *stg) +static int show_stream_group(AVTextFormatContext *tfc, 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); - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP); + avtext_print_section_header(tfc, 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"); @@ -2166,60 +2166,60 @@ static int show_stream_group(AVTextFormatContext *w, InputFile *ifile, AVStreamG else print_str_opt("type", "unknown"); if (do_show_stream_group_components) - print_stream_group_params(w, stg); + print_stream_group_params(tfc, stg); /* Print disposition information */ if (do_show_stream_group_disposition) - print_dispositions(w, stg->disposition, SECTION_ID_STREAM_GROUP_DISPOSITION); + print_dispositions(tfc, stg->disposition, SECTION_ID_STREAM_GROUP_DISPOSITION); if (do_show_stream_group_tags) - ret = show_tags(w, stg->metadata, SECTION_ID_STREAM_GROUP_TAGS); + ret = show_tags(tfc, stg->metadata, SECTION_ID_STREAM_GROUP_TAGS); if (ret < 0) goto end; - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_STREAMS); + avtext_print_section_header(tfc, 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); + ret = show_stream(tfc, fmt_ctx, stg->streams[i]->index, &ifile->streams[stg->streams[i]->index], IN_STREAM_GROUP); if (ret < 0) break; } } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); end: av_bprint_finalize(&pbuf, NULL); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_stream_groups(AVTextFormatContext *w, InputFile *ifile) +static int show_stream_groups(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_STREAM_GROUPS); + avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUPS); for (i = 0; i < fmt_ctx->nb_stream_groups; i++) { AVStreamGroup *stg = fmt_ctx->stream_groups[i]; - ret = show_stream_group(w, ifile, stg); + ret = show_stream_group(tfc, ifile, stg); if (ret < 0) break; } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_chapters(AVTextFormatContext *w, InputFile *ifile) +static int show_chapters(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int i, ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_CHAPTERS); + avtext_print_section_header(tfc, NULL, SECTION_ID_CHAPTERS); for (i = 0; i < fmt_ctx->nb_chapters; i++) { AVChapter *chapter = fmt_ctx->chapters[i]; - avtext_print_section_header(w, NULL, SECTION_ID_CHAPTER); + avtext_print_section_header(tfc, NULL, SECTION_ID_CHAPTER); print_int("id", chapter->id); print_q ("time_base", chapter->time_base, '/'); print_int("start", chapter->start); @@ -2227,21 +2227,21 @@ static int show_chapters(AVTextFormatContext *w, InputFile *ifile) print_int("end", chapter->end); print_time("end_time", chapter->end, &chapter->time_base); if (do_show_chapter_tags) - ret = show_tags(w, chapter->metadata, SECTION_ID_CHAPTER_TAGS); - avtext_print_section_footer(w); + ret = show_tags(tfc, chapter->metadata, SECTION_ID_CHAPTER_TAGS); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); return ret; } -static int show_format(AVTextFormatContext *w, InputFile *ifile) +static int show_format(AVTextFormatContext *tfc, InputFile *ifile) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; int64_t size = fmt_ctx->pb ? avio_size(fmt_ctx->pb) : -1; int ret = 0; - avtext_print_section_header(w, NULL, SECTION_ID_FORMAT); + avtext_print_section_header(tfc, 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); @@ -2259,19 +2259,19 @@ static int show_format(AVTextFormatContext *w, InputFile *ifile) else print_str_opt("bit_rate", "N/A"); print_int("probe_score", fmt_ctx->probe_score); if (do_show_format_tags) - ret = show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); + ret = show_tags(tfc, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); fflush(stdout); return ret; } -static void show_error(AVTextFormatContext *w, int err) +static void show_error(AVTextFormatContext *tfc, int err) { - avtext_print_section_header(w, NULL, SECTION_ID_ERROR); + avtext_print_section_header(tfc, NULL, SECTION_ID_ERROR); print_int("code", err); print_str("string", av_err2str(err)); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } static int open_input_file(InputFile *ifile, const char *filename, @@ -2413,7 +2413,7 @@ static void close_input_file(InputFile *ifile) avformat_close_input(&ifile->fmt_ctx); } -static int probe_file(AVTextFormatContext *tctx, const char *filename, +static int probe_file(AVTextFormatContext *tfc, const char *filename, const char *print_filename) { InputFile ifile = { 0 }; @@ -2455,40 +2455,40 @@ static int probe_file(AVTextFormatContext *tctx, const char *filename, if (do_read_frames || do_read_packets) { if (do_show_frames && do_show_packets && - tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT) + tfc->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) - avtext_print_section_header(tctx, NULL, section_id); - ret = read_packets(tctx, &ifile); + avtext_print_section_header(tfc, NULL, section_id); + ret = read_packets(tfc, &ifile); if (do_show_frames || do_show_packets) - avtext_print_section_footer(tctx); + avtext_print_section_footer(tfc); CHECK_END; } if (do_show_programs) { - ret = show_programs(tctx, &ifile); + ret = show_programs(tfc, &ifile); CHECK_END; } if (do_show_stream_groups) { - ret = show_stream_groups(tctx, &ifile); + ret = show_stream_groups(tfc, &ifile); CHECK_END; } if (do_show_streams) { - ret = show_streams(tctx, &ifile); + ret = show_streams(tfc, &ifile); CHECK_END; } if (do_show_chapters) { - ret = show_chapters(tctx, &ifile); + ret = show_chapters(tfc, &ifile); CHECK_END; } if (do_show_format) { - ret = show_format(tctx, &ifile); + ret = show_format(tfc, &ifile); CHECK_END; } @@ -2511,18 +2511,18 @@ static void show_usage(void) av_log(NULL, AV_LOG_INFO, "\n"); } -static void ffprobe_show_program_version(AVTextFormatContext *w) +static void ffprobe_show_program_version(AVTextFormatContext *tfc) { AVBPrint pbuf; av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); - avtext_print_section_header(w, NULL, SECTION_ID_PROGRAM_VERSION); + avtext_print_section_header(tfc, 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); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); av_bprint_finalize(&pbuf, NULL); } @@ -2531,20 +2531,20 @@ static void ffprobe_show_program_version(AVTextFormatContext *w) do { \ if (CONFIG_##LIBNAME) { \ unsigned int version = libname##_version(); \ - avtext_print_section_header(w, NULL, SECTION_ID_LIBRARY_VERSION); \ + avtext_print_section_header(tfc, 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); \ - avtext_print_section_footer(w); \ + avtext_print_section_footer(tfc); \ } \ } while (0) -static void ffprobe_show_library_versions(AVTextFormatContext *w) +static void ffprobe_show_library_versions(AVTextFormatContext *tfc) { - avtext_print_section_header(w, NULL, SECTION_ID_LIBRARY_VERSIONS); + avtext_print_section_header(tfc, NULL, SECTION_ID_LIBRARY_VERSIONS); SHOW_LIB_VERSION(avutil, AVUTIL); SHOW_LIB_VERSION(avcodec, AVCODEC); SHOW_LIB_VERSION(avformat, AVFORMAT); @@ -2553,7 +2553,7 @@ static void ffprobe_show_library_versions(AVTextFormatContext *w) SHOW_LIB_VERSION(swscale, SWSCALE); SHOW_LIB_VERSION(swresample, SWRESAMPLE); SHOW_LIB_VERSION(postproc, POSTPROC); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } #define PRINT_PIX_FMT_FLAG(flagname, name) \ @@ -2561,14 +2561,14 @@ static void ffprobe_show_library_versions(AVTextFormatContext *w) print_int(name, !!(pixdesc->flags & AV_PIX_FMT_FLAG_##flagname)); \ } while (0) -static void ffprobe_show_pixel_formats(AVTextFormatContext *w) +static void ffprobe_show_pixel_formats(AVTextFormatContext *tfc) { const AVPixFmtDescriptor *pixdesc = NULL; int i, n; - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMATS); + avtext_print_section_header(tfc, NULL, SECTION_ID_PIXEL_FORMATS); while (pixdesc = av_pix_fmt_desc_next(pixdesc)) { - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT); + avtext_print_section_header(tfc, 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)) { @@ -2582,7 +2582,7 @@ static void ffprobe_show_pixel_formats(AVTextFormatContext *w) if (n) print_int ("bits_per_pixel", n); else print_str_opt("bits_per_pixel", "N/A"); if (do_show_pixel_format_flags) { - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_FLAGS); + avtext_print_section_header(tfc, 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"); @@ -2590,21 +2590,21 @@ static void ffprobe_show_pixel_formats(AVTextFormatContext *w) PRINT_PIX_FMT_FLAG(PLANAR, "planar"); PRINT_PIX_FMT_FLAG(RGB, "rgb"); PRINT_PIX_FMT_FLAG(ALPHA, "alpha"); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } if (do_show_pixel_format_components && (pixdesc->nb_components > 0)) { - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENTS); + avtext_print_section_header(tfc, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENTS); for (i = 0; i < pixdesc->nb_components; i++) { - avtext_print_section_header(w, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENT); + avtext_print_section_header(tfc, NULL, SECTION_ID_PIXEL_FORMAT_COMPONENT); print_int("index", i + 1); print_int("bit_depth", pixdesc->comp[i].depth); - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } - avtext_print_section_footer(w); + avtext_print_section_footer(tfc); } static int opt_show_optional_fields(void *optctx, const char *opt, const char *arg) -- 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] 73+ messages in thread
end of thread, other threads:[~2025-03-09 18:55 UTC | newest] Thread overview: 73+ 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-02 17:54 ` Stefano Sabatini 2025-03-02 19:44 ` Soft Works 2025-03-05 20:20 ` Stefano Sabatini 2025-03-05 20:58 ` Soft Works 2025-03-08 14:00 ` Stefano Sabatini 2025-03-08 15:01 ` Soft Works 2025-03-08 14:36 ` Stefano Sabatini 2025-03-08 15:30 ` Soft Works 2025-03-08 18:12 ` Stefano Sabatini 2025-03-08 19:25 ` Soft Works 2025-03-09 18:55 ` Soft Works 2025-03-01 10:01 ` [FFmpeg-devel] [PATCH v3 2/7] fftools/ffprobe: Change to use textformat api softworkz 2025-03-08 14:18 ` Stefano Sabatini 2025-03-01 10:02 ` [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz 2025-03-08 14:46 ` Stefano Sabatini 2025-03-08 15:46 ` Soft Works 2025-03-08 17:54 ` Soft Works 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 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 2/7] fftools/ffprobe: Change to use textformat api softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 4/7] fftools/ffmpeg_filter: Move some declaration to new header file softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 5/7] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 6/7] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz 2025-03-01 22:54 ` [FFmpeg-devel] [PATCH v4 7/7] fftools: Enable filtergraph printing and update docs softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz 2025-03-08 19:08 ` Stefano Sabatini 2025-03-08 19:49 ` Soft Works 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 2/8] fftools/ffprobe: Change to use textformat api softworkz 2025-03-08 19:23 ` Stefano Sabatini 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz 2025-03-08 19:24 ` Stefano Sabatini 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 4/8] fftools/ffmpeg_filter: Move some declaration to new header file softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 5/8] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 7/8] fftools: Enable filtergraph printing and update docs softworkz 2025-03-08 17:55 ` [FFmpeg-devel] [PATCH v5 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tc) softworkz 2025-03-08 19:30 ` Stefano Sabatini 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing ffmpegagent 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 1/8] fftools/textformat: Extract and generalize textformat api from ffprobe.c softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 2/8] fftools/ffprobe: Change to use textformat api softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 3/8] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 4/8] fftools/ffmpeg_filter: Move some declaration to new header file softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 5/8] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 7/8] fftools: Enable filtergraph printing and update docs softworkz 2025-03-08 20:16 ` [FFmpeg-devel] [PATCH v6 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tfc) 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