|
|
|
import gradio as gr |
|
import google.generativeai as genai |
|
|
|
import os |
|
import io |
|
from scipy.io.wavfile import write as write_wav |
|
import numpy as np |
|
import traceback |
|
import json |
|
|
|
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") |
|
if not GOOGLE_API_KEY: |
|
raise ValueError("GOOGLE_API_KEY not found in environment variables.") |
|
genai.configure(api_key=GOOGLE_API_KEY) |
|
|
|
|
|
TTS_MODEL_NAME = "gemini-2.5-pro-preview-tts" |
|
|
|
|
|
AVAILABLE_VOICES = ["پیشفرض (مدل انتخاب کند)"] |
|
|
|
def generate_audio(text_to_speak, selected_voice_name="پیشفرض (مدل انتخاب کند)"): |
|
if not text_to_speak: |
|
raise gr.Error("لطفاً متنی را برای تبدیل به صدا وارد کنید.") |
|
print(f"درخواست TTS برای متن: '{text_to_speak[:50]}...' با گوینده: {selected_voice_name} و مدل: models/{TTS_MODEL_NAME}") |
|
|
|
try: |
|
model = genai.GenerativeModel(f"models/{TTS_MODEL_NAME}") |
|
|
|
generation_config_params = {} |
|
|
|
if selected_voice_name != "پیشفرض (مدل انتخاب کند)": |
|
print(f"توجه: انتخاب گوینده ('{selected_voice_name}') هنوز به طور کامل پیادهسازی نشده است.") |
|
|
|
generation_config_to_pass = None |
|
if generation_config_params: |
|
generation_config_to_pass = genai.types.GenerationConfig(**generation_config_params) |
|
print(f"ارسال درخواست به Gemini با generation_config: {generation_config_params}") |
|
else: |
|
print("ارسال درخواست به Gemini بدون generation_config خاص (با تنظیمات پیشفرض مدل).") |
|
|
|
response = model.generate_content( |
|
text_to_speak, |
|
generation_config=generation_config_to_pass |
|
) |
|
|
|
audio_bytes = None |
|
generated_mime_type = None |
|
sample_rate = 24000 |
|
|
|
if hasattr(response, 'candidates') and response.candidates and \ |
|
response.candidates[0].content and response.candidates[0].content.parts: |
|
for part in response.candidates[0].content.parts: |
|
if hasattr(part, 'inline_data') and part.inline_data and \ |
|
hasattr(part.inline_data, 'mime_type') and part.inline_data.mime_type.startswith("audio/"): |
|
audio_bytes = part.inline_data.data |
|
generated_mime_type = part.inline_data.mime_type |
|
if ";rate=" in generated_mime_type: |
|
try: |
|
sample_rate = int(generated_mime_type.split(";rate=")[1]) |
|
print(f"نرخ نمونهبرداری از MIME type استخراج شد: {sample_rate} Hz") |
|
except: |
|
print(f"خطا در استخراج نرخ نمونهبرداری از MIME type: {generated_mime_type}. از پیشفرض {sample_rate} Hz استفاده میشود.") |
|
print(f"داده صوتی با MIME type: {generated_mime_type} دریافت شد.") |
|
break |
|
|
|
if audio_bytes is None: |
|
if hasattr(response, 'audio_content'): |
|
audio_bytes = response.audio_content |
|
generated_mime_type = "audio/wav" |
|
print("داده صوتی از فیلد audio_content دریافت شد.") |
|
else: |
|
print("پاسخ کامل مدل (برای دیباگ):", response) |
|
error_text = response.prompt_feedback if hasattr(response, 'prompt_feedback') else str(response) |
|
raise gr.Error(f"پاسخ صوتی از مدل دریافت نشد. پاسخ مدل: {error_text}") |
|
|
|
output_filename = "output.wav" |
|
if "pcm" in (generated_mime_type or "").lower(): |
|
print(f"داده PCM خام ({len(audio_bytes)} بایت) با نرخ نمونهبرداری {sample_rate} Hz دریافت شد، در حال تبدیل به WAV...") |
|
audio_np = np.frombuffer(audio_bytes, dtype=np.int16) |
|
wav_io = io.BytesIO() |
|
write_wav(wav_io, sample_rate, audio_np) |
|
wav_io.seek(0) |
|
with open(output_filename, "wb") as f: |
|
f.write(wav_io.read()) |
|
elif audio_bytes: |
|
print(f"داده صوتی با فرمت {generated_mime_type} ({len(audio_bytes)} بایت) دریافت شد، مستقیم ذخیره میشود.") |
|
with open(output_filename, "wb") as f: |
|
f.write(audio_bytes) |
|
else: |
|
raise gr.Error("هیچ داده صوتی برای ذخیره وجود ندارد.") |
|
|
|
print(f"فایل صوتی در {output_filename} ذخیره شد.") |
|
return output_filename |
|
|
|
except genai.types.BlockedPromptException as bpe: |
|
print(f"درخواست توسط مدل بلاک شد: {bpe}") |
|
raise gr.Error(f"محتوای شما توسط مدل پذیرفته نشد. لطفاً متن دیگری را امتحان کنید. دلیل: {bpe}") |
|
except Exception as e: |
|
print(f"خطای کلی در تولید صدا: {e}") |
|
traceback.print_exc() |
|
error_message_from_api = "" |
|
|
|
if hasattr(e, 'args') and e.args: |
|
if isinstance(e.args[0], str) and "HttpError" in e.args[0]: |
|
error_message_from_api = str(e.args[0]) |
|
try: |
|
details_start = error_message_from_api.find('{') |
|
if details_start != -1: |
|
json_str_candidate = error_message_from_api[details_start:] |
|
cleaned_json_str = ''.join(c for c in json_str_candidate if ord(c) >= 32 or c in ('\t','\r','\n')).strip() |
|
error_obj = json.loads(cleaned_json_str) |
|
if 'error' in error_obj and 'message' in error_obj['error']: |
|
error_message_from_api = error_obj['error']['message'] |
|
elif 'message' in error_obj : |
|
error_message_from_api = error_obj['message'] |
|
except Exception as json_e: |
|
print(f"خطا در parse کردن جزئیات JSON از پیام خطای API: {json_e}") |
|
else: |
|
error_message_from_api = str(e.args[0]) |
|
elif hasattr(e, 'message') and isinstance(e.message, str): |
|
error_message_from_api = e.message |
|
|
|
final_error_message = f"خطا در ارتباط با Gemini API یا پردازش صدا: {str(e)}" |
|
if error_message_from_api and error_message_from_api not in final_error_message : |
|
final_error_message += f" | پیام دقیقتر API: {error_message_from_api}" |
|
raise gr.Error(final_error_message) |
|
|
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Soft()) as demo: |
|
gr.Markdown("# تبدیل متن به صدا با Gemini ♊") |
|
gr.Markdown("متن خود را وارد کنید تا با استفاده از مدلهای جدید Gemini به صدا تبدیل شود.") |
|
with gr.Row(): |
|
with gr.Column(scale=2): |
|
text_input = gr.Textbox(lines=5, label="متن ورودی", placeholder="متن خود را اینجا بنویسید...") |
|
submit_button = gr.Button("🔊 تبدیل به صدا", variant="primary") |
|
with gr.Column(scale=1): |
|
audio_output = gr.Audio(label="خروجی صدا", type="filepath") |
|
gr.Examples( |
|
examples=[ |
|
["سلام، حال شما چطور است؟"], |
|
["به دنیای هوش مصنوعی خوش آمدید."], |
|
["این یک تست برای تبدیل متن به صدا با استفاده از جیمینای است."] |
|
], |
|
inputs=[text_input] |
|
) |
|
submit_button.click( |
|
fn=generate_audio, |
|
inputs=[text_input], |
|
outputs=[audio_output], |
|
api_name="text_to_speech" |
|
) |
|
gr.Markdown("---") |
|
gr.Markdown(f"مدل مورد استفاده: `models/{TTS_MODEL_NAME}`") |
|
gr.Markdown("توجه: برای انتخاب گویندههای مختلف، نیاز به بررسی مستندات دقیق مدل TTS و بروزرسانی کد است.") |
|
|
|
if __name__ == "__main__": |
|
demo.launch(debug=True) |