Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files- .gitattributes +6 -0
- .gradio/cached_examples/21/Edited Video/ab83f4b76b6dbcfa450f/tmptcf9qsfp.mp4 +3 -0
- .gradio/cached_examples/21/indices.csv +1 -0
- .gradio/cached_examples/21/log.csv +2 -0
- .gradio/certificate.pem +31 -0
- .ipynb_checkpoints/app-checkpoint.py +241 -0
- .ipynb_checkpoints/requirements-checkpoint.txt +13 -0
- README.md +5 -4
- app.py +241 -0
- examples/leopard.mp4 +3 -0
- examples/man_walking.mp4 +3 -0
- examples/neon.mp4 +3 -0
- examples/painter.mp4 +3 -0
- examples/woman.mp4 +3 -0
- requirements.txt +13 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
examples/man_walking.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/leopard.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
examples/woman.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
examples/neon.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
examples/painter.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
.gradio/cached_examples/21/Edited[[:space:]]Video/ab83f4b76b6dbcfa450f/tmptcf9qsfp.mp4 filter=lfs diff=lfs merge=lfs -text
|
.gradio/cached_examples/21/Edited Video/ab83f4b76b6dbcfa450f/tmptcf9qsfp.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ccaae8023fdc0a0f8a6e2232922b5dd4e72ddcc26279a3e73c40f3d7aa655e48
|
| 3 |
+
size 396712
|
.gradio/cached_examples/21/indices.csv
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
0
|
.gradio/cached_examples/21/log.csv
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Edited Video,timestamp
|
| 2 |
+
"{""video"": {""path"": "".gradio/cached_examples/21/Edited Video/ab83f4b76b6dbcfa450f/tmptcf9qsfp.mp4"", ""url"": ""/gradio_api/file=/tmp/gradio/6f16fb9b267c48ec5aeba92557b8df29ac8eb49eb6c344d8b58c3c22e4b13948/tmptcf9qsfp.mp4"", ""size"": null, ""orig_name"": ""tmptcf9qsfp.mp4"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}, ""subtitles"": null}",2025-09-19 03:00:58.163311
|
.gradio/certificate.pem
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-----BEGIN CERTIFICATE-----
|
| 2 |
+
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
|
| 31 |
+
-----END CERTIFICATE-----
|
.ipynb_checkpoints/app-checkpoint.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
pip uninstall torch torchvision torchaudio
|
| 3 |
+
pip install -U torch torchvision torchaudio
|
| 4 |
+
'''
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
import torch
|
| 8 |
+
import spaces
|
| 9 |
+
from typing import List
|
| 10 |
+
from PIL import Image
|
| 11 |
+
from diffusers import LucyEditPipeline, AutoencoderKLWan
|
| 12 |
+
from diffusers.utils import export_to_video, load_video
|
| 13 |
+
import tempfile
|
| 14 |
+
import os
|
| 15 |
+
|
| 16 |
+
model_id = "decart-ai/Lucy-Edit-Dev"
|
| 17 |
+
vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32)
|
| 18 |
+
pipe = LucyEditPipeline.from_pretrained(model_id, vae=vae, torch_dtype=torch.bfloat16)
|
| 19 |
+
#pipe.to("cuda")
|
| 20 |
+
pipe.enable_model_cpu_offload()
|
| 21 |
+
|
| 22 |
+
def calculate_resolution(input_width, input_height, min_dimension=480, max_dimension=832, compatible_round=32):
|
| 23 |
+
"""Calculate optimal resolution preserving aspect ratio within bounds"""
|
| 24 |
+
# Ensure dimensions are multiples of the compatible rounding
|
| 25 |
+
def round_to(x, compatible_round):
|
| 26 |
+
return max(min_dimension, min(max_dimension, int(round(x / compatible_round) * compatible_round)))
|
| 27 |
+
|
| 28 |
+
# Get aspect ratio
|
| 29 |
+
aspect_ratio = input_width / input_height
|
| 30 |
+
|
| 31 |
+
# Square videos (aspect ratio close to 1:1)
|
| 32 |
+
if 0.98 <= aspect_ratio <= 1.02:
|
| 33 |
+
return 640, 640
|
| 34 |
+
|
| 35 |
+
# Landscape videos (width > height)
|
| 36 |
+
elif aspect_ratio > 1:
|
| 37 |
+
# Try to use max width
|
| 38 |
+
new_width = max_dimension
|
| 39 |
+
new_height = new_width / aspect_ratio
|
| 40 |
+
|
| 41 |
+
# If height would be too small, use min height
|
| 42 |
+
if new_height < min_dimension:
|
| 43 |
+
new_height = min_dimension
|
| 44 |
+
new_width = new_height * aspect_ratio
|
| 45 |
+
# If width exceeds max, clamp it
|
| 46 |
+
if new_width > max_dimension:
|
| 47 |
+
new_width = max_dimension
|
| 48 |
+
|
| 49 |
+
return round_to(new_width, compatible_round), round_to(new_height, compatible_round)
|
| 50 |
+
|
| 51 |
+
# Portrait videos (height > width)
|
| 52 |
+
else:
|
| 53 |
+
# Try to use max height
|
| 54 |
+
new_height = max_dimension
|
| 55 |
+
new_width = new_height * aspect_ratio
|
| 56 |
+
|
| 57 |
+
# If width would be too small, use min width
|
| 58 |
+
if new_width < min_dimension:
|
| 59 |
+
new_width = min_dimension
|
| 60 |
+
new_height = new_width / aspect_ratio
|
| 61 |
+
# If height exceeds max, clamp it
|
| 62 |
+
if new_height > max_dimension:
|
| 63 |
+
new_height = max_dimension
|
| 64 |
+
|
| 65 |
+
return round_to(new_width, compatible_round), round_to(new_height, compatible_round)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
@spaces.GPU(duration=120)
|
| 69 |
+
def process_video(
|
| 70 |
+
video_path,
|
| 71 |
+
prompt,
|
| 72 |
+
negative_prompt="",
|
| 73 |
+
num_frames=81,
|
| 74 |
+
auto_resize=True,
|
| 75 |
+
manual_height=480,
|
| 76 |
+
manual_width=832,
|
| 77 |
+
guidance_scale=5,
|
| 78 |
+
progress=gr.Progress(track_tqdm=True)
|
| 79 |
+
):
|
| 80 |
+
# Load and preprocess video
|
| 81 |
+
progress(0.2, desc="Loading video...")
|
| 82 |
+
|
| 83 |
+
# Get video dimensions
|
| 84 |
+
temp_video = load_video(video_path)
|
| 85 |
+
print(len(temp_video))
|
| 86 |
+
if temp_video and len(temp_video) > 0:
|
| 87 |
+
original_width, original_height = temp_video[0].size
|
| 88 |
+
|
| 89 |
+
# Calculate dimensions
|
| 90 |
+
if auto_resize:
|
| 91 |
+
width, height = calculate_resolution(original_width, original_height)
|
| 92 |
+
else:
|
| 93 |
+
width, height = manual_width, manual_height
|
| 94 |
+
else:
|
| 95 |
+
raise gr.Error("Could not load video or video is empty")
|
| 96 |
+
|
| 97 |
+
# Convert video function
|
| 98 |
+
def convert_video(video: List[Image.Image]) -> List[Image.Image]:
|
| 99 |
+
# Ensure we don't exceed the video length
|
| 100 |
+
frames_to_load = min(len(video), num_frames)
|
| 101 |
+
video_frames = video[:frames_to_load]
|
| 102 |
+
# Resize frames
|
| 103 |
+
video_frames = [frame.resize((width, height)) for frame in video_frames]
|
| 104 |
+
return video_frames
|
| 105 |
+
|
| 106 |
+
# Load video from file path
|
| 107 |
+
video = load_video(video_path, convert_method=convert_video)
|
| 108 |
+
|
| 109 |
+
# Ensure we have the right number of frames
|
| 110 |
+
if len(video) < num_frames:
|
| 111 |
+
num_frames = len(video)
|
| 112 |
+
|
| 113 |
+
# Generate edited video
|
| 114 |
+
progress(0.5, desc="Generating edited video...")
|
| 115 |
+
output = pipe(
|
| 116 |
+
prompt=prompt,
|
| 117 |
+
video=video,
|
| 118 |
+
negative_prompt=negative_prompt,
|
| 119 |
+
height=height,
|
| 120 |
+
width=width,
|
| 121 |
+
num_frames=num_frames,
|
| 122 |
+
guidance_scale=guidance_scale,
|
| 123 |
+
).frames[0]
|
| 124 |
+
|
| 125 |
+
# Export to temporary file
|
| 126 |
+
progress(0.9, desc="Exporting video...")
|
| 127 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file:
|
| 128 |
+
output_path = tmp_file.name
|
| 129 |
+
|
| 130 |
+
export_to_video(output, output_path, fps=24)
|
| 131 |
+
|
| 132 |
+
progress(1.0, desc="Complete!")
|
| 133 |
+
return output_path
|
| 134 |
+
|
| 135 |
+
css = '''
|
| 136 |
+
.fillable{max-width: 1100px !important}
|
| 137 |
+
'''
|
| 138 |
+
with gr.Blocks(title="Lucy Edit - Video Editing with Text", css=css) as demo:
|
| 139 |
+
gr.HTML(f"""<p align="center">
|
| 140 |
+
<img src="https://huggingface.co/decart-ai/Lucy-Edit-Dev/resolve/main/assets/logo.png" width="480" style="margin-top: -25px" alt="Lucy Edit Dev Logo"/>
|
| 141 |
+
</p>
|
| 142 |
+
|
| 143 |
+
<p align="center">
|
| 144 |
+
🤗 <a href="https://github.com/DecartAI/lucy-edit-comfyui"><b>Model</b></a>
|
| 145 |
+
| 🧪 <a href="https://github.com/DecartAI/lucy-edit-comfyui"><b>ComfyUI</b></a>
|
| 146 |
+
| 📖 <a href="https://platform.decart.ai">Playground</a>
|
| 147 |
+
| 📑 <a href="#">arXiv (Coming soon)</a>
|
| 148 |
+
| 💬 <a href="https://discord.gg/decart">Discord</a>
|
| 149 |
+
</p>""")
|
| 150 |
+
|
| 151 |
+
with gr.Row():
|
| 152 |
+
with gr.Column(scale=1):
|
| 153 |
+
# Input controls
|
| 154 |
+
video_input = gr.Video(label="Input Video")
|
| 155 |
+
|
| 156 |
+
prompt = gr.Textbox(
|
| 157 |
+
label="Edit Prompt",
|
| 158 |
+
placeholder="Describe what you want to change in the video...",
|
| 159 |
+
lines=3
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
with gr.Accordion("Advanced Settings", open=False):
|
| 163 |
+
negative_prompt = gr.Textbox(
|
| 164 |
+
label="Negative Prompt (optional)",
|
| 165 |
+
placeholder="Describe what you DON'T want in the video...",
|
| 166 |
+
lines=2
|
| 167 |
+
)
|
| 168 |
+
auto_resize = gr.Checkbox(
|
| 169 |
+
label="Auto-resize (preserve aspect ratio)",
|
| 170 |
+
value=True,
|
| 171 |
+
info="Automatically calculate dimensions based on input video"
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
num_frames = gr.Slider(
|
| 175 |
+
label="Number of Frames",
|
| 176 |
+
minimum=1,
|
| 177 |
+
maximum=120,
|
| 178 |
+
value=81,
|
| 179 |
+
step=1,
|
| 180 |
+
info="More frames = longer processing time"
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
with gr.Row():
|
| 184 |
+
manual_height = gr.Slider(
|
| 185 |
+
label="Height (when auto-resize is off)",
|
| 186 |
+
minimum=256,
|
| 187 |
+
maximum=1024,
|
| 188 |
+
value=480,
|
| 189 |
+
step=32
|
| 190 |
+
)
|
| 191 |
+
manual_width = gr.Slider(
|
| 192 |
+
label="Width (when auto-resize is off)",
|
| 193 |
+
minimum=256,
|
| 194 |
+
maximum=1024,
|
| 195 |
+
value=832,
|
| 196 |
+
step=32
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
guidance_scale = gr.Slider(
|
| 200 |
+
label="Guidance Scale",
|
| 201 |
+
minimum=1.0,
|
| 202 |
+
maximum=20.0,
|
| 203 |
+
value=5.0,
|
| 204 |
+
step=0.5,
|
| 205 |
+
info="Higher values follow the prompt more strictly"
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
generate_btn = gr.Button("Edit Video", variant="primary")
|
| 209 |
+
|
| 210 |
+
with gr.Column(scale=1):
|
| 211 |
+
video_output = gr.Video(label="Edited Video", autoplay=True)
|
| 212 |
+
|
| 213 |
+
gr.Examples(
|
| 214 |
+
examples=[
|
| 215 |
+
["examples/neon.mp4", "Add a colorful scarlet macaw parrot perched on the man's left shoulder, bright red and blue wing feathers with yellow accents, curved black beak, intelligent dark eyes, talons gripping fabric naturally, long tail feathers extending downward, glossy plumage catching light, slight wing adjustment for balance, natural weight distribution, soft shadow beneath bird."],
|
| 216 |
+
["examples/painter.mp4", "Change the hair color to platinum blonde with natural highlights, subtle root shadowing, silky texture, gentle waves, soft shine, dimensional tones, strand definition, natural movement, professional color treatment, salon-quality finish, light-catching shimmer, varied blonde shades from honey to ash, realistic color gradation, healthy glossy appearance, volumetric lighting interaction."],
|
| 217 |
+
],
|
| 218 |
+
inputs=[video_input, prompt],
|
| 219 |
+
outputs=video_output,
|
| 220 |
+
fn=process_video,
|
| 221 |
+
cache_examples="lazy",
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
# Event handlers
|
| 225 |
+
generate_btn.click(
|
| 226 |
+
fn=process_video,
|
| 227 |
+
inputs=[
|
| 228 |
+
video_input,
|
| 229 |
+
prompt,
|
| 230 |
+
negative_prompt,
|
| 231 |
+
num_frames,
|
| 232 |
+
auto_resize,
|
| 233 |
+
manual_height,
|
| 234 |
+
manual_width,
|
| 235 |
+
guidance_scale
|
| 236 |
+
],
|
| 237 |
+
outputs=video_output
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
if __name__ == "__main__":
|
| 241 |
+
demo.launch(share=True)
|
.ipynb_checkpoints/requirements-checkpoint.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
git+https://github.com/huggingface/diffusers.git@8c72cd12ee65e420c86a0724f0182f966f339a7e
|
| 2 |
+
transformers
|
| 3 |
+
accelerate
|
| 4 |
+
safetensors
|
| 5 |
+
sentencepiece
|
| 6 |
+
imageio
|
| 7 |
+
imageio-ffmpeg
|
| 8 |
+
ftfy
|
| 9 |
+
|
| 10 |
+
huggingface_hub
|
| 11 |
+
gradio
|
| 12 |
+
httpx[socks]
|
| 13 |
+
spaces
|
README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
---
|
| 2 |
title: Lucy Edit Dev
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.46.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
title: Lucy Edit Dev
|
| 3 |
+
emoji: ✂️🎥
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.46.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
short_description: Video editing with a prompt
|
| 11 |
---
|
| 12 |
|
| 13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
pip uninstall torch torchvision torchaudio
|
| 3 |
+
pip install -U torch torchvision torchaudio
|
| 4 |
+
'''
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
import torch
|
| 8 |
+
import spaces
|
| 9 |
+
from typing import List
|
| 10 |
+
from PIL import Image
|
| 11 |
+
from diffusers import LucyEditPipeline, AutoencoderKLWan
|
| 12 |
+
from diffusers.utils import export_to_video, load_video
|
| 13 |
+
import tempfile
|
| 14 |
+
import os
|
| 15 |
+
|
| 16 |
+
model_id = "decart-ai/Lucy-Edit-Dev"
|
| 17 |
+
vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32)
|
| 18 |
+
pipe = LucyEditPipeline.from_pretrained(model_id, vae=vae, torch_dtype=torch.bfloat16)
|
| 19 |
+
#pipe.to("cuda")
|
| 20 |
+
pipe.enable_model_cpu_offload()
|
| 21 |
+
|
| 22 |
+
def calculate_resolution(input_width, input_height, min_dimension=480, max_dimension=832, compatible_round=32):
|
| 23 |
+
"""Calculate optimal resolution preserving aspect ratio within bounds"""
|
| 24 |
+
# Ensure dimensions are multiples of the compatible rounding
|
| 25 |
+
def round_to(x, compatible_round):
|
| 26 |
+
return max(min_dimension, min(max_dimension, int(round(x / compatible_round) * compatible_round)))
|
| 27 |
+
|
| 28 |
+
# Get aspect ratio
|
| 29 |
+
aspect_ratio = input_width / input_height
|
| 30 |
+
|
| 31 |
+
# Square videos (aspect ratio close to 1:1)
|
| 32 |
+
if 0.98 <= aspect_ratio <= 1.02:
|
| 33 |
+
return 640, 640
|
| 34 |
+
|
| 35 |
+
# Landscape videos (width > height)
|
| 36 |
+
elif aspect_ratio > 1:
|
| 37 |
+
# Try to use max width
|
| 38 |
+
new_width = max_dimension
|
| 39 |
+
new_height = new_width / aspect_ratio
|
| 40 |
+
|
| 41 |
+
# If height would be too small, use min height
|
| 42 |
+
if new_height < min_dimension:
|
| 43 |
+
new_height = min_dimension
|
| 44 |
+
new_width = new_height * aspect_ratio
|
| 45 |
+
# If width exceeds max, clamp it
|
| 46 |
+
if new_width > max_dimension:
|
| 47 |
+
new_width = max_dimension
|
| 48 |
+
|
| 49 |
+
return round_to(new_width, compatible_round), round_to(new_height, compatible_round)
|
| 50 |
+
|
| 51 |
+
# Portrait videos (height > width)
|
| 52 |
+
else:
|
| 53 |
+
# Try to use max height
|
| 54 |
+
new_height = max_dimension
|
| 55 |
+
new_width = new_height * aspect_ratio
|
| 56 |
+
|
| 57 |
+
# If width would be too small, use min width
|
| 58 |
+
if new_width < min_dimension:
|
| 59 |
+
new_width = min_dimension
|
| 60 |
+
new_height = new_width / aspect_ratio
|
| 61 |
+
# If height exceeds max, clamp it
|
| 62 |
+
if new_height > max_dimension:
|
| 63 |
+
new_height = max_dimension
|
| 64 |
+
|
| 65 |
+
return round_to(new_width, compatible_round), round_to(new_height, compatible_round)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
@spaces.GPU(duration=120)
|
| 69 |
+
def process_video(
|
| 70 |
+
video_path,
|
| 71 |
+
prompt,
|
| 72 |
+
negative_prompt="",
|
| 73 |
+
num_frames=81,
|
| 74 |
+
auto_resize=True,
|
| 75 |
+
manual_height=480,
|
| 76 |
+
manual_width=832,
|
| 77 |
+
guidance_scale=5,
|
| 78 |
+
progress=gr.Progress(track_tqdm=True)
|
| 79 |
+
):
|
| 80 |
+
# Load and preprocess video
|
| 81 |
+
progress(0.2, desc="Loading video...")
|
| 82 |
+
|
| 83 |
+
# Get video dimensions
|
| 84 |
+
temp_video = load_video(video_path)
|
| 85 |
+
print(len(temp_video))
|
| 86 |
+
if temp_video and len(temp_video) > 0:
|
| 87 |
+
original_width, original_height = temp_video[0].size
|
| 88 |
+
|
| 89 |
+
# Calculate dimensions
|
| 90 |
+
if auto_resize:
|
| 91 |
+
width, height = calculate_resolution(original_width, original_height)
|
| 92 |
+
else:
|
| 93 |
+
width, height = manual_width, manual_height
|
| 94 |
+
else:
|
| 95 |
+
raise gr.Error("Could not load video or video is empty")
|
| 96 |
+
|
| 97 |
+
# Convert video function
|
| 98 |
+
def convert_video(video: List[Image.Image]) -> List[Image.Image]:
|
| 99 |
+
# Ensure we don't exceed the video length
|
| 100 |
+
frames_to_load = min(len(video), num_frames)
|
| 101 |
+
video_frames = video[:frames_to_load]
|
| 102 |
+
# Resize frames
|
| 103 |
+
video_frames = [frame.resize((width, height)) for frame in video_frames]
|
| 104 |
+
return video_frames
|
| 105 |
+
|
| 106 |
+
# Load video from file path
|
| 107 |
+
video = load_video(video_path, convert_method=convert_video)
|
| 108 |
+
|
| 109 |
+
# Ensure we have the right number of frames
|
| 110 |
+
if len(video) < num_frames:
|
| 111 |
+
num_frames = len(video)
|
| 112 |
+
|
| 113 |
+
# Generate edited video
|
| 114 |
+
progress(0.5, desc="Generating edited video...")
|
| 115 |
+
output = pipe(
|
| 116 |
+
prompt=prompt,
|
| 117 |
+
video=video,
|
| 118 |
+
negative_prompt=negative_prompt,
|
| 119 |
+
height=height,
|
| 120 |
+
width=width,
|
| 121 |
+
num_frames=num_frames,
|
| 122 |
+
guidance_scale=guidance_scale,
|
| 123 |
+
).frames[0]
|
| 124 |
+
|
| 125 |
+
# Export to temporary file
|
| 126 |
+
progress(0.9, desc="Exporting video...")
|
| 127 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file:
|
| 128 |
+
output_path = tmp_file.name
|
| 129 |
+
|
| 130 |
+
export_to_video(output, output_path, fps=24)
|
| 131 |
+
|
| 132 |
+
progress(1.0, desc="Complete!")
|
| 133 |
+
return output_path
|
| 134 |
+
|
| 135 |
+
css = '''
|
| 136 |
+
.fillable{max-width: 1100px !important}
|
| 137 |
+
'''
|
| 138 |
+
with gr.Blocks(title="Lucy Edit - Video Editing with Text", css=css) as demo:
|
| 139 |
+
gr.HTML(f"""<p align="center">
|
| 140 |
+
<img src="https://huggingface.co/decart-ai/Lucy-Edit-Dev/resolve/main/assets/logo.png" width="480" style="margin-top: -25px" alt="Lucy Edit Dev Logo"/>
|
| 141 |
+
</p>
|
| 142 |
+
|
| 143 |
+
<p align="center">
|
| 144 |
+
🤗 <a href="https://github.com/DecartAI/lucy-edit-comfyui"><b>Model</b></a>
|
| 145 |
+
| 🧪 <a href="https://github.com/DecartAI/lucy-edit-comfyui"><b>ComfyUI</b></a>
|
| 146 |
+
| 📖 <a href="https://platform.decart.ai">Playground</a>
|
| 147 |
+
| 📑 <a href="#">arXiv (Coming soon)</a>
|
| 148 |
+
| 💬 <a href="https://discord.gg/decart">Discord</a>
|
| 149 |
+
</p>""")
|
| 150 |
+
|
| 151 |
+
with gr.Row():
|
| 152 |
+
with gr.Column(scale=1):
|
| 153 |
+
# Input controls
|
| 154 |
+
video_input = gr.Video(label="Input Video")
|
| 155 |
+
|
| 156 |
+
prompt = gr.Textbox(
|
| 157 |
+
label="Edit Prompt",
|
| 158 |
+
placeholder="Describe what you want to change in the video...",
|
| 159 |
+
lines=3
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
with gr.Accordion("Advanced Settings", open=False):
|
| 163 |
+
negative_prompt = gr.Textbox(
|
| 164 |
+
label="Negative Prompt (optional)",
|
| 165 |
+
placeholder="Describe what you DON'T want in the video...",
|
| 166 |
+
lines=2
|
| 167 |
+
)
|
| 168 |
+
auto_resize = gr.Checkbox(
|
| 169 |
+
label="Auto-resize (preserve aspect ratio)",
|
| 170 |
+
value=True,
|
| 171 |
+
info="Automatically calculate dimensions based on input video"
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
num_frames = gr.Slider(
|
| 175 |
+
label="Number of Frames",
|
| 176 |
+
minimum=1,
|
| 177 |
+
maximum=120,
|
| 178 |
+
value=81,
|
| 179 |
+
step=1,
|
| 180 |
+
info="More frames = longer processing time"
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
with gr.Row():
|
| 184 |
+
manual_height = gr.Slider(
|
| 185 |
+
label="Height (when auto-resize is off)",
|
| 186 |
+
minimum=256,
|
| 187 |
+
maximum=1024,
|
| 188 |
+
value=480,
|
| 189 |
+
step=32
|
| 190 |
+
)
|
| 191 |
+
manual_width = gr.Slider(
|
| 192 |
+
label="Width (when auto-resize is off)",
|
| 193 |
+
minimum=256,
|
| 194 |
+
maximum=1024,
|
| 195 |
+
value=832,
|
| 196 |
+
step=32
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
guidance_scale = gr.Slider(
|
| 200 |
+
label="Guidance Scale",
|
| 201 |
+
minimum=1.0,
|
| 202 |
+
maximum=20.0,
|
| 203 |
+
value=5.0,
|
| 204 |
+
step=0.5,
|
| 205 |
+
info="Higher values follow the prompt more strictly"
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
generate_btn = gr.Button("Edit Video", variant="primary")
|
| 209 |
+
|
| 210 |
+
with gr.Column(scale=1):
|
| 211 |
+
video_output = gr.Video(label="Edited Video", autoplay=True)
|
| 212 |
+
|
| 213 |
+
gr.Examples(
|
| 214 |
+
examples=[
|
| 215 |
+
["examples/neon.mp4", "Add a colorful scarlet macaw parrot perched on the man's left shoulder, bright red and blue wing feathers with yellow accents, curved black beak, intelligent dark eyes, talons gripping fabric naturally, long tail feathers extending downward, glossy plumage catching light, slight wing adjustment for balance, natural weight distribution, soft shadow beneath bird."],
|
| 216 |
+
["examples/painter.mp4", "Change the hair color to platinum blonde with natural highlights, subtle root shadowing, silky texture, gentle waves, soft shine, dimensional tones, strand definition, natural movement, professional color treatment, salon-quality finish, light-catching shimmer, varied blonde shades from honey to ash, realistic color gradation, healthy glossy appearance, volumetric lighting interaction."],
|
| 217 |
+
],
|
| 218 |
+
inputs=[video_input, prompt],
|
| 219 |
+
outputs=video_output,
|
| 220 |
+
fn=process_video,
|
| 221 |
+
cache_examples="lazy",
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
# Event handlers
|
| 225 |
+
generate_btn.click(
|
| 226 |
+
fn=process_video,
|
| 227 |
+
inputs=[
|
| 228 |
+
video_input,
|
| 229 |
+
prompt,
|
| 230 |
+
negative_prompt,
|
| 231 |
+
num_frames,
|
| 232 |
+
auto_resize,
|
| 233 |
+
manual_height,
|
| 234 |
+
manual_width,
|
| 235 |
+
guidance_scale
|
| 236 |
+
],
|
| 237 |
+
outputs=video_output
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
if __name__ == "__main__":
|
| 241 |
+
demo.launch(share=True)
|
examples/leopard.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:edcd64ede1c2e982d12bad891dfaf984330aafb9704065e7b3451837e6a1ab06
|
| 3 |
+
size 12796248
|
examples/man_walking.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a19fede8433fc08ffd9af62fb3594706d3db75e634a35e764ad3b66865c8b38a
|
| 3 |
+
size 16581071
|
examples/neon.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e62dc89173956d4d22defac1d24f0835c8d8d0490f7969dd13b38429d69165ca
|
| 3 |
+
size 3182890
|
examples/painter.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:37b385068db4c25d8f29104580b3267a4fafc2afbd4235f57a206a587a16e56f
|
| 3 |
+
size 2941421
|
examples/woman.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d70c43b33d8e8bfb33d3c7dc6e9bfbd5d4e655119026820c7ebdaceca6faeeed
|
| 3 |
+
size 18397860
|
requirements.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
git+https://github.com/huggingface/diffusers.git@8c72cd12ee65e420c86a0724f0182f966f339a7e
|
| 2 |
+
transformers
|
| 3 |
+
accelerate
|
| 4 |
+
safetensors
|
| 5 |
+
sentencepiece
|
| 6 |
+
imageio
|
| 7 |
+
imageio-ffmpeg
|
| 8 |
+
ftfy
|
| 9 |
+
|
| 10 |
+
huggingface_hub
|
| 11 |
+
gradio
|
| 12 |
+
httpx[socks]
|
| 13 |
+
spaces
|