Spaces:
Sleeping
ποΈ AWESOME: Add Advanced Video Export Controls!
Browse filesNEW CONTROLS ADDED:
β
Effect Intensity Slider (0.1x to 3.0x)
- Control how strong camera movements are
- 0.5x = subtle, professional
- 1.0x = balanced default
- 2.0x = dramatic, bold
- 3.0x = extreme effects!
β
Number of Loops (1-10)
- Repeat animations seamlessly
- Perfect for longer videos
β
Video Quality Selection
- High: 8 Mbps (best quality)
- Medium: 5 Mbps (balanced)
- Low: 3 Mbps (smaller files)
β
Extended Duration (1-30s)
- Previously limited to 10s
- Now up to 30s per loop!
β
More Resolution Options
- Added 4K UHD (3840x2160)
- Portrait 1080p (1080x1920)
- Portrait 720p (720x1280)
- Kept all existing options
IMPROVEMENTS:
π¬ All 14 effects now respect intensity multiplier
π Better progress info showing total duration and loops
π Improved filename with resolution and FPS
π‘ Help tooltips for all new controls
π Updated documentation
NOW MATCHES LOCAL VERSION FEATURES!
All the power of the local Three.js version
Available on HuggingFace Spaces!
π€ Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
|
@@ -154,9 +154,17 @@ if 'depth_colored' in st.session_state:
|
|
| 154 |
col_vid1, col_vid2 = st.columns(2)
|
| 155 |
|
| 156 |
with col_vid1:
|
| 157 |
-
video_duration = st.slider("Duration (seconds)", 1, 10,
|
| 158 |
video_fps = st.selectbox("FPS", [24, 30, 60], index=1)
|
| 159 |
-
video_resolution = st.selectbox("Resolution", [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
with col_vid2:
|
| 162 |
video_effect = st.selectbox("Camera Effect", [
|
|
@@ -176,6 +184,22 @@ if 'depth_colored' in st.session_state:
|
|
| 176 |
"Orbit"
|
| 177 |
])
|
| 178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
if st.button("π¬ Export Video", type="primary"):
|
| 180 |
with st.spinner("Generating video..."):
|
| 181 |
try:
|
|
@@ -186,21 +210,38 @@ if 'depth_colored' in st.session_state:
|
|
| 186 |
# This ensures we export the real photo with camera effects, not the colored depth visualization
|
| 187 |
original_image = st.session_state['original_image']
|
| 188 |
|
| 189 |
-
#
|
| 190 |
-
if
|
| 191 |
-
width, height =
|
| 192 |
-
elif
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
height, width = original_image.shape[:2]
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
# Resize original image (not depth map!)
|
| 200 |
image_resized = cv2.resize(original_image, (width, height))
|
| 201 |
|
| 202 |
-
#
|
| 203 |
-
|
|
|
|
| 204 |
|
| 205 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
|
| 206 |
output_path = tmp_file.name
|
|
@@ -209,11 +250,13 @@ if 'depth_colored' in st.session_state:
|
|
| 209 |
out = cv2.VideoWriter(output_path, fourcc, video_fps, (width, height))
|
| 210 |
|
| 211 |
for frame_num in range(total_frames):
|
| 212 |
-
progress
|
|
|
|
| 213 |
|
| 214 |
# Apply effect - NOW USING REAL PHOTO instead of depth map!
|
|
|
|
| 215 |
if video_effect == "Zoom In":
|
| 216 |
-
scale = 1.0 + (progress * 0.5)
|
| 217 |
center_x, center_y = width // 2, height // 2
|
| 218 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 219 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
@@ -222,7 +265,7 @@ if 'depth_colored' in st.session_state:
|
|
| 222 |
frame = cv2.resize(cropped, (width, height))
|
| 223 |
|
| 224 |
elif video_effect == "Zoom Out":
|
| 225 |
-
scale = 1.5 - (progress * 0.5)
|
| 226 |
center_x, center_y = width // 2, height // 2
|
| 227 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 228 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
@@ -232,9 +275,9 @@ if 'depth_colored' in st.session_state:
|
|
| 232 |
|
| 233 |
elif video_effect == "Ken Burns (Zoom + Pan)":
|
| 234 |
# Ken Burns: zoom in while panning
|
| 235 |
-
scale = 1.0 + (progress * 0.4)
|
| 236 |
-
pan_x = int(width * progress * 0.2)
|
| 237 |
-
pan_y = int(height * progress * 0.1)
|
| 238 |
center_x = width // 2 + pan_x
|
| 239 |
center_y = height // 2 + pan_y
|
| 240 |
new_w, new_h = int(width / scale), int(height / scale)
|
|
@@ -245,7 +288,7 @@ if 'depth_colored' in st.session_state:
|
|
| 245 |
|
| 246 |
elif video_effect == "Dolly In":
|
| 247 |
# Dolly in: smooth zoom with slight scale
|
| 248 |
-
scale = 1.0 + (progress * 0.3)
|
| 249 |
center_x, center_y = width // 2, height // 2
|
| 250 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 251 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
@@ -254,7 +297,7 @@ if 'depth_colored' in st.session_state:
|
|
| 254 |
frame = cv2.resize(cropped, (width, height))
|
| 255 |
|
| 256 |
elif video_effect == "Dolly Out":
|
| 257 |
-
scale = 1.3 - (progress * 0.3)
|
| 258 |
center_x, center_y = width // 2, height // 2
|
| 259 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 260 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
@@ -263,24 +306,24 @@ if 'depth_colored' in st.session_state:
|
|
| 263 |
frame = cv2.resize(cropped, (width, height))
|
| 264 |
|
| 265 |
elif video_effect == "Pan Left":
|
| 266 |
-
offset = int(width * progress * 0.3)
|
| 267 |
frame = np.roll(image_resized, -offset, axis=1)
|
| 268 |
|
| 269 |
elif video_effect == "Pan Right":
|
| 270 |
-
offset = int(width * progress * 0.3)
|
| 271 |
frame = np.roll(image_resized, offset, axis=1)
|
| 272 |
|
| 273 |
elif video_effect == "Pan Up":
|
| 274 |
-
offset = int(height * progress * 0.3)
|
| 275 |
frame = np.roll(image_resized, -offset, axis=0)
|
| 276 |
|
| 277 |
elif video_effect == "Pan Down":
|
| 278 |
-
offset = int(height * progress * 0.3)
|
| 279 |
frame = np.roll(image_resized, offset, axis=0)
|
| 280 |
|
| 281 |
elif video_effect == "Tilt Up":
|
| 282 |
# Tilt up: perspective transformation
|
| 283 |
-
tilt_factor = progress * 0.3
|
| 284 |
pts1 = np.float32([[0, 0], [width, 0], [0, height], [width, height]])
|
| 285 |
pts2 = np.float32([
|
| 286 |
[0, int(height * tilt_factor)],
|
|
@@ -292,7 +335,7 @@ if 'depth_colored' in st.session_state:
|
|
| 292 |
frame = cv2.warpPerspective(image_resized, matrix, (width, height))
|
| 293 |
|
| 294 |
elif video_effect == "Tilt Down":
|
| 295 |
-
tilt_factor = progress * 0.3
|
| 296 |
pts1 = np.float32([[0, 0], [width, 0], [0, height], [width, height]])
|
| 297 |
pts2 = np.float32([
|
| 298 |
[0, 0],
|
|
@@ -304,21 +347,21 @@ if 'depth_colored' in st.session_state:
|
|
| 304 |
frame = cv2.warpPerspective(image_resized, matrix, (width, height))
|
| 305 |
|
| 306 |
elif video_effect == "Rotate CW":
|
| 307 |
-
angle = progress * 360
|
| 308 |
center = (width // 2, height // 2)
|
| 309 |
rotation_matrix = cv2.getRotationMatrix2D(center, -angle, 1.0)
|
| 310 |
frame = cv2.warpAffine(image_resized, rotation_matrix, (width, height))
|
| 311 |
|
| 312 |
elif video_effect == "Rotate CCW":
|
| 313 |
-
angle = progress * 360
|
| 314 |
center = (width // 2, height // 2)
|
| 315 |
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
|
| 316 |
frame = cv2.warpAffine(image_resized, rotation_matrix, (width, height))
|
| 317 |
|
| 318 |
elif video_effect == "Orbit":
|
| 319 |
# Orbit: rotate + slight zoom
|
| 320 |
-
angle = progress * 360
|
| 321 |
-
scale = 1.0 + (np.sin(progress * np.pi) * 0.2)
|
| 322 |
center = (width // 2, height // 2)
|
| 323 |
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
|
| 324 |
frame = cv2.warpAffine(image_resized, rotation_matrix, (width, height))
|
|
@@ -336,11 +379,13 @@ if 'depth_colored' in st.session_state:
|
|
| 336 |
with open(output_path, 'rb') as f:
|
| 337 |
video_bytes = f.read()
|
| 338 |
|
| 339 |
-
|
|
|
|
|
|
|
| 340 |
st.download_button(
|
| 341 |
label="π₯ Download Video",
|
| 342 |
data=video_bytes,
|
| 343 |
-
file_name=f"
|
| 344 |
mime="video/mp4"
|
| 345 |
)
|
| 346 |
|
|
@@ -361,6 +406,18 @@ st.markdown("""
|
|
| 361 |
- β
Fast processing (~800ms on CPU, ~200ms on GPU)
|
| 362 |
- β
SUPERB quality depth maps
|
| 363 |
- β
**Professional video export** with cinematic camera movements
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
|
| 365 |
### Camera Effects:
|
| 366 |
- πΉ **Zoom In/Out** - Smooth zoom controls
|
|
|
|
| 154 |
col_vid1, col_vid2 = st.columns(2)
|
| 155 |
|
| 156 |
with col_vid1:
|
| 157 |
+
video_duration = st.slider("Duration (seconds)", 1, 30, 10, help="Length of each animation loop")
|
| 158 |
video_fps = st.selectbox("FPS", [24, 30, 60], index=1)
|
| 159 |
+
video_resolution = st.selectbox("Resolution", [
|
| 160 |
+
"Original",
|
| 161 |
+
"4K UHD (3840x2160)",
|
| 162 |
+
"1080p (1920x1080)",
|
| 163 |
+
"720p (1280x720)",
|
| 164 |
+
"Square 1080p (1080x1080)",
|
| 165 |
+
"Portrait 1080p (1080x1920)",
|
| 166 |
+
"Portrait 720p (720x1280)"
|
| 167 |
+
], index=2)
|
| 168 |
|
| 169 |
with col_vid2:
|
| 170 |
video_effect = st.selectbox("Camera Effect", [
|
|
|
|
| 184 |
"Orbit"
|
| 185 |
])
|
| 186 |
|
| 187 |
+
effect_intensity = st.slider("Effect Intensity", 0.1, 3.0, 1.0, 0.1,
|
| 188 |
+
help="Control how strong the camera movement is (0.5 = subtle, 2.0 = dramatic)")
|
| 189 |
+
|
| 190 |
+
# Additional controls row
|
| 191 |
+
col_vid3, col_vid4 = st.columns(2)
|
| 192 |
+
with col_vid3:
|
| 193 |
+
loop_count = st.slider("Number of Loops", 1, 10, 1,
|
| 194 |
+
help="How many times to repeat the animation")
|
| 195 |
+
|
| 196 |
+
with col_vid4:
|
| 197 |
+
video_quality = st.selectbox("Video Quality", [
|
| 198 |
+
"High (8 Mbps)",
|
| 199 |
+
"Medium (5 Mbps)",
|
| 200 |
+
"Low (3 Mbps)"
|
| 201 |
+
], index=0)
|
| 202 |
+
|
| 203 |
if st.button("π¬ Export Video", type="primary"):
|
| 204 |
with st.spinner("Generating video..."):
|
| 205 |
try:
|
|
|
|
| 210 |
# This ensures we export the real photo with camera effects, not the colored depth visualization
|
| 211 |
original_image = st.session_state['original_image']
|
| 212 |
|
| 213 |
+
# Parse resolution
|
| 214 |
+
if "4K" in video_resolution:
|
| 215 |
+
width, height = 3840, 2160
|
| 216 |
+
elif "1080p" in video_resolution:
|
| 217 |
+
if "Portrait" in video_resolution:
|
| 218 |
+
width, height = 1080, 1920
|
| 219 |
+
elif "Square" in video_resolution:
|
| 220 |
+
width, height = 1080, 1080
|
| 221 |
+
else:
|
| 222 |
+
width, height = 1920, 1080
|
| 223 |
+
elif "720p" in video_resolution:
|
| 224 |
+
if "Portrait" in video_resolution:
|
| 225 |
+
width, height = 720, 1280
|
| 226 |
+
else:
|
| 227 |
+
width, height = 1280, 720
|
| 228 |
+
else: # Original
|
| 229 |
height, width = original_image.shape[:2]
|
| 230 |
|
| 231 |
+
# Parse video quality/bitrate
|
| 232 |
+
if "High" in video_quality:
|
| 233 |
+
bitrate = 8_000_000
|
| 234 |
+
elif "Medium" in video_quality:
|
| 235 |
+
bitrate = 5_000_000
|
| 236 |
+
else: # Low
|
| 237 |
+
bitrate = 3_000_000
|
| 238 |
+
|
| 239 |
# Resize original image (not depth map!)
|
| 240 |
image_resized = cv2.resize(original_image, (width, height))
|
| 241 |
|
| 242 |
+
# Calculate total frames with loops
|
| 243 |
+
frames_per_loop = video_duration * video_fps
|
| 244 |
+
total_frames = frames_per_loop * loop_count
|
| 245 |
|
| 246 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
|
| 247 |
output_path = tmp_file.name
|
|
|
|
| 250 |
out = cv2.VideoWriter(output_path, fourcc, video_fps, (width, height))
|
| 251 |
|
| 252 |
for frame_num in range(total_frames):
|
| 253 |
+
# Calculate progress within current loop (0 to 1)
|
| 254 |
+
progress = (frame_num % frames_per_loop) / frames_per_loop
|
| 255 |
|
| 256 |
# Apply effect - NOW USING REAL PHOTO instead of depth map!
|
| 257 |
+
# Effect intensity multiplier allows user to control how dramatic the movement is
|
| 258 |
if video_effect == "Zoom In":
|
| 259 |
+
scale = 1.0 + (progress * 0.5 * effect_intensity)
|
| 260 |
center_x, center_y = width // 2, height // 2
|
| 261 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 262 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
|
|
| 265 |
frame = cv2.resize(cropped, (width, height))
|
| 266 |
|
| 267 |
elif video_effect == "Zoom Out":
|
| 268 |
+
scale = 1.5 - (progress * 0.5 * effect_intensity)
|
| 269 |
center_x, center_y = width // 2, height // 2
|
| 270 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 271 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
|
|
| 275 |
|
| 276 |
elif video_effect == "Ken Burns (Zoom + Pan)":
|
| 277 |
# Ken Burns: zoom in while panning
|
| 278 |
+
scale = 1.0 + (progress * 0.4 * effect_intensity)
|
| 279 |
+
pan_x = int(width * progress * 0.2 * effect_intensity)
|
| 280 |
+
pan_y = int(height * progress * 0.1 * effect_intensity)
|
| 281 |
center_x = width // 2 + pan_x
|
| 282 |
center_y = height // 2 + pan_y
|
| 283 |
new_w, new_h = int(width / scale), int(height / scale)
|
|
|
|
| 288 |
|
| 289 |
elif video_effect == "Dolly In":
|
| 290 |
# Dolly in: smooth zoom with slight scale
|
| 291 |
+
scale = 1.0 + (progress * 0.3 * effect_intensity)
|
| 292 |
center_x, center_y = width // 2, height // 2
|
| 293 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 294 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
|
|
| 297 |
frame = cv2.resize(cropped, (width, height))
|
| 298 |
|
| 299 |
elif video_effect == "Dolly Out":
|
| 300 |
+
scale = 1.3 - (progress * 0.3 * effect_intensity)
|
| 301 |
center_x, center_y = width // 2, height // 2
|
| 302 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 303 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
|
|
| 306 |
frame = cv2.resize(cropped, (width, height))
|
| 307 |
|
| 308 |
elif video_effect == "Pan Left":
|
| 309 |
+
offset = int(width * progress * 0.3 * effect_intensity)
|
| 310 |
frame = np.roll(image_resized, -offset, axis=1)
|
| 311 |
|
| 312 |
elif video_effect == "Pan Right":
|
| 313 |
+
offset = int(width * progress * 0.3 * effect_intensity)
|
| 314 |
frame = np.roll(image_resized, offset, axis=1)
|
| 315 |
|
| 316 |
elif video_effect == "Pan Up":
|
| 317 |
+
offset = int(height * progress * 0.3 * effect_intensity)
|
| 318 |
frame = np.roll(image_resized, -offset, axis=0)
|
| 319 |
|
| 320 |
elif video_effect == "Pan Down":
|
| 321 |
+
offset = int(height * progress * 0.3 * effect_intensity)
|
| 322 |
frame = np.roll(image_resized, offset, axis=0)
|
| 323 |
|
| 324 |
elif video_effect == "Tilt Up":
|
| 325 |
# Tilt up: perspective transformation
|
| 326 |
+
tilt_factor = progress * 0.3 * effect_intensity
|
| 327 |
pts1 = np.float32([[0, 0], [width, 0], [0, height], [width, height]])
|
| 328 |
pts2 = np.float32([
|
| 329 |
[0, int(height * tilt_factor)],
|
|
|
|
| 335 |
frame = cv2.warpPerspective(image_resized, matrix, (width, height))
|
| 336 |
|
| 337 |
elif video_effect == "Tilt Down":
|
| 338 |
+
tilt_factor = progress * 0.3 * effect_intensity
|
| 339 |
pts1 = np.float32([[0, 0], [width, 0], [0, height], [width, height]])
|
| 340 |
pts2 = np.float32([
|
| 341 |
[0, 0],
|
|
|
|
| 347 |
frame = cv2.warpPerspective(image_resized, matrix, (width, height))
|
| 348 |
|
| 349 |
elif video_effect == "Rotate CW":
|
| 350 |
+
angle = progress * 360 * effect_intensity
|
| 351 |
center = (width // 2, height // 2)
|
| 352 |
rotation_matrix = cv2.getRotationMatrix2D(center, -angle, 1.0)
|
| 353 |
frame = cv2.warpAffine(image_resized, rotation_matrix, (width, height))
|
| 354 |
|
| 355 |
elif video_effect == "Rotate CCW":
|
| 356 |
+
angle = progress * 360 * effect_intensity
|
| 357 |
center = (width // 2, height // 2)
|
| 358 |
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
|
| 359 |
frame = cv2.warpAffine(image_resized, rotation_matrix, (width, height))
|
| 360 |
|
| 361 |
elif video_effect == "Orbit":
|
| 362 |
# Orbit: rotate + slight zoom
|
| 363 |
+
angle = progress * 360 * effect_intensity
|
| 364 |
+
scale = 1.0 + (np.sin(progress * np.pi) * 0.2 * effect_intensity)
|
| 365 |
center = (width // 2, height // 2)
|
| 366 |
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
|
| 367 |
frame = cv2.warpAffine(image_resized, rotation_matrix, (width, height))
|
|
|
|
| 379 |
with open(output_path, 'rb') as f:
|
| 380 |
video_bytes = f.read()
|
| 381 |
|
| 382 |
+
total_duration = video_duration * loop_count
|
| 383 |
+
st.success(f"β
Video generated! {total_frames} frames at {video_fps} FPS ({total_duration}s total, {loop_count} loop{'s' if loop_count > 1 else ''})")
|
| 384 |
+
st.info(f"π Settings: {video_resolution} | {video_quality} | Effect Intensity: {effect_intensity}x")
|
| 385 |
st.download_button(
|
| 386 |
label="π₯ Download Video",
|
| 387 |
data=video_bytes,
|
| 388 |
+
file_name=f"dimensio_{video_effect.lower().replace(' ', '_').replace('(', '').replace(')', '')}_{width}x{height}_{video_fps}fps.mp4",
|
| 389 |
mime="video/mp4"
|
| 390 |
)
|
| 391 |
|
|
|
|
| 406 |
- β
Fast processing (~800ms on CPU, ~200ms on GPU)
|
| 407 |
- β
SUPERB quality depth maps
|
| 408 |
- β
**Professional video export** with cinematic camera movements
|
| 409 |
+
- β
**Advanced controls** - Effect intensity, loops, quality settings
|
| 410 |
+
|
| 411 |
+
### Video Export Controls:
|
| 412 |
+
- β±οΈ **Duration** - 1 to 30 seconds per loop
|
| 413 |
+
- π **Loops** - Repeat animation 1-10 times
|
| 414 |
+
- ποΈ **Effect Intensity** - Control movement strength (0.1x to 3.0x)
|
| 415 |
+
- 0.5x = Subtle, professional movements
|
| 416 |
+
- 1.0x = Default, balanced effects
|
| 417 |
+
- 2.0x = Dramatic, bold camera work
|
| 418 |
+
- π **Resolutions** - Original, 4K UHD, 1080p, 720p, Square, Portrait modes
|
| 419 |
+
- π¬ **Quality** - High (8 Mbps), Medium (5 Mbps), Low (3 Mbps)
|
| 420 |
+
- ποΈ **Frame Rates** - 24fps (cinematic), 30fps (standard), 60fps (smooth)
|
| 421 |
|
| 422 |
### Camera Effects:
|
| 423 |
- πΉ **Zoom In/Out** - Smooth zoom controls
|