Spaces:
Running
Running
import os | |
import sys | |
import time | |
import subprocess | |
import argparse | |
import multiprocessing as mp | |
# Don't import FILM module globally - we'll import the specific function when needed | |
mp.set_start_method("spawn", force=True) # see if buggy | |
def parse_arguments(): | |
parser = argparse.ArgumentParser( | |
description="Orchestrate DiffMorpher || LCM-LoRa || LCM, and FILM for smooth morphing between two images.") | |
# ------------------- DIFFMORPHER ARGS ------------------- | |
parser.add_argument( | |
"--model_path", type=str, default="stabilityai/stable-diffusion-2-1-base", | |
help="Pretrained model to use for DiffMorpher (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--image_path_0", type=str, required=True, | |
help="Path of the first image" | |
) | |
parser.add_argument( | |
"--prompt_0", type=str, default="", | |
help="Prompt describing the first image (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--image_path_1", type=str, required=True, | |
help="Path of the second image" | |
) | |
parser.add_argument( | |
"--prompt_1", type=str, default="", | |
help="Prompt describing the second image (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--output_path", type=str, default="./results", | |
help="Output folder for DiffMorpher keyframes/gif (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--save_lora_dir", type=str, default="./lora", | |
help="Directory to save LoRA outputs (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--load_lora_path_0", type=str, default="", | |
help="Path to LoRA checkpoint for image 0 (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--load_lora_path_1", type=str, default="", | |
help="Path to LoRA checkpoint for image 1 (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--use_adain", action="store_true", | |
help="Use AdaIN in DiffMorpher pipeline" | |
) | |
parser.add_argument( | |
"--use_reschedule", action="store_true", | |
help="Use reschedule sampling in DiffMorpher" | |
) | |
parser.add_argument( | |
"--lamb", type=float, default=0.6, | |
help="Lambda for self-attention replacement in DiffMorpher (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--fix_lora_value", type=float, default=None, | |
help="Fix LoRA value in DiffMorpher (default: LoRA interpolation)" | |
) | |
parser.add_argument( | |
"--save_inter", action="store_true", | |
help="Save intermediate frames as individual images (e.g. .png) in DiffMorpher" | |
) | |
parser.add_argument( | |
"--num_frames", type=int, default=16, | |
help="Number of keyframes to generate (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--duration", type=int, default=100, | |
help="Duration of each keyframe in the final .gif (default: %(default)s ms)" | |
) | |
parser.add_argument( | |
"--no_lora", action="store_true", | |
help="Disable LoRA usage in DiffMorpher" | |
) | |
parser.add_argument( | |
"--use_lcm", action="store_true", | |
help="Enable LCM-LoRA acceleration for faster sampling" | |
) | |
# ------------------- FILM ARGS ------------------- | |
parser.add_argument( | |
"--use_film", action="store_true", | |
help="Flag to indicate whether to run FILM after generating keyframes" | |
) | |
parser.add_argument( | |
"--film_input_folder", type=str, default="", | |
help="Folder containing keyframes for FILM. If empty, will use DiffMorpher output folder." | |
) | |
parser.add_argument( | |
"--film_output_folder", type=str, default="./FILM_Results", | |
help="Folder where FILM's final interpolated video is saved (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--film_fps", type=int, default=30, | |
help="FPS for the final video - 'Pseudo-Playback-Speed', since total frames are same (default: %(default)s)" | |
) | |
parser.add_argument( | |
"--film_num_recursions", type=int, default=3, | |
help="Number of recursive interpolations to perform in FILM (default: %(default)s)" | |
) | |
return parser.parse_args() | |
def run_diffmorpher(args): | |
""" | |
Calls DiffMorpher's main.py via subprocess using the CLI arguments. | |
Expects `Image-Morpher/` to be a submodule in the current repo. | |
""" | |
diffmorpher_script = os.path.join("Image-Morpher", "main.py") | |
cmd = [ | |
sys.executable, diffmorpher_script, | |
"--model_path", args.model_path, | |
"--image_path_0", args.image_path_0, | |
"--prompt_0", args.prompt_0, | |
"--image_path_1", args.image_path_1, | |
"--prompt_1", args.prompt_1, | |
"--output_path", args.output_path, | |
"--save_lora_dir", args.save_lora_dir, | |
"--lamb", str(args.lamb), | |
"--num_frames", str(args.num_frames), | |
"--duration", str(args.duration), | |
] | |
if args.load_lora_path_0: | |
cmd += ["--load_lora_path_0", args.load_lora_path_0] | |
if args.load_lora_path_1: | |
cmd += ["--load_lora_path_1", args.load_lora_path_1] | |
if args.use_adain: | |
cmd.append("--use_adain") | |
if args.use_reschedule: | |
cmd.append("--use_reschedule") | |
if args.fix_lora_value is not None: | |
cmd += ["--fix_lora_value", str(args.fix_lora_value)] | |
if args.no_lora: | |
cmd.append("--no_lora") | |
# ---- Always add --save_inter to ensure keyframes are saved ---- | |
cmd.append("--save_inter") | |
# ---- Add LCM-LoRA flag if set ---- | |
if args.use_lcm: | |
cmd.append("--use_lcm") | |
print("[INFO] Running DiffMorpher with command:") | |
print(" ".join(cmd)) | |
start = time.time() | |
subprocess.run(cmd, check=True) | |
end = time.time() | |
print(f"[INFO] DiffMorpher completed in {end - start:.2f} seconds.") | |
def create_simple_video_from_keyframes(keyframes_folder, output_folder, fps=40): | |
""" | |
If the user does NOT want FILM, we still make a basic video from keyframes. | |
Assumes frames are saved as .png or .jpg in keyframes_folder. | |
""" | |
import cv2 | |
from glob import glob | |
from datetime import datetime | |
os.makedirs(output_folder, exist_ok=True) | |
images = sorted(glob(os.path.join(keyframes_folder, "*.png"))) | |
if not images: | |
images = sorted(glob(os.path.join(keyframes_folder, "*.jpg"))) | |
if not images: | |
print(f"[WARN] No .png or .jpg frames found in {keyframes_folder}.") | |
return | |
# Prepare video writer | |
first_frame = cv2.imread(images[0]) | |
height, width, _ = first_frame.shape | |
fourcc = cv2.VideoWriter_fourcc(*'mp4v') | |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
out_video_path = os.path.join(output_folder, f"simple_morph_{timestamp}.mp4") | |
out = cv2.VideoWriter(out_video_path, fourcc, fps, (width, height)) | |
for img_path in images: | |
frame = cv2.imread(img_path) | |
out.write(frame) | |
out.release() | |
print(f"[INFO] Basic morphing video saved at: {out_video_path}") | |
def run_film_interpolation(input_folder, output_folder, fps, num_recursions): | |
""" | |
Import and run FILM processing only when needed. | |
This function is called only if args.use_film is True. | |
""" | |
# Import the process_keyframes function from FILM.py only when needed | |
from FILM import process_keyframes | |
# Now run the FILM processing | |
return process_keyframes( | |
input_folder=input_folder, | |
output_folder=output_folder, | |
fps=fps, | |
num_recursions=num_recursions | |
) | |
def main(): | |
args = parse_arguments() | |
overall_start_time = time.time() | |
# 1) Run DiffMorpher to generate keyframes | |
run_diffmorpher(args) | |
# 2) Determine the folder containing the keyframes | |
# If user didn't explicitly give `--film_input_folder`, use `args.output_path` | |
keyframes_folder = args.film_input_folder if args.film_input_folder else args.output_path | |
# 3) If user wants to use FILM, perform high-quality interpolation on the keyframes | |
if args.use_film: | |
print("[INFO] Running FILM to enhance the keyframes...") | |
start_film_time = time.time() | |
# Call the wrapper function that imports FILM only when needed | |
success = run_film_interpolation( | |
input_folder=keyframes_folder, | |
output_folder=args.film_output_folder, | |
fps=args.film_fps, | |
num_recursions=args.film_num_recursions | |
) | |
end_film_time = time.time() | |
if success: | |
print(f"[INFO] FILM interpolation completed in {end_film_time - start_film_time:.2f} seconds.") | |
else: | |
print("[ERROR] FILM interpolation failed. See above for details.") | |
else: | |
# 4) If user does NOT want FILM, create a simple .mp4 from the keyframes | |
print("[INFO] Skipping FILM interpolation. Creating a basic video from DiffMorpher keyframes...") | |
create_simple_video_from_keyframes( | |
keyframes_folder=keyframes_folder, | |
output_folder=args.film_output_folder, | |
fps=args.film_fps | |
) | |
# 5) Print total execution time | |
overall_end_time = time.time() | |
print(f"[INFO] Entire pipeline completed in {overall_end_time - overall_start_time:.2f} seconds.") | |
if __name__ == "__main__": | |
main() |