Spaces:
Paused
Paused
| import os | |
| import uuid | |
| import shutil | |
| import tempfile | |
| import gradio as gr | |
| import torch | |
| from moviepy.editor import VideoFileClip | |
| from pydub import AudioSegment | |
| from huggingface_hub import snapshot_download | |
| from omegaconf import OmegaConf | |
| from diffusers import AutoencoderKL, DDIMScheduler | |
| from latentsync.models.unet import UNet3DConditionModel | |
| from latentsync.pipelines.lipsync_pipeline import LipsyncPipeline | |
| from latentsync.whisper.audio2feature import Audio2Feature | |
| from accelerate.utils import set_seed | |
| AUTHOR = "Lý Trần" | |
| COMMUNITY = "LTTEAM" | |
| COMMUNITY_LINK = "https://www.facebook.com/groups/622526090937760" | |
| REPO_ID = "LTTEAM/Nhep_Mieng" | |
| os.makedirs("checkpoints", exist_ok=True) | |
| snapshot_download( | |
| repo_id=REPO_ID, | |
| local_dir="./checkpoints" | |
| ) | |
| def process_video(input_video_path: str, temp_dir: str = "temp_video") -> str: | |
| os.makedirs(temp_dir, exist_ok=True) | |
| video = VideoFileClip(input_video_path) | |
| output_path = os.path.join( | |
| temp_dir, f"crop_{os.path.basename(input_video_path)}" | |
| ) | |
| if video.duration > 10: | |
| video = video.subclip(0, 10) | |
| video.write_videofile(output_path, codec="libx264", audio_codec="aac") | |
| return output_path | |
| def process_audio(input_audio_path: str, temp_dir: str) -> str: | |
| os.makedirs(temp_dir, exist_ok=True) | |
| audio = AudioSegment.from_file(input_audio_path) | |
| max_ms = 8 * 1000 | |
| if len(audio) > max_ms: | |
| audio = audio[:max_ms] | |
| output_path = os.path.join(temp_dir, "trim_audio.wav") | |
| audio.export(output_path, format="wav") | |
| return output_path | |
| def main(video_path, audio_path, progress=gr.Progress(track_tqdm=True)): | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| print(f"[INFO] Chạy trên device: {device}") | |
| space_id = os.environ.get("SPACE_ID", "") | |
| is_shared_ui = "fffiloni/LatentSync" in space_id | |
| # Nếu chạy trên shared UI, lưu tạm và cắt ngắn đầu vào | |
| temp_dir = None | |
| if is_shared_ui: | |
| temp_dir = tempfile.mkdtemp() | |
| video_path = process_video(video_path, temp_dir) | |
| audio_path = process_audio(audio_path, temp_dir) | |
| # Nạp cấu hình và checkpoint | |
| config = OmegaConf.load("configs/unet/second_stage.yaml") | |
| unet_ckpt = "checkpoints/latentsync_unet.pt" | |
| scheduler = DDIMScheduler.from_pretrained("configs") | |
| # Chọn Whisper model dựa vào cross_attention_dim | |
| dim = config.model.cross_attention_dim | |
| if dim == 768: | |
| whisper_ckpt = "checkpoints/whisper/small.pt" | |
| elif dim == 384: | |
| whisper_ckpt = "checkpoints/whisper/tiny.pt" | |
| else: | |
| raise NotImplementedError("cross_attention_dim phải là 768 hoặc 384") | |
| # Tạo audio encoder | |
| audio_encoder = Audio2Feature( | |
| model_path=whisper_ckpt, | |
| device=device, | |
| num_frames=config.data.num_frames | |
| ) | |
| # Nạp VAE | |
| vae = AutoencoderKL.from_pretrained( | |
| "stabilityai/sd-vae-ft-mse", | |
| torch_dtype=torch.float16 if device=="cuda" else torch.float32 | |
| ) | |
| vae.config.scaling_factor = 0.18215 | |
| vae.config.shift_factor = 0 | |
| # Nạp UNet | |
| unet, _ = UNet3DConditionModel.from_pretrained( | |
| OmegaConf.to_container(config.model), | |
| unet_ckpt, | |
| device=device | |
| ) | |
| # Chuyển dtype phù hợp | |
| unet = unet.to(dtype=torch.float16) if device=="cuda" else unet.to(dtype=torch.float32) | |
| # Khởi tạo pipeline và chuyển lên device | |
| pipeline = LipsyncPipeline( | |
| vae=vae, | |
| audio_encoder=audio_encoder, | |
| unet=unet, | |
| scheduler=scheduler, | |
| ).to(device) | |
| # Thiết lập seed | |
| seed = -1 | |
| if seed != -1: | |
| set_seed(seed) | |
| else: | |
| torch.seed() | |
| print(f"[INFO] Seed khởi tạo: {torch.initial_seed()}") | |
| # Thực thi pipeline | |
| output_id = uuid.uuid4().hex | |
| result_path = f"output_{output_id}.mp4" | |
| pipeline( | |
| video_path=video_path, | |
| audio_path=audio_path, | |
| video_out_path=result_path, | |
| video_mask_path=result_path.replace(".mp4", "_mask.mp4"), | |
| num_frames=config.data.num_frames, | |
| num_inference_steps=config.run.inference_steps, | |
| guidance_scale=1.0, | |
| weight_dtype=torch.float16 if device=="cuda" else torch.float32, | |
| width=config.data.resolution, | |
| height=config.data.resolution, | |
| ) | |
| # Dọn dẹp thư mục tạm nếu có | |
| if is_shared_ui and temp_dir and os.path.exists(temp_dir): | |
| shutil.rmtree(temp_dir) | |
| return result_path | |
| custom_css = """ | |
| :root { | |
| --primary: #4CAF50; | |
| --secondary: #8BC34A; | |
| --accent: #FFC107; | |
| --dark: #1E1E1E; | |
| --light: #F5F5F5; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background-color: var(--light); | |
| } | |
| div#main-container { | |
| margin: 0 auto; | |
| max-width: 900px; | |
| background: white; | |
| padding: 2rem; | |
| border-radius: 12px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
| } | |
| h1 { | |
| color: var(--primary); | |
| border-bottom: 2px solid var(--secondary); | |
| padding-bottom: 0.5rem; | |
| } | |
| .gr-button { | |
| background: var(--primary) !important; | |
| color: white !important; | |
| border: none !important; | |
| padding: 0.75rem 1.5rem !important; | |
| border-radius: 8px !important; | |
| font-weight: 600 !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .gr-button:hover { | |
| background: var(--secondary) !important; | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important; | |
| } | |
| .gr-box { | |
| border-radius: 8px !important; | |
| border: 1px solid #e0e0e0 !important; | |
| } | |
| footer { | |
| text-align: center; | |
| margin-top: 2rem; | |
| color: #666; | |
| font-size: 0.9rem; | |
| } | |
| .example-container { | |
| background: #f9f9f9; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| margin-top: 1rem; | |
| } | |
| """ | |
| with gr.Blocks(css=custom_css, title="LatentSync - Đồng bộ môi bằng AI") as demo: | |
| with gr.Column(elem_id="main-container"): | |
| # Header | |
| gr.Markdown(f"# 🎤 LatentSync - Đồng bộ môi bằng AI") | |
| gr.Markdown(f"**Tác giả:** {AUTHOR} | **Cộng đồng:** [{COMMUNITY}]({COMMUNITY_LINK})") | |
| # Giới thiệu | |
| with gr.Accordion("ℹ️ Giới thiệu ứng dụng", open=False): | |
| gr.Markdown(""" | |
| Ứng dụng sử dụng mô hình AI tiên tiến để đồng bộ chuyển động môi trong video với âm thanh đầu vào. | |
| **Cách sử dụng:** | |
| 1. Tải lên video chứa khuôn mặt cần đồng bộ môi | |
| 2. Tải lên file âm thanh hoặc ghi âm trực tiếp | |
| 3. Nhấn nút "Chạy đồng bộ" và chờ kết quả | |
| **Lưu ý:** | |
| - Video nên có khuôn mặt rõ ràng, ánh sáng tốt | |
| - Âm thanh cần rõ ràng, không nhiễu | |
| - Thời gian xử lý phụ thuộc vào độ dài video và cấu hình máy | |
| """) | |
| # Input/Output | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### 🎥 Đầu vào") | |
| video_in = gr.Video(label="Video đầu vào (MP4)", format="mp4", interactive=True) | |
| audio_in = gr.Audio(label="Âm thanh đầu vào", type="filepath", interactive=True) | |
| with gr.Row(): | |
| btn = gr.Button("🚀 Chạy đồng bộ", variant="primary") | |
| clear_btn = gr.Button("🔄 Xóa hết") | |
| with gr.Column(): | |
| gr.Markdown("### 📼 Kết quả") | |
| video_out = gr.Video(label="Video kết quả", interactive=False) | |
| with gr.Row(): | |
| download_btn = gr.Button("💾 Tải xuống") | |
| # Ví dụ mẫu - ĐÃ SỬA LỖI Ở ĐÂY | |
| with gr.Accordion("📂 Ví dụ mẫu", open=True): | |
| gr.Examples( | |
| examples=[ | |
| ["assets/demo1_video.mp4", "assets/demo1_audio.wav"], | |
| ["assets/demo2_video.mp4", "assets/demo2_audio.wav"], | |
| ["assets/demo3_video.mp4", "assets/demo3_audio.wav"], | |
| ], | |
| inputs=[video_in, audio_in], | |
| outputs=[video_out], | |
| fn=main, # Thêm hàm xử lý chính | |
| label="Nhấn vào ví dụ để thử ngay", | |
| # cache_examples=True # Đã bỏ cache_examples vì cần thêm cấu hình | |
| ) | |
| # Footer | |
| gr.Markdown(f""" | |
| --- | |
| *Ứng dụng được phát triển bởi {AUTHOR} và cộng đồng {COMMUNITY}* | |
| *Phiên bản 1.0 | [Tham gia nhóm]({COMMUNITY_LINK}) để cập nhật và hỗ trợ* | |
| """) | |
| # Xử lý sự kiện | |
| btn.click(fn=main, inputs=[video_in, audio_in], outputs=[video_out]) | |
| clear_btn.click(lambda: [None, None, None], outputs=[video_in, audio_in, video_out]) | |
| download_btn.click(lambda x: x, inputs=[video_out], outputs=[video_out]) | |
| demo.launch( | |
| share=True, | |
| show_error=True, | |
| server_name="0.0.0.0", | |
| server_port=int(os.environ.get("PORT", 7860)), | |
| ) |