From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id 69ABA4B20B for ; Sat, 1 Mar 2025 10:02:29 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 2521B68DEC8; Sat, 1 Mar 2025 12:02:26 +0200 (EET) Received: from mail-pl1-f173.google.com (mail-pl1-f173.google.com [209.85.214.173]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 33C5D68DE50 for ; Sat, 1 Mar 2025 12:02:13 +0200 (EET) Received: by mail-pl1-f173.google.com with SMTP id d9443c01a7336-2230c74c8b6so13983345ad.0; Sat, 01 Mar 2025 02:02:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1740823331; x=1741428131; darn=ffmpeg.org; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date:from :references:in-reply-to:message-id:from:to:cc:subject:date :message-id:reply-to; bh=PDAoiscJPVPPfdVVBtJgt210JO0diFGlS9WJ8y6avlU=; b=LJLHiBHDbMh3wwdE9LjM/DcjQfUYEvjNO5Ep6mnUYb9oo01OG6q1kvTa0htIHCc0zC lhcbpPvrSzhgcvXh06GZfQZyNbdIcPL+f/jyiMyuNzauGb+SENDup/9//bgFwhNeibwd f0FaEaxiFJhIUD+lViCgXLK62ppmWZDzucyAAOXtUcA+3pHsIfDJeqYNszDKjkQUXEgZ o8LNCp3KXZhS0tvMGJCKjqR9tSZQmw/j1ffBdV3NVSd8QX2csOh4Z5/6OSUv2FO2wbfC jHPI/qDch/gUkTT40Zdis6J2ID1r4hcE+iQsbUYyYAC+9vuXay445YSXc0Pl+ThtD91e IKgQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1740823331; x=1741428131; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date:from :references:in-reply-to:message-id:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=PDAoiscJPVPPfdVVBtJgt210JO0diFGlS9WJ8y6avlU=; b=sYL6bValNL46fQinrBHQT9Zul7oZxsrl3cTLWy4J/nD3ug6a3bURDq66HeWj5ek8XB +r/l1ZtgVoDmwKmcDagrdxKUhe073DBRvFjon4mGdQymIYaMyXg/sXiVEBKLr0RVXEgi TBaFY0+hHes2Qj6p4MYKA0hltUm60YI6bZDMHA70fE8zSDY7QN2q64PwOEivLh8EhkBm dTaT0AdBckP/JJ3OvnllcykyeMfrQSjRi3CGmOP6943jRVxHBmy9tvp9BR6UdCYQApvp YPw/Blprj4UR3fTT2faK30BFZVMpE4yVLwP44N7Y/dr0LSuYBxN43pQPvEIUEE93d0KB CtSg== X-Forwarded-Encrypted: i=1; AJvYcCVi5z6bCYZttRBAkIlYsGiUJROVt3fFmkT6HxJ+qQvpC6TTlW8z8OpjuKtJ4fE3U13ePsYSgrRdB/oMTsO5mK8fbQGstX6HiQU=@ffmpeg.org X-Gm-Message-State: AOJu0YzNLb04nWfGaNh8tRa8FBxIzFxMutwXQYu7sLsD7kwEdogXiCcf yfWkbm7n75Q6JT2vKbNNt5ZpD3XHYldng4WVXzgAiJktjqTs/mwf/cBg0Q== X-Gm-Gg: ASbGncvMIJQQyz8SwNmeFe1kiN+wVORStmki5mWkZJVoh/l9aIv2AN/26FtKUj7+LHE 0X/NTj6By7G8UuIv//Lt5WjUXH4rmKWRv5i75xgC+hFKil4y0rjbrD6qqtLi/vFcRWkM5z4fFUe +Tf6EVkkmCZ3ThxbXsE68KfwZN5CZXe6ZRLzLbVglewmUbyJfVOtsGSR0JBkH+vVu4TgtwR0gYU DkxNGduedLZZi7GjUQQiiK2I4sbQ8I2Url1nhsAEk0WPpZ0RFVvhyOGT5WBc4TrCY6FMkbYVt7y SwidpAMg5c5v2PEGe7SaqOU+8odtaoGkB/QjW9k6noZUZVBSFtGUex8R2u7pOuA= X-Google-Smtp-Source: AGHT+IE/yOX7bdF9Uc5UQkly78033TJXdVVpjS0b0ffkbGVJitLEjJeV0M/p1X89v+veTuIxae+qpA== X-Received: by 2002:a17:903:1c9:b0:215:7421:262 with SMTP id d9443c01a7336-22368fbe712mr117658415ad.12.1740823330853; Sat, 01 Mar 2025 02:02:10 -0800 (PST) Received: from [127.0.0.1] (master.gitmailbox.com. [34.83.118.50]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-223505100casm46271285ad.217.2025.03.01.02.02.10 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Sat, 01 Mar 2025 02:02:10 -0800 (PST) Message-Id: In-Reply-To: References: From: ffmpegagent Date: Sat, 01 Mar 2025 10:01:57 +0000 Fcc: Sent MIME-Version: 1.0 To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Soft Works , softworkz , Andreas Rheinhardt Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Archived-At: List-Archive: List-Post: 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 ## 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".