高速なTTSサービス「Cartesia」を試す
とあるところで知った。
2つのサービスを提供している
-
Sonic: 音声生成API、いわゆるTTS。
- ストリーミング用に最適化された高速なTTS
- ピッチ・スピード・感情・発話の細かい制御が可能
- 15秒で音声クローンが可能
- 日本語を含む多言語に対応
- On-Device: State Spaceモデルを使用したマルチモーダルAIサービス?
有償サービスではあるが、このSonicというTTSを試してみる。
参考
公式ドキュメント
概要
Cartesiaへようこそ
当社のAPIは、開発者が自然で応答性に優れたリアルタイムのマルチモーダルAI体験を構築することを可能にします。
Cartesia APIは現在、当社の最先端の多言語生成音声モデルであるSonicを提供しています。Sonicはテキスト入力を受け取り、極めてリアルな音声で応答します。また、音声を複製することも可能です。速度や感情、発音やアクセントなど、あらゆる側面を制御して生成することができます。
**Sonicは世界最速のテキスト読み上げモデルです。**最初のバイトのオーディオをわずか90ミリ秒でストリーミングできるため、リアルタイムや会話形式の体験に最適です。(例えるなら、まばたきの約2倍の速さです。)
**Sonicは、世界最高品質のテキスト読み上げモデルです。**スピードが最優先事項でない場合でも、コンテンツのナレーション、動画の吹き替え、その他どのような用途であっても、Sonicは最良の選択です。
利用可能なSonicモデルのバリエーションとその機能については、「モデル」セクションをご覧ください。
利用可能なモデル
Cartesiaは、テキスト音声変換用の高精度かつ低レイテンシのSonicモデルを含む、最先端のモデル群を提供しています。
利用可能なモデルのサマリ
モデルID | 説明 | 利用可能な言語 |
---|---|---|
sonic | 現行安定版のエイリアス現在はsonic-2024-12-12を指す。 | en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr |
sonic-preview | プレビュー版。 | en |
sonic-2024-12-12 | 2024年12月更新。 | en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr |
sonic-2024-10-19 | 2024年10月更新。 | en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr |
sonic-english | [廃止予定] 英語専用モデル。 | en |
sonic-multilingual | [廃止予定] 多言語対応モデル。 | en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr |
各モデルの説明
Sonic
特徴 | 詳細 |
---|---|
モデルエイリアス | sonic |
リリース日 | 2024年12月 |
最新バージョン | sonic-2024-12-12 |
特徴 | 高精度・低遅延・表現力豊かな音声生成 |
Sonic Preview
特徴 | 詳細 |
---|---|
モデルエイリアス | sonic-preview |
利用可能な言語 | en |
特徴 | プレビュー版で最新機能をテスト可能 |
Sonic English (廃止予定)
特徴 | 詳細 |
---|---|
モデルID | sonic-english |
リリース日 | 2024年5月 |
最新バージョン | 2024年10月 |
利用可能な言語 | en |
機能 | 略語、イニシャリズム、数字、日付、電話番号、SSNの対応 |
問題点 | 連続した単語や長い数字で生成がループ・高速化する場合がある |
Sonic Multilingual (廃止予定)
特徴 | 詳細 |
---|---|
モデルID | sonic-multilingual |
リリース日 | 2024年6月 |
最新バージョン | 2024年9月 |
利用可能な言語 | en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr |
機能 | 数字、日付、電話番号の対応(en, fr, de, es, zh) |
問題点 | 日本語・ポルトガル語で一部不正確な結果が出る場合がある |
推奨事項
-
sonic
とsonic-preview
は最新安定版およびプレビュー版として使用可能。 - 本番環境では日付バージョンのモデルを推奨。
- 廃止予定のモデルは今後の使用を避け、
sonic
を利用することを推奨。
料金
プラン
プラン | 料金 | クレジット | 同時生成数 | 利用可能な機能等 |
---|---|---|---|---|
Free | $0/月 | 10K(約13分に相当) | 1 | 15言語での音声生成 Discordでのサポート 共有時はCartesiaのクレジット表記が必要 |
Pro | $5/月 | 100K(約2時間に相当) | 3 | 5秒の音声から音声クローンが可能 あらゆる音声クリップの音声を変更可能 音声をあらゆる言語・アクセントでローカライズ 商用利用 |
Startup | $49/月 | 1.25M(約27時間に相当) | 5 | Proにあるすべての機能 スタートアップ向けにより高い上限設定 |
Scale | $299/月 | 8M(約173時間に相当) | 15 | - Starupにあるすべての機能 ビジネスのスケールに対してより高い上限設定 |
Enterprise | カスタム | カスタム | カスタム | Scaleにあるすべての機能 Slackでの専任サポート SOC2 Type IIコンプライアンス SSO 音声モデルのファインチューニング カスタムのSLAと制限 |
音声クローンや音声変換を利用するには、最低でもProプランが必要になる。TTSだけなら無料でも使えそう。
サービスごとのクレジット消費
- TTS: 1文字で1クレジット
- 音声変換(ボイスチェンジャー): 1秒で15クレジット
その他FAQもあるので、実際に利用する場合は見ておくと良い。
クライアントSDK
PythonとJavaScript/TypeScriptがある。Pythonで試してみる。なお、試すのはTTSのみ。音声クローンや音声変換は料金プランの変更が必要になるので、一旦はFreeプランで進めようと思う。
事前準備
今回はローカルのMac上で試す。以下の準備が必要。
- Cartesiaでアカウント作成
- APIキーを作成
- ffmpegをインストール
以下でアカウントを作成
アカウント作成後、Playgroundが表示されるので、APIキーを作成。APIキーは二度と表示されないので安全な場所に控えておくこと。
Macでffmpegをインストールしておく
brew install ffmpeg
brew info ffmpeg
==> ffmpeg: stable 7.1 (bottled), HEAD
Play, record, convert, and stream audio and video
https://ffmpeg.org/
Installed
/opt/homebrew/Cellar/ffmpeg/7.1_4 (287 files, 52.1MB) *
(snip)
Python仮想環境を作成。自分はmiseを使うが、適宜。
mkdir cartesia-work && cd cartesia-work
mise use python@3.12
cat << 'EOS' >> .mise.toml
[env]
_.python.venv = { path = ".venv", create = true }
EOS
パッケージインストール
pip install cartesia
pip freeze | grep -i cartesia
cartesia==1.3.1
APIキーを環境変数にセットしておく
export CARTESIA_API_KEY="XXXXXXXXXX"
利用可能な音声の確認
利用可能な音声を確認する。
from cartesia import Cartesia
import os
import json
from collections import Counter
client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
# 各音声ごとに以下が返される
# - id
# - mode
# - is_public
# - name
# - description
# - created_at
# - gender
# - language
# - embedding
voices = client.voices.list()
print("全音声の数:", len(voices))
print()
print("各言語ごとの音声数:")
language_counts = Counter(voice.get("language") for voice in voices)
for language, count in sorted(language_counts.items()):
print(f"{language}: {count}")
print()
print("日本語音声:")
for voice in voices:
if voice.get("language") == "ja": # languageが"ja"である場合
# embeddingはデータが大きいので省略している
filtered_voice = {key: value for key, value in voice.items() if key != "embedding"}
print(json.dumps(filtered_voice, indent=2, ensure_ascii=False))
全音声の数: 138
各言語ごとの音声数:
de: 7
en: 73
es: 10
fr: 7
hi: 5
it: 3
ja: 4
ko: 3
nl: 3
pl: 4
pt: 3
ru: 4
sv: 2
tr: 3
zh: 7
日本語音声:
{
"id": "2b568345-1d48-4047-b25f-7baccf842eb0",
"mode": "stability",
"is_public": true,
"name": "Japanese Woman Conversational",
"description": "This voice is gentle and friendly, perfect for casual conversations in Japanese",
"created_at": "1970-01-17T16:00:00-08:00",
"gender": "feminine",
"language": "ja"
}
{
"id": "97e7d7a9-dfaa-4758-a936-f5f844ac34cc",
"mode": "stability",
"is_public": true,
"name": "Japanese Man Book",
"description": "This voice is deep and even, perfect for narrational content in Japanese",
"created_at": "1970-01-01T16:00:00-08:00",
"gender": "masculine",
"language": "ja"
}
{
"id": "e8a863c6-22c7-4671-86ca-91cacffc038d",
"mode": "stability",
"is_public": true,
"name": "Japanese Male Conversational",
"description": "This voice is clear and confident, perfect for a Japanese call center agent ",
"created_at": "1970-01-08T16:00:00-08:00",
"gender": "masculine",
"language": "ja"
}
{
"id": "44863732-e415-4084-8ba1-deabe34ce3d2",
"mode": "stability",
"is_public": true,
"name": "Japanese Children Book",
"description": "This voice is young and expressive, perfect for character work in games and videos",
"created_at": "1970-02-06T16:00:00-08:00",
"gender": "feminine",
"language": "ja"
}
日本語は
- 女性・会話
- 男性・朗読
- 男性・会話
- 女性(子供)・朗読
の4種類みたい。TTSを行う際はこのIDを指定する。
TTS
まずはファイルに出力してみる。
from cartesia import Cartesia
import os
client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
data = client.tts.bytes(
model_id="sonic",
transcript="こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。",
voice_id="2b568345-1d48-4047-b25f-7baccf842eb0", # 日本語・女性・会話
# 出力フォーマットは https://docs.cartesia.ai/api-reference/tts/bytes で確認
output_format={
"container": "wav",
"encoding": "pcm_f32le",
"sample_rate": 44100,
},
language="ja",
)
with open("cartesia_ja_female_conversational_sample01.wav", "wb") as f:
f.write(data)
生成された音声は以下。
ちょっと早口かな。多少違和感がある箇所は残るものの、結構自然な感じ。
他の音声についても。
日本語・男性・会話
日本語・男性・朗読
日本語・女性(子供)・朗読
説明とマッチしている音声か?というと微妙に違うような気もするが、発話の調整ができるようなので、どこまでできるかは後ほど見ていきたい。
Server-Sent Events (SSE)、つまりストリーミング。pyaudioを使う。
pip install pyaudio
pip freeze | grep -i pyaudio
PyAudio==0.2.14
from cartesia import Cartesia
import pyaudio
import os
client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
voice_id = "2b568345-1d48-4047-b25f-7baccf842eb0" # 日本語・女性・会話
voice = client.voices.get(id=voice_id)
transcript="こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。"
model_id = "sonic"
output_format = {
"container": "raw",
"encoding": "pcm_f32le",
"sample_rate": 44100,
}
language="ja"
p = pyaudio.PyAudio()
rate = 44100
stream = None
# オーディオの生成とストリーミング
for output in client.tts.sse(
model_id=model_id,
transcript=transcript,
voice_embedding=voice["embedding"],
stream=True,
output_format=output_format,
language=language,
):
buffer = output["audio"]
if not stream:
stream = p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=True)
# オーディオデータをストリームに書き出す
stream.write(buffer)
stream.stop_stream()
stream.close()
p.terminate()
これ何度か実行して気づいたのだけど、常に同じ音声が生成されるわけではなく、生成するたびに微妙に違う結果になる。
非同期の場合。AsyncCartesia
を使う。
from cartesia import AsyncCartesia
import asyncio
import pyaudio
import os
async def write_stream():
client = AsyncCartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
voice_id="2b568345-1d48-4047-b25f-7baccf842eb0" # 日本語・女性・会話
voice = client.voices.get(id=voice_id)
transcript = "こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。"
model_id = "sonic"
output_format = {
"container": "raw",
"encoding": "pcm_f32le",
"sample_rate": 44100,
}
language="ja"
p = pyaudio.PyAudio()
rate = 44100
stream = None
# オーディオの生成とストリーミング
async for output in await client.tts.sse(
model_id=model_id,
transcript=transcript,
voice_embedding=voice["embedding"],
stream=True,
output_format=output_format,
language=language,
):
buffer = output["audio"]
if not stream:
stream = p.open(
format=pyaudio.paFloat32, channels=1, rate=rate, output=True
)
# オーディオデータをストリームに書き出す
stream.write(buffer)
stream.stop_stream()
stream.close()
p.terminate()
await client.close()
asyncio.run(write_stream())
同期・非同期ともにストリーミングだとかなりレスポンスは速く感じる。
Websocketを使用したTTS
CartesiaはWebsocketも提供している
from cartesia import Cartesia
import pyaudio
import os
client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
voice_id="2b568345-1d48-4047-b25f-7baccf842eb0" # 日本語・女性・会話
voice = client.voices.get(id=voice_id)
transcript = "こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。"
model_id = "sonic"
output_format = {
"container": "raw",
"encoding": "pcm_f32le",
"sample_rate": 22050,
}
language="ja"
p = pyaudio.PyAudio()
rate = 22050
stream = None
# websocket接続を作成
ws = client.tts.websocket()
# websocketを使用してオーディオの生成とストリーミング
for output in ws.send(
model_id=model_id,
transcript=transcript,
voice_embedding=voice["embedding"],
stream=True,
output_format=output_format,
language="ja",
):
buffer = output["audio"]
if not stream:
stream = p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=True)
# オーディオデータをストリームに書き出す
stream.write(buffer)
stream.stop_stream()
stream.close()
p.terminate()
ws.close() # websocket接続をクローズ
Websocketの場合は「Conditioning speech on previous generations」に対応している。これは何かというと、
-
長い入力テキストでもリアルタイム性を確保
- 一般的には、長い文章をまとめて送信すると、処理に時間がかかる
- テキストを小分けにして逐次送信しつつ音声生成が可能することで、リアルタイム性を向上可能
-
音声の一貫性を確保
- 一般的には、文章を小分けにして音声生成した場合、文脈のつながりが考慮されないため、発話が不自然になってしまうことがある
- 前に生成した音声の情報を考慮し、文脈や音調が自然になるように続きの音声を生成可能。
が可能になる、つまりストリーミングは入出力の両方で可能、かつ、その場合でも自然に音声生成できるということらしい。Cartesiaではこれを"Audio Continuations"(オーディオ継続)と呼んでいる。
import asyncio
import os
import pyaudio
from cartesia import AsyncCartesia
async def send_transcripts(ctx):
voice_id="2b568345-1d48-4047-b25f-7baccf842eb0" # 日本語・女性・会話
model_id = "sonic"
output_format = {
"container": "raw",
"encoding": "pcm_f32le",
"sample_rate": 44100,
}
language="ja"
transcripts = [
"こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。",
"このAPIを使えば、高品質な音声をリアルタイムで生成することができます。",
"さらに、文章を小分けにして送信しながら、文脈を考慮したスムーズな音声生成が可能です。",
"多言語対応や感情表現を組み込むこともでき、幅広い用途で活用できます。",
"音声の速度や感情のトーンを調整することで、より自然なコミュニケーションを実現します。",
"Cartesia APIを使って、新しい音声体験をぜひお試しください!"
]
for transcript in transcripts:
# 入力テキストが利用可能になったら送信
await ctx.send(
model_id=model_id,
transcript=transcript,
voice_id=voice_id,
continue_=True, # 文脈を保持して次の入力を待つ
output_format=output_format,
language=language,
)
# 入力がこれ以上送信されないことを示す。これをしない場合、5秒間の無操作後にコンテキストが閉じられる。
await ctx.no_more_inputs()
async def receive_and_play_audio(ctx):
p = pyaudio.PyAudio()
stream = None
rate = 44100
async for output in ctx.receive():
buffer = output["audio"]
if not stream:
stream = p.open(
format=pyaudio.paFloat32,
channels=1,
rate=rate,
output=True
)
stream.write(buffer)
stream.stop_stream()
stream.close()
p.terminate()
async def stream_and_listen():
client = AsyncCartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
# WebSocket接続を設定
ws = await client.tts.websocket()
# 音声送受信用のコンテキストを作成
ctx = ws.context() # コンテキストIDが指定されていない場合はランダムに生成される
send_task = asyncio.create_task(send_transcripts(ctx))
listen_task = asyncio.create_task(receive_and_play_audio(ctx))
# 2つのコルーチンタスクを同時に呼び出す
await asyncio.gather(send_task, listen_task)
await ws.close()
await client.close()
asyncio.run(stream_and_listen())
コードとしては、
- WebSocket接続を確立し、「コンテキスト」を作成する
-
ws.context()
で、音声生成用のコンテキストが作成される
-
- テキストを順次送信
-
ctx.send()
で、コンテキスト内でテキストを逐次送信 -
continue_=True
を指定すると、コンテキストが維持される - これにより、サーバー側で前後の文脈を考慮した音声生成が可能になる
-
- 音声データをリアルタイムで再生
-
ctx.receive()
で、サーバーから逐次送られる音声データを受信。 -
buffer
に格納された音声データをリアルタイムで再生。
-
- コンテキストの終了
- テキストの送信が完了したら、
no_more_inputs()
を呼び出してコンテキストを終了。 - 呼び出さない場合、コンテキストは5秒間の無操作後に自動終了。
- テキストの送信が完了したら、
という感じ。
実際に試した動画はこちら。
多少の違和感はあるものの、それでもかなりのリアルタイム性が維持されているのは結構凄いのではないだろうか?
上記の例は非同期を使用しているが、同期の場合でも1秒未満の間隔でテキストチャンクを生成するジェネレータを使えば同じことができる。(ただし非同期の場合とは異なるAPIを使うらしい)
from cartesia import Cartesia
import pyaudio
import os
client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
transcripts = [
"こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。",
"このAPIを使えば、高品質な音声をリアルタイムで生成することができます。",
"さらに、文章を小分けにして送信しながら、文脈を考慮したスムーズな音声生成が可能です。",
"多言語対応や感情表現を組み込むこともでき、幅広い用途で活用できます。",
"音声の速度や感情のトーンを調整することで、より自然なコミュニケーションを実現します。",
"Cartesia APIを使って、新しい音声体験をぜひお試しください!"
]
# 各テキストをスペースで終わらせると、音声がスムーズになる
def chunk_generator(transcripts):
for transcript in transcripts:
if transcript.endswith(" "):
yield transcript
else:
yield transcript + " "
voice_id="2b568345-1d48-4047-b25f-7baccf842eb0" # 日本語・女性・会話
model_id = "sonic-english"
output_format = {
"container": "raw",
"encoding": "pcm_f32le",
"sample_rate": 44100,
}
language = "ja"
p = pyaudio.PyAudio()
rate = 44100
stream = None
# WebSocket接続を設定
ws = client.tts.websocket()
# 音声送受信用のコンテキストを作成
ctx = ws.context() # コンテキストIDが指定されていない場合はランダムに生成される
# テキストジェネレータを渡して音声を生成・ストリーム送信
output_stream = ctx.send(
model_id=model_id,
transcript=chunk_generator(transcripts),
voice_id=voice_id,
output_format=output_format,
language=language,
)
for output in output_stream:
buffer = output["audio"]
if not stream:
stream = p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=True)
# 音声データをストリームに書き込む
stream.write(buffer)
stream.stop_stream()
stream.close()
p.terminate()
ws.close() # WebSocket接続を閉じる
ドキュメントにも記載がある。
また、WebSocketではタイムスタンプも取得できる。音声生成時の各単語に開始時刻と終了時刻を付与することができる。これを使えば、音声と入力テキストの同期が必要なユースケース、例えば、字幕生成などで活用できる。ただし日本語は非対応なので、英語の場合の例。
from cartesia import Cartesia
import os
client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
transcript = "Hello! This is a sample of audio generation using Cartesia's Text-to-Speech API with timestamps added via WebSocket."
voice_id = "87748186-23bb-4158-a1eb-332911b0b708"
model_id = "sonic"
output_format = {
"container": "raw",
"encoding": "pcm_f32le",
"sample_rate": 44100,
}
language = "en"
# WebSocket接続を作成
ws = client.tts.websocket()
# タイムスタンプを含む音声生成リクエストを送信
response = ws.send(
model_id=model_id,
transcript=transcript,
voice_id=voice_id,
output_format=output_format,
language=language,
stream=False, # ストリーミングを無効化して完全なレスポンスを取得
add_timestamps=True # タイムスタンプを有効化
)
# タイムスタンプ情報を取得
word_timestamps = response['word_timestamps']
# 単語ごとのタイムスタンプを出力
words = word_timestamps['words']
start_times = word_timestamps['start']
end_times = word_timestamps['end']
for word, start, end in zip(words, start_times, end_times):
print(f"単語: {word}, 開始時刻: {start:.2f}, 終了時刻: {end:.2f}")
# WebSocket接続を閉じる
ws.close()
単語: Hello!, 開始時刻: 0.02, 終了時刻: 0.36
単語: This, 開始時刻: 1.32, 終了時刻: 1.46
単語: is, 開始時刻: 1.46, 終了時刻: 1.60
単語: a, 開始時刻: 1.78, 終了時刻: 1.78
単語: sample, 開始時刻: 1.94, 終了時刻: 2.34
単語: of, 開始時刻: 2.34, 終了時刻: 2.40
単語: audio, 開始時刻: 2.64, 終了時刻: 2.98
単語: generation, 開始時刻: 3.14, 終了時刻: 3.60
単語: using, 開始時刻: 3.82, 終了時刻: 4.06
単語: Cartesia's, 開始時刻: 4.18, 終了時刻: 4.50
単語: Text-to-Speech, 開始時刻: 4.50, 終了時刻: 5.73
単語: API, 開始時刻: 5.73, 終了時刻: 6.48
単語: with, 開始時刻: 6.94, 終了時刻: 6.94
単語: timestamps, 開始時刻: 7.24, 終了時刻: 7.90
単語: added, 開始時刻: 8.02, 終了時刻: 8.38
単語: via, 開始時刻: 8.38, 終了時刻: 8.64
単語: WebSocket., 開始時刻: 8.64, 終了時刻: 9.18
マルチリンガルなTTS
アルファ版扱いのようだが、同じ音声で複数の言語を発話することができる。
といっても、実は上でやっているようにlanguage
で異なる言語を指定するだけ。
from cartesia import Cartesia
import os
client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
data = client.tts.bytes(
model_id="sonic",
transcript="Hello! This is a sample of audio generation using Cartesia's Text-to-Speech API with Japanese voice speaking in English.",
voice_id="2b568345-1d48-4047-b25f-7baccf842eb0", # 日本語・女性・会話
output_format={
"container": "wav",
"encoding": "pcm_f32le",
"sample_rate": 44100,
},
language="en", # 英語
)
with open("cartesia_ja_female_conversation_sample_en.wav", "wb") as f:
f.write(data)
なお、READMEではsonic-multilingual
を使っているが、このモデルはDeprecatedなので、通常のsonic
を使用した。
ちなみにドキュメントに以下のようなことが書いてあった。
Sonicの音声にはアクセントがあります。 Sonicの音声を使って別の言語の音声を生成しようとすると、やはり同じアクセントになります。 例えば、アメリカ人の声を使ってスペイン語の音声を作成しようとすると、出来上がったスペイン語の音声はアメリカ人のアクセントになってしまいます。
ターゲットとする言語のネイティブスピーカーのような音声を得るには、APIまたはプレイグラウンドを通じてSonicのローカライズ機能を使用します。
ローカライズ機能は、ローカライズする音声、音声の性別、ローカライズするターゲット言語とアクセントを受け取り、音声を生成するために使用できるエンベッディングを生成します(または、新しい音声として保存します)。
なるほど、どうやら設定を行ったうえで新しい音声を作成するような流れになる様子。
音声の速度や感情を制御する
Experimentalステータスではあるが、音声の再生速度や感情を調整することができる。
from cartesia import Cartesia
import os
client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
data = client.tts.bytes(
model_id="sonic",
transcript="こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。",
voice_id="2b568345-1d48-4047-b25f-7baccf842eb0", # 日本語・女性・会話
output_format={
"container": "wav",
"encoding": "pcm_f32le",
"sample_rate": 44100,
},
language="ja",
# _experimental_voice_controls で速度や感情を設定
_experimental_voice_controls={
# speed: 音声速度を速める。2つの指定方法がある。
# - slowest/slow/normal/fast/fastestの文字列で指定
# - -1.0〜1.0の少数で指定(-が遅い)
"speed": "slowest",
# emotion: 感情を設定する。`emotion_name:level`で指定。
# - emotion_name: anger/positivity/surprise/sadness/curiosity から指定
# - level: lowest/low/high/hightestから指定(mediumがデフォルトで指定不要)
"emotion": ["positivity:highest"] # 感情をポジティブで強調
},
)
with open("cartesia_ja_female_conversational_slowest_hightest_positivity_sample01.wav", "wb") as f:
f.write(data)
ドキュメントにも記載がある
その他
IPA記号による発音の指定ができる
SSMLの<break>
タグが使える
SSMLの<say-as interpret-as="digit">
に似た<spell>
がある。スペルとして読み上げる。
各種インテグレーション
特定のプラットフォームやフレームワークとのインテグレーションもある
Twilio
Pipecat
LiveKit
音声クローン
音声クローン。Proプラン以上にする必要があるので、今回は試していないが、少し気になる。$5だし試してみようかな?
まとめ
全然知らなかったけど、リアルタイム性を踏まえるとかなり凄いのでは?
ここのところ、LLMをSTT・TTSと組み合わせてリアルタイムに近い音声インタフェースを色々試している。自分が試した限りでは、
- STTはまあそれなりに高速化できる、結構複雑化するけど。
- LLMのオーバーヘッドはたしかに一番大きいのだけども、出力はストリーミングでなんとか。
- TTSにストリーミングで渡したとしても、オーバーヘッドはそこそこ気になるレベル&処理が複雑になる。
という感じで、実はTTSもなかなか難しいと感じていたのだが、Cartesiaはめちゃめちゃレスポンスが速い上に入力ストリーミングもあって、音声インタフェースでレイテンシーを極力小さくしたい、というニーズをかなりいい感じに満たせるのではないか?と感じた。
多少の読み間違いはあるし、イントネーションがおかしいところもあるのだが、
- OpenAIでも結構読み間違えるし、ElevenLabsはかなり間違える。それらと比較してもそこまで悪くはない気がする。
- 生成速度も高速、かつ、入出力でストリーミングに対応しており、入力ストリーミングの場合にチャンク単位での生成時における不自然さを解消する仕組みがある。かなりリアルタイムに近いTTSが可能
- 上記をかなりシンプルに書ける
- 試していないが、音声クローンにも対応している
ということを考えると、かなり良いのではないか?という気がする。
のだが、調べても日本語で書いた記事は出てこない。なぜだろう?と思って調べてみたけど、TTSは去年の夏頃に出たばかりなのね。多分まだ新しくて知名度がそれほどないのではないかという気がする。
LLMのストリーミングと組み合わせてみたい。ネイティブで実装しても良いと思うけど、インテグレーションで記載されている、PipecatとかLiveKitと組み合わせるのも良さそう。
やっぱりちょっと長い文章を食わせたり、固有名詞なんかはかなり読み間違える感があるなぁ。ElevenLabsと比較すると幾分かマシではあるが、これはもうモデルの学習率の問題だよな。