# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import os from omegaconf import OmegaConf import torch from diffusers import AutoencoderKL, DDIMScheduler from latentsync.models.unet import UNet3DConditionModel from latentsync.pipelines.lipsync_pipeline import LipsyncPipeline from accelerate.utils import set_seed from latentsync.whisper.audio2feature import Audio2Feature def main(config, args): if not os.path.exists(args.video_path): raise RuntimeError(f"Video path '{args.video_path}' not found") if not os.path.exists(args.audio_path): raise RuntimeError(f"Audio path '{args.audio_path}' not found") # Check if the GPU supports float16 is_fp16_supported = torch.cuda.is_available() and torch.cuda.get_device_capability()[0] > 7 dtype = torch.float16 if is_fp16_supported else torch.float32 print(f"Input video path: {args.video_path}") print(f"Input audio path: {args.audio_path}") print(f"Loaded checkpoint path: {args.inference_ckpt_path}") # Use relative path for scheduler configuration current_dir = os.path.dirname(os.path.abspath(__file__)) scheduler_path = os.path.join(current_dir, "..", "configs", "scheduler") # Check if scheduler directory exists if not os.path.exists(scheduler_path): print(f"Creating scheduler directory at {scheduler_path}") os.makedirs(scheduler_path, exist_ok=True) # Create scheduler config file if it doesn't exist scheduler_config_file = os.path.join(scheduler_path, "scheduler_config.json") config_file = os.path.join(scheduler_path, "config.json") if not os.path.exists(scheduler_config_file): # Default scheduler config scheduler_config = { "_class_name": "DDIMScheduler", "beta_end": 0.012, "beta_schedule": "scaled_linear", "beta_start": 0.00085, "clip_sample": False, "num_train_timesteps": 1000, "set_alpha_to_one": False, "steps_offset": 1, "trained_betas": None, "skip_prk_steps": True } import json with open(scheduler_config_file, 'w') as f: json.dump(scheduler_config, f, indent=2) # Also create a copy as config.json for compatibility with open(config_file, 'w') as f: json.dump(scheduler_config, f, indent=2) print(f"Loading scheduler from: {scheduler_path}") try: scheduler = DDIMScheduler.from_pretrained(scheduler_path) except Exception as e: print(f"Error loading scheduler: {e}") # Fallback to creating scheduler directly scheduler = DDIMScheduler( beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", clip_sample=False, set_alpha_to_one=False, steps_offset=1, skip_prk_steps=True ) # Use relative paths for whisper models as well if config.model.cross_attention_dim == 768: whisper_model_path = os.path.join(current_dir, "..", "checkpoints", "whisper", "small.pt") elif config.model.cross_attention_dim == 384: whisper_model_path = os.path.join(current_dir, "..", "checkpoints", "whisper", "tiny.pt") else: raise NotImplementedError("cross_attention_dim must be 768 or 384") audio_encoder = Audio2Feature( model_path=whisper_model_path, device="cuda", num_frames=config.data.num_frames, audio_feat_length=config.data.audio_feat_length, ) vae = AutoencoderKL.from_pretrained("stabilityai/sd-vae-ft-mse", torch_dtype=dtype) vae.config.scaling_factor = 0.18215 vae.config.shift_factor = 0 denoising_unet, _ = UNet3DConditionModel.from_pretrained( OmegaConf.to_container(config.model), args.inference_ckpt_path, device="cpu", ) denoising_unet = denoising_unet.to(dtype=dtype) pipeline = LipsyncPipeline( vae=vae, audio_encoder=audio_encoder, denoising_unet=denoising_unet, scheduler=scheduler, ).to("cuda") if args.seed != -1: set_seed(args.seed) else: torch.seed() print(f"Initial seed: {torch.initial_seed()}") pipeline( video_path=args.video_path, audio_path=args.audio_path, video_out_path=args.video_out_path, video_mask_path=args.video_out_path.replace(".mp4", "_mask.mp4"), num_frames=config.data.num_frames, num_inference_steps=args.inference_steps, guidance_scale=args.guidance_scale, weight_dtype=dtype, width=config.data.resolution, height=config.data.resolution, mask_image_path=config.data.mask_image_path, ) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--unet_config_path", type=str, default="configs/unet.yaml") parser.add_argument("--inference_ckpt_path", type=str, required=True) parser.add_argument("--video_path", type=str, required=True) parser.add_argument("--audio_path", type=str, required=True) parser.add_argument("--video_out_path", type=str, required=True) parser.add_argument("--inference_steps", type=int, default=20) parser.add_argument("--guidance_scale", type=float, default=1.0) parser.add_argument("--seed", type=int, default=1247) args = parser.parse_args() config = OmegaConf.load(args.unet_config_path) main(config, args)