File size: 21,272 Bytes
ba4a16b 327b95d 7c3976d 327b95d 7c3976d bc0e481 7c3976d b5d553f 7c3976d bbf0e20 7c3976d f6f8a7e 7c3976d 59637c2 06d0b49 755f714 06d0b49 7c3976d 06d0b49 7c3976d 06d0b49 7c3976d 06d0b49 7c3976d 06d0b49 755f714 7c3976d 755f714 7c3976d 755f714 7c3976d d6e8044 7c3976d d6e8044 7c3976d 755f714 7c3976d d6e8044 7c3976d d6e8044 7c3976d 755f714 7c3976d 755f714 7c3976d 59637c2 7c3976d 59637c2 06d0b49 59637c2 7c3976d 755f714 7c3976d 755f714 7c3976d 59637c2 7c3976d 755f714 7c3976d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | 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) |