blender-voicevox-pl/voicevox.py

142 lines
5.0 KiB
Python

"""
VoiceVox API連携
VoiceVoxエンジンとの通信を担当
"""
import hashlib
import json
import os
import urllib.error
import urllib.request
from typing import Optional
class VoiceVoxAPI:
"""VoiceVox エンジンとの通信を管理"""
def __init__(self, host: str = "127.0.0.1", port: int = 50021):
self.base_url = f"http://{host}:{port}"
self.last_error = "" # 最後のエラーメッセージを保存
def test_connection(self) -> bool:
"""接続テスト"""
try:
url = f"{self.base_url}/version"
with urllib.request.urlopen(url, timeout=5) as response:
return response.status == 200
except Exception as e:
print(f"[VoiceVox API Error] Connection test failed: {e}")
return False
def get_speakers(self) -> Optional[list]:
"""利用可能な話者一覧を取得"""
try:
url = f"{self.base_url}/speakers"
with urllib.request.urlopen(url, timeout=10) as response:
data = response.read()
text = data.decode("utf-8")
speakers = json.loads(text)
return speakers
except Exception as e:
print(f"[VoiceVox API Error] Failed to get speakers: {e}")
return None
def generate_speech(
self,
text: str,
speaker_id: int = 0,
speed_scale: float = 1.0,
pitch_scale: float = 0.0,
intonation_scale: float = 1.0,
volume_scale: float = 1.0,
output_dir: str = "/tmp",
) -> Optional[str]:
"""
音声を生成して保存
Returns:
生成された音声ファイルのパス、失敗時はNone
"""
try:
os.makedirs(output_dir, exist_ok=True)
query_url = f"{self.base_url}/audio_query"
query_params = urllib.parse.urlencode(
{
"text": text,
"speaker": speaker_id,
}
)
full_query_url = f"{query_url}?{query_params}"
try:
req = urllib.request.Request(full_query_url, data=b"", method="POST")
with urllib.request.urlopen(req, timeout=10) as response:
query_data = json.loads(response.read())
except urllib.error.HTTPError as e:
print(f"[VoiceVox API Error] HTTPエラー: {e.code} {e.reason}")
print(
f"[VoiceVox API Error] レスポンス: {e.read().decode('utf-8', errors='replace')}"
)
raise
except urllib.error.URLError as e:
print(f"[VoiceVox API Error] 接続エラー: {e.reason}")
print(
f"[VoiceVox API Error] VoiceVoxエンジンが起動しているか確認してください"
)
raise
# パラメータを適用
query_data["speedScale"] = speed_scale
query_data["pitchScale"] = pitch_scale
query_data["intonationScale"] = intonation_scale
query_data["volumeScale"] = volume_scale
synthesis_url = f"{self.base_url}/synthesis"
synthesis_params = urllib.parse.urlencode({"speaker": speaker_id})
req = urllib.request.Request(
f"{synthesis_url}?{synthesis_params}",
data=json.dumps(query_data).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as response:
audio_data = response.read()
except urllib.error.HTTPError as e:
print(f"[VoiceVox API Error] 合成HTTPエラー: {e.code} {e.reason}")
print(
f"[VoiceVox API Error] レスポンス: {e.read().decode('utf-8', errors='replace')}"
)
raise
text_hash = hashlib.md5(text.encode("utf-8")).hexdigest()[:8]
filename = f"voicevox_{text_hash}_{speaker_id}.wav"
filepath = os.path.join(output_dir, filename)
with open(filepath, "wb") as f:
f.write(audio_data)
return filepath
except urllib.error.URLError as e:
error_msg = (
f"接続エラー: VoiceVoxエンジンに接続できません ({self.base_url})"
)
print(f"[VoiceVox API Error] {error_msg}")
self.last_error = error_msg
return None
except urllib.error.HTTPError as e:
error_msg = f"HTTPエラー {e.code}: {e.reason}"
print(f"[VoiceVox API Error] {error_msg}")
self.last_error = error_msg
return None
except Exception as e:
error_msg = f"{type(e).__name__}: {str(e)}"
print(f"[VoiceVox API Error] 音声生成失敗: {error_msg}")
self.last_error = error_msg
return None