PR #20898 opened by Ayose C. (ayosec)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20898
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20898.patch


### Problem

FFmpeg can be used with no inputs. For example, the command

```bash
$ ffmpeg -filter_complex color=blue:d=1 /tmp/blue.mp4
```

creates a 1-second video with a solid color.

When the `-print_graphs_format mermaid` (or `mermaidhtml`) is used on a command 
like that, the `subgraph` section for `ff-inputfiles` and `ff-decoders` is 
incomplete:

```
subgraph G1_Inputs["<div class="ff-inputfiles">  end
class G1_Inputs ff-inputfiles


subgraph G1_Decoders["<div class="ff-decoders">  end
class G1_Decoders ff-decoders
```

Opening the generated file in a browser throws this error:

```
Parse error on line 26:
...subgraph G1_Encoders["<div class='ff-enc
-----------------------^
Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND',
'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND',
'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'SQS'
```

(`G1_Encoders` is right after `G1_Decoders`)

The mermaid code is missing the `</div>"]` fragment to complete the `subgraph` 
line.

### Cause

The fragment to complete the `subgraph` header [is written in 
`mermaid_print_section_header`](https://code.ffmpeg.org/FFmpeg/FFmpeg/src/commit/418235e98a42681a7f243ba6de8f7e9284a677b5/fftools/textformat/tf_mermaid.c#L303-L312):

```c
static void mermaid_print_section_header(AVTextFormatContext *tfc, const void 
*data)
{
    // ...
    if (parent_section && parent_section->flags & 
AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
        // ...
        if (parent_sec_data.subgraph_start_incomplete) {
            // ...
            writer_put_str(tfc, "</div>\"]\n");
            mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
        }
    }

    // ...

    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
        // ...
        writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", 
sec_ctx->context_id, section->name);

        mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
```


The function is invoked from 
[`print_filter`](https://code.ffmpeg.org/FFmpeg/FFmpeg/src/commit/418235e98a42681a7f243ba6de8f7e9284a677b5/fftools/graph/graphprint.c#L406-L436).
 It assumes that the `header` function is called at least once before the 
footer, but this is not the case when there are no inputs:

```c
avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);

for (unsigned i = 0; i < filter->nb_inputs; i++) {
    // ...
    avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
    // ...
}

avtext_print_section_footer(tfc);
```


### Fix

The field `subgraph_start_incomplete` tracks if the `subgraph` is line is 
completed or no. So I moved the code to complete it a function, which is called 
from the original point (inside the `mermaid_print_section_header`) and from 
`mermaid_print_section_footer`.


### Test

Running the following command:

```bash
ffmpeg \
    -print_graphs \
    -print_graphs_format mermaidhtml \
    -print_graphs_file /tmp/after.html 
    -filter_complex color \
    -frames:v 1 \
    -f null -
```

Before/after the fix shows the expected diff:

```diff
--- /tmp/before.html
+++ /tmp/after.html
@@ -84,11 +84,13 @@
   G0_Parsed_color_0 video-G0_Parsed_color_0-out__0_0@== 
"<span>yuv420p</span><br><span>320x240</span><br><span>1:1</span><br>&nbsp;<br>&nbsp;<br>&nbsp;"
 ==> out__0_0


-  subgraph G1_Inputs["<div class="ff-inputfiles">  end
+  subgraph G1_Inputs["<div class="ff-inputfiles"></div>"]
+  end
   class G1_Inputs ff-inputfiles


-  subgraph G1_Decoders["<div class="ff-decoders">  end
+  subgraph G1_Decoders["<div class="ff-decoders"></div>"]
+  end
   class G1_Decoders ff-decoders
```

The `after.html` also loads correctly:

![image](/attachments/7509c2cd-6000-45ed-8ea3-3ed1fb7a0230)

I didn't find FATE tests related to the mermaid generation, but I did a 
before/after comparison with multiple filtergraphs, and in all cases the only 
difference is the fix.



>From f21ddefb50ca169c90ff2e37cea228e7866d285b Mon Sep 17 00:00:00 2001
From: Ayose <[email protected]>
Date: Wed, 12 Nov 2025 07:21:23 +0000
Subject: [PATCH] fftools/tf_mermaid: close subgraph header when there are no
 inputs.

Ensure that the fragment to close the header (`</div>\"]`) is written when the
function `mermaid_print_section_header` is called only once, which happens when
the filtergraph has no inputs.

Signed-off-by: Ayose <[email protected]>
---
 fftools/textformat/tf_mermaid.c | 31 ++++++++++++++++++-------------
 1 file changed, 18 insertions(+), 13 deletions(-)

diff --git a/fftools/textformat/tf_mermaid.c b/fftools/textformat/tf_mermaid.c
index ef730d570b..fdd4ab7e57 100644
--- a/fftools/textformat/tf_mermaid.c
+++ b/fftools/textformat/tf_mermaid.c
@@ -240,6 +240,21 @@ static void set_str(const char **dst, const char *src)
         *dst = av_strdup(src);
 }
 
+static void mermaid_subgraph_complete_start(MermaidContext *mmc, 
AVTextFormatContext *tfc, int level) {
+    struct section_data parent_sec_data = mmc->section_data[level];
+    AVBPrint *parent_buf = &tfc->section_pbuf[level];
+
+    if (parent_sec_data.subgraph_start_incomplete) {
+
+        if (parent_buf->len > 0)
+            writer_printf(tfc, "%s", parent_buf->str);
+
+        writer_put_str(tfc, "</div>\"]\n");
+
+        mmc->section_data[level].subgraph_start_incomplete = 0;
+    }
+}
+
 #define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
 
 static void mermaid_print_section_header(AVTextFormatContext *tfc, const void 
*data)
@@ -296,19 +311,7 @@ static void 
mermaid_print_section_header(AVTextFormatContext *tfc, const void *d
     }
 
     if (parent_section && parent_section->flags & 
AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
-
-        struct section_data parent_sec_data = mmc->section_data[tfc->level - 
1];
-        AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
-
-        if (parent_sec_data.subgraph_start_incomplete) {
-
-            if (parent_buf->len > 0)
-                writer_printf(tfc, "%s", parent_buf->str);
-
-            writer_put_str(tfc, "</div>\"]\n");
-
-            mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
-        }
+        mermaid_subgraph_complete_start(mmc, tfc, tfc->level - 1);
     }
 
     av_freep(&mmc->section_data[tfc->level].section_id);
@@ -454,6 +457,8 @@ static void 
mermaid_print_section_footer(AVTextFormatContext *tfc)
 
     } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
 
+        mermaid_subgraph_complete_start(mmc, tfc, tfc->level);
+
         MM_INDENT();
         writer_put_str(tfc, "end\n");
 
-- 
2.49.1

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to