File size: 8,966 Bytes
963b1a5
 
 
 
 
3f0cc27
8182d33
 
 
3f0cc27
963b1a5
 
 
 
 
 
bcf0a9f
963b1a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad5b4b7
 
963b1a5
 
 
 
 
bcf0a9f
 
ca501e3
 
963b1a5
 
 
 
 
 
 
 
 
 
 
 
8182d33
963b1a5
 
 
 
 
 
 
 
 
 
 
ca501e3
963b1a5
ca501e3
963b1a5
 
 
 
 
 
 
 
 
 
 
ad5b4b7
963b1a5
 
 
 
 
 
 
 
 
 
 
 
 
 
3fe375f
 
ca501e3
 
 
963b1a5
 
 
 
 
 
 
 
 
ad5b4b7
963b1a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8182d33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
963b1a5
 
 
 
 
 
 
 
8182d33
963b1a5
 
 
 
 
 
8182d33
 
 
963b1a5
 
ad5b4b7
963b1a5
 
8182d33
963b1a5
8182d33
 
 
 
963b1a5
 
 
 
 
 
ad5b4b7
963b1a5
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
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(
        "--fps", type=int, default=30,
        help="FPS for the output video (default: %(default)s)"
    )
    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_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)
    ]

    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):
    """
    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.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.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()