Update app.py
Browse files
app.py
CHANGED
@@ -6,66 +6,102 @@ import re
|
|
6 |
import struct
|
7 |
import time
|
8 |
import zipfile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
|
10 |
-
# --- START: تغییر نحوه Import برای google-generativeai ---
|
11 |
try:
|
12 |
-
|
13 |
-
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
GOOGLE_LIBS_AVAILABLE = True
|
|
|
16 |
except ImportError as e:
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
# یا برنامه را همینجا متوقف کنیم اگر این کتابخانهها حیاتی هستند.
|
22 |
-
# برای سادگی فعلی، فرض میکنیم اگر import نشوند، در ادامه با خطا مواجه میشویم.
|
23 |
-
# --- END: تغییر نحوه Import ---
|
24 |
|
|
|
25 |
|
26 |
try:
|
27 |
from pydub import AudioSegment
|
28 |
PYDUB_AVAILABLE = True
|
29 |
except ImportError:
|
30 |
-
|
31 |
PYDUB_AVAILABLE = False
|
32 |
|
33 |
-
# --- START: منطق چرخش API Key ---
|
34 |
GEMINI_API_KEYS = []
|
35 |
i = 1
|
36 |
while os.environ.get(f"GEMINI_API_KEY_{i}"):
|
37 |
GEMINI_API_KEYS.append(os.environ.get(f"GEMINI_API_KEY_{i}"))
|
38 |
i += 1
|
39 |
-
|
40 |
NUM_API_KEYS = len(GEMINI_API_KEYS)
|
41 |
CURRENT_KEY_INDEX_GLOBAL = 0
|
42 |
-
|
43 |
-
def _log(message):
|
44 |
print(f"[لاگ آلفا TTS] {message}")
|
45 |
-
|
46 |
if not GOOGLE_LIBS_AVAILABLE:
|
47 |
_log("🔴 به دلیل عدم بارگذاری کتابخانههای اصلی گوگل، عملکرد برنامه مختل خواهد شد.")
|
48 |
-
|
49 |
if NUM_API_KEYS == 0:
|
50 |
_log("⛔️ هشدار: هیچ Secret با نام GEMINI_API_KEY_n یافت نشد! برنامه بدون کلید API کار نخواهد کرد.")
|
51 |
else:
|
52 |
_log(f"✅ تعداد {NUM_API_KEYS} کلید API جیمینای بارگذاری شد.")
|
53 |
-
|
54 |
def get_api_key_for_attempt(attempt_within_request):
|
55 |
-
if NUM_API_KEYS == 0:
|
56 |
-
return None, -1, -1
|
57 |
-
|
58 |
actual_key_index_in_list = (CURRENT_KEY_INDEX_GLOBAL + attempt_within_request) % NUM_API_KEYS
|
59 |
key_to_use = GEMINI_API_KEYS[actual_key_index_in_list]
|
60 |
key_display_number = actual_key_index_in_list + 1
|
61 |
return key_to_use, key_display_number, actual_key_index_in_list
|
62 |
-
|
63 |
def advance_global_key_index_for_next_request():
|
64 |
global CURRENT_KEY_INDEX_GLOBAL
|
65 |
-
if NUM_API_KEYS > 0:
|
66 |
-
CURRENT_KEY_INDEX_GLOBAL = (CURRENT_KEY_INDEX_GLOBAL + 1) % NUM_API_KEYS
|
67 |
# --- END: منطق چرخش API Key ---
|
68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
SPEAKER_VOICES = [
|
70 |
"Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager",
|
71 |
"Sulafat", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux",
|
@@ -75,7 +111,7 @@ SPEAKER_VOICES = [
|
|
75 |
]
|
76 |
FIXED_MODEL_NAME = "gemini-2.5-flash-preview-tts"
|
77 |
DEFAULT_MAX_CHUNK_SIZE = 3800
|
78 |
-
DEFAULT_SLEEP_BETWEEN_REQUESTS = 6
|
79 |
RETRY_SLEEP_AFTER_QUOTA_ERROR = 2
|
80 |
DEFAULT_OUTPUT_FILENAME_BASE = "alpha_tts_audio"
|
81 |
|
@@ -152,12 +188,14 @@ def merge_audio_files_func(file_paths, output_path):
|
|
152 |
except Exception as e: _log(f"❌ خطا در ادغام فایلهای صوتی: {e}"); return False
|
153 |
|
154 |
def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val):
|
155 |
-
if not GOOGLE_LIBS_AVAILABLE:
|
156 |
-
_log("❌ کتابخانههای گوگل
|
|
|
|
|
|
|
157 |
return None
|
158 |
if NUM_API_KEYS == 0:
|
159 |
_log("❌ هیچ کلید API برای استفاده موجود نیست.")
|
160 |
-
# advance_global_key_index_for_next_request() # حتی اگر کلیدی نیست، برای یکنواختی فراخوانی میشود
|
161 |
return None
|
162 |
|
163 |
_log("🚀 شروع فرآیند تولید صدا...")
|
@@ -181,27 +219,20 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
|
|
181 |
for chunk_idx, chunk_text in enumerate(text_chunks):
|
182 |
chunk_processed_successfully = False
|
183 |
_log(f" 🔊 پردازش قطعه {chunk_idx + 1}/{len(text_chunks)}...")
|
184 |
-
max_attempts_for_chunk = NUM_API_KEYS
|
185 |
|
186 |
for attempt_num_for_chunk in range(max_attempts_for_chunk):
|
187 |
selected_api_key, key_display_num, actual_key_idx = get_api_key_for_attempt(attempt_num_for_chunk)
|
188 |
-
# selected_api_key در این نقطه نباید None باشد چون NUM_API_KEYS > 0 است
|
189 |
-
|
190 |
_log(f" प्रयास {attempt_num_for_chunk + 1}/{max_attempts_for_chunk} برای قطعه {chunk_idx+1} با کلید شماره {key_display_num} (...{selected_api_key[-4:]})")
|
191 |
|
192 |
try:
|
193 |
-
# استفاده از `genai` که در ابتدای فایل import شده
|
194 |
client = genai.Client(api_key=selected_api_key)
|
195 |
-
|
196 |
if prompt_input and prompt_input.strip():
|
197 |
processed_prompt = prompt_input.strip()
|
198 |
-
if not re.search(r'[.!?؟،:۔]$', processed_prompt):
|
199 |
-
processed_prompt += "،"
|
200 |
final_text_for_api = f"{processed_prompt} {chunk_text.strip()}"
|
201 |
-
else:
|
202 |
-
final_text_for_api = chunk_text.strip()
|
203 |
|
204 |
-
# استفاده از `types` که در ابتدای فایل import شده
|
205 |
contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text_for_api)])]
|
206 |
config = types.GenerateContentConfig(temperature=temperature_val, response_modalities=["audio"],
|
207 |
speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
|
@@ -211,122 +242,77 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
|
|
211 |
response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
|
212 |
|
213 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
|
214 |
-
inline_data = response.candidates[0].content.parts[0].inline_data
|
215 |
-
data_buffer = inline_data.data
|
216 |
ext = mimetypes.guess_extension(inline_data.mime_type) or ".wav"
|
217 |
if "audio/L" in inline_data.mime_type and ext == ".wav": data_buffer = convert_to_wav(data_buffer, inline_data.mime_type)
|
218 |
if not ext.startswith("."): ext = "." + ext
|
219 |
-
|
220 |
temp_fpath_for_chunk = f"{fname_base}{ext}"
|
221 |
if os.path.exists(temp_fpath_for_chunk):
|
222 |
try: os.remove(temp_fpath_for_chunk)
|
223 |
except OSError: pass
|
224 |
-
|
225 |
fpath = save_binary_file(temp_fpath_for_chunk, data_buffer)
|
226 |
if fpath:
|
227 |
-
generated_files.append(fpath)
|
228 |
-
chunk_processed_successfully = True
|
229 |
_log(f" ✅ قطعه {chunk_idx+1} با کلید شماره {key_display_num} موفقیت آمیز بود.")
|
230 |
-
if chunk_idx < len(text_chunks) - 1:
|
231 |
-
time.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS)
|
232 |
break
|
233 |
-
else:
|
234 |
-
|
235 |
-
|
236 |
-
except google_exceptions.ResourceExhausted as e_quota: # استفاده از google_exceptions
|
237 |
_log(f" ❌ خطای سهمیه برای قطعه {chunk_idx+1} با کلید شماره {key_display_num}: {str(e_quota)[:100]}...")
|
238 |
if attempt_num_for_chunk < max_attempts_for_chunk - 1:
|
239 |
-
_log(f" ... تلاش با کلید بعدی پس از {RETRY_SLEEP_AFTER_QUOTA_ERROR} ثانیه.")
|
240 |
-
|
241 |
-
else:
|
242 |
-
_log(f" ⛔️ تمام کلیدهای API برای قطعه {chunk_idx+1} امتحان شدند و ناموفق بودند (خطای سهمیه).")
|
243 |
-
all_chunks_processed = False
|
244 |
-
|
245 |
except Exception as e_general:
|
246 |
-
_log(f" ❌ خطای عمومی در تولید قطعه {chunk_idx+1} با کلید {key_display_num}: {str(e_general)[:150]}")
|
247 |
-
if attempt_num_for_chunk < max_attempts_for_chunk - 1:
|
248 |
-
|
249 |
-
|
250 |
-
all_chunks_processed = False
|
251 |
-
|
252 |
-
if chunk_processed_successfully:
|
253 |
-
break
|
254 |
-
|
255 |
if not chunk_processed_successfully:
|
256 |
-
_log(f" ⛔️ پردازش قطعه {chunk_idx+1} پس از {max_attempts_for_chunk} تلاش ناموفق بود.")
|
257 |
-
all_chunks_processed = False
|
258 |
-
break
|
259 |
-
|
260 |
advance_global_key_index_for_next_request()
|
261 |
-
|
262 |
if not all_chunks_processed or not generated_files:
|
263 |
_log("❌ هیچ فایل صوتی معتبری تولید نشد.")
|
264 |
-
for fp in generated_files:
|
265 |
try: os.remove(fp)
|
266 |
except: pass
|
267 |
return None
|
268 |
-
|
269 |
-
final_audio_file = None
|
270 |
-
final_output_path_base = f"{DEFAULT_OUTPUT_FILENAME_BASE}_final"
|
271 |
-
|
272 |
if len(generated_files) > 1:
|
273 |
if PYDUB_AVAILABLE:
|
274 |
merged_fn = f"{final_output_path_base}.wav"
|
275 |
if os.path.exists(merged_fn): os.remove(merged_fn)
|
276 |
-
if merge_audio_files_func(generated_files, merged_fn):
|
277 |
-
final_audio_file = merged_fn
|
278 |
else:
|
279 |
if generated_files:
|
280 |
try:
|
281 |
-
target_ext = os.path.splitext(generated_files[0])[1]
|
282 |
-
renamed_first_chunk = f"{final_output_path_base}{target_ext}"
|
283 |
if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
|
284 |
-
os.rename(generated_files[0], renamed_first_chunk)
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
final_audio_file = generated_files[0]
|
289 |
-
|
290 |
-
for fp_cleanup in generated_files: # پاک کردن فایلهای جزئی
|
291 |
-
if final_audio_file and os.path.abspath(fp_cleanup) == os.path.abspath(final_audio_file):
|
292 |
-
continue
|
293 |
try: os.remove(fp_cleanup)
|
294 |
except: pass
|
295 |
else:
|
296 |
-
_log("⚠️ pydub
|
297 |
if generated_files:
|
298 |
try:
|
299 |
-
target_ext = os.path.splitext(generated_files[0])[1]
|
300 |
-
renamed_first_chunk = f"{final_output_path_base}{target_ext}"
|
301 |
if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
|
302 |
-
os.rename(generated_files[0], renamed_first_chunk)
|
303 |
-
final_audio_file = renamed_first_chunk
|
304 |
for i_gf in range(1, len(generated_files)):
|
305 |
try: os.remove(generated_files[i_gf])
|
306 |
except: pass
|
307 |
-
except Exception as e_rename_single:
|
308 |
-
_log(f"خطا در تغییر نام اولین قطعه (بدون pydub): {e_rename_single}")
|
309 |
-
final_audio_file = generated_files[0]
|
310 |
elif len(generated_files) == 1:
|
311 |
try:
|
312 |
-
target_ext = os.path.splitext(generated_files[0])[1]
|
313 |
-
final_single_fn = f"{final_output_path_base}{target_ext}"
|
314 |
if os.path.exists(final_single_fn): os.remove(final_single_fn)
|
315 |
-
os.rename(generated_files[0], final_single_fn)
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
if final_audio_file and os.path.exists(final_audio_file):
|
322 |
-
_log(f"✅ فایل صوتی نهایی با موفقیت تولید شد: {os.path.basename(final_audio_file)}")
|
323 |
-
elif final_audio_file:
|
324 |
-
_log(f"⚠️ فایل نهایی '{final_audio_file}' پس از پردازش وجود ندارد!")
|
325 |
-
return None
|
326 |
-
else:
|
327 |
-
_log(f"❓ وضعیت نامشخص برای فایل نهایی.")
|
328 |
-
return None
|
329 |
-
|
330 |
return final_audio_file
|
331 |
|
332 |
def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_prompt, speaker_voice, temperature, progress=gr.Progress(track_tqdm=True)):
|
@@ -335,46 +321,27 @@ def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_pr
|
|
335 |
if uploaded_file:
|
336 |
try:
|
337 |
with open(uploaded_file.name, 'r', encoding='utf-8') as f: actual_text = f.read().strip()
|
338 |
-
if not actual_text: _log("❌ فایل آپلود شده خالی
|
339 |
-
except Exception as e: _log(f"❌ خطا در خواندن
|
340 |
-
else: _log("❌
|
341 |
else:
|
342 |
actual_text = text_to_speak
|
343 |
-
if not actual_text or not actual_text.strip(): _log("❌ متن ورودی
|
344 |
-
|
345 |
-
|
346 |
-
gr.Warning("خطای سیستمی: کتابخانههای مورد نیاز گوگل بارگذاری نشدهاند. لطفاً با پشتیبانی تماس بگیرید.")
|
347 |
return None
|
348 |
if NUM_API_KEYS == 0:
|
349 |
-
gr.Warning("خطای سیستمی:
|
350 |
return None
|
351 |
-
|
352 |
final_path = core_generate_audio(actual_text, speech_prompt, speaker_voice, temperature)
|
353 |
-
|
354 |
if final_path is None:
|
355 |
-
gr.Info("
|
356 |
-
|
357 |
return final_path
|
358 |
|
359 |
-
# --- CSS ---
|
360 |
custom_css_inspired_by_image = f"""
|
361 |
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
|
362 |
-
:root {{
|
363 |
-
--app-font: 'Vazirmatn', sans-serif;
|
364 |
-
--app-header-grad-start: #2980b9;
|
365 |
-
--app-header-grad-end: #2ecc71;
|
366 |
-
--app-panel-bg: #FFFFFF;
|
367 |
-
--app-input-bg: #F7F7F7;
|
368 |
-
--app-button-bg: #2979FF;
|
369 |
-
--app-main-bg: linear-gradient(170deg, #E0F2FE 0%, #F3E8FF 100%);
|
370 |
-
--app-text-primary: #333;
|
371 |
-
--app-text-secondary: #555;
|
372 |
-
--app-border-color: #E0E0E0;
|
373 |
-
--radius-card: 20px;
|
374 |
-
--radius-input: 8px;
|
375 |
-
--shadow-card: 0 10px 30px -5px rgba(0,0,0,0.1);
|
376 |
-
--shadow-button: 0 4px 10px -2px rgba(41,121,255,0.5);
|
377 |
-
}}
|
378 |
body, .gradio-container {{ font-family: var(--app-font); direction: rtl; background: var(--app-main-bg); color: var(--app-text-primary); font-size: 16px; line-height: 1.65; }}
|
379 |
.gradio-container {{ max-width:100% !important; min-height:100vh; margin:0 !important; padding:0 !important; display:flex; flex-direction:column; }}
|
380 |
.app-header-alpha {{ padding: 3rem 1.5rem 4rem 1.5rem; text-align: center; background-image: linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%); color: white; border-bottom-left-radius: var(--radius-card); border-bottom-right-radius: var(--radius-card); box-shadow: 0 6px 20px -5px rgba(0,0,0,0.2); }}
|
@@ -397,98 +364,37 @@ label[for*="speaker_voice_alpha_v3"] > .label-text::before {{ content: '🎤'; }
|
|
397 |
label[for*="temperature_slider_alpha_v3"] > .label-text::before {{ content: '🌡️'; }}
|
398 |
#output_audio_player_alpha_v3 audio {{ width: 100%; border-radius: var(--radius-input); margin-top:0.8rem; }}
|
399 |
.temp_description_class_alpha_v3 {{ font-size: 0.85em; color: #777; margin-top: -0.4rem; margin-bottom: 1rem; }}
|
400 |
-
.app-footer-final {{text-align:center;font-size:0.9em;color: var(--app-text-secondary);opacity:0.8; margin-top:3rem;padding:1.5rem 0; border-top:1px solid var(--app-border-color);}}
|
401 |
-
"""
|
402 |
-
|
403 |
-
alpha_header_html_v3 = """
|
404 |
-
<div class='app-header-alpha'>
|
405 |
-
<h1>Alpha TTS</h1>
|
406 |
-
<p>جادوی تبدیل متن به صدا در دستان شما</p>
|
407 |
-
</div>
|
408 |
-
"""
|
409 |
|
410 |
-
|
411 |
-
# فقط در صورتی که کتابخانه اصلی گوگل بارگذاری شده باشد، UI را بساز
|
412 |
-
if GOOGLE_LIBS_AVAILABLE:
|
413 |
with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), css=custom_css_inspired_by_image, title="آلفا TTS") as demo:
|
414 |
gr.HTML(alpha_header_html_v3)
|
415 |
-
|
416 |
with gr.Column(elem_classes=["main-content-panel-alpha"]):
|
417 |
use_file_input_cb = gr.Checkbox(label="📄 استفاده از فایل متنی (.txt)", value=False, elem_id="use_file_cb_alpha_v3")
|
418 |
-
uploaded_file_input = gr.File(
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
)
|
424 |
-
text_to_speak_tb = gr.Textbox(
|
425 |
-
label="متن فارسی برای تبدیل",
|
426 |
-
placeholder="مثال: سلام، فردا هوا چطور است؟",
|
427 |
-
lines=5,
|
428 |
-
value="",
|
429 |
-
visible=True,
|
430 |
-
elem_id="text_input_main_alpha_v3"
|
431 |
-
)
|
432 |
-
use_file_input_cb.change(
|
433 |
-
fn=lambda x: (gr.update(visible=x, label=" " if x else "متن فارسی برای تبدیل"), gr.update(visible=not x)),
|
434 |
-
inputs=use_file_input_cb,
|
435 |
-
outputs=[uploaded_file_input, text_to_speak_tb]
|
436 |
-
)
|
437 |
-
|
438 |
-
speech_prompt_tb = gr.Textbox(
|
439 |
-
label="سبک گفتار (اختیاری)",
|
440 |
-
placeholder="مثال: با لحنی شاد و پرانرژی",
|
441 |
-
value="با لحنی دوستانه و رسا صحبت کن.",
|
442 |
-
lines=2, elem_id="speech_prompt_alpha_v3"
|
443 |
-
)
|
444 |
-
speaker_voice_dd = gr.Dropdown(
|
445 |
-
SPEAKER_VOICES, label="انتخاب گوینده و لهجه", value="Charon", elem_id="speaker_voice_alpha_v3"
|
446 |
-
)
|
447 |
-
temperature_slider = gr.Slider(
|
448 |
-
minimum=0.1, maximum=1.5, step=0.05, value=0.9, label="میزان خلاقیت صدا",
|
449 |
-
elem_id="temperature_slider_alpha_v3"
|
450 |
-
)
|
451 |
gr.Markdown("<p class='temp_description_class_alpha_v3'>مقادیر بالاتر = تنوع بیشتر، مقادیر پایینتر = یکنواختی بیشتر.</p>")
|
452 |
-
|
453 |
generate_button = gr.Button("🚀 تولید و پخش صدا", elem_classes=["generate-button-final"], elem_id="generate_button_alpha_v3")
|
454 |
-
|
455 |
output_audio = gr.Audio(label=" ", type="filepath", elem_id="output_audio_player_alpha_v3")
|
456 |
-
|
457 |
-
generate_button.click(
|
458 |
-
fn=gradio_tts_interface,
|
459 |
-
inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ],
|
460 |
-
outputs=[output_audio]
|
461 |
-
)
|
462 |
-
|
463 |
gr.Markdown("<h3 class='section-title-main-alpha' style='margin-top:2.5rem; text-align:center; border-bottom:none;'>نمونههای کاربردی</h3>", elem_id="examples_section_title_v3")
|
464 |
-
gr.Examples(
|
465 |
-
examples=[
|
466 |
-
[False, None, "سلام بر شما، امیدوارم روز خوبی داشته باشید.", "با لحنی گرم و صمیمی.", "Zephyr", 0.85],
|
467 |
-
[False, None, "این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است. امیدوارم از نتیجه راضی باشید.", "با صدایی طبیعی و روان.", "Charon", 0.9],
|
468 |
-
[False, None, "آیا میتوانم سوالی از شما بپرسم؟ لطفاً راهنمایی کنید.", "با کنجکاوی", "Puck", 0.95],
|
469 |
-
[False, None,
|
470 |
-
"این یک متن بسیار طولانی است که به احتمال زیاد به چندین قطعه تقسیم خواهد شد. هدف از این نمونه، بررسی عملکرد صحیح تقسیم متن و همچنین آزمایش مکانیزم چرخش کلید API در صورتی که سهمیه یک کلید در حین پردازش تمام شود، میباشد. امیدواریم که برنامه بتواند به طور خودکار به کلید بعدی سوئیچ کرده و فرآیند تولید صدا را با موفقیت به اتمام برساند. این بخش اول است. این بخش دوم است. و این هم بخش سوم برای طولانیتر کردن متن.",
|
471 |
-
"با لحنی آرام و واضح", "Achird", 0.8],
|
472 |
-
],
|
473 |
-
inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ],
|
474 |
-
outputs=[output_audio],
|
475 |
-
fn=gradio_tts_interface,
|
476 |
-
cache_examples=False
|
477 |
-
)
|
478 |
gr.Markdown("<p class='app-footer-final'>Alpha Language Learning © 2024</p>")
|
479 |
|
480 |
-
# --- Launch ---
|
481 |
if __name__ == "__main__":
|
482 |
-
if GOOGLE_LIBS_AVAILABLE and NUM_API_KEYS > 0:
|
483 |
demo.launch()
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
with gr.Blocks() as error_demo:
|
493 |
-
gr.Markdown("# خطای پیکربندی \n\n هیچ کلید API معتبری برای سرویس Gemini یافت نشد. لطفاً از تنظیم صحیح Secrets مطمئن شوید.")
|
494 |
error_demo.launch()
|
|
|
6 |
import struct
|
7 |
import time
|
8 |
import zipfile
|
9 |
+
import importlib.metadata # برای بررسی نسخه پکیج
|
10 |
+
|
11 |
+
# --- START: Import کتابخانههای گوگل با بررسی دقیقتر ---
|
12 |
+
GOOGLE_LIBS_AVAILABLE = False
|
13 |
+
GENAI_CLIENT_AVAILABLE = False
|
14 |
+
|
15 |
+
def _log_startup(message): # تابع لاگ مخصوص این بخش
|
16 |
+
print(f"[Startup Log] {message}")
|
17 |
|
|
|
18 |
try:
|
19 |
+
# ابتدا سعی در import کردن google.generativeai
|
20 |
+
import google.generativeai as genai
|
21 |
+
_log_startup("ماژول 'google.generativeai' با موفقیت به عنوان 'genai' وارد شد.")
|
22 |
+
|
23 |
+
# بررسی نسخه نصب شده
|
24 |
+
try:
|
25 |
+
version = importlib.metadata.version('google-generativeai')
|
26 |
+
_log_startup(f"نسخه نصب شده 'google-generativeai': {version}")
|
27 |
+
except importlib.metadata.PackageNotFoundError:
|
28 |
+
_log_startup("هشدار: پکیج 'google-generativeai' نصب شده، اما نسخهی آن قابل تشخیص نیست.")
|
29 |
+
|
30 |
+
# بررسی وجود Client
|
31 |
+
if hasattr(genai, 'Client'):
|
32 |
+
_log_startup("ویژگی 'Client' در ماژول 'genai' (google.generativeai) یافت شد.")
|
33 |
+
GENAI_CLIENT_AVAILABLE = True
|
34 |
+
else:
|
35 |
+
_log_startup("⛔️ خطای مهم: ویژگی 'Client' در ماژول 'genai' (google.generativeai) یافت نشد.")
|
36 |
+
_log_startup(f" محتویات ماژول genai: {dir(genai)}") # نمایش تمام محتویات برای دیباگ بیشتر
|
37 |
+
|
38 |
+
# Import کردن types و exceptions
|
39 |
+
from google.generativeai import types
|
40 |
+
from google.api_core import exceptions as google_exceptions
|
41 |
+
_log_startup("'types' و 'google_exceptions' با موفقیت وارد شدند.")
|
42 |
GOOGLE_LIBS_AVAILABLE = True
|
43 |
+
|
44 |
except ImportError as e:
|
45 |
+
_log_startup(f"❌ خطای حیاتی در Import: {e}")
|
46 |
+
_log_startup(" لطفاً از صحت 'google-generativeai' و 'google-api-core' در requirements.txt و ریاستارت کامل Space مطمئن شوید.")
|
47 |
+
except Exception as e_other:
|
48 |
+
_log_startup(f"❌ خطای ناشناخته در حین import یا بررسی کتابخانههای گوگل: {e_other}")
|
|
|
|
|
|
|
49 |
|
50 |
+
# --- END: Import کتابخانههای گوگل ---
|
51 |
|
52 |
try:
|
53 |
from pydub import AudioSegment
|
54 |
PYDUB_AVAILABLE = True
|
55 |
except ImportError:
|
56 |
+
_log_startup("⚠️ کتابخانه pydub یافت نشد. قابلیت ادغام فایلهای صوتی غیرفعال خواهد بود.")
|
57 |
PYDUB_AVAILABLE = False
|
58 |
|
59 |
+
# --- START: منطق چرخش API Key (بدون تغییر نسبت به قبل) ---
|
60 |
GEMINI_API_KEYS = []
|
61 |
i = 1
|
62 |
while os.environ.get(f"GEMINI_API_KEY_{i}"):
|
63 |
GEMINI_API_KEYS.append(os.environ.get(f"GEMINI_API_KEY_{i}"))
|
64 |
i += 1
|
|
|
65 |
NUM_API_KEYS = len(GEMINI_API_KEYS)
|
66 |
CURRENT_KEY_INDEX_GLOBAL = 0
|
67 |
+
def _log(message): # تابع لاگ اصلی برنامه
|
|
|
68 |
print(f"[لاگ آلفا TTS] {message}")
|
|
|
69 |
if not GOOGLE_LIBS_AVAILABLE:
|
70 |
_log("🔴 به دلیل عدم بارگذاری کتابخانههای اصلی گوگل، عملکرد برنامه مختل خواهد شد.")
|
|
|
71 |
if NUM_API_KEYS == 0:
|
72 |
_log("⛔️ هشدار: هیچ Secret با نام GEMINI_API_KEY_n یافت نشد! برنامه بدون کلید API کار نخواهد کرد.")
|
73 |
else:
|
74 |
_log(f"✅ تعداد {NUM_API_KEYS} کلید API جیمینای بارگذاری شد.")
|
|
|
75 |
def get_api_key_for_attempt(attempt_within_request):
|
76 |
+
if NUM_API_KEYS == 0: return None, -1, -1
|
|
|
|
|
77 |
actual_key_index_in_list = (CURRENT_KEY_INDEX_GLOBAL + attempt_within_request) % NUM_API_KEYS
|
78 |
key_to_use = GEMINI_API_KEYS[actual_key_index_in_list]
|
79 |
key_display_number = actual_key_index_in_list + 1
|
80 |
return key_to_use, key_display_number, actual_key_index_in_list
|
|
|
81 |
def advance_global_key_index_for_next_request():
|
82 |
global CURRENT_KEY_INDEX_GLOBAL
|
83 |
+
if NUM_API_KEYS > 0: CURRENT_KEY_INDEX_GLOBAL = (CURRENT_KEY_INDEX_GLOBAL + 1) % NUM_API_KEYS
|
|
|
84 |
# --- END: منطق چرخش API Key ---
|
85 |
|
86 |
+
# ... (بقیه کد شامل SPEAKER_VOICES, FIXED_MODEL_NAME, توابع save_binary_file, convert_to_wav, etc. بدون تغییر باقی میماند) ...
|
87 |
+
# ... فقط مطمئن شوید که در core_generate_audio، قبل از استفاده از genai.Client، چک GENAI_CLIENT_AVAILABLE را اضافه کنید ...
|
88 |
+
|
89 |
+
# تابع core_generate_audio با یک بررسی اضافه در ابتدا
|
90 |
+
def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val):
|
91 |
+
if not GOOGLE_LIBS_AVAILABLE or not GENAI_CLIENT_AVAILABLE:
|
92 |
+
_log("❌ کتابخانههای گوگل یا genai.Client به درستی بارگذاری نشدهاند. امکان تولید صدا وجود ندارد.")
|
93 |
+
return None
|
94 |
+
# ... (بقیه کد core_generate_audio که قبلاً داشتید، از اینجا شروع میشود) ...
|
95 |
+
# ... و به جای if not GOOGLE_LIBS_AVAILABLE: در ابتدای تابع قبلی، حالا از GENAI_CLIENT_AVAILABLE هم چک میکنید ...
|
96 |
+
|
97 |
+
_log("🚀 شروع فرآیند تولید صدا...")
|
98 |
+
# ... (بقیه کد core_generate_audio بدون تغییر)
|
99 |
+
# client = genai.Client(api_key=selected_api_key) # این خط باید بعد از بررسی GENAI_CLIENT_AVAILABLE باشد
|
100 |
+
|
101 |
+
# ... (بقیه کد شامل SPEAKER_VOICES, FIXED_MODEL_NAME, توابع save_binary_file, convert_to_wav, etc. باید از نسخه کامل قبلی کپی شود)
|
102 |
+
# این یک خلاصه است، کد کامل قبلی را با تغییرات بخش import ترکیب کنید.
|
103 |
+
|
104 |
+
# --- کد کامل توابع کمکی (برای اطمینان از کامل بودن) ---
|
105 |
SPEAKER_VOICES = [
|
106 |
"Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager",
|
107 |
"Sulafat", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux",
|
|
|
111 |
]
|
112 |
FIXED_MODEL_NAME = "gemini-2.5-flash-preview-tts"
|
113 |
DEFAULT_MAX_CHUNK_SIZE = 3800
|
114 |
+
DEFAULT_SLEEP_BETWEEN_REQUESTS = 6
|
115 |
RETRY_SLEEP_AFTER_QUOTA_ERROR = 2
|
116 |
DEFAULT_OUTPUT_FILENAME_BASE = "alpha_tts_audio"
|
117 |
|
|
|
188 |
except Exception as e: _log(f"❌ خطا در ادغام فایلهای صوتی: {e}"); return False
|
189 |
|
190 |
def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val):
|
191 |
+
if not GOOGLE_LIBS_AVAILABLE: # اولین بررسی
|
192 |
+
_log("❌ کتابخانههای گوگل به طور کلی بارگذاری نشدهاند.")
|
193 |
+
return None
|
194 |
+
if not GENAI_CLIENT_AVAILABLE: # بررسی وجود Client
|
195 |
+
_log("❌ ویژگی 'Client' در کتابخانه 'google.generativeai' یافت نشد. لطفاً نسخه کتابخانه را بررسی کنید.")
|
196 |
return None
|
197 |
if NUM_API_KEYS == 0:
|
198 |
_log("❌ هیچ کلید API برای استفاده موجود نیست.")
|
|
|
199 |
return None
|
200 |
|
201 |
_log("🚀 شروع فرآیند تولید صدا...")
|
|
|
219 |
for chunk_idx, chunk_text in enumerate(text_chunks):
|
220 |
chunk_processed_successfully = False
|
221 |
_log(f" 🔊 پردازش قطعه {chunk_idx + 1}/{len(text_chunks)}...")
|
222 |
+
max_attempts_for_chunk = NUM_API_KEYS
|
223 |
|
224 |
for attempt_num_for_chunk in range(max_attempts_for_chunk):
|
225 |
selected_api_key, key_display_num, actual_key_idx = get_api_key_for_attempt(attempt_num_for_chunk)
|
|
|
|
|
226 |
_log(f" प्रयास {attempt_num_for_chunk + 1}/{max_attempts_for_chunk} برای قطعه {chunk_idx+1} با کلید شماره {key_display_num} (...{selected_api_key[-4:]})")
|
227 |
|
228 |
try:
|
|
|
229 |
client = genai.Client(api_key=selected_api_key)
|
|
|
230 |
if prompt_input and prompt_input.strip():
|
231 |
processed_prompt = prompt_input.strip()
|
232 |
+
if not re.search(r'[.!?؟،:۔]$', processed_prompt): processed_prompt += "،"
|
|
|
233 |
final_text_for_api = f"{processed_prompt} {chunk_text.strip()}"
|
234 |
+
else: final_text_for_api = chunk_text.strip()
|
|
|
235 |
|
|
|
236 |
contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text_for_api)])]
|
237 |
config = types.GenerateContentConfig(temperature=temperature_val, response_modalities=["audio"],
|
238 |
speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
|
|
|
242 |
response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
|
243 |
|
244 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
|
245 |
+
inline_data = response.candidates[0].content.parts[0].inline_data; data_buffer = inline_data.data
|
|
|
246 |
ext = mimetypes.guess_extension(inline_data.mime_type) or ".wav"
|
247 |
if "audio/L" in inline_data.mime_type and ext == ".wav": data_buffer = convert_to_wav(data_buffer, inline_data.mime_type)
|
248 |
if not ext.startswith("."): ext = "." + ext
|
|
|
249 |
temp_fpath_for_chunk = f"{fname_base}{ext}"
|
250 |
if os.path.exists(temp_fpath_for_chunk):
|
251 |
try: os.remove(temp_fpath_for_chunk)
|
252 |
except OSError: pass
|
|
|
253 |
fpath = save_binary_file(temp_fpath_for_chunk, data_buffer)
|
254 |
if fpath:
|
255 |
+
generated_files.append(fpath); chunk_processed_successfully = True
|
|
|
256 |
_log(f" ✅ قطعه {chunk_idx+1} با کلید شماره {key_display_num} موفقیت آمیز بود.")
|
257 |
+
if chunk_idx < len(text_chunks) - 1: time.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS)
|
|
|
258 |
break
|
259 |
+
else: _log(f" ⚠️ پاسخ API برای قطعه {chunk_idx+1} با کلید {key_display_num} بدون داده صوتی بود.")
|
260 |
+
except google_exceptions.ResourceExhausted as e_quota:
|
|
|
|
|
261 |
_log(f" ❌ خطای سهمیه برای قطعه {chunk_idx+1} با کلید شماره {key_display_num}: {str(e_quota)[:100]}...")
|
262 |
if attempt_num_for_chunk < max_attempts_for_chunk - 1:
|
263 |
+
_log(f" ... تلاش با کلید بعدی پس از {RETRY_SLEEP_AFTER_QUOTA_ERROR} ثانیه."); time.sleep(RETRY_SLEEP_AFTER_QUOTA_ERROR)
|
264 |
+
else: _log(f" ⛔️ تمام کلیدهای API برای قطعه {chunk_idx+1} امتحان شدند (خطای سهمیه)."); all_chunks_processed = False
|
|
|
|
|
|
|
|
|
265 |
except Exception as e_general:
|
266 |
+
_log(f" ❌ خطای عمومی در تولید قطعه {chunk_idx+1} با کلید {key_display_num}: {type(e_general).__name__} - {str(e_general)[:150]}")
|
267 |
+
if attempt_num_for_chunk < max_attempts_for_chunk - 1: time.sleep(RETRY_SLEEP_AFTER_QUOTA_ERROR)
|
268 |
+
else: all_chunks_processed = False
|
269 |
+
if chunk_processed_successfully: break
|
|
|
|
|
|
|
|
|
|
|
270 |
if not chunk_processed_successfully:
|
271 |
+
_log(f" ⛔️ پردازش قطعه {chunk_idx+1} پس از {max_attempts_for_chunk} تلاش ناموفق بود."); all_chunks_processed = False; break
|
|
|
|
|
|
|
272 |
advance_global_key_index_for_next_request()
|
|
|
273 |
if not all_chunks_processed or not generated_files:
|
274 |
_log("❌ هیچ فایل صوتی معتبری تولید نشد.")
|
275 |
+
for fp in generated_files:
|
276 |
try: os.remove(fp)
|
277 |
except: pass
|
278 |
return None
|
279 |
+
final_audio_file = None; final_output_path_base = f"{DEFAULT_OUTPUT_FILENAME_BASE}_final"
|
|
|
|
|
|
|
280 |
if len(generated_files) > 1:
|
281 |
if PYDUB_AVAILABLE:
|
282 |
merged_fn = f"{final_output_path_base}.wav"
|
283 |
if os.path.exists(merged_fn): os.remove(merged_fn)
|
284 |
+
if merge_audio_files_func(generated_files, merged_fn): final_audio_file = merged_fn
|
|
|
285 |
else:
|
286 |
if generated_files:
|
287 |
try:
|
288 |
+
target_ext = os.path.splitext(generated_files[0])[1]; renamed_first_chunk = f"{final_output_path_base}{target_ext}"
|
|
|
289 |
if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
|
290 |
+
os.rename(generated_files[0], renamed_first_chunk); final_audio_file = renamed_first_chunk
|
291 |
+
except Exception as e_rename: _log(f"خطا در تغییر نام اولین قطعه: {e_rename}"); final_audio_file = generated_files[0]
|
292 |
+
for fp_cleanup in generated_files:
|
293 |
+
if final_audio_file and os.path.abspath(fp_cleanup) == os.path.abspath(final_audio_file): continue
|
|
|
|
|
|
|
|
|
|
|
294 |
try: os.remove(fp_cleanup)
|
295 |
except: pass
|
296 |
else:
|
297 |
+
_log("⚠️ pydub نیست. اولین قطعه ارائه میشود.")
|
298 |
if generated_files:
|
299 |
try:
|
300 |
+
target_ext = os.path.splitext(generated_files[0])[1]; renamed_first_chunk = f"{final_output_path_base}{target_ext}"
|
|
|
301 |
if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
|
302 |
+
os.rename(generated_files[0], renamed_first_chunk); final_audio_file = renamed_first_chunk
|
|
|
303 |
for i_gf in range(1, len(generated_files)):
|
304 |
try: os.remove(generated_files[i_gf])
|
305 |
except: pass
|
306 |
+
except Exception as e_rename_single: _log(f"خطا در تغییر نام (بدون pydub): {e_rename_single}"); final_audio_file = generated_files[0]
|
|
|
|
|
307 |
elif len(generated_files) == 1:
|
308 |
try:
|
309 |
+
target_ext = os.path.splitext(generated_files[0])[1]; final_single_fn = f"{final_output_path_base}{target_ext}"
|
|
|
310 |
if os.path.exists(final_single_fn): os.remove(final_single_fn)
|
311 |
+
os.rename(generated_files[0], final_single_fn); final_audio_file = final_single_fn
|
312 |
+
except Exception as e_rename_single_final: _log(f"خطا در تغییر نام فایل تکی: {e_rename_single_final}"); final_audio_file = generated_files[0]
|
313 |
+
if final_audio_file and os.path.exists(final_audio_file): _log(f"✅ فایل نهایی: {os.path.basename(final_audio_file)}")
|
314 |
+
elif final_audio_file: _log(f"⚠️ فایل نهایی '{final_audio_file}' وجود ندارد!"); return None
|
315 |
+
else: _log(f"❓ وضعیت نامشخص برای فایل نهایی."); return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
316 |
return final_audio_file
|
317 |
|
318 |
def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_prompt, speaker_voice, temperature, progress=gr.Progress(track_tqdm=True)):
|
|
|
321 |
if uploaded_file:
|
322 |
try:
|
323 |
with open(uploaded_file.name, 'r', encoding='utf-8') as f: actual_text = f.read().strip()
|
324 |
+
if not actual_text: _log("❌ فایل آپلود شده خالی است."); return None
|
325 |
+
except Exception as e: _log(f"❌ خطا در خواندن فایل: {e}"); return None
|
326 |
+
else: _log("❌ فایل آپلود نشده."); return None
|
327 |
else:
|
328 |
actual_text = text_to_speak
|
329 |
+
if not actual_text or not actual_text.strip(): _log("❌ متن ورودی خالی."); return None
|
330 |
+
if not GOOGLE_LIBS_AVAILABLE or not GENAI_CLIENT_AVAILABLE:
|
331 |
+
gr.Warning("خطای سیستمی: کتابخانههای مورد نیاز بارگذاری نشدهاند.")
|
|
|
332 |
return None
|
333 |
if NUM_API_KEYS == 0:
|
334 |
+
gr.Warning("خطای سیستمی: کلید API موجود نیست.")
|
335 |
return None
|
|
|
336 |
final_path = core_generate_audio(actual_text, speech_prompt, speaker_voice, temperature)
|
|
|
337 |
if final_path is None:
|
338 |
+
gr.Info("امکان تولید صدا وجود ندارد. لطفاً دقایقی دیگر یا با متن کوتاهتری امتحان کنید.")
|
|
|
339 |
return final_path
|
340 |
|
341 |
+
# --- CSS و UI (بدون تغییر نسبت به نسخه کامل قبلی) ---
|
342 |
custom_css_inspired_by_image = f"""
|
343 |
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
|
344 |
+
:root {{ --app-font: 'Vazirmatn', sans-serif; --app-header-grad-start: #2980b9; --app-header-grad-end: #2ecc71; --app-panel-bg: #FFFFFF; --app-input-bg: #F7F7F7; --app-button-bg: #2979FF; --app-main-bg: linear-gradient(170deg, #E0F2FE 0%, #F3E8FF 100%); --app-text-primary: #333; --app-text-secondary: #555; --app-border-color: #E0E0E0; --radius-card: 20px; --radius-input: 8px; --shadow-card: 0 10px 30px -5px rgba(0,0,0,0.1); --shadow-button: 0 4px 10px -2px rgba(41,121,255,0.5);}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
345 |
body, .gradio-container {{ font-family: var(--app-font); direction: rtl; background: var(--app-main-bg); color: var(--app-text-primary); font-size: 16px; line-height: 1.65; }}
|
346 |
.gradio-container {{ max-width:100% !important; min-height:100vh; margin:0 !important; padding:0 !important; display:flex; flex-direction:column; }}
|
347 |
.app-header-alpha {{ padding: 3rem 1.5rem 4rem 1.5rem; text-align: center; background-image: linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%); color: white; border-bottom-left-radius: var(--radius-card); border-bottom-right-radius: var(--radius-card); box-shadow: 0 6px 20px -5px rgba(0,0,0,0.2); }}
|
|
|
364 |
label[for*="temperature_slider_alpha_v3"] > .label-text::before {{ content: '🌡️'; }}
|
365 |
#output_audio_player_alpha_v3 audio {{ width: 100%; border-radius: var(--radius-input); margin-top:0.8rem; }}
|
366 |
.temp_description_class_alpha_v3 {{ font-size: 0.85em; color: #777; margin-top: -0.4rem; margin-bottom: 1rem; }}
|
367 |
+
.app-footer-final {{text-align:center;font-size:0.9em;color: var(--app-text-secondary);opacity:0.8; margin-top:3rem;padding:1.5rem 0; border-top:1px solid var(--app-border-color);}}"""
|
368 |
+
alpha_header_html_v3 = """<div class='app-header-alpha'><h1>Alpha TTS</h1><p>جادوی تبدیل متن به صدا در دستان شما</p></div>"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
369 |
|
370 |
+
if GOOGLE_LIBS_AVAILABLE and GENAI_CLIENT_AVAILABLE:
|
|
|
|
|
371 |
with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), css=custom_css_inspired_by_image, title="آلفا TTS") as demo:
|
372 |
gr.HTML(alpha_header_html_v3)
|
|
|
373 |
with gr.Column(elem_classes=["main-content-panel-alpha"]):
|
374 |
use_file_input_cb = gr.Checkbox(label="📄 استفاده از فایل متنی (.txt)", value=False, elem_id="use_file_cb_alpha_v3")
|
375 |
+
uploaded_file_input = gr.File(label=" ", file_types=['.txt'], visible=False, elem_id="file_uploader_alpha_main_v3")
|
376 |
+
text_to_speak_tb = gr.Textbox(label="متن فارسی برای تبدیل", placeholder="مثال: سلام، فردا هوا چطور است؟", lines=5, value="", visible=True, elem_id="text_input_main_alpha_v3")
|
377 |
+
use_file_input_cb.change(fn=lambda x: (gr.update(visible=x, label=" " if x else "متن فارسی برای تبدیل"), gr.update(visible=not x)), inputs=use_file_input_cb, outputs=[uploaded_file_input, text_to_speak_tb])
|
378 |
+
speech_prompt_tb = gr.Textbox(label="سبک گفتار (اختیاری)", placeholder="مثال: با لحنی شاد و پرانرژی", value="با لحنی دوستانه و رسا صحبت کن.", lines=2, elem_id="speech_prompt_alpha_v3")
|
379 |
+
speaker_voice_dd = gr.Dropdown(SPEAKER_VOICES, label="انتخاب گوینده و لهجه", value="Charon", elem_id="speaker_voice_alpha_v3")
|
380 |
+
temperature_slider = gr.Slider(minimum=0.1, maximum=1.5, step=0.05, value=0.9, label="میزان خلاقیت صدا", elem_id="temperature_slider_alpha_v3")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
381 |
gr.Markdown("<p class='temp_description_class_alpha_v3'>مقادیر بالاتر = تنوع بیشتر، مقادیر پایینتر = یکنواختی بیشتر.</p>")
|
|
|
382 |
generate_button = gr.Button("🚀 تولید و پخش صدا", elem_classes=["generate-button-final"], elem_id="generate_button_alpha_v3")
|
|
|
383 |
output_audio = gr.Audio(label=" ", type="filepath", elem_id="output_audio_player_alpha_v3")
|
384 |
+
generate_button.click(fn=gradio_tts_interface, inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ], outputs=[output_audio])
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
gr.Markdown("<h3 class='section-title-main-alpha' style='margin-top:2.5rem; text-align:center; border-bottom:none;'>نمونههای کاربردی</h3>", elem_id="examples_section_title_v3")
|
386 |
+
gr.Examples(examples=[[False,None,"سلام بر شما، امیدوارم روز خوبی داشته باشید.","با لحنی گرم و صمیمی.","Zephyr",0.85],[False,None,"این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است. امیدوارم از نتیجه راضی باشید.","با صدایی طبیعی و روان.","Charon",0.9],[False,None,"آیا میتوانم سوالی از شما بپرسم؟ لطفاً راهنمایی کنید.","با کنجکاوی","Puck",0.95],[False,None,"این یک متن بسیار طولانی است که به احتمال زیاد به چندین قطعه تقسیم خواهد شد. هدف از این نمونه، بررسی عملکرد صحیح تقسیم متن و همچنین آزمایش مکانیزم چرخش کلید API در صورتی که سهمیه یک کلید در حین پردازش تمام شود، میباشد. امیدواریم که برنامه بتواند به طور خودکار به کلید بعدی سوئیچ کرده و فرآیند تولید صدا را با موفقیت به اتمام برساند. این بخش اول است. این بخش دوم است. و این هم بخش سوم برای طولانیتر کردن متن.","با لحنی آرام و واضح","Achird",0.8],], inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ], outputs=[output_audio], fn=gradio_tts_interface, cache_examples=False )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
387 |
gr.Markdown("<p class='app-footer-final'>Alpha Language Learning © 2024</p>")
|
388 |
|
|
|
389 |
if __name__ == "__main__":
|
390 |
+
if GOOGLE_LIBS_AVAILABLE and GENAI_CLIENT_AVAILABLE and NUM_API_KEYS > 0:
|
391 |
demo.launch()
|
392 |
+
else:
|
393 |
+
if not GOOGLE_LIBS_AVAILABLE: msg = "کتابخانههای گوگل بارگذاری نشدند."
|
394 |
+
elif not GENAI_CLIENT_AVAILABLE: msg = "'genai.Client' یافت نشد. نسخه کتابخانه را بررسی کنید."
|
395 |
+
elif NUM_API_KEYS == 0: msg = "هیچ کلید API یافت نشد."
|
396 |
+
else: msg = "خطای ناشناخته در شروع برنامه."
|
397 |
+
_log(f"🔴 برنامه به دلیل '{msg}' اجرا نشد.")
|
398 |
+
with gr.Blocks(title="خطا") as error_demo:
|
399 |
+
gr.Markdown(f"# خطای اجرای برنامه\n\n**دلیل:** {msg}\n\nلطفاً لاگهای برنامه یا تنظیمات Space را بررسی کنید.")
|
|
|
|
|
400 |
error_demo.launch()
|