someone-in-the-world Claude Sonnet 5 commited on
Commit
9fbf3f5
·
1 Parent(s): 819311d

Wire up graceful degradation and effect preview (FR-7, QF-2, QF-3)

Browse files

- A failed interpolation or upscaling stage now surfaces a gr.Warning and falls back to
the last successfully-produced result instead of discarding the generated video (QF-2).
- Adds a live "Output: N frames @ X fps, WxHpx" preview that updates as duration, video
fluidity, and upscale settings change, before the user clicks Generate (QF-3).

Closes #7

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +46 -8
app.py CHANGED
@@ -73,13 +73,21 @@ def run_inference(
73
  def _report_interpolation_progress(done: int, total: int) -> None:
74
  progress(0.7 + 0.2 * (done / total), desc=f"Interpolating frames ({done}/{total})...")
75
 
76
- final_frames = interpolate_frames(
77
- raw_frames, multiplier=int(frame_factor), progress_callback=_report_interpolation_progress
78
- )
 
 
 
 
 
 
 
 
79
  else:
80
  final_frames = list(raw_frames)
 
81
 
82
- final_fps = FIXED_FPS * frame_factor
83
  return final_frames, final_fps
84
 
85
 
@@ -123,10 +131,15 @@ def generate_video(
123
  progress(0.95 + 0.04 * (done / total), desc=f"Upscaling 4x on CPU ({done}/{total} frames)...")
124
 
125
  progress(0.95, desc=f"Upscaling 4x on CPU (0/{len(final_frames)} frames)...")
126
- pil_frames = [
127
- Image.fromarray((np.asarray(frame) * 255.0).round().astype(np.uint8)) for frame in final_frames
128
- ]
129
- final_frames = upscale_frames(pil_frames, progress_callback=_report_upscale_progress)
 
 
 
 
 
130
 
131
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
132
  video_path = tmpfile.name
@@ -135,6 +148,25 @@ def generate_video(
135
  return video_path, video_path, current_seed
136
 
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  with gr.Blocks() as demo:
139
  gr.Markdown("# High Quality Video Generation")
140
  gr.Markdown("Turn a still image into a short video clip guided by a text prompt.")
@@ -164,6 +196,7 @@ with gr.Blocks() as demo:
164
  guidance_scale_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1.0, label="Guidance Scale")
165
  seed_input = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42)
166
  randomize_seed_checkbox = gr.Checkbox(label="Randomize seed", value=True)
 
167
  generate_button = gr.Button("Generate Video", variant="primary")
168
  with gr.Column():
169
  video_output = gr.Video(label="Generated Video", autoplay=True)
@@ -186,5 +219,10 @@ with gr.Blocks() as demo:
186
  outputs=[video_output, file_output, seed_input],
187
  )
188
 
 
 
 
 
 
189
  if __name__ == "__main__":
190
  demo.queue().launch(show_error=True)
 
73
  def _report_interpolation_progress(done: int, total: int) -> None:
74
  progress(0.7 + 0.2 * (done / total), desc=f"Interpolating frames ({done}/{total})...")
75
 
76
+ try:
77
+ final_frames = interpolate_frames(
78
+ raw_frames, multiplier=int(frame_factor), progress_callback=_report_interpolation_progress
79
+ )
80
+ final_fps = FIXED_FPS * frame_factor
81
+ except Exception as e:
82
+ # QF-2: don't discard a successfully generated base video over a post-processing failure.
83
+ print(f"Interpolation failed, returning base video: {e}")
84
+ gr.Warning(f"Frame interpolation failed ({e}); returning the video without it.")
85
+ final_frames = list(raw_frames)
86
+ final_fps = FIXED_FPS
87
  else:
88
  final_frames = list(raw_frames)
89
+ final_fps = FIXED_FPS
90
 
 
91
  return final_frames, final_fps
92
 
93
 
 
131
  progress(0.95 + 0.04 * (done / total), desc=f"Upscaling 4x on CPU ({done}/{total} frames)...")
132
 
133
  progress(0.95, desc=f"Upscaling 4x on CPU (0/{len(final_frames)} frames)...")
134
+ try:
135
+ pil_frames = [
136
+ Image.fromarray((np.asarray(frame) * 255.0).round().astype(np.uint8)) for frame in final_frames
137
+ ]
138
+ final_frames = upscale_frames(pil_frames, progress_callback=_report_upscale_progress)
139
+ except Exception as e:
140
+ # QF-2: a failed upscale still returns the pre-upscale (interpolated/base) result.
141
+ print(f"Upscaling failed, returning pre-upscale video: {e}")
142
+ gr.Warning(f"Upscaling failed ({e}); returning the video without it.")
143
 
144
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
145
  video_path = tmpfile.name
 
148
  return video_path, video_path, current_seed
149
 
150
 
151
+ def preview_effect(input_image, duration_seconds, frame_multiplier, upscale_output):
152
+ """QF-3: show the resulting fps/resolution/duration before the user hits Generate."""
153
+ num_frames = get_num_frames(duration_seconds)
154
+ frame_factor = frame_multiplier // FIXED_FPS
155
+ out_fps = FIXED_FPS * frame_factor if frame_factor > 1 else FIXED_FPS
156
+ out_frame_count = ((num_frames - 1) * frame_factor + 1) if frame_factor > 1 else num_frames
157
+
158
+ if input_image is not None:
159
+ w, h = resize_image(input_image).size
160
+ else:
161
+ w = h = None
162
+
163
+ if upscale_output and w is not None:
164
+ w, h = w * 4, h * 4
165
+
166
+ resolution = f"{w}×{h}px" if w is not None else "resolution depends on the uploaded image"
167
+ return f"**Output:** ~{out_frame_count} frames @ {out_fps} fps, {resolution}"
168
+
169
+
170
  with gr.Blocks() as demo:
171
  gr.Markdown("# High Quality Video Generation")
172
  gr.Markdown("Turn a still image into a short video clip guided by a text prompt.")
 
196
  guidance_scale_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1.0, label="Guidance Scale")
197
  seed_input = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42)
198
  randomize_seed_checkbox = gr.Checkbox(label="Randomize seed", value=True)
199
+ effect_preview = gr.Markdown()
200
  generate_button = gr.Button("Generate Video", variant="primary")
201
  with gr.Column():
202
  video_output = gr.Video(label="Generated Video", autoplay=True)
 
219
  outputs=[video_output, file_output, seed_input],
220
  )
221
 
222
+ preview_inputs = [input_image_component, duration_input, frame_multiplier_input, upscale_checkbox]
223
+ for component in preview_inputs:
224
+ component.change(fn=preview_effect, inputs=preview_inputs, outputs=effect_preview)
225
+ demo.load(fn=preview_effect, inputs=preview_inputs, outputs=effect_preview)
226
+
227
  if __name__ == "__main__":
228
  demo.queue().launch(show_error=True)