nalin0503 commited on
Commit
792f5e9
Β·
1 Parent(s): 8b4471b

add app.py and reqs.txt

Browse files
Files changed (2) hide show
  1. app.py +340 -0
  2. requirements.txt +16 -0
app.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import tempfile
5
+ import base64
6
+ from io import BytesIO
7
+
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",
14
+ layout="wide",
15
+ page_icon="πŸŒ€"
16
+ )
17
+
18
+ def save_uploaded_file(uploaded_file, dst_path):
19
+ with open(dst_path, "wb") as f:
20
+ f.write(uploaded_file.getbuffer())
21
+
22
+ def get_img_as_base64(img):
23
+ buffered = BytesIO()
24
+ img.save(buffered, format="PNG")
25
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
26
+
27
+ def main():
28
+ # ---------------- CUSTOM CSS FOR A PROFESSIONAL, DARK THEME ----------------
29
+ st.markdown(
30
+ """
31
+ <style>
32
+ /* Import Google Font */
33
+ @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
34
+
35
+ /* Global styling */
36
+ body {
37
+ font-family: 'Roboto', sans-serif;
38
+ color: #f1f1f1;
39
+ }
40
+ h1, h2, h3, h4 {
41
+ color: #ffffff;
42
+ }
43
+ p, span, label {
44
+ color: #f1f1f1;
45
+ }
46
+ body, p {
47
+ line-height: 1.6;
48
+ letter-spacing: 0.3px;
49
+ }
50
+ /* Header: Centered large logo and title */
51
+ .header-logo-large {
52
+ display: block;
53
+ margin-left: auto;
54
+ margin-right: auto;
55
+ width: 200px;
56
+ }
57
+ .header-title {
58
+ text-align: center;
59
+ font-size: 2.8rem;
60
+ font-weight: bold;
61
+ color: #ffffff;
62
+ margin-top: 0.5rem;
63
+ }
64
+ /* Dark animated background */
65
+ .stApp {
66
+ background: linear-gradient(315deg, #000428, #004e92);
67
+ animation: gradient 30s ease infinite;
68
+ background-size: 400% 400%;
69
+ background-attachment: fixed;
70
+ }
71
+ @keyframes gradient {
72
+ 0% { background-position: 0% 0%; }
73
+ 50% { background-position: 100% 100%; }
74
+ 100% { background-position: 0% 0%; }
75
+ }
76
+ /* Main container styling */
77
+ .main .block-container {
78
+ max-width: 900px;
79
+ margin: 0 auto;
80
+ padding: 2rem 1rem;
81
+ background-color: transparent;
82
+ color: #f1f1f1;
83
+ }
84
+ /* Run button styling */
85
+ div.stButton > button {
86
+ background-image: linear-gradient(45deg, #8e44ad, #732d91);
87
+ box-shadow: 0 0 10px rgba(142,68,173,0.6), 0 0 20px rgba(114,45,145,0.4);
88
+ border: none;
89
+ color: #ffffff;
90
+ padding: 0.6rem 1.2rem;
91
+ border-radius: 5px;
92
+ cursor: pointer;
93
+ font-family: 'Roboto', sans-serif;
94
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
95
+ }
96
+ div.stButton > button:hover {
97
+ transform: scale(1.02);
98
+ box-shadow: 0 0 20px rgba(142,68,173,0.8), 0 0 30px rgba(114,45,145,0.6);
99
+ }
100
+ /* File uploader label styling */
101
+ .stFileUploader label {
102
+ font-size: 1rem;
103
+ color: #f1f1f1;
104
+ }
105
+ /* Advanced Options divider styling */
106
+ .right-column-divider {
107
+ border-left: 2px solid #f1f1f1;
108
+ padding-left: 1rem;
109
+ margin-left: 1rem;
110
+ }
111
+ </style>
112
+ """,
113
+ unsafe_allow_html=True
114
+ )
115
+
116
+ # ---------------- HEADER & LOGO ----------------
117
+ logo_path = os.path.join("lcm-lora", "metamorphLogo_nobg.png")
118
+ if os.path.exists(logo_path):
119
+ try:
120
+ logo = Image.open(logo_path)
121
+ logo_base64 = get_img_as_base64(logo)
122
+ st.markdown(
123
+ f"""
124
+ <div style="text-align: center;">
125
+ <img src="data:image/png;base64,{logo_base64}" class="header-logo-large" alt="Metamorph Logo">
126
+ </div>
127
+ """,
128
+ unsafe_allow_html=True
129
+ )
130
+ except Exception:
131
+ pass
132
+
133
+ st.markdown("<h1 class='header-title'>Metamorph Web App</h1>", unsafe_allow_html=True)
134
+ st.markdown(
135
+ """
136
+ <p style='text-align: center; font-size: 1.1rem;'>
137
+ DiffMorpher is used for keyframe generation by default, with FILM for interpolation.
138
+ Optionally, you can enable LCM-LoRA for accelerated inference (with slight decrease in quality).
139
+ Upload two images, optionally provide textual prompts, and fine-tune the settings to create a smooth, high-quality morphing video.
140
+ </p>
141
+ <hr>
142
+ """,
143
+ unsafe_allow_html=True
144
+ )
145
+
146
+ # ---------------- SECTION 1: IMAGE & PROMPT INPUTS ----------------
147
+ st.subheader("1. Upload Source Images & Prompts")
148
+ col_imgA, col_imgB = st.columns(2)
149
+ with col_imgA:
150
+ st.markdown("#### Image A")
151
+ uploaded_image_A = st.file_uploader("Upload your first image", type=["png", "jpg", "jpeg"], key="imgA")
152
+ if uploaded_image_A is not None:
153
+ # use_container_width instead of use_column_width
154
+ st.image(uploaded_image_A, caption="Preview - Image A", use_container_width=True)
155
+ prompt_A = st.text_input("Prompt for Image A (optional)", value="", key="promptA")
156
+ with col_imgB:
157
+ st.markdown("#### Image B")
158
+ uploaded_image_B = st.file_uploader("Upload your second image", type=["png", "jpg", "jpeg"], key="imgB")
159
+ if uploaded_image_B is not None:
160
+ # use_container_width instead of use_column_width
161
+ st.image(uploaded_image_B, caption="Preview - Image B", use_container_width=True)
162
+ prompt_B = st.text_input("Prompt for Image B (optional)", value="", key="promptB")
163
+
164
+ st.markdown("<hr>", unsafe_allow_html=True)
165
+
166
+ # ---------------- SECTION 2: CONFIGURE MORPHING PIPELINE ----------------
167
+ st.subheader("2. Configure Morphing Pipeline")
168
+ st.markdown(
169
+ """
170
+ <p style="font-size: 1rem;">
171
+ Select a preset below to automatically adjust quality and inference time.
172
+ If you choose <strong>Custom βš™οΈ</strong>, the advanced settings will automatically expand so you can fine-tune the configuration.
173
+ </p>
174
+ """,
175
+ unsafe_allow_html=True
176
+ )
177
+
178
+ # Preset Options (Dropdown)
179
+ st.markdown("**Preset Options**")
180
+ preset_option = st.selectbox(
181
+ "Select a preset for quality and inference time",
182
+ options=[
183
+ "Maximum quality, highest inference time πŸ†",
184
+ "Medium quality, medium inference time βš–οΈ",
185
+ "Low quality, lowest inference time ⚑",
186
+ "Creative morph 🎨",
187
+ "Custom βš™οΈ"
188
+ ],
189
+ index=0,
190
+ label_visibility="collapsed" # Hide the label in the UI but keep it for accessibility
191
+ )
192
+
193
+ # Determine preset defaults based on selection
194
+ if preset_option.startswith("Maximum quality"):
195
+ # "Maximum quality, highest inference time πŸ†"
196
+ preset_model = "Base Stable Diffusion V2-1 (No LCM-LoRA support)"
197
+ preset_film = True
198
+ preset_lcm = False
199
+ elif preset_option.startswith("Medium quality"):
200
+ # "Medium quality, medium inference time βš–οΈ"
201
+ preset_model = "Base Stable Diffusion V2-1 (No LCM-LoRA support)"
202
+ preset_film = False
203
+ preset_lcm = False
204
+ elif preset_option.startswith("Low quality"):
205
+ # "Low quality, lowest inference time ⚑"
206
+ preset_model = "Base Stable Diffusion V1-5"
207
+ preset_film = False
208
+ preset_lcm = True
209
+ elif preset_option.startswith("Creative morph"):
210
+ # "Creative morph 🎨"
211
+ preset_model = "Dreamshaper-7 (fine-tuned SD V1-5)"
212
+ preset_film = True
213
+ preset_lcm = True
214
+ else:
215
+ # "Custom βš™οΈ"
216
+ preset_model = None
217
+ preset_film = None
218
+ preset_lcm = None
219
+
220
+ # Auto-expand advanced options if "Custom βš™οΈ" is chosen
221
+ advanced_expanded = True if preset_option.endswith("βš™οΈ") else False
222
+
223
+ # Advanced Options for fine-tuning
224
+ with st.expander("Advanced Options", expanded=advanced_expanded):
225
+ options_list = [
226
+ "Base Stable Diffusion V1-5",
227
+ "Dreamshaper-7 (fine-tuned SD V1-5)",
228
+ "Base Stable Diffusion V2-1 (No LCM-LoRA support)"
229
+ ]
230
+ default_model = preset_model if preset_model is not None else "Base Stable Diffusion V1-5"
231
+ default_index = options_list.index(default_model)
232
+ model_option = st.selectbox("Select Model Card", options=options_list, index=default_index)
233
+
234
+ col_left, col_right = st.columns(2)
235
+ # Left Column: Keyframe Generator Parameters
236
+ with col_left:
237
+ st.markdown("##### Keyframe Generator Parameters")
238
+ num_frames = st.number_input("Number of keyframes (2–200)", min_value=2, max_value=200, value=20)
239
+ if model_option == "Base Stable Diffusion V2-1 (No LCM-LoRA support)":
240
+ enable_lcm_lora = st.checkbox(
241
+ "Enable LCM-LoRA (accelerated inference, slight decrease in quality)",
242
+ value=False,
243
+ disabled=True,
244
+ help="LCM-LoRA is not available for the selected model card."
245
+ )
246
+ else:
247
+ lcm_default = preset_lcm if preset_lcm is not None else False
248
+ enable_lcm_lora = st.checkbox(
249
+ "Enable LCM-LoRA (accelerated inference, slight decrease in quality)",
250
+ value=lcm_default
251
+ )
252
+ use_adain = st.checkbox("Use AdaIN", value=True)
253
+ use_reschedule = st.checkbox("Use reschedule sampling", value=True)
254
+ 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)
255
+ # Right Column: Inter-frame Interpolator Parameters (FILM)
256
+ with col_right:
257
+ st.markdown("<div class='right-column-divider'>", unsafe_allow_html=True)
258
+ st.markdown("##### Inter-frame Interpolator Parameters")
259
+ default_use_film = preset_film if preset_film is not None else True
260
+ use_film = st.checkbox("Use FILM interpolation", value=default_use_film)
261
+ film_fps = st.number_input("FILM FPS (1–120)", min_value=1, max_value=120, value=30)
262
+ film_recursions = st.number_input("FILM recursion passes (1–6)", min_value=1, max_value=6, value=3)
263
+ st.markdown("</div>", unsafe_allow_html=True)
264
+
265
+ st.markdown("<hr>", unsafe_allow_html=True)
266
+
267
+ # ---------------- SECTION 3: EXECUTE MORPH PIPELINE ----------------
268
+ st.subheader("3. Generate Morphing Video")
269
+ st.markdown("Once satisfied with your inputs, click below to start the process.")
270
+ if st.button("Run Morphing Pipeline", key="run_pipeline"):
271
+ if not (uploaded_image_A and uploaded_image_B):
272
+ st.error("Please upload both images before running the morphing pipeline.")
273
+ return
274
+
275
+ with tempfile.TemporaryDirectory() as temp_dir:
276
+ imgA_path = os.path.join(temp_dir, "imageA.png")
277
+ imgB_path = os.path.join(temp_dir, "imageB.png")
278
+ save_uploaded_file(uploaded_image_A, imgA_path)
279
+ save_uploaded_file(uploaded_image_B, imgB_path)
280
+
281
+ output_dir = os.path.join(temp_dir, "morph_results")
282
+ film_output_dir = os.path.join(temp_dir, "film_output")
283
+ os.makedirs(output_dir, exist_ok=True)
284
+ os.makedirs(film_output_dir, exist_ok=True)
285
+
286
+ # Build the CLI command. Note: numeric parameters are converted to strings for CLI compatibility.
287
+ cmd = [
288
+ sys.executable, "run_morphing.py",
289
+ "--model_path", model_option if model_option != "Dreamshaper-7 (fine-tuned SD V1-5)" else "lykon/dreamshaper-7",
290
+ "--image_path_0", imgA_path,
291
+ "--image_path_1", imgB_path,
292
+ "--prompt_0", prompt_A,
293
+ "--prompt_1", prompt_B,
294
+ "--output_path", output_dir,
295
+ "--film_output_folder", film_output_dir,
296
+ "--num_frames", str(num_frames),
297
+ "--keyframe_duration", str(keyframe_duration)
298
+ ]
299
+ if enable_lcm_lora:
300
+ cmd.append("--use_lcm")
301
+ if use_adain:
302
+ cmd.append("--use_adain")
303
+ if use_reschedule:
304
+ cmd.append("--use_reschedule")
305
+ if use_film:
306
+ cmd.append("--use_film")
307
+ cmd.extend(["--film_fps", str(film_fps)])
308
+ cmd.extend(["--film_num_recursions", str(film_recursions)])
309
+
310
+ st.info("Initializing pipeline. Please wait...")
311
+ with st.spinner("Generating morph..."):
312
+ try:
313
+ subprocess.run(cmd, check=True)
314
+ except subprocess.CalledProcessError as e:
315
+ st.error(f"Error running pipeline: {e}")
316
+ return
317
+
318
+ possible_outputs = [f for f in os.listdir(film_output_dir) if f.endswith(".mp4")]
319
+ if not possible_outputs:
320
+ possible_outputs = [f for f in os.listdir(output_dir) if f.endswith(".mp4")]
321
+
322
+ if possible_outputs:
323
+ final_video_path = os.path.join(
324
+ film_output_dir if os.listdir(film_output_dir) else output_dir,
325
+ possible_outputs[0]
326
+ )
327
+ st.success("Morphing complete! πŸŽ‰")
328
+ st.video(final_video_path)
329
+ with open(final_video_path, "rb") as f:
330
+ st.download_button(
331
+ "Download Result Video",
332
+ data=f.read(),
333
+ file_name="morph_result.mp4",
334
+ mime="video/mp4"
335
+ )
336
+ else:
337
+ st.warning("No .mp4 output found. Check logs for details.")
338
+
339
+ if __name__ == "__main__":
340
+ main()
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ accelerate==0.23.0
3
+ diffusers==0.17.1
4
+ einops==0.7.0
5
+ gradio==4.7.1
6
+ numpy==1.26.1
7
+ opencv_python==4.5.5.64
8
+ packaging==23.2
9
+ Pillow==10.1.0
10
+ safetensors==0.4.0
11
+ tqdm==4.65.0
12
+ transformers==4.34.1
13
+ torch
14
+ torchvision
15
+ lpips
16
+ # peft