142 lines
5.0 KiB
Python
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
|