Aiocr_Radar / app.py
Asem75's picture
Update app.py
2b0cbed verified
Raw
History Blame Contribute Delete
29.4 kB
import os
import re
import json
import uuid
import gc
import asyncio
import threading
import signal
import numpy as np
from scipy.io import wavfile
from pydub import AudioSegment
import gradio as gr
import edge_tts
from huggingface_hub import hf_hub_download, HfApi
# ----------------- ุญู„ ู…ุดูƒู„ุฉ ุฃู…ุงู† PyTorch ูˆุงู„ู€ Checkpoint (ุทุฑูŠู‚ุฉ Monkey-patch) -----------------
import torch
_orig_torch_load = torch.load
def _patched_torch_load(*args, **kwargs):
kwargs.setdefault("weights_only", False)
return _orig_torch_load(*args, **kwargs)
torch.load = _patched_torch_load
# ุงู„ู…ูˆุงูู‚ุฉ ุงู„ุชู„ู‚ุงุฆูŠุฉ ุนู„ู‰ ุดุฑูˆุท ุงุณุชุฎุฏุงู… Coqui ู„ุชูุงุฏูŠ ุชูˆู‚ู ุงู„ุณูŠุฑูุฑ
os.environ['COQUI_TOS_AGREED'] = '1'
from TTS.api import TTS
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ุฅุนุฏุงุฏุงุช ุงู„ุจูŠุฆุฉ ูˆุงู„ุฎุฒู†ุฉ
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
HF_TOKEN = os.environ.get("HF_TOKEN", "").strip()
# ๐Ÿ” ู…ูุชุงุญ ุณุฑูŠ ู…ุดุชุฑูƒ ู„ุญู…ุงูŠุฉ ู†ู‚ุทุฉ API ุงู„ุฎุงุฑุฌูŠุฉ ูู‚ุท (ุงู„ูˆุงุฌู‡ุฉ ุงู„ุนุงู…ุฉ ุชุจู‚ู‰
# ุนุงู…ุฉ ุจุงู„ูƒุงู…ู„ ู„ุชุณุฌูŠู„ ุฏุฎูˆู„ VIP ุงู„ุทุจูŠุนูŠ). ูŠูุถุจุท ููŠ:
# Settings โ†’ Variables and secrets โ†’ INTERNAL_API_KEY
# ูˆูŠุฌุจ ุถุจุท ู†ูุณ ุงู„ู‚ูŠู…ุฉ ุจุงู„ุถุจุท ููŠ ู…ุณุงุญุฉ "ุงู„ุฃุณุชุงุฐ ุนุจูˆุฏ" (teacher_app) ุฃูŠุถุงู‹.
INTERNAL_API_KEY = os.environ.get("INTERNAL_API_KEY", "").strip()
VOICES_DIR = "./platform_voices"
AUDIO_FOLDER = "./audio" # ุงู„ู…ุฌู„ุฏ ุงู„ุฎุงุต ุจุงู„ุฃุตูˆุงุช ุงู„ุนุฑุจูŠุฉ ุงู„ู…ุฑุฌุนูŠุฉ ุงู„ุซุงุจุชุฉ (ู„ู„ุงุณุชู†ุณุงุฎ)
DATASET_REPO = "Asem75/aiocr_asistant"
USER_REGISTRY_PATH = "system/user_registry.json"
def ensure_dir(path: str):
"""ุชู†ุดุฆ ุงู„ู…ุฌู„ุฏุŒ ูˆุฅุฐุง ูƒุงู† ู…ูˆุฌูˆุฏุงู‹ ูƒู…ู„ู ุนุงุฏูŠ (ูˆู„ูŠุณ ู…ุฌู„ุฏุงู‹) ุชุญุฐูู‡ ุฃูˆู„ุงู‹
ุซู… ุชู†ุดุฆ ุงู„ู…ุฌู„ุฏ ุจุฃู…ุงู†."""
if os.path.exists(path) and not os.path.isdir(path):
os.remove(path)
os.makedirs(path, exist_ok=True)
ensure_dir(VOICES_DIR)
ensure_dir(AUDIO_FOLDER)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 1) ุจูˆุงุจุฉ ุงู„ุญู…ุงูŠุฉ ุงู„ุตุงุฑู…ุฉ (VIP Only Gate)
#
# ุงู„ุดูƒู„ ุงู„ุญู‚ูŠู‚ูŠ ุงู„ู…ุคูƒูŽู‘ุฏ ู„ู…ู„ู "system/user_registry.json" (ู…ู† ุงู„ุฎุฒู†ุฉ):
# {
# "Divid": {
# "email": "eduassistant75@gmail.com",
# "device_token": "SECURE-FPR-23726480",
# "rank": "free",
# "registered_at": "2026-06-20 13:51:58"
# },
# "ุงุณู…_ู…ุณุชุฎุฏู…_ุขุฎุฑ": { ... }
# }
# ุงู„ู…ูุชุงุญ ุงู„ุฎุงุฑุฌูŠ ู„ูƒู„ ุนู†ุตุฑ ู‡ูˆ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ู†ูุณู‡ (user_name) โ€” ูˆู„ูŠุณ ุญู‚ู„ุงู‹
# ุฏุงุฎู„ูŠุงู‹. ุงู„ู‚ูŠู…ุฉ ุงู„ุฏุงุฎู„ูŠุฉ ู„ุง ุชุญุชูˆูŠ ุนู„ู‰ user_name ุฅุทู„ุงู‚ุงู‹.
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def load_user_registry():
"""ูŠู‚ุฑุฃ ุณุฌู„ ุงู„ู…ุณุชุฎุฏู…ูŠู† ู…ู† ุงู„ุฎุฒู†ุฉ ูƒู‚ุงู…ูˆุณ {user_name: {email, device_token,
rank, registered_at}}. ุนู†ุฏ ุฃูŠ ูุดู„ ูŠูุฑุฌุน ู‚ุงู…ูˆุณุงู‹ ูุงุฑุบุงู‹ โ†’ ูุดู„ ุขู…ู†
(Fail-Closed): ู„ุง ุฃุญุฏ ูŠุฏุฎู„ ุจุฏู„ ุฃู† ูŠุฏุฎู„ ุงู„ุฌู…ูŠุน ุจุงู„ุฎุทุฃ."""
try:
path = hf_hub_download(
repo_id=DATASET_REPO,
filename=USER_REGISTRY_PATH,
repo_type="dataset",
token=HF_TOKEN or None,
force_download=True, # ู„ุถู…ุงู† ุณุญุจ ุขุฎุฑ ู†ุณุฎุฉ ู…ู† ุงู„ุณุฌู„ ู…ุน ูƒู„ ู…ุญุงูˆู„ุฉ ุฏุฎูˆู„
)
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, dict):
return data
return {}
except Exception as e:
print(f"โš ๏ธ ูุดู„ ุชุญู…ูŠู„ ุณุฌู„ ุงู„ู…ุณุชุฎุฏู…ูŠู† (ุฑูุถ ุขู…ู† ู„ูƒู„ ุงู„ุฏุฎูˆู„): {e}")
return {}
def authenticate_user(email: str, device_token: str):
"""ูŠุทุงุจู‚ ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ูˆุจุตู…ุฉ ุงู„ุฌู‡ุงุฒ ุนุจุฑ ูƒู„ ุนู†ุงุตุฑ ุงู„ุณุฌู„. ูŠูุฑุฌุน
ู‚ุงู…ูˆุณุงู‹ ูŠุญุชูˆูŠ user_name (ุงู„ู…ูุชุงุญ ุงู„ุฎุงุฑุฌูŠ) + ุจุงู‚ูŠ ุจูŠุงู†ุงุช ุงู„ู…ุณุชุฎุฏู…ุŒ ูู‚ุท
ุฅุฐุง ูƒุงู†ุช ุงู„ุฑุชุจุฉ 'vip' ุจุงู„ุถุจุท. ุฃูŠ ุญุงู„ุฉ ุฃุฎุฑู‰ (ุบูŠุฑ ู…ุณุฌู„ุŒ ุฑุชุจุฉ 'free'ุŒ
ุชุทุงุจู‚ ุฌุฒุฆูŠ) ุชูุฑุฌุน None โ†’ ุญุธุฑ ูƒุงู…ู„."""
email_n = (email or "").strip().lower()
token_n = (device_token or "").strip()
if not email_n or not token_n:
return None
registry = load_user_registry()
for user_name, info in registry.items():
if (info.get("email", "").strip().lower() == email_n
and info.get("device_token", "").strip() == token_n):
if info.get("rank") == "vip":
return {"user_name": user_name, **info}
return None # ู…ูˆุฌูˆุฏ ู„ูƒู† ุฑุชุจุชู‡ ู„ูŠุณุช vip โ†’ ุญุธุฑ
return None # ุบูŠุฑ ู…ุณุฌู„ ุฃุตู„ุงู‹ โ†’ ุญุธุฑ
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 2) ุงู„ู…ู„ูุงุช ุงู„ู†ุตูŠุฉ ุงู„ู…ุฎุตุตุฉ ู„ูƒู„ ุนู…ูŠู„ + ุญูุธ ุงู„ุฃุตูˆุงุช ุจุงุณู…ู‡
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def list_user_text_files(user_name: str):
"""ูŠุณุฑุฏ ูู‚ุท ุงู„ู…ู„ูุงุช ุงู„ู…ูˆุฌูˆุฏุฉ ุฏุงุฎู„ Final_Arabic_Files/{user_name}/ ููŠ
ุงู„ุฎุฒู†ุฉุŒ ุจุญูŠุซ ู„ุง ูŠุฑู‰ ุฃูŠ ุนู…ูŠู„ ุฅู„ุง ู…ู„ูุงุชู‡ ุงู„ุฎุงุตุฉ."""
if not user_name:
return []
try:
api = HfApi()
all_files = api.list_repo_files(repo_id=DATASET_REPO, repo_type="dataset", token=HF_TOKEN or None)
prefix = f"Final_Arabic_Files/{user_name}/"
return [f for f in all_files if f.startswith(prefix) and not f.endswith("/")]
except Exception as e:
print(f"โš ๏ธ ูุดู„ ุณุฑุฏ ู…ู„ูุงุช ุงู„ู…ุณุชุฎุฏู… {user_name}: {e}")
return []
def load_user_text_file(file_path: str) -> str:
if not file_path:
return ""
try:
local_path = hf_hub_download(repo_id=DATASET_REPO, filename=file_path, repo_type="dataset", token=HF_TOKEN or None)
with open(local_path, "r", encoding="utf-8") as f:
return f.read()
except Exception as e:
return f"โš ๏ธ ุชุนุฐุฑ ุชุญู…ูŠู„ ุงู„ู…ู„ู: {e}"
def upload_audio_to_vault(local_path: str, user_name: str = None):
"""ูŠุฑูุน ุงู„ู…ู„ู ุงู„ุตูˆุชูŠ ู„ู„ุฎุฒู†ุฉ. ุฅุฐุง ุชู… ุชู…ุฑูŠุฑ user_name ูŠูุญูุธ ุชุญุช
voices/{user_name}/{filename}ุŒ ูˆุฅู„ุง ูŠูุญูุธ ููŠ voices/{filename} (ู„ู„ุญุงู„ุงุช
ุงู„ุนุงู…ุฉ/ุงู„ุงุฎุชุจุงุฑูŠุฉ ุจุฏูˆู† ู…ุณุชุฎุฏู… ู…ุญุฏุฏ)."""
if not HF_TOKEN:
return " (โš ๏ธ ู„ุง ูŠูˆุฌุฏ ุฑู…ุฒ HF_TOKEN)"
try:
api = HfApi()
filename = os.path.basename(local_path)
path_in_repo = f"voices/{user_name}/{filename}" if user_name else f"voices/{filename}"
api.upload_file(
path_or_fileobj=local_path,
path_in_repo=path_in_repo,
repo_id=DATASET_REPO,
repo_type="dataset",
token=HF_TOKEN,
)
return f" (๐Ÿ’พ ุชู… ุงู„ุญูุธ ููŠ: {path_in_repo})"
except Exception as e:
return f" (โš ๏ธ ูุดู„ ุงู„ู…ุฒุงู…ู†ุฉ: {e})"
# ุฌู„ุจ ู‚ุงุฆู…ุฉ ุงู„ู…ู„ูุงุช ุงู„ุตูˆุชูŠุฉ ุงู„ู…ุฑุฌุนูŠุฉ ุงู„ุซุงุจุชุฉ (ู„ุงุณุชู†ุณุงุฎ XTTS) ู…ู† ู…ุฌู„ุฏ audio ุงู„ู…ุญู„ูŠ
def get_local_speaker_waves():
if not os.path.exists(AUDIO_FOLDER):
return []
files = [f for f in os.listdir(AUDIO_FOLDER) if f.endswith(('.wav', '.mp3'))]
return [(f.split('.')[0].replace('-', ' '), os.path.join(AUDIO_FOLDER, f)) for f in files]
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ุญู„ู‚ุฉ Edge-TTS ุงู„ุฏุงุฆู…ุฉ (ุฎูŠุท ูˆุงุญุฏ ุทูˆูŠู„ ุงู„ุนู…ุฑุŒ ุจุฏูˆู† ุฅู†ุดุงุก/ุฅุบู„ุงู‚ ู…ุชูƒุฑุฑ)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
_edge_loop = None
_edge_thread = None
def _start_edge_loop():
global _edge_loop, _edge_thread
if _edge_loop is not None:
return
_edge_loop = asyncio.new_event_loop()
def run_loop():
asyncio.set_event_loop(_edge_loop)
_edge_loop.run_forever()
_edge_thread = threading.Thread(target=run_loop, daemon=True)
_edge_thread.start()
def _stop_edge_loop():
global _edge_loop, _edge_thread
if _edge_loop is not None:
_edge_loop.call_soon_threadsafe(_edge_loop.stop)
_edge_thread.join(timeout=5)
_edge_loop.close()
_edge_loop = None
_edge_thread = None
def _shutdown_handler(signum, frame):
_stop_edge_loop()
exit(0)
signal.signal(signal.SIGINT, _shutdown_handler)
signal.signal(signal.SIGTERM, _shutdown_handler)
def get_available_voices():
_start_edge_loop()
async def _fetch():
voices = await edge_tts.list_voices()
arabic, english = [], []
for v in voices:
short_name = v["ShortName"]
if "Neural" not in short_name:
continue
label = f"{v['Locale']} - {short_name} ({v['Gender']})"
if v["Locale"].startswith("ar"):
arabic.append((label, short_name))
elif v["Locale"].startswith("en"):
english.append((label, short_name))
arabic.sort(key=lambda x: x[0])
english.sort(key=lambda x: x[0])
return arabic, english
future = asyncio.run_coroutine_threadsafe(_fetch(), _edge_loop)
return future.result(timeout=30)
print("โณ ุฌุงุฑูŠ ุชุญู…ูŠู„ ู‚ุงุฆู…ุฉ ุฃุตูˆุงุช Edgeโ€‘TTS...")
try:
ARABIC_EDGE_VOICES, ENGLISH_EDGE_VOICES = get_available_voices()
except Exception:
ARABIC_EDGE_VOICES = [("ุงู„ุนุฑุจูŠุฉ - ุตูˆุช ุงูุชุฑุงุถูŠ", "ar-JO-TaimNeural")]
ENGLISH_EDGE_VOICES = [("English - Default", "en-US-JennyNeural")]
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ู…ุญุฑูƒุงุช ุงู„ุชูˆู„ูŠุฏ ุงู„ุตูˆุชูŠ (ูƒู„ ุฏุงู„ุฉ ุชุฃุฎุฐ ุฌู…ู„ุฉ ูˆุงุญุฏุฉ ู‚ุตูŠุฑุฉ ูˆุชูุฑุฌุน ู…ุณุงุฑ ู…ู„ู)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def generate_edge_audio(text: str, voice: str) -> str:
filepath = os.path.join(VOICES_DIR, f"edge_{uuid.uuid4().hex}.mp3")
async def _task():
communicate = edge_tts.Communicate(text.strip(), voice)
await communicate.save(filepath)
_start_edge_loop()
future = asyncio.run_coroutine_threadsafe(_task(), _edge_loop)
future.result(timeout=120)
return filepath
_piper_voice = None
_piper_lock = threading.Lock()
def load_piper_lazy():
global _piper_voice
if _piper_voice is not None:
return _piper_voice
with _piper_lock:
if _piper_voice is not None:
return _piper_voice
from piper import PiperVoice
model_path = hf_hub_download(repo_id=DATASET_REPO, filename="models/ar_JO-kareem-low.onnx", repo_type="dataset", token=HF_TOKEN or None)
config_path = hf_hub_download(repo_id=DATASET_REPO, filename="models/ar_JO-kareem-low.onnx.json", repo_type="dataset", token=HF_TOKEN or None)
_piper_voice = PiperVoice.load(model_path, config_path)
return _piper_voice
def generate_piper_audio(text: str) -> str:
voice = load_piper_lazy()
filepath = os.path.join(VOICES_DIR, f"piper_{uuid.uuid4().hex}.wav")
try:
gc.collect()
pcm_chunks = [chunk.audio_int16_bytes for chunk in voice.synthesize(text.strip())]
wavfile.write(filepath, 22050, np.frombuffer(b"".join(pcm_chunks), dtype=np.int16))
return filepath
except Exception as e:
if os.path.exists(filepath): os.remove(filepath)
raise RuntimeError(f"Piper Error: {e}")
_xtts_model = None
_xtts_lock = threading.Lock()
def load_xtts_lazy():
global _xtts_model
if _xtts_model is not None:
return _xtts_model
with _xtts_lock:
if _xtts_model is not None:
return _xtts_model
print("โณ ุฌุงุฑูŠ ุชุญู…ูŠู„ ู†ู…ูˆุฐุฌ ุงู„ุงุณุชู†ุณุงุฎ XTTS-v2...")
device = "cuda" if torch.cuda.is_available() else "cpu"
_xtts_model = TTS("tts_models/multilingual/multi-dataset/xtts_v2", gpu=torch.cuda.is_available()).to(device)
print("โœ… ุชู… ุชุญู…ูŠู„ ู…ุญุฑูƒ XTTS-v2 ุจู†ุฌุงุญ.")
return _xtts_model
def generate_xtts_audio(text: str, speaker_audio_path: str) -> str:
if not speaker_audio_path or not os.path.exists(speaker_audio_path):
raise ValueError("ูŠุฌุจ ุชุญุฏูŠุฏ ุฃูˆ ุฑูุน ู…ู„ู ุตูˆุชูŠ ู…ุฑุฌุนูŠ ู„ู„ุงุณุชู†ุณุงุฎ.")
tts = load_xtts_lazy()
filepath = os.path.join(VOICES_DIR, f"xtts_{uuid.uuid4().hex}.wav")
try:
gc.collect()
tts.tts_to_file(text=text.strip(), speaker_wav=speaker_audio_path, language="ar", file_path=filepath)
return filepath
except Exception as e:
if os.path.exists(filepath): os.remove(filepath)
raise RuntimeError(f"XTTS Error: {e}")
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 4) ุชู‚ุทูŠุน ุงู„ู†ุตูˆุต ุงู„ุทูˆูŠู„ุฉ ูˆุฏู…ุฌ ุงู„ู…ู‚ุงุทุน ุงู„ุตูˆุชูŠุฉ
#
# ุงู„ุชู‚ุทูŠุน ุงู„ุฃุณุงุณูŠ ุนู„ู‰ ุนู„ุงู…ุงุช ุฅู†ู‡ุงุก ุงู„ุฌู…ู„ุฉ (. ! ุŸ). ุฅุฐุง ุจู‚ูŠุช ุฌู…ู„ุฉ ูˆุงุญุฏุฉ
# ุทูˆูŠู„ุฉ ุฌุฏุงู‹ ุจุนุฏ ุฐู„ูƒ (ุฃุทูˆู„ ู…ู† MAX_CHUNK_CHARS)ุŒ ู†ู‚ุณู‘ู…ู‡ุง ุฅุถุงููŠุงู‹ ุนู„ู‰
# ุงู„ูุงุตู„ุฉ (ุŒ ,) ูู‚ุท ูƒุญู…ุงูŠุฉ ุฅุถุงููŠุฉ โ€” ู„ุชุฌู†ู‘ุจ ุชูุชูŠุช ูƒู„ ุฌู…ู„ุฉ ู‚ุตูŠุฑุฉ ุนุงุฏูŠุฉ ุฅู„ู‰
# ุฃุฌุฒุงุก ุตุบูŠุฑุฉ ุฌุฏุงู‹ ุจู„ุง ุฏุงุนู.
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
MAX_CHUNK_CHARS = 300
_SENTENCE_END_RE = re.compile(r'(?<=[.!ุŸ])\s+')
_SOFT_SPLIT_RE = re.compile(r'(?<=[ุŒ,])\s+')
def split_text_into_chunks(text: str):
text = (text or "").strip()
if not text:
return []
sentences = [s.strip() for s in _SENTENCE_END_RE.split(text) if s.strip()]
if not sentences:
sentences = [text]
chunks = []
for s in sentences:
if len(s) <= MAX_CHUNK_CHARS:
chunks.append(s)
continue
# ุฌู…ู„ุฉ ุทูˆูŠู„ุฉ ุฌุฏุงู‹: ุชู‚ุทูŠุน ุฅุถุงููŠ ุนู„ู‰ ุงู„ููˆุงุตู„ ู„ุญู…ุงูŠุฉ ุงู„ู…ุญุฑูƒ ุงู„ุตูˆุชูŠ
buf = ""
for part in [p.strip() for p in _SOFT_SPLIT_RE.split(s) if p.strip()]:
if len(buf) + len(part) + 1 <= MAX_CHUNK_CHARS:
buf = f"{buf} {part}".strip()
else:
if buf:
chunks.append(buf)
buf = part
if buf:
chunks.append(buf)
return chunks
def synthesize_long_text(text: str, synth_fn, *synth_args) -> str:
"""ูŠู‚ุทู‘ุน ุงู„ู†ุตุŒ ูŠูˆู„ู‘ุฏ ูƒู„ ุฌุฒุก ุนุจุฑ synth_fnุŒ ุซู… ูŠุฏู…ุฌ ูƒู„ ุงู„ู…ู‚ุงุทุน ููŠ ู…ู„ู WAV
ูˆุงุญุฏ ู†ู‡ุงุฆูŠ ุนุจุฑ pydub. ุฅุฐุง ูƒุงู† ุงู„ู†ุต ู‚ุตูŠุฑุงู‹ (ุฌู…ู„ุฉ ูˆุงุญุฏุฉ) ูŠุนู…ู„ ู…ุจุงุดุฑุฉ
ุจุฏูˆู† ุฃูŠ ุชู‚ุทูŠุน ู„ุชูˆููŠุฑ ุงู„ูˆู‚ุช."""
chunks = split_text_into_chunks(text)
if not chunks:
raise ValueError("ุงู„ู†ุต ูุงุฑุบ ุจุนุฏ ุงู„ู…ุนุงู„ุฌุฉ.")
if len(chunks) == 1:
return synth_fn(chunks[0], *synth_args)
combined = AudioSegment.empty()
temp_files = []
try:
for chunk in chunks:
part_path = synth_fn(chunk, *synth_args)
temp_files.append(part_path)
combined += AudioSegment.from_file(part_path)
combined += AudioSegment.silent(duration=180) # ูุงุตู„ ุทุจูŠุนูŠ ู‚ุตูŠุฑ ุจูŠู† ุงู„ุฌู…ู„
final_path = os.path.join(VOICES_DIR, f"merged_{uuid.uuid4().hex}.wav")
combined.export(final_path, format="wav")
return final_path
finally:
for p in temp_files:
if os.path.exists(p):
try:
os.remove(p)
except Exception:
pass
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ุฏุงู„ุฉ ุงู„ุชูˆู„ูŠุฏ ุงู„ุฑุฆูŠุณูŠุฉ (ูˆุงุฌู‡ุฉ ุงู„ู…ุณุชุฎุฏู… ุงู„ุฏุงุฎู„ูŠุฉุŒ ุจุนุฏ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def synth_arabic(text, engine, edge_voice, xtts_mode, local_speaker, uploaded_speaker, user_name):
if not text or not text.strip():
return None, "โš ๏ธ ุงู„ุฑุฌุงุก ูƒุชุงุจุฉ ู†ุต ุฃูˆู„ุงู‹"
try:
if engine == "ู…ุญุฑูƒ Coqui XTTS-v2 (ุงุณุชู†ุณุงุฎ ุงู„ุฃุตูˆุงุช ุงู„ุนุฑุจูŠุฉ)":
chosen_speaker = local_speaker if xtts_mode == "ุงุฎุชุฑ ู…ู† ุงู„ุฃุตูˆุงุช ุงู„ุนุฑุจูŠุฉ ุงู„ู…ุฑููˆุนุฉ ููŠ ุงู„ู…ุณุชูˆุฏุน" else uploaded_speaker
path = synthesize_long_text(text, generate_xtts_audio, chosen_speaker)
elif engine == "ู…ุญุฑูƒ Piper ุงู„ู…ุญู„ูŠ (ุจุตูˆุช ูƒุฑูŠู…)":
path = synthesize_long_text(text, generate_piper_audio)
else:
path = synthesize_long_text(text, generate_edge_audio, edge_voice)
msg = upload_audio_to_vault(path, user_name)
return path, f"โœ… ุชู… ุงู„ุชูˆู„ูŠุฏ ุจู†ุฌุงุญ!{msg}"
except Exception as e:
return None, f"โŒ {e}"
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# 3) ู†ู‚ุทุฉ API ุงู„ุฎุงุฑุฌูŠุฉ (Webhook) ู„ู…ุณุงุญุฉ ุงู„ู…ุญุงุฏุซุฉ ุงู„ุฃุฎุฑู‰
#
# ๐Ÿ” ู…ุญู…ูŠุฉ ุงู„ุขู† ุจู…ูุชุงุญ ุณุฑูŠ (api_key) ูŠูู‚ุงุฑูŽู† ุจู…ุชุบูŠู‘ุฑ ุงู„ุจูŠุฆุฉ
# INTERNAL_API_KEY. ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฑุณูˆู…ูŠุฉ (ุชุณุฌูŠู„ ุฏุฎูˆู„ VIP) ุชุจู‚ู‰ ุนุงู…ุฉ ุจุงู„ูƒุงู…ู„
# ูˆู„ุง ุชุชุฃุซุฑ ุจู‡ุฐุง โ€” ุงู„ุญู…ุงูŠุฉ ู‡ู†ุง ุฎุงุตุฉ ุจู†ู‚ุทุฉ ุงู„ู€ API ูู‚ุท.
# ูุดู„ ุขู…ู† (Fail-Closed): ุฅุฐุง ู„ู… ูŠูุถุจุท INTERNAL_API_KEY ููŠ ุฅุนุฏุงุฏุงุช ู‡ุฐู‡
# ุงู„ู…ุณุงุญุฉุŒ ุชูุฑูุถ ูƒู„ ุงู„ุงุณุชุฏุนุงุกุงุช ุชู„ู‚ุงุฆูŠุงู‹ ุจุฏู„ ุฃู† ุชุจู‚ู‰ ู…ูุชูˆุญุฉ ุจุงู„ุฎุทุฃ.
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def synth_arabic_api(text: str, speaker_name: str, api_key: str):
if not INTERNAL_API_KEY or (api_key or "").strip() != INTERNAL_API_KEY:
raise gr.Error("๐Ÿšซ ูˆุตูˆู„ ู…ุฑููˆุถ: ู…ูุชุงุญ API ุบูŠุฑ ุตุญูŠุญ ุฃูˆ ุบูŠุฑ ู…ููุนูŽู‘ู„ ููŠ ู‡ุฐู‡ ุงู„ู…ุณุงุญุฉ.")
if not text or not text.strip():
raise gr.Error("ุงู„ู†ุต ุงู„ู…ูุฑุณู„ ูุงุฑุบ.")
speaker_name = (speaker_name or "").strip()
if speaker_name.startswith("ar-"):
return synthesize_long_text(text, generate_edge_audio, speaker_name)
speaker_path = os.path.join(AUDIO_FOLDER, speaker_name)
if not os.path.exists(speaker_path):
raise gr.Error(f"ู„ู… ูŠุชู… ุงู„ุนุซูˆุฑ ุนู„ู‰ ุงู„ุตูˆุช ุงู„ู…ุฑุฌุนูŠ: {speaker_name}")
return synthesize_long_text(text, generate_xtts_audio, speaker_path)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ูˆุงุฌู‡ุฉ Gradio
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
LOCAL_SPEAKERS = get_local_speaker_waves()
with gr.Blocks(title="ู…ู†ุตุฉ ุงู„ุฃุณุชุงุฐ ุนุจูˆุฏ") as demo:
gr.Markdown("# ๐ŸŽ™๏ธ ู…ู†ุตุฉ ุงู„ุฃุณุชุงุฐ ุนุจูˆุฏ - ู†ุธุงู… ุงู„ุชูˆู„ูŠุฏ ุงู„ุตูˆุชูŠ ุงู„ุงุญุชุฑุงููŠ")
user_name_state = gr.State("")
# ---------------- ุดุงุดุฉ ุงู„ุญุฑุงุณุฉ (VIP Login Gate) ----------------
with gr.Column(visible=True) as login_screen:
gr.Markdown("### ๐Ÿ”’ ุงู„ุฏุฎูˆู„ ู…ู‚ุตูˆุฑ ุนู„ู‰ ุฃุนุถุงุก VIP ูู‚ุท")
email_input = gr.Textbox(label="ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ")
device_input = gr.Textbox(label="ุจุตู…ุฉ ุงู„ุฌู‡ุงุฒ (Device Token)")
login_btn = gr.Button("๐Ÿšช ุฏุฎูˆู„", variant="primary")
# ---------------- ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฑุฆูŠุณูŠุฉ (ู…ุฎููŠุฉ ุญุชู‰ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„) ----------------
with gr.Column(visible=False) as main_app:
welcome_md = gr.Markdown("")
with gr.Tabs():
with gr.Tab("ุงู„ุนุฑุจูŠุฉ"):
with gr.Row():
with gr.Column(scale=3):
text_ar = gr.Textbox(label="โœ๏ธ ุงู„ู†ุต ุงู„ุนุฑุจูŠ ู„ุชูˆู„ูŠุฏู‡ ุตูˆุชุงู‹", lines=6, rtl=True)
with gr.Column(scale=1):
engine_ar = gr.Radio(
choices=["ู…ุญุฑูƒ ู…ุงูŠูƒุฑูˆุณูˆูุช ุงู„ุณุญุงุจูŠ (Edge-TTS)", "ู…ุญุฑูƒ Piper ุงู„ู…ุญู„ูŠ (ุจุตูˆุช ูƒุฑูŠู…)", "ู…ุญุฑูƒ Coqui XTTS-v2 (ุงุณุชู†ุณุงุฎ ุงู„ุฃุตูˆุงุช ุงู„ุนุฑุจูŠุฉ)"],
label="โš™๏ธ ุงุฎุชุฑ ุงู„ู…ุญุฑูƒ ุงู„ุตูˆุชูŠ", value="ู…ุญุฑูƒ ู…ุงูŠูƒุฑูˆุณูˆูุช ุงู„ุณุญุงุจูŠ (Edge-TTS)"
)
voice_ar = gr.Dropdown(choices=ARABIC_EDGE_VOICES, label="๐ŸŽ™๏ธ ุงู„ุตูˆุช ุงู„ุนุฑุจูŠ (Edge)", value=ARABIC_EDGE_VOICES[0][1] if ARABIC_EDGE_VOICES else None)
xtts_mode_ar = gr.Radio(choices=["ุงุฎุชุฑ ู…ู† ุงู„ุฃุตูˆุงุช ุงู„ุนุฑุจูŠุฉ ุงู„ู…ุฑููˆุนุฉ ููŠ ุงู„ู…ุณุชูˆุฏุน", "ุฑูุน ู…ู„ู ุตูˆุชูŠ ุฌุฏูŠุฏ (ู…ุงูŠูƒุฑูˆููˆู†)"], label="๐ŸŽ›๏ธ ู…ุตุฏุฑ ุตูˆุช ุงู„ุงุณุชู†ุณุงุฎ", value="ุฑูุน ู…ู„ู ุตูˆุชูŠ ุฌุฏูŠุฏ (ู…ุงูŠูƒุฑูˆููˆู†)", visible=False)
local_speaker_ar = gr.Dropdown(choices=LOCAL_SPEAKERS, label="๐Ÿ‘ค ุงู„ุฃุตูˆุงุช ุงู„ุนุฑุจูŠุฉ ุงู„ู…ุชุงุญุฉ ููŠ ุงู„ุณูŠุฑูุฑ", visible=False)
uploaded_speaker_ar = gr.Audio(label="๐ŸŽ™๏ธ ุงุฑูุน ู…ู„ู ุตูˆุชูŠ ู…ุฑุฌุนูŠ (6 ุซูˆุงู†ู)", type="filepath", visible=False)
btn_ar = gr.Button("๐Ÿš€ ุชูˆู„ูŠุฏ ุงู„ุตูˆุช ุงู„ุนุฑุจูŠ", variant="primary")
audio_ar = gr.Audio(label="ุงู„ู…ู„ู ุงู„ุตูˆุชูŠ ุงู„ู†ุงุชุฌ", type="filepath")
status_ar = gr.Textbox(label="ุญุงู„ุฉ ุงู„ุชูˆู„ูŠุฏ ูˆุงู„ุฑูุน ุณุญุงุจูŠุงู‹", interactive=False)
def update_engine_fields(engine):
is_edge = engine == "ู…ุญุฑูƒ ู…ุงูŠูƒุฑูˆุณูˆูุช ุงู„ุณุญุงุจูŠ (Edge-TTS)"
is_xtts = engine == "ู…ุญุฑูƒ Coqui XTTS-v2 (ุงุณุชู†ุณุงุฎ ุงู„ุฃุตูˆุงุช ุงู„ุนุฑุจูŠุฉ)"
return gr.update(visible=is_edge), gr.update(visible=is_xtts), gr.update(visible=False), gr.update(visible=False)
def update_xtts_source(mode):
is_local = mode == "ุงุฎุชุฑ ู…ู† ุงู„ุฃุตูˆุงุช ุงู„ุนุฑุจูŠุฉ ุงู„ู…ุฑููˆุนุฉ ููŠ ุงู„ู…ุณุชูˆุฏุน"
return gr.update(visible=is_local), gr.update(visible=not is_local)
engine_ar.change(fn=update_engine_fields, inputs=engine_ar, outputs=[voice_ar, xtts_mode_ar, local_speaker_ar, uploaded_speaker_ar], api_name=False)
xtts_mode_ar.change(fn=update_xtts_source, inputs=xtts_mode_ar, outputs=[local_speaker_ar, uploaded_speaker_ar], api_name=False)
btn_ar.click(
fn=synth_arabic,
inputs=[text_ar, engine_ar, voice_ar, xtts_mode_ar, local_speaker_ar, uploaded_speaker_ar, user_name_state],
outputs=[audio_ar, status_ar],
api_name=False, # โš ๏ธ ู‡ุงู…: ุจุฏูˆู† ู‡ุฐุงุŒ Gradio ูŠูุตุฏู‘ุฑู‡ุง ุชู„ู‚ุงุฆูŠุงู‹ ุจุงุณู… ุฏุงู„ุชู‡ุง "/synth_arabic" ูˆูŠุชุนุงุฑุถ ู…ุน ู†ู‚ุทุฉ ุงู„ู€ API ุงู„ู…ู‚ุตูˆุฏุฉ ุฃุฏู†ุงู‡
)
with gr.Tab("English"):
with gr.Row():
with gr.Column(scale=3):
text_en = gr.Textbox(label="โœ๏ธ English Text", lines=6)
with gr.Column(scale=1):
voice_en = gr.Dropdown(choices=ENGLISH_EDGE_VOICES, label="๐ŸŽ™๏ธ English Voice", value=ENGLISH_EDGE_VOICES[0][1] if ENGLISH_EDGE_VOICES else None)
btn_en = gr.Button("๐Ÿš€ Generate English Audio", variant="primary")
audio_en = gr.Audio(label="Output Audio", type="filepath")
status_en = gr.Textbox(label="Status", interactive=False)
btn_en.click(
fn=lambda t, v: (synthesize_long_text(t, generate_edge_audio, v), "โœ… Done"),
inputs=[text_en, voice_en], outputs=[audio_en, status_en],
api_name=False,
)
with gr.Tab("๐Ÿ“ ู…ู„ูุงุชูŠ"):
gr.Markdown("ุงู„ู…ู„ูุงุช ุงู„ู†ุตูŠุฉ ุงู„ู…ุฎุตุตุฉ ู„ูƒ ูู‚ุท ู…ู† ุงู„ุฎุฒู†ุฉ (Final_Arabic_Files/ุงุณู…ูƒ/).")
refresh_files_btn = gr.Button("๐Ÿ”„ ุชุญุฏูŠุซ ู‚ุงุฆู…ุฉ ู…ู„ูุงุชูŠ")
my_files_dropdown = gr.Dropdown(label="ุงุฎุชุฑ ู…ู„ูุงู‹ ู†ุตูŠุงู‹", choices=[])
load_file_btn = gr.Button("๐Ÿ“ฅ ุชุญู…ูŠู„ ู…ุญุชูˆู‰ ุงู„ู…ู„ู ุงู„ู…ุฎุชุงุฑ")
my_file_text = gr.Textbox(label="ู…ุญุชูˆู‰ ุงู„ู…ู„ู", lines=8, rtl=True)
use_in_generate_btn = gr.Button("โžก๏ธ ุงุณุชุฎุฏู… ู‡ุฐุง ุงู„ู†ุต ููŠ ุชุจูˆูŠุจ ุงู„ุนุฑุจูŠุฉ")
def refresh_my_files(user_name):
return gr.update(choices=list_user_text_files(user_name))
refresh_files_btn.click(fn=refresh_my_files, inputs=[user_name_state], outputs=[my_files_dropdown], api_name=False)
load_file_btn.click(fn=load_user_text_file, inputs=[my_files_dropdown], outputs=[my_file_text], api_name=False)
use_in_generate_btn.click(fn=lambda txt: txt, inputs=[my_file_text], outputs=[text_ar], api_name=False)
# ---------------- ุฑุจุท ุฒุฑ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ----------------
def handle_login(email, device_token):
user = authenticate_user(email, device_token)
if user is None:
raise gr.Error("๐Ÿšซ ุงู„ูˆุตูˆู„ ู…ุฑููˆุถ: ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุฃูˆ ุจุตู…ุฉ ุงู„ุฌู‡ุงุฒ ุบูŠุฑ ุตุญูŠุญุฉุŒ ุฃูˆ ุงู„ุญุณุงุจ ู„ูŠุณ VIP.")
resolved_name = user.get("user_name") or user.get("email", "guest")
files = list_user_text_files(resolved_name)
return (
gr.update(visible=False), # login_screen
gr.update(visible=True), # main_app
resolved_name, # user_name_state
f"### ู…ุฑุญุจุงู‹ ุจูƒุŒ {resolved_name} ๐Ÿ‘‹", # welcome_md
gr.update(choices=files), # my_files_dropdown
)
login_btn.click(
fn=handle_login,
inputs=[email_input, device_input],
outputs=[login_screen, main_app, user_name_state, welcome_md, my_files_dropdown],
api_name=False,
)
# ---------------- ู†ู‚ุทุฉ API ุฎููŠุฉ ู„ู…ุณุงุญุฉ ุงู„ู…ุญุงุฏุซุฉ ุงู„ุฎุงุฑุฌูŠุฉ ----------------
# ู…ูƒูˆู‘ู†ุงุช ุบูŠุฑ ุธุงู‡ุฑุฉ ููŠ ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฑุณูˆู…ูŠุฉุŒ ุชูุณุชุฎุฏูŽู… ูู‚ุท ุนุจุฑ ุงุณุชุฏุนุงุก API.
with gr.Row(visible=False):
api_text_in = gr.Textbox()
api_speaker_in = gr.Textbox()
api_key_in = gr.Textbox()
api_audio_out = gr.Audio(type="filepath")
api_trigger_btn = gr.Button()
api_trigger_btn.click(
fn=synth_arabic_api,
inputs=[api_text_in, api_speaker_in, api_key_in],
outputs=api_audio_out,
api_name="synth_arabic",
)
if __name__ == "__main__":
_start_edge_loop()
demo.queue(max_size=1).launch(server_name="0.0.0.0")