Update app.py
Browse files
app.py
CHANGED
|
@@ -6,6 +6,7 @@ import os
|
|
| 6 |
import io
|
| 7 |
from scipy.io.wavfile import write as write_wav
|
| 8 |
import numpy as np
|
|
|
|
| 9 |
|
| 10 |
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 11 |
if not GOOGLE_API_KEY:
|
|
@@ -14,9 +15,6 @@ genai.configure(api_key=GOOGLE_API_KEY)
|
|
| 14 |
|
| 15 |
TTS_MODEL_NAME = "gemini-2.5-flash-preview-tts"
|
| 16 |
|
| 17 |
-
# نامهای گویندهها باید از مستندات دقیق مدل TTS گرفته شود.
|
| 18 |
-
# اینها فقط مثال هستند و ممکن است برای این مدل معتبر نباشند.
|
| 19 |
-
# فعلاً یک لیست ساده با "پیشفرض" میگذاریم.
|
| 20 |
AVAILABLE_VOICES = ["پیشفرض (مدل انتخاب کند)"]
|
| 21 |
# اگر نامهای واقعی را پیدا کردید، اینجا اضافه کنید:
|
| 22 |
# AVAILABLE_VOICES.extend(["voice-name-1", "voice-name-2"])
|
|
@@ -30,26 +28,20 @@ def generate_audio(text_to_speak, selected_voice_name="پیشفرض (مدل
|
|
| 30 |
try:
|
| 31 |
model = genai.GenerativeModel(f"models/{TTS_MODEL_NAME}")
|
| 32 |
|
| 33 |
-
# --- اصلاح کلیدی: تنظیم صریح response_modalities ---
|
| 34 |
generation_config_params = {
|
| 35 |
-
"response_modalities": ["AUDIO"]
|
| 36 |
}
|
| 37 |
|
| 38 |
-
#
|
| 39 |
-
# و ما نام پارامتر صحیح را برای voice در generation_config بدانیم:
|
| 40 |
if selected_voice_name != "پیشفرض (مدل انتخاب کند)":
|
| 41 |
-
#
|
| 42 |
-
#
|
| 43 |
-
# فرض میکنیم "voice" است:
|
| 44 |
-
# generation_config_params["voice"] = selected_voice_name
|
| 45 |
-
# یا اگر ساختار speech_config مانند Live API است:
|
| 46 |
# generation_config_params["speech_config"] = types.SpeechConfig(
|
| 47 |
# voice_config=types.VoiceConfig(
|
| 48 |
# prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=selected_voice_name)
|
| 49 |
# )
|
| 50 |
# )
|
| 51 |
-
print(f"توجه: انتخاب گوینده هنوز پیادهسازی نشده است. از
|
| 52 |
-
|
| 53 |
|
| 54 |
generation_config = genai.types.GenerationConfig(**generation_config_params)
|
| 55 |
|
|
@@ -59,21 +51,18 @@ def generate_audio(text_to_speak, selected_voice_name="پیشفرض (مدل
|
|
| 59 |
text_to_speak,
|
| 60 |
generation_config=generation_config
|
| 61 |
)
|
| 62 |
-
# --- پایان اصلاح ---
|
| 63 |
-
|
| 64 |
|
| 65 |
audio_bytes = None
|
| 66 |
generated_mime_type = None
|
| 67 |
-
sample_rate = 24000 # پیشفرض
|
| 68 |
|
| 69 |
if hasattr(response, 'candidates') and response.candidates and \
|
| 70 |
response.candidates[0].content and response.candidates[0].content.parts:
|
| 71 |
for part in response.candidates[0].content.parts:
|
| 72 |
if hasattr(part, 'inline_data') and part.inline_data and \
|
| 73 |
-
part.inline_data.mime_type.startswith("audio/"):
|
| 74 |
audio_bytes = part.inline_data.data
|
| 75 |
generated_mime_type = part.inline_data.mime_type
|
| 76 |
-
# برخی API ها ممکن است نرخ نمونهبرداری را در mime_type بفرستند
|
| 77 |
if ";rate=" in generated_mime_type:
|
| 78 |
try:
|
| 79 |
sample_rate = int(generated_mime_type.split(";rate=")[1])
|
|
@@ -83,10 +72,10 @@ def generate_audio(text_to_speak, selected_voice_name="پیشفرض (مدل
|
|
| 83 |
print(f"داده صوتی با MIME type: {generated_mime_type} دریافت شد.")
|
| 84 |
break
|
| 85 |
|
| 86 |
-
if audio_bytes is None:
|
| 87 |
if hasattr(response, 'audio_content'):
|
| 88 |
audio_bytes = response.audio_content
|
| 89 |
-
generated_mime_type = "audio/wav"
|
| 90 |
print("داده ��وتی از فیلد audio_content دریافت شد.")
|
| 91 |
else:
|
| 92 |
print("پاسخ کامل مدل (برای دیباگ):", response)
|
|
@@ -94,32 +83,66 @@ def generate_audio(text_to_speak, selected_voice_name="پیشفرض (مدل
|
|
| 94 |
raise gr.Error(f"پاسخ صوتی از مدل دریافت نشد. پاسخ مدل: {error_text}")
|
| 95 |
|
| 96 |
output_filename = "output.wav"
|
| 97 |
-
# فرض میکنیم API بایتهای خام PCM برمیگرداند اگر mime_type شامل pcm باشد
|
| 98 |
-
# یا یک فایل WAV کامل.
|
| 99 |
if "pcm" in (generated_mime_type or "").lower():
|
| 100 |
print(f"داده PCM خام ({len(audio_bytes)} بایت) با نرخ نمونهبرداری {sample_rate} Hz دریافت شد، در حال تبدیل به WAV...")
|
| 101 |
-
audio_np = np.frombuffer(audio_bytes, dtype=np.int16)
|
| 102 |
wav_io = io.BytesIO()
|
| 103 |
write_wav(wav_io, sample_rate, audio_np)
|
| 104 |
wav_io.seek(0)
|
| 105 |
with open(output_filename, "wb") as f:
|
| 106 |
f.write(wav_io.read())
|
| 107 |
-
elif audio_bytes:
|
| 108 |
print(f"داده صوتی با فرمت {generated_mime_type} ({len(audio_bytes)} بایت) دریافت شد، مستقیم ذخیره میشود.")
|
| 109 |
with open(output_filename, "wb") as f:
|
| 110 |
f.write(audio_bytes)
|
| 111 |
else:
|
| 112 |
raise gr.Error("هیچ داده صوتی برای ذخیره وجود ندارد.")
|
| 113 |
|
| 114 |
-
|
| 115 |
print(f"فایل صوتی در {output_filename} ذخیره شد.")
|
| 116 |
return output_filename
|
| 117 |
|
| 118 |
-
except genai.types.BlockedPromptException as bpe:
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
|
| 122 |
-
# ایجاد رابط کاربری Gradio
|
| 123 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 124 |
gr.Markdown("# تبدیل متن به صدا ب�� Gemini ♊")
|
| 125 |
gr.Markdown("متن خود را وارد کنید تا با استفاده از مدلهای جدید Gemini به صدا تبدیل شود.")
|
|
@@ -127,12 +150,19 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 127 |
with gr.Row():
|
| 128 |
with gr.Column(scale=2):
|
| 129 |
text_input = gr.Textbox(lines=5, label="متن ورودی", placeholder="متن خود را اینجا بنویسید...")
|
| 130 |
-
# voice_dropdown = gr.Dropdown(choices=AVAILABLE_VOICES, value=AVAILABLE_VOICES[0], label="انتخاب گوینده") # فعال
|
| 131 |
submit_button = gr.Button("🔊 تبدیل به صدا", variant="primary")
|
| 132 |
with gr.Column(scale=1):
|
| 133 |
audio_output = gr.Audio(label="خروجی صدا", type="filepath")
|
| 134 |
|
| 135 |
-
gr.Examples(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
submit_button.click(
|
| 138 |
fn=generate_audio,
|
|
@@ -146,6 +176,5 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 146 |
gr.Markdown(f"مدل مورد استفاده: `models/{TTS_MODEL_NAME}`")
|
| 147 |
gr.Markdown("توجه: برای انتخاب گویندههای مختلف، نیاز به بررسی مستندات دقیق مدل TTS و بروزرسانی کد است.")
|
| 148 |
|
| 149 |
-
|
| 150 |
if __name__ == "__main__":
|
| 151 |
-
demo.launch(debug=True)
|
|
|
|
| 6 |
import io
|
| 7 |
from scipy.io.wavfile import write as write_wav
|
| 8 |
import numpy as np
|
| 9 |
+
import traceback # برای چاپ کامل خطا
|
| 10 |
|
| 11 |
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 12 |
if not GOOGLE_API_KEY:
|
|
|
|
| 15 |
|
| 16 |
TTS_MODEL_NAME = "gemini-2.5-flash-preview-tts"
|
| 17 |
|
|
|
|
|
|
|
|
|
|
| 18 |
AVAILABLE_VOICES = ["پیشفرض (مدل انتخاب کند)"]
|
| 19 |
# اگر نامهای واقعی را پیدا کردید، اینجا اضافه کنید:
|
| 20 |
# AVAILABLE_VOICES.extend(["voice-name-1", "voice-name-2"])
|
|
|
|
| 28 |
try:
|
| 29 |
model = genai.GenerativeModel(f"models/{TTS_MODEL_NAME}")
|
| 30 |
|
|
|
|
| 31 |
generation_config_params = {
|
| 32 |
+
"response_modalities": ["AUDIO"]
|
| 33 |
}
|
| 34 |
|
| 35 |
+
# برای انتخاب گوینده، این بخش نیاز به اطلاعات از مستندات دارد
|
|
|
|
| 36 |
if selected_voice_name != "پیشفرض (مدل انتخاب کند)":
|
| 37 |
+
# مثال: generation_config_params["voice"] = selected_voice_name
|
| 38 |
+
# یا اگر ساختار speech_config لازم است:
|
|
|
|
|
|
|
|
|
|
| 39 |
# generation_config_params["speech_config"] = types.SpeechConfig(
|
| 40 |
# voice_config=types.VoiceConfig(
|
| 41 |
# prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=selected_voice_name)
|
| 42 |
# )
|
| 43 |
# )
|
| 44 |
+
print(f"توجه: انتخاب گوینده ('{selected_voice_name}') هنوز به طور کامل پیادهسازی نشده است. از تنظیمات پیشفرض مدل برای گوینده استفاده میشود.")
|
|
|
|
| 45 |
|
| 46 |
generation_config = genai.types.GenerationConfig(**generation_config_params)
|
| 47 |
|
|
|
|
| 51 |
text_to_speak,
|
| 52 |
generation_config=generation_config
|
| 53 |
)
|
|
|
|
|
|
|
| 54 |
|
| 55 |
audio_bytes = None
|
| 56 |
generated_mime_type = None
|
| 57 |
+
sample_rate = 24000 # پیشفرض، از مستندات چک شود
|
| 58 |
|
| 59 |
if hasattr(response, 'candidates') and response.candidates and \
|
| 60 |
response.candidates[0].content and response.candidates[0].content.parts:
|
| 61 |
for part in response.candidates[0].content.parts:
|
| 62 |
if hasattr(part, 'inline_data') and part.inline_data and \
|
| 63 |
+
hasattr(part.inline_data, 'mime_type') and part.inline_data.mime_type.startswith("audio/"):
|
| 64 |
audio_bytes = part.inline_data.data
|
| 65 |
generated_mime_type = part.inline_data.mime_type
|
|
|
|
| 66 |
if ";rate=" in generated_mime_type:
|
| 67 |
try:
|
| 68 |
sample_rate = int(generated_mime_type.split(";rate=")[1])
|
|
|
|
| 72 |
print(f"داده صوتی با MIME type: {generated_mime_type} دریافت شد.")
|
| 73 |
break
|
| 74 |
|
| 75 |
+
if audio_bytes is None:
|
| 76 |
if hasattr(response, 'audio_content'):
|
| 77 |
audio_bytes = response.audio_content
|
| 78 |
+
generated_mime_type = "audio/wav"
|
| 79 |
print("داده ��وتی از فیلد audio_content دریافت شد.")
|
| 80 |
else:
|
| 81 |
print("پاسخ کامل مدل (برای دیباگ):", response)
|
|
|
|
| 83 |
raise gr.Error(f"پاسخ صوتی از مدل دریافت نشد. پاسخ مدل: {error_text}")
|
| 84 |
|
| 85 |
output_filename = "output.wav"
|
|
|
|
|
|
|
| 86 |
if "pcm" in (generated_mime_type or "").lower():
|
| 87 |
print(f"داده PCM خام ({len(audio_bytes)} بایت) با نرخ نمونهبرداری {sample_rate} Hz دریافت شد، در حال تبدیل به WAV...")
|
| 88 |
+
audio_np = np.frombuffer(audio_bytes, dtype=np.int16)
|
| 89 |
wav_io = io.BytesIO()
|
| 90 |
write_wav(wav_io, sample_rate, audio_np)
|
| 91 |
wav_io.seek(0)
|
| 92 |
with open(output_filename, "wb") as f:
|
| 93 |
f.write(wav_io.read())
|
| 94 |
+
elif audio_bytes:
|
| 95 |
print(f"داده صوتی با فرمت {generated_mime_type} ({len(audio_bytes)} بایت) دریافت شد، مستقیم ذخیره میشود.")
|
| 96 |
with open(output_filename, "wb") as f:
|
| 97 |
f.write(audio_bytes)
|
| 98 |
else:
|
| 99 |
raise gr.Error("هیچ داده صوتی برای ذخیره وجود ندارد.")
|
| 100 |
|
|
|
|
| 101 |
print(f"فایل صوتی در {output_filename} ذخیره شد.")
|
| 102 |
return output_filename
|
| 103 |
|
| 104 |
+
except genai.types.BlockedPromptException as bpe:
|
| 105 |
+
print(f"درخواست توسط مدل بلاک شد: {bpe}")
|
| 106 |
+
raise gr.Error(f"محتوای شما توسط مدل پذیرفته نشد. لطفاً متن دیگری را امتحان کنید. دلیل: {bpe}")
|
| 107 |
+
except Exception as e: # این بلوک except باید دارای بدنه با تورفتگی باشد
|
| 108 |
+
print(f"خطای کلی در تولید صدا: {e}")
|
| 109 |
+
traceback.print_exc() # چاپ کامل traceback برای دیباگ
|
| 110 |
+
error_message_from_api = ""
|
| 111 |
+
# تلاش برای استخراج پیام خطای دقیقتر از آبجکت خطای google-generativeai
|
| 112 |
+
if hasattr(e, 'args') and e.args:
|
| 113 |
+
# خطاهای API گوگل معمولاً جزئیات را در e.args[0] یا یک ساختار پیچیدهتر دارند
|
| 114 |
+
# برای خطای 400 که قبلاً دیدیم، پیام در e.args[0] بود.
|
| 115 |
+
if isinstance(e.args[0], str) and "HttpError" in e.args[0]:
|
| 116 |
+
try:
|
| 117 |
+
# پیام خطا ممکن است شامل یک رشته JSON باشد
|
| 118 |
+
msg_str = str(e.args[0])
|
| 119 |
+
# استخراج بخش JSON مانند قبل
|
| 120 |
+
details_start = msg_str.find('{')
|
| 121 |
+
if details_start != -1:
|
| 122 |
+
error_details_json = msg_str[details_start:]
|
| 123 |
+
# حذف کاراکترهای کنترلی احتمالی و تلاش برای parse
|
| 124 |
+
cleaned_json_str = ''.join(c for c in error_details_json if ord(c) >= 32 or c in ('\t', '\n', '\r'))
|
| 125 |
+
error_obj = json.loads(cleaned_json_str)
|
| 126 |
+
if 'error' in error_obj and 'message' in error_obj['error']:
|
| 127 |
+
error_message_from_api = error_obj['error']['message']
|
| 128 |
+
elif 'message' in error_obj: # گاهی اوقات پیام مستقیم در آبجکت خطا است
|
| 129 |
+
error_message_from_api = error_obj['message']
|
| 130 |
+
except Exception as json_e:
|
| 131 |
+
print(f"خطا در parse کردن جزئیات خطای API: {json_e}")
|
| 132 |
+
error_message_from_api = str(e.args[0]) # اگر parse نشد، خود پیام اصلی را بگیر
|
| 133 |
+
else:
|
| 134 |
+
error_message_from_api = str(e.args[0])
|
| 135 |
+
|
| 136 |
+
final_error_message = f"خطا در ارتباط با Gemini API یا پردازش صدا: {str(e)}"
|
| 137 |
+
if error_message_from_api and error_message_from_api not in final_error_message:
|
| 138 |
+
final_error_message += f" | پیام دقیقتر API: {error_message_from_api}"
|
| 139 |
+
elif not error_message_from_api and hasattr(e, 'message') and isinstance(e.message, str): # fallback
|
| 140 |
+
final_error_message += f" | {e.message}"
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
raise gr.Error(final_error_message)
|
| 144 |
|
| 145 |
|
|
|
|
| 146 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 147 |
gr.Markdown("# تبدیل متن به صدا ب�� Gemini ♊")
|
| 148 |
gr.Markdown("متن خود را وارد کنید تا با استفاده از مدلهای جدید Gemini به صدا تبدیل شود.")
|
|
|
|
| 150 |
with gr.Row():
|
| 151 |
with gr.Column(scale=2):
|
| 152 |
text_input = gr.Textbox(lines=5, label="متن ورودی", placeholder="متن خود را اینجا بنویسید...")
|
| 153 |
+
# voice_dropdown = gr.Dropdown(choices=AVAILABLE_VOICES, value=AVAILABLE_VOICES[0], label="انتخاب گوینده") # در آینده فعال شود
|
| 154 |
submit_button = gr.Button("🔊 تبدیل به صدا", variant="primary")
|
| 155 |
with gr.Column(scale=1):
|
| 156 |
audio_output = gr.Audio(label="خروجی صدا", type="filepath")
|
| 157 |
|
| 158 |
+
gr.Examples(
|
| 159 |
+
examples=[
|
| 160 |
+
["سلام، حال شما چطور است؟"],
|
| 161 |
+
["به دنیای هوش مصنوعی خوش آمدید."],
|
| 162 |
+
["این یک تست برای تبدیل متن به صدا با استفاده از جیمینای است."]
|
| 163 |
+
],
|
| 164 |
+
inputs=[text_input]
|
| 165 |
+
)
|
| 166 |
|
| 167 |
submit_button.click(
|
| 168 |
fn=generate_audio,
|
|
|
|
| 176 |
gr.Markdown(f"مدل مورد استفاده: `models/{TTS_MODEL_NAME}`")
|
| 177 |
gr.Markdown("توجه: برای انتخاب گویندههای مختلف، نیاز به بررسی مستندات دقیق مدل TTS و بروزرسانی کد است.")
|
| 178 |
|
|
|
|
| 179 |
if __name__ == "__main__":
|
| 180 |
+
demo.launch(debug=True) # debug=True برای دیدن لاگهای دقیقتر در کنسول هاگینگ فیس
|