1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ffmpegのfilter_complexをGraphviz(dot)で見える化する Pythonスクリプト

Posted at

ffmpeg-pythonを使うと、各種動画生成が簡単に行えます。
このモジュールは動画処理を外部のffmpegプログラムに任せており、ffmpeg向けの命令群を構築して、ffmpeg.runでffmpegに投げています。

ffmpeg.get_args()でffmpegに渡す引数をダンプできます。
このうち-filter_complexで渡しているのが、中核処理のコマンド(filter)です。

数百種類あります。
FFmpeg Filters Documentation

これらのコマンドは、「入力、処理、結果」というシンプルな形式になっています。

引数ダンプを見てもデータの流れが分かりづらかったので、Graphvizのdot言語に変換する Pythonスクリプトを書きました。
入力と出力先を指定するシンプルなI/Fです。

filter2dot.py
#!/usr/bin/env python3
import argparse
import re
import sys


def main(argv: list[str]) -> int:
    # parse argument
    parser = argparse.ArgumentParser(description="")
    parser.add_argument("file")
    parser.add_argument("outfile")
    args = parser.parse_args(argv)

    src2node = {}
    node2dst = {}
    node2cmd = {}

    with open(args.file, encoding="utf8") as f:
        for line in f:
            if m := re.fullmatch(
                r"(.+\])([^\[\]]+)(\[.+)", line.strip().rstrip(";")
            ):
                srctxt, cmd, dsttxt = m.group(1, 2, 3)
                srcs = [t.strip("[]") for t in srctxt.split("][")]
                dsts = [t.strip("[]") for t in dsttxt.split("][")]
                nodename = f"{''.join(srcs)}-{''.join(dsts)}"
                for src in srcs:
                    src0 = src.split(":")[0]
                    src2node[src0] = nodename
                    if src0.isdecimal():
                        node2dst[src0] = [src0]
                        node2cmd[src0] = src
                node2dst[nodename] = dsts
                node2cmd[nodename] = (
                    cmd.replace(":", "\\n").replace("=", "\\n", 1)
                )

    with open(args.outfile, "w", encoding="utf8") as f:
        print("digraph {", file=f)
        print("    rankdir=TB;", file=f)
        print("    // node", file=f)
        for node, cmd in node2cmd.items():
            print(f'    "{node}"[label="{cmd}"];', file=f)
        print("    // edge", file=f)
        for node, dsts in node2dst.items():
            for dst in dsts:
                if dst in src2node:
                    print(
                        f'    "{node}" -> "{src2node[dst]}" [label="{dst}"];',
                        file=f,
                    )
                else:
                    print(f'    // "{node}" -> "??" [label="{dst}"]', file=f)
        print("}", file=f)

    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

以下は入出力例です。なお長いコマンド群の頭を抜き出しているので、1つに合流しきれていません。

入力例
[0]geq=a=255:b=255:g=200:r=200[s0];
[s0]split=1[s1];
[1]geq=a=255:b=255:g=200:r=200[s2];
[s2]split=1[s3];
[s3]trim=end=305.893922[s4];
[2:v]split=1[s5];
[s5]setsar=1/1[s6];
[s6]scale=768:-1[s7];
[s7]loop=loop=-1:size=32767:start=0[s8];
[s4][s8]overlay=eof_action=pass:shortest=False:x=-268:y=302[s9];
[3:v]split=1[s10];
[s10]setsar=1/1[s11];
[s11]scale=633:-1[s12];
[s12]loop=loop=-1:size=32767:start=0[s13];
[s9][s13]overlay=eof_action=pass:shortest=False:x=1478:y=324[s14];
[4]geq=a=0:b=0:g=0:r=0[s15];
[s15]split=2[s16][s17];
[s17]split=2[s18][s19];
[5]geq=a=0:b=0:g=0:r=0[s20];
[s20]split=2[s21][s22];
[s21]trim=end=3.897333[s23];
[6]geq=a=178:b=0:g=0:r=0[s24];
[s24]split=2[s25][s26];
出力例
digraph {
    rankdir=TB;
    // node
    "0"[label="0"];
    "0-s0"[label="geq\na=255\nb=255\ng=200\nr=200"];
    "s0-s1"[label="split\n1"];
    "1"[label="1"];
    "1-s2"[label="geq\na=255\nb=255\ng=200\nr=200"];
    "s2-s3"[label="split\n1"];
    "s3-s4"[label="trim\nend=305.893922"];
    "2"[label="2:v"];
    "2:v-s5"[label="split\n1"];
    "s5-s6"[label="setsar\n1/1"];
    "s6-s7"[label="scale\n768\n-1"];
    "s7-s8"[label="loop\nloop=-1\nsize=32767\nstart=0"];
    "s4s8-s9"[label="overlay\neof_action=pass\nshortest=False\nx=-268\ny=302"];
    "3"[label="3:v"];
    "3:v-s10"[label="split\n1"];
    "s10-s11"[label="setsar\n1/1"];
    "s11-s12"[label="scale\n633\n-1"];
    "s12-s13"[label="loop\nloop=-1\nsize=32767\nstart=0"];
    "s9s13-s14"[label="overlay\neof_action=pass\nshortest=False\nx=1478\ny=324"];
    "4"[label="4"];
    "4-s15"[label="geq\na=0\nb=0\ng=0\nr=0"];
    "s15-s16s17"[label="split\n2"];
    "s17-s18s19"[label="split\n2"];
    "5"[label="5"];
    "5-s20"[label="geq\na=0\nb=0\ng=0\nr=0"];
    "s20-s21s22"[label="split\n2"];
    "s21-s23"[label="trim\nend=3.897333"];
    "6"[label="6"];
    "6-s24"[label="geq\na=178\nb=0\ng=0\nr=0"];
    "s24-s25s26"[label="split\n2"];
    // edge
    "0" -> "0-s0" [label="0"];
    "0-s0" -> "s0-s1" [label="s0"];
    // "s0-s1" -> "??" [label="s1"]
    "1" -> "1-s2" [label="1"];
    "1-s2" -> "s2-s3" [label="s2"];
    "s2-s3" -> "s3-s4" [label="s3"];
    "s3-s4" -> "s4s8-s9" [label="s4"];
    "2" -> "2:v-s5" [label="2"];
    "2:v-s5" -> "s5-s6" [label="s5"];
    "s5-s6" -> "s6-s7" [label="s6"];
    "s6-s7" -> "s7-s8" [label="s7"];
    "s7-s8" -> "s4s8-s9" [label="s8"];
    "s4s8-s9" -> "s9s13-s14" [label="s9"];
    "3" -> "3:v-s10" [label="3"];
    "3:v-s10" -> "s10-s11" [label="s10"];
    "s10-s11" -> "s11-s12" [label="s11"];
    "s11-s12" -> "s12-s13" [label="s12"];
    "s12-s13" -> "s9s13-s14" [label="s13"];
    // "s9s13-s14" -> "??" [label="s14"]
    "4" -> "4-s15" [label="4"];
    "4-s15" -> "s15-s16s17" [label="s15"];
    // "s15-s16s17" -> "??" [label="s16"]
    "s15-s16s17" -> "s17-s18s19" [label="s17"];
    // "s17-s18s19" -> "??" [label="s18"]
    // "s17-s18s19" -> "??" [label="s19"]
    "5" -> "5-s20" [label="5"];
    "5-s20" -> "s20-s21s22" [label="s20"];
    "s20-s21s22" -> "s21-s23" [label="s21"];
    // "s20-s21s22" -> "??" [label="s22"]
    // "s21-s23" -> "??" [label="s23"]
    "6" -> "6-s24" [label="6"];
    "6-s24" -> "s24-s25s26" [label="s24"];
    // "s24-s25s26" -> "??" [label="s25"]
    // "s24-s25s26" -> "??" [label="s26"]
}

x.dot.png

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?