2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Node-REDでCoeFont CLOUD APIを使い高品質のTTSを試す

Last updated at Posted at 2021-07-31

はじめに

以前、スマートスピーカーやTTSの音声が聞き取りづらかった経緯があり、「あみたろの声素材工房」から音声素材を頂いて、Node-REDで時報などを発話させていました。
そして今回、あみたろさんがCoeFontというAI音声合成で声のフォントを作成されたので、今まで音声素材の組み合わせでは実現できなかったセリフを作って活用しようと考えています。

そして、CoeFont CLOUDではCoeFont APIが用意されており、TTS(Text-to-Speach)で利用できるようです。
CoeFontは実に色々な方の色々な表情の声が利用できるので、今までのTTSでは難しかった以下のような発話ができます。

  • 滑らかで自然な発話になる!
  • 自分の好みの声を選べる!(その声の人を応援できる!)
  • 感情も表現できたりする!

前提環境

サブフローのテンプレートで環境変数が扱えノードのようにできるバージョン以上を前提としています。

Node-REDサブフローの作成

CoeFont APIドキュメントにnode.jsのソースがあるので、ほぼそのまま利用します。

事前に、request,cryptoのモジュールをNode-REDのホームディレクトリ(ここでは~/.node-redとして説明します)でインストールしておきます。

cd ~/.node-red
npm install axios crypto

~/.node-red/settings.js の functionGlobalContext に request,cryptoのモジュールを定義して、functionノードで利用できるようにしておきます。

settings.js
           : (略)
    functionGlobalContext: {
        os: require('os'),
        axios: require('axios'),
        crypto: require('crypto')
    },
           : (略)

もしくは、v1.3の新機能 externalModules を利用してください。※個人的に使った事はまだありません。

Node-REDを再起動して、以下のフローを読み込むと「CoeFont API」がサブフローとして使えるようになります。

↓三角をクリックでJSONデータが展開されます。

CoeFont CLOUD サブフロー
[
  {
    "id": "adcdb07133d05fd7",
    "type": "subflow",
    "name": "CoeFont CLOUD",
    "info": "",
    "category": "",
    "in": [
      {
        "x": 50,
        "y": 80,
        "wires": [
          {
            "id": "7cb8f439e55c1230"
          }
        ]
      }
    ],
    "out": [
      {
        "x": 410,
        "y": 80,
        "wires": [
          {
            "id": "c714d2a12696eeba",
            "port": 0
          }
        ]
      }
    ],
    "env": [
      {
        "name": "KEY",
        "type": "str",
        "value": "",
        "ui": {
          "icon": "font-awesome/fa-key",
          "label": {
            "en-US": "Access Key"
          },
          "type": "input",
          "opts": {
            "types": [
              "str"
            ]
          }
        }
      },
      {
        "name": "SECRET",
        "type": "cred",
        "ui": {
          "icon": "font-awesome/fa-user-secret",
          "label": {
            "en-US": "Client Secret"
          }
        }
      },
      {
        "name": "COEFONT",
        "type": "str",
        "value": "33240884-bd28-46c0-820e-f30b16290e08",
        "ui": {
          "icon": "font-awesome/fa-address-book-o",
          "label": {
            "en-US": "coefont ID"
          },
          "type": "select",
          "opts": {
            "opts": [
              {
                "l": {
                  "en-US": "あみたろ・ハキハキ"
                },
                "v": "33240884-bd28-46c0-820e-f30b16290e08"
              }
            ]
          }
        }
      },
      {
        "name": "SPEED",
        "type": "num",
        "value": "1.0",
        "ui": {
          "icon": "font-awesome/fa-forward",
          "label": {
            "en-US": "speed"
          },
          "type": "spinner",
          "opts": {
            "min": 0,
            "max": 10
          }
        }
      },
      {
        "name": "PITCH",
        "type": "num",
        "value": "0",
        "ui": {
          "icon": "font-awesome/fa-sort",
          "label": {
            "en-US": "pitch (x10)"
          },
          "type": "spinner",
          "opts": {
            "min": -300,
            "max": 300
          }
        }
      },
      {
        "name": "KUTEN",
        "type": "num",
        "value": "0.7",
        "ui": {
          "icon": "font-awesome/fa-spinner",
          "label": {
            "en-US": "kuten"
          },
          "type": "input",
          "opts": {
            "types": [
              "num"
            ]
          }
        }
      },
      {
        "name": "TOTEN",
        "type": "num",
        "value": "0.4",
        "ui": {
          "icon": "font-awesome/fa-spinner",
          "label": {
            "en-US": "toten"
          },
          "type": "input",
          "opts": {
            "types": [
              "num"
            ]
          }
        }
      },
      {
        "name": "VOLUME",
        "type": "num",
        "value": "1.0",
        "ui": {
          "icon": "font-awesome/fa-volume-up",
          "label": {
            "en-US": "volume"
          },
          "type": "spinner",
          "opts": {
            "min": 0,
            "max": 5
          }
        }
      },
      {
        "name": "INTONATION",
        "type": "num",
        "value": "1.0",
        "ui": {
          "icon": "font-awesome/fa-music",
          "label": {
            "en-US": "intonation"
          },
          "type": "spinner",
          "opts": {
            "min": 0,
            "max": 2
          }
        }
      },
      {
        "name": "URI",
        "type": "str",
        "value": "https://api.coefont.cloud/v1/text2speech",
        "ui": {
          "icon": "font-awesome/fa-globe",
          "type": "input",
          "opts": {
            "types": [
              "str"
            ]
          }
        }
      },
      {
        "name": "APIドキュメント:https://docs.coefont.cloud/",
        "type": "str",
        "value": "",
        "ui": {
          "type": "none"
        }
      }
    ],
    "meta": {},
    "credentials": {
      "SECRET": ""
    },
    "color": "#3FADB5",
    "icon": "font-awesome/fa-commenting"
  },
  {
    "id": "c714d2a12696eeba",
    "type": "switch",
    "z": "adcdb07133d05fd7",
    "name": "",
    "property": "payload",
    "propertyType": "msg",
    "rules": [
      {
        "t": "istype",
        "v": "buffer",
        "vt": "buffer"
      }
    ],
    "checkall": "false",
    "repair": false,
    "outputs": 1,
    "x": 300,
    "y": 80,
    "wires": [
      []
    ]
  },
  {
    "id": "7cb8f439e55c1230",
    "type": "function",
    "z": "adcdb07133d05fd7",
    "name": "",
    "func": "\nconst axios = new global.get('axios');\nconst crypto = new global.get('crypto');\n\nconst data = JSON.stringify({\n  'coefont': env.get(\"COEFONT\"),\n  'text': msg.payload,\n  'speed': env.get(\"SPEED\"),\n  'pitch': env.get(\"PITCH\") * 10,\n  'kuten': env.get(\"KUTEN\"),\n  'toten': env.get(\"TOTEN\"),\n  'volume': env.get(\"VOLUME\"),\n  'intonation': env.get(\"INTONATION\")\n});\nconst date = String(Math.floor(Date.now() / 1000));\n\nconst signature = crypto.createHmac('sha256', env.get(\"SECRET\")).update(date+data).digest('hex')\n\naxios.post(env.get(\"URI\"), data, {\n    headers: {\n      'Content-Type': 'application/json',\n      'Authorization': env.get(\"KEY\"),\n      'X-Coefont-Date': date,\n      'X-Coefont-Content': signature\n    },\n    responseType: 'arraybuffer'\n  })\n  .then(response => {\n    msg.payload = new Buffer(response.data, \"binary\");\n  })\n  .catch(error => {\n    console.log(error)\n  })\n  .finally(() => {\n    node.send(msg);\n  })\n\nreturn null;",
    "outputs": 1,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 160,
    "y": 80,
    "wires": [
      [
        "c714d2a12696eeba"
      ]
    ]
  }
]

入力は、喋らせたい内容を msg.payload に設定しておきます。
出力は、wav形式のファイルがバイナリバッファで msg.payload に保存されています。

サブフローの設定項目は以下のようになっています。
調整値の詳細については、CoeFont APIドキュメントを参照してください。

項目 説明
Access Key CoeFont CLOUDで発行したAccess Keyを指定します。
Client Secret CoeFont CLOUDで発行したClient Secretを指定します。
coefont ID CoeFont ID(UUID)を指定します。
speed 音声の速度(倍)を指定します。
pitch (x10) 音声のピッチを指定します。
kuten 句点間隔(秒)を指定します。
toten 読点間隔(秒)を指定します。
volume 音量(倍)を指定します。
intonation 抑揚を指定します。
URI リクエストURIを設定します。

試してみる

事前に、「node-red-contrib-play-audio」ノードをインストールしておきます。
CoeFontサブフローに続けて「play audio」ノードでブラウザから喋らせています。

スクリーンショット 2021-09-09 22.47.22.png

↓三角をクリックでこのフローのJSONデータが展開されます。

サンプルフロー
[
  {
    "id": "adcdb07133d05fd7",
    "type": "subflow",
    "name": "CoeFont CLOUD",
    "info": "",
    "category": "",
    "in": [
      {
        "x": 50,
        "y": 80,
        "wires": [
          {
            "id": "7cb8f439e55c1230"
          }
        ]
      }
    ],
    "out": [
      {
        "x": 410,
        "y": 80,
        "wires": [
          {
            "id": "c714d2a12696eeba",
            "port": 0
          }
        ]
      }
    ],
    "env": [
      {
        "name": "KEY",
        "type": "str",
        "value": "",
        "ui": {
          "icon": "font-awesome/fa-key",
          "label": {
            "en-US": "Access Key"
          },
          "type": "input",
          "opts": {
            "types": [
              "str"
            ]
          }
        }
      },
      {
        "name": "SECRET",
        "type": "cred",
        "ui": {
          "icon": "font-awesome/fa-user-secret",
          "label": {
            "en-US": "Client Secret"
          }
        }
      },
      {
        "name": "COEFONT",
        "type": "str",
        "value": "33240884-bd28-46c0-820e-f30b16290e08",
        "ui": {
          "icon": "font-awesome/fa-address-book-o",
          "label": {
            "en-US": "coefont ID"
          },
          "type": "select",
          "opts": {
            "opts": [
              {
                "l": {
                  "en-US": "あみたろ・ハキハキ"
                },
                "v": "33240884-bd28-46c0-820e-f30b16290e08"
              }
            ]
          }
        }
      },
      {
        "name": "SPEED",
        "type": "num",
        "value": "1.0",
        "ui": {
          "icon": "font-awesome/fa-forward",
          "label": {
            "en-US": "speed"
          },
          "type": "spinner",
          "opts": {
            "min": 0,
            "max": 10
          }
        }
      },
      {
        "name": "PITCH",
        "type": "num",
        "value": "0",
        "ui": {
          "icon": "font-awesome/fa-sort",
          "label": {
            "en-US": "pitch (x10)"
          },
          "type": "spinner",
          "opts": {
            "min": -300,
            "max": 300
          }
        }
      },
      {
        "name": "KUTEN",
        "type": "num",
        "value": "0.7",
        "ui": {
          "icon": "font-awesome/fa-spinner",
          "label": {
            "en-US": "kuten"
          },
          "type": "input",
          "opts": {
            "types": [
              "num"
            ]
          }
        }
      },
      {
        "name": "TOTEN",
        "type": "num",
        "value": "0.4",
        "ui": {
          "icon": "font-awesome/fa-spinner",
          "label": {
            "en-US": "toten"
          },
          "type": "input",
          "opts": {
            "types": [
              "num"
            ]
          }
        }
      },
      {
        "name": "VOLUME",
        "type": "num",
        "value": "1.0",
        "ui": {
          "icon": "font-awesome/fa-volume-up",
          "label": {
            "en-US": "volume"
          },
          "type": "spinner",
          "opts": {
            "min": 0,
            "max": 5
          }
        }
      },
      {
        "name": "INTONATION",
        "type": "num",
        "value": "1.0",
        "ui": {
          "icon": "font-awesome/fa-music",
          "label": {
            "en-US": "intonation"
          },
          "type": "spinner",
          "opts": {
            "min": 0,
            "max": 2
          }
        }
      },
      {
        "name": "URI",
        "type": "str",
        "value": "https://api.coefont.cloud/v1/text2speech",
        "ui": {
          "icon": "font-awesome/fa-globe",
          "type": "input",
          "opts": {
            "types": [
              "str"
            ]
          }
        }
      },
      {
        "name": "APIドキュメント:https://docs.coefont.cloud/",
        "type": "str",
        "value": "",
        "ui": {
          "type": "none"
        }
      }
    ],
    "meta": {},
    "credentials": {
      "SECRET": ""
    },
    "color": "#3FADB5",
    "icon": "font-awesome/fa-commenting"
  },
  {
    "id": "c714d2a12696eeba",
    "type": "switch",
    "z": "adcdb07133d05fd7",
    "name": "",
    "property": "payload",
    "propertyType": "msg",
    "rules": [
      {
        "t": "istype",
        "v": "buffer",
        "vt": "buffer"
      }
    ],
    "checkall": "false",
    "repair": false,
    "outputs": 1,
    "x": 300,
    "y": 80,
    "wires": [
      []
    ]
  },
  {
    "id": "7cb8f439e55c1230",
    "type": "function",
    "z": "adcdb07133d05fd7",
    "name": "",
    "func": "\nconst axios = new global.get('axios');\nconst crypto = new global.get('crypto');\n\nconst data = JSON.stringify({\n  'coefont': env.get(\"COEFONT\"),\n  'text': msg.payload,\n  'speed': env.get(\"SPEED\"),\n  'pitch': env.get(\"PITCH\") * 10,\n  'kuten': env.get(\"KUTEN\"),\n  'toten': env.get(\"TOTEN\"),\n  'volume': env.get(\"VOLUME\"),\n  'intonation': env.get(\"INTONATION\")\n});\nconst date = String(Math.floor(Date.now() / 1000));\n\nconst signature = crypto.createHmac('sha256', env.get(\"SECRET\")).update(date+data).digest('hex')\n\naxios.post(env.get(\"URI\"), data, {\n    headers: {\n      'Content-Type': 'application/json',\n      'Authorization': env.get(\"KEY\"),\n      'X-Coefont-Date': date,\n      'X-Coefont-Content': signature\n    },\n    responseType: 'arraybuffer'\n  })\n  .then(response => {\n    msg.payload = new Buffer(response.data, \"binary\");\n  })\n  .catch(error => {\n    console.log(error)\n  })\n  .finally(() => {\n    node.send(msg);\n  })\n\nreturn null;",
    "outputs": 1,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 160,
    "y": 80,
    "wires": [
      [
        "c714d2a12696eeba"
      ]
    ]
  },
  {
    "id": "a17ed66939995391",
    "type": "subflow:adcdb07133d05fd7",
    "z": "c33d685b.315298",
    "name": "",
    "env": [
      {
        "name": "KEY",
        "value": "Set your Access Key",
        "type": "str"
      },
      {
        "name": "SECRET",
        "type": "cred"
      }
    ],
    "x": 660,
    "y": 1820,
    "wires": [
      [
        "61734d220179578a"
      ]
    ]
  },
  {
    "id": "fd521f8d8cfaddf9",
    "type": "inject",
    "z": "c33d685b.315298",
    "name": "",
    "props": [
      {
        "p": "payload"
      },
      {
        "p": "topic",
        "vt": "str"
      }
    ],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": 0.1,
    "topic": "",
    "payload": "",
    "payloadType": "date",
    "x": 300,
    "y": 1820,
    "wires": [
      [
        "7197bc148b5571ec"
      ]
    ]
  },
  {
    "id": "7197bc148b5571ec",
    "type": "template",
    "z": "c33d685b.315298",
    "name": "",
    "field": "payload",
    "fieldType": "msg",
    "format": "handlebars",
    "syntax": "mustache",
    "template": "これはテストです。",
    "output": "str",
    "x": 480,
    "y": 1820,
    "wires": [
      [
        "a17ed66939995391"
      ]
    ]
  },
  {
    "id": "61734d220179578a",
    "type": "play audio",
    "z": "c33d685b.315298",
    "name": "",
    "voice": "0",
    "x": 850,
    "y": 1820,
    "wires": []
  }
]

以前使っていたフローで喋らせている動画を掲載しておきます。結構楽しいですね。

おわりに

TTSと言えば「聞き取りづらい」「声の種類が少ない」「それなりの品質にすると利用料金が高い」というイメージが強かったのですが、CoeFont CLOUDの利用料金は安く、音声もかなり自然に近いと思います。
CoeFont CLOUDでは個人で活動されている方や有名な方まで幅広い方々が声を提供しています。

みなさんも、CoeFont CLOUDで応援したい方を見つけて、たくさん利用して推し活しましょう!!

2
2
1

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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?