nalin0503 commited on
Commit
8182d33
·
1 Parent(s): 57d3f65

Add updated files from main repo

Browse files
Files changed (4) hide show
  1. FILM.py +49 -23
  2. Image-Morpher/main.py +7 -7
  3. app.py +143 -100
  4. run_morphing.py +30 -10
FILM.py CHANGED
@@ -19,15 +19,20 @@ Adjust 'fps' and 'num_recursions' parameters as needed
19
  """
20
  import os
21
  import tensorflow as tf
22
- import tensorflow_hub as hub
23
  import cv2
24
  import numpy as np
25
  from glob import glob
26
  from datetime import datetime
27
  import time
 
28
 
29
- # Load the FILM model
30
- model = hub.load('https://tfhub.dev/google/film/1')
 
 
 
 
 
31
 
32
  def preprocess_image(image_path):
33
  """Load and preprocess an image for the FILM model."""
@@ -38,7 +43,7 @@ def preprocess_image(image_path):
38
 
39
  class Interpolator:
40
  """Wrapper class for the FILM model to perform frame interpolation."""
41
- def __init__(self, align=64):
42
  self._model = model
43
  self._align = align
44
 
@@ -53,33 +58,43 @@ def _recursive_generator(frame1, frame2, num_recursions, interpolator):
53
  if num_recursions == 0:
54
  yield frame1 # exit condition
55
  else:
56
- dt_value = np.full(shape=(1,), fill_value=0.5, dtype=np.float32)
57
  mid_frame = interpolator(
58
- np.expand_dims(frame1, axis=0), np.expand_dims(frame2, axis=0), dt_value)[0]
59
  yield from _recursive_generator(frame1, mid_frame, num_recursions - 1, interpolator) # 1st half
60
  yield from _recursive_generator(mid_frame, frame2, num_recursions - 1, interpolator) # 2nd half
61
 
62
  def interpolate_recursively(frames, num_recursions, interpolator):
63
  """Apply recursive interpolation to a list of input frames."""
64
  n = len(frames)
65
- if n == 0:
66
- raise ValueError("No input frames found.")
67
  for i in range(1, n):
68
  yield from _recursive_generator(frames[i - 1], frames[i], num_recursions, interpolator)
69
  yield frames[-1]
70
 
71
  def process_keyframes(input_folder, output_folder, fps=30, num_recursions=3):
72
  """Process keyframes to create an interpolated video, using functions above"""
 
 
 
 
 
 
73
  keyframes = sorted(glob(os.path.join(input_folder, '*.png')))
74
  if not keyframes:
75
- raise ValueError(f"No PNG keyframes found in {input_folder}")
76
-
77
- # Ensure the output folder exists
78
- os.makedirs(output_folder, exist_ok=True)
 
 
 
 
 
 
79
 
80
  frames = [preprocess_image(frame).numpy() for frame in keyframes]
81
 
82
- interpolator = Interpolator()
83
  interpolated_frames = list(interpolate_recursively(frames, num_recursions, interpolator))
84
 
85
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") # For unique output..
@@ -97,14 +112,25 @@ def process_keyframes(input_folder, output_folder, fps=30, num_recursions=3):
97
 
98
  out.release()
99
  print(f'Video created with {len(interpolated_frames)} frames: {output_video}')
 
100
 
101
- # Usage
102
- # input_folder = 'sample_keyframes'
103
- # output_folder = 'FILM_Results'
104
-
105
- # start_time = time.time()
106
- # process_keyframes(input_folder, output_folder, fps=30, num_recursions=3)
107
- # end_time = time.time()
108
-
109
- # total_execution_time = end_time - start_time
110
- # print(f'Total script execution time: {total_execution_time:.2f} seconds')
 
 
 
 
 
 
 
 
 
 
 
19
  """
20
  import os
21
  import tensorflow as tf
 
22
  import cv2
23
  import numpy as np
24
  from glob import glob
25
  from datetime import datetime
26
  import time
27
+ import sys
28
 
29
+ def load_film_model():
30
+ """Loads the FILM model only when called explicitly."""
31
+ print("Loading FILM model...")
32
+ import tensorflow_hub as hub
33
+ model = hub.load('https://tfhub.dev/google/film/1')
34
+ print("FILM model loaded successfully.")
35
+ return model
36
 
37
  def preprocess_image(image_path):
38
  """Load and preprocess an image for the FILM model."""
 
43
 
44
  class Interpolator:
45
  """Wrapper class for the FILM model to perform frame interpolation."""
46
+ def __init__(self, model, align=64):
47
  self._model = model
48
  self._align = align
49
 
 
58
  if num_recursions == 0:
59
  yield frame1 # exit condition
60
  else:
61
+ time = np.full(shape=(1,), fill_value=0.5, dtype=np.float32)
62
  mid_frame = interpolator(
63
+ np.expand_dims(frame1, axis=0), np.expand_dims(frame2, axis=0), time)[0]
64
  yield from _recursive_generator(frame1, mid_frame, num_recursions - 1, interpolator) # 1st half
65
  yield from _recursive_generator(mid_frame, frame2, num_recursions - 1, interpolator) # 2nd half
66
 
67
  def interpolate_recursively(frames, num_recursions, interpolator):
68
  """Apply recursive interpolation to a list of input frames."""
69
  n = len(frames)
 
 
70
  for i in range(1, n):
71
  yield from _recursive_generator(frames[i - 1], frames[i], num_recursions, interpolator)
72
  yield frames[-1]
73
 
74
  def process_keyframes(input_folder, output_folder, fps=30, num_recursions=3):
75
  """Process keyframes to create an interpolated video, using functions above"""
76
+ # Check if input folder exists
77
+ if not os.path.exists(input_folder):
78
+ print(f"Error: Input folder '{input_folder}' does not exist.")
79
+ return False
80
+
81
+ # Check if input folder contains PNG files
82
  keyframes = sorted(glob(os.path.join(input_folder, '*.png')))
83
  if not keyframes:
84
+ print(f"Error: No PNG files found in '{input_folder}'.")
85
+ return False
86
+
87
+ # Create output folder if it doesn't exist
88
+ if not os.path.exists(output_folder):
89
+ print(f"Creating output folder: '{output_folder}'")
90
+ os.makedirs(output_folder)
91
+
92
+ # Only load the FILM model when needed
93
+ model = load_film_model()
94
 
95
  frames = [preprocess_image(frame).numpy() for frame in keyframes]
96
 
97
+ interpolator = Interpolator(model)
98
  interpolated_frames = list(interpolate_recursively(frames, num_recursions, interpolator))
99
 
100
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") # For unique output..
 
112
 
113
  out.release()
114
  print(f'Video created with {len(interpolated_frames)} frames: {output_video}')
115
+ return True
116
 
117
+ # Main execution
118
+ if __name__ == "__main__":
119
+ # Usage
120
+ input_folder = 'results/Trump_Biden_New'
121
+ output_folder = 'FILM_Results'
122
+
123
+ print(f"Starting FILM video interpolation process...")
124
+ print(f"Input folder: {input_folder}")
125
+ print(f"Output folder: {output_folder}")
126
+
127
+ start_time = time.time()
128
+ success = process_keyframes(input_folder, output_folder, fps=30, num_recursions=3)
129
+ end_time = time.time()
130
+
131
+ if success:
132
+ total_execution_time = end_time - start_time
133
+ print(f'Total script execution time: {total_execution_time:.2f} seconds')
134
+ else:
135
+ print("Interpolation process failed. Please check the error messages above.")
136
+ sys.exit(1)
Image-Morpher/main.py CHANGED
@@ -11,11 +11,11 @@ import time
11
  import logging
12
  import gc
13
 
14
- os.environ["HF_HOME"] = "/app/hf_cache"
15
- os.environ["DIFFUSERS_CACHE"] = "/app/hf_cache"
16
- os.environ["TORCH_HOME"] = "/app/torch_cache"
17
- os.environ["TRANSFORMERS_CACHE"] = "/app/hf_cache"
18
- os.environ["HF_DATASETS_CACHE"] = "/app/hf_cache/datasets"
19
 
20
  logs_folder = "logs"
21
  os.makedirs(logs_folder, exist_ok=True)
@@ -102,8 +102,8 @@ args = parser.parse_args()
102
  os.makedirs(args.output_path, exist_ok=True)
103
 
104
  # Clear any existing PyTorch GPU allocations
105
- torch.cuda.empty_cache()
106
- gc.collect()
107
 
108
  # Set environment variable for memory allocation
109
  os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
 
11
  import logging
12
  import gc
13
 
14
+ # os.environ["HF_HOME"] = "/app/hf_cache"
15
+ # os.environ["DIFFUSERS_CACHE"] = "/app/hf_cache"
16
+ # os.environ["TORCH_HOME"] = "/app/torch_cache"
17
+ # os.environ["TRANSFORMERS_CACHE"] = "/app/hf_cache"
18
+ # os.environ["HF_DATASETS_CACHE"] = "/app/hf_cache/datasets"
19
 
20
  logs_folder = "logs"
21
  os.makedirs(logs_folder, exist_ok=True)
 
102
  os.makedirs(args.output_path, exist_ok=True)
103
 
104
  # Clear any existing PyTorch GPU allocations
105
+ # torch.cuda.empty_cache()
106
+ # gc.collect()
107
 
108
  # Set environment variable for memory allocation
109
  os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
app.py CHANGED
@@ -1,6 +1,3 @@
1
- """
2
- Cleaned up version, Close-to-Final UI features and functionality logic.
3
- """
4
  import os
5
  import sys
6
  import subprocess
@@ -11,8 +8,6 @@ from io import BytesIO
11
  import streamlit as st
12
  from PIL import Image
13
 
14
- print(subprocess.check_output(["nvidia-smi"]).decode())
15
-
16
  # Set Streamlit page configuration (centered content via CSS)
17
  st.set_page_config(
18
  page_title="Metamorph: DiffMorpher + LCM-LoRA + FILM",
@@ -21,14 +16,26 @@ st.set_page_config(
21
  )
22
 
23
  def save_uploaded_file(uploaded_file, dst_path):
 
24
  with open(dst_path, "wb") as f:
25
  f.write(uploaded_file.getbuffer())
26
 
27
  def get_img_as_base64(img):
 
28
  buffered = BytesIO()
29
  img.save(buffered, format="PNG")
30
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
31
 
 
 
 
 
 
 
 
 
 
 
32
  def main():
33
  # ---------------- CUSTOM CSS FOR A PROFESSIONAL, DARK THEME ----------------
34
  st.markdown(
@@ -118,6 +125,13 @@ def main():
118
  unsafe_allow_html=True
119
  )
120
 
 
 
 
 
 
 
 
121
  # ---------------- HEADER & LOGO ----------------
122
  logo_path = "metamorphLogo_nobg.png"
123
  if os.path.exists(logo_path):
@@ -132,8 +146,8 @@ def main():
132
  """,
133
  unsafe_allow_html=True
134
  )
135
- except Exception:
136
- pass
137
 
138
  st.markdown("<h1 class='header-title'>Metamorph Web App</h1>", unsafe_allow_html=True)
139
  st.markdown(
@@ -155,14 +169,12 @@ def main():
155
  st.markdown("#### Image A")
156
  uploaded_image_A = st.file_uploader("Upload your first image", type=["png", "jpg", "jpeg"], key="imgA")
157
  if uploaded_image_A is not None:
158
- # use_container_width instead of use_column_width
159
  st.image(uploaded_image_A, caption="Preview - Image A", use_container_width=True)
160
  prompt_A = st.text_input("Prompt for Image A (optional)", value="", key="promptA")
161
  with col_imgB:
162
  st.markdown("#### Image B")
163
  uploaded_image_B = st.file_uploader("Upload your second image", type=["png", "jpg", "jpeg"], key="imgB")
164
  if uploaded_image_B is not None:
165
- # use_container_width instead of use_column_width
166
  st.image(uploaded_image_B, caption="Preview - Image B", use_container_width=True)
167
  prompt_B = st.text_input("Prompt for Image B (optional)", value="", key="promptB")
168
 
@@ -198,12 +210,12 @@ def main():
198
  # Determine preset defaults based on selection
199
  if preset_option.startswith("Maximum quality"):
200
  # "Maximum quality, highest inference time 🏆"
201
- preset_model = "Base Stable Diffusion V2-1 (No LCM-LoRA support)"
202
  preset_film = True
203
  preset_lcm = False
204
  elif preset_option.startswith("Medium quality"):
205
  # "Medium quality, medium inference time ⚖️"
206
- preset_model = "Base Stable Diffusion V2-1 (No LCM-LoRA support)"
207
  preset_film = False
208
  preset_lcm = False
209
  elif preset_option.startswith("Low quality"):
@@ -230,7 +242,7 @@ def main():
230
  options_list = [
231
  "Base Stable Diffusion V1-5",
232
  "Dreamshaper-7 (fine-tuned SD V1-5)",
233
- "Base Stable Diffusion V2-1 (No LCM-LoRA support)"
234
  ]
235
  default_model = preset_model if preset_model is not None else "Base Stable Diffusion V1-5"
236
  default_index = options_list.index(default_model)
@@ -241,23 +253,28 @@ def main():
241
  with col_left:
242
  st.markdown("##### Keyframe Generator Parameters")
243
  num_frames = st.number_input("Number of keyframes (2–50)", min_value=2, max_value=50, value=16)
244
- # Removed disabling for SD V2-1 so that LCM-LoRA can be enabled for it as well.
245
  lcm_default = preset_lcm if preset_lcm is not None else False
 
246
  enable_lcm_lora = st.checkbox(
247
- "Enable LCM-LoRA (accelerated inference, slight decrease in quality)",
248
- value=lcm_default
 
249
  )
250
- use_adain = st.checkbox("Use AdaIN", value=True)
251
- use_reschedule = st.checkbox("Use reschedule sampling", value=True)
 
252
  keyframe_duration = st.number_input("Keyframe Duration (seconds, only if not using FILM)", min_value=0.01, max_value=5.0, value=0.1, step=0.01)
 
253
  # Right Column: Inter-frame Interpolator Parameters (FILM)
254
  with col_right:
255
  st.markdown("<div class='right-column-divider'>", unsafe_allow_html=True)
256
  st.markdown("##### Inter-frame Interpolator Parameters")
257
  default_use_film = preset_film if preset_film is not None else True
258
- use_film = st.checkbox("Use FILM interpolation", value=default_use_film)
259
- film_fps = st.number_input("FILM FPS (1–120)", min_value=1, max_value=120, value=30)
260
- film_recursions = st.number_input("FILM recursion passes (1–6)", min_value=1, max_value=6, value=3)
 
261
  st.markdown("</div>", unsafe_allow_html=True)
262
 
263
  st.markdown("<hr>", unsafe_allow_html=True)
@@ -265,98 +282,124 @@ def main():
265
  # ---------------- SECTION 3: EXECUTE MORPH PIPELINE ----------------
266
  st.subheader("3. Generate Morphing Video")
267
  st.markdown("Once satisfied with your inputs, click below to start the process.")
 
268
  if st.button("Run Morphing Pipeline", key="run_pipeline"):
 
269
  if not (uploaded_image_A and uploaded_image_B):
270
  st.error("Please upload both images before running the morphing pipeline.")
271
  return
272
 
273
- # Check that the pipeline script exists
274
- if not os.path.exists("run_morphing.py"):
275
- st.error("Pipeline script 'run_morphing.py' not found in the current directory.")
276
- return
277
-
278
  with tempfile.TemporaryDirectory() as temp_dir:
279
  try:
 
280
  imgA_path = os.path.join(temp_dir, "imageA.png")
281
  imgB_path = os.path.join(temp_dir, "imageB.png")
282
  save_uploaded_file(uploaded_image_A, imgA_path)
283
  save_uploaded_file(uploaded_image_B, imgB_path)
284
- except Exception as e:
285
- st.error(f"Error saving uploaded images: {e}")
286
- return
287
-
288
- output_dir = os.path.join(temp_dir, "morph_results")
289
- film_output_dir = os.path.join(temp_dir, "film_output")
290
- os.makedirs(output_dir, exist_ok=True)
291
- os.makedirs(film_output_dir, exist_ok=True)
292
-
293
- # Convert seconds to milliseconds
294
- duration_ms = int(keyframe_duration * 1000)
295
-
296
- # Build the CLI command
297
- # Here you can add a mapping if you want to convert display names to actual model identifiers.
298
- # For this example, we assume model_option is already a valid model identifier,
299
- # or use a conditional similar to before.
300
- actual_model_path = (
301
- "lykon/dreamshaper-7" if model_option == "Dreamshaper-7 (fine-tuned SD V1-5)"
302
- else "sd-legacy/stable-diffusion-v1-5" if model_option == "Base Stable Diffusion V1-5"
303
- else "stabilityai/stable-diffusion-2-1-base"
304
- )
305
-
306
- cmd = [
307
- sys.executable, "run_morphing.py",
308
- "--model_path", actual_model_path,
309
- "--image_path_0", imgA_path,
310
- "--image_path_1", imgB_path,
311
- "--prompt_0", prompt_A,
312
- "--prompt_1", prompt_B,
313
- "--output_path", output_dir,
314
- "--film_output_folder", film_output_dir,
315
- "--num_frames", str(num_frames),
316
- "--duration", str(duration_ms)
317
- ]
318
- if enable_lcm_lora:
319
- cmd.append("--use_lcm")
320
- if use_adain:
321
- cmd.append("--use_adain")
322
- if use_reschedule:
323
- cmd.append("--use_reschedule")
324
- if use_film:
325
- cmd.append("--use_film")
326
- cmd.extend(["--film_fps", str(film_fps)])
327
- cmd.extend(["--film_num_recursions", str(film_recursions)])
328
-
329
- st.info("Initializing pipeline. Please wait...")
330
- try:
331
- subprocess.run(cmd, check=True)
332
- except subprocess.CalledProcessError as e:
333
- st.error(f"Error running pipeline: {e}")
334
- return
335
-
336
- # Check for output file in FILM folder first; if not, then in output_dir
337
- possible_outputs = [f for f in os.listdir(film_output_dir) if f.endswith(".mp4")]
338
- if not possible_outputs:
339
- possible_outputs = [f for f in os.listdir(output_dir) if f.endswith(".mp4")]
340
-
341
- if possible_outputs:
342
- final_video_path = os.path.join(
343
- film_output_dir if os.listdir(film_output_dir) else output_dir,
344
- possible_outputs[0]
345
  )
346
- st.success("Morphing complete! 🎉")
347
- st.video(final_video_path)
348
- try:
349
- with open(final_video_path, "rb") as f:
350
- st.download_button(
351
- "Download Result Video",
352
- data=f.read(),
353
- file_name="morph_result.mp4",
354
- mime="video/mp4"
355
- )
356
- except Exception as e:
357
- st.error(f"Error reading output video: {e}")
358
- else:
359
- st.warning("No .mp4 output found. Check logs for details.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
  if __name__ == "__main__":
362
  main()
 
 
 
 
1
  import os
2
  import sys
3
  import subprocess
 
8
  import streamlit as st
9
  from PIL import Image
10
 
 
 
11
  # Set Streamlit page configuration (centered content via CSS)
12
  st.set_page_config(
13
  page_title="Metamorph: DiffMorpher + LCM-LoRA + FILM",
 
16
  )
17
 
18
  def save_uploaded_file(uploaded_file, dst_path):
19
+ """Save an uploaded file to a destination path."""
20
  with open(dst_path, "wb") as f:
21
  f.write(uploaded_file.getbuffer())
22
 
23
  def get_img_as_base64(img):
24
+ """Convert PIL Image to base64 for embedding in HTML."""
25
  buffered = BytesIO()
26
  img.save(buffered, format="PNG")
27
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
28
 
29
+ def ensure_scripts_exist():
30
+ """Check if the required script files exist."""
31
+ required_scripts = ["run_morphing.py", "FILM.py"]
32
+ missing_scripts = [script for script in required_scripts if not os.path.exists(script)]
33
+
34
+ if missing_scripts:
35
+ error_msg = f"Missing required script(s): {', '.join(missing_scripts)}"
36
+ return False, error_msg
37
+ return True, ""
38
+
39
  def main():
40
  # ---------------- CUSTOM CSS FOR A PROFESSIONAL, DARK THEME ----------------
41
  st.markdown(
 
125
  unsafe_allow_html=True
126
  )
127
 
128
+ # Check if required scripts exist
129
+ scripts_exist, error_msg = ensure_scripts_exist()
130
+ if not scripts_exist:
131
+ st.error(error_msg)
132
+ st.error("Please make sure all required scripts are in the same directory as this Streamlit app.")
133
+ return
134
+
135
  # ---------------- HEADER & LOGO ----------------
136
  logo_path = "metamorphLogo_nobg.png"
137
  if os.path.exists(logo_path):
 
146
  """,
147
  unsafe_allow_html=True
148
  )
149
+ except Exception as e:
150
+ st.warning(f"Logo could not be loaded: {e}")
151
 
152
  st.markdown("<h1 class='header-title'>Metamorph Web App</h1>", unsafe_allow_html=True)
153
  st.markdown(
 
169
  st.markdown("#### Image A")
170
  uploaded_image_A = st.file_uploader("Upload your first image", type=["png", "jpg", "jpeg"], key="imgA")
171
  if uploaded_image_A is not None:
 
172
  st.image(uploaded_image_A, caption="Preview - Image A", use_container_width=True)
173
  prompt_A = st.text_input("Prompt for Image A (optional)", value="", key="promptA")
174
  with col_imgB:
175
  st.markdown("#### Image B")
176
  uploaded_image_B = st.file_uploader("Upload your second image", type=["png", "jpg", "jpeg"], key="imgB")
177
  if uploaded_image_B is not None:
 
178
  st.image(uploaded_image_B, caption="Preview - Image B", use_container_width=True)
179
  prompt_B = st.text_input("Prompt for Image B (optional)", value="", key="promptB")
180
 
 
210
  # Determine preset defaults based on selection
211
  if preset_option.startswith("Maximum quality"):
212
  # "Maximum quality, highest inference time 🏆"
213
+ preset_model = "Base Stable Diffusion V2-1"
214
  preset_film = True
215
  preset_lcm = False
216
  elif preset_option.startswith("Medium quality"):
217
  # "Medium quality, medium inference time ⚖️"
218
+ preset_model = "Base Stable Diffusion V2-1"
219
  preset_film = False
220
  preset_lcm = False
221
  elif preset_option.startswith("Low quality"):
 
242
  options_list = [
243
  "Base Stable Diffusion V1-5",
244
  "Dreamshaper-7 (fine-tuned SD V1-5)",
245
+ "Base Stable Diffusion V2-1"
246
  ]
247
  default_model = preset_model if preset_model is not None else "Base Stable Diffusion V1-5"
248
  default_index = options_list.index(default_model)
 
253
  with col_left:
254
  st.markdown("##### Keyframe Generator Parameters")
255
  num_frames = st.number_input("Number of keyframes (2–50)", min_value=2, max_value=50, value=16)
256
+ # Note: LCM compatibility check updated
257
  lcm_default = preset_lcm if preset_lcm is not None else False
258
+
259
  enable_lcm_lora = st.checkbox(
260
+ "Enable LCM-LoRA",
261
+ value=lcm_default,
262
+ help="Accelerates inference with slight quality decrease"
263
  )
264
+
265
+ use_adain = st.checkbox("Use AdaIN", value=True, help="Adaptive Instance Normalization for improved generation")
266
+ use_reschedule = st.checkbox("Use reschedule sampling", value=True, help="Better sampling strategy")
267
  keyframe_duration = st.number_input("Keyframe Duration (seconds, only if not using FILM)", min_value=0.01, max_value=5.0, value=0.1, step=0.01)
268
+
269
  # Right Column: Inter-frame Interpolator Parameters (FILM)
270
  with col_right:
271
  st.markdown("<div class='right-column-divider'>", unsafe_allow_html=True)
272
  st.markdown("##### Inter-frame Interpolator Parameters")
273
  default_use_film = preset_film if preset_film is not None else True
274
+ use_film = st.checkbox("Use FILM interpolation", value=default_use_film, help="Frame Interpolation for Large Motion - creates smooth transitions")
275
+ film_fps = st.number_input("FILM FPS (1–120)", min_value=1, max_value=120, value=30, help="Output video frames per second")
276
+ film_recursions = st.number_input("FILM recursion passes (1–6)", min_value=1, max_value=6, value=3,
277
+ help="Higher values create more intermediate frames (smoother but slower)")
278
  st.markdown("</div>", unsafe_allow_html=True)
279
 
280
  st.markdown("<hr>", unsafe_allow_html=True)
 
282
  # ---------------- SECTION 3: EXECUTE MORPH PIPELINE ----------------
283
  st.subheader("3. Generate Morphing Video")
284
  st.markdown("Once satisfied with your inputs, click below to start the process.")
285
+
286
  if st.button("Run Morphing Pipeline", key="run_pipeline"):
287
+ # Validate inputs
288
  if not (uploaded_image_A and uploaded_image_B):
289
  st.error("Please upload both images before running the morphing pipeline.")
290
  return
291
 
292
+ # Create a temporary directory for processing
 
 
 
 
293
  with tempfile.TemporaryDirectory() as temp_dir:
294
  try:
295
+ # Save uploaded images to temp directory
296
  imgA_path = os.path.join(temp_dir, "imageA.png")
297
  imgB_path = os.path.join(temp_dir, "imageB.png")
298
  save_uploaded_file(uploaded_image_A, imgA_path)
299
  save_uploaded_file(uploaded_image_B, imgB_path)
300
+
301
+ # Create output directories
302
+ output_dir = os.path.join(temp_dir, "morph_results")
303
+ film_output_dir = os.path.join(temp_dir, "film_output")
304
+ os.makedirs(output_dir, exist_ok=True)
305
+ os.makedirs(film_output_dir, exist_ok=True)
306
+
307
+ # Convert seconds to milliseconds for duration
308
+ duration_ms = int(keyframe_duration * 1000)
309
+
310
+ # Map UI model names to actual model paths
311
+ actual_model_path = (
312
+ "lykon/dreamshaper-7" if model_option == "Dreamshaper-7 (fine-tuned SD V1-5)"
313
+ else "stabilityai/stable-diffusion-2-1-base" if model_option == "Base Stable Diffusion V2-1"
314
+ else "sd-legacy/stable-diffusion-v1-5" # Default to SD V1-5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  )
316
+
317
+ # Build the command for run_morphing.py
318
+ cmd = [
319
+ sys.executable, "run_morphing.py",
320
+ "--model_path", actual_model_path,
321
+ "--image_path_0", imgA_path,
322
+ "--image_path_1", imgB_path,
323
+ "--prompt_0", prompt_A,
324
+ "--prompt_1", prompt_B,
325
+ "--output_path", output_dir,
326
+ "--film_output_folder", film_output_dir,
327
+ "--num_frames", str(num_frames),
328
+ "--duration", str(duration_ms)
329
+ ]
330
+
331
+ # Add optional flags
332
+ if enable_lcm_lora:
333
+ cmd.append("--use_lcm")
334
+ if use_adain:
335
+ cmd.append("--use_adain")
336
+ if use_reschedule:
337
+ cmd.append("--use_reschedule")
338
+ if use_film:
339
+ cmd.append("--use_film")
340
+
341
+ # Add film parameters
342
+ cmd.extend(["--film_fps", str(film_fps)])
343
+ cmd.extend(["--film_num_recursions", str(film_recursions)])
344
+
345
+ # Run the pipeline
346
+ st.info("Initializing pipeline. This may take a few minutes...")
347
+ progress_bar = st.progress(0)
348
+ status_text = st.empty()
349
+
350
+ # Update progress status
351
+ for i in range(1, 11):
352
+ status_text.text(f"Step {i}/10: {'Preparing images' if i <= 2 else 'Generating keyframes' if i <= 6 else 'Interpolating frames' if i <= 9 else 'Finalizing video'}")
353
+ progress_bar.progress(i * 10)
354
+
355
+ if i == 3: # Start actual processing
356
+ try:
357
+ subprocess.run(cmd, check=True)
358
+ except subprocess.CalledProcessError as e:
359
+ st.error(f"Error running morphing pipeline: {e}")
360
+ return
361
+ break
362
+
363
+ # Set progress to 100% when done
364
+ progress_bar.progress(100)
365
+ status_text.text("Processing complete!")
366
+
367
+ # Check for output video
368
+ video_found = False
369
+
370
+ # First check FILM output directory if FILM was used
371
+ if use_film:
372
+ possible_outputs = [f for f in os.listdir(film_output_dir) if f.endswith(".mp4")]
373
+ if possible_outputs:
374
+ final_video_path = os.path.join(film_output_dir, possible_outputs[0])
375
+ video_found = True
376
+
377
+ # If not found in FILM dir, check regular output dir
378
+ if not video_found:
379
+ possible_outputs = [f for f in os.listdir(output_dir) if f.endswith(".mp4")]
380
+ if possible_outputs:
381
+ final_video_path = os.path.join(output_dir, possible_outputs[0])
382
+ video_found = True
383
+
384
+ if video_found:
385
+ st.success("Morphing complete! 🎉")
386
+ st.video(final_video_path)
387
+ try:
388
+ with open(final_video_path, "rb") as f:
389
+ video_bytes = f.read()
390
+ st.download_button(
391
+ "Download Result Video",
392
+ data=video_bytes,
393
+ file_name="metamorph_result.mp4",
394
+ mime="video/mp4"
395
+ )
396
+ except Exception as e:
397
+ st.error(f"Error preparing video for download: {e}")
398
+ else:
399
+ st.warning("No output video was generated. Check logs for details.")
400
+
401
+ except Exception as e:
402
+ st.error(f"An error occurred during processing: {e}")
403
 
404
  if __name__ == "__main__":
405
  main()
run_morphing.py CHANGED
@@ -3,11 +3,10 @@ import sys
3
  import time
4
  import subprocess
5
  import argparse
6
-
7
- from FILM import process_keyframes
8
-
9
  import multiprocessing as mp
10
- mp.set_start_method("spawn", force=True)
 
 
11
 
12
  def parse_arguments():
13
  parser = argparse.ArgumentParser(
@@ -98,10 +97,10 @@ def parse_arguments():
98
  )
99
  parser.add_argument(
100
  "--film_output_folder", type=str, default="./FILM_Results",
101
- help="Folder where FILMs final interpolated video is saved (default: %(default)s)"
102
  )
103
  parser.add_argument(
104
- "--film_fps", type=int, default=40,
105
  help="FPS for the final video - 'Pseudo-Playback-Speed', since total frames are same (default: %(default)s)"
106
  )
107
  parser.add_argument(
@@ -191,6 +190,22 @@ def create_simple_video_from_keyframes(keyframes_folder, output_folder, fps=40):
191
  out.release()
192
  print(f"[INFO] Basic morphing video saved at: {out_video_path}")
193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  def main():
195
  args = parse_arguments()
196
  overall_start_time = time.time()
@@ -199,22 +214,27 @@ def main():
199
  run_diffmorpher(args)
200
 
201
  # 2) Determine the folder containing the keyframes
202
- # If user didnt explicitly give `--film_input_folder`, use `args.output_path`
203
  keyframes_folder = args.film_input_folder if args.film_input_folder else args.output_path
204
 
205
  # 3) If user wants to use FILM, perform high-quality interpolation on the keyframes
206
  if args.use_film:
207
  print("[INFO] Running FILM to enhance the keyframes...")
208
  start_film_time = time.time()
209
- # from FILM.py:
210
- process_keyframes(
 
211
  input_folder=keyframes_folder,
212
  output_folder=args.film_output_folder,
213
  fps=args.film_fps,
214
  num_recursions=args.film_num_recursions
215
  )
 
216
  end_film_time = time.time()
217
- print(f"[INFO] FILM interpolation completed in {end_film_time - start_film_time:.2f} seconds.")
 
 
 
218
  else:
219
  # 4) If user does NOT want FILM, create a simple .mp4 from the keyframes
220
  print("[INFO] Skipping FILM interpolation. Creating a basic video from DiffMorpher keyframes...")
 
3
  import time
4
  import subprocess
5
  import argparse
 
 
 
6
  import multiprocessing as mp
7
+
8
+ # Don't import FILM module globally - we'll import the specific function when needed
9
+ mp.set_start_method("spawn", force=True) # see if buggy
10
 
11
  def parse_arguments():
12
  parser = argparse.ArgumentParser(
 
97
  )
98
  parser.add_argument(
99
  "--film_output_folder", type=str, default="./FILM_Results",
100
+ help="Folder where FILM's final interpolated video is saved (default: %(default)s)"
101
  )
102
  parser.add_argument(
103
+ "--film_fps", type=int, default=30,
104
  help="FPS for the final video - 'Pseudo-Playback-Speed', since total frames are same (default: %(default)s)"
105
  )
106
  parser.add_argument(
 
190
  out.release()
191
  print(f"[INFO] Basic morphing video saved at: {out_video_path}")
192
 
193
+ def run_film_interpolation(input_folder, output_folder, fps, num_recursions):
194
+ """
195
+ Import and run FILM processing only when needed.
196
+ This function is called only if args.use_film is True.
197
+ """
198
+ # Import the process_keyframes function from FILM.py only when needed
199
+ from FILM import process_keyframes
200
+
201
+ # Now run the FILM processing
202
+ return process_keyframes(
203
+ input_folder=input_folder,
204
+ output_folder=output_folder,
205
+ fps=fps,
206
+ num_recursions=num_recursions
207
+ )
208
+
209
  def main():
210
  args = parse_arguments()
211
  overall_start_time = time.time()
 
214
  run_diffmorpher(args)
215
 
216
  # 2) Determine the folder containing the keyframes
217
+ # If user didn't explicitly give `--film_input_folder`, use `args.output_path`
218
  keyframes_folder = args.film_input_folder if args.film_input_folder else args.output_path
219
 
220
  # 3) If user wants to use FILM, perform high-quality interpolation on the keyframes
221
  if args.use_film:
222
  print("[INFO] Running FILM to enhance the keyframes...")
223
  start_film_time = time.time()
224
+
225
+ # Call the wrapper function that imports FILM only when needed
226
+ success = run_film_interpolation(
227
  input_folder=keyframes_folder,
228
  output_folder=args.film_output_folder,
229
  fps=args.film_fps,
230
  num_recursions=args.film_num_recursions
231
  )
232
+
233
  end_film_time = time.time()
234
+ if success:
235
+ print(f"[INFO] FILM interpolation completed in {end_film_time - start_film_time:.2f} seconds.")
236
+ else:
237
+ print("[ERROR] FILM interpolation failed. See above for details.")
238
  else:
239
  # 4) If user does NOT want FILM, create a simple .mp4 from the keyframes
240
  print("[INFO] Skipping FILM interpolation. Creating a basic video from DiffMorpher keyframes...")