File size: 34,407 Bytes
e4d0150 d29f80b 3b52ce7 3012bdb 3b52ce7 3012bdb d29f80b 150c009 d29f80b 3012bdb d29f80b e4d0150 1799f89 d29f80b 3b52ce7 d29f80b 3b52ce7 d29f80b e4d0150 d29f80b eb51a4d 3b52ce7 eb51a4d 6fd3544 3012bdb d29f80b a17f9d2 3012bdb e4d0150 674a927 3012bdb e4d0150 3012bdb e4d0150 2757c04 1799f89 ec42d41 674a927 b62be89 2757c04 674a927 ec42d41 eb51a4d d29f80b a17f9d2 674a927 d29f80b 674a927 ec42d41 3012bdb eb51a4d d29f80b 1799f89 eb51a4d e4d0150 47095d4 d29f80b eb51a4d d29f80b eb51a4d 6fd3544 3012bdb a17f9d2 e4bf7cb 3012bdb 3b52ce7 d29f80b 3b52ce7 1799f89 3012bdb 1799f89 674a927 3b52ce7 d29f80b 150c009 d29f80b 6fd3544 d29f80b 150c009 47095d4 3012bdb 3b52ce7 d29f80b 3b52ce7 eb51a4d d29f80b b40ae8b 3012bdb d29f80b 3b52ce7 d29f80b 3b52ce7 150c009 d29f80b 3012bdb 150c009 d29f80b 6fd3544 3b52ce7 3012bdb 6fd3544 3012bdb 3b52ce7 3012bdb 6fd3544 3012bdb 3b52ce7 e4d0150 d29f80b 3012bdb d29f80b 3012bdb d29f80b 150c009 3012bdb d29f80b 3012bdb d29f80b 3012bdb d29f80b 3012bdb 6fd3544 d29f80b 150c009 3b52ce7 3012bdb 3b52ce7 3012bdb d29f80b 3012bdb 3b52ce7 a17f9d2 3b52ce7 d29f80b 3012bdb 3b52ce7 d29f80b 6fd3544 3012bdb d29f80b 3012bdb 6fd3544 3b52ce7 d29f80b 6fd3544 d29f80b 3b52ce7 d29f80b 3b52ce7 150c009 d29f80b 6fd3544 3012bdb 078eb75 3b52ce7 3012bdb 3b52ce7 d29f80b 6fd3544 3012bdb 6fd3544 3012bdb d29f80b 3012bdb d29f80b 6fd3544 674a927 e4d0150 d29f80b e4d0150 d29f80b 6fd3544 d29f80b 6fd3544 d29f80b 3012bdb 674a927 d29f80b 3012bdb 6fd3544 d29f80b 3b52ce7 3012bdb d29f80b 3b52ce7 6fd3544 3b52ce7 6fd3544 3b52ce7 d29f80b 3b52ce7 d29f80b 3b52ce7 d29f80b 3b52ce7 290123f 3b52ce7 d29f80b 3012bdb 3b52ce7 d29f80b b40ae8b d29f80b 3012bdb d29f80b 6fd3544 d29f80b 3012bdb d29f80b 6fd3544 d29f80b 6fd3544 d29f80b 3012bdb d29f80b 3012bdb d29f80b 6fd3544 d29f80b 3012bdb d29f80b 47095d4 d29f80b 6fd3544 d29f80b b40ae8b 3012bdb d29f80b 3012bdb d29f80b 290123f d29f80b 3b52ce7 d29f80b 6fd3544 d29f80b 3012bdb d29f80b 3b52ce7 6fd3544 d29f80b 6fd3544 d29f80b 6fd3544 d29f80b e4d0150 18059a6 d29f80b 3b52ce7 d29f80b 6fd3544 3b52ce7 |
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 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 |
import gradio as gr
# import base64 # Not used in your original core logic
import mimetypes
import os
import re
import struct
import time
# import zipfile # Not used in your original core logic
from google import genai
from google.genai import types as genai_types # Aliased to avoid conflict with built-in 'types'
import logging # Standard Python logging
try:
from pydub import AudioSegment
PYDUB_AVAILABLE = True
except ImportError:
PYDUB_AVAILABLE = False
# logging.warning("Pydub is not available. Audio merging will be disabled.") # Initialized later
# --- Basic Logging Setup ---
# Using a simpler logging setup if the AlphaTranslator_Styled one is too complex for "no other changes"
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# --- START: Core TTS Logic from YOUR AlphaTTS_Original (UNCHANGED) ---
SPEAKER_VOICES = [
"Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager",
"Sulafat", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux",
"Pulcherrima", "Umbriel", "Algieba", "Despina", "Erinome", "Algenib",
"Rasalthgeti", "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus",
"Iapetus", "Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda"
]
FIXED_MODEL_NAME = "gemini-2.5-flash-preview-tts" # FROM YOUR ORIGINAL CODE
DEFAULT_MAX_CHUNK_SIZE = 3800
DEFAULT_SLEEP_BETWEEN_REQUESTS = 8
DEFAULT_OUTPUT_FILENAME_BASE = "alpha_tts_audio"
def _log(message, log_list): # YOUR _log function
log_list.append(message)
logging.info(f"[AlphaTTS_LOG] {message}") # Standard logging also
def save_binary_file(file_name, data, log_list):
try:
with open(file_name, "wb") as f: f.write(data)
_log(f"✅ فایل ذخیره شد: {file_name}", log_list)
return file_name
except Exception as e:
_log(f"❌ خطا در ذخیره فایل {file_name}: {e}", log_list)
return None
def convert_to_wav(audio_data: bytes, mime_type: str) -> bytes:
parameters = parse_audio_mime_type(mime_type)
bits_per_sample, rate = parameters["bits_per_sample"], parameters["rate"]
num_channels, data_size = 1, len(audio_data)
bytes_per_sample, block_align = bits_per_sample // 8, num_channels * (bits_per_sample // 8)
byte_rate, chunk_size = rate * block_align, 36 + data_size
header = struct.pack("<4sI4s4sIHHIIHH4sI", b"RIFF", chunk_size, b"WAVE", b"fmt ", 16, 1, num_channels, rate, byte_rate, block_align, bits_per_sample, b"data", data_size)
return header + audio_data
def parse_audio_mime_type(mime_type: str) -> dict[str, int]:
bits, rate = 16, 24000
for param in mime_type.split(";"):
param = param.strip()
if param.lower().startswith("rate="):
try: rate = int(param.split("=", 1)[1])
except ValueError: pass
elif param.startswith("audio/L"):
try: bits = int(param.split("L", 1)[1])
except ValueError: pass
return {"bits_per_sample": bits, "rate": rate}
def smart_text_split(text, max_size=3800, log_list=None):
if len(text) <= max_size: return [text]
chunks, current_chunk = [], ""
sentences = re.split(r'(?<=[.!?؟۔])\s+', text) # Added Persian full stop for robustness
for sentence in sentences:
if len(current_chunk) + len(sentence) + 1 > max_size:
if current_chunk: chunks.append(current_chunk.strip())
current_chunk = sentence
while len(current_chunk) > max_size:
split_idx = -1
for punc in ['،', ',', ';', ':', ' ']:
try:
idx = current_chunk.rindex(punc, max_size // 2, max_size)
if idx > split_idx:
split_idx = idx
except ValueError:
pass
if split_idx != -1 :
part, current_chunk = current_chunk[:split_idx+1], current_chunk[split_idx+1:]
else:
part, current_chunk = current_chunk[:max_size], current_chunk[max_size:]
chunks.append(part.strip())
else: current_chunk += (" " if current_chunk and sentence else "") + sentence
if current_chunk: chunks.append(current_chunk.strip())
final_chunks = [c for c in chunks if c]
if log_list: _log(f"📊 متن به {len(final_chunks)} قطعه تقسیم شد.", log_list)
return final_chunks
def merge_audio_files_func(file_paths, output_path, log_list):
if not PYDUB_AVAILABLE:
_log("❌ pydub در دسترس نیست. ادغام انجام نشد.", log_list)
return False
try:
_log(f"🔗 ادغام {len(file_paths)} فایل صوتی...", log_list)
combined = AudioSegment.empty()
for i, fp in enumerate(file_paths):
if os.path.exists(fp):
try:
segment = AudioSegment.from_file(fp)
combined += segment
if i < len(file_paths) - 1:
combined += AudioSegment.silent(duration=150)
except Exception as e_pydub:
_log(f"⚠️ خطای Pydub در پردازش فایل '{fp}': {e_pydub}. از این فایل صرف نظر می شود.", log_list)
continue
else:
_log(f"⚠️ فایل پیدا نشد: {fp}", log_list)
if len(combined) == 0:
_log("❌ هیچ قطعه صوتی برای ادغام وجود ندارد.", log_list)
return False
combined.export(output_path, format="wav")
_log(f"✅ فایل ادغام شده: {output_path}", log_list)
return True
except Exception as e:
_log(f"❌ خطا در ادغام: {e}", log_list) # traceback.format_exc() removed to keep it closer to original
return False
def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val, log_list): # YOUR core_generate_audio
output_base_name = DEFAULT_OUTPUT_FILENAME_BASE
max_chunk, sleep_time = DEFAULT_MAX_CHUNK_SIZE, DEFAULT_SLEEP_BETWEEN_REQUESTS
_log(f"🚀 شروع فرآیند با مدل: {FIXED_MODEL_NAME}...", log_list)
api_key = os.environ.get("GEMINI_API_KEY") # YOUR WAY OF GETTING API KEY
if not api_key:
_log("❌ کلید API با نام GEMINI_API_KEY در متغیرهای محیطی تنظیم نشده.", log_list)
return None # Return None only, as per your original AlphaTTS
try:
client = genai.Client(api_key=api_key) # YOUR WAY OF CLIENT INSTANTIATION
_log(f"کلاینت Gemini با کلید API برای مدل {FIXED_MODEL_NAME} مقداردهی اولیه شد.", log_list)
except Exception as e:
_log(f"❌ خطا در مقداردهی اولیه کلاینت Gemini: {e}", log_list)
return None
if not text_input or not text_input.strip():
_log("❌ متن ورودی خالی.", log_list)
return None
text_chunks = smart_text_split(text_input, max_chunk, log_list)
if not text_chunks:
_log("❌ متن قابل پردازش نیست.", log_list)
return None
generated_files = []
for i, chunk in enumerate(text_chunks):
_log(f"🔊 پردازش قطعه {i+1}/{len(text_chunks)} (صدا: {selected_voice}, دما: {temperature_val})...", log_list)
final_text = f'"{prompt_input}"\n{chunk}' if prompt_input and prompt_input.strip() else chunk
# Using genai_types (aliased) for Content, Part etc. as in your original imports
contents = [genai_types.Content(role="user", parts=[genai_types.Part.from_text(text=final_text)])]
config = genai_types.GenerateContentConfig( # YOUR CONFIG OBJECT
temperature=temperature_val,
response_modalities=["audio"],
speech_config=genai_types.SpeechConfig(
voice_config=genai_types.VoiceConfig(
prebuilt_voice_config=genai_types.PrebuiltVoiceConfig(voice_name=selected_voice)
)
)
)
_log(f"کانفیگ API برای قطعه {i+1}: دما={temperature_val}, صدا={selected_voice}, مدالیته=['audio']", log_list)
fname_base = f"{output_base_name}_part{i+1:03d}"
try:
# YOUR API CALL
response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
inline_data = response.candidates[0].content.parts[0].inline_data
data_buffer = inline_data.data
mime_type = inline_data.mime_type
_log(f"داده صوتی در candidate.part[0].inline_data برای قطعه {i+1} یافت شد.", log_list)
ext = mimetypes.guess_extension(mime_type) or ".wav"
if "audio/L" in mime_type and ext == ".wav":
_log(f"تبدیل صدای خام PCM (MIME: {mime_type}) به WAV برای قطعه {i+1}.", log_list)
data_buffer = convert_to_wav(data_buffer, mime_type)
if not ext.startswith("."): ext = "." + ext
fpath = save_binary_file(f"{fname_base}{ext}", data_buffer, log_list)
if fpath: generated_files.append(fpath)
else:
_log(f"⚠️ پاسخ API برای قطعه {i+1} بدون داده صوتی در مسیر مورد انتظار.", log_list)
_log(f"ساختار کامل پاسخ (اولین 500 کاراکتر): {str(response)[:500]}", log_list)
# continue # As per your original code, it continues
except Exception as e: # Catching generic Exception as in your original
_log(f"❌ خطا در تولید قطعه {i+1}: {e}", log_list)
# traceback.format_exc() was not in your original core_generate_audio, so removed here
continue
if i < len(text_chunks) - 1 and len(text_chunks) > 1:
_log(f"💤 توقف کوتاه ({sleep_time} ثانیه) قبل از قطعه بعدی...", log_list)
time.sleep(sleep_time)
if not generated_files:
_log("❌ هیچ فایلی تولید نشد.", log_list)
return None # Return None only as per your original AlphaTTS
_log(f"🎉 {len(generated_files)} فایل(های) صوتی تولید شد.", log_list)
final_audio_file = None
final_output_path_base = f"{output_base_name}_final"
if len(generated_files) > 1:
if PYDUB_AVAILABLE:
merged_fn = f"{final_output_path_base}.wav"
if os.path.exists(merged_fn):
try: os.remove(merged_fn)
except OSError: _log(f"⚠️ عدم امکان حذف فایل ادغام شده قبلی '{merged_fn}' (خطای سیستم عامل)", log_list)
except Exception as e_rm: _log(f"⚠️ عدم امکان حذف فایل ادغام شده قبلی '{merged_fn}': {e_rm}", log_list)
if merge_audio_files_func(generated_files, merged_fn, log_list):
final_audio_file = merged_fn
for fp_path in generated_files:
if os.path.abspath(fp_path) != os.path.abspath(merged_fn):
try: os.remove(fp_path)
except OSError: _log(f"⚠️ عدم امکان حذف فایل موقت '{fp_path}' (خطای سیستم عامل)", log_list)
except Exception as e_del: _log(f"⚠️ عدم امکان حذف فایل موقت '{fp_path}': {e_del}", log_list)
else:
_log("⚠️ ادغام فایلهای صوتی ناموفق بود. اولین قطعه ارائه میشود.", log_list)
if generated_files:
try:
first_chunk_path = generated_files[0]
target_ext = os.path.splitext(first_chunk_path)[1]
fallback_fn = f"{final_output_path_base}{target_ext}" # Simplified name for fallback
if os.path.exists(fallback_fn) and os.path.abspath(first_chunk_path) != os.path.abspath(fallback_fn):
os.remove(fallback_fn)
if os.path.abspath(first_chunk_path) != os.path.abspath(fallback_fn):
os.rename(first_chunk_path, fallback_fn)
final_audio_file = fallback_fn
for i_gf in range(1, len(generated_files)):
try: os.remove(generated_files[i_gf])
except: pass # Keep silent as per your original
except Exception as e_rename_fb:
_log(f"خطا در تغییر نام فایل اولین قطعه: {e_rename_fb}", log_list) # Was `e_rename` in your original
final_audio_file = generated_files[0]
else:
_log("⚠️ pydub نیست. اولین قطعه ارائه میشود.", log_list)
if generated_files:
try:
first_chunk_path = generated_files[0]
target_ext = os.path.splitext(first_chunk_path)[1]
single_fallback_fn = f"{final_output_path_base}{target_ext}" # Simplified name
if os.path.exists(single_fallback_fn) and os.path.abspath(first_chunk_path) != os.path.abspath(single_fallback_fn):
os.remove(single_fallback_fn)
if os.path.abspath(first_chunk_path) != os.path.abspath(single_fallback_fn):
os.rename(first_chunk_path, single_fallback_fn)
final_audio_file = single_fallback_fn
for i_gf in range(1, len(generated_files)):
try: os.remove(generated_files[i_gf])
except: pass # Keep silent
except Exception as e_rename_single_npd: # Was `e_rename_single` in your original
_log(f"خطا در تغییر نام فایل اولین قطعه (بدون pydub): {e_rename_single_npd}", log_list)
final_audio_file = generated_files[0]
elif len(generated_files) == 1:
try:
single_file_path = generated_files[0]
target_ext = os.path.splitext(single_file_path)[1]
final_single_fn = f"{final_output_path_base}{target_ext}"
if os.path.exists(final_single_fn) and os.path.abspath(single_file_path) != os.path.abspath(final_single_fn):
os.remove(final_single_fn)
if os.path.abspath(single_file_path) != os.path.abspath(final_single_fn):
os.rename(single_file_path, final_single_fn)
final_audio_file = final_single_fn
except Exception as e_rename_sgl_final: # Was `e_rename_single_final` in your original
_log(f"خطا در تغییر نام فایل تکی نهایی: {e_rename_sgl_final}", log_list)
final_audio_file = generated_files[0]
if final_audio_file and not os.path.exists(final_audio_file):
_log(f"⚠️ فایل نهایی '{final_audio_file}' وجود ندارد!", log_list)
return None
return final_audio_file # Returns only path, as per your original AlphaTTS
# Your original gradio_tts_interface
def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_prompt, speaker_voice, temperature, progress=gr.Progress(track_tqdm=True)):
logs = []
actual_text = ""
if use_file_input:
if uploaded_file:
try:
# In Gradio, uploaded_file.name is the path to the temporary file
with open(uploaded_file.name, 'r', encoding='utf-8') as f: actual_text = f.read().strip()
if not actual_text: return None # Return None only, as per your original
except Exception as e: _log(f"❌ خطا خواندن فایل: {e}", logs); return None
else: return None
else:
actual_text = text_to_speak
if not actual_text or not actual_text.strip(): return None
final_path = core_generate_audio(actual_text, speech_prompt, speaker_voice, temperature, logs)
# Your original code commented out printing logs here, so I'll keep it commented.
# for log_entry in logs: print(log_entry) # For debugging in HF console
return final_path # Returns only path, as per your original AlphaTTS
# --- END: Core TTS Logic from YOUR AlphaTTS_Original (UNCHANGED) ---
# --- START: Gradio UI with AlphaTranslator_Styled Appearance ---
# (Using CSS variables from AlphaTranslator_Styled for colors and fonts)
FLY_PRIMARY_COLOR_HEX = "#4F46E5"
FLY_SECONDARY_COLOR_HEX = "#10B981"
FLY_ACCENT_COLOR_HEX = "#D97706"
FLY_TEXT_COLOR_HEX = "#1F2937"
FLY_SUBTLE_TEXT_HEX = "#6B7280"
FLY_LIGHT_BACKGROUND_HEX = "#F9FAFB"
FLY_WHITE_HEX = "#FFFFFF"
FLY_BORDER_COLOR_HEX = "#D1D5DB"
FLY_INPUT_BG_HEX_SIMPLE = "#F3F4F6"
FLY_PANEL_BG_SIMPLE = "#E0F2FE"
app_theme_outer_styled = gr.themes.Base( # New theme object name to avoid conflict if you had `app_theme_outer`
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
).set(
body_background_fill=FLY_LIGHT_BACKGROUND_HEX,
)
# CSS from AlphaTranslator_Styled, adapted slightly for your component names/IDs if needed
# Your original component IDs are like "use_file_cb_alpha_v3", "file_uploader_alpha_main_v3", etc.
# The CSS below uses general selectors but can be made more specific if those IDs are kept.
applied_css_for_alphatts = f"""
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
/* Poppins and Inter are from AlphaTranslator_Styled, Vazirmatn from your AlphaTTS_Original theme */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
:root {{
--fly-primary: {FLY_PRIMARY_COLOR_HEX}; --fly-secondary: {FLY_SECONDARY_COLOR_HEX};
--fly-accent: {FLY_ACCENT_COLOR_HEX}; --fly-text-primary: {FLY_TEXT_COLOR_HEX};
--fly-text-secondary: {FLY_SUBTLE_TEXT_HEX}; --fly-bg-light: {FLY_LIGHT_BACKGROUND_HEX};
--fly-bg-white: {FLY_WHITE_HEX}; --fly-border-color: {FLY_BORDER_COLOR_HEX};
--fly-input-bg-simple: {FLY_INPUT_BG_HEX_SIMPLE}; --fly-panel-bg-simple: {FLY_PANEL_BG_SIMPLE};
--font-global: 'Vazirmatn', 'Inter', 'Poppins', system-ui, sans-serif; /* Vazirmatn prioritized */
--font-english: 'Poppins', 'Inter', system-ui, sans-serif;
--radius-sm: 0.375rem; --radius-md: 0.5rem; --radius-lg: 0.75rem; --radius-xl: 1rem; --radius-full: 9999px;
--shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1),0 8px 10px -6px rgba(0,0,0,0.1);
--fly-primary-rgb: 79,70,229; --fly-accent-rgb: 217,119,6;
/* Variables from your original AlphaTTS CSS if they were different and needed */
/* For example, if your original had --app-button-bg for the blue button */
--app-button-bg-original: #2979FF; /* Blue from your original AlphaTTS button */
--shadow-button-original: 0 4px 10px -2px rgba(41,121,255,0.5);
--radius-input-original: 8px;
--app-border-color-original: #E0E0E0;
--app-input-bg-original: #F7F7F7;
}}
body {{
font-family:var(--font-global); direction:rtl; background-color:var(--fly-bg-light);
color:var(--fly-text-primary); line-height:1.7; font-size:16px;
}}
.gradio-container {{ /* Overall container styling from AlphaTranslator_Styled */
max-width:100% !important; width:100% !important; min-height:100vh;
margin:0 auto !important; padding:0 !important; border-radius:0 !important;
box-shadow:none !important; background:linear-gradient(170deg, #E0F2FE 0%, #F3E8FF 100%);
display:flex; flex-direction:column;
}}
/* Header styling from AlphaTranslator_Styled */
.app-header-alphatts {{ /* Changed class name slightly to avoid conflict if both apps run */
text-align:center; padding:2.5rem 1rem; margin:0;
background:linear-gradient(135deg,var(--fly-primary) 0%,var(--fly-secondary) 100%);
color:var(--fly-bg-white); border-bottom-left-radius:var(--radius-xl);
border-bottom-right-radius:var(--radius-xl); box-shadow:var(--shadow-lg);
position:relative; overflow:hidden;
}}
.app-header-alphatts::before {{ /* Decorative element from AlphaTranslator_Styled */
content:''; position:absolute; top:-50px; right:-50px; width:150px; height:150px;
background:rgba(255,255,255,0.1); border-radius:var(--radius-full);
opacity:0.5; transform:rotate(45deg);
}}
.app-header-alphatts h1 {{ /* h1 from AlphaTranslator_Styled */
font-size:2.25em !important; font-weight:800 !important; margin:0 0 0.5rem 0;
font-family:var(--font-english); letter-spacing:-0.5px; text-shadow:0 2px 4px rgba(0,0,0,0.1);
}}
.app-header-alphatts p {{ /* p from AlphaTranslator_Styled */
font-size:1em !important; margin-top:0.25rem; font-weight:400;
color:rgba(255,255,255,0.85) !important;
}}
/* Main content panel styling from AlphaTranslator_Styled */
.main-content-area-alphatts {{ /* Changed class name slightly */
flex-grow:1; padding:0.75rem; width:100%; margin:0 auto; box-sizing:border-box;
}}
.content-panel-alphatts {{ /* Changed class name slightly */
background-color:var(--fly-bg-white); padding:1rem; border-radius:var(--radius-xl);
box-shadow:var(--shadow-xl); margin-top:-2rem; position:relative; z-index:10;
margin-bottom:2rem; width:100%; box-sizing:border-box;
}}
/* Styling for YOUR UI elements, applying AlphaTranslator_Styled aesthetics */
/* Inputs (Textbox, Dropdown, File) */
.content-panel-alphatts .gr-input > label + div > textarea,
.content-panel-alphatts .gr-dropdown > label + div > div > input,
.content-panel-alphatts .gr-dropdown > label + div > div > select,
.content-panel-alphatts .gr-textbox > label + div > textarea,
.content-panel-alphatts .gr-file > label + div /* For file input styling */
{{
border-radius:var(--radius-input-original) !important; /* Your original radius */
border:1.5px solid var(--fly-border-color) !important; /* Border from AlphaTranslator */
font-size:0.95em !important; background-color:var(--fly-input-bg-simple) !important; /* BG from AlphaTranslator */
padding:10px 12px !important; color:var(--fly-text-primary) !important;
}}
.content-panel-alphatts .gr-input > label + div > textarea:focus,
.content-panel-alphatts .gr-dropdown > label + div > div > input:focus,
.content-panel-alphatts .gr-dropdown > label + div > div > select:focus,
.content-panel-alphatts .gr-textbox > label + div > textarea:focus,
.content-panel-alphatts .gr-file > label + div:focus-within
{{
border-color:var(--fly-primary) !important; /* Focus border from AlphaTranslator */
box-shadow:0 0 0 3px rgba(var(--fly-primary-rgb),0.12) !important;
background-color:var(--fly-bg-white) !important;
}}
.content-panel-alphatts .gr-file > label + div {{ text-align:center; border-style: dashed !important; }}
/* Button: Using --fly-accent for consistency with AlphaTranslator's primary action color */
.content-panel-alphatts .gr-button[elem_id="generate_button_alpha_v3"], /* Your button ID */
.content-panel-alphatts button[variant="primary"] /* General primary button */
{{
background:var(--fly-accent) !important; /* Orange accent from AlphaTranslator */
margin-top:1.5rem !important; padding:12px 20px !important; /* Adjusted padding */
transition:all 0.25s ease-in-out !important; color:white !important; font-weight:600 !important;
border-radius:var(--radius-input-original) !important; /* Your original radius */ border:none !important;
box-shadow:0 3px 8px -1px rgba(var(--fly-accent-rgb),0.3) !important;
width:100% !important; font-size:1.05em !important; /* Your original font size */
display:flex; align-items:center; justify-content:center;
}}
.content-panel-alphatts .gr-button[elem_id="generate_button_alpha_v3"]:hover,
.content-panel-alphatts button[variant="primary"]:hover
{{
background:#B45309 !important; /* Darker orange */ transform:translateY(-1px) !important;
box-shadow:0 5px 10px -1px rgba(var(--fly-accent-rgb),0.4) !important;
}}
/* Labels (using AlphaTranslator_Styled general label style) */
.content-panel-alphatts label > span.label-text
{{
font-weight:500 !important; color:#4B5563 !important;
font-size:0.88em !important; margin-bottom:6px !important; display:inline-block;
}}
/* Your original specific label styling with icons (if you want to keep them) */
/* You would need to ensure your Gradio labels have the correct `for` attribute linking to input `elem_id`
or use JavaScript to add these pseudo-elements if Gradio doesn't directly support `for` on labels.
For simplicity, I'm omitting the ::before icon styles unless you confirm they are essential
and your Gradio setup can support them easily. The general label style above will apply.
*/
/* Temperature description (from your original AlphaTTS CSS) */
.content-panel-alphatts .temp_description_class_alpha_v3 {{
font-size: 0.85em; color: #777; margin-top: -0.4rem; margin-bottom: 1rem;
}}
/* Audio Player (general styling, can be targeted by ID if set) */
.content-panel-alphatts .gr-audio audio, /* General audio player */
.content-panel-alphatts #output_audio_player_alpha_v3 audio /* Your specific ID */
{{
width: 100%; border-radius: var(--radius-input-original); margin-top:0.8rem;
}}
/* Examples (using AlphaTranslator_Styled examples button style) */
.content-panel-alphatts div[label*="نمونههای کاربردی"] .gr-button.gr-button-tool, /* Targetting by label */
.content-panel-alphatts div[label*="نمونههای کاربردی"] .gr-sample-button
{{
background-color:#E0E7FF !important; color:var(--fly-primary) !important;
border-radius:6px !important; font-size:0.78em !important; padding:4px 8px !important;
}}
.content-panel-alphatts .custom-hr {{height:1px;background-color:var(--fly-border-color);margin:1.5rem 0;border:none;}}
/* Footer styling from AlphaTranslator_Styled */
.app-footer-alphatts {{ /* Changed class name slightly */
text-align:center;font-size:0.85em;color:var(--fly-text-secondary);margin-top:2.5rem;
padding:1rem 0;background-color:rgba(255,255,255,0.3);backdrop-filter:blur(5px);
border-top:1px solid var(--fly-border-color);
}}
footer {{display:none !important;}} /* Hides default Gradio footer */
/* Responsive adjustments from AlphaTranslator_Styled */
@media (min-width:640px) {{
.main-content-area-alphatts {{padding:1.5rem;max-width:700px;}}
.content-panel-alphatts {{padding:1.5rem;}}
.app-header-alphatts h1 {{font-size:2.5em !important;}}
.app-header-alphatts p {{font-size:1.05em !important;}}
}}
@media (min-width:768px) {{
.main-content-area-alphatts {{max-width:780px;}}
.content-panel-alphatts {{padding:2rem;}}
.content-panel-alphatts .gr-button[elem_id="generate_button_alpha_v3"],
.content-panel-alphatts button[variant="primary"]
{{
width:auto !important; align-self:flex-start;
}}
.app-header-alphatts h1 {{font-size:2.75em !important;}}
.app-header-alphatts p {{font-size:1.1em !important;}}
}}
"""
# Using your original Gradio Blocks structure
# The theme `gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")])` is from your original.
# We are applying `app_theme_outer_styled` for the body background and `applied_css_for_alphatts` for specifics.
with gr.Blocks(theme=app_theme_outer_styled, css=applied_css_for_alphatts, title=f"آلفا TTS ({FIXED_MODEL_NAME.split('-')[1]})") as demo:
# Applying the header from AlphaTranslator_Styled structure
gr.HTML(f"""
<div class='app-header-alphatts'>
<h1>🚀 Alpha TTS</h1>
<p>جادوی تبدیل متن به صدا در دستان شما (Gemini {FIXED_MODEL_NAME.split('-')[1]})</p>
</div>
""")
# Applying the main content panel structure from AlphaTranslator_Styled
with gr.Column(elem_classes=["main-content-area-alphatts"]):
with gr.Column(elem_classes=["content-panel-alphatts"]): # Your original AlphaTTS used Column, let's keep it simple
# AlphaTranslator used Group, but Column is fine.
# Your original UI layout from AlphaTTS_Original
# Note: `elem_id`s are from your original AlphaTTS code.
# CSS selectors have been updated to try and match these or use general selectors.
# Warning if GEMINI_API_KEY is not set
if not os.environ.get("GEMINI_API_KEY"):
missing_key_msg = (
"⚠️ هشدار: متغیر محیطی GEMINI_API_KEY تنظیم نشده است. "
"قابلیت تبدیل متن به گفتار احتمالاً کار نخواهد کرد. "
"لطفاً این متغیر را در بخش Secrets این Space تنظیم کنید."
)
gr.Markdown(f"<div class='api-warning-message'>{missing_key_msg}</div>")
use_file_input_cb = gr.Checkbox(label="📄 استفاده از فایل متنی (.txt)", value=False, elem_id="use_file_cb_alpha_v3")
uploaded_file_input = gr.File(
label=" ",
file_types=['.txt'],
visible=False,
elem_id="file_uploader_alpha_main_v3"
)
text_to_speak_tb = gr.Textbox(
label="متن فارسی برای تبدیل",
placeholder="مثال: سلام، فردا هوا چطور است؟",
lines=5,
value="",
visible=True,
elem_id="text_input_main_alpha_v3"
)
# Your original change function for checkbox
use_file_input_cb.change(
fn=lambda x: (gr.update(visible=x, label=" " if x else "متن فارسی برای تبدیل"), gr.update(visible=not x)),
inputs=use_file_input_cb,
outputs=[uploaded_file_input, text_to_speak_tb]
)
speech_prompt_tb = gr.Textbox(
label="سبک گفتار (اختیاری)",
placeholder="مثال: با لحنی شاد و پرانرژی",
value="با لحنی دوستانه و رسا صحبت کن.",
lines=2, elem_id="speech_prompt_alpha_v3"
)
speaker_voice_dd = gr.Dropdown(
SPEAKER_VOICES, label="انتخاب گوینده و لهجه", value="Charon", elem_id="speaker_voice_alpha_v3"
)
temperature_slider = gr.Slider(
minimum=0.1, maximum=1.5, step=0.05, value=0.9, label="میزان خلاقیت صدا",
elem_id="temperature_slider_alpha_v3"
)
gr.Markdown("<p class='temp_description_class_alpha_v3'>مقادیر بالاتر = تنوع بیشتر، مقادیر پایینتر = یکنواختی بیشتر.</p>")
generate_button = gr.Button("🚀 تولید و پخش صدا", elem_id="generate_button_alpha_v3") # Removed variant="primary" to let CSS handle it via elem_id
output_audio = gr.Audio(label=" ", type="filepath", elem_id="output_audio_player_alpha_v3")
# Your original Examples section
# Applying a custom HR from AlphaTranslator_Styled
gr.HTML("<hr class='custom-hr'>")
gr.Markdown(
"<h3 style='text-align:center; font-weight:500; color:var(--fly-text-secondary); margin-top:1.5rem; margin-bottom:1rem;'>نمونههای کاربردی</h3>",
# elem_id="examples_section_title_v3" # elem_id from your original
)
gr.Examples(
examples=[
[False, None, "سلام بر شما، امیدوارم روز خوبی داشته باشید.", "با لحنی گرم و صمیمی.", "Zephyr", 0.85],
[False, None, "این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است.", "با صدایی طبیعی و روان.", "Charon", 0.9],
],
inputs=[use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider],
outputs=[output_audio], # Original AlphaTTS only output to audio
fn=gradio_tts_interface,
cache_examples=os.getenv("GRADIO_CACHE_EXAMPLES", "False").lower() == "true" # From AlphaTranslator
)
# Footer from AlphaTranslator_Styled
gr.Markdown(f"<p class='app-footer-alphatts'>Alpha TTS © 2024 - Model: {FIXED_MODEL_NAME}</p>")
# --- Event Handlers (from YOUR AlphaTTS_Original) ---
if generate_button is not None:
generate_button.click(
fn=gradio_tts_interface,
inputs=[use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider],
outputs=[output_audio] # Original AlphaTTS only output to audio
)
else:
logging.error("دکمه تولید صدا (generate_button_alpha_v3) به درستی مقداردهی اولیه نشده است.")
# --- END: Gradio UI ---
if __name__ == "__main__":
# Removed auto-restart thread to keep it closer to your original AlphaTTS.
# If you need it, you can re-add the auto_restart_service function and thread start.
# Check if PYDUB is available at launch
if not PYDUB_AVAILABLE:
logging.warning("Pydub (for audio merging) not found. Please install with `pip install pydub`. Merging will be disabled if multiple audio chunks are generated.")
demo.launch(
server_name="0.0.0.0",
server_port=int(os.getenv("PORT", 7860)),
debug=os.environ.get("GRADIO_DEBUG", "False").lower() == "true",
show_error=True
) |