"""
Cleaned up version, Close-to-Final UI features and functionality logic.
"""
import os
import sys
import subprocess
import tempfile
import base64
from io import BytesIO
import streamlit as st
from PIL import Image
# Set Streamlit page configuration (centered content via CSS)
st.set_page_config(
page_title="Metamorph: DiffMorpher + LCM-LoRA + FILM",
layout="wide",
page_icon="🌀"
)
def save_uploaded_file(uploaded_file, dst_path):
with open(dst_path, "wb") as f:
f.write(uploaded_file.getbuffer())
def get_img_as_base64(img):
buffered = BytesIO()
img.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode("utf-8")
def main():
# ---------------- CUSTOM CSS FOR A PROFESSIONAL, DARK THEME ----------------
st.markdown(
"""
""",
unsafe_allow_html=True
)
# ---------------- HEADER & LOGO ----------------
logo_path = "metamorphLogo_nobg.png"
if os.path.exists(logo_path):
try:
logo = Image.open(logo_path)
logo_base64 = get_img_as_base64(logo)
st.markdown(
f"""
""",
unsafe_allow_html=True
)
except Exception:
pass
st.markdown("", unsafe_allow_html=True)
st.markdown(
"""
DiffMorpher is used for keyframe generation by default, with FILM for interpolation.
Optionally, you can enable LCM-LoRA for accelerated inference (with slight decrease in quality).
Upload two images, optionally provide textual prompts, and fine-tune the settings to create a smooth, high-quality morphing video.
""",
unsafe_allow_html=True
)
# ---------------- SECTION 1: IMAGE & PROMPT INPUTS ----------------
st.subheader("1. Upload Source Images & Prompts")
col_imgA, col_imgB = st.columns(2)
with col_imgA:
st.markdown("#### Image A")
uploaded_image_A = st.file_uploader("Upload your first image", type=["png", "jpg", "jpeg"], key="imgA")
if uploaded_image_A is not None:
# use_container_width instead of use_column_width
st.image(uploaded_image_A, caption="Preview - Image A", use_container_width=True)
prompt_A = st.text_input("Prompt for Image A (optional)", value="", key="promptA")
with col_imgB:
st.markdown("#### Image B")
uploaded_image_B = st.file_uploader("Upload your second image", type=["png", "jpg", "jpeg"], key="imgB")
if uploaded_image_B is not None:
# use_container_width instead of use_column_width
st.image(uploaded_image_B, caption="Preview - Image B", use_container_width=True)
prompt_B = st.text_input("Prompt for Image B (optional)", value="", key="promptB")
st.markdown("
", unsafe_allow_html=True)
# ---------------- SECTION 2: CONFIGURE MORPHING PIPELINE ----------------
st.subheader("2. Configure Morphing Pipeline")
st.markdown(
"""
Select a preset below to automatically adjust quality and inference time.
If you choose Custom ⚙️, the advanced settings will automatically expand so you can fine-tune the configuration.
""",
unsafe_allow_html=True
)
# Preset Options (Dropdown)
st.markdown("**Preset Options**")
preset_option = st.selectbox(
"Select a preset for quality and inference time",
options=[
"Maximum quality, highest inference time 🏆",
"Medium quality, medium inference time ⚖️",
"Low quality, lowest inference time ⚡",
"Creative morph 🎨",
"Custom ⚙️"
],
index=0,
label_visibility="collapsed" # Hide the label in the UI but keep it for accessibility
)
# Determine preset defaults based on selection
if preset_option.startswith("Maximum quality"):
# "Maximum quality, highest inference time 🏆"
preset_model = "Base Stable Diffusion V2-1 (No LCM-LoRA support)"
preset_film = True
preset_lcm = False
elif preset_option.startswith("Medium quality"):
# "Medium quality, medium inference time ⚖️"
preset_model = "Base Stable Diffusion V2-1 (No LCM-LoRA support)"
preset_film = False
preset_lcm = False
elif preset_option.startswith("Low quality"):
# "Low quality, lowest inference time ⚡"
preset_model = "Base Stable Diffusion V1-5"
preset_film = False
preset_lcm = True
elif preset_option.startswith("Creative morph"):
# "Creative morph 🎨"
preset_model = "Dreamshaper-7 (fine-tuned SD V1-5)"
preset_film = True
preset_lcm = True
else:
# "Custom ⚙️"
preset_model = None
preset_film = None
preset_lcm = None
# Auto-expand advanced options if "Custom ⚙️" is chosen
advanced_expanded = True if preset_option.endswith("⚙️") else False
# Advanced Options for fine-tuning
with st.expander("Advanced Options", expanded=advanced_expanded):
options_list = [
"Base Stable Diffusion V1-5",
"Dreamshaper-7 (fine-tuned SD V1-5)",
"Base Stable Diffusion V2-1 (No LCM-LoRA support)"
]
default_model = preset_model if preset_model is not None else "Base Stable Diffusion V1-5"
default_index = options_list.index(default_model)
model_option = st.selectbox("Select Model Card", options=options_list, index=default_index)
col_left, col_right = st.columns(2)
# Left Column: Keyframe Generator Parameters
with col_left:
st.markdown("##### Keyframe Generator Parameters")
num_frames = st.number_input("Number of keyframes (2–50)", min_value=2, max_value=50, value=16)
# Removed disabling for SD V2-1 so that LCM-LoRA can be enabled for it as well.
lcm_default = preset_lcm if preset_lcm is not None else False
enable_lcm_lora = st.checkbox(
"Enable LCM-LoRA (accelerated inference, slight decrease in quality)",
value=lcm_default
)
use_adain = st.checkbox("Use AdaIN", value=True)
use_reschedule = st.checkbox("Use reschedule sampling", value=True)
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)
# Right Column: Inter-frame Interpolator Parameters (FILM)
with col_right:
st.markdown("", unsafe_allow_html=True)
st.markdown("##### Inter-frame Interpolator Parameters")
default_use_film = preset_film if preset_film is not None else True
use_film = st.checkbox("Use FILM interpolation", value=default_use_film)
film_fps = st.number_input("FILM FPS (1–120)", min_value=1, max_value=120, value=30)
film_recursions = st.number_input("FILM recursion passes (1–6)", min_value=1, max_value=6, value=3)
st.markdown("
", unsafe_allow_html=True)
st.markdown("
", unsafe_allow_html=True)
# ---------------- SECTION 3: EXECUTE MORPH PIPELINE ----------------
st.subheader("3. Generate Morphing Video")
st.markdown("Once satisfied with your inputs, click below to start the process.")
if st.button("Run Morphing Pipeline", key="run_pipeline"):
if not (uploaded_image_A and uploaded_image_B):
st.error("Please upload both images before running the morphing pipeline.")
return
# Check that the pipeline script exists
if not os.path.exists("run_morphing.py"):
st.error("Pipeline script 'run_morphing.py' not found in the current directory.")
return
with tempfile.TemporaryDirectory() as temp_dir:
try:
imgA_path = os.path.join(temp_dir, "imageA.png")
imgB_path = os.path.join(temp_dir, "imageB.png")
save_uploaded_file(uploaded_image_A, imgA_path)
save_uploaded_file(uploaded_image_B, imgB_path)
except Exception as e:
st.error(f"Error saving uploaded images: {e}")
return
output_dir = os.path.join(temp_dir, "morph_results")
film_output_dir = os.path.join(temp_dir, "film_output")
os.makedirs(output_dir, exist_ok=True)
os.makedirs(film_output_dir, exist_ok=True)
# Convert seconds to milliseconds
duration_ms = int(keyframe_duration * 1000)
# Build the CLI command
# Here you can add a mapping if you want to convert display names to actual model identifiers.
# For this example, we assume model_option is already a valid model identifier,
# or use a conditional similar to before.
actual_model_path = (
"lykon/dreamshaper-7" if model_option == "Dreamshaper-7 (fine-tuned SD V1-5)"
else "sd-legacy/stable-diffusion-v1-5" if model_option == "Base Stable Diffusion V1-5"
else "stabilityai/stable-diffusion-2-1-base"
)
cmd = [
sys.executable, "run_morphing.py",
"--model_path", actual_model_path,
"--image_path_0", imgA_path,
"--image_path_1", imgB_path,
"--prompt_0", prompt_A,
"--prompt_1", prompt_B,
"--output_path", output_dir,
"--film_output_folder", film_output_dir,
"--num_frames", str(num_frames),
"--duration", str(duration_ms)
]
if enable_lcm_lora:
cmd.append("--use_lcm")
if use_adain:
cmd.append("--use_adain")
if use_reschedule:
cmd.append("--use_reschedule")
if use_film:
cmd.append("--use_film")
cmd.extend(["--film_fps", str(film_fps)])
cmd.extend(["--film_num_recursions", str(film_recursions)])
st.info("Initializing pipeline. Please wait...")
try:
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError as e:
st.error(f"Error running pipeline: {e}")
return
# Check for output file in FILM folder first; if not, then in output_dir
possible_outputs = [f for f in os.listdir(film_output_dir) if f.endswith(".mp4")]
if not possible_outputs:
possible_outputs = [f for f in os.listdir(output_dir) if f.endswith(".mp4")]
if possible_outputs:
final_video_path = os.path.join(
film_output_dir if os.listdir(film_output_dir) else output_dir,
possible_outputs[0]
)
st.success("Morphing complete! 🎉")
st.video(final_video_path)
try:
with open(final_video_path, "rb") as f:
st.download_button(
"Download Result Video",
data=f.read(),
file_name="morph_result.mp4",
mime="video/mp4"
)
except Exception as e:
st.error(f"Error reading output video: {e}")
else:
st.warning("No .mp4 output found. Check logs for details.")
if __name__ == "__main__":
main()