Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Stefano Sabatini <stefasab@gmail.com>
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Cc: softworkz <softworkz@hotmail.com>
Subject: Re: [FFmpeg-devel] [PATCH v6 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing
Date: Tue, 11 Mar 2025 00:03:09 +0100
Message-ID: <Z89vrVVbF7+r1wia@mariano> (raw)
In-Reply-To: <8ae08a700697d4c6c350939896c6a75405057883.1741464994.git.ffmpegagent@gmail.com>

On date Saturday 2025-03-08 20:16:32 +0000, softworkz wrote:
> 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;

style: char *VAR here and below (also make sure to run patcheck in
case it spots other style issues)

>  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

2018?

> + *
> + * 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} },

more readable if you move indent this to the right to explain it is
not on the same level

> +    [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 },  },

MIXEDSTyLE is expected?

> +    [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 },  },

Why not Logs/Log? It's OK but I'd keep ID and names in synch to
simplify reading/editing.

> +};
> +
> +/* 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);

Is this even needed? If missing I'd expect type to be none. Or not?

> +    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;

let's not mix snakes and camels

> +
> +    avtext_print_section_header(w, NULL, SECTION_ID_HWFRAMESCONTEXT);
> +

> +    print_int("HasHwFramesContext", 1);

again, probably not needed since in this case you can deduct the info
from a missing hw/sw pixdesc?

> +    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);

is this meaningful in case of no context? Or should we rather skip them?

> +
> +    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;

I guess this does not exist yet right?

> +
> +        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);

development leftover?

> +            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);

weird reindent

> +    }
> +}
> +
> +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);

something more descriptive such as "class name"? 

> +        print_str("Description", filter->filter->description);
> +    }
> +
> +    if (filter->hw_device_ctx) {

> +        AVHWDeviceContext* decCtx = (AVHWDeviceContext*)filter->hw_device_ctx->data;

again, use snake_case for overall consistency

> +        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);

possibly unrelated, but I'm a bit surprised by the asymmetry

> +
> +        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)

mixed type*var styles

> +{
> +    char layoutString[64];

snake_case, here and below

> +    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);

ditto, although I'm a bit lost about what the thricely nested bird is

> +            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), "?"));

nit++: drop double space

> +            print_int("Width", ifilter->width);
> +            print_int("Height", ifilter->height);
> +            print_q("SAR", ifilter->sample_aspect_ratio, ':');
> +            break;

> +        case AVMEDIA_TYPE_AUDIO:
> +

weird empty line

> +            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);
> +        }

nit+: style if () { ... } else if () {...} should be slightly more
readable

> +
> +        avtext_print_section_footer(w); // SECTION_ID_INPUT
> +    }
> +
> +    avtext_print_section_footer(w); // SECTION_ID_INPUTS

> +
> +

drop double emtpy line

> +    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);

ditto, having more descriptive names would help user big times

> +            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);

yes but... this might be broken if you make assumption based on the
value (might change when bumping major) so should be probably drop

> +
> +        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), "?"));

double space

also you can probably use a function/macro to factorized duplicated pattern

> +            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);

ChannelLayout? We don't print other representations anyway so there is
no ambiguity here

> +            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;

style

> +            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) {

if (!text_formatter)
?

for consistencty with the line above

> +        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph  output format with name '%s'\n", w_name);

double space

> +        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);

Is this function already logging? If not it probably should, if yes
this is log is probably redundant.

> +        return AVERROR(EINVAL);

why not propagating the error?

> +    }
> +
> +    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);

If I understand this is printing the sections and then discarding the
generated buffer, right? Maybe add a not to explain why this is done
(I don't know...).

> +
> +        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);

double space

> +        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);

ditto

> +        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');

why it not hardcoding the newline in the message?

> +        }
> +
> +        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;

char *print_graphs_xxx = 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" },

prints -> print

why to stderr not stdout? that's what's generally exepected on unix*

> +    { "print_graphs_file", OPT_TYPE_STRING, 0,
> +        { &print_graphs_file },
> +        "writes graph details to a file", "filename" },

write graph ...

> +    { "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" },

non blocking but it would be good to avoid the hardcode in a case a
new format is added (or we'll skip update)

[...]

About the output style, this is using FooBar style against foo_bar
employed by ffprobe itself. I'm not against this, but I wonder if this
was considered and if using a more consistent style across tools is
helpful.

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

  reply	other threads:[~2025-03-10 23:03 UTC|newest]

Thread overview: 79+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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-10 21:47             ` Stefano Sabatini
2025-03-08 20:16           ` [FFmpeg-devel] [PATCH v6 5/8] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
2025-03-10 22:11             ` Stefano Sabatini
2025-03-08 20:16           ` [FFmpeg-devel] [PATCH v6 6/8] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
2025-03-10 23:03             ` Stefano Sabatini [this message]
2025-03-08 20:16           ` [FFmpeg-devel] [PATCH v6 7/8] fftools: Enable filtergraph printing and update docs softworkz
2025-03-10 23:04             ` Stefano Sabatini
2025-03-10 23:23               ` Soft Works
2025-03-08 20:16           ` [FFmpeg-devel] [PATCH v6 8/8] fftools/ffprobe: Rename AVTextFormatContext variables (w => tfc) softworkz
2025-03-10 21:46           ` [FFmpeg-devel] [PATCH v6 0/8] print_graphs: Complete Filtergraph Printing Stefano Sabatini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=Z89vrVVbF7+r1wia@mariano \
    --to=stefasab@gmail.com \
    --cc=ffmpeg-devel@ffmpeg.org \
    --cc=softworkz@hotmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

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