Files changed (4) hide show
  1. .gitattributes +0 -2
  2. app.py +70 -60
  3. examples/neon.mp4 +0 -3
  4. examples/painter.mp4 +0 -3
.gitattributes CHANGED
@@ -36,5 +36,3 @@ saved_model/**/* 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
 
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
 
 
app.py CHANGED
@@ -13,53 +13,58 @@ vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=to
13
  pipe = LucyEditPipeline.from_pretrained(model_id, vae=vae, torch_dtype=torch.bfloat16)
14
  pipe.to("cuda")
15
 
16
- def calculate_resolution(input_width, input_height, min_dimension=480, max_dimension=832, compatible_round=32):
17
- """Calculate optimal resolution preserving aspect ratio within bounds"""
18
- # Ensure dimensions are multiples of the compatible rounding
19
- def round_to(x, compatible_round):
20
- return max(min_dimension, min(max_dimension, int(round(x / compatible_round) * compatible_round)))
21
-
22
  # Get aspect ratio
23
  aspect_ratio = input_width / input_height
24
-
25
- # Square videos (aspect ratio close to 1:1)
26
- if 0.98 <= aspect_ratio <= 1.02:
27
  return 640, 640
28
-
29
  # Landscape videos (width > height)
30
  elif aspect_ratio > 1:
31
- # Try to use max width
32
  new_width = max_dimension
33
- new_height = new_width / aspect_ratio
34
-
35
- # If height would be too small, use min height
36
  if new_height < min_dimension:
37
  new_height = min_dimension
38
- new_width = new_height * aspect_ratio
39
- # If width exceeds max, clamp it
 
40
  if new_width > max_dimension:
41
  new_width = max_dimension
42
-
43
- return round_to(new_width, compatible_round), round_to(new_height, compatible_round)
44
-
45
- # Portrait videos (height > width)
46
  else:
47
- # Try to use max height
48
  new_height = max_dimension
49
- new_width = new_height * aspect_ratio
50
-
51
- # If width would be too small, use min width
52
  if new_width < min_dimension:
53
  new_width = min_dimension
54
- new_height = new_width / aspect_ratio
55
- # If height exceeds max, clamp it
 
56
  if new_height > max_dimension:
57
  new_height = max_dimension
58
-
59
- return round_to(new_width, compatible_round), round_to(new_height, compatible_round)
60
-
61
-
62
- @spaces.GPU(duration=120)
 
 
 
 
63
  def process_video(
64
  video_path,
65
  prompt,
@@ -73,21 +78,24 @@ def process_video(
73
  ):
74
  # Load and preprocess video
75
  progress(0.2, desc="Loading video...")
76
-
77
  # Get video dimensions
78
  temp_video = load_video(video_path)
79
  print(len(temp_video))
80
  if temp_video and len(temp_video) > 0:
81
  original_width, original_height = temp_video[0].size
82
-
83
  # Calculate dimensions
84
  if auto_resize:
85
  width, height = calculate_resolution(original_width, original_height)
 
86
  else:
87
  width, height = manual_width, manual_height
 
 
88
  else:
89
  raise gr.Error("Could not load video or video is empty")
90
-
91
  # Convert video function
92
  def convert_video(video: List[Image.Image]) -> List[Image.Image]:
93
  # Ensure we don't exceed the video length
@@ -96,14 +104,15 @@ def process_video(
96
  # Resize frames
97
  video_frames = [frame.resize((width, height)) for frame in video_frames]
98
  return video_frames
99
-
100
  # Load video from file path
101
  video = load_video(video_path, convert_method=convert_video)
102
-
103
  # Ensure we have the right number of frames
104
  if len(video) < num_frames:
 
105
  num_frames = len(video)
106
-
107
  # Generate edited video
108
  progress(0.5, desc="Generating edited video...")
109
  output = pipe(
@@ -115,14 +124,14 @@ def process_video(
115
  num_frames=num_frames,
116
  guidance_scale=guidance_scale,
117
  ).frames[0]
118
-
119
  # Export to temporary file
120
  progress(0.9, desc="Exporting video...")
121
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file:
122
  output_path = tmp_file.name
123
-
124
  export_to_video(output, output_path, fps=24)
125
-
126
  progress(1.0, desc="Complete!")
127
  return output_path
128
 
@@ -141,18 +150,18 @@ with gr.Blocks(title="Lucy Edit - Video Editing with Text", css=css) as demo:
141
  &nbsp;|&nbsp; 📑 <a href="#">arXiv (Coming soon)</a>
142
  &nbsp;|&nbsp; 💬 <a href="https://discord.gg/decart">Discord</a>
143
  </p>""")
144
-
145
  with gr.Row():
146
  with gr.Column(scale=1):
147
  # Input controls
148
  video_input = gr.Video(label="Input Video")
149
-
150
  prompt = gr.Textbox(
151
  label="Edit Prompt",
152
  placeholder="Describe what you want to change in the video...",
153
  lines=3
154
  )
155
-
156
  with gr.Accordion("Advanced Settings", open=False):
157
  negative_prompt = gr.Textbox(
158
  label="Negative Prompt (optional)",
@@ -164,7 +173,7 @@ with gr.Blocks(title="Lucy Edit - Video Editing with Text", css=css) as demo:
164
  value=True,
165
  info="Automatically calculate dimensions based on input video"
166
  )
167
-
168
  num_frames = gr.Slider(
169
  label="Number of Frames",
170
  minimum=1,
@@ -173,7 +182,7 @@ with gr.Blocks(title="Lucy Edit - Video Editing with Text", css=css) as demo:
173
  step=1,
174
  info="More frames = longer processing time"
175
  )
176
-
177
  with gr.Row():
178
  manual_height = gr.Slider(
179
  label="Height (when auto-resize is off)",
@@ -189,7 +198,7 @@ with gr.Blocks(title="Lucy Edit - Video Editing with Text", css=css) as demo:
189
  value=832,
190
  step=32
191
  )
192
-
193
  guidance_scale = gr.Slider(
194
  label="Guidance Scale",
195
  minimum=1.0,
@@ -198,23 +207,24 @@ with gr.Blocks(title="Lucy Edit - Video Editing with Text", css=css) as demo:
198
  step=0.5,
199
  info="Higher values follow the prompt more strictly"
200
  )
201
-
202
  generate_btn = gr.Button("Edit Video", variant="primary")
203
-
204
  with gr.Column(scale=1):
205
- video_output = gr.Video(label="Edited Video", autoplay=True)
206
 
207
  gr.Examples(
208
- examples=[
209
- ["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."],
210
- ["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."],
211
- ],
212
- inputs=[video_input, prompt],
213
- outputs=video_output,
214
- fn=process_video,
215
- cache_examples="lazy",
216
- )
217
-
 
218
  # Event handlers
219
  generate_btn.click(
220
  fn=process_video,
@@ -232,4 +242,4 @@ with gr.Blocks(title="Lucy Edit - Video Editing with Text", css=css) as demo:
232
  )
233
 
234
  if __name__ == "__main__":
235
- demo.launch(share=True)
 
13
  pipe = LucyEditPipeline.from_pretrained(model_id, vae=vae, torch_dtype=torch.bfloat16)
14
  pipe.to("cuda")
15
 
16
+ def calculate_resolution(input_width, input_height, min_dimension=480, max_dimension=832):
17
+ """Calculate optimal resolution preserving aspect ratio"""
18
+ # Ensure dimensions are multiples of 16
19
+ def round_to_16(x):
20
+ return int(round(x / 16.0) * 16)
21
+
22
  # Get aspect ratio
23
  aspect_ratio = input_width / input_height
24
+
25
+ # Square videos
26
+ if 0.95 <= aspect_ratio <= 1.05:
27
  return 640, 640
28
+
29
  # Landscape videos (width > height)
30
  elif aspect_ratio > 1:
31
+ # Start with max width
32
  new_width = max_dimension
33
+ new_height = int(new_width / aspect_ratio)
34
+
35
+ # If height is too small, use min height instead
36
  if new_height < min_dimension:
37
  new_height = min_dimension
38
+ new_width = int(new_height * aspect_ratio)
39
+
40
+ # Clamp width if needed
41
  if new_width > max_dimension:
42
  new_width = max_dimension
43
+ new_height = int(new_width / aspect_ratio)
44
+
45
+ # Portrait videos (height > width)
 
46
  else:
47
+ # Start with max height
48
  new_height = max_dimension
49
+ new_width = int(new_height * aspect_ratio)
50
+
51
+ # If width is too small, use min width instead
52
  if new_width < min_dimension:
53
  new_width = min_dimension
54
+ new_height = int(new_width / aspect_ratio)
55
+
56
+ # Clamp height if needed
57
  if new_height > max_dimension:
58
  new_height = max_dimension
59
+ new_width = int(new_height / aspect_ratio)
60
+
61
+ # Round to multiples of 16 and ensure within bounds
62
+ final_width = round_to_16(max(min_dimension, min(max_dimension, new_width)))
63
+ final_height = round_to_16(max(min_dimension, min(max_dimension, new_height)))
64
+
65
+ return final_width, final_height
66
+
67
+ @spaces.GPU(duration=90)
68
  def process_video(
69
  video_path,
70
  prompt,
 
78
  ):
79
  # Load and preprocess video
80
  progress(0.2, desc="Loading video...")
81
+
82
  # Get video dimensions
83
  temp_video = load_video(video_path)
84
  print(len(temp_video))
85
  if temp_video and len(temp_video) > 0:
86
  original_width, original_height = temp_video[0].size
87
+
88
  # Calculate dimensions
89
  if auto_resize:
90
  width, height = calculate_resolution(original_width, original_height)
91
+ gr.Info(f"Auto-resized from {original_width}x{original_height} to {width}x{height} (preserving aspect ratio)")
92
  else:
93
  width, height = manual_width, manual_height
94
+ if abs((original_width/original_height) - (width/height)) > 0.1:
95
+ gr.Warning(f"Output aspect ratio ({width}x{height}) differs significantly from input ({original_width}x{original_height}). Video may appear stretched.")
96
  else:
97
  raise gr.Error("Could not load video or video is empty")
98
+
99
  # Convert video function
100
  def convert_video(video: List[Image.Image]) -> List[Image.Image]:
101
  # Ensure we don't exceed the video length
 
104
  # Resize frames
105
  video_frames = [frame.resize((width, height)) for frame in video_frames]
106
  return video_frames
107
+
108
  # Load video from file path
109
  video = load_video(video_path, convert_method=convert_video)
110
+
111
  # Ensure we have the right number of frames
112
  if len(video) < num_frames:
113
+ gr.Warning(f"Video has only {len(video)} frames, using all available frames.")
114
  num_frames = len(video)
115
+
116
  # Generate edited video
117
  progress(0.5, desc="Generating edited video...")
118
  output = pipe(
 
124
  num_frames=num_frames,
125
  guidance_scale=guidance_scale,
126
  ).frames[0]
127
+
128
  # Export to temporary file
129
  progress(0.9, desc="Exporting video...")
130
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file:
131
  output_path = tmp_file.name
132
+
133
  export_to_video(output, output_path, fps=24)
134
+
135
  progress(1.0, desc="Complete!")
136
  return output_path
137
 
 
150
  &nbsp;|&nbsp; 📑 <a href="#">arXiv (Coming soon)</a>
151
  &nbsp;|&nbsp; 💬 <a href="https://discord.gg/decart">Discord</a>
152
  </p>""")
153
+
154
  with gr.Row():
155
  with gr.Column(scale=1):
156
  # Input controls
157
  video_input = gr.Video(label="Input Video")
158
+
159
  prompt = gr.Textbox(
160
  label="Edit Prompt",
161
  placeholder="Describe what you want to change in the video...",
162
  lines=3
163
  )
164
+
165
  with gr.Accordion("Advanced Settings", open=False):
166
  negative_prompt = gr.Textbox(
167
  label="Negative Prompt (optional)",
 
173
  value=True,
174
  info="Automatically calculate dimensions based on input video"
175
  )
176
+
177
  num_frames = gr.Slider(
178
  label="Number of Frames",
179
  minimum=1,
 
182
  step=1,
183
  info="More frames = longer processing time"
184
  )
185
+
186
  with gr.Row():
187
  manual_height = gr.Slider(
188
  label="Height (when auto-resize is off)",
 
198
  value=832,
199
  step=32
200
  )
201
+
202
  guidance_scale = gr.Slider(
203
  label="Guidance Scale",
204
  minimum=1.0,
 
207
  step=0.5,
208
  info="Higher values follow the prompt more strictly"
209
  )
210
+
211
  generate_btn = gr.Button("Edit Video", variant="primary")
212
+
213
  with gr.Column(scale=1):
214
+ video_output = gr.Video(label="Edited Video")
215
 
216
  gr.Examples(
217
+ examples=[
218
+ ["examples/man_walking.mp4", "make the man into an alien"],
219
+ ["examples/leopard.mp4", "make the leopard into a lion"],
220
+ ["examples/woman.mp4", "make the woman's coat blue"],
221
+ ],
222
+ inputs=[video_input, prompt],
223
+ outputs=video_output,
224
+ fn=process_video,
225
+ cache_examples="lazy",
226
+ )
227
+
228
  # Event handlers
229
  generate_btn.click(
230
  fn=process_video,
 
242
  )
243
 
244
  if __name__ == "__main__":
245
+ demo.launch(share=True)
examples/neon.mp4 DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:e62dc89173956d4d22defac1d24f0835c8d8d0490f7969dd13b38429d69165ca
3
- size 3182890
 
 
 
 
examples/painter.mp4 DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:37b385068db4c25d8f29104580b3267a4fafc2afbd4235f57a206a587a16e56f
3
- size 2941421