🍣

Azure AI Speech サービスのバッチ合成APIを利用し、テキストから音声ファイルを作ってみた!

2024/06/27に公開

awsの人気サービスtop50をたった10分で解説してくれるという動画が神動画としてX界隈でプチバズっていました。
https://www.youtube.com/watch?v=JIbIYCM48to

こちらは英語だったので、雰囲気しか見ていませんが、これはAzure版を作るべき!と思い、自分の勉強がてら50個(正確には52個)ピックアップして、簡単な概要とともに動画作成することにしました。


さて、ここで1点問題が、調べた内容を自分で音声録音するのは手間だな...超めんどくさいな...。って気持ちがMAX!!
最近Ai Tuberを調べてることもあり、Ai音声で作ることにしました。

無料で使えるAi音声としてはStyle-Bert-VITS2が今の所TOPの性能らしいですが、音声に起こせるテキストには基本100文字の制限が...。
次点で紹介されていたのが、Google Cloud Text-to-Speech。サンプル音声を聞きましたが、AI AI しすぎてる・・。
Azureにもあるなっと思って調べてみたら、サンプル聞いてびっくり。超ナチュラルぅ! ほんとAzureはなんで話題にならないんでしょうね。

ってことで、Azureの AI Speech サービスのバッチ合成APIを利用して、テキストから音声ファイルを作ってみました!

最初に制作したyotubeをご紹介。

GPTさんと壁打ちしながらAzureのおすすめ機能を聞いて、知りたい内容を深掘り → 文章はClaudeさんに作ってもらいました。
動画映像はCanva! 正直ほとんどCanvaでスライド選んでる時間で、作業自体は1時間程度で出来ました。恐ろしい時代です。

https://youtu.be/CZRqmIyE5WI


では、Visual Studio Code (VSCode) を使用して、Azure AI Speech サービスのバッチ合成 APIを利用し、ファイルからテキストを読み込んで音声を生成する手順を説明します。

1. 準備:

  • Azure アカウントとAzure AI Speech サービスのリソースが必要です。
  • Node.jsがインストールされていることを確認してください。
    「Azure AI Speech」を作成

2. プロジェクトのセットアップ:

  • VSCodeを開き、新しいフォルダを作成します。
  • ターミナルを開き、以下のコマンドを実行してpackage.jsonを作成します:
    npm init -y
    
  • 必要なパッケージをインストールします:
    npm install microsoft-cognitiveservices-speech-sdk fs dotenv
    

3. 環境変数の設定:

  • プロジェクトのルートに.envファイルを作成し、以下の内容を追加します:
    SPEECH_KEY=your_speech_key
    SPEECH_REGION=your_speech_region
    
  • your_speech_keyyour_speech_regionを実際のAzure AI Speechサービスの値に置き換えてください。

4. スクリプトの作成:

  • batchSynthesize.jsという名前の新しいファイルを作成し、以下のコードを追加します:
require('dotenv').config();
const sdk = require("microsoft-cognitiveservices-speech-sdk");
const fs = require("fs");
const { promisify } = require('util');

const speechConfig = sdk.SpeechConfig.fromSubscription(process.env.SPEECH_KEY, process.env.SPEECH_REGION);
speechConfig.speechSynthesisVoiceName = "ja-JP-NanamiNeural";

const inputFile = "input.txt";
const outputFile = "output.wav";

const CHUNK_SIZE = 1000;

function splitText(text, maxLength = CHUNK_SIZE) {
    const chunks = [];
    let chunk = "";
    const sentences = text.split(/(?<=[。.!?])/);
    
    for (const sentence of sentences) {
        if ((chunk + sentence).length <= maxLength) {
            chunk += sentence;
        } else {
            if (chunk) chunks.push(chunk);
            chunk = sentence;
        }
    }
    if (chunk) chunks.push(chunk);
    return chunks;
}

async function synthesizeSpeech(text) {
    return new Promise((resolve, reject) => {
        const synthesizer = new sdk.SpeechSynthesizer(speechConfig);
        synthesizer.speakTextAsync(
            text,
            result => {
                if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
                    resolve(Buffer.from(result.audioData));
                } else {
                    reject(new Error(result.errorDetails));
                }
                synthesizer.close();
            },
            error => {
                reject(error);
                synthesizer.close();
            }
        );
    });
}

function mergeWavBuffers(buffers) {
    // 最初のバッファからヘッダー情報を取得
    const headerBuffer = buffers[0].slice(0, 44);
    const header = {
        chunkSize: headerBuffer.readUInt32LE(4),
        subchunk2Size: headerBuffer.readUInt32LE(40)
    };

    // データチャンクのみを結合
    const dataBuffers = buffers.map(buffer => buffer.slice(44));
    const mergedData = Buffer.concat(dataBuffers);

    // 新しいヘッダーサイズを計算
    header.chunkSize = mergedData.length + 36;
    header.subchunk2Size = mergedData.length;

    // 新しいヘッダーを書き込む
    headerBuffer.writeUInt32LE(header.chunkSize, 4);
    headerBuffer.writeUInt32LE(header.subchunk2Size, 40);

    // ヘッダーとデータを結合
    return Buffer.concat([headerBuffer, mergedData]);
}

async function main() {
    try {
        const text = fs.readFileSync(inputFile, 'utf8');
        const chunks = splitText(text);
        const audioBuffers = [];

        for (let i = 0; i < chunks.length; i++) {
            console.log(`チャンク ${i + 1}/${chunks.length} を処理中...(約${chunks[i].length}文字)`);
            const audioBuffer = await synthesizeSpeech(chunks[i]);
            audioBuffers.push(audioBuffer);
        }

        console.log("全てのチャンクの処理が完了しました。音声ファイルを結合しています...");
        const combinedBuffer = mergeWavBuffers(audioBuffers);
        fs.writeFileSync(outputFile, combinedBuffer);
        console.log(`音声ファイルが保存されました: ${outputFile}`);
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

main();

5. 入力ファイルの作成:

  • プロジェクトのルートにinput.txtファイルを作成し、音声に変換したいテキストを記述します。

6. スクリプトの実行:

  • ターミナルで以下のコマンドを実行します:
    node batchSynthesize.js
    

7. 結果の確認:

  • スクリプトが正常に実行されると、output.wavファイルが生成されます。
  • このファイルを任意の音声プレーヤーで再生して、生成された音声を確認できます。

このスクリプトは、指定されたテキストファイルを読み込み、テキストを小さなチャンク(デフォルトで最大1000文字)に分割、Azure AI Speech サービスを使用して音声に変換し、生成された音声バッファを結合して、1つの音声ファイル(WAV)として保存するようにしました。日本語の音声(NanamiNeural)を使用しています。


終わりに

今回3,500文字くらいを音声データにしましたが、3分かかりませんでした。爆速!
音声の精度もいいですし、1分あたり20トランザクションという制限はありますが、1か月あたり50万文字まで無料ですし。他サービスより超絶良いのでは??

ちなみに、Speech Studioという画面に行って、下の方にある音声ギャラリーに行けば、他の音声のサンプルがあるので、お試し出来ます。個人的には直紀さんの声がセクシーでした!
「Azure AI Speech」を作成
「Azure AI Speech」を作成

GitHubで編集を提案

Discussion