import os import time import asyncio import logging import threading from typing import List, Optional, Any from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse, FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles from pydantic import BaseModel import edge_tts from deep_translator import GoogleTranslator import uuid # --- پیکربندی اولیه --- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') AUDIO_DIR = "audio_cache" os.makedirs(AUDIO_DIR, exist_ok=True) # پاکسازی فایل‌های صوتی قدیمی برای جلوگیری از پر شدن هاست def cleanup_old_audio(): while True: try: now = time.time() for filename in os.listdir(AUDIO_DIR): filepath = os.path.join(AUDIO_DIR, filename) if os.stat(filepath).st_mtime < now - 3600: # حذف فایل‌های قدیمی‌تر از ۱ ساعت os.remove(filepath) except Exception as e: logging.error(f"Cleanup error: {e}") time.sleep(1800) threading.Thread(target=cleanup_old_audio, daemon=True).start() # --- دیتابیس جامع زبان‌ها و صداها --- # زبان‌های پرکاربرد در ابتدا قرار گرفته‌اند و سپس سایر زبان‌ها LANGUAGES_MAP = { # --- زبان‌های پرکاربرد --- "انگلیسی": {"code": "en", "voices": {'زن - جنی (آمریکا)': 'en-US-JennyNeural', 'مرد - گای (آمریکا)': 'en-US-GuyNeural', 'زن - آریا (آمریکا)': 'en-US-AriaNeural', 'مرد - کریستوفر (آمریکا)': 'en-US-ChristopherNeural', 'زن - لیبی (بریتانیا)': 'en-GB-LibbyNeural', 'مرد - رایان (بریتانیا)': 'en-GB-RyanNeural', 'زن - ناتاشا (استرالیا)': 'en-AU-NatashaNeural', 'مرد - ویلیام (استرالیا)': 'en-AU-WilliamNeural', 'مرد - لیام (کانادا)': 'en-CA-LiamNeural', 'زن - کلارا (کانادا)': 'en-CA-ClaraNeural', 'زن - نیرجا (هند)': 'en-IN-NeerjaNeural', 'مرد - پرابات (هند)': 'en-IN-PrabhatNeural', 'زن - آسیمی (نیجریه)': 'en-NG-EzinneNeural', 'مرد - آبه (نیجریه)': 'en-NG-AbeoNeural'}}, "فارسی": {"code": "fa", "voices": {'زن - دلارا': 'fa-IR-DilaraNeural', 'مرد - فرید': 'fa-IR-FaridNeural'}}, "عربی": {"code": "ar", "voices": {'زن - فاطمه (امارات)': 'ar-AE-FatimaNeural', 'مرد - حمدان (امارات)': 'ar-AE-HamdanNeural', 'زن - سلما (مصر)': 'ar-EG-SalmaNeural', 'مرد - شاکر (مصر)': 'ar-EG-ShakirNeural', 'مرد - حامد (عربستان)': 'ar-SA-HamedNeural', 'زن - زاریة (عربستان)': 'ar-SA-ZariyahNeural'}}, "چینی": {"code": "zh-CN", "voices": {'مرد - یونشی': 'zh-CN-YunxiNeural', 'زن - شیائوشیا': 'zh-CN-XiaoxiaoNeural', 'زن - شیائویی': 'zh-CN-XiaoyiNeural', 'مرد - یونجیان': 'zh-CN-YunjianNeural'}}, "چینی (سنتی)": {"code": "zh-TW", "voices": {'زن - هسیاوچن': 'zh-TW-HsiaoChenNeural', 'مرد - یونژه': 'zh-TW-YunJheNeural', 'زن - هسیاویو': 'zh-TW-HsiaoYuNeural'}}, "ترکی": {"code": "tr", "voices": {'زن - امل': 'tr-TR-EmelNeural', 'مرد - احمد': 'tr-TR-AhmetNeural'}}, "فرانسوی": {"code": "fr", "voices": {'زن - الویز (فرانسه)': 'fr-FR-EloiseNeural', 'مرد - هنری (فرانسه)': 'fr-FR-HenriNeural', 'زن - دنیس (فرانسه)': 'fr-FR-DeniseNeural', 'زن - سیلویا (کانادا)': 'fr-CA-SylvieNeural', 'مرد - آنتوان (کانادا)': 'fr-CA-AntoineNeural', 'زن - آریان (سوئیس)': 'fr-CH-ArianeNeural', 'مرد - فابریس (سوئیس)': 'fr-CH-FabriceNeural', 'زن - شارلین (بلژیک)': 'fr-BE-CharlineNeural', 'مرد - جرارد (بلژیک)': 'fr-BE-GerardNeural'}}, "آلمانی": {"code": "de", "voices": {'زن - کاترین (آلمان)': 'de-DE-KatjaNeural', 'مرد - کنراد (آلمان)': 'de-DE-ConradNeural', 'زن - آملی (آلمان)': 'de-DE-AmelieNeural', 'زن - اینگرید (اتریش)': 'de-AT-IngridNeural', 'مرد - یوناس (اتریش)': 'de-AT-JonasNeural', 'زن - لنی (سوئیس)': 'de-CH-LeniNeural', 'مرد - یان (سوئیس)': 'de-CH-JanNeural'}}, "اسپانیایی": {"code": "es", "voices": {'زن - الویرا (اسپانیا)': 'es-ES-ElviraNeural', 'مرد - آلوارو (اسپانیا)': 'es-ES-AlvaroNeural', 'زن - دالیا (مکزیک)': 'es-MX-DaliaNeural', 'مرد - خورخه (مکزیک)': 'es-MX-JorgeNeural', 'زن - النا (آرژانتین)': 'es-AR-ElenaNeural', 'مرد - توماس (آرژانتین)': 'es-AR-TomasNeural', 'زن - سالومه (کلمبیا)': 'es-CO-SalomeNeural', 'مرد - گونزالو (کلمبیا)': 'es-CO-GonzaloNeural'}}, "روسی": {"code": "ru", "voices": {'زن - سوتلانا': 'ru-RU-SvetlanaNeural', 'مرد - دیمیتری': 'ru-RU-DmitryNeural', 'زن - داریا': 'ru-RU-DariyaNeural'}}, "ژاپنی": {"code": "ja", "voices": {'زن - نانامی': 'ja-JP-NanamiNeural', 'مرد - کیتا': 'ja-JP-KeitaNeural', 'زن - آئوی': 'ja-JP-AoiNeural'}}, "ایتالیایی": {"code": "it", "voices": {'زن - السا': 'it-IT-ElsaNeural', 'مرد - دیگو': 'it-IT-DiegoNeural', 'زن - ایزابلا': 'it-IT-IsabellaNeural'}}, "کره‌ای": {"code": "ko", "voices": {'زن - سونوهی': 'ko-KR-SunHiNeural', 'مرد - اینجون': 'ko-KR-InJoonNeural', 'زن - جیمین': 'ko-KR-JiMinNeural'}}, "هندی": {"code": "hi", "voices": {'زن - مادور': 'hi-IN-MadhurNeural', 'مرد - سوآرا': 'hi-IN-SwaraNeural'}}, "هلندی": {"code": "nl", "voices": {'زن - کوئلت': 'nl-NL-ColetteNeural', 'مرد - مارتین': 'nl-NL-MaartenNeural', 'زن - دنا (بلژیک)': 'nl-BE-DenaNeural', 'مرد - آرنو (بلژیک)': 'nl-BE-ArnaudNeural'}}, "سوئدی": {"code": "sv", "voices": {'زن - سوفی': 'sv-SE-SofieNeural', 'مرد - ماتیاس': 'sv-SE-MattiasNeural'}}, "لهستانی": {"code": "pl", "voices": {'زن - زوفیا': 'pl-PL-ZofiaNeural', 'مرد - مارک': 'pl-PL-MarekNeural'}}, "پرتغالی": {"code": "pt", "voices": {'زن - فرانسیسکا (برزیل)': 'pt-BR-FranciscaNeural', 'مرد - آنتونیو (برزیل)': 'pt-BR-AntonioNeural', 'زن - راکل (پرتغال)': 'pt-PT-RaquelNeural', 'مرد - دوارته (پرتغال)': 'pt-PT-DuarteNeural'}}, # --- سایر زبان‌ها --- "آفریقانس": {"code": "af", "voices": {'زن - آدریا': 'af-ZA-AdriNeural', 'مرد - ویلم': 'af-ZA-WillemNeural'}}, "آلبانیایی": {"code": "sq", "voices": {'زن - آنیرا': 'sq-AL-AnilaNeural', 'مرد - ایلیر': 'sq-AL-IlirNeural'}}, "آمهاری": {"code": "am", "voices": {'زن - مکدز': 'am-ET-MekdesNeural', 'مرد - آمها': 'am-ET-AmehaNeural'}}, "ارمنی": {"code": "hy", "voices": {}}, "آذربایجانی": {"code": "az", "voices": {'زن - بانو': 'az-AZ-BanuNeural', 'مرد - بابک': 'az-AZ-BabekNeural'}}, "باسک": {"code": "eu", "voices": {'زن - آینهوا': 'eu-ES-AinhoaNeural', 'مرد - آندر': 'eu-ES-AnderNeural'}}, "بلاروسی": {"code": "be", "voices": {}}, "بنگالی": {"code": "bn", "voices": {'زن - نابانیا (بنگلادش)': 'bn-BD-NabanitaNeural', 'مرد - پردیپ (بنگلادش)': 'bn-BD-PradeepNeural', 'زن - تانیا (هند)': 'bn-IN-TanyaNeural', 'مرد - بشکر (هند)': 'bn-IN-BashkarNeural'}}, "بوسنیایی": {"code": "bs", "voices": {'زن - وسنا': 'bs-BA-VesnaNeural', 'مرد - گوران': 'bs-BA-GoranNeural'}}, "بلغاری": {"code": "bg", "voices": {'زن - کالینا': 'bg-BG-KalinaNeural', 'مرد - بوریس': 'bg-BG-BorislavNeural'}}, "کاتالان": {"code": "ca", "voices": {'زن - جوآنا': 'ca-ES-JoanaNeural', 'مرد - انریک': 'ca-ES-EnricNeural'}}, "سبوانو": {"code": "ceb", "voices": {}}, "کورسی": {"code": "co", "voices": {}}, "کروات": {"code": "hr", "voices": {'زن - گابریلا': 'hr-HR-GabrijelaNeural', 'مرد - سرکو': 'hr-HR-SreckoNeural'}}, "چک": {"code": "cs", "voices": {'زن - ولاستا': 'cs-CZ-VlastaNeural', 'مرد - آنتونین': 'cs-CZ-AntoninNeural'}}, "دانمارکی": {"code": "da", "voices": {'زن - کریستل': 'da-DK-ChristelNeural', 'مرد - جپ': 'da-DK-JeppeNeural'}}, "اسپرانتو": {"code": "eo", "voices": {}}, "استونیایی": {"code": "et", "voices": {'زن - آنو': 'et-EE-AnuNeural', 'مرد - کرت': 'et-EE-KertNeural'}}, "فنلاندی": {"code": "fi", "voices": {'زن - سلما': 'fi-FI-SelmaNeural', 'مرد - هری': 'fi-FI-HarriNeural'}}, "فریسی": {"code": "fy", "voices": {}}, "گالیسیایی": {"code": "gl", "voices": {'زن - سابیا': 'gl-ES-SabelaNeural', 'مرد - روی': 'gl-ES-RoiNeural'}}, "گرجی": {"code": "ka", "voices": {'زن - اکا': 'ka-GE-EkaNeural', 'مرد - گیورگی': 'ka-GE-GiorgiNeural'}}, "یونانی": {"code": "el", "voices": {'زن - آتینا': 'el-GR-AthinaNeural', 'مرد - نستوراس': 'el-GR-NestorasNeural'}}, "گجراتی": {"code": "gu", "voices": {'زن - دوانی': 'gu-IN-DhwaniNeural', 'مرد - نیرانجان': 'gu-IN-NiranjanNeural'}}, "هائیتی": {"code": "ht", "voices": {}}, "هاوسا": {"code": "ha", "voices": {}}, "هاوایی": {"code": "haw", "voices": {}}, "عبری": {"code": "iw", "voices": {'زن - هیلا': 'he-IL-HilaNeural', 'مرد - آوری': 'he-IL-AvriNeural'}}, # کد ترجمه به iw تغییر کرد "همونگ": {"code": "hmn", "voices": {}}, "مجاری": {"code": "hu", "voices": {'زن - نوئمی': 'hu-HU-NoemiNeural', 'مرد - تاماس': 'hu-HU-TamasNeural'}}, "ایسلندی": {"code": "is", "voices": {'زن - گودرون': 'is-IS-GudrunNeural', 'مرد - گونار': 'is-IS-GunnarNeural'}}, "ایگبو": {"code": "ig", "voices": {}}, "اندونزیایی": {"code": "id", "voices": {'زن - گادیس': 'id-ID-GadisNeural', 'مرد - آردی': 'id-ID-ArdiNeural'}}, "ایرلندی": {"code": "ga", "voices": {'زن - اورلا': 'ga-IE-OrlaNeural', 'مرد - کولم': 'ga-IE-ColmNeural'}}, "جاوه‌ای": {"code": "jw", "voices": {'زن - سیتی': 'jv-ID-SitiNeural', 'مرد - بیما': 'jv-ID-BimasenaNeural'}}, # کد ترجمه به jw تغییر کرد "کانادا": {"code": "kn", "voices": {'زن - ساپانا': 'kn-IN-SapnaNeural', 'مرد - گاگان': 'kn-IN-GaganNeural'}}, "قزاقی": {"code": "kk", "voices": {'زن - آیگول': 'kk-KZ-AigulNeural', 'مرد - دولت': 'kk-KZ-DauletNeural'}}, "خمر": {"code": "km", "voices": {'زن - سریموچ': 'km-KH-SreymomNeural', 'مرد - پیسث': 'km-KH-PisethNeural'}}, "کردی": {"code": "ku", "voices": {}}, "قرقیزی": {"code": "ky", "voices": {}}, "لائو": {"code": "lo", "voices": {'زن - چانتاوونگ': 'lo-LA-ChanthavongNeural', 'مرد - کئو': 'lo-LA-KeoNeural'}}, "لاتین": {"code": "la", "voices": {}}, "لتونیایی": {"code": "lv", "voices": {'زن - اورلیا': 'lv-LV-EveritaNeural', 'مرد - نیلس': 'lv-LV-NilsNeural'}}, "لیتوانیایی": {"code": "lt", "voices": {'زن - اونا': 'lt-LT-OnaNeural', 'مرد - لئوناس': 'lt-LT-LeonasNeural'}}, "لوکزامبورگی": {"code": "lb", "voices": {}}, "مقدونی": {"code": "mk", "voices": {'زن - ماریا': 'mk-MK-MarijaNeural', 'مرد - الکساندر': 'mk-MK-AleksandarNeural'}}, "مالاگاسی": {"code": "mg", "voices": {}}, "مالایی": {"code": "ms", "voices": {'زن - یاسمین': 'ms-MY-YasminNeural', 'مرد - عثمان': 'ms-MY-OsmanNeural'}}, "مالایالم": {"code": "ml", "voices": {'زن - سوبهانجا': 'ml-IN-SobhanaNeural', 'مرد - میدون': 'ml-IN-MidhunNeural'}}, "مالتی": {"code": "mt", "voices": {'زن - گریس': 'mt-MT-GraceNeural', 'مرد - جوزف': 'mt-MT-JosephNeural'}}, "مائوری": {"code": "mi", "voices": {}}, "مراتی": {"code": "mr", "voices": {'زن - آروهی': 'mr-IN-AarohiNeural', 'مرد - مانوهر': 'mr-IN-ManoharNeural'}}, "مغولی": {"code": "mn", "voices": {'زن - یسوی': 'mn-MN-YesuiNeural', 'مرد - باتا': 'mn-MN-BataaNeural'}}, "میانماری (برمه‌ای)": {"code": "my", "voices": {'زن - نیلار': 'my-MM-NilarNeural', 'مرد - تیها': 'my-MM-ThihaNeural'}}, "نپالی": {"code": "ne", "voices": {'زن - هما': 'ne-NP-HemkalaNeural', 'مرد - ساگار': 'ne-NP-SagarNeural'}}, "نروژی": {"code": "no", "voices": {'زن - پرنیله': 'nb-NO-PernilleNeural', 'مرد - فین': 'nb-NO-FinnNeural'}}, "پشتو": {"code": "ps", "voices": {'زن - لطیفه': 'ps-AF-LatifaNeural', 'مرد - گل‌نواز': 'ps-AF-GulNawazNeural'}}, "پنجابی": {"code": "pa", "voices": {}}, "رومانیایی": {"code": "ro", "voices": {'زن - آلینا': 'ro-RO-AlinaNeural', 'مرد - امیل': 'ro-RO-EmilNeural'}}, "صربی": {"code": "sr", "voices": {'زن - سوفیا': 'sr-RS-SophieNeural', 'مرد - نیکولا': 'sr-RS-NicholasNeural'}}, "سسوتو": {"code": "st", "voices": {}}, "سینهالی": {"code": "si", "voices": {'زن - تیمی': 'si-LK-ThiliniNeural', 'مرد - سامیرا': 'si-LK-SameeraNeural'}}, "اسلواکی": {"code": "sk", "voices": {'زن - ویکتوریا': 'sk-SK-ViktoriaNeural', 'مرد - لوکاس': 'sk-SK-LukasNeural'}}, "اسلوونیایی": {"code": "sl", "voices": {'زن - پترا': 'sl-SI-PetraNeural', 'مرد - روک': 'sl-SI-RokNeural'}}, "سومالیایی": {"code": "so", "voices": {'زن - اوباخ': 'so-SO-UbaxNeural', 'مرد - موزه': 'so-SO-MuuseNeural'}}, "سوندانی": {"code": "su", "voices": {'زن - توتی': 'su-ID-TutiNeural', 'مرد - جاجانگ': 'su-ID-JajangNeural'}}, "سواحیلی": {"code": "sw", "voices": {'زن - زوری (کنیا)': 'sw-KE-ZuriNeural', 'مرد - رفیق (کنیا)': 'sw-KE-RafikiNeural', 'زن - ریما (تانزانیا)': 'sw-TZ-RehemaNeural', 'مرد - دادی (تانزانیا)': 'sw-TZ-DaudiNeural'}}, "تاجیکی": {"code": "tg", "voices": {}}, "تامیلی": {"code": "ta", "voices": {'زن - پالاوی (هند)': 'ta-IN-PallaviNeural', 'مرد - والان (هند)': 'ta-IN-ValluvarNeural', 'زن - کانی (سریلانکا)': 'ta-LK-KaniNeural', 'مرد - کومار (سریلانکا)': 'ta-LK-KumarNeural'}}, "تلوگو": {"code": "te", "voices": {'زن - شروتی': 'te-IN-ShrutiNeural', 'مرد - موهان': 'te-IN-MohanNeural'}}, "تایلندی": {"code": "th", "voices": {'زن - پرم‌وادی': 'th-TH-PremwadeeNeural', 'مرد - نیوات': 'th-TH-NiwatNeural'}}, "اوکراینی": {"code": "uk", "voices": {'زن - پولینا': 'uk-UA-PolinaNeural', 'مرد - اوستاپ': 'uk-UA-OstapNeural'}}, "اردو": {"code": "ur", "voices": {'زن - گل (پاکستان)': 'ur-PK-UzmaNeural', 'مرد - اسد (پاکستان)': 'ur-PK-AsadNeural', 'زن - گلرخ (هند)': 'ur-IN-GulNeural', 'مرد - سلمان (هند)': 'ur-IN-SalmanNeural'}}, "ازبکی": {"code": "uz", "voices": {'زن - مدینه': 'uz-UZ-MadinaNeural', 'مرد - سردار': 'uz-UZ-SardorNeural'}}, "ویتنامی": {"code": "vi", "voices": {'زن - هوآی‌می': 'vi-VN-HoaiMyNeural', 'مرد - نام‌مین': 'vi-VN-NamMinhNeural'}}, "ولزی": {"code": "cy", "voices": {'زن - نیا': 'cy-GB-NiaNeural', 'مرد - آلد': 'cy-GB-AledNeural'}}, "خوسا": {"code": "xh", "voices": {}}, "یدی": {"code": "yi", "voices": {}}, "یوروبا": {"code": "yo", "voices": {}}, "زولو": {"code": "zu", "voices": {'زن - تاندکا': 'zu-ZA-ThandoNeural', 'مرد - تبوگو': 'zu-ZA-ThembaNeural'}} } # --- دیتابیس قدیمی برای پشتیبانی API دوستان شما --- LEGACY_VOICES = { 'انگلیسی (آمریکا) - جنی (زن)': 'en-US-JennyNeural', 'انگلیسی (آمریکا) - گای (مرد)': 'en-US-GuyNeural', 'انگلیسی (آمریکا) - آنا (زن، صدای کودک)': 'en-US-AnaNeural', 'انگلیسی (آمریکا) - آریا (زن)': 'en-US-AriaNeural', 'انگلیسی (بریتانیا) - لیبی (زن)': 'en-GB-LibbyNeural', 'انگلیسی (بریتانیا) - رایان (مرد)': 'en-GB-RyanNeural', } # --- راه‌اندازی سرور --- app = FastAPI(title="Alpha Translator API") app.mount("/audio_cache", StaticFiles(directory=AUDIO_DIR), name="audio_cache") # --- توابع اصلی پردازش --- async def translate_text(text: str, source: str, target: str) -> str: def _do_translate(): return GoogleTranslator(source=source, target=target).translate(text) try: return await asyncio.to_thread(_do_translate) except Exception as e: logging.error(f"Translation Error: {e}") return f"خطا در ترجمه: {str(e)}" async def generate_audio(text: str, voice_id: str, rate: int, volume: int, pitch: int) -> str: filename = f"{uuid.uuid4().hex}.mp3" filepath = os.path.join(AUDIO_DIR, filename) rate_str, volume_str, pitch_str = f"{int(rate):+g}%", f"{int(volume):+g}%", f"{int(pitch):+g}Hz" try: communicate = edge_tts.Communicate(text, voice_id, rate=rate_str, volume=volume_str, pitch=pitch_str) await communicate.save(filepath) return f"/audio_cache/{filename}" except Exception as e: logging.error(f"TTS Error: {e}") return "" # --- مدل‌های داده (برای دریافت اطلاعات از فرانت‌اند) --- class TranslateRequest(BaseModel): text: str source_lang: str target_lang: str voice_key: str rate: int = 0 volume: int = 0 pitch: int = 0 class GradioLegacyRequest(BaseModel): data: List[Any] # [text, voice, rate, volume, pitch] # --- Endpoints API وب‌سایت --- @app.get("/") async def serve_index(): return FileResponse("index.html") @app.get("/api/config") async def get_config(): return {"languages": LANGUAGES_MAP} @app.post("/api/translate_and_speak") async def api_translate_speak(req: TranslateRequest): src_code = "auto" if req.source_lang == "شناسایی خودکار" else LANGUAGES_MAP.get(req.source_lang, {}).get("code", "auto") target_lang_data = LANGUAGES_MAP.get(req.target_lang, {}) tgt_code = target_lang_data.get("code", "en") # پیدا کردن آیدی دقیق صدا voice_id = target_lang_data.get("voices", {}).get(req.voice_key) if not voice_id: voices = list(target_lang_data.get("voices", {}).values()) voice_id = voices[0] if voices else "en-US-JennyNeural" # 1. ترجمه translated_text = await translate_text(req.text, src_code, tgt_code) if "خطا در ترجمه" in translated_text: return {"success": False, "error": translated_text} # 2. تولید صدا (اگر زبانی صدا نداشته باشد، این بخش نادیده گرفته می‌شود) audio_url = "" if target_lang_data.get("voices"): audio_url = await generate_audio(translated_text, voice_id, req.rate, req.volume, req.pitch) return { "success": True, "translated_text": translated_text, "audio_url": audio_url } # --- شبیه‌ساز API گاردین برای ربات‌ها/اسکریپت‌های دوستان شما --- @app.post("/run/translate_and_speak") @app.post("/api/translate_and_speak_legacy") async def legacy_api(req: GradioLegacyRequest, request: Request): """ این بخش دقیقا ساختار Gradio را شبیه سازی می‌کند تا هیچ اسکریپتی از کار نیفتد. """ try: text, voice_key, rate, volume, pitch = req.data[0], req.data[1], req.data[2], req.data[3], req.data[4] # ترجمه به انگلیسی (رفتار قبلی سیستم) translated_text = await translate_text(text, "fa", "en") # پیدا کردن صدای انگلیسی قدیمی voice_id = LEGACY_VOICES.get(voice_key, "en-US-JennyNeural") audio_url = await generate_audio(translated_text, voice_id, rate, volume, pitch) # ساختن آدرس کامل فایل صوتی برای کلاینت‌ها base_url = str(request.base_url).rstrip("/") full_audio_url = f"{base_url}{audio_url}" if audio_url else None # خروجی دقیقاً با ساختار پاسخ Gradio return JSONResponse(content={ "data": [ translated_text, {"name": full_audio_url, "data": full_audio_url, "is_file": True} if full_audio_url else None ] }) except Exception as e: return JSONResponse(content={"error": str(e)}, status_code=500)