@@ -79,70 +79,96 @@ async def synthesize(self, text, speaker_id, output_path, speed: float = 1.0):
7979 """
8080 # .envを毎回再読込してURLリストを更新
8181 self .base_urls = self ._load_base_urls ()
82- for base_url in self .base_urls : # 各URLを順番に試行
82+ for base_url in self .base_urls :
8383 if os .getenv ("DEBUG" ) == "1" :
84- print (f"Using VOICEVOX URL: { base_url } " ) # 追加: 使用するURLをprint
85- try :
86- async with aiohttp .ClientSession () as session :
87- start_time = time .perf_counter () # 計測開始
88-
89- # Step 1: Generate audio query
90- async with session .post (
91- f"{ base_url } /audio_query" ,
92- params = {"text" : text , "speaker" : speaker_id }
93- ) as query_response :
94- query_response .raise_for_status ()
95- audio_query = await query_response .json ()
96- # スピードを上書き
97- if "speedScale" in audio_query :
98- audio_query ["speedScale" ] = speed
99-
100- # Step 2: Synthesize audio
101- async with session .post (
102- f"{ base_url } /synthesis" ,
103- params = {"speaker" : speaker_id },
104- json = audio_query
105- ) as synthesis_response :
106- synthesis_response .raise_for_status ()
107- wav_bytes = await synthesis_response .read ()
108-
109- elapsed = time .perf_counter () - start_time # 秒
110-
111- # Step 3: Save WAV file and compute duration
112- with wave .open (io .BytesIO (wav_bytes ), "rb" ) as wav_file :
113- n_frames = wav_file .getnframes ()
114- framerate = wav_file .getframerate ()
115- duration_sec = n_frames / framerate if framerate else 0.0
116-
117- # Update Prometheus metric: seconds of processing per 1 minute of audio
118- if duration_sec > 0 :
119- seconds_per_minute = elapsed * 60.0 / duration_sec
120- else :
121- seconds_per_minute = 0.0
122- try :
123- VOICE_GENERATION_TIME_PER_MINUTE .set (seconds_per_minute )
124- except Exception :
125- # 安全のため例外は無視(メトリクス失敗で処理を止めない)
126- pass
84+ print (f"Using VOICEVOX URL: { base_url } " )
85+ last_error = None
86+ for attempt in range (3 ):
87+ try :
88+ async with aiohttp .ClientSession () as session :
89+ start_time = time .perf_counter ()
90+ # Step 1: Generate audio query
91+ async with session .post (
92+ f"{ base_url } /audio_query" ,
93+ params = {"text" : text , "speaker" : speaker_id }
94+ ) as query_response :
95+ if query_response .status >= 500 :
96+ raise aiohttp .ClientResponseError (
97+ request_info = query_response .request_info ,
98+ history = query_response .history ,
99+ status = query_response .status ,
100+ message = f"HTTP { query_response .status } " ,
101+ headers = query_response .headers
102+ )
103+ query_response .raise_for_status ()
104+ audio_query = await query_response .json ()
105+ if "speedScale" in audio_query :
106+ audio_query ["speedScale" ] = speed
107+
108+ # Step 2: Synthesize audio
109+ async with session .post (
110+ f"{ base_url } /synthesis" ,
111+ params = {"speaker" : speaker_id },
112+ json = audio_query
113+ ) as synthesis_response :
114+ if synthesis_response .status >= 500 :
115+ raise aiohttp .ClientResponseError (
116+ request_info = synthesis_response .request_info ,
117+ history = synthesis_response .history ,
118+ status = synthesis_response .status ,
119+ message = f"HTTP { synthesis_response .status } " ,
120+ headers = synthesis_response .headers
121+ )
122+ synthesis_response .raise_for_status ()
123+ wav_bytes = await synthesis_response .read ()
124+
125+ elapsed = time .perf_counter () - start_time
126+
127+ # Step 3: Save WAV file and compute duration
128+ with wave .open (io .BytesIO (wav_bytes ), "rb" ) as wav_file :
129+ n_frames = wav_file .getnframes ()
130+ framerate = wav_file .getframerate ()
131+ duration_sec = n_frames / framerate if framerate else 0.0
132+
133+ # Update Prometheus metric: seconds of processing per 1 minute of audio
134+ if duration_sec > 0 :
135+ seconds_per_minute = elapsed * 60.0 / duration_sec
136+ else :
137+ seconds_per_minute = 0.0
138+ try :
139+ VOICE_GENERATION_TIME_PER_MINUTE .set (seconds_per_minute )
140+ except Exception :
141+ # 安全のため例外は無視(メトリクス失敗で処理を止めない)
142+ pass
143+
144+ # 出力先をプロジェクトルートの tmp ディレクトリに固定し、そのパスを返す
145+ filename = os .path .basename (output_path )
146+ if self .tmp_dir :
147+ tmp_output_path = os .path .join (self .tmp_dir , filename )
148+ else :
149+ tmp_output_path = os .path .abspath (output_path )
127150
128- # 出力先をプロジェクトルートの tmp ディレクトリに固定し、そのパスを返す
129- filename = os .path .basename (output_path )
130- if self .tmp_dir :
131- tmp_output_path = os .path .join (self .tmp_dir , filename )
132- else :
133- tmp_output_path = os .path .abspath (output_path )
134-
135- # tmp に保存
136- with wave .open (tmp_output_path , "wb" ) as output_file :
137- output_file .setparams (wav_file .getparams ())
138- output_file .writeframes (wav_file .readframes (n_frames ))
139-
140- return tmp_output_path
141- except aiohttp .ClientError as e :
142- logging .error (f"VOICEVOX synthesis failed for URL { base_url } : { e } " )
143- continue # 次のURLを試行
144- # すべてのURLで失敗した場合
145- raise RuntimeError (f"All VOICEVOX URLs failed for synthesis: { text [:50 ]} ..." )
151+ # tmp に保存
152+ with wave .open (tmp_output_path , "wb" ) as output_file :
153+ output_file .setparams (wav_file .getparams ())
154+ output_file .writeframes (wav_file .readframes (n_frames ))
155+
156+ return tmp_output_path
157+ except aiohttp .ClientResponseError as e :
158+ logging .error (f"VOICEVOX synthesis failed for URL { base_url } (attempt { attempt + 1 } /3): { e } " )
159+ last_error = e
160+ time .sleep (0.5 )
161+ continue
162+ except aiohttp .ClientError as e :
163+ logging .error (f"VOICEVOX synthesis failed for URL { base_url } (attempt { attempt + 1 } /3): { e } " )
164+ last_error = e
165+ time .sleep (0.5 )
166+ continue
167+ except Exception as e :
168+ logging .error (f"VOICEVOX synthesis unexpected error for URL { base_url } : { e } " )
169+ break
170+ # 3回とも失敗した場合のみ次のURLへ
171+ raise RuntimeError (f"All VOICEVOX URLs failed for synthesis: { text [:50 ]} ... Last error: { last_error } " )
146172
147173 async def synthesize_bytes (self , text , speaker_id ) -> tuple [str , bytes ]:
148174 """
@@ -157,53 +183,81 @@ async def synthesize_bytes(self, text, speaker_id) -> tuple[str, bytes]:
157183 """
158184 # .envを毎回再読込してURLリストを更新
159185 self .base_urls = self ._load_base_urls ()
160- for base_url in self .base_urls : # 各URLを順番に試行
186+ for base_url in self .base_urls :
161187 if os .getenv ("DEBUG" ) == "1" :
162- print (f"Using VOICEVOX URL: { base_url } " ) # 追加: 使用するURLをprint
163- try :
164- async with aiohttp .ClientSession () as session :
165- start_time = time .perf_counter () # 計測開始
166-
167- # Step 1: Generate audio query
168- async with session .post (
169- f"{ base_url } /audio_query" ,
170- params = {"text" : text , "speaker" : speaker_id }
171- ) as query_response :
172- query_response .raise_for_status ()
173- audio_query = await query_response .json ()
174-
175- # Step 2: Synthesize audio
176- async with session .post (
177- f"{ base_url } /synthesis" ,
178- params = {"speaker" : speaker_id },
179- json = audio_query
180- ) as synthesis_response :
181- synthesis_response .raise_for_status ()
182- wav_bytes = await synthesis_response .read ()
183-
184- elapsed = time .perf_counter () - start_time # 秒
185-
186- # Compute duration and set metric
187- try :
188- with wave .open (io .BytesIO (wav_bytes ), "rb" ) as wav_file :
189- n_frames = wav_file .getnframes ()
190- framerate = wav_file .getframerate ()
191- duration_sec = n_frames / framerate if framerate else 0.0
192- if duration_sec > 0 :
193- seconds_per_minute = elapsed * 60.0 / duration_sec
194- else :
195- seconds_per_minute = 0.0
196- VOICE_GENERATION_TIME_PER_MINUTE .set (seconds_per_minute )
197- except Exception :
198- # 例外は無視して wav_bytes を返す(メトリクスの失敗で処理を止めない)
199- pass
200-
201- return base_url , wav_bytes # 変更: URL とバイトデータをタプルで返す
202- except aiohttp .ClientError as e :
203- logging .error (f"VOICEVOX synthesize_bytes failed for URL { base_url } : { e } " )
204- continue # 次のURLを試行
205- # すべてのURLで失敗した場合
206- raise RuntimeError (f"All VOICEVOX URLs failed for synthesize_bytes: { text [:50 ]} ..." )
188+ print (f"Using VOICEVOX URL: { base_url } " )
189+ last_error = None
190+ for attempt in range (3 ):
191+ try :
192+ async with aiohttp .ClientSession () as session :
193+ start_time = time .perf_counter ()
194+
195+ # Step 1: Generate audio query
196+ async with session .post (
197+ f"{ base_url } /audio_query" ,
198+ params = {"text" : text , "speaker" : speaker_id }
199+ ) as query_response :
200+ if query_response .status >= 500 :
201+ raise aiohttp .ClientResponseError (
202+ request_info = query_response .request_info ,
203+ history = query_response .history ,
204+ status = query_response .status ,
205+ message = f"HTTP { query_response .status } " ,
206+ headers = query_response .headers
207+ )
208+ query_response .raise_for_status ()
209+ audio_query = await query_response .json ()
210+
211+ # Step 2: Synthesize audio
212+ async with session .post (
213+ f"{ base_url } /synthesis" ,
214+ params = {"speaker" : speaker_id },
215+ json = audio_query
216+ ) as synthesis_response :
217+ if synthesis_response .status >= 500 :
218+ raise aiohttp .ClientResponseError (
219+ request_info = synthesis_response .request_info ,
220+ history = synthesis_response .history ,
221+ status = synthesis_response .status ,
222+ message = f"HTTP { synthesis_response .status } " ,
223+ headers = synthesis_response .headers
224+ )
225+ synthesis_response .raise_for_status ()
226+ wav_bytes = await synthesis_response .read ()
227+
228+ elapsed = time .perf_counter () - start_time
229+
230+ # Compute duration and set metric
231+ try :
232+ with wave .open (io .BytesIO (wav_bytes ), "rb" ) as wav_file :
233+ n_frames = wav_file .getnframes ()
234+ framerate = wav_file .getframerate ()
235+ duration_sec = n_frames / framerate if framerate else 0.0
236+ if duration_sec > 0 :
237+ seconds_per_minute = elapsed * 60.0 / duration_sec
238+ else :
239+ seconds_per_minute = 0.0
240+ VOICE_GENERATION_TIME_PER_MINUTE .set (seconds_per_minute )
241+ except Exception :
242+ # 例外は無視して wav_bytes を返す(メトリクスの失敗で処理を止めない)
243+ pass
244+
245+ return base_url , wav_bytes
246+ except aiohttp .ClientResponseError as e :
247+ logging .error (f"VOICEVOX synthesize_bytes failed for URL { base_url } (attempt { attempt + 1 } /3): { e } " )
248+ last_error = e
249+ time .sleep (0.5 )
250+ continue
251+ except aiohttp .ClientError as e :
252+ logging .error (f"VOICEVOX synthesize_bytes failed for URL { base_url } (attempt { attempt + 1 } /3): { e } " )
253+ last_error = e
254+ time .sleep (0.5 )
255+ continue
256+ except Exception as e :
257+ logging .error (f"VOICEVOX synthesize_bytes unexpected error for URL { base_url } : { e } " )
258+ break
259+ # 3回とも失敗した場合のみ次のURLへ
260+ raise RuntimeError (f"All VOICEVOX URLs failed for synthesize_bytes: { text [:50 ]} ... Last error: { last_error } " )
207261
208262# Example usage:
209263# voicelib = VOICEVOXLib()
0 commit comments