From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <ffmpeg-devel-bounces@ffmpeg.org>
Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100])
	by master.gitmailbox.com (Postfix) with ESMTPS id A3FCF4BB91
	for <ffmpegdev@gitmailbox.com>; Sat, 29 Mar 2025 05:27:30 +0000 (UTC)
Received: from [127.0.1.1] (localhost [127.0.0.1])
	by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 50399687C58;
	Sat, 29 Mar 2025 07:27:26 +0200 (EET)
Received: from mail-pj1-f51.google.com (mail-pj1-f51.google.com
 [209.85.216.51])
 by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 91E29687C36
 for <ffmpeg-devel@ffmpeg.org>; Sat, 29 Mar 2025 07:27:19 +0200 (EET)
Received: by mail-pj1-f51.google.com with SMTP id
 98e67ed59e1d1-301c4850194so3896935a91.2
 for <ffmpeg-devel@ffmpeg.org>; Fri, 28 Mar 2025 22:27:19 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1743226037; x=1743830837; darn=ffmpeg.org;
 h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date
 :message-id:from:from:to:cc:subject:date:message-id:reply-to;
 bh=/v4wsMzO61EVKUb2KN2C0Jf9XM9JUnjXe/XF9ZonVLQ=;
 b=FZDEHYK/AyTZpov+yWvkm0BMwuKXtEtIfW8WGNTiJ/mk11kguxjbotZBJQ2buX+V6W
 T+lGuVOKohR+/b2LFHZR+269Za7Ikphq2gkLxtt5eZox4qao5HHIsHk7eLwxhEUH4oc9
 BnKJObk7Sm87lQ6MHfALj/Rr47PbL3HiTWFEWZFqtHPcLk725RWalelJ++XSFvUvl9nT
 ohuYgE+cGIvY/6tdb5COh+J9butb0iQp9ZuH4OXsiBaAOoBdTpQ6IMezePFK2rWyMJi9
 F/6HPMYfXFwuAHKlZ9xB9fZqhOU3jrKpgtvHRpRAXYINirc7xlis08aPKdeJGla1Y1pw
 FKtQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1743226037; x=1743830837;
 h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date
 :message-id:from:x-gm-message-state:from:to:cc:subject:date
 :message-id:reply-to;
 bh=/v4wsMzO61EVKUb2KN2C0Jf9XM9JUnjXe/XF9ZonVLQ=;
 b=daSzF5OgSbAt8PA3PkVMShEn1OIqccuaYRhN2SlcHYm1cYDK5XWXcaWsPxyacWuR9z
 /JcbtZRSzQ3s8qjwT3MsouAPK7pHeGOnIQRwKFC+mKGDhwNCggSMgt54FdTdSbDH5zcw
 jHX+szFzPpI14cTApOaMcFKADB4gteHjk8gfXYKaZb/aJ0n0hkRhBAkwlfWcfofGCmfA
 TUr0vJWJzRvj4Z/BMmdHn1ma2VTi9ZkAjdT1PyJLf2mG7d8HOCF7mKGeiL4XEVYw9qev
 AbkqRoLhWtoHrf9vpFxqIXeJRLqKT64MB9FUiVbln3AgfjlRlRknpr7gDCv1S2QlR/Xz
 KUbw==
X-Gm-Message-State: AOJu0Yw4eGG98YbI5K47KuKsr4zTvg/I2plyrYNsurkCo9EBZxSH05Md
 7280+nENCyx810m6kM/chBPsp8O/lC/wUoQuRkRpFTp8auC+F+KA7VxbFA==
X-Gm-Gg: ASbGncuBJ3dUK2KgrD6nzTmixezgyncWD6Nm8wXiWVAUOMtlBNocI414EJHq2dEqa68
 ON7XHpZhhSQdtz4MegZW9Q0glNmvchE1vmLgBeHy7CmgWRafzLQo6cqblGGfXcySanGoTkmRzvk
 Be/rVMi5CWWv5beommCbNvtRDKL47iDdh9Y3miOojkiopV32cviuXkUd89+vHd/NNr49trinrFD
 ue+2AWPgSjr0Nu4fTV46pw9Wi+Sn6KFTkS3wUJTYRAY+8wo4OUkgUfBibtX6//2hKcGC0wc9/Rw
 qHsdg1V6lYYXtGr4riSumS8j4yMPfbbiaGplL6l8sE/X8D/MICKKt57cHDka3Z8=
X-Google-Smtp-Source: AGHT+IEaGJe1fLSNmTpDEtxdykdH/e2cQsHwptQBbvudkNxlbl6V5yfoxA4/QwfLOVFxhlCOtDk8ww==
X-Received: by 2002:a17:90b:1dc2:b0:304:ec28:4437 with SMTP id
 98e67ed59e1d1-3053215d195mr2409114a91.22.1743226037083; 
 Fri, 28 Mar 2025 22:27:17 -0700 (PDT)
Received: from [127.0.0.1] (master.gitmailbox.com. [34.83.118.50])
 by smtp.gmail.com with ESMTPSA id
 d9443c01a7336-2291f1cf88dsm27746875ad.119.2025.03.28.22.27.16
 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
 Fri, 28 Mar 2025 22:27:16 -0700 (PDT)
From: softworkz <ffmpegagent@gmail.com>
X-Google-Original-From: softworkz <softworkz@hotmail.com>
Message-Id: <pull.63.ffstaging.FFmpeg.1743226030871.ffmpegagent@gmail.com>
Date: Sat, 29 Mar 2025 05:27:10 +0000
Fcc: Sent
MIME-Version: 1.0
To: ffmpeg-devel@ffmpeg.org
Subject: [FFmpeg-devel] [PATCH] fftools/resorces: Add resource_manager with
 build-time compression
X-BeenThere: ffmpeg-devel@ffmpeg.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: FFmpeg development discussions and patches <ffmpeg-devel.ffmpeg.org>
List-Unsubscribe: <https://ffmpeg.org/mailman/options/ffmpeg-devel>,
 <mailto:ffmpeg-devel-request@ffmpeg.org?subject=unsubscribe>
List-Archive: <https://ffmpeg.org/pipermail/ffmpeg-devel>
List-Post: <mailto:ffmpeg-devel@ffmpeg.org>
List-Help: <mailto:ffmpeg-devel-request@ffmpeg.org?subject=help>
List-Subscribe: <https://ffmpeg.org/mailman/listinfo/ffmpeg-devel>,
 <mailto:ffmpeg-devel-request@ffmpeg.org?subject=subscribe>
Reply-To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Cc: softworkz <softworkz@hotmail.com>
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Errors-To: ffmpeg-devel-bounces@ffmpeg.org
Sender: "ffmpeg-devel" <ffmpeg-devel-bounces@ffmpeg.org>
Archived-At: <https://master.gitmailbox.com/ffmpegdev/pull.63.ffstaging.FFmpeg.1743226030871.ffmpegagent@gmail.com/>
List-Archive: <https://master.gitmailbox.com/ffmpegdev/>
List-Post: <mailto:ffmpegdev@gitmailbox.com>

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
    [RFC] fftools/resorces: Add resource_manager with build-time compression
    
    Hello everybody,
    
    this is a resource manager implementation which I've done for the
    upcoming execution graph rendering patchset, which needs some css and
    http strings for generating the output and I wanted check back whether
    it's okay to do it that way?
    
    To minimize the amount of storage required, it is using compression at
    build time, inspired from the PTX compresion that is already in employed
    for compresing CUDA kernels. The purpose is to allow having the actual
    content in the tree, viewable and editable as reeguar html or css files
    rather than a cryptic string that is handcrafted by the developer.
    
    For CSS files, it also applies some minification: Comment lines are
    removed, linebreaks removed and whitespace is folded, then it gets
    compressed and finally added as data via BIN2C. Same for html but
    without minification.
    
    In case of PTX compression there's a configure option to disable, hence
    I'm wondering whether I might need to do the same if there are any cases
    where it doesn't work?
    
    Thanks
    
    (note that the two css and html files are identical - it's just for
    illustration).

Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-63%2Fsoftworkz%2Fsubmit_resourcerfc-v1
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-63/softworkz/submit_resourcerfc-v1
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/63

 ffbuild/common.mak            |  24 ++-
 fftools/Makefile              |   4 +
 fftools/ffmpeg.c              |   3 +
 fftools/resources/.gitignore  |   6 +
 fftools/resources/Makefile    |  29 +++
 fftools/resources/graph.css   | 353 ++++++++++++++++++++++++++++++++++
 fftools/resources/graph.html  |  86 +++++++++
 fftools/resources/resman.c    | 195 +++++++++++++++++++
 fftools/resources/resman.h    |  51 +++++
 fftools/resources/schema.css  | 353 ++++++++++++++++++++++++++++++++++
 fftools/resources/schema.html |  86 +++++++++
 11 files changed, 1189 insertions(+), 1 deletion(-)
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h
 create mode 100644 fftools/resources/schema.css
 create mode 100644 fftools/resources/schema.html

diff --git a/ffbuild/common.mak b/ffbuild/common.mak
index ca45a0f368..023a93ae49 100644
--- a/ffbuild/common.mak
+++ b/ffbuild/common.mak
@@ -139,6 +139,28 @@ else
 	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
 endif
 
+# Minify CSS: Remove comment lines, remove line breaks, fold whitespace
+%.css.min: %.css
+	sed 's!/\\*.*\\*/!!g' $< \
+	| tr '\n' ' ' \
+	| tr -s ' ' \
+	| sed 's/^ //; s/ $$//' \
+	> $@
+
+%.css.min.gz: TAG = GZIP
+%.css.min.gz: %.css.min
+	$(M)gzip -nc9 $< > $@
+
+%.css.c: %.css.min.gz $(BIN2CEXE)
+	$(BIN2C) $< $@ $(subst .,_,$(basename $(notdir $@)))
+
+%.html.gz: TAG = GZIP
+%.html.gz: %.html
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) > $@
+
+%.html.c: %.html.gz $(BIN2CEXE)
+	$(BIN2C) $< $@ $(subst .,_,$(basename $(notdir $@)))
+
 clean::
 	$(RM) $(BIN2CEXE) $(CLEANSUFFIXES:%=ffbuild/%)
 
@@ -214,7 +236,7 @@ $(TOOLOBJS): | tools
 
 OUTDIRS := $(OUTDIRS) $(dir $(OBJS) $(HOBJS) $(HOSTOBJS) $(SLIBOBJS) $(SHLIBOBJS) $(STLIBOBJS) $(TESTOBJS))
 
-CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
+CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *.html.gz *.html.c *.css.min *.css.min.gz *.css.c  *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
 LIBSUFFIXES       = *.a *.lib *.so *.so.* *.dylib *.dll *.def *.dll.a
 
 define RULES
diff --git a/fftools/Makefile b/fftools/Makefile
index 4499799818..aa7c269ef9 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -9,6 +9,9 @@ AVBASENAMES  = ffmpeg ffplay ffprobe
 ALLAVPROGS   = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
 ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
 
+
+include $(SRC_PATH)/fftools/resources/Makefile
+
 OBJS-ffmpeg +=                  \
     fftools/ffmpeg_dec.o        \
     fftools/ffmpeg_demux.o      \
@@ -21,6 +24,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_sched.o      \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
+    $(OBJS-resman)              \
 
 OBJS-ffplay += fftools/ffplay_renderer.o
 
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..8c7d0c4449 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 "fftools/resources/resman.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -345,6 +346,8 @@ static void ffmpeg_cleanup(int ret)
 
     uninit_opts();
 
+    ff_resman_uninit();
+
     avformat_network_deinit();
 
     if (received_sigterm) {
diff --git a/fftools/resources/.gitignore b/fftools/resources/.gitignore
new file mode 100644
index 0000000000..b9af7c593f
--- /dev/null
+++ b/fftools/resources/.gitignore
@@ -0,0 +1,6 @@
+*.html.c
+*.css.c
+*.html.gz
+*.css.gz
+*.css.min.gz
+*.css.min
diff --git a/fftools/resources/Makefile b/fftools/resources/Makefile
new file mode 100644
index 0000000000..ee70c76207
--- /dev/null
+++ b/fftools/resources/Makefile
@@ -0,0 +1,29 @@
+clean::
+	$(RM) $(CLEANSUFFIXES:%=fftools/resources/%)
+
+
+HTML_RESOURCES := fftools/resources/graph.html \
+                  fftools/resources/schema.html \
+
+# .html => (gzip) .html.gz => (bin2c) .html.c => (cc) .o
+HTML_RESOURCES_GZ := $(HTML_RESOURCES:.html=.html.gz)
+HTML_RESOURCES_C := $(HTML_RESOURCES_GZ:.html.gz=.html.c)
+HTML_RESOURCES_OBJS := $(HTML_RESOURCES_C:.c=.o)
+
+CSS_RESOURCES := fftools/resources/graph.css   \
+                 fftools/resources/schema.css  \
+
+# .css => (sh) .css.min  => (gzip) .css.min.gz => (bin2c) .css.c => (cc) .o
+CSS_RESOURCES_MIN := $(CSS_RESOURCES:.css=.css.min)
+CSS_RESOURCES_GZ := $(CSS_RESOURCES_MIN:.css.min=.css.min.gz)
+CSS_RESOURCES_C := $(CSS_RESOURCES_GZ:.css.min.gz=.css.c)
+CSS_RESOURCES_OBJS := $(CSS_RESOURCES_C:.c=.o)
+
+# Uncomment to prevent deletion
+#.PRECIOUS: %.css.c %.css.min %.css.gz  %.css.min.gz
+
+OBJS-resman +=                  \
+    fftools/resources/resman.o          \
+    $(HTML_RESOURCES_OBJS)      \
+    $(CSS_RESOURCES_OBJS)       \
+
diff --git a/fftools/resources/graph.css b/fftools/resources/graph.css
new file mode 100644
index 0000000000..ab480673ab
--- /dev/null
+++ b/fftools/resources/graph.css
@@ -0,0 +1,353 @@
+/* Variables */
+.root {
+    --ff-colvideo: #6eaa7b;
+    --ff-colaudio: #477fb3;
+    --ff-colsubtitle: #ad76ab;
+    --ff-coltext: #666;
+}
+
+/* Common & Misc */
+.ff-inputfiles rect, .ff-outputfiles rect, .ff-inputstreams rect, .ff-outputstreams rect, .ff-decoders rect, .ff-encoders rect {
+    stroke-width: 0;
+    stroke: transparent;
+    filter: none !important;
+    fill: transparent !important;
+    display: none !important;
+}
+
+.cluster span {
+    color: var(--ff-coltext);
+}
+
+.cluster rect {
+    stroke: #dfdfdf !important;
+    transform: translateY(-2.3rem);
+    filter: drop-shadow(1px 2px 2px rgba(185,185,185,0.2)) !important;
+    rx: 8;
+    ry: 8;
+}
+
+.cluster-label {
+    font-size: 1.1rem;
+}
+
+    .cluster-label .nodeLabel {
+        display: block;
+        font-weight: 500;
+        color: var(--ff-coltext);
+    }
+
+    .cluster-label div {
+        max-width: unset !important;
+        padding: 3px;
+    }
+
+    .cluster-label foreignObject {
+        transform: translateY(-0.7rem);
+    }
+
+/* Input and output files */
+.node.ff-inputfile .label foreignObject, .node.ff-outputfile .label foreignObject {
+    overflow: visible;
+}
+
+.cluster.ff-inputfile .cluster-label foreignObject div:not(foreignObject div div), .cluster.ff-outputfile .cluster-label foreignObject div:not(foreignObject div div) {
+    display: table !important;
+}
+
+.nodeLabel div.ff-inputfile, .nodeLabel div.ff-outputfile {
+    font-size: 1.1rem;
+    font-weight: 500;
+    min-width: 14rem;
+    width: 100%;
+    display: flex;
+    color: var(--ff-coltext);
+    margin-top: 0.1rem;
+    line-height: 1.35;
+    padding-bottom: 1.9rem;
+}
+
+.nodeLabel div.ff-outputfile {
+    flex-direction: row-reverse;
+}
+
+.ff-inputfile .index, .ff-outputfile .index {
+    order: 2;
+    color: var(--ff-coltext);
+    text-align: center;
+    border-radius: 0.45rem;
+    border: 0.18em solid #666666db;
+    font-weight: 600;
+    padding: 0 0.3em;
+    opacity: 0.8;
+}
+
+    .ff-inputfile .index::before {
+        content: 'In ';
+    }
+
+    .ff-outputfile .index::before {
+        content: 'Out ';
+    }
+
+.ff-inputfile .demuxer_name, .ff-outputfile .muxer_name {
+    flex: 1;
+    order: 1;
+    font-size: 0.9rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: center;
+    max-width: 8rem;
+    align-content: center;
+    margin: 0.2rem 0.4rem 0 0.4rem;
+}
+
+.ff-inputfile .file_extension, .ff-outputfile .file_extension {
+    order: 0;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.45rem;
+    font-weight: 600;
+    padding: 0 0.4em;
+    align-content: center;
+    opacity: 0.8;
+}
+
+.ff-inputfile .url, .ff-outputfile .url {
+    order: 4;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0.75rem;
+    font-size: 0.7rem;
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin: 0 0.3rem;
+    direction: rtl;
+    color: #999;
+}
+
+.cluster.ff-inputfile rect, .cluster.ff-outputfile rect {
+    transform: translateY(-1.8rem);
+    fill: url(#ff-radgradient);
+}
+
+/* Input and output streams */
+.node.ff-inputstream rect, .node.ff-outputstream rect {
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    fill: white;
+    stroke: #e5e5e5 !important;
+    height: 2.7rem;
+    transform: translateY(0.2rem);
+    filter: none;
+    rx: 3;
+    ry: 3;
+}
+
+.node.ff-inputstream .label foreignObject, .node.ff-outputstream .label foreignObject {
+    transform: translateY(-0.2%);
+    overflow: visible;
+}
+
+    .node.ff-inputstream .label foreignObject div:not(foreignObject div div), .node.ff-outputstream .label foreignObject div:not(foreignObject div div) {
+        display: block !important;
+        line-height: 1.5 !important;
+    }
+
+.nodeLabel div.ff-inputstream, .nodeLabel div.ff-outputstream {
+    font-size: 1.0rem;
+    font-weight: 500;
+    min-width: 12rem;
+    width: 100%;
+    display: flex;
+}
+
+.nodeLabel div.ff-outputstream {
+    flex-direction: row-reverse;
+}
+
+.ff-inputstream .name, .ff-outputstream .name {
+    flex: 1;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: left;
+    align-content: center;
+    margin-bottom: -0.15rem;
+}
+
+.ff-inputstream .index, .ff-outputstream .index {
+    flex: 0 0 1.4rem;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.3rem;
+    font-weight: 600;
+    margin-right: -0.3rem;
+    margin-left: 0.4rem;
+    opacity: 0.8;
+}
+
+.ff-outputstream .index {
+    margin-right: 0.6rem;
+    margin-left: -0.4rem;
+}
+
+.ff-inputstream::before, .ff-outputstream::before {
+    font-variant-emoji: text;
+    flex: 0 0 2rem;
+    margin-left: -0.8rem;
+    margin-right: 0.2rem;
+}
+
+.ff-outputstream::before {
+    margin-left: 0.2rem;
+    margin-right: -0.6rem;
+}
+
+.ff-inputstream.video::before, .ff-outputstream.video::before {
+    content: '\239A';
+    color: var(--ff-colvideo);
+    font-size: 2.25rem;
+    line-height: 0.5;
+    font-weight: bold;
+}
+
+.ff-inputstream.audio::before, .ff-outputstream.audio::before {
+    content: '\1F39D';
+    color: var(--ff-colaudio);
+    font-size: 1.75rem;
+    line-height: 0.9;
+}
+
+.ff-inputstream.subtitle::before, .ff-outputstream.subtitle::before {
+    content: '\1AC';
+    color: var(--ff-colsubtitle);
+    font-size: 1.2rem;
+    line-height: 1.1;
+    transform: scaleX(1.5);
+    margin-top: 0.050rem;
+}
+
+.ff-inputstream.attachment::before, .ff-outputstream.attachment::before {
+    content: '\1F4CE';
+    font-size: 1.3rem;
+    line-height: 1.15;
+}
+
+.ff-inputstream.data::before, .ff-outputstream.data::before {
+    content: '\27E8\2219\2219\2219\27E9';
+    font-size: 1.15rem;
+    line-height: 1.17;
+    letter-spacing: -0.3px;
+}
+
+/* Filter Graphs */
+.cluster.ff-filters rect {
+    stroke-dasharray: 6 !important;
+    stroke-width: 1.3px;
+    stroke: #d1d1d1 !important;
+    filter: none !important;
+}
+
+.cluster.ff-filters div.ff-filters .id {
+    display: none;
+}
+
+.cluster.ff-filters div.ff-filters .name {
+    margin-right: 0.5rem;
+    font-size: 0.9rem;
+}
+
+.cluster.ff-filters div.ff-filters .description {
+    font-weight: 400;
+    font-size: 0.75rem;
+    vertical-align: middle;
+    color: #777;
+    font-family: Cascadia Code, Lucida Console, monospace;
+}
+
+/* Filter Shapes */
+.node.ff-filter rect {
+    rx: 10;
+    ry: 10;
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.node.ff-filter .label foreignObject {
+    transform: translateY(-0.4rem);
+    overflow: visible;
+}
+
+.nodeLabel div.ff-filter {
+    font-size: 1.0rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    min-width: 5.5rem;
+    margin-bottom: 0.5rem;
+}
+
+    .nodeLabel div.ff-filter span {
+        color: inherit;
+    }
+
+/* Decoders & Encoders */
+.node.ff-decoder rect, .node.ff-encoder rect {
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.nodeLabel div.ff-decoder, .nodeLabel div.ff-encoder {
+    font-size: 0.85rem;
+    font-weight: 500;
+    min-width: 3.5rem;
+}
+
+/* Links and Arrows */
+path.flowchart-link[id|='video'] {
+    stroke: var(--ff-colvideo);
+}
+
+path.flowchart-link[id|='audio'] {
+    stroke: var(--ff-colaudio);
+}
+
+path.flowchart-link[id|='subtitle'] {
+    stroke: var(--ff-colsubtitle);
+}
+
+marker.marker path {
+    fill: context-stroke;
+}
+
+.edgeLabel foreignObject {
+    transform: translateY(-1rem);
+}
+
+.edgeLabel p {
+    background: transparent;
+    white-space: nowrap;
+    margin: 1rem 0.5rem !important;
+    font-weight: 500;
+    color: var(--ff-coltext);
+}
+
+.edgeLabel, .labelBkg {
+    background: transparent;
+}
+
+.edgeLabels .edgeLabel * {
+    font-size: 0.8rem;
+}
diff --git a/fftools/resources/graph.html b/fftools/resources/graph.html
new file mode 100644
index 0000000000..cd94276fd4
--- /dev/null
+++ b/fftools/resources/graph.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <title>FFmpeg Graph</title>
+</head>
+<body>
+<style>
+    html {
+        color: #666;
+        font-family: Roboto;
+        height: 100%;
+    }
+
+    body {
+        background-color: #f9f9f9;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        margin: 0;
+        padding: 1.7rem 1.7rem 3.5rem 1.7rem;
+    }
+
+    div#banner {
+        align-items: center;
+        display: flex;
+        flex-direction: row;
+        margin-bottom: 1.5rem;
+        margin-left: 0.6vw;
+    }
+
+    div#header {
+        aspect-ratio: 1/1;
+        background-image: url(https://trac.ffmpeg.org/ffmpeg-logo.png);
+        background-size: cover;
+        width: 1.6rem;
+    }
+
+    h1 {
+        font-size: 1.2rem;
+        margin: 0 0.5rem;
+    }
+
+    pre.mermaid {
+        align-items: center;
+        background-color: white;
+        box-shadow: 2px 2px 25px 0px #00000010;
+        color: transparent;
+        display: flex;
+        flex: 1;
+        justify-content: center;
+        margin: 0;
+        overflow: overlay;
+    }
+
+    pre.mermaid svg {
+        height: auto;
+        margin: 0;
+        max-width: unset !important;
+        width: auto;
+    }
+
+    pre.mermaid svg * {
+        user-select: none;
+    }
+</style>
+<div id="banner">
+    <div id="header"></div>
+    <h1>FFmpeg Execution Graph</h1>
+</div>
+<pre class="mermaid">
+__###__
+</pre>
+<script type="module">
+        import vanillaJsWheelZoom from 'https://cdn.jsdelivr.net/npm/vanilla-js-wheel-zoom@9.0.4/+esm';
+        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
+        function initViewer() {
+            var element = document.querySelector('.mermaid svg')
+            vanillaJsWheelZoom.create('pre.mermaid svg', { type: 'html', smoothTimeDrag: 0, width: element.clientWidth, height: element.clientHeight, maxScale: 3 });
+        }
+        mermaid.initialize({ startOnLoad: false }); 
+        document.fonts.ready.then(() => { mermaid.run({ querySelector: '.mermaid', postRenderCallback: initViewer }); });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/fftools/resources/resman.c b/fftools/resources/resman.c
new file mode 100644
index 0000000000..a55ff8bf66
--- /dev/null
+++ b/fftools/resources/resman.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <zlib.h>
+#include "resman.h"
+#include "libavutil/avassert.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+
+extern const unsigned char ff_graph_html_data[];
+extern const unsigned int  ff_graph_html_len;
+
+extern const unsigned char ff_graph_css_data[];
+extern const unsigned ff_graph_css_len;
+
+extern const unsigned char ff_schema_css_data[];
+extern const unsigned ff_schema_css_len;
+
+extern const unsigned char ff_schema_html_data[];
+extern const unsigned ff_schema_html_len;
+
+static const FFResourceDefinition resource_definitions[] = {
+    [FF_RESOURCE_GRAPH_CSS]   = { FF_RESOURCE_GRAPH_CSS,   "graph.css",   &ff_graph_css_data[0],   &ff_graph_css_len },
+    [FF_RESOURCE_GRAPH_HTML]  = { FF_RESOURCE_GRAPH_HTML,  "graph.html",  &ff_graph_html_data[0],  &ff_graph_html_len },
+    [FF_RESOURCE_SCHEMA_CSS]  = { FF_RESOURCE_SCHEMA_CSS,  "schema.css",  &ff_schema_css_data[0],  &ff_schema_css_len },
+    [FF_RESOURCE_SCHEMA_HTML] = { FF_RESOURCE_SCHEMA_HTML, "schema.html", &ff_schema_html_data[0], &ff_schema_html_len },
+};
+
+
+static const AVClass resman_class = {
+    .class_name = "ResourceManager",
+};
+
+typedef struct ResourceManagerContext {
+    const AVClass *class;
+    AVDictionary *resource_dic;
+} ResourceManagerContext;
+
+static AVMutex mutex = AV_MUTEX_INITIALIZER;
+
+ResourceManagerContext *resman_ctx = NULL;
+
+
+static int decompress_gzip(ResourceManagerContext *ctx, uint8_t *in, unsigned in_len, char **out, size_t *out_len)
+{
+    z_stream strm;
+    unsigned chunk = 65536;
+    int ret;
+
+    memset(&strm, 0, sizeof(strm));
+
+    // 15 + 16: auto-detect GZIP or zlib
+    uint8_t *buf = (uint8_t *)av_mallocz(chunk + 1);
+    if (!buf)
+        return AVERROR(ENOMEM);
+
+    // 15 + 16 tells zlib to detect GZIP or zlib automatically
+    ret = inflateInit2(&strm, 15 + 16);
+    if (ret != Z_OK) {
+        av_log(ctx, AV_LOG_ERROR, "Error during zlib initialisation: %s\n", strm.msg);
+        av_free(buf);
+        return AVERROR(ENOSYS);
+    }
+
+    strm.avail_in = in_len;
+    strm.next_in  = in;
+    strm.avail_out = chunk;
+    strm.next_out  = buf;
+
+    ret = inflate(&strm, Z_FINISH);
+    if (ret != Z_OK && ret != Z_STREAM_END) {
+        av_log(ctx, AV_LOG_ERROR, "inflate return value: %d, %s\n", ret, strm.msg);
+        inflateEnd(&strm);
+        av_free(buf);
+        return (ret == Z_OK) ? Z_BUF_ERROR : ret;
+    }
+
+    *out_len = chunk - strm.avail_out;
+    buf[*out_len] = 0;
+
+    inflateEnd(&strm);
+    *out = (char *)buf;
+    return Z_OK;
+}
+
+static ResourceManagerContext *get_resman_context(void)
+{
+    ResourceManagerContext *res = resman_ctx;
+
+    ff_mutex_lock(&mutex);
+
+    if (res)
+        goto end;;
+
+    res = resman_ctx = av_mallocz(sizeof(ResourceManagerContext));
+
+    if (resman_ctx)
+        resman_ctx->class = &resman_class;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
+
+
+void ff_resman_uninit(void)
+{
+    ff_mutex_lock(&mutex);
+
+    if (resman_ctx) {
+        if (resman_ctx->resource_dic)
+            av_dict_free(&resman_ctx->resource_dic);
+        av_freep(&resman_ctx);
+    }
+
+    ff_mutex_unlock(&mutex);
+}
+
+
+char *ff_resman_get_string(FFResourceId resource_id)
+{
+    ResourceManagerContext *ctx = get_resman_context();
+    FFResourceDefinition *resource_definition = NULL;
+    AVDictionaryEntry *dic_entry;
+    char *res = NULL;
+
+    if (!ctx)
+        return NULL;
+
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(resource_definitions); ++i) {
+        FFResourceDefinition def = resource_definitions[i];
+        if (def.resource_id == resource_id) {
+            resource_definition = &def;
+            break;
+        }
+    }
+
+    if (!resource_definition) {
+        av_log(ctx, AV_LOG_ERROR, "Unable to find resource with ID %d\n", resource_id);
+        return NULL;
+    }
+
+    ff_mutex_lock(&mutex);
+
+    dic_entry = av_dict_get(ctx->resource_dic, resource_definition->name, NULL, 0);
+
+    if (!dic_entry) {
+        char *out;
+        size_t out_len;
+
+        int ret = decompress_gzip(ctx, (uint8_t *)resource_definition->data, *resource_definition->data_len, &out, &out_len);;
+
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to decompress the resource with ID %d\n", resource_id);
+            goto end;
+        }
+
+        av_dict_set(&ctx->resource_dic, resource_definition->name, out, 0);
+        av_freep(&out);
+
+        dic_entry = av_dict_get(ctx->resource_dic, resource_definition->name, NULL, 0);
+    }
+
+    res = dic_entry->value;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
+
diff --git a/fftools/resources/resman.h b/fftools/resources/resman.h
new file mode 100644
index 0000000000..e162b0502f
--- /dev/null
+++ b/fftools/resources/resman.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2025 - 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_RESOURCES_RESMAN_H
+#define FFTOOLS_RESOURCES_RESMAN_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "fftools/ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+
+typedef enum {
+    FF_RESOURCE_GRAPH_CSS,
+    FF_RESOURCE_GRAPH_HTML,
+    FF_RESOURCE_SCHEMA_CSS,
+    FF_RESOURCE_SCHEMA_HTML,
+} FFResourceId;
+
+typedef struct FFResourceDefinition {
+    FFResourceId resource_id;
+    const char *name;
+
+    const unsigned char *data;
+    const unsigned *data_len;
+
+} FFResourceDefinition;
+
+void ff_resman_uninit(void);
+
+char *ff_resman_get_string(FFResourceId resource_id);
+
+#endif /* FFTOOLS_RESOURCES_RESMAN_H */
diff --git a/fftools/resources/schema.css b/fftools/resources/schema.css
new file mode 100644
index 0000000000..ab480673ab
--- /dev/null
+++ b/fftools/resources/schema.css
@@ -0,0 +1,353 @@
+/* Variables */
+.root {
+    --ff-colvideo: #6eaa7b;
+    --ff-colaudio: #477fb3;
+    --ff-colsubtitle: #ad76ab;
+    --ff-coltext: #666;
+}
+
+/* Common & Misc */
+.ff-inputfiles rect, .ff-outputfiles rect, .ff-inputstreams rect, .ff-outputstreams rect, .ff-decoders rect, .ff-encoders rect {
+    stroke-width: 0;
+    stroke: transparent;
+    filter: none !important;
+    fill: transparent !important;
+    display: none !important;
+}
+
+.cluster span {
+    color: var(--ff-coltext);
+}
+
+.cluster rect {
+    stroke: #dfdfdf !important;
+    transform: translateY(-2.3rem);
+    filter: drop-shadow(1px 2px 2px rgba(185,185,185,0.2)) !important;
+    rx: 8;
+    ry: 8;
+}
+
+.cluster-label {
+    font-size: 1.1rem;
+}
+
+    .cluster-label .nodeLabel {
+        display: block;
+        font-weight: 500;
+        color: var(--ff-coltext);
+    }
+
+    .cluster-label div {
+        max-width: unset !important;
+        padding: 3px;
+    }
+
+    .cluster-label foreignObject {
+        transform: translateY(-0.7rem);
+    }
+
+/* Input and output files */
+.node.ff-inputfile .label foreignObject, .node.ff-outputfile .label foreignObject {
+    overflow: visible;
+}
+
+.cluster.ff-inputfile .cluster-label foreignObject div:not(foreignObject div div), .cluster.ff-outputfile .cluster-label foreignObject div:not(foreignObject div div) {
+    display: table !important;
+}
+
+.nodeLabel div.ff-inputfile, .nodeLabel div.ff-outputfile {
+    font-size: 1.1rem;
+    font-weight: 500;
+    min-width: 14rem;
+    width: 100%;
+    display: flex;
+    color: var(--ff-coltext);
+    margin-top: 0.1rem;
+    line-height: 1.35;
+    padding-bottom: 1.9rem;
+}
+
+.nodeLabel div.ff-outputfile {
+    flex-direction: row-reverse;
+}
+
+.ff-inputfile .index, .ff-outputfile .index {
+    order: 2;
+    color: var(--ff-coltext);
+    text-align: center;
+    border-radius: 0.45rem;
+    border: 0.18em solid #666666db;
+    font-weight: 600;
+    padding: 0 0.3em;
+    opacity: 0.8;
+}
+
+    .ff-inputfile .index::before {
+        content: 'In ';
+    }
+
+    .ff-outputfile .index::before {
+        content: 'Out ';
+    }
+
+.ff-inputfile .demuxer_name, .ff-outputfile .muxer_name {
+    flex: 1;
+    order: 1;
+    font-size: 0.9rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: center;
+    max-width: 8rem;
+    align-content: center;
+    margin: 0.2rem 0.4rem 0 0.4rem;
+}
+
+.ff-inputfile .file_extension, .ff-outputfile .file_extension {
+    order: 0;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.45rem;
+    font-weight: 600;
+    padding: 0 0.4em;
+    align-content: center;
+    opacity: 0.8;
+}
+
+.ff-inputfile .url, .ff-outputfile .url {
+    order: 4;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0.75rem;
+    font-size: 0.7rem;
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin: 0 0.3rem;
+    direction: rtl;
+    color: #999;
+}
+
+.cluster.ff-inputfile rect, .cluster.ff-outputfile rect {
+    transform: translateY(-1.8rem);
+    fill: url(#ff-radgradient);
+}
+
+/* Input and output streams */
+.node.ff-inputstream rect, .node.ff-outputstream rect {
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    fill: white;
+    stroke: #e5e5e5 !important;
+    height: 2.7rem;
+    transform: translateY(0.2rem);
+    filter: none;
+    rx: 3;
+    ry: 3;
+}
+
+.node.ff-inputstream .label foreignObject, .node.ff-outputstream .label foreignObject {
+    transform: translateY(-0.2%);
+    overflow: visible;
+}
+
+    .node.ff-inputstream .label foreignObject div:not(foreignObject div div), .node.ff-outputstream .label foreignObject div:not(foreignObject div div) {
+        display: block !important;
+        line-height: 1.5 !important;
+    }
+
+.nodeLabel div.ff-inputstream, .nodeLabel div.ff-outputstream {
+    font-size: 1.0rem;
+    font-weight: 500;
+    min-width: 12rem;
+    width: 100%;
+    display: flex;
+}
+
+.nodeLabel div.ff-outputstream {
+    flex-direction: row-reverse;
+}
+
+.ff-inputstream .name, .ff-outputstream .name {
+    flex: 1;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: left;
+    align-content: center;
+    margin-bottom: -0.15rem;
+}
+
+.ff-inputstream .index, .ff-outputstream .index {
+    flex: 0 0 1.4rem;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.3rem;
+    font-weight: 600;
+    margin-right: -0.3rem;
+    margin-left: 0.4rem;
+    opacity: 0.8;
+}
+
+.ff-outputstream .index {
+    margin-right: 0.6rem;
+    margin-left: -0.4rem;
+}
+
+.ff-inputstream::before, .ff-outputstream::before {
+    font-variant-emoji: text;
+    flex: 0 0 2rem;
+    margin-left: -0.8rem;
+    margin-right: 0.2rem;
+}
+
+.ff-outputstream::before {
+    margin-left: 0.2rem;
+    margin-right: -0.6rem;
+}
+
+.ff-inputstream.video::before, .ff-outputstream.video::before {
+    content: '\239A';
+    color: var(--ff-colvideo);
+    font-size: 2.25rem;
+    line-height: 0.5;
+    font-weight: bold;
+}
+
+.ff-inputstream.audio::before, .ff-outputstream.audio::before {
+    content: '\1F39D';
+    color: var(--ff-colaudio);
+    font-size: 1.75rem;
+    line-height: 0.9;
+}
+
+.ff-inputstream.subtitle::before, .ff-outputstream.subtitle::before {
+    content: '\1AC';
+    color: var(--ff-colsubtitle);
+    font-size: 1.2rem;
+    line-height: 1.1;
+    transform: scaleX(1.5);
+    margin-top: 0.050rem;
+}
+
+.ff-inputstream.attachment::before, .ff-outputstream.attachment::before {
+    content: '\1F4CE';
+    font-size: 1.3rem;
+    line-height: 1.15;
+}
+
+.ff-inputstream.data::before, .ff-outputstream.data::before {
+    content: '\27E8\2219\2219\2219\27E9';
+    font-size: 1.15rem;
+    line-height: 1.17;
+    letter-spacing: -0.3px;
+}
+
+/* Filter Graphs */
+.cluster.ff-filters rect {
+    stroke-dasharray: 6 !important;
+    stroke-width: 1.3px;
+    stroke: #d1d1d1 !important;
+    filter: none !important;
+}
+
+.cluster.ff-filters div.ff-filters .id {
+    display: none;
+}
+
+.cluster.ff-filters div.ff-filters .name {
+    margin-right: 0.5rem;
+    font-size: 0.9rem;
+}
+
+.cluster.ff-filters div.ff-filters .description {
+    font-weight: 400;
+    font-size: 0.75rem;
+    vertical-align: middle;
+    color: #777;
+    font-family: Cascadia Code, Lucida Console, monospace;
+}
+
+/* Filter Shapes */
+.node.ff-filter rect {
+    rx: 10;
+    ry: 10;
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.node.ff-filter .label foreignObject {
+    transform: translateY(-0.4rem);
+    overflow: visible;
+}
+
+.nodeLabel div.ff-filter {
+    font-size: 1.0rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    min-width: 5.5rem;
+    margin-bottom: 0.5rem;
+}
+
+    .nodeLabel div.ff-filter span {
+        color: inherit;
+    }
+
+/* Decoders & Encoders */
+.node.ff-decoder rect, .node.ff-encoder rect {
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.nodeLabel div.ff-decoder, .nodeLabel div.ff-encoder {
+    font-size: 0.85rem;
+    font-weight: 500;
+    min-width: 3.5rem;
+}
+
+/* Links and Arrows */
+path.flowchart-link[id|='video'] {
+    stroke: var(--ff-colvideo);
+}
+
+path.flowchart-link[id|='audio'] {
+    stroke: var(--ff-colaudio);
+}
+
+path.flowchart-link[id|='subtitle'] {
+    stroke: var(--ff-colsubtitle);
+}
+
+marker.marker path {
+    fill: context-stroke;
+}
+
+.edgeLabel foreignObject {
+    transform: translateY(-1rem);
+}
+
+.edgeLabel p {
+    background: transparent;
+    white-space: nowrap;
+    margin: 1rem 0.5rem !important;
+    font-weight: 500;
+    color: var(--ff-coltext);
+}
+
+.edgeLabel, .labelBkg {
+    background: transparent;
+}
+
+.edgeLabels .edgeLabel * {
+    font-size: 0.8rem;
+}
diff --git a/fftools/resources/schema.html b/fftools/resources/schema.html
new file mode 100644
index 0000000000..cd94276fd4
--- /dev/null
+++ b/fftools/resources/schema.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <title>FFmpeg Graph</title>
+</head>
+<body>
+<style>
+    html {
+        color: #666;
+        font-family: Roboto;
+        height: 100%;
+    }
+
+    body {
+        background-color: #f9f9f9;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        margin: 0;
+        padding: 1.7rem 1.7rem 3.5rem 1.7rem;
+    }
+
+    div#banner {
+        align-items: center;
+        display: flex;
+        flex-direction: row;
+        margin-bottom: 1.5rem;
+        margin-left: 0.6vw;
+    }
+
+    div#header {
+        aspect-ratio: 1/1;
+        background-image: url(https://trac.ffmpeg.org/ffmpeg-logo.png);
+        background-size: cover;
+        width: 1.6rem;
+    }
+
+    h1 {
+        font-size: 1.2rem;
+        margin: 0 0.5rem;
+    }
+
+    pre.mermaid {
+        align-items: center;
+        background-color: white;
+        box-shadow: 2px 2px 25px 0px #00000010;
+        color: transparent;
+        display: flex;
+        flex: 1;
+        justify-content: center;
+        margin: 0;
+        overflow: overlay;
+    }
+
+    pre.mermaid svg {
+        height: auto;
+        margin: 0;
+        max-width: unset !important;
+        width: auto;
+    }
+
+    pre.mermaid svg * {
+        user-select: none;
+    }
+</style>
+<div id="banner">
+    <div id="header"></div>
+    <h1>FFmpeg Execution Graph</h1>
+</div>
+<pre class="mermaid">
+__###__
+</pre>
+<script type="module">
+        import vanillaJsWheelZoom from 'https://cdn.jsdelivr.net/npm/vanilla-js-wheel-zoom@9.0.4/+esm';
+        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
+        function initViewer() {
+            var element = document.querySelector('.mermaid svg')
+            vanillaJsWheelZoom.create('pre.mermaid svg', { type: 'html', smoothTimeDrag: 0, width: element.clientWidth, height: element.clientHeight, maxScale: 3 });
+        }
+        mermaid.initialize({ startOnLoad: false }); 
+        document.fonts.ready.then(() => { mermaid.run({ querySelector: '.mermaid', postRenderCallback: initViewer }); });
+    </script>
+</body>
+</html>
\ No newline at end of file

base-commit: 5fac8d062d2bfe74b8844dd2538137b087b985e5
-- 
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".