Fragments of verbose memory

冗長な記憶の断片 - Web技術のメモをほぼ毎日更新(準備中)

Feb 3, 2021 - コメント - 日記

n8nでImageMagickを使えるようにしてみる

n8n にはEditImageという画像処理ノードがインテグレーションされていて、簡単な操作ならこれで十分処理ができます。EditImageは内部でGraphicsMagick が使われていますが、とある理由からImageMagick を使いたくなりshell-jsonrpc を使って実装してみました。shell-jsonrpcは、以前の記事でバイナリデータに対応するようになっています。

手順としては以下の通りです

  1. shell-jsonrpcにimagemagickを組み込んだDockerイメージの作成
  2. 1.のコンテナをn8nのdocker-composeに組み込む
  3. n8nにてインテグレーション

1. shell-jsonrpcにimagemagickを組み込んだDockerイメージの作成

これは簡単でalpineのパッケージをインストールするだけです。

FROM tumf/shell-jsonrpc:latest
RUN apk add --update imagemagick

このイメージはtumf/imagemagick-jsonrpcのDocker Hubリポジトリに公開してあります。

2. 1.のコンテナをn8nのdocker-composeに組み込む

これも簡単で、以下の通りn8nのdocker-compose.ymlに追記します。

  imagemagick-jsonrpc:
    image: tumf/imagemagick-jsonrpc:latest

これでdocker-comopse up -dでコンテナを立ち上げれば、imagemagick-jsonrpcが無事n8nから利用可能になります。

3. n8nにてインテグレーション

今回このような形でインテグレーションしました。試しにLinuxのロゴを縮小して回転するサンプルを作成してみます。

こんな感じに変換します。

インテグレーションの前に、n8nがどの様にバイナリデータを保持しているかを補足させてください。

n8nのバイナリデータの持ち方

n8nの通常のデータは、[{ "json": 通常のデータ }]のようにjsonキーに格納されていますが、バイナリデータは以下のようにbinaryキーに格納されています。

n8nのバイナリの構造

1
2
3
4
5
6
7
8
[{ "binary": {
    "data": { 
        "mimeType": "image/png",
        "data": "base64 encoded data",
        "fileName": "pPSWNyH.png",
        "fileExtension": "png"
      }
 }, },... ]

こんな感じで$binary.data.dataにBase64エンコードされたバイナリデータが格納されています。

インテグレーション!

ノードを配置するとこんな感じ

ソース

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
{
  "name": "imagemagick",
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "requestMethod": "POST",
        "url": "http://imagemagick-jsonrpc:8999",
        "jsonParameters": true,
        "options": {},
        "bodyParametersJson": "={\"jsonrpc\":\"2.0\", \"id\": \"1\", \"method\":\"shell\", \"params\": [{ \"command\": \"convert - -resize 200x200 -rotate 20 -\", \"stdin_binary\": true, \"stdin\": \"{{$json['data']}}\" } ] }"
      },
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        810,
        300
      ]
    },
    {
      "parameters": {
        "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Tux.svg/1280px-Tux.svg.png",
        "responseFormat": "file",
        "options": {}
      },
      "name": "HTTP Request1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "items[0].json = {};\nitems[0].json.data = items[0].binary.data.data;\nreturn items;\n"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        620,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "items[0].binary = { \"data\": {\n  \"data\": items[0].json.result[0].stdout,\n  \"mimeType\": \"image/png\",\n  \"fileName\": \"out.png\",\n  \"fileExtension\": \"png\"\n} };\nitems[0].json = {};\n\nreturn items;"
      },
      "name": "Function1",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        990,
        300
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "HTTP Request1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Function1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request1": {
      "main": [
        [
          {
            "node": "Function",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {},
  "id": "13"
}

imagemagic-jsonrpcの前後にFunctionノードを挟んでjsonデータをbinaryに変換しています。

最初のFunction

1
2
3
items[0].json = {};
items[0].json.data = items[0].binary.data.data;
return items;

この、items[0].json.dataはBase64エンコードされた元画像ファイルのデータが入りますので、これをimagemagick-jsonrpcの標準入力に与えます。

1
2
3
4
5
6
7
8
{
    "jsonrpc":"2.0", 
    "id": "1",
    "method":"shell",
    "params": [
        { "command": "convert - -resize 200x200 -rotate 20 -", "stdin_binary": true, "stdin": "{{$json['data']}}" } 
    ]
}

こんなリクエストになります。

二つ目のFunction

1
2
3
4
5
6
7
8
9
items[0].binary = { "data": {
  "data": items[0].json.result[0].stdout,
  "mimeType": "image/png",
  "fileName": "out.png",
  "fileExtension": "png"
} };
items[0].json = {};

return items;

簡単ですね。