1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Google I/O 2025】 Gemini API + Node.js で音声合成(TTS)を試す

Last updated at Posted at 2025-05-22

はじめに

Google I/O 2025 で発表されていたものの 1つ、Gemini API を使った音声合成(TTS)を試してみます。

●音声生成(テキスト読み上げ)  |  Gemini API  |  Google AI for Developers
 https://ai.google.dev/gemini-api/docs/speech-generation?hl=ja#javascript

image.png

直近で試したこと: Google I/O 2025関連

Google I/O 2025 で発表されていたものは、直近で他にも試しています。具体的には以下になりますが、今回もそれらと同様 Node.js を使って試します。

●Gemini API & SDK が MCP 対応したので試す(Node.js を利用)【Google I/O 2025】 - Qiita
 https://qiita.com/youtoy/items/fd1b123c3f7fc3516264

●【Google I/O 2025】 API の無料枠で Gemini・Gemma の新モデルを試す(Node.js を利用) - Qiita
 https://qiita.com/youtoy/items/714a1bd58a80f856663c

あと、まだ記事にはできてないですが、以下の音楽生成も Node.js で試しました。

サクッと試す

それでは実際に試していきます。

下準備

下準備として、パッケージのインストールと APIキーの設定を行います。

パッケージのインストール

公式ドキュメントの最初のサンプルを見てみたところ、以下 2つのパッケージを使うようです。

●wav - npm
 https://www.npmjs.com/package/wav

●@google/genai - npm
 https://www.npmjs.com/package/@google/genai

これらを以下のコマンドでインストールします。

npm i @google/genai wav

APIキーの設定

次に APIキーの設定です。環境変数 GEMINI_API_KEY に Gemini の APIキーをセットしておきます。

コードと処理の実行

公式ドキュメントに掲載されている、以下のコードを試してみます。

import { GoogleGenAI } from "@google/genai";
import wav from "wav";

async function saveWaveFile(
  filename,
  pcmData,
  channels = 1,
  rate = 24000,
  sampleWidth = 2
) {
  return new Promise((resolve, reject) => {
    const writer = new wav.FileWriter(filename, {
      channels,
      sampleRate: rate,
      bitDepth: sampleWidth * 8,
    });

    writer.on("finish", resolve);
    writer.on("error", reject);

    writer.write(pcmData);
    writer.end();
  });
}

async function main() {
  const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });

  const response = await ai.models.generateContent({
    model: "gemini-2.5-flash-preview-tts",
    contents: [{ parts: [{ text: "Say cheerfully: Have a wonderful day!" }] }],
    config: {
      responseModalities: ["AUDIO"],
      speechConfig: {
        voiceConfig: {
          prebuiltVoiceConfig: { voiceName: "Kore" },
        },
      },
    },
  });

  const data = response.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data;
  const audioBuffer = Buffer.from(data, "base64");

  const fileName = "out.wav";
  await saveWaveFile(fileName, audioBuffer);
}
await main();

音声合成用のモデル

モデルは「gemini-2.5-flash-preview-tts」が使われています。ちなみに、もう 1つ「gemini-2.5-pro-preview-tts」というモデルも使えるようです。

image.png

image.png

処理結果

上記を実行してみたところ、「out.wav」というファイルが生成されました。それを再生すると「Have a wonderful day!」という明るい声を聞くことができました。

2つ目のお試し

別のサンプル(複数のスピーカーによるテキスト読み上げ)を少し書きかえて試してみます。

下準備(パッケージのインストールと APIキーの設定)は、先ほどと同じです。

声の種類

この後試す音声合成の声の種類を、公式サンプルで使われているもの(voiceName:「Kore」「Puck」)とは別のものに変えてみることにします。

その部分を変える際の候補の参照先として、公式ドキュメント上に書かれている声の種類を見てみます。また、それと重複しますが、Google AI Studio上で選べる声の種類の一部を、以下に合わせて掲載してみます。

image.png

image.png

image.png

image.png

この中から 2種類の声を選ぶことにします。

プロンプトでの音声スタイルを制御

プロンプトでの音声スタイルを制御についても、情報を見てみます。

image.png

これもこの後に使ってみます。

コード

試すコードは以下のとおりです。

声の種類、しゃべる内容を変えていて、さらに音声スタイルを制御するプロンプトを加えています。

import { GoogleGenAI } from "@google/genai";
import wav from "wav";

async function saveWaveFile(
  filename,
  pcmData,
  channels = 1,
  rate = 24000,
  sampleWidth = 2
) {
  return new Promise((resolve, reject) => {
    const writer = new wav.FileWriter(filename, {
      channels,
      sampleRate: rate,
      bitDepth: sampleWidth * 8,
    });

    writer.on("finish", resolve);
    writer.on("error", reject);

    writer.write(pcmData);
    writer.end();
  });
}

async function main() {
  const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });

  const prompt = `話し声がすごくゆっくりなスピーカー1と、退屈そうで声が小さいスピーカー2の、2人の会話の音声合成を行ってください:
         スピーカー1: 今日は良い天気ですね。元気にしてますか?
         スピーカー2: 悪くはないかな。あなたこそどうですか?`;

  const response = await ai.models.generateContent({
    model: "gemini-2.5-flash-preview-tts",
    contents: [{ parts: [{ text: prompt }] }],
    config: {
      responseModalities: ["AUDIO"],
      speechConfig: {
        multiSpeakerVoiceConfig: {
          speakerVoiceConfigs: [
            {
              speaker: "スピーカー1",
              voiceConfig: {
                prebuiltVoiceConfig: { voiceName: "Autonoe" },
              },
            },
            {
              speaker: "スピーカー2",
              voiceConfig: {
                prebuiltVoiceConfig: { voiceName: "Umbriel" },
              },
            },
          ],
        },
      },
    },
  });

  const data = response.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data;
  const audioBuffer = Buffer.from(data, "base64");

  const fileName = "out02.wav";
  await saveWaveFile(fileName, audioBuffer);
}

await main();

wavファイルを生成して再生

上記を実行して wavファイルを生成し、それを再生します。wavファイルを再生している時の音を、キャプチャして動画にしてみました。

こんな感じの合成された音声を得ることができました。

【追記】 別サンプルでのお試し

記事を公開した後、Google AI Studio で別のサンプルが出てきたので、それも試してみました。具体的には、以下の部分をクリックすると表示されるサンプルです。

image.png

選べる言語などは、以下となっています。

image.png

下準備

この後のコードを実行する前の下準備についてふれておきます。

以下はパッケージのインストールです。

npm i @google/genai mime

あとは APIキーの設定です。環境変数 GEMINI_API_KEY に Gemini の APIキーをセットしておきます。

実装したコード: JavaScript

実装したコードは以下で、TypeScript のサンプルコードを JavaScript に変更しています。

import { GoogleGenAI } from "@google/genai";
import mime from "mime";
import { writeFile } from "fs";

function saveBinaryFile(fileName, content) {
  writeFile(fileName, content, "utf8", (err) => {
    if (err) {
      console.error(`Error writing file ${fileName}:`, err);
      return;
    }
    console.log(`File ${fileName} saved to file system.`);
  });
}

async function main() {
  const ai = new GoogleGenAI({
    apiKey: process.env.GEMINI_API_KEY,
  });

  const config = {
    temperature: 1,
    responseModalities: ["audio"],
    multiSpeakerVoiceConfig: {
      speakerVoiceConfigs: [
        {
          speaker: "Speaker 1",
          voiceConfig: { prebuiltVoiceConfig: { voiceName: "Zephyr" } },
        },
        {
          speaker: "Speaker 2",
          voiceConfig: { prebuiltVoiceConfig: { voiceName: "Puck" } },
        },
      ],
    },
  };

  const model = "gemini-2.5-flash-preview-tts";
  const contents = [
    {
      role: "user",
      parts: [
        {
          text: `Read aloud in a warm, welcoming tone
Speaker 1: Hello! We're excited to show you our native speech capabilities
Speaker 2: Where you can direct a voice, create realistic dialog, and so much more. Edit these placeholders to get started.`,
        },
      ],
    },
  ];

  const response = await ai.models.generateContentStream({
    model,
    config,
    contents,
  });

  for await (const chunk of response) {
    const cand = chunk.candidates?.[0]?.content;
    if (!cand?.parts) continue;
    const part = cand.parts[0];

    if (part.inlineData) {
      const fileName = "test";
      const { mimeType, data } = part.inlineData;
      let fileExtension = mime.getExtension(mimeType || "");
      let buffer = Buffer.from(data || "", "base64");

      if (!fileExtension) {
        fileExtension = "wav";
        buffer = convertToWav(data || "", mimeType || "");
      }

      saveBinaryFile(`${fileName}.${fileExtension}`, buffer);
    } else if (chunk.text) {
      console.log(chunk.text);
    }
  }
}

main();

function convertToWav(rawData, mimeType) {
  const options = parseMimeType(mimeType);
  const wavHeader = createWavHeader(rawData.length, options);
  const audioBuffer = Buffer.from(rawData, "base64");
  return Buffer.concat([wavHeader, audioBuffer]);
}

function parseMimeType(mimeType) {
  const [fileType, ...params] = mimeType.split(";").map((s) => s.trim());
  const [, format] = fileType.split("/");
  const options = { numChannels: 1 };

  if (format && format.startsWith("L")) {
    const bits = parseInt(format.slice(1), 10);
    if (!isNaN(bits)) options.bitsPerSample = bits;
  }

  for (const param of params) {
    const [key, value] = param.split("=").map((s) => s.trim());
    if (key === "rate") options.sampleRate = parseInt(value, 10);
  }

  return options;
}

function createWavHeader(dataLength, options) {
  const { numChannels, sampleRate, bitsPerSample } = options;
  const byteRate = (sampleRate * numChannels * bitsPerSample) / 8;
  const blockAlign = (numChannels * bitsPerSample) / 8;
  const buffer = Buffer.alloc(44);

  buffer.write("RIFF", 0);
  buffer.writeUInt32LE(36 + dataLength, 4);
  buffer.write("WAVE", 8);
  buffer.write("fmt ", 12);
  buffer.writeUInt32LE(16, 16);
  buffer.writeUInt16LE(1, 20);
  buffer.writeUInt16LE(numChannels, 22);
  buffer.writeUInt32LE(sampleRate, 24);
  buffer.writeUInt32LE(byteRate, 28);
  buffer.writeUInt16LE(blockAlign, 32);
  buffer.writeUInt16LE(bitsPerSample, 34);
  buffer.write("data", 36);
  buffer.writeUInt32LE(dataLength, 40);

  return buffer;
}

これを実行して、合成された音声の wavファイルが生成されたのを確認できました。

curl を使ったサンプル

ちなみに、先ほどの選択肢で REST を選んだ場合、以下の内容が提示されました。

#!/bin/bash
set -e -E

GEMINI_API_KEY="$GEMINI_API_KEY"
MODEL_ID="gemini-2.5-flash-preview-tts"
GENERATE_CONTENT_API="streamGenerateContent"

cat << EOF > request.json
{
    "contents": [
      {
        "role": "user",
        "parts": [
          {
            "text": "Read aloud in a warm, welcoming tone\nSpeaker 1: Hello! We're excited to show you our native speech capabilities\nSpeaker 2: Where you can direct a voice, create realistic dialog, and so much more. Edit these placeholders to get started."
          },
        ]
      },
    ],
    "generationConfig": {
      "responseModalities": ["audio", ],
      "temperature": 1,
      "speech_config": {
        "multi_speaker_voice_config": {
          "speaker_voice_configs": [
            {
              "speaker": "Speaker 1",
              "voice_config": {
                "prebuilt_voice_config": {
                  "voice_name": "Zephyr"
                }
              }
            },
            {
              "speaker": "Speaker 2",
              "voice_config": {
                "prebuilt_voice_config": {
                  "voice_name": "Puck"
                }
              }
            },
          ]
        },
      },
    },
}
EOF

curl \
-X POST \
-H "Content-Type: application/json" \
"https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:${GENERATE_CONTENT_API}?key=${GEMINI_API_KEY}" -d '@request.json'
1
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?