File size: 26,171 Bytes
c3da9f3 | 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 | """
ACE-Step v1.5 - HuggingFace Space Entry Point
This file serves as the entry point for HuggingFace Space deployment.
It initializes the service and launches the Gradio interface.
ZeroGPU Support:
- ZeroGPU uses the 'spaces' package to intercept CUDA operations
- Models are loaded to "cuda" during startup but actual GPU allocation is deferred
- Handlers are registered globally so forked processes inherit them without pickling
- @spaces.GPU decorators are on top-level Gradio event handlers, not internal functions
- nano-vllm uses direct CUDA APIs that bypass spaces interception, so we use PyTorch backend
اسپیسِ ترکیبی «ساخت آهنگ + کلون کردن صدا + آواتار سخنگو»
--------------------------------------------------------
• تبِ «🎵 ساخت آهنگ»: همان ACE-Step قبلی، بدونِ هیچ تغییری، روی GPU (ZeroGPU).
• تبِ «🎙️ کلون کردن صدا»: همان اسپیسِ صدای قبلی (Vevo)، کاملاً روی CPU، در
یک پروسهی جدا (voice_engine.py) که ZeroGPU را ایمپورت نمیکند — توضیحِ کامل
بالای voice_engine.py و voice_clone.py. حالا تا «دو» تبدیل را همزمان میسازد
(کارِ تنها همهی هستهها، دو کار هر کدام نصف؛ اکشن پرو کارِ دوم را فقط وقتی
میفرستد که هیچ اسپیسِ خالیِ دیگری نیست)، صدای مرجعِ بلند را به پُرصداترین ۱۲
ثانیهاش کوتاه میکند و کارِ بیصاحب را همان لحظه لغو میکند.
• تبِ «🗣️ آواتار سخنگو»: همان اسپیسِ آواتارِ قبلی (MiniMax-H3، عکس + صدا ← ویدیو)
روی GPU (کارتِ xlarge، مثلِ قبل)، بدونِ روشِ فایلِ .pt — توضیحِ کامل بالای
avatar_tab.py.
• مسیرهای API:
ساخت آهنگ ← دکمهی «Generate Music» (همان قبلی؛ اکشن پرو آن را از روی
نام دکمه و تعدادِ ورودیها در کانفیگ پیدا میکند)
کلون صدا ← /predict_voice_conversion (ورودی: صدای منبع، صدای مرجع)
وضعیت صدا ← /voice_status (فقط صفِ صدا؛ بدونِ صف و فوری؛ "slots": 2)
آواتار ← /generate (همان مسیرِ اسپیسِ آواتارِ قبلی)
• diffusers: یک نسخهی قفلشده از diffusers main (همان کامیتِ اسپیسِ آواتار) در
vendor/diffusers-d5baa4fb.zip — چرا: README، بخشِ «وابستگیها».
"""
import os
import sys
# Get current directory (app.py location)
current_dir = os.path.dirname(os.path.abspath(__file__))
# Add nano-vllm to Python path (local package)
nano_vllm_path = os.path.join(current_dir, "acestep", "third_parts", "nano-vllm")
if os.path.exists(nano_vllm_path):
sys.path.insert(0, nano_vllm_path)
# diffusers (هم VAEِ آهنگ و هم کلِ مدلِ آواتار از آن استفاده میکنند): نسخهی قفلشدهی
# diffusers main @ d5baa4fb (همان کامیتی که اسپیسِ آواتار داشت)، بهصورتِ یک فایلِ zip که
# پایتون مستقیم از داخلش ایمپورت میکند. از pip نصب نمیشود چون آن نسخه huggingface_hub>=1.23
# میخواهد و transformersِ آهنگ (<4.58) فقط huggingface_hub<1.0 را قبول میکند (توضیح: README).
_vendored_diffusers = os.path.join(current_dir, "vendor", "diffusers-d5baa4fb.zip")
if os.path.exists(_vendored_diffusers):
sys.path.insert(0, _vendored_diffusers)
# Disable Gradio analytics
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
# Clear proxy settings that may affect Gradio
for proxy_var in ['http_proxy', 'https_proxy', 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY']:
os.environ.pop(proxy_var, None)
# Import spaces for ZeroGPU support (must be imported before torch for proper interception)
# This is a no-op if not running on HuggingFace Spaces
try:
import spaces
HAS_SPACES = True
except ImportError:
HAS_SPACES = False
import torch
from acestep.handler import AceStepHandler
from acestep.llm_inference import LLMHandler
from acestep.dataset_handler import DatasetHandler
from acestep.gradio_ui import create_gradio_interface
# پچ فوقالعاده برای استخراج خودکار مسیر متنی و تبدیل خودکار فرمتهای غیر استاندارد گوشی (مانند m4a) به wav استاندارد
original_load_audio_file = AceStepHandler._load_audio_file
import subprocess
import uuid
def convert_to_standard_wav(audio_file_path: str) -> str:
"""
مبدل صوتی پویا: فایلهای غیر استاندارد را به فرمت استودیویی WAV تبدیل میکند تا با تضمین ۱۰۰٪ لود شوند
"""
if not audio_file_path or not os.path.exists(audio_file_path):
return audio_file_path
ext = os.path.splitext(audio_file_path)[1].lower()
# لیست فرمتهای رایج گوشی که نیاز به تبدیل خودکار به فرمت استاندارد wav دارند
if ext in [".m4a", ".aac", ".3gp", ".amr", ".webm", ".ogg", ".opus", ".mp4"]:
logger_name = "Auto-Convert"
print(f"[{logger_name}] Detected format {ext}. Converting {audio_file_path} to standard WAV...")
try:
uploads_dir = os.path.join(current_dir, "data", "shared_uploads")
os.makedirs(uploads_dir, exist_ok=True)
temp_wav = os.path.join(uploads_dir, f"converted_{uuid.uuid4().hex}.wav")
# فرآیند تبدیل بلادرنگ و سریع با ابزار سیستمی ffmpeg
cmd = [
"ffmpeg", "-y",
"-i", audio_file_path,
"-ac", "2",
"-ar", "48000",
"-acodec", "pcm_s16le",
temp_wav
]
subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
print(f"[{logger_name}] Converted successfully to: {temp_wav}")
return temp_wav
except Exception as e:
print(f"[{logger_name}] Failed to convert: {e}", file=sys.stderr)
return audio_file_path
def patched_load_audio_file(self, audio_file):
if isinstance(audio_file, list) and len(audio_file) > 0:
audio_file = audio_file[0]
if isinstance(audio_file, dict):
if "path" in audio_file and audio_file["path"]:
audio_file = audio_file["path"]
elif "value" in audio_file and audio_file["value"]:
audio_file = audio_file["value"]
# تبدیل خودکار فرمتهای موبایل قبل از تحویل به موتور دکودر
if isinstance(audio_file, str):
audio_file = convert_to_standard_wav(audio_file)
return original_load_audio_file(self, audio_file)
AceStepHandler._load_audio_file = patched_load_audio_file
# بخشِ «کلون کردن صدا» (فقط CPU، پروسهی جدا) -- voice_clone.py
import voice_clone
# بخشِ «آواتار سخنگو» (GPU، کارتِ xlarge) -- avatar_tab.py
# AVATAR_DISABLED=1 (اختیاری، برای آزمایش) این تب و مدلش را کاملاً خاموش میکند.
AVATAR_ENABLED = os.environ.get("AVATAR_DISABLED", "").lower() not in ("1", "true", "yes")
if AVATAR_ENABLED:
import avatar_tab
# پچ هوشمند برای جلوگیری از کرش تفکیک صدا در خودِ اسپیس و صادر کردن خطای صریح در صورت عدم پردازش فایل صوتی مرجع
original_prepare_batch = AceStepHandler._prepare_batch
def patched_prepare_batch(self, *args, **kwargs):
args_list = list(args)
# اگر کاربر فایل صوتی مرجع فرستاده ولی پردازش نشده و مقدار NoneType است، خطای واضح صادر میکنیم تا برنامه متوقف شود
# و از تولید خروجی بیصدا یا پیشفرض جلوگیری به عمل آید.
if "refer_audios" in kwargs and kwargs["refer_audios"] is None:
raise ValueError("The uploaded Reference Audio could not be processed. Please make sure the audio file is valid, has sound, and is not corrupted.")
elif len(args_list) > 5 and args_list[5] is None:
raise ValueError("The uploaded Reference Audio could not be processed. Please make sure the audio file is valid, has sound, and is not corrupted.")
return original_prepare_batch(self, *args_list, **kwargs)
AceStepHandler._prepare_batch = patched_prepare_batch
# پچ هوشمند و پویا برای پیدا کردن و تصحیح فیلد تعداد تولید در تمام مراحل اجرای متدها
import acestep.gradio_ui.events.results_handlers as res_h
original_gen = res_h.generate_with_batch_management
def patched_generate_with_batch_management(*args, **kwargs):
args_list = list(args)
if len(args_list) > 14:
if args_list[14] is None or (isinstance(args_list[14], (int, float)) and args_list[14] < 1):
args_list[14] = 1
if "batch_size_input" in kwargs and (kwargs["batch_size_input"] is None or kwargs["batch_size_input"] < 1):
kwargs["batch_size_input"] = 1
generator = original_gen(*args_list, **kwargs)
for partial_result in generator:
if isinstance(partial_result, tuple):
modified_result = list(partial_result)
for i, item in enumerate(modified_result):
if isinstance(item, dict) and "batch_size_input" in item:
if item["batch_size_input"] is None or item["batch_size_input"] < 1:
item["batch_size_input"] = 1
yield tuple(modified_result)
else:
yield partial_result
res_h.generate_with_batch_management = patched_generate_with_batch_management
# پچ هوشمند برای حذف محدودیت قفل مرورگر در فیلدهای عددی با مقدار حداقل ۱
import gradio as gr
original_number_init = gr.Number.__init__
def patched_number_init(self, *args, **kwargs):
if "minimum" in kwargs and kwargs["minimum"] == 1:
kwargs["minimum"] = None # رفع کلی مشکل فیلدهای با محدودیت حداقل ۱ عددی
original_number_init(self, *args, **kwargs)
gr.Number.__init__ = patched_number_init
# اعمال پچ اصلاحی برای غیرفعال کردن Flash Attention ناسازگار روی ZeroGPU و استفاده از موتور پایدار sdpa
AceStepHandler.is_flash_attention_available = lambda self: False
AceStepHandler.is_flash_attn3_available = lambda self: False
AceStepHandler.get_best_attn_implementation = lambda self: "sdpa"
LLMHandler.is_flash_attention_available = lambda self: False
LLMHandler.is_flash_attn3_available = lambda self: False
LLMHandler.get_best_attn_implementation = lambda self: "sdpa"
# Detect ZeroGPU environment
IS_HUGGINGFACE_SPACE = os.environ.get("SPACE_ID") is not None
# ZeroGPU detection: check env var OR assume ZeroGPU for all HF Spaces (safer default)
# The SPACE_HARDWARE env var is unreliable, so we assume ZeroGPU if on HF Space
IS_ZEROGPU = IS_HUGGINGFACE_SPACE or os.environ.get("ZEROGPU") is not None
def get_gpu_memory_gb():
"""
Get GPU memory in GB. Returns 0 if no GPU is available.
"""
try:
if torch.cuda.is_available():
total_memory = torch.cuda.get_device_properties(0).total_memory
memory_gb = total_memory / (1024**3)
return memory_gb
else:
return 0
except Exception as e:
print(f"Warning: Failed to detect GPU memory: {e}", file=sys.stderr)
return 0
def get_persistent_storage_path():
"""
Detect and return a writable persistent storage path.
"""
# Check for local checkpoint directory override (for development)
checkpoint_dir_override = os.environ.get("CHECKPOINT_DIR")
if checkpoint_dir_override:
if checkpoint_dir_override.endswith("/checkpoints") or checkpoint_dir_override.endswith("\\checkpoints"):
checkpoint_dir_override = os.path.dirname(checkpoint_dir_override)
if os.path.exists(checkpoint_dir_override):
print(f"Using local checkpoint directory (CHECKPOINT_DIR): {checkpoint_dir_override}")
return checkpoint_dir_override
else:
print(f"Warning: CHECKPOINT_DIR path does not exist: {checkpoint_dir_override}")
# Try HuggingFace Space persistent storage first
hf_data_path = "/data"
# Check if /data exists and is writable
if os.path.exists(hf_data_path):
try:
test_file = os.path.join(hf_data_path, ".write_test")
with open(test_file, 'w') as f:
f.write("test")
os.remove(test_file)
print(f"Using HuggingFace persistent storage: {hf_data_path}")
return hf_data_path
except (PermissionError, OSError) as e:
print(f"Warning: /data exists but is not writable: {e}")
# Fall back to app directory (non-persistent but works without special config)
fallback_path = os.path.join(current_dir, "data")
os.makedirs(fallback_path, exist_ok=True)
print(f"Using local storage (non-persistent): {fallback_path}")
print("Note: To enable persistent storage, configure it in HuggingFace Space settings")
return fallback_path
def main():
"""Main entry point for HuggingFace Space"""
# موتورِ صدا همین اول روشن میشود تا مدلِ صدا (روی CPU) همزمان با مدلهای
# آهنگ بالا بیاید؛ هیچ ربطی به GPU ندارد.
if os.environ.get("VOICE_ENGINE_DISABLED", "").lower() not in ("1", "true", "yes"):
voice_clone.start_engine()
# فیلترِ محتوای آواتار (ncii_guard، پروسهی جدا و فقط CPU) هم همین اول، در پسزمینه.
if AVATAR_ENABLED:
avatar_tab.start_guard_async()
# Check for DEBUG_UI mode (skip model initialization for UI development)
debug_ui = os.environ.get("DEBUG_UI", "").lower() in ("1", "true", "yes")
if debug_ui:
print("=" * 60)
print("DEBUG_UI mode enabled - skipping model initialization")
print("UI will be fully functional but generation is disabled")
print("=" * 60)
# Log ZeroGPU detection
if IS_ZEROGPU:
print("=" * 60)
print("ZeroGPU environment detected")
print("- Using spaces package for GPU allocation")
print("- PyTorch backend forced for LLM (nano-vllm incompatible)")
print("- GPU will be allocated on-demand during generation")
print("=" * 60)
# Get persistent storage path (auto-detect)
persistent_storage_path = get_persistent_storage_path()
# Detect GPU memory for auto-configuration
# Note: In ZeroGPU, GPU may not be available during startup, so this may return 0
gpu_memory_gb = get_gpu_memory_gb()
# For ZeroGPU, we don't need CPU offload as GPU is allocated dynamically
if IS_ZEROGPU:
auto_offload = False
print("ZeroGPU: CPU offload disabled (GPU allocated on-demand)")
else:
auto_offload = gpu_memory_gb > 0 and gpu_memory_gb < 16
if not debug_ui and not IS_ZEROGPU:
if auto_offload:
print(f"Detected GPU memory: {gpu_memory_gb:.2f} GB (< 16GB)")
print("Auto-enabling CPU offload to reduce GPU memory usage")
elif gpu_memory_gb > 0:
print(f"Detected GPU memory: {gpu_memory_gb:.2f} GB (>= 16GB)")
print("CPU offload disabled by default")
else:
print("No GPU detected, running on CPU")
# Create handler instances
print("Creating handlers...")
dit_handler = AceStepHandler(persistent_storage_path=persistent_storage_path)
llm_handler = LLMHandler(persistent_storage_path=persistent_storage_path)
dataset_handler = DatasetHandler()
# Service mode configuration from environment variables
# Default changed to the non-turbo XL SFT checkpoint (5B DiT, 50-step CFG sampling)
# for maximum audio quality. This is the same weights as
# ACE-Step/acestep-v15-xl-sft-diffusers (just packaged for this handler
# instead of HF `diffusers`), and is the highest-quality checkpoint ACE-Step
# currently ships. Override with the SERVICE_MODE_DIT_MODEL env var if you
# want turbo speed instead (e.g. "acestep-v15-xl-turbo", 8 steps, no CFG).
config_path = os.environ.get(
"SERVICE_MODE_DIT_MODEL",
"acestep-v15-xl-sft"
)
# Second DiT model: disabled by default because xl-sft + xl-turbo loaded
# together needs ~18GB+ VRAM. Set SERVICE_MODE_DIT_MODEL_2 (e.g. to
# "acestep-v15-xl-turbo") if your GPU has enough VRAM and you want a fast
# draft model alongside the quality model in the UI dropdown.
config_path_2 = os.environ.get("SERVICE_MODE_DIT_MODEL_2", "").strip()
lm_model_path = os.environ.get(
"SERVICE_MODE_LM_MODEL",
"acestep-5Hz-lm-1.7B"
)
# For ZeroGPU, force PyTorch backend (nano-vllm uses direct CUDA APIs)
if IS_ZEROGPU:
backend = "pt"
else:
backend = os.environ.get("SERVICE_MODE_BACKEND", "vllm")
device = "auto"
print(f"Service mode configuration:")
print(f" DiT model 1: {config_path}")
if config_path_2:
print(f" DiT model 2: {config_path_2}")
print(f" LM model: {lm_model_path}")
print(f" Backend: {backend}")
print(f" Offload to CPU: {auto_offload}")
print(f" DEBUG_UI: {debug_ui}")
print(f" ZeroGPU: {IS_ZEROGPU}")
# Determine flash attention availability
use_flash_attention = dit_handler.is_flash_attention_available()
print(f" Flash Attention: {use_flash_attention}")
# Initialize models (skip in DEBUG_UI mode)
init_status = ""
enable_generate = False
dit_handler_2 = None
if debug_ui:
# In DEBUG_UI mode, skip all model initialization
init_status = "⚠️ DEBUG_UI mode - models not loaded\nUI is functional but generation is disabled"
enable_generate = False
print("Skipping model initialization (DEBUG_UI mode)")
else:
# Initialize primary DiT model
print(f"Initializing DiT model 1: {config_path}...")
init_status, enable_generate = dit_handler.initialize_service(
project_root=current_dir,
config_path=config_path,
device=device,
use_flash_attention=use_flash_attention,
compile_model=False,
offload_to_cpu=auto_offload,
offload_dit_to_cpu=False
)
if not enable_generate:
print(f"Warning: DiT model 1 initialization issue: {init_status}", file=sys.stderr)
else:
print("DiT model 1 initialized successfully")
# Initialize second DiT model if configured
if config_path_2:
print(f"Initializing DiT model 2: {config_path_2}...")
dit_handler_2 = AceStepHandler(persistent_storage_path=persistent_storage_path)
# Share VAE, text_encoder, and silence_latent from the first handler to save memory
init_status_2, enable_generate_2 = dit_handler_2.initialize_service(
project_root=current_dir,
config_path=config_path_2,
device=device,
use_flash_attention=use_flash_attention,
compile_model=False,
offload_to_cpu=auto_offload,
offload_dit_to_cpu=False,
# Share components from first handler
shared_vae=dit_handler.vae,
shared_text_encoder=dit_handler.text_encoder,
shared_text_tokenizer=dit_handler.text_tokenizer,
shared_silence_latent=dit_handler.silence_latent,
)
if not enable_generate_2:
print(f"Warning: DiT model 2 initialization issue: {init_status_2}", file=sys.stderr)
init_status += f"\n⚠️ DiT model 2 failed: {init_status_2}"
else:
print("DiT model 2 initialized successfully")
init_status += f"\n✅ DiT model 2: {config_path_2}"
# Initialize LM model
checkpoint_dir = dit_handler._get_checkpoint_dir()
print(f"Initializing 5Hz LM: {lm_model_path}...")
lm_status, lm_success = llm_handler.initialize(
checkpoint_dir=checkpoint_dir,
lm_model_path=lm_model_path,
backend=backend,
device=device,
offload_to_cpu=auto_offload,
dtype=dit_handler.dtype
)
if lm_success:
print("5Hz LM initialized successfully")
init_status += f"\n{lm_status}"
else:
print(f"Warning: 5Hz LM initialization failed: {lm_status}", file=sys.stderr)
init_status += f"\n{lm_status}"
# Build available models list for UI
available_dit_models = [config_path]
if config_path_2 and dit_handler_2 is not None:
available_dit_models.append(config_path_2)
# Prepare initialization parameters for UI
init_params = {
'pre_initialized': True,
'service_mode': True,
'checkpoint': None,
'config_path': config_path,
'config_path_2': config_path_2 if config_path_2 else None,
'device': device,
'init_llm': True,
'lm_model_path': lm_model_path,
'backend': backend,
'use_flash_attention': use_flash_attention,
'offload_to_cpu': auto_offload,
'offload_dit_to_cpu': False,
'init_status': init_status,
'enable_generate': enable_generate,
'dit_handler': dit_handler,
'dit_handler_2': dit_handler_2,
'available_dit_models': available_dit_models,
'llm_handler': llm_handler,
'language': 'en',
'persistent_storage_path': persistent_storage_path,
'debug_ui': debug_ui,
}
print("Service initialization completed!")
# Create Gradio interface with pre-initialized handlers
print("Creating Gradio interface...")
demo = create_gradio_interface(
dit_handler,
llm_handler,
dataset_handler,
init_params=init_params,
language='en',
# تبِ دوم: «کلون کردن صدا» (همان رابطِ اسپیسِ صدای قبلی)
# تبِ سوم: «آواتار سخنگو» (همان رابطِ اسپیسِ آواتارِ قبلی، بدونِ بخشِ فایلِ .pt)
extra_tabs=[("🎙️ کلون کردن صدا", "voice", voice_clone.build_voice_tab)]
+ ([("🗣️ آواتار سخنگو", "avatar", avatar_tab.build_avatar_tab)] if AVATAR_ENABLED else []),
)
# مدلِ آواتار درست بعد از اینکه ZeroGPU وزنهای آهنگ را بستهبندی (و از رم آزاد) کرد
# بالا میآید، ولی هنوز پیش از اولین درخواست؛ یعنی کمترین اوجِ مصرفِ رم و باز هم
# «وقتی اسپیس بالا آمد همهچیز آماده است». بدونِ ZeroGPU همینجا بالا میآید.
if AVATAR_ENABLED and not debug_ui:
if not avatar_tab.start_after_zerogpu_pack():
print("Loading the talking-avatar model (MiniMax-H3)...")
avatar_tab.load_models()
# پیادهسازی سرویس اختصاصی آپلود اشتراکی برای اشتراکگذاری بینقص فایلهای صوتی بین کانتینرهای ZeroGPU
from fastapi import UploadFile, File
import shutil
@demo.app.post("/custom_upload")
async def custom_upload(file: UploadFile = File(...)):
# ساخت دایرکتوری در حافظه مشترک و دائم پروژه
uploads_dir = os.path.join(current_dir, "data", "shared_uploads")
os.makedirs(uploads_dir, exist_ok=True)
# ذخیرهسازی مطمئن فایل صوتی — فقط نامِ فایل، بدونِ مسیر (نامی مثلِ «../../app.py» نباید
# بیرون از همین پوشه چیزی بنویسد یا فایلهای خودِ اسپیس را عوض کند)
safe_name = os.path.basename((file.filename or "").replace("\\", "/"))
if safe_name in ("", ".", ".."):
safe_name = "upload.bin"
file_path = os.path.join(uploads_dir, safe_name)
with open(file_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
return {"path": file_path}
# فعال کردن پویا و مستقیم دکمههای دانلود برای تمام کامپوننتهای صوتی
for component in demo.blocks.values():
if isinstance(component, gr.Audio):
component.buttons = ["play", "download"]
# Enable queue for multi-user support
print("Enabling queue for multi-user support...")
demo.queue(max_size=20)
# Launch
print("Launching server on 0.0.0.0:7860...")
# ssr_mode=False: رندرِ سمتِ سرور (Node) فقط CPU مصرف میکرد و در لاگ خطای «[405] POST /upload»
# میداد؛ این اسپیس بیشتر از راهِ API استفاده میشود و صفحهاش بدونِ آن هم کامل کار میکند.
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True,
ssr_mode=False,
)
if __name__ == "__main__":
main() |