149 lines
5.8 KiB
Python
149 lines
5.8 KiB
Python
"""
|
|
VoiceVox API連携
|
|
VoiceVoxエンジンとの通信を担当
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import hashlib
|
|
from typing import Optional
|
|
import urllib.request
|
|
import urllib.error
|
|
|
|
|
|
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"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()
|
|
return json.loads(data)
|
|
except Exception as e:
|
|
print(f"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:
|
|
print(f"[VoiceVox API] Step 1: 出力ディレクトリを作成: {output_dir}")
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
# 1. 音声合成用のクエリを作成
|
|
print(f"[VoiceVox API] Step 2: 音声クエリを生成中...")
|
|
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}"
|
|
print(f"[VoiceVox API] リクエストURL: {full_query_url}")
|
|
|
|
try:
|
|
# POSTリクエストにするため空のdataを渡す
|
|
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())
|
|
print(f"[VoiceVox API] クエリ生成成功")
|
|
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
|
|
|
|
# 2. 音声合成を実行
|
|
print(f"[VoiceVox API] Step 3: 音声合成を実行中...")
|
|
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()
|
|
print(f"[VoiceVox API] 音声合成成功 ({len(audio_data)} bytes)")
|
|
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
|
|
|
|
# 3. 音声ファイルを保存
|
|
print(f"[VoiceVox API] Step 4: 音声ファイルを保存中...")
|
|
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)
|
|
|
|
print(f"[VoiceVox API] 音声保存完了: {filepath}")
|
|
return filepath
|
|
|
|
except urllib.error.URLError as e:
|
|
error_msg = f"接続エラー: VoiceVoxエンジンに接続できません ({self.base_url})"
|
|
print(f"[VoiceVox API Error] {error_msg}")
|
|
print(f"[VoiceVox API Error] 詳細: {e}")
|
|
self.last_error = error_msg
|
|
import traceback
|
|
traceback.print_exc()
|
|
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
|
|
import traceback
|
|
traceback.print_exc()
|
|
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
|
|
import traceback
|
|
traceback.print_exc()
|
|
return None
|