Metamorph / run_morphing.py
nalin0503's picture
Add updated files from main repo
8182d33
raw
history blame
9.24 kB
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()