この記事は、ニフティグループ Advent Calendar 2024 19日目の記事です。
はじめに
おはようございます。IWSです。
少しいきなりにはなるのですが、みなさんはずんだもんは好きですか?好きですよね?
前々からゲーム実況の動画などで活躍していたり、「Cevio AI Song」 や 「NEUTRINO」 なども出て歌うずんだもんがいたりと見かける機会が多くなったのではないかと思います。
私も 「VOICEPEAK 東北きりたん」 を購入して一緒についてきた ずんだもん を喋らせて遊んだりしました。
ですが、喋らせて終わりでは少し寂しいし、私もずんだもんでなにかしたいなぁ〜と思ったのでちょっとやってみました。
やりたいこと
ニフティには「もじこえ」 というテキストを投稿すると音声に変換してコミュニケーションが取れる社内Webアプリがあります。(もじこえについてはぜひこちらのブログをご覧ください)
音声は Amazon Polly を使って生成しているのですが、ここにみんなが大好きな ずんだもん を追加しようというのが目標です。
イメージとしてはこんな感じでしょうか
もじこえが Node.js で作られているのでこちらもそれに合わせます。
クライアント側からテキストを送信してもらいサーバー側へ渡す。サーバー側はそれをVOICEVOXのコンテナへさらにリクエストを飛ばして音声データを生成してもらうという形です。

VOICEVOX
喋るずんだもんには「Cevio AI」や「VOICEPEAK」などありますが今回はAPIで利用したいというのもあるので「VOICEVOX」を使用します。
https://voicevox.hiroshiba.jp/
core, engine, editor の3種類がありますがAPIとして呼び出して音声合成ができればいいので VOICEVOX_engine を選んでいます。
https://github.com/VOICEVOX/voicevox/blob/main/docs/全体構成.md
Docker Hubでイメージが公開されているのがありがたいですね
https://hub.docker.com/r/voicevox/voicevox_engine
docker-compose.yml にこれを追加するだけでコンテナを立ち上げられます
| 1 2 3 4 5 6 | voicevox:   image: voicevox/voicevox_engine:0.20.0   container_name: voicevox   ports:     - '50021:50021' | 
ずんだもんボイスを生成しよう
VOICEVOXのAPIにリクエストを投げることで音声を生成することができます。
APIには「テキストを音声クエリに変換するAPI」と「音声クエリを音声データに変換するAPI」の2種類があり、2つのAPIをそれぞれ呼ぶことでテキストから音声データの生成までを行います
https://github.com/VOICEVOX/voicevox_engine?tab=readme-ov-file#http-リクエストで音声合成するサンプルコード
| 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 | // テキストから音声クエリを作成 const query = axios.create({   baseURL: `http://voicevox:50021`,   responseType: 'json', }); const queryResponse = await query   .post(`/audio_query?text=${text}&speaker=3`)   .catch((error) => {     console.error(error);   }); if (!queryResponse) {   return ''; } // 音声クエリから音声を合成 const synthesis = axios.create({   baseURL: `http://voicevox:50021`,   headers: {     'Content-Type': 'application/json',   },   responseType: 'arraybuffer', }); const jsonString = JSON.stringify(queryResponse.data); const synthesisResponse = await synthesis   .post(`/synthesis?speaker=3`, jsonString)   .catch((error) => {     console.error(error);   }); if (!synthesisResponse) {   console.log('音声の合成に失敗しました');   return ''; } | 
APIを呼ぶ際にクエリパラメーターで speaker={id} とすることで音声ライブラリの好きなキャラクターに喋らせることができます。3 でずんだもん、8 で 春日部つむぎ に喋ってもらうことができ、一部キャラは喋り方を変えたりもできます。(セクシーずんだもんにしたりささやきずんだもんにしたり……)
音声ライブラリの一覧はコンテナを立ち上げて /speakers にアクセスすると見ることができます。
音声合成ができたらファイルに保存しておきます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // 一意のトークンを生成 const token = uuidv4(); // Pathの指定と音声ファイルの保存 const audioPath = path.join(   process.cwd(),   'public',   'audio',   `${token}.mp3` ); fs.writeFileSync(   audioPath,   new Uint8Array(synthesisResponse.data as Buffer) ); const speakUrl = `/audio/${token}.mp3`; return speakUrl; | 
トークンをつかって適当な名前で保存しているだけです。ファイルの保存ができたらそのファイルへのPathを返しています。
Pathを返しているのはクライアント側で /audio/${token}.mp3 を再生するという実装をしているからです。返ってきた speakUrl のURLを使用して
| 1 2 3 | const music = new Audio(speakUrl); music.play(); | 
のようにしてあげればブラウザ側で音声が再生ができます。
コード全体
| 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 | // VOICEVOX export const voicevoxTextToSpeakUrl = async (text: string): Promise<string> => {   /**    * テキストをVOICEVOXで音声に変換し音声ファイルのURLを返します。    *    * @param text 音声に変換するテキスト    * @returns 音声ファイルのURL、失敗した場合は空文字列    */   try {     // 音声クエリを作成     const query = axios.create({       baseURL: `http://voicevox:50021`,       responseType: 'json',     });     const queryResponse = await query       .post(`/audio_query?text=${text}&speaker=3`)       .catch((error) => {         console.error(error);       });     if (!queryResponse) {       return '';     }     // クエリから音声合成     const synthesis = axios.create({       baseURL: `http://voicevox:50021`,       headers: {         'Content-Type': 'application/json',       },       responseType: 'arraybuffer',     });     const jsonString = JSON.stringify(queryResponse.data);     const synthesisResponse = await synthesis       .post(`/synthesis?speaker=3`, jsonString)       .catch((error) => {         console.error(error);       });     if (!synthesisResponse) {       console.log('音声の合成に失敗しました');       return '';     }     // 一意のトークンを生成     const token = uuidv4();     // Pathの指定と音声ファイルの保存     const audioPath = path.join(       process.cwd(),       'public',       'audio',       `${token}.mp3`     );     fs.writeFileSync(       audioPath,       new Uint8Array(synthesisResponse.data as Buffer)     );     const speakUrl = `/audio/${token}.mp3`;     // 一度再生したら音声ファイルはもう使わないため削除     setTimeout(() => {       fs.unlink(audioPath, (err) => {         if (err) {           console.error(`ファイル削除エラー: ${audioPath}`, err);         } else {           console.log(`ファイルが削除されました: ${audioPath}`);         }       });     }, 20000);     return speakUrl;   } catch (e) {     console.error('error: ', e);     return '';   } }; | 
まとめ
VOICEVOXをつかってずんだもんを触ってみました。APIが提供されているおかげでもじこえに簡単にずんだもんを組み込むことができましたね。これでずんだもんライフが充実します!
ぜひ皆さんもVOICEVOXを使ってなにかやってみてください!
明日は @takatakanian さんの記事です!お楽しみに!
クレジット
VOICEVOX:ずんだもん
 
            


