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 برای دیدن لاگهای دقیقتر در کنسول هاگینگ فیس
|