""" 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