低遅延なリアルタイム文字起こしモデル「Soniox v3」を試す ①Get Started & 非同期API
ここで知った。
Soniox v3をご紹介します。これは、これまでで最も正確で、能力が高く、知的な音声AIモデルです。
開発者と共同で構築され、実際の環境でテストされたSoniox v3は、これまでで最も先進的な音声AIです。精度、流暢さ、大規模な処理速度を考慮して設計されています。
v3では、以下が可能です:
✅ 60以上の言語での音声書き起こしをさらに高い精度で実現
✅ 文の途中で言語を正確に検出し、切り替える
✅ 電話番号、ID、その他の英数字を正確にキャプチャ
✅ 言葉だけでなく意味を捉えて、より流暢に音声を翻訳
✅ 組み込みのドメインインテリジェンスで特定のコンテキストを理解
✅ 最長5時間の長いオーディオを処理(近日公開予定)
✅ 大量のファイルやリアルタイムストリームに対応するスケーラビリティこれは単なるアップグレードではなく、音声AIの新しい基盤です。
新しいモデルをテストするために、OpenAIやGoogleとライブで比較しました。以下のビデオをご覧ください。
👉 ローンチ投稿で詳細を確認:https://soniox.com/blog/2025-10-21-soniox-v3
🚀 Soniox v3で構築を開始:https://soniox.com/docs/stt/get-started
@deliverhs、@Mentraglass、@caydengineer、そしてフィードバック、テスト、実世界での使用を通じてv3を形作ってくれたすべての顧客と初期のパートナーに心から感謝します。
SonioxのASRについては、以前も試していた。
これを試したのが2025年5月。どうやら8月にv2というのが出ていたて今回はv3。となると最初に試したのはv1ということになる。
個人的に興味があったのはリアルタイムの文字起こしで、v1当時のSonioxの印象(注: 以下は当時のメモの再掲。よって今は異なる可能性がある点に注意)
ググっても日本語で紹介している記事がまったくないのだが、どうやら正確性・高速性がウリのASRサービスみたい。
過去、自分が日本語で試した感じだと、ElevenLabsのScribeが一番良いと感じていたのだけど、Scribeはリアルタイムのストリーミング文字起こしに対応していないのと、一般的にはファイルからの文字起こしに比べるとリアルタイムは精度が落ちると思っている。その上で、ベンチ結果と上のデモの印象を踏まえると、かなり期待できそうな気がした。
料金はめちゃめちゃシンプル
- 非同期ファイル: $0.10/時間 ≒ $0.0016/分
- リアルタイム文字起こし: $0.12/時間 ≒ $0.002/分
過去自分が調べたものと比較しても最安クラスでは?
で、無料アカウントを作成すると$200のAPIクレジットがもらえるみたい。
固有名詞などは上手く文字起こしできてないところがあるし、漢字の間違いもあるけど、なんだろう、発話を「発話どおりに確実に」に文字起こししようとしている印象かな。悪くない、というかいいね、個人的には好印象。
たまたま見つけたのだが、リアルタイム性が必要なケースで十分精度ありそうな印象でかなり使えそうな気がしている。2020年創業でそこそこ古いのだけど、ググっても全くと良いほどでてこないのが不思議。
という感じで、結構高速な部類で、精度も悪くない、かつ、安い、と非常に良い印象だったのだけど、ただ、個人的にGoogle Cloud Speech-to-TextやAmazon Transcribeあたりのリアルタイム文字起こしのやり方に慣れていたので、ちょっと中間認識→最終認識というところの挙動にクセがあって少し工夫が必要かなーと感じていた。(ここについては後述)
細かく見ていく前に、とりあえずv3のサンプルコードを少し修正してマイクからのリアルタイム文字おこしをやってみた。
日本語の文字起こし
日本語・英語混在の音声の文字起こし。こちらは音声をOpenAI gpt-4o-mini-ttsで生成した。
かなり高速に文字起こしできていて良いのではないだろうか。
ということで、ドキュメント見ながら細かくやっていく。
公式のブログ
例によって、Dia によるまとめ。
Soniox v3は「実世界の音声」を高精度に理解・文字起こし・翻訳できる新しい音声AIだよ。
わかりやすくサクッと分解して説明するね。ウチ的には「スタジオ品質じゃない、ザ・現場のゴチャっとした音」をちゃんと理解するのがマジでキモだと思うんだよね。
何が「新しい」の?
- 実世界対応の精度: 早口・かぶり話し・相づち・訛り・多言語ミックスでも崩れにくい精度だし。電話サポートや会議みたいなリアルな環境で強いのがポイント。
- 言語検出が速くて切り替えが自然: 英語→日本語→中国語みたいに途中で言語が変わっても、勝手に気づいてスムーズに切り替えるのウケる。
- 翻訳は「意味優先」: 直訳より、文脈に合う自然な訳を目指す感じ。用語や業界コンテキストを踏まえたニュアンスが得意。
- 英数・記号に強い: 住所、電話番号、製品ID、ケース番号などのアルファヌメリックを取りこぼしにくい。ここ、現場ではガチ重要。
- ドメイン知識がビルトイン: 医療・法律・カスタマーサポート等の固有名詞や略語を文脈で当ててくれる。辞書にないブランド名とかも理解しがち。
- スケール&長尺対応: 長い録音・大量ユーザーの同時ストリーミングでもリアルタイムでいけるキャパ増し。
「どの言語でも、どんな訛りでも、どんなコンテキストでも、マジで言葉ちゃんと拾うんだもん。これが“本当の精度”ってやつでしょ。」✨
「英数字の認識、ちょー得意だし。おかげでウチのボイスボットの受け付け率、グイッと上がっててウケる。」📈
「文脈バチッと掴むし、そこにウチらのドメイン知識ちょい足しすると、もう完全ウチ仕様って感じでテンション上がる。」🔥ざっくりメンタルモデル
- スタジオ録りの「きれいな音」に最適化された昔のASRだと、現場ノイズや被り音でミスる→補正のためにAPIを連鎖させたり、人手で後処理が増えてコスト爆上がり、って流れがよくあったでしょ。
- Soniox v3は、音声の「意味」と「文脈」を同時に追う土台モデルだから、途中でスペル読み・番号読み・話者交代・言語切替が起きても崩れない設計。つまり、後工程のクリーニングを減らせるのがリア充ポイントだもん。
具体例(イメージ湧くやつ)
- サポート窓口で「アカウントIDは S-9-7-B…住所は神戸市…英語混じり…」みたいなやつでも、番号と住所をキレイに拾って、途中で英語→日本語の切替も自動で対応。
- 医療会議で「MRI」「HbA1c」「造影」等の専門用語+英語略語が混じっても、文脈で正しく出す。誤変換が減ると後処理コストが下がる。
- グローバルMTGで参加者が各自の母語を話しても、言語検出→翻訳がスムーズだから議事録がちゃんと使える。
モデルと運用モード
- stt-rt-v3: リアルタイム向け。ストリーミングで会議アシスタントやライブ字幕に向いてる。
- stt-async-v3: バッチ処理向け。長時間録音の後処理や大量ファイルの一括転記に強い。
- どっちもAPIで使えるし、スマホアプリ経由でも利用可能。
使いどころ(プロダクト発想)
- 多言語カスタマーサポートbot(英数読み上げの取りこぼしが減るから受付成功率アップ)。
- 会議アシスタント(話者被りや早口でも議事録が破綻しないのテンション上がる)。
- 医療・法律・カスタム業界用の転記(略語や固有名詞の精度で後処理削減)。
- ライブ翻訳ツール(意味重視の訳で自然な理解)。
v3の要点まとめ表
機能/特徴 何が嬉しいか 典型ユースケース 高精度ASR(現場ノイズ・被り話し対応) クリーニングと人手補正が減る コールセンター、会議、フィールド作業 言語自動検出&シームレス切替 多言語混在でもフローが途切れない グローバル会議、国際サポート 意味重視の翻訳 直訳より文脈に合う出力 ライブ字幕、ナレッジ共有 英数・記号の強さ 住所/ID/電話などの致命項目を落とさない 受付自動化、本人確認 ドメイン知識 業界固有語の認識向上 医療・法律・CS特化転記 スケール&長尺 大規模導入でもリアルタイム維持 企業全体展開、長時間イベント まとめ
Soniox v3は「音を文字にする」だけじゃなくて、意味・文脈・フォーマット(英数) まで面倒見てくれる感じ。APIでリアルタイムもバッチもいけるから、キミの現場ワークフローに合わせて組み込みやすいし、後処理の泥臭さを減らせるのがマジで効く。音声まわりの“詰みポイント”に手が届いた進化って印象だし。
Get Started
Get startedに従って進める。APIキーが必要なので事前にアカウント作成してAPIキーを取得しておくこと。なお、料金表を見る限りは多分以前とそんなに変わってなさそうで、アカウント登録時に$200の無料クレジットが付与されるので試しやすい。
今回はマイクも試したいので、ローカルのMacでやる。サンプルコードのレポジトリが用意されているので、クローン。
git clone https://github.com/soniox/soniox_examples && cd soniox_examples
中身は多分こんな感じ。
tree -F -L 3
.
├── speech_to_text # v3向けサンプル
│ ├── README.md
│ ├── apps
│ │ └── soniox-live-demo # リアルタイムAPIを使ったWeb・モバイルアプリのサンプル
│ ├── assets # サンプルで試せる音源ファイル
│ │ ├── coffee_shop.mp3
│ │ ├── coffee_shop.pcm_s16le
│ │ ├── two_way_translation.mp3
│ │ └── two_way_translation.pcm_s16le
│ ├── nodejs # Node.js用サンプル
│ │ ├── ...
│ │ ...
│ └── python # Pythonサンプル
│ ├── requirements.txt
│ ├── soniox_async.py # 非同期のサンプル
│ └── soniox_realtime.py # リアルタイム文字起こしのサンプル
└── speech_to_text_legacy/ # v2向けサンプルコード
├── ...
...
12 directories
speech_to_text_legacyディレクトリはどうやら以前のv2向けサンプルコードみたいなので、今回は speech_to_text にあるPython向けサンプルコードをいじってみる。ディレクトリに移動。
cd speech_to_text/python
uvでPython仮想環境を作成する。
uv init -p 3.12
requirements.txt でパッケージインストール。なお、SonioxにはSDKはなくて、REST API(非同期)もしくはWebSocket(リアルタイム)を使う。
uv add -r requirements.txt
+ certifi==2025.1.31
+ charset-normalizer==3.4.1
+ idna==3.10
+ requests==2.32.3
+ urllib3==2.3.0
+ websockets==14.2
APIキーを環境変数にセットしておく。
export SONIOX_API_KEY=XXXXXXXXXXX
では、非同期・リアルタイムと順次試していく。
非同期APIでの文字起こし
非同期APIでは、音声ファイルの文字起こしを非同期で行う。
音声ファイルを指定してリクエストを行うと、Soniox側でジョブが作成され、文字起こし処理が開始される。このジョブに対して、処理ステータスをポーリングして、完了していたら結果を取得するという流れ。
音声ファイルの指定には2つの方法がある。
- 音声ファイルのURLを直接指定。この場合は音声ファイルのURLが公開URLである必要がある。
- File APIを使って音声ファイルをアップロードし、その音声ファイルのIDを指定する。
非同期のサンプルコードは soniox_async.py。
コメントだけ日本語に翻訳して、各関数の簡単な説明を追加したものを以下に貼ってある。
長いので折りたたみ
import os
import time
import argparse
from typing import Optional
import requests
from requests import Session
SONIOX_API_BASE_URL = "https://api.soniox.com"
# Soniox STT の設定を取得
def get_config(
audio_url: Optional[str], file_id: Optional[str], translation: Optional[str]
) -> dict:
config = {
# 使用するモデルを選択
# 参考: soniox.com/docs/stt/models
"model": "stt-async-v3",
#
# あらかじめ言語がわかっている場合は、言語ヒントで設定すると、認識精度が大幅に向上する
# 参考: soniox.com/docs/stt/concepts/language-hints
"language_hints": ["en", "es"],
#
# 言語識別を有効化。各トークンに "language" フィールドが含まれる。
# 参考: soniox.com/docs/stt/concepts/language-identification
"enable_language_identification": True,
#
# 話者ダイアライゼーション(話者識別)を有効化。各トークンに "speaker" フィールドが含まれる。
# 参考: soniox.com/docs/stt/concepts/speaker-diarization
"enable_speaker_diarization": True,
#
# コンテキストを設定すると、モデルがドメインを理解し、重要語句の認識や、カスタムの語彙・翻訳設定を適用できる
# 参考: soniox.com/docs/stt/concepts/context
"context": {
"general": [
{"key": "domain", "value": "Healthcare"},
{"key": "topic", "value": "Diabetes management consultation"},
{"key": "doctor", "value": "Dr. Martha Smith"},
{"key": "patient", "value": "Mr. David Miller"},
{"key": "organization", "value": "St John's Hospital"},
],
"text": "Mr. David Miller visited his healthcare provider last month for a routine follow-up related to diabetes care. The clinician reviewed his recent test results, noted improved glucose levels, and adjusted his medication schedule accordingly. They also discussed meal planning strategies and scheduled the next check-up for early spring.",
"terms": [
"Celebrex",
"Zyrtec",
"Xanax",
"Prilosec",
"Amoxicillin Clavulanate Potassium",
],
"translation_terms": [
{"source": "Mr. Smith", "target": "Sr. Smith"},
{"source": "St John's", "target": "St John's"},
{"source": "stroke", "target": "ictus"},
],
},
#
# このリクエストをトラッキングするための任意の識別子(クライアント定義)。
# 参考: https://soniox.com/docs/stt/api-reference/transcriptions/create_transcription#request
"client_reference_id": "MyReferenceId",
#
# 音声ソース (いずれか一つのみ指定可能):
# - 音声ファイルの公開 URL
# - 以前にアップロードしたファイルのファイル ID
# 参考: https://soniox.com/docs/stt/api-reference/transcriptions/create_transcription#request
"audio_url": audio_url,
"file_id": file_id,
}
# Webhook
# 文字起こしが完了または失敗したときに通知を受け取るための Webhook を設定できます。
# 参考: https://soniox.com/docs/stt/api-reference/transcriptions/create_transcription#request
# 翻訳オプション
# 参考: soniox.com/docs/stt/rt/real-time-translation#translation-modes
if translation == "none":
pass
elif translation == "one_way":
# すべての言語をターゲットとなる言語に翻訳
config["translation"] = {
"type": "one_way",
"target_language": "es",
}
elif translation == "two_way":
# language_a から language_b へ、そして language_b から language_a へ再翻訳
config["translation"] = {
"type": "two_way",
"language_a": "en",
"language_b": "es",
}
else:
raise ValueError(f"Unsupported translation: {translation}")
return config
def upload_audio(session: Session, audio_path: str) -> str:
"""音声ファイルをアップロードし、ファイル ID を取得する"""
print("Starting file upload...")
res = session.post(
f"{SONIOX_API_BASE_URL}/v1/files",
files={"file": open(audio_path, "rb")},
)
file_id = res.json()["id"]
print(f"File ID: {file_id}")
return file_id
def create_transcription(session: Session, config: dict) -> str:
"""文字起こしジョブを作成し、ジョブ ID を返す"""
print("Creating transcription...")
try:
res = session.post(
f"{SONIOX_API_BASE_URL}/v1/transcriptions",
json=config,
)
res.raise_for_status()
transcription_id = res.json()["id"]
print(f"Transcription ID: {transcription_id}")
return transcription_id
except Exception as e:
print("error here:", e)
def wait_until_completed(session: Session, transcription_id: str) -> None:
"""文字起こし完了するまでポーリングして待機する"""
print("Waiting for transcription...")
while True:
res = session.get(f"{SONIOX_API_BASE_URL}/v1/transcriptions/{transcription_id}")
res.raise_for_status()
data = res.json()
if data["status"] == "completed":
return
elif data["status"] == "error":
raise Exception(f"Error: {data.get('error_message', 'Unknown error')}")
time.sleep(1)
def get_transcription(session: Session, transcription_id: str) -> dict:
"""文字起こし結果を取得する"""
res = session.get(
f"{SONIOX_API_BASE_URL}/v1/transcriptions/{transcription_id}/transcript"
)
res.raise_for_status()
return res.json()
def delete_transcription(session: Session, transcription_id: str) -> dict:
"""文字起こしを削除する"""
res = session.delete(f"{SONIOX_API_BASE_URL}/v1/transcriptions/{transcription_id}")
res.raise_for_status()
def delete_file(session: Session, file_id: str) -> dict:
"""音声ファイルを削除する"""
res = session.delete(f"{SONIOX_API_BASE_URL}/v1/files/{file_id}")
res.raise_for_status()
def delete_all_files(session: Session) -> None:
"""アップロード済みのすべての音声ファイルを削除する"""
files: list[dict] = []
cursor: str = ""
while True:
print("Getting files...")
res = session.get(f"{SONIOX_API_BASE_URL}/v1/files?cursor={cursor}")
res.raise_for_status()
res_json = res.json()
files.extend(res_json["files"])
cursor = res_json["next_page_cursor"]
if cursor is None:
break
total = len(files)
if total == 0:
print("No files to delete.")
return
print(f"Deleting {total} files...")
for idx, file in enumerate(files):
file_id = file["id"]
print(f"Deleting file: {file_id} ({idx + 1}/{total})")
delete_file(session, file_id)
def delete_all_transcriptions(session: Session) -> None:
"""完了またはエラー状態の文字起こしをすべて削除する"""
transcriptions: list[dict] = []
cursor: str = ""
while True:
print("Getting transcriptions...")
res = session.get(f"{SONIOX_API_BASE_URL}/v1/transcriptions?cursor={cursor}")
res.raise_for_status()
res_json = res.json()
for transcription in res_json["transcriptions"]:
status = transcription["status"]
# 完了またはエラーのステータスの文字起こしのみ削除
if status in ("completed", "error"):
transcriptions.append(transcription)
cursor = res_json["next_page_cursor"]
if cursor is None:
break
total = len(transcriptions)
if total == 0:
print("No transcriptions to delete.")
return
print(f"Deleting {total} transcriptions...")
for idx, transcription in enumerate(transcriptions):
transcription_id = transcription["id"]
print(f"Deleting transcription: {transcription_id} ({idx + 1}/{total})")
delete_transcription(session, transcription_id)
# トークン列を人間が読みやすい書き起こしに整形する。
def render_tokens(final_tokens: list[dict]) -> str:
text_parts: list[str] = []
current_speaker: Optional[str] = None
current_language: Optional[str] = None
# トークンを順に処理
for token in final_tokens:
text = token["text"]
speaker = token.get("speaker")
language = token.get("language")
is_translation = token.get("translation_status") == "translation"
# 話者が変わった場合 -> 話者タグを追加
if speaker is not None and speaker != current_speaker:
if current_speaker is not None:
text_parts.append("\n\n")
current_speaker = speaker
current_language = None # 話者が変わったら言語をリセット
text_parts.append(f"Speaker {current_speaker}:")
# 言語が変わった場合 -> 言語または翻訳タグを追加
if language is not None and language != current_language:
current_language = language
prefix = "[Translation] " if is_translation else ""
text_parts.append(f"\n{prefix}[{current_language}] ")
text = text.lstrip()
text_parts.append(text)
return "".join(text_parts)
def transcribe_file(
session: Session,
audio_url: Optional[str],
audio_path: Optional[str],
translation: Optional[str],
) -> None:
"""音声ファイルを文字起こしして、結果を出力する"""
if audio_url is not None:
# 文字起こしする音声ファイルの公開 URL
assert audio_path is None
file_id = None
elif audio_path is not None:
# ローカルファイルをアップロードして、ファイル ID を取得
assert audio_url is None
file_id = upload_audio(session, audio_path)
else:
raise ValueError("Missing audio: audio_url or audio_path must be specified.")
config = get_config(audio_url, file_id, translation)
transcription_id = create_transcription(session, config)
wait_until_completed(session, transcription_id)
result = get_transcription(session, transcription_id)
text = render_tokens(result["tokens"])
print(text)
delete_transcription(session, transcription_id)
if file_id is not None:
delete_file(session, file_id)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--audio_url", help="Public URL of the audio file to transcribe."
)
parser.add_argument(
"--audio_path", help="Path to a local audio file to transcribe."
)
parser.add_argument("--delete_all_files", action="store_true")
parser.add_argument("--delete_all_transcriptions", action="store_true")
parser.add_argument("--translation", default="none")
args = parser.parse_args()
api_key = os.environ.get("SONIOX_API_KEY")
if not api_key:
raise RuntimeError(
"Missing SONIOX_API_KEY.\n"
"1. Get your API key at https://console.soniox.com\n"
"2. Run: export SONIOX_API_KEY=<YOUR_API_KEY>"
)
# 認証済みセッションを作成
session = requests.Session()
session.headers["Authorization"] = f"Bearer {api_key}"
# アップロード済みのファイルをすべて削除
if args.delete_all_files:
delete_all_files(session)
return
# すべての文字起こしを削除
if args.delete_all_transcriptions:
delete_all_transcriptions(session)
return
# 削除しない場合、いずれかの音声ソース指定が必須
if not (args.audio_url or args.audio_path):
parser.error("Provide --audio_url or --audio_path (or use a delete flag).")
transcribe_file(session, args.audio_url, args.audio_path, args.translation)
if __name__ == "__main__":
main()
設定の箇所を見るといろいろな機能があるのだけど、細かいところは後で確認するとして、まずはサンプルを動かしてみる。
このサンプルは、オプション引数で諸々の処理をすべて行えるCLIになっている。Usageはこんな感じ。
uv run soniox_async.py --help
usage: soniox_async.py [-h] [--audio_url AUDIO_URL] [--audio_path AUDIO_PATH] [--delete_all_files]
[--delete_all_transcriptions] [--translation TRANSLATION]
options:
-h, --help show this help message and exit
--audio_url AUDIO_URL
Public URL of the audio file to transcribe.
--audio_path AUDIO_PATH
Path to a local audio file to transcribe.
--delete_all_files
--delete_all_transcriptions
--translation TRANSLATION
用意されているサンプル音声を使って試してみる。サンプル音声はassetsディレクトリにあって、以下のような内容になっている。
tree ../assets
../assets
├── coffee_shop.mp3 # コーヒーショップでの女性店員と男性客との会話。mp3。
├── coffee_shop.pcm_s16le # 上記のraw PCM
├── two_way_translation.mp3 # 男性が英語、女性がスペイン語での会話。mp3。
└── two_way_translation.pcm_s16le # 上記のraw PCM
まずは、コーヒーショップのサンプル音声を使って、ローカルファイルの文字起こしから。
uv run soniox_async.py --audio_path ../assets/coffee_shop.mp3
サクッと終わる。
Starting file upload...
File ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Creating transcription...
Transcription ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Waiting for transcription...
Speaker 1:
[en] What is your best seller here?
Speaker 2:
[en] Our best seller here is cold brew iced coffee and lattes.
Speaker 1:
[en] Okay. And on a day like today, where it's snowing quite a bit, do a lot of people still order iced coffee?
Speaker 2:
[en] Here in Maine, yes.
Speaker 1:
[en] Really?
Speaker 2:
[en] Yes.
ファイルがアップロードしてファイルIDを発行、それを使って文字起こしジョブを発行、ジョブが完了したら文字起こし結果を取得、という感じ。話者や言語の識別なども行われているのがわかる。
このコーヒーショップのサンプル音声はURLでも公開されているので、次はURLでの指定。
uv run soniox_async.py --audio_url "https://soniox.com/media/examples/coffee_shop.mp3"
Creating transcription...
Transcription ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Waiting for transcription...
Speaker 1:
[en] What is your best seller here?
Speaker 2:
[en] Our best seller here is cold brew iced coffee and lattes.
Speaker 1:
[en] Okay. And on a day like today, where it's snowing quite a bit, do a lot of people still order iced coffee?
Speaker 2:
[en] Here in Maine, yes.
Speaker 1:
[en] Really?
Speaker 2:
[en] Yes.
まあ結果はおなじになるよね。
アップロードした音声ファイルや、実行した文字起こしジョブの結果はSoniox側に保存されているようなので、これを削除する。
uv run soniox_async.py --delete_all_files
Getting files...
No files to delete.
uv run soniox_async.py --delete_all_transcriptions
Getting transcriptions...
No transcriptions to delete.
んー、両方とも存在しないみたいなんだけど・・・と思ったら、文字起こしの最後で消してる・・・これオプションの意味がほとんどないな・・・
途中で失敗したとかで残るケースもあるとは思うので全く意味がないわけではないし、まあよしなにやってくれてるんだろうけど、Get startedみたいなので一連の流れを理解してもらうケースなら、その旨を書いてそこで留める法が良いと思うな。
とりあえずこのスクリプトだと削除まで自動でやってくれる。自分で実装する場合には削除しなければずっと残るということだけ認識しておけばよい。
次に、手持ちの日本語音声ファイルでもやってみる。自分が過去に開催した勉強会の冒頭5分程度の音声を抜き出したものを使う。
uv run soniox_async.py --audio_path voice_lunch_jp_5min.wav
こちらも数秒程度で終了
Starting file upload...
File ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Creating transcription...
Transcription ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Waiting for transcription...
Speaker 1:
[ja] はい、じゃあ始めます。ちょっとまだ来られてない方もいらっしゃるんですけど。ボイスランチ、JP始めます。皆さん、日曜日。はーい。はい、日曜日にお集まりいただきましてありがとうございます。えーと、今日久しぶりにですね、オフラインということで、えーと今日はですね、スペシャルなゲストをお二人来ていただいておりますということで。(snip)
コードブロックだと横に長いので全文はこちら。
はい、じゃあ始めます。ちょっとまだ来られてない方もいらっしゃるんですけど。ボイスランチ、JP始めます。皆さん、日曜日。はーい。はい、日曜日にお集まりいただきましてありがとうございます。えーと、今日久しぶりにですね、オフラインということで、えーと今日はですね、スペシャルなゲストをお二人来ていただいておりますということで。はい、えーと今日ちょっとトピックにまいりますけれども、えーとボイスフローのCEOであるブレゼン・リームさんと、あと、えーとセールスフォースの、えーと株式会社ジョーダン・デザインのディレクターであるグレッグ・ベネットさんに来ていただいてまーす。ということで、日本に来ていただいてありがとうございます。はい。で、今日はちょっとこのお二人にまたあとでいろいろと聞こうという、えーとコーナーがありますので、えーとそこでまたいろいろと聞きたいと思います。で、今日のアジェンダなんですけども、えーとちょっと時間過ぎちゃいましたが、まず最初にボイスランチJPについてっていうとこ、あと会場のとこですね、少しご説明させていただいて、1つ目のセッションで、えーとまず私のほうから、えーとボイスフローの2022年の、えーと新機能とかですね、そのへんの話を少しさせていただいて、そのあと、えーと2つ目のセッションで、えーとブレゼンさんとグレッグさんにいろいろカンバセーショナルデザインですね、について何でも聞こうぜみたいなところを予定しております。で、そのあと15時から、えーと15時で一旦は終了という形でさせていただいて、ちょっと一応ボイスランチJP、確か記念撮影は必須ですよね。なのでそれだけさせていただいて、そのあとちょっと1時間ぐらい、あの簡単に、あのお菓子と飲み物を用意してますので、懇親会というのをそのままさせていただこうと思っています。で、えーとボイスランチJPについてなんですけども、えーとボイスランチはボイスUIとか音声関連ですね、そういった技術に、えーと実際に携わってる人、もしくは興味がある人たちのためのグローバルなコミュニティという形になっていて、えーとボイスランチの、えーと日本リージョンという形がボイスランチJPになってますと。で、えーと過去もまあずっとやってますけど、オンラインオフラインでいろんな音声の、えーとデザインだったり技術だったりっていうところで情報とかを共有して、みんなで業界盛り上げていこうぜというようなことでやっております。で、今日の、えーとハッシュタグですね、えーと#ボイスランチJPでいろいろと自由にシェアしてください。で、あと会場ですね、えーと今回、えーとグラニカ様のご厚意で利用させていただいてます。ありがとうございます。で、ぜひこちらもシェアをお願いしたいですと。で、今日と配信のところもいろいろとやっていただいてますので、非常に感謝しております。で、ちょっと今、あのごめんなさい、抜けた。今あの、えーとコロナで、えーと会場に来られる方とかもあまりいないということでされてないんですけれども、あの通常はなんかこうでIoT機器のとかガジェットとかを展示されているようなので、えーとそういったものがあるとき、こん、今度ですね、また体験してみていただければなと思っていますというとこで、あとすいません、えーとトイレがこちら、で、あとタバコ吸われてる方はこちらのところになってますんで、よろしくお願いします。はい、ということで最初の挨拶はこれで。じゃあまず私のほうのセッションからさせていただきますというとこで、ボイスフローアップデート2022というところで、えーと今年の新機能について少しお話をします。えーと自己紹介です。えーと清水と申します。えーと神戸でインフラのエンジニアをやってましたので、えーと普段はKubernetesとかAWSとかTerraformとかをいじってまして、最近ちょっとフリーランスになりました。で、えーとちょっと調べてみたらボイスフローを一番最初に始めたのが2019年の頭ぐらいなんで、大体4年弱ぐらいですね、いろいろと触ってまして。あと、えーと音声関連のコミュニティのとこでは、えーとボイスランチJP、今回のやつですね、えー以外に、えーとAZAG、Amazon Alexa、Japan User Groupとか、あと、えーとボイスフローの、えーと日本語ユーザーグループということでVFJUGというのをやっています。はい、えーと日本語コミュニティのほうはフェイスブックのほうで、えーとやってますので、もしよろしければ見ていただければなと思いますと。あと2年ぐらい前にですね、えーと技術書店のほうで、ここに今日スタッフで来ていただいてる皆さんとですね、一緒にあの同人誌作ろうぜということで、えーと作ったんですけれども、もうこれちょっと2年ぐらい経って中身がだいぶ古くなってしまっているので、すでにちょっと販売は終了しております。今日ちょっと持ってきたかったんですけど、すいません、忘れてしまいました。はい、なんでこういうこともやっていますと。
以前のバージョンでもそうなんだけど、比較的フィラーとかはそのまま出力される感じ(フィラー多くてちょっと凹む)で、個人的にはWhisperみたいに勝手に書き換えられるよりは、こちらのほうが好み。
専門用語とか固有名詞などの認識もある程度は認識できているように見えるが、設定で会話のコンテキストや専門用語を渡せるので、それを正しく指定すれば改善できるかもしれない。このサンプルコードではあらかじめヘルスケア関連のコンテキストが設定されていて、音声の内容とは合ってないという点に留意されたし。
複数話者についても試してみる。以前にElevenLabsで作った男性・女性の対話音声を使う。
uv run soniox_async.py --audio_path elevenlabs_dialogue_sample.mp3
Starting file upload...
File ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Creating transcription...
Transcription ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Waiting for transcription...
Speaker 1:
[ja] こんにちは、花子です。今日は太郎さんと2025年春の競馬、G1レースの展望について語りたいと思います。まずは各レースの見どころについて教えてください。
Speaker 2:
[ja] こんにちは、太郎です。春のG1レースは例年どおり熱い展開が期待されますね。例えば、フェブラリー・ステークスで見せたコスタ・ノヴァの走りは印象的でしたし、今後のレースにも大きな影響を与えそうです。
Speaker 1:
[ja] 確かに。さらに、札木賞ではファウストラーゼンやピコ・チャン・ブラックといった有力馬が注目されています。若手の勢いがこのレースの鍵になりそうですね。
Speaker 2:
[ja] その通りです。そして、NHKマイルカップからはアドマイヤ・ズームの動きにも期待が高まっています。各レースで個性豊かな馬たちが戦うので、騎手や調教師の戦略も見逃せません。
Speaker 1:
[ja] また、春の締めくくりとして注目される天皇賞・晴れも、昨シーズンの歴史を踏まえて、今年はより一層白熱した争いが予想されますね。
Speaker 2:
[ja] まさにその通りです。伝統と革新が融合する春のG1レース、ファンにとっては見逃せないシーズンになりそうです。
Speaker 1:
[ja] 今日は、太郎さんが各レースの見どころや有力馬について熱く語ってくれました。来る春のレース、私たちもワクワクしながら応援していきましょう。
Speaker 2:
[ja] はい。最新情報をしっかりチェックしながら、競馬ファンとして楽しみたいですね。ご清聴ありがとうございました。
Speaker 1:
[ja] ありがとうございました。次回もお楽しみに。
日本語でも話者を識別できている。固有名詞については上に書いた通り。まあ競馬の固有名詞は難しいね。
非同期APIでの文字起こし+翻訳
非同期で文字起こししつつ翻訳も行える。翻訳は2つの方法がある。
- 特定の1言語に翻訳(
one_way)- すべての音声を特定の1言語に翻訳する。
- 言語をスイッチして翻訳(
two_way)- 2つの言語が含まれる場合に、それぞれをもう片方の言語に翻訳する。
- 例: 英語・スペイン語が含まれる音声は、英語はスペイン語・スペイン語は英語、という感じ
設定としては以下のような設定をJSONを渡すだけ。
{
"translation": {
"type": "one_way",
"target_language": "es",
}
}
{
"translation": {
"type": "two_way",
"language_a": "en",
"language_b": "es",
}
}
サンプルコードでは --translation で one_way / two_way で指定できるようになっている。最初のコーヒーショップのサンプル音声をスペイン語で文字起こししてみる。
uv run soniox_async.py --audio_path ../assets/coffee_shop.mp3 --translation one_way
Speaker 1:
[en] What is your best seller here?
[Translation] [es] ¿Cuál es tu más vendido aquí?
Speaker 2:
[en] Our best seller here is
[Translation] [es] Nuestro más vendido aquí es
[en] cold brew iced coffee and lattes.
[Translation] [es] café helado de cold brew y lattes.
Speaker 1:
[en] Okay, and on a day like today
[Translation] [es] De acuerdo, y en un día como hoy
[en] where it's snowing quite a bit,
[Translation] [es] cuando está nevando bastante,
[en] do a lot of people still order iced coffee?
[Translation] [es] ¿mucha gente aún pide café helado?
Speaker 2:
[en] Here in Maine, yes.
[Translation] [es] Aquí en Maine, sí.
Speaker 1:
[en] Really?
[Translation] [es] ¿De verdad?
Speaker 2:
[en] Yes.
[Translation] [es] Sí.
元の発話の文字起こしとともに翻訳された文章が返ってきているのがわかる。
two_wayの方。こちらは two_way_translation.mp3 の音声サンプルが、男性は英語・女性はスペイン語での会話となっているので、これを使う。
uv run soniox_async.py --audio_path ../assets/two_way_translation.mp3 --translation two_way
Speaker 1:
[en] Hey, how are you?
[Translation] [es] Hola, ¿cómo estás?
Speaker 2:
[es] Hola, yo soy bien,
[Translation] [en] Hi, I'm fine,
[es] ¿cómo estás?
[Translation] [en] how are you?
Speaker 1:
[en] I'm fine too.
[Translation] [es] Yo también estoy bien.
翻訳の設定は以下あたり。
日本語も絡めて、少しいろいろ試してみる。まず one_wayで、日本語音声を英語に。
(snip)
"language_hints": ["ja"],
(snip)
elif translation == "one_way":
# Translates all languages into the target language.
config["translation"] = {
"type": "one_way",
"target_language": "en",
}
(snip)
uv run soniox_async.py --audio_path elevenlabs_dialogue_sample.mp3 --translation one_way
Speaker 1:
[ja] こんにちは、花子です。
[Translation] [en] Hello, this is Hanako.
[ja] 今日は太郎さんと
[Translation] [en] Today, with Mr. Taro,
[ja] 2025年春の競馬、
[Translation] [en] we'll discuss the 2025 spring horse races,
[ja] G1レースの展望について
[Translation] [en] and the outlook for the G1 race,
[ja] 語りたいと思います。
[Translation] [en] and share some thoughts.
[ja] まずは各レースの見どころについて
[Translation] [en] First, please tell us the highlights
[ja] 教えてください。
[Translation] [en] of each race.
Speaker 2:
[ja] こんにちは、太郎です。
[Translation] [en] Hello, this is Taro.
[ja] 春のG1レースは
[Translation] [en] The spring G1 race
[ja] 例年どおり熱い展開が
[Translation] [en] is expected to have a hot run
[ja] 期待されますね。
[Translation] [en] as usual.
[ja] 例えば、February Stakesで見せた
[Translation] [en] For example, the Costa Nova race
[ja] コスタ・ノヴァの走りは
[Translation] [en] shown at the February Stakes
[ja] 印象的でしたし、
[Translation] [en] was impressive,
[ja] 今後のレースにも
[Translation] [en] and it seems it will
[ja] 大きな影響を与えそうです。
[Translation] [en] greatly affect future races.
Speaker 1:
[ja] 確かに。
[Translation] [en] Indeed.
[ja] さらに、皐月賞では
[Translation] [en] Furthermore, at the Satsuki Sho,
[ja] ファウストラーゼンや
[Translation] [en] Fauzturazen and
[ja] ピコ・チャン・ブラックといった
[Translation] [en] Pico-Chan Black, among other
[ja] 有力馬が注目されています。
[Translation] [en] strong horses, are in the spotlight.
[ja] 若手の勢いが
[Translation] [en] The momentum of the young players
[ja] このレースの鍵になりそうですね。
[Translation] [en] seems to be key for this race.
Speaker 2:
[ja] その通りです。
[Translation] [en] Exactly.
[ja] そして、NHKマイルカップからは
[Translation] [en] And from the NHK Mile Cup,
[ja] アドマイヤ・ズームの動きにも
[Translation] [en] there are also expectations for
[ja] 期待が高まっています。
[Translation] [en] Admire Zoom's movements.
[ja] 各レースで
[Translation] [en] In each race,
[ja] 個性豊かな馬たちが戦うので、
[Translation] [en] diverse horses will compete,
[ja] 機種や調教師の戦略も
[Translation] [en] so we mustn't overlook the strategies
[ja] 見逃せません。
[Translation] [en] of the models and coaches.
Speaker 1:
[ja] また、春の締めくくりとして
[Translation] [en] Also, as a wrap-up for spring,
[ja] 注目される天皇賞・晴れも、
[Translation] [en] the Tenno-shō Hare, which is in the spotlight,
[ja] 昨シーズンの歴史を踏まえて、
[Translation] [en] based on last season's history,
[ja] 今年はより一層
[Translation] [en] is expected to become even more
[ja] 白熱した争いが
[Translation] [en] intense this year,
[ja] 予想されますね。
[Translation] [en] as anticipated.
Speaker 2:
[ja] まさにその通りです。
[Translation] [en] Exactly.
[ja] 伝統と革新が融合する
[Translation] [en] A spring G1 race where tradition
[ja] 春のG1レース、
[Translation] [en] and innovation merge,
[ja] ファンにとっては
[Translation] [en] will be an unmissable season
[ja] 見逃せないシーズンになりそうです。
[Translation] [en] for fans.
Speaker 1:
[ja] 今日は、太郎さんが
[Translation] [en] Today, Mr. Taro
[ja] 各レースの見どころや
[Translation] [en] talked warmly about the highlights
[ja] 有力馬について
[Translation] [en] and the strong horses
[ja] 熱く語ってくれました。
[Translation] [en] in each race.
[ja] 来る春のレース、
[Translation] [en] In the upcoming spring races,
[ja] 私たちもワクワクしながら
[Translation] [en] let's also cheer with excitement
[ja] 応援していきましょう。
[Translation] [en] and keep supporting them.
Speaker 2:
[ja] はい。
[Translation] [en] Yes.
[ja] 最新情報をしっかりチェックしながら、
[Translation] [en] While closely checking the latest information,
[ja] 競馬ファンとして
[Translation] [en] as horse racing fans,
[ja] 楽しみたいですね。
[Translation] [en] we want to enjoy it.
[ja] ご清聴ありがとうございました。
[Translation] [en] Thank you for your attention.
Speaker 1:
[ja] ありがとうございました。
[Translation] [en] Thank you very much.
[ja] 次回もお楽しみに。
[Translation] [en] Look forward to next time.
逆に、英語・スペイン語混在を日本語に。
(snip)
"language_hints": ["en", "es"],
(snip)
elif translation == "one_way":
# Translates all languages into the target language.
config["translation"] = {
"type": "one_way",
"target_language": "ja",
}
(snip)
uv run soniox_async.py --audio_path ../assets/coffee_shop.mp3 --translation one_way
Speaker 1:
[en] What is your best seller here?
[Translation] [ja] こちらで一番売れている商品は何ですか?
Speaker 2:
[en] Our best seller here is
[Translation] [ja] こちらで一番売れているのは、
[en] cold brew iced coffee and lattes.
[Translation] [ja] コールドブリューのアイスコーヒーとラテです。
Speaker 1:
[en] Okay, and on a day like today
[Translation] [ja] なるほど。今日のような日は、
[en] where it's snowing quite a bit,
[Translation] [ja] 雪がかなり降っている日でも、
[en] do a lot of people still order iced coffee?
[Translation] [ja] 多くの人はまだアイスコーヒーを注文しますか?
Speaker 2:
[en] Here in Maine, yes.
[Translation] [ja] メイン州では、はい。
Speaker 1:
[en] Really?
[Translation] [ja] 本当ですか?
Speaker 2:
[en] Yes.
[Translation] [ja] はい。
two_way の方は手頃なサンプルがなかったのでスキップ。
非同期APIその他
その他、非同期APIの場合に使える機能や制約など。ここはざっくり。
音声ファイルのフォーマット
非同期APIは以下のオーディオフォーマットに対応している。
- aac
- aiff
- amr
- asf
- flac
- mp3
- ogg
- wav
- webm
- m4a
- mp4
対応しているオーディオフォーマットであれば、明示的に指定する必要はなく、自動で判別してくれる。
Webhook
文字起こし完了時・エラー時などにWebhookを使って通知を送信できる
制限とクォータ
非同期APIには以下の制限がある。
ファイル制限
| 制限項目 | デフォルト | 備考 |
|---|---|---|
| 総ファイルストレージ | 10 GB | アップロード済みファイルの合計 |
| 保持できるファイル数 | 10,000 | 同時に保存可能な最大数 |
| ファイルの最大長 | 60分 | 増加不可 / 近日300分に対応予定 |
- ファイルの自動削除は行われないので手動での削除が必要
- クォータの緩和はコンソールから行える(ファイル最大長以外)
文字起こし制限
| 制限項目 | デフォルト | 備考 |
|---|---|---|
| 保留中のジョブ数 | 100 | 作成済みで未処理のリクエスト |
| 総ジョブ数 | 2,000 | 保留+完了+失敗の合計 |
| ファイルの最大長 | 60分 | 増加不可 / 近日300分に対応予定 |
- 常に文字起こしができる状態にしておくには以下を考慮する
- 保留中のジョブ数が100未満を維持する
- 完了済・失敗ジョブは削除して、総ジョブ数が2000を超えないようにする
- クォータの緩和はコンソールから行える(ファイル最大長以外)
エラー処理
ファイルアップロード、文字起こし、Webhookなどで起きるエラーと対処などについて書かれている。
設定等については、非同期APIとリアルタイムAPIで共通なものもあるようなので、後でまとめて。
長くなったので分けた。
続きはこちら。