Hamed744 commited on
Commit
3090f63
·
verified ·
1 Parent(s): ee44815

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -109
app.py CHANGED
@@ -3,139 +3,176 @@ import gradio as gr
3
  import google.generativeai as genai
4
  import os
5
  import io
6
- from scipy.io.wavfile import write as write_wav # برای ذخیره فایل صوتی
 
 
 
 
7
 
8
- # دریافت API Key از Secrets هاگینگ فیس
9
- # مطمئن شوید که یک Secret به نام GOOGLE_API_KEY در اسپیس خود تعریف کرده‌اید
10
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
11
-
12
  if not GOOGLE_API_KEY:
13
- raise ValueError("GOOGLE_API_KEY not found in environment variables. Please set it in Hugging Face Secrets.")
14
-
15
  genai.configure(api_key=GOOGLE_API_KEY)
16
 
17
- # انتخاب مدل TTS
18
- # گزینه ها: "gemini-2.5-flash-preview-tts" یا "gemini-2.5-pro-preview-tts"
19
- # برای شروع، از flash استفاده می‌کنیم
20
- TTS_MODEL_NAME = "gemini-2.5-flash-preview-tts" # یا "tts-1" اگر نام‌های ساده‌تر هم کار می‌کنند (باید مستندات دقیق را چک کرد)
21
- # بر اساس مستندات جدید، نام دقیق مدل‌ها به این شکل است.
22
-
23
- # لیستی از صداهای موجود (این لیست ممکن است نیاز به بروزرسانی بر اساس مستندات دقیق مدل TTS داشته باشد)
24
- # این فقط یک مثال است، باید نام‌های دقیق voice ها را از مستندات پیدا کنید.
25
- # مستندات قبلی برای Live API صداهایی مانند Puck, Charon, Kore, Fenrir, Aoede, Leda, Orus, and Zephyr را ذکر کرده بود.
26
- # اما برای مدل‌های TTS اختصاصی، ممکن است لیست متفاوت باشد یا اصلاً نیازی به انتخاب voice نباشد و مدل خودش بهینه عمل کند.
27
- # فعلاً این بخش را ساده نگه می‌داریم و به مدل اجازه می‌دهیم voice را انتخاب کند.
28
- AVAILABLE_VOICES = ["Default"] # یا لیستی از نام‌های واقعی voice اگر دارید
29
-
30
- def generate_audio(text_to_speak, voice_selection="Default"):
31
- """
32
- متن را به صدا تبدیل می‌کند با استفاده از Gemini API.
33
- """
34
  if not text_to_speak:
35
  raise gr.Error("لطفاً متنی را برای تبدیل به صدا وارد کنید.")
36
- print(f"درخواست TTS برای متن: '{text_to_speak[:50]}...' با voice: {voice_selection}")
37
 
38
  try:
39
- # ایجاد مدل
40
- # توجه: نحوه فراخوانی مدل TTS ممکن است با generate_content متفاوت باشد.
41
- # باید مستندات دقیق را برای "Text-to-speech (TTS)" با Gemini 2.5 Flash/Pro بررسی کنیم.
42
- # فرض می‌کنیم که می‌توانیم با generate_content و ارسال متن، خروجی صوتی بگیریم.
43
- # این بخش احتمالاً نیاز به اصلاح بر اساس API دقیق TTS دارد.
44
-
45
- # بر اساس مستندات قیمت‌گذاری، مدل‌های TTS ورودی متن و خروجی صدا دارند.
46
- # نحوه دقیق فراخوانی برای دریافت بایت‌های صوتی ممکن است به این شکل باشد:
47
- model = genai.GenerativeModel(TTS_MODEL_NAME)
48
-
49
- # برای مدل‌های TTS، "prompt" همان متنی است که می‌خواهیم به صدا تبدیل شود.
50
- # ممکن است نیاز به پارامترهای خاصی در generation_config برای صدا باشد.
51
- response = model.generate_content(
52
- text_to_speak,
53
- # generation_config=genai.types.GenerationConfig(
54
- # # پارامترهای خاص TTS در اینجا قرار می‌گیرند، اگر وجود داشته باشد
55
- # # مثلاً voice، سرعت، لحن و ...
56
- # # response_mime_type="audio/wav" or "audio/mp3" ???
57
- # )
58
- )
59
-
60
- # پاسخ مدل‌های TTS معمولاً شامل بایت‌های صوتی است.
61
- # باید بررسی کنیم که پاسخ در چه فرمتی است.
62
- # فرض می‌کنیم پاسخ دارای یک پراپرتی audio_content یا مشابه است که بایت‌ها را دارد.
63
- # این بخش کاملاً به خروجی واقعی API بستگی دارد.
64
-
65
- # --- این بخش حدسی است و باید با مستندات API تطبیق داده شود ---
66
- if hasattr(response, 'audio_content') and response.audio_content:
67
- audio_bytes = response.audio_content
68
- elif hasattr(response, 'candidates') and response.candidates[0].content.parts[0].inline_data:
69
- # این ساختار برای inline_data در پاسخ‌های چندوجهی است
70
- audio_part = response.candidates[0].content.parts[0]
71
- if audio_part.inline_data.mime_type.startswith("audio/"):
72
- audio_bytes = audio_part.inline_data.data
73
- else:
74
- raise gr.Error(f"فرمت پاسخ صوتی نامعتبر: {audio_part.inline_data.mime_type}")
75
- elif hasattr(response, 'text'): # اگر به اشتباه پاسخ متنی گرفتیم
76
- raise gr.Error(f"مدل پاسخ متنی برگرداند به جای صدا: {response.text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  else:
78
- print("پاسخ کامل مدل:", response) # برای دیباگ
79
- raise gr.Error("پاسخ صوتی از مدل دریافت نشد یا فرمت آن ناشناخته است.")
80
- # --- پایان بخش حدسی ---
81
-
82
- # ذخیره بایت‌های صوتی در یک فایل WAV موقت
83
- # ما به نرخ نمونه‌برداری (sample rate) صدای خروجی نیاز داریم.
84
- # مدل‌های TTS معمولاً با نرخ نمونه‌برداری مشخصی خروجی می‌دهند (مثلاً 24000 Hz).
85
- # این مقدار باید از مستندات API گرفته شود. فرض می‌کنیم 24000 Hz است.
86
- sample_rate = 24000 # هرتز - این را از مستندات API برای مدل TTS خود چک کنید!
87
-
88
- # تبدیل بایت‌ها به فرمتی که Gradio بتواند پخش کند (فایل WAV)
89
- # کتابخانه google-generativeai ممکن است مستقیماً فایل صوتی برنگرداند، بلکه بایت‌های خام PCM.
90
- # یا ممکن است یک آبجکت خاص Audio برگرداند.
91
- # ساده‌ترین حالت این است که API مستقیماً بایت‌های یک فایل WAV را برگرداند.
92
- # اگر بایت‌های خام PCM برمی‌گرداند، باید آنها را به WAV تبدیل کنیم.
93
-
94
- # فرض می‌کنیم audio_bytes حاوی داده‌های یک فایل WAV کامل است
95
- # یا باید با استفاده از scipy.io.wavfile یا wave آن را بسازیم.
96
- # اگر audio_bytes داده خام PCM16 است:
97
- # import numpy as np
98
- # audio_np = np.frombuffer(audio_bytes, dtype=np.int16)
99
- # wav_io = io.BytesIO()
100
- # write_wav(wav_io, sample_rate, audio_np)
101
- # output_audio_path = wav_io # Gradio می‌تواند BytesIO را به عنوان فایل صوتی بپذیرد
102
-
103
- # برای سادگی، فرض می‌کنیم audio_bytes بایت‌های یک فایل صوتی قابل پخش است (مثلاً WAV)
104
- # و Gradio می‌تواند آن را مستقیماً به عنوان (sample_rate, np_array) یا مسیر فایل یا BytesIO بپذیرد.
105
- # اگر API یک آبجکت خاص برمی‌گرداند، باید آن را مطابق مستندات پردازش کنید.
106
-
107
- # برای اینکه Gradio بتواند پخش کند، ما به (sample_rate, numpy_array) نیاز داریم
108
- # یا مسیر یک فایل. اگر بایت‌های خام PCM داریم:
109
- # این بخش نیاز به کار بیشتری دارد اگر API بایت‌های خام PCM برمی‌گرداند.
110
- # فعلاً فرض می‌کنیم API یک فرمت قابل قبول برای Gradio برمی‌گرداند یا ما آن را تبدیل می‌کنیم.
111
-
112
- # ساده‌ترین راه برای تست اولیه: ذخیره بایت‌ها در فایل و برگرداندن مسیر فایل
113
- output_filename = "output_audio.wav"
114
- with open(output_filename, "wb") as f:
115
- f.write(audio_bytes)
116
 
117
  print(f"فایل صوتی در {output_filename} ذخیره شد.")
118
- return output_filename # Gradio می‌تواند مسیر فایل را برای Audio output بگیرد
119
 
 
 
 
120
  except Exception as e:
121
  print(f"خطا در تولید صدا: {e}")
122
- # نمایش جزئیات بیشتر خطا برای دیباگ
123
  import traceback
124
  traceback.print_exc()
125
- raise gr.Error(f"خطا در ارتباط با Gemini API یا پردازش صدا: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- # ایجاد رابط کاربری Gradio
128
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
129
  gr.Markdown("# تبدیل متن به صدا با Gemini ♊")
130
  gr.Markdown("متن خود را وارد کنید تا با استفاده از مدل‌های جدید Gemini به صدا تبدیل شود.")
131
 
132
  with gr.Row():
133
  with gr.Column(scale=2):
134
  text_input = gr.Textbox(lines=5, label="متن ورودی", placeholder="متن خود را اینجا بنویسید...")
135
- # voice_dropdown = gr.Dropdown(choices=AVAILABLE_VOICES, value="Default", label="انتخاب صدا (اختیاری)") # فعلاً ساده
136
  submit_button = gr.Button("🔊 تبدیل به صدا", variant="primary")
137
  with gr.Column(scale=1):
138
- audio_output = gr.Audio(label="خروجی صدا", type="filepath") # یا type="numpy" اگر آرایه برمی‌گردانید
139
 
140
  gr.Examples(
141
  examples=[
@@ -148,13 +185,15 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
148
 
149
  submit_button.click(
150
  fn=generate_audio,
151
- inputs=[text_input], # voice_dropdown اگر فعال بود
152
  outputs=[audio_output],
153
  api_name="text_to_speech"
154
  )
155
 
156
  gr.Markdown("---")
157
- gr.Markdown(f"مدل مورد استفاده: `{TTS_MODEL_NAME}`")
 
 
158
 
159
  if __name__ == "__main__":
160
- demo.launch(debug=True) # debug=True برای دیدن لاگ‌های بیشتر در کنسول
 
3
  import google.generativeai as genai
4
  import os
5
  import io
6
+ # از scipy.io.wavfile استفاده نمی‌کنیم اگر API مستقیم بایت‌های WAV برمی‌گرداند
7
+ # یا اگر فرمت دیگری برمی‌گرداند و ما با روش دیگری به WAV تبدیل می‌کنیم.
8
+ # فعلاً آن را نگه می‌داریم تا ببینیم خروجی API چیست.
9
+ from scipy.io.wavfile import write as write_wav
10
+ import numpy as np # برای کار با آرایه‌های صوتی اگر لازم شد
11
 
 
 
12
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
 
13
  if not GOOGLE_API_KEY:
14
+ raise ValueError("GOOGLE_API_KEY not found in environment variables.")
 
15
  genai.configure(api_key=GOOGLE_API_KEY)
16
 
17
+ TTS_MODEL_NAME = "gemini-2.5-flash-preview-tts" # بر اساس پیام خطا، پیشوند models/ لازم است
18
+
19
+ def generate_audio(text_to_speak):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  if not text_to_speak:
21
  raise gr.Error("لطفاً متنی را برای تبدیل به صدا وارد کنید.")
22
+ print(f"درخواست TTS برای متن: '{text_to_speak[:50]}...'")
23
 
24
  try:
25
+ model = genai.GenerativeModel(f"models/{TTS_MODEL_NAME}") # اضافه کردن پیشوند models/
26
+
27
+ # --- اصلاح کلیدی: تنظیم صریح response_modalities ---
28
+ # مستندات اخیر برای Live API از response_modalities در config اصلی استفاده می‌کرد.
29
+ # اما برای generate_content، معمولاً در generation_config است.
30
+ # همچنین، پیام خطا به "combination of response modalities" اشاره دارد.
31
+ # SDK پایتون ممکن است به response_mime_type یا چیز مشابهی نیاز داشته باشد.
32
+
33
+ # بر اساس پیام خطا، مدل فقط AUDIO را به عنوان response modality می‌پذیرد.
34
+ # نحوه دقیق تنظیم این در SDK پایتون برای TTS API باید از مستندات TTS API بررسی شود.
35
+ # یک تلاش ممکن:
36
+ # response = model.generate_content(text_to_speak, request_options={"response_mime_type": "audio/wav"})
37
+ # یا اگر generation_config پارامتر خاصی برای این دارد.
38
+
39
+ # با توجه به پیام خطا، به نظر می‌رسد API انتظار دارد که بداند خروجی فقط AUDIO است.
40
+ # ممکن است نیازی به generation_config نباشد اگر مدل ذاتاً فقط صدا تولید می‌کند.
41
+ # اما اگر API عمومی generate_content استفاده می‌شود، باید مشخص کنیم.
42
+
43
+ # === شروع بخش مهم برای اصلاح ===
44
+ # تلاش برای استفاده از ساختاری که در مستندات Live API برای response_modalities دیدیم،
45
+ # اما اعمال آن به generate_content ممکن است نیاز به تطبیق داشته باشد.
46
+ # کتابخانه google-generativeai برای TTS ممکن است روش خاص خود را داشته باشد.
47
+ #
48
+ # یک فرض این است که اگر مدل فقط صدا تولید می‌کند، نیازی به تنظیم خاصی نیست
49
+ # و مشکل از جای دیگری است. اما پیام خطا صریحاً به response_modalities اشاره دارد.
50
+ #
51
+ # بیایید سعی کنیم generation_config را با response_mime_type تنظیم کنیم،
52
+ # چون این روشی رایج برای درخواست فرمت خاص خروجی در API های گوگل است.
53
+ # مقدار دقیق mime_type (audio/wav, audio/mpeg, audio/opus) باید از مستندات TTS API بیاید.
54
+ # فرض می‌کنیم WAV:
55
+
56
+ # --- تلاش 1: استفاده از generation_config با response_mime_type ---
57
+ # این یک حدس است و باید مستندات API را برای TTS بررسی کنید.
58
+ # response = model.generate_content(
59
+ # text_to_speak,
60
+ # generation_config=genai.types.GenerationConfig(
61
+ # response_mime_type="audio/wav" # یا "audio/mpeg" یا هر فرمتی که مدل پشتیبانی می‌کند
62
+ # )
63
+ # )
64
+ # اگر این کار نکرد، شاید مدل TTS یک متد خاص به جز generate_content دارد، مثلاً model.synthesize_speech()
65
+
66
+ # --- تلاش 2: ساده‌ترین حالت، با فرض اینکه مدل خودش می‌داند باید صدا تولید کند ---
67
+ # و مشکل از پیشوند models/ بوده.
68
+ # در پیام خطا آمده: "models/gemini-2.5-flash-preview-tts accepts..."
69
+ # پس نام مدل باید با models/ باشد.
70
+
71
+ response = model.generate_content(text_to_speak) # بدون generation_config خاص فعلاً
72
+
73
+
74
+ # --- پردازش پاسخ ---
75
+ # این بخش همچنان حدسی است و به ساختار واقعی پاسخ API بستگی دارد.
76
+ audio_bytes = None
77
+ generated_mime_type = None
78
+
79
+ if hasattr(response, 'candidates') and response.candidates and \
80
+ hasattr(response.candidates[0], 'content') and response.candidates[0].content and \
81
+ hasattr(response.candidates[0].content, 'parts') and response.candidates[0].content.parts:
82
+ for part in response.candidates[0].content.parts:
83
+ if hasattr(part, 'inline_data') and part.inline_data and \
84
+ hasattr(part.inline_data, 'mime_type') and part.inline_data.mime_type.startswith("audio/"):
85
+ audio_bytes = part.inline_data.data
86
+ generated_mime_type = part.inline_data.mime_type
87
+ print(f"داده صوتی با MIME type: {generated_mime_type} دریافت شد.")
88
+ break # اولین بخش صوتی را می‌گیریم
89
+
90
+ if audio_bytes is None and hasattr(response, 'audio_content'): # برخی API ها ممکن است این فیلد را داشته باشند
91
+ audio_bytes = response.audio_content
92
+ generated_mime_type = "audio/wav" # فرض می‌کنیم WAV است اگر mime_type مشخص نیست
93
+ print("داده صوتی از فیلد audio_content دریافت شد.")
94
+
95
+
96
+ if audio_bytes is None:
97
+ print("پاسخ کامل مدل (برای دیباگ):", response)
98
+ try:
99
+ # اگر پاسخ خطا بود، متن خطا را نشان بده
100
+ error_text = response.prompt_feedback if hasattr(response, 'prompt_feedback') else str(response)
101
+ raise gr.Error(f"پاسخ صوتی از مدل دریافت نشد. پاسخ مدل: {error_text}")
102
+ except Exception as e_resp:
103
+ raise gr.Error(f"پاسخ صوتی از مدل دریافت نشد یا فرمت ناشناخته است. خطای داخلی: {e_resp}")
104
+
105
+
106
+ # --- ذخیره و برگرداندن فایل صوتی ---
107
+ # نرخ نمونه‌برداری پیش‌فرض برای اکثر مدل‌های TTS گوگل 24000 هرتز است.
108
+ # این را از مستندات دقیق مدل خود چک کنید.
109
+ sample_rate = 24000
110
+
111
+ output_filename = "output.wav" # همیشه با پسوند wav ذخیره می‌کنیم
112
+
113
+ # اگر API بایت‌های خام PCM برمی‌گرداند، باید آنها را به WAV تبدیل کنیم.
114
+ # فرض می‌کنیم audio_bytes بایت‌های یک فایل WAV کامل است،
115
+ # یا بایت‌های خام PCM که نیاز به تبدیل به WAV با هدر مناسب دارند.
116
+ # برای سادگی فعلاً مستقیم می‌نویسیم، اگر فرمت فایل صوتی کامل باشد.
117
+ if "pcm" in (generated_mime_type or "").lower():
118
+ # اگر PCM خام است، باید هدر WAV را اضافه کنیم
119
+ print("داده PCM خام دریافت شد، در حال تبدیل به WAV...")
120
+ # فرض می‌کنیم 16-bit mono PCM
121
+ audio_np = np.frombuffer(audio_bytes, dtype=np.int16)
122
+ wav_io = io.BytesIO()
123
+ write_wav(wav_io, sample_rate, audio_np)
124
+ wav_io.seek(0)
125
+ with open(output_filename, "wb") as f:
126
+ f.write(wav_io.read())
127
  else:
128
+ # اگر فرمت دیگری است (مثلاً خود WAV یا MP3)، مستقیم می‌نویسیم
129
+ print(f"داده صوتی با فرمت {generated_mime_type} دریافت شد، مستقیم ذخیره می‌شود.")
130
+ with open(output_filename, "wb") as f:
131
+ f.write(audio_bytes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
  print(f"فایل صوتی در {output_filename} ذخیره شد.")
134
+ return output_filename
135
 
136
+ except genai.types.BlockedPromptException as bpe:
137
+ print(f"درخواست توسط مدل بلاک شد: {bpe}")
138
+ raise gr.Error(f"محتوای شما توسط مدل پذیرفته نشد. لطفاً متن دیگری را امتحان کنید. دلیل: {bpe}")
139
  except Exception as e:
140
  print(f"خطا در تولید صدا: {e}")
 
141
  import traceback
142
  traceback.print_exc()
143
+ # نمایش پیام خطای اصلی از API اگر موجود است
144
+ error_message_from_api = ""
145
+ if hasattr(e, 'message') and isinstance(e.message, str) and "HttpError" in e.message: # نمونه
146
+ try:
147
+ # تلاش برای استخراج جزئیات بیشتر از خطای API
148
+ import json
149
+ details_start = e.message.find('{')
150
+ if details_start != -1:
151
+ error_details_json = e.message[details_start:]
152
+ error_obj = json.loads(error_details_json.strip().replace('\n', '').replace('\\n', ''))
153
+ if 'error' in error_obj and 'message' in error_obj['error']:
154
+ error_message_from_api = error_obj['error']['message']
155
+ except:
156
+ pass # اگر نشد، مهم نیست
157
+
158
+ final_error_message = f"خطا در ارتباط با Gemini API یا پردازش صدا: {str(e)}"
159
+ if error_message_from_api:
160
+ final_error_message += f" | پیام API: {error_message_from_api}"
161
+
162
+ raise gr.Error(final_error_message)
163
+
164
 
 
165
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
166
+ # ... (بقیه کد رابط کاربری Gradio بدون تغییر) ...
167
  gr.Markdown("# تبدیل متن به صدا با Gemini ♊")
168
  gr.Markdown("متن خود را وارد کنید تا با استفاده از مدل‌های جدید Gemini به صدا تبدیل شود.")
169
 
170
  with gr.Row():
171
  with gr.Column(scale=2):
172
  text_input = gr.Textbox(lines=5, label="متن ورودی", placeholder="متن خود را اینجا بنویسید...")
 
173
  submit_button = gr.Button("🔊 تبدیل به صدا", variant="primary")
174
  with gr.Column(scale=1):
175
+ audio_output = gr.Audio(label="خروجی صدا", type="filepath")
176
 
177
  gr.Examples(
178
  examples=[
 
185
 
186
  submit_button.click(
187
  fn=generate_audio,
188
+ inputs=[text_input],
189
  outputs=[audio_output],
190
  api_name="text_to_speech"
191
  )
192
 
193
  gr.Markdown("---")
194
+ # نمایش نام مدل با پیشوند models/ بر اساس پیام خطا
195
+ gr.Markdown(f"مدل مورد استفاده: `models/{TTS_MODEL_NAME}`")
196
+
197
 
198
  if __name__ == "__main__":
199
+ demo.launch(debug=True)