Spaces:
Build error
Build error
| import huggingface_hub | |
| # 核心修補:Monkey Patch,解決新版 huggingface_hub 缺失 cached_download 的問題 | |
| if not hasattr(huggingface_hub, 'cached_download'): | |
| from huggingface_hub import hf_hub_download | |
| huggingface_hub.cached_download = hf_hub_download | |
| import os | |
| import sys | |
| import subprocess | |
| import threading | |
| import time | |
| import shutil | |
| import gradio as gr | |
| from huggingface_hub import snapshot_download, HfApi | |
| from omegaconf import OmegaConf | |
| import torch | |
| cuda_available = torch.cuda.is_available() | |
| print(f"Is CUDA available: {cuda_available}") | |
| CHAMP_MASTER_DIR = "champ-master" | |
| # 🌟 隔離區宣告:前處理專用房間的路徑 | |
| VENV_DIR = os.path.abspath("preproc_env") | |
| VENV_PYTHON = os.path.join(VENV_DIR, "bin", "python") | |
| # ========================================================================= | |
| # 🏗️ [Runtime 環境防禦與隔離區架設] | |
| # ========================================================================= | |
| print("\n🏗️ [環境建置] 開始架設前處理隔離特區...") | |
| # 1. 從模型庫同步組員的前處理專案 | |
| checkpoint_script = os.path.join(CHAMP_MASTER_DIR, "process_single_video.py") | |
| if not os.path.exists(checkpoint_script): | |
| print(f"⏳ 偵測到 Space 本地缺乏前處理,正在從模型庫同步中...") | |
| try: | |
| os.makedirs(CHAMP_MASTER_DIR, exist_ok=True) | |
| snapshot_download( | |
| repo_id="yoyozs11/champ_preprocess", | |
| local_dir=CHAMP_MASTER_DIR, | |
| repo_type="model" | |
| ) | |
| print("✅ 前處理模組同步成功!") | |
| except Exception as e: | |
| print(f"❌ 同步前處理模組失敗: {e}") | |
| # 2. 🌟【核心關鍵:蓋隔離房間】自動建立前處理專用的獨立虛擬環境 | |
| if not os.path.exists(VENV_DIR): | |
| print(f"📦 正在為組員程式碼建立專屬隔離環境 (venv) 於: {VENV_DIR}...") | |
| try: | |
| # 使用系統 Python 創建一個完全乾淨隔離的子資料夾房間 | |
| subprocess.run([sys.executable, "-m", "venv", VENV_DIR], check=True) | |
| print("⏳ 正在隔離環境內安裝舊版 NumPy 與 4D-Humans 連鎖依賴套件...") | |
| # 🎯 在隔離房間(preproc_env)內安裝前處理需要的所有套件 | |
| # 這裡精確對齊了你提供的前處理 requirements.txt 清單,徹底解決 cv2 等缺失問題 | |
| preprocess_deps = [ | |
| "pip", "install", "--upgrade", "pip", "setuptools", "wheel", | |
| "numpy==1.23.5", # 強制鎖定 numpy 1.x 版,修復 numpy._core 錯誤 | |
| "opencv-python-headless", # 解決 cv2 找不到的問題 | |
| "torch==2.2.2", "torchvision==0.17.2", # 補上探測器(Detector)與核心張量庫 | |
| "pytorch-lightning==2.1.0", "gdown", "webdataset", | |
| "pandas", "scikit-image", "trimesh", "smplx==0.1.28", "chumpy", | |
| "timm", "einops", "yacs", "onnxruntime-gpu", "pillow", "tqdm", "pyrender" | |
| ] | |
| subprocess.run([VENV_PYTHON, "-m"] + preprocess_deps, check=True) | |
| print("✅ 前處理專屬隔離區套件配置大成功!") | |
| except Exception as e: | |
| print(f"❌ 隔離環境建立或套件安裝失敗: {e}") | |
| # 3. 對齊 Blender 軟連結 (對應 Dockerfile) | |
| blender_target_path = os.path.join(CHAMP_MASTER_DIR, "blender") | |
| if os.path.exists("blender") and not os.path.exists(blender_target_path): | |
| try: | |
| os.symlink(os.path.abspath("blender"), blender_target_path) | |
| print("✅ Blender 軟連結架設成功,完美對齊隔離前處理路徑!") | |
| except Exception as e: | |
| print(f"⚠️ Blender 軟連結對齊失敗: {e}") | |
| # 4. 自動下載 Champ 推論核心權重 (外面環境維持原本的最新狀態) | |
| PRETRAINED_MODELS_DIR = "pretrained_models" | |
| if not os.path.exists(PRETRAINED_MODELS_DIR) or len(os.listdir(PRETRAINED_MODELS_DIR)) < 4: | |
| print("⏳ 正在從 Hugging Face 官方庫下載 Champ 推論核心權重...") | |
| try: | |
| snapshot_download( | |
| repo_id="fudan-generative-ai/champ", | |
| local_dir=PRETRAINED_MODELS_DIR, | |
| repo_type="model", | |
| ignore_patterns=[".git*", "README.md"] | |
| ) | |
| print("✅ Champ 核心推論權重下載成功。") | |
| except Exception as e: | |
| print(f"❌ 核心權重下載失敗: {e}") | |
| print("🚀 [初始化完畢] 隔離區與外圍系統全線解耦串聯就緒,Gradio 即將點亮!\n") | |
| # ========================================================================= | |
| # 🔒 自毀防禦計時器 | |
| # ========================================================================= | |
| def delayed_pause_space(repo_id, delay_seconds=180): | |
| print(f"🤖 [背景防禦線] 自毀計畫已啟動,倒數 {delay_seconds} 秒後將自動關機...") | |
| time.sleep(delay_seconds) | |
| hf_token = os.getenv("HF_TOKEN") | |
| if hf_token: | |
| try: | |
| api = HfApi(token=hf_token) | |
| api.pause_space(repo_id=repo_id) | |
| print("💤 [背景防禦線] Space 已進入安全休眠狀態。") | |
| except Exception: pass | |
| # ========================================================================= | |
| # 5. 全新絕對沙盒防禦對齊版 Gradio Trigger Function (環境路徑劫持版) | |
| # ========================================================================= | |
| def gradio_predict(input_image, input_video, start_frame, end_frame): | |
| import shutil # 引入實體檔案搬運工 | |
| if not input_image or not input_video or not isinstance(input_video, str): | |
| return "❌ 錯誤:請確保正確上傳了參考人像與動作影片!", None, None | |
| # ------------------------------------------------------------------------- | |
| # 🛡️ 實體防線:既然組員腳本硬要去讀 /app/champ-master/images,我們就直接蓋在它要的地方! | |
| # ------------------------------------------------------------------------- | |
| custom_ref_root = os.path.abspath(CHAMP_MASTER_DIR) | |
| custom_ref_images = os.path.join(custom_ref_root, "images") | |
| if os.path.exists(custom_ref_images): | |
| shutil.rmtree(custom_ref_images) | |
| os.makedirs(custom_ref_images, exist_ok=True) | |
| target_image_path = os.path.join(custom_ref_images, "0001.png") | |
| try: | |
| shutil.copy(input_image, target_image_path) | |
| print(f"🎯 [物理降維打擊成功] 參考圖已直接入駐組員大本營: {target_image_path}") | |
| except Exception as e: | |
| return f"❌ 參考圖絕對沙盒配置失敗: {e}", None, None | |
| # ------------------------------------------------------------------------- | |
| print("\n" + "="*50) | |
| print("🎬 [階段 1] 啟動隔離房間內的前處理執行組...") | |
| print("="*50) | |
| try: | |
| script_name = "process_single_video.py" | |
| # 🌟 核心破關線:複製當前的環境變數,並進行「路徑劫持」 | |
| env = os.environ.copy() | |
| # 1. 把隔離房間的 bin 夾(裡面裝有我們剛裝好的 python)強行插到 Linux PATH 的最前面! | |
| # 這樣組員腳本在裡面不論呼叫幾百次 python,全部都會被強行導流進隔離房間! | |
| venv_bin_dir = os.path.join(VENV_DIR, "bin") | |
| env["PATH"] = f"{venv_bin_dir}:{env.get('PATH', '')}" | |
| # 2. 同步強注原始碼白名單 | |
| master_path = os.path.abspath(CHAMP_MASTER_DIR) | |
| fourd_path = os.path.abspath(os.path.join(CHAMP_MASTER_DIR, "4D-Humans")) | |
| env["PYTHONPATH"] = f"{master_path}:{fourd_path}:{env.get('PYTHONPATH', '')}" | |
| # 3. 呼叫隔離房間的 Python 啟動前處理主程式 | |
| preprocess_cmd = [ | |
| VENV_PYTHON, script_name, | |
| "--video", os.path.abspath(input_video), | |
| "--ref", "./" | |
| ] | |
| subprocess.run(preprocess_cmd, check=True, cwd=CHAMP_MASTER_DIR, env=env) | |
| print("✅ 隔離區前處理連鎖通車成功!") | |
| except Exception as e: | |
| return f"❌ 前處理隔離階段發生錯誤: {e}", None, None | |
| # 動態計算前處理生成的資料夾路徑 | |
| video_filename = os.path.basename(input_video) | |
| video_name, _ = os.path.splitext(video_filename) | |
| computed_guidance_folder = os.path.join(CHAMP_MASTER_DIR, "transferd_result", video_name) | |
| if not os.path.exists(computed_guidance_folder): | |
| backup_folder = os.path.join(CHAMP_MASTER_DIR, "transferd_result") | |
| if os.path.exists(backup_folder) and len(os.listdir(backup_folder)) > 0: | |
| computed_guidance_folder = backup_folder | |
| print(f"🔍 最終定位條件影像資料夾: {computed_guidance_folder}") | |
| if not os.path.exists(computed_guidance_folder): | |
| return f"❌ 錯誤:找不到前處理生成目錄 {computed_guidance_folder}", None, None | |
| # 外圍主要大腦(Champ 推理層)路徑對齊 | |
| current_dir = os.path.dirname(os.path.abspath(__file__)) | |
| if current_dir not in sys.path: sys.path.insert(0, current_dir) | |
| if master_abs not in sys.path: sys.path.insert(0, master_abs) | |
| try: | |
| from inference import main as run_champ_inference | |
| except Exception as e: | |
| return f"❌ 載入 inference.py 失敗: {e}", None, None | |
| config = OmegaConf.load(os.path.join("configs", "inference", "inference.yaml")) | |
| config.data.ref_image_path = os.path.abspath(target_image_path) | |
| config.data.guidance_data_folder = os.path.abspath(computed_guidance_folder) | |
| config.data.frame_range = [int(start_frame), int(end_frame)] | |
| try: | |
| print("🚀 [階段 2] 外圍核心啟動擴散模型推理...") | |
| run_champ_inference(config) | |
| video_animation, video_grid, video_grid_wguidance = None, None, None | |
| results_dir = "results" | |
| if os.path.exists(results_dir): | |
| subdirs = [os.path.join(results_dir, d) for d in os.listdir(results_dir) if os.path.isdir(os.path.join(results_dir, d))] | |
| if subdirs: | |
| latest_dir = max(subdirs, key=os.path.getmtime) | |
| if os.path.exists(os.path.join(latest_dir, "animation.mp4")): video_animation = os.path.join(latest_dir, "animation.mp4") | |
| if os.path.exists(os.path.join(latest_dir, "grid.mp4")): video_grid = os.path.join(latest_dir, "grid.mp4") | |
| if os.path.exists(os.path.join(latest_dir, "grid_wguidance.mp4")): video_grid_wguidance = os.path.join(latest_dir, "grid_wguidance.mp4") | |
| threading.Thread(target=delayed_pause_space, args=("yoyozs11/champ_demo", 180), daemon=True).start() | |
| return video_animation, video_grid, video_grid_wguidance | |
| except Exception as e: | |
| return f"❌ 推論階段發生崩潰,錯誤訊息: {e}", None, None | |
| # ========================================== | |
| # 6. Gradio Web UI 佈局 | |
| # ========================================== | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Champ 3D 動作生成系統 (高階獨立虛擬環境隔離版)") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| input_img = gr.Image(type="filepath", label="1. 上傳人像圖片") | |
| input_vid = gr.Video(label="2. 上傳自訂動作影片 (MP4)", format="mp4") | |
| with gr.Row(): | |
| start_f = gr.Number(value=0, label="動作起始幀", precision=0) | |
| end_f = gr.Number(value=20, label="動作結束幀", precision=0) | |
| btn = gr.Button("開始前處理與影片生成", variant="primary") | |
| with gr.Column(scale=1): | |
| output_video_anim = gr.Video(label="3-1. 純動作生成結果") | |
| output_video_grid = gr.Video(label="3-2. 人像與結果對照組") | |
| output_video_grid_wguid = gr.Video(label="3-3. 完整骨架引導對照組") | |
| btn.click( | |
| fn=gradio_predict, | |
| inputs=[input_img, input_vid, start_f, end_f], | |
| outputs=[output_video_anim, output_video_grid, output_video_grid_wguid] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Default()) |