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 7BD704E509 for ; Wed, 12 Mar 2025 04:06:00 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id A5AEC68E427; Wed, 12 Mar 2025 06:05:09 +0200 (EET) Received: from mail-pl1-f170.google.com (mail-pl1-f170.google.com [209.85.214.170]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id A389C68E3FE for ; Wed, 12 Mar 2025 06:05:07 +0200 (EET) Received: by mail-pl1-f170.google.com with SMTP id d9443c01a7336-225477548e1so70084725ad.0 for ; Tue, 11 Mar 2025 21:05:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1741752306; x=1742357106; darn=ffmpeg.org; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date :references:in-reply-to:message-id:from:from:to:cc:subject:date :message-id:reply-to; bh=YHpe8R8JkjswThnRXUcJu8DK5UnqOhvCqTO6IRkHmxk=; b=X2EzGCaRI46R+KRHxDTu4h25WXHEHJYPKHpodiL2VdkInoGSOc7j5pUs6PIAhxXcxj 5/DRUfu3d2+CAMaBVvyeu9vGPiezX9nOB31d0dI5LhIx/WTdtQhnZzWFbNB7FbZYNyb2 18TKstIohTFT9g2ZL9KrHyiRytLeyI2mhRzXyxi+T3hNbRclz6AEn/oF3Rvrda9TU9Qv wz7v0w8R8WcVKUo7YvvUs1ArcfWYGOf4p/Fz4JQgEF9ICxG+uUeYvLYeTeuTX5vMgwN3 6Va3FQ3Y4rF/OJaA3atpEiIl5/i4xlVBPRCY0jSVoN1/BYlPzYTkQ8V7jDYKp+gb2xAJ bxFg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1741752306; x=1742357106; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date :references:in-reply-to:message-id:from:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=YHpe8R8JkjswThnRXUcJu8DK5UnqOhvCqTO6IRkHmxk=; b=HcX5wjNFTkUulkSlogTWMv7HK04eSV1GJw0M8IOmf6CkvgniUFiI/wTZA6tnh4lbx8 RDc1FdVCX330nSFOSLxEEX4wOlucOkcybMKOLjIHwtQGa1wSKDe97K9oYda1BNXDtPS8 pSvHvuCkrwwDdNoinEFpxcwNCQ/Ld31seb+87O333plQOnIYmwUATduAt8FA7oPIhHkq NxP2Yx6A8HWl5nKG11gSwcs0bO2gWvRxuGNM4TJf38RatAHN2/VvsH6qCV0iUOXmR+Z/ Tp5gGJI/UHkOgFqvGb4IWIBZa0P+CQiF/GQbcn1t6eUcQ7nqarB8SCsTKjPcKiNJwKMm rWKA== X-Gm-Message-State: AOJu0Yyl5Lvc/H3rnQWKzTjcrYP1ZIf8fSok6QRhVVaOAW5K/lA5b7tI 0wYCUVaARU2Q4//GwgoKFNPnwv+zkxtsH0Ox8dgCEm0eBgr+12PYErMM6qGN X-Gm-Gg: ASbGncuppswgREQYxxraV2xsIjttlL/X37MIzANKoLfdQysQu/cV5m02Vr7cyPglW6w oFv9vOCv0bsAyzW59Qel0Z1Q0SHLYm14TaFlDExpeyl8dv3jwLaiazX5wyvb7BFW0vY5+PTOqQj y7MqWP3+Sh5beYRYJhmAWhW3DJG9ulfSYHuIIHmt4wL4OdVn8ocWBcPdPbQ8vBBNeNsXtvcomwT OgKjlRiIIE8FaJjIPP9FkwJy3qYaTChluaoneP9/aPIstocoPPuIZ5y4OyDjLEZ8nPtSXGW3J7S AEL6Kvmpbq65O+liqNUtrLofR3OHkveKzoDyB6wrVtGfwMGbLUfWo6XcIsQjUCo= X-Google-Smtp-Source: AGHT+IF2a6OU2MRXL1IPcabarU3Zw0/ioU5PqmxD6BCR2i/SXlvFv0RSvwrZa3crginCNVqfseGAvg== X-Received: by 2002:a17:902:e745:b0:224:1eab:97b5 with SMTP id d9443c01a7336-224288866e8mr341025015ad.1.1741752305550; Tue, 11 Mar 2025 21:05:05 -0700 (PDT) Received: from [127.0.0.1] (master.gitmailbox.com. [34.83.118.50]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-22410a7f8e3sm105691995ad.121.2025.03.11.21.05.05 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 11 Mar 2025 21:05:05 -0700 (PDT) From: softworkz X-Google-Original-From: softworkz Message-Id: In-Reply-To: References: Date: Wed, 12 Mar 2025 04:04:24 +0000 Fcc: Sent MIME-Version: 1.0 To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH v7 6/7] fftools/ffmpeg_graphprint: Add options for 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: softworkz 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: From: softworkz Enables filtergraph printing and adds the options to the docs 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 --- doc/ffmpeg.texi | 11 + fftools/Makefile | 11 + fftools/ffmpeg.c | 4 + fftools/ffmpeg.h | 3 + fftools/ffmpeg_filter.c | 5 + fftools/ffmpeg_graphprint.c | 470 ++++++++++++++++++++++++++++++ fftools/ffmpeg_graphprint.h | 35 +++ fftools/ffmpeg_opt.c | 12 + fftools/textformat/avtextformat.c | 6 +- fftools/textformat/avtextformat.h | 16 +- 10 files changed, 563 insertions(+), 10 deletions(-) create mode 100644 fftools/ffmpeg_graphprint.c create mode 100644 fftools/ffmpeg_graphprint.h diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi index fca220a334..14fc75c033 100644 --- a/doc/ffmpeg.texi +++ b/doc/ffmpeg.texi @@ -1388,6 +1388,17 @@ 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. +Use a dash (-) for the file name to write to stdout. + +@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/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.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.h b/fftools/ffmpeg.h index 86a3e10c6b..4c92c7b40a 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_filter.c b/fftools/ffmpeg_filter.c index e6bfc72265..3e2337ea41 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" @@ -2974,6 +2975,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; diff --git a/fftools/ffmpeg_graphprint.c b/fftools/ffmpeg_graphprint.c new file mode 100644 index 0000000000..7efbfcdef9 --- /dev/null +++ b/fftools/ffmpeg_graphprint.c @@ -0,0 +1,470 @@ +/* + * 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 + +#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_HWFRAMESCONTEXT, +} SectionID; + +static struct AVTextFormatSection sections[] = { + [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "graph_description", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER, + { SECTION_ID_PROGRAM_VERSION, SECTION_ID_FILTERGRAPHS, -1} }, + [SECTION_ID_PROGRAM_VERSION] = { SECTION_ID_PROGRAM_VERSION, "program_version", 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, -1 } }, + [SECTION_ID_INPUT] = { SECTION_ID_INPUT, "input", 0, { SECTION_ID_HWFRAMESCONTEXT, -1 }, }, + + [SECTION_ID_OUTPUTS] = { SECTION_ID_OUTPUTS, "outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_OUTPUT, -1 } }, + [SECTION_ID_OUTPUT] = { SECTION_ID_OUTPUT, "output", 0, { SECTION_ID_HWFRAMESCONTEXT, -1 }, }, + + [SECTION_ID_FILTERS] = { SECTION_ID_FILTERS, "filters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER, -1 } }, + [SECTION_ID_FILTER] = { SECTION_ID_FILTER, "filter", 0, { -1 }, }, + + [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context", 0, { -1 }, }, +}; + +/* Text Format API Shortcuts */ +#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) + +static void print_hwdevicecontext(AVTextFormatContext *tfc, const AVHWDeviceContext *hw_device_context) +{ + print_int("has_hw_device_context", 1); + print_str("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type)); +} + +static void print_hwframescontext(AVTextFormatContext *tfc, const AVHWFramesContext *hw_frames_context) +{ + const AVPixFmtDescriptor* pix_desc_hw; + const AVPixFmtDescriptor* pix_desc_sw; + + avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT); + + print_int("has_hw_frames_context", 1); + print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type)); + + pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format); + if (pix_desc_hw) { + print_str("hw_pixel_format", pix_desc_hw->name); + if (pix_desc_hw->alias) + print_str("hw_pixel_format_alias", pix_desc_hw->alias); + } + + pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format); + if (pix_desc_sw) { + print_str("sw_pixel_format", pix_desc_sw->name); + if (pix_desc_sw->alias) + print_str("sw_pixel_format_alias", pix_desc_sw->alias); + } + + print_int("width", hw_frames_context->width); + print_int("height", hw_frames_context->height); + print_int("initial_pool_size", hw_frames_context->initial_pool_size); + + avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT +} + +static void print_link(AVTextFormatContext *tfc, AVFilterLink *link) +{ + AVBufferRef *hw_frames_ctx; + char layout_string[64]; + + print_str("media_type", av_get_media_type_string(link->type)); + + 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_str("color_range", av_color_range_name(link->color_range)); + print_str("color_space", av_color_space_name(link->colorspace)); + 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); + break; + + case AVMEDIA_TYPE_AUDIO: + av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string)); + print_str("channel_layout", layout_string); + print_int("channels", link->ch_layout.nb_channels); + print_int("sample_rate", link->sample_rate); + break; + } + + print_q("time_base", link->time_base, '/'); + + hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link); + + if (hw_frames_ctx && hw_frames_ctx->data) + print_hwframescontext(tfc, (AVHWFramesContext *)hw_frames_ctx->data); +} + +static void print_filter(AVTextFormatContext *tfc, const AVFilterContext* filter) +{ + avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER); + + print_str("filter_id", filter->name); + + if (filter->filter) { + print_str("filter_name", filter->filter->name); + print_str("description", filter->filter->description); + } + + if (filter->hw_device_ctx) { + AVHWDeviceContext* device_context = (AVHWDeviceContext*)filter->hw_device_ctx->data; + print_hwdevicecontext(tfc, device_context); + if (filter->extra_hw_frames > 0) + print_int("extra_hw_frames", filter->extra_hw_frames); + } + + avtext_print_section_header(tfc, NULL, SECTION_ID_INPUTS); + + for (unsigned i = 0; i < filter->nb_inputs; i++) { + AVFilterLink *link = filter->inputs[i]; + avtext_print_section_header(tfc, NULL, SECTION_ID_INPUT); + + print_int("input_index", i); + print_str("pad_name", avfilter_pad_get_name(link->dstpad, 0));; + print_str("source_filter_id", link->src->name); + print_str("source_pad_name", avfilter_pad_get_name(link->srcpad, 0)); + + print_link(tfc, link); + + avtext_print_section_footer(tfc); // SECTION_ID_INPUT + } + + avtext_print_section_footer(tfc); // SECTION_ID_INPUTS + + avtext_print_section_header(tfc, NULL, SECTION_ID_OUTPUTS); + + for (unsigned i = 0; i < filter->nb_outputs; i++) { + AVFilterLink *link = filter->outputs[i]; + avtext_print_section_header(tfc, NULL, SECTION_ID_OUTPUT); + + print_int("output_index", i); + print_str("pad_name", avfilter_pad_get_name(link->srcpad, 0)); + print_str("dest_filter_id", link->dst->name); + print_str("dest_pad_name", avfilter_pad_get_name(link->dstpad, 0)); + + print_link(tfc, link); + + avtext_print_section_footer(tfc); // SECTION_ID_OUTPUT + } + + avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTS + + avtext_print_section_footer(tfc); // 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 *tfc, FilterGraph *fg, AVFilterGraph *graph) +{ + FilterGraphPriv *fgp = fgp_from_fg(fg); + + print_int("graph_index", fg->index); + print_str("description", fgp->graph_desc); + + avtext_print_section_header(tfc, NULL, SECTION_ID_INPUTS); + + for (int i = 0; i < fg->nb_inputs; i++) { + InputFilterPriv* ifilter = ifp_from_ifilter(fg->inputs[i]); + enum AVMediaType media_type = ifilter->type; + + avtext_print_section_header(tfc, NULL, SECTION_ID_INPUT); + + print_int("input_index", ifilter->index); + + if (ifilter->linklabel) + print_str("link_label", (const char*)ifilter->linklabel); + + if (ifilter->filter) { + print_str("filter_id", ifilter->filter->name); + print_str("filter_name", ifilter->filter->filter->name); + } + + print_str("media_type", av_get_media_type_string(media_type)); + + avtext_print_section_footer(tfc); // SECTION_ID_INPUT + } + + avtext_print_section_footer(tfc); // SECTION_ID_INPUTS + + avtext_print_section_header(tfc, NULL, SECTION_ID_OUTPUTS); + + for (int i = 0; i < fg->nb_outputs; i++) { + OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]); + + avtext_print_section_header(tfc, NULL, SECTION_ID_OUTPUT); + + print_int("output_index", ofilter->index); + + print_str("name", ofilter->name); + + if (fg->outputs[i]->linklabel) + print_str("link_label", (const char*)fg->outputs[i]->linklabel); + + if (ofilter->filter) { + print_str("filter_id", ofilter->filter->name); + print_str("filter_name", ofilter->filter->filter->name); + } + + print_str("media_type", av_get_media_type_string(fg->outputs[i]->type)); + + avtext_print_section_footer(tfc); // SECTION_ID_OUTPUT + } + + avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTS + + avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERS); + + if (graph) { + for (unsigned i = 0; i < graph->nb_filters; i++) { + AVFilterContext *filter = graph->filters[i]; + avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER); + + print_filter(tfc, filter); + + avtext_print_section_footer(tfc); // SECTION_ID_FILTER + } + } + + avtext_print_section_footer(tfc); // 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 ) { + 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) { + + // Due to the threading model each graph needs to print itself into a buffer + // from its own thread. The actual printing happens short before cleanup in ffmpeg.c + // where all grahps are assembled together. To make this work, we need to put the + // formatting context into the same state like it would be when printing all at once, + // so here we print the section headers and clear the buffer to get into the right state. + 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) { + 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 ret; + } + + 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; + + if (!strcmp(print_graphs_file, "-")) { + printf("%s", target_buf.str); + } else { + 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) + 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 + +#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..fe4ee637e3 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 }, + "print filtergraph details to stderr" }, + { "print_graphs_file", OPT_TYPE_STRING, 0, + { &print_graphs_file }, + "write 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" }, diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c index 6c09f9d2cd..7f5bebfafa 100644 --- a/fftools/textformat/avtextformat.c +++ b/fftools/textformat/avtextformat.c @@ -398,10 +398,12 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st vali = vald; } - if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald)) + if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald)) { snprintf(buf, buf_size, "%f", vald); - else + } 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 : ""); } diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h index 4689499246..03754c760f 100644 --- a/fftools/textformat/avtextformat.h +++ b/fftools/textformat/avtextformat.h @@ -86,17 +86,17 @@ typedef struct AVTextFormatter { #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 + 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 + 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 nb_sections; ///< number of sections - int level; ///< current level, starting from 0 + 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]; @@ -155,7 +155,7 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name, const uint8_ 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, +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); -- 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".