MySafeCode commited on
Commit
af797a7
Β·
verified Β·
1 Parent(s): 20207fc

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +428 -410
app.py CHANGED
@@ -1,411 +1,429 @@
1
-
2
-
3
- import gradio as gr
4
- import cv2
5
- import numpy as np
6
- import trimesh
7
- import tempfile
8
- import os
9
- import logging
10
-
11
- # Set up logging
12
- logging.basicConfig(level=logging.INFO)
13
- logger = logging.getLogger(__name__)
14
-
15
- # Fix OpenGL issues for headless environments
16
- os.environ['PYOPENGL_PLATFORM'] = 'osmesa'
17
-
18
- # -------------------------
19
- # GLOBAL (checkerboard persistence)
20
- # -------------------------
21
- _checkerboard_colors = None
22
-
23
- # -------------------------
24
- # VIDEO LOADING (BGR β†’ RGB FIXED βœ…)
25
- # -------------------------
26
- def read_video_frames(video_path, start=0, end=None, frame_step=1):
27
- """Read video frames with proper error handling"""
28
- try:
29
- cap = cv2.VideoCapture(video_path)
30
- if not cap.isOpened():
31
- raise ValueError(f"Cannot open video file: {video_path}")
32
-
33
- frames = []
34
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
35
-
36
- if total_frames == 0:
37
- raise ValueError("Video file appears to be empty or corrupted")
38
-
39
- if end is None or end > total_frames:
40
- end = total_frames
41
-
42
- count = 0
43
- frames_read = 0
44
-
45
- while True:
46
- ret, frame = cap.read()
47
- if not ret or count >= end:
48
- break
49
-
50
- if count >= start and (count - start) % frame_step == 0:
51
- # FIX COLOR ORDER HERE
52
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
53
- frames.append(frame)
54
- frames_read += 1
55
-
56
- count += 1
57
-
58
- cap.release()
59
-
60
- if not frames:
61
- raise ValueError("No frames could be read from the video")
62
-
63
- logger.info(f"Successfully read {frames_read} frames")
64
- return np.array(frames)
65
-
66
- except Exception as e:
67
- logger.error(f"Error reading video: {str(e)}")
68
- raise
69
-
70
- # -------------------------
71
- # DOWNSAMPLING
72
- # -------------------------
73
- def downsample_frames(frames, block_size=1, method='stride'):
74
- """Downsample frames with better error handling"""
75
- if block_size == 1 or frames.size == 0:
76
- return frames
77
-
78
- z, h, w, c = frames.shape
79
-
80
- if method == 'stride':
81
- return frames[:, ::block_size, ::block_size]
82
-
83
- elif method == 'mean':
84
- new_h = h // block_size
85
- new_w = w // block_size
86
- out = np.zeros((z, new_h, new_w, c), dtype=np.uint8)
87
-
88
- for zi in range(z):
89
- for i in range(new_h):
90
- for j in range(new_w):
91
- block = frames[
92
- zi,
93
- i*block_size:(i+1)*block_size,
94
- j*block_size:(j+1)*block_size
95
- ]
96
- if block.size > 0:
97
- out[zi, i, j] = block.mean(axis=(0,1)).astype(np.uint8)
98
- return out
99
-
100
- return frames
101
-
102
- # -------------------------
103
- # VOXEL MASK
104
- # -------------------------
105
- def frames_to_voxels(frames, threshold=10):
106
- """Convert frames to voxel representation"""
107
- if frames.size == 0:
108
- return np.array([])
109
-
110
- # Ensure we're working with the right dimensions
111
- if len(frames.shape) == 4:
112
- return (np.sum(frames, axis=3) > threshold)
113
- else:
114
- raise ValueError("Frames must be 4D array (z, h, w, c)")
115
-
116
- # -------------------------
117
- # VOXEL β†’ MESH (FIXED COLORS βœ…)
118
- # -------------------------
119
- def voxels_to_mesh(frames, voxels, voxel_size=1.0):
120
- """Convert voxels to mesh with proper color handling"""
121
- if voxels.size == 0 or frames.size == 0:
122
- return trimesh.Scene()
123
-
124
- meshes = []
125
- z_len, h, w = voxels.shape
126
-
127
- for z in range(z_len):
128
- for y in range(h):
129
- for x in range(w):
130
- if voxels[z, y, x]:
131
- # Ensure we have valid frame dimensions
132
- if z < frames.shape[0] and y < frames.shape[1] and x < frames.shape[2]:
133
- color = frames[z, frames.shape[1] - 1 - y, x].astype(np.uint8)
134
-
135
- try:
136
- cube = trimesh.creation.box(extents=[voxel_size]*3)
137
- cube.apply_translation([x, y, z])
138
-
139
- # Apply colors correctly (RGBA uint8)
140
- rgba = np.append(color, 255)
141
- cube.visual.face_colors = np.tile(rgba, (12,1))
142
-
143
- meshes.append(cube)
144
- except Exception as e:
145
- logger.warning(f"Could not create cube at position ({x}, {y}, {z}): {str(e)}")
146
-
147
- if meshes:
148
- try:
149
- return trimesh.util.concatenate(meshes)
150
- except Exception as e:
151
- logger.warning(f"Could not concatenate meshes: {str(e)}")
152
- return meshes[0] if meshes else trimesh.Scene()
153
-
154
- return trimesh.Scene()
155
-
156
- # -------------------------
157
- # RANDOM CHECKERBOARD (ONE-TIME COLORS βœ…)
158
- # -------------------------
159
- def default_checkerboard():
160
- """Generate a default checkerboard pattern"""
161
- global _checkerboard_colors
162
-
163
- h, w, z_len = 10, 10, 2
164
- frames = np.zeros((z_len, h, w, 3), dtype=np.uint8)
165
-
166
- if _checkerboard_colors is None:
167
- _checkerboard_colors = np.random.randint(
168
- 0, 256, size=(z_len, h, w, 3), dtype=np.uint8
169
- )
170
-
171
- for z in range(z_len):
172
- for y in range(h):
173
- for x in range(w):
174
- if (x + y + z) % 2 == 0:
175
- frames[z, y, x] = [0, 0, 0]
176
- else:
177
- frames[z, y, x] = _checkerboard_colors[z, y, x]
178
-
179
- try:
180
- voxels = frames_to_voxels(frames, threshold=1)
181
- mesh = voxels_to_mesh(frames, voxels, voxel_size=2)
182
-
183
- tmp = tempfile.gettempdir()
184
- obj = os.path.join(tmp, "checkerboard.obj")
185
- glb = os.path.join(tmp, "checkerboard.glb")
186
-
187
- mesh.export(obj)
188
- mesh.export(glb)
189
-
190
- return obj, glb, glb
191
- except Exception as e:
192
- logger.error(f"Error creating checkerboard: {str(e)}")
193
- raise
194
-
195
- # -------------------------
196
- # MAIN GENERATOR
197
- # -------------------------
198
- def generate_voxel_files(
199
- video_file,
200
- start_frame,
201
- end_frame,
202
- frame_step,
203
- block_size,
204
- downsample_method
205
- ):
206
- """Main function to generate voxel files from video"""
207
- try:
208
- if video_file is None:
209
- logger.info("No video file provided, generating checkerboard")
210
- return default_checkerboard()
211
-
212
- # Ensure video_file has a valid name attribute
213
- video_path = getattr(video_file, 'name', video_file)
214
- if not video_path or not os.path.exists(video_path):
215
- raise ValueError("Invalid video file path")
216
-
217
- logger.info(f"Processing video: {video_path}")
218
-
219
- frames = read_video_frames(
220
- video_path,
221
- start=start_frame,
222
- end=end_frame,
223
- frame_step=frame_step
224
- )
225
-
226
- if frames.size == 0:
227
- raise ValueError("No frames could be processed")
228
-
229
- frames = downsample_frames(
230
- frames,
231
- block_size=block_size,
232
- method=downsample_method
233
- )
234
-
235
- voxels = frames_to_voxels(frames)
236
- mesh = voxels_to_mesh(frames, voxels)
237
-
238
- tmp = tempfile.gettempdir()
239
- obj = os.path.join(tmp, "output.obj")
240
- glb = os.path.join(tmp, "output.glb")
241
-
242
- mesh.export(obj)
243
- mesh.export(glb)
244
-
245
- logger.info("Successfully generated voxel files")
246
- return obj, glb, glb
247
-
248
- except Exception as e:
249
- logger.error(f"Error in generate_voxel_files: {str(e)}")
250
- # Return checkerboard as fallback
251
- return default_checkerboard()
252
-
253
- # -------------------------
254
- # GRADIO 6.0+ UI - MINIMAL VERSION
255
- # -------------------------
256
- def create_interface():
257
- """Create minimal Gradio interface compatible with your version"""
258
-
259
- # Use gr.Blocks without any styling parameters
260
- with gr.Blocks(title="MP4 β†’ Voxels β†’ 3D") as interface:
261
-
262
- gr.Markdown("# πŸ“Ή MP4 β†’ Voxels β†’ 3D")
263
- gr.Markdown("Convert video files into voxelized 3D meshes. If no file is uploaded, a random-color checkerboard appears.")
264
-
265
- with gr.Row():
266
- with gr.Column(scale=1):
267
- video_input = gr.File(
268
- label="Upload MP4 Video",
269
- file_types=["video"],
270
- file_count="single"
271
- )
272
-
273
- gr.Markdown("### Frame Settings")
274
- start_frame = gr.Slider(
275
- minimum=0,
276
- maximum=500,
277
- value=0,
278
- step=1,
279
- label="Start Frame"
280
- )
281
-
282
- end_frame = gr.Slider(
283
- minimum=0,
284
- maximum=500,
285
- value=50,
286
- step=1,
287
- label="End Frame"
288
- )
289
-
290
- frame_step = gr.Slider(
291
- minimum=1,
292
- maximum=10,
293
- value=1,
294
- step=1,
295
- label="Frame Step"
296
- )
297
-
298
- gr.Markdown("### Processing Settings")
299
- block_size = gr.Slider(
300
- minimum=1,
301
- maximum=32,
302
- value=32, # DEFAULT
303
- step=1,
304
- label="Pixel Block Size"
305
- )
306
-
307
- downsample_method = gr.Radio(
308
- choices=["stride", "mean"],
309
- value="stride",
310
- label="Downsample Method"
311
- )
312
-
313
- process_btn = gr.Button("πŸ”„ Convert to Voxels", variant="primary")
314
-
315
- with gr.Column(scale=2):
316
- with gr.Row():
317
- obj_output = gr.File(label="OBJ File", file_types=[".obj"])
318
- glb_output = gr.File(label="GLB File", file_types=[".glb"])
319
-
320
- model_3d = gr.Model3D(
321
- label="3D Preview",
322
- height=600
323
- )
324
-
325
- status = gr.Textbox(
326
- label="Status",
327
- value="Ready to process...",
328
- interactive=False
329
- )
330
-
331
- # Event handlers
332
- def process_with_status(video_file, start, end, step, block, method):
333
- try:
334
- result = generate_voxel_files(video_file, start, end, step, block, method)
335
- if result and len(result) == 3:
336
- obj_path, glb_path, glb_preview = result
337
- return (
338
- gr.update(value="βœ… Processing complete!"),
339
- obj_path,
340
- glb_path,
341
- glb_preview
342
- )
343
- else:
344
- return (
345
- gr.update(value="❌ Processing failed"),
346
- None,
347
- None,
348
- None
349
- )
350
- except Exception as e:
351
- logger.error(f"Processing error: {str(e)}")
352
- return (
353
- gr.update(value=f"❌ Error: {str(e)}"),
354
- None,
355
- None,
356
- None
357
- )
358
-
359
- # Connect the button click event
360
- process_btn.click(
361
- fn=process_with_status,
362
- inputs=[
363
- video_input,
364
- start_frame,
365
- end_frame,
366
- frame_step,
367
- block_size,
368
- downsample_method
369
- ],
370
- outputs=[
371
- status,
372
- obj_output,
373
- glb_output,
374
- model_3d
375
- ]
376
- )
377
-
378
- # Auto-process when video is uploaded
379
- video_input.upload(
380
- fn=process_with_status,
381
- inputs=[
382
- video_input,
383
- start_frame,
384
- end_frame,
385
- frame_step,
386
- block_size,
387
- downsample_method
388
- ],
389
- outputs=[
390
- status,
391
- obj_output,
392
- glb_output,
393
- model_3d
394
- ]
395
- )
396
-
397
- return interface
398
-
399
- if __name__ == "__main__":
400
- try:
401
- interface = create_interface()
402
- interface.launch(
403
- server_name="0.0.0.0",
404
- server_port=7860,
405
- debug=True,
406
- share=False,
407
- show_error=True
408
- )
409
- except Exception as e:
410
- logger.error(f"Failed to launch Gradio interface: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  raise
 
1
+ import faulthandler
2
+ faulthandler.enable()
3
+
4
+ import gradio as gr
5
+ import cv2
6
+ import numpy as np
7
+ import trimesh
8
+ import tempfile
9
+ import os
10
+ import logging
11
+
12
+ # Set up logging
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Fix OpenGL issues for headless environments
17
+ os.environ['PYOPENGL_PLATFORM'] = 'osmesa'
18
+
19
+ # -------------------------
20
+ # GLOBAL (checkerboard persistence)
21
+ # -------------------------
22
+ _checkerboard_colors = None
23
+
24
+ # -------------------------
25
+ # VIDEO LOADING (BGR β†’ RGB FIXED βœ…)
26
+ # -------------------------
27
+ def read_video_frames(video_path, start=0, end=None, frame_step=1):
28
+ """Read video frames with proper error handling"""
29
+ try:
30
+ cap = cv2.VideoCapture(video_path)
31
+ if not cap.isOpened():
32
+ raise ValueError(f"Cannot open video file: {video_path}")
33
+
34
+ frames = []
35
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
36
+
37
+ if total_frames == 0:
38
+ raise ValueError("Video file appears to be empty or corrupted")
39
+
40
+ if end is None or end > total_frames:
41
+ end = total_frames
42
+
43
+ count = 0
44
+ frames_read = 0
45
+
46
+ while True:
47
+ ret, frame = cap.read()
48
+ if not ret or count >= end:
49
+ break
50
+
51
+ if count >= start and (count - start) % frame_step == 0:
52
+ # FIX COLOR ORDER HERE
53
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
54
+ frames.append(frame)
55
+ frames_read += 1
56
+
57
+ count += 1
58
+
59
+ cap.release()
60
+
61
+ if not frames:
62
+ raise ValueError("No frames could be read from the video")
63
+
64
+ logger.info(f"Successfully read {frames_read} frames")
65
+ return np.array(frames)
66
+
67
+ except Exception as e:
68
+ logger.error(f"Error reading video: {str(e)}")
69
+ raise
70
+
71
+ # -------------------------
72
+ # DOWNSAMPLING
73
+ # -------------------------
74
+ def downsample_frames(frames, block_size=1, method='stride'):
75
+ """Downsample frames with better error handling"""
76
+ if block_size == 1 or frames.size == 0:
77
+ return frames
78
+
79
+ z, h, w, c = frames.shape
80
+
81
+ if method == 'stride':
82
+ return frames[:, ::block_size, ::block_size]
83
+
84
+ elif method == 'mean':
85
+ new_h = h // block_size
86
+ new_w = w // block_size
87
+ out = np.zeros((z, new_h, new_w, c), dtype=np.uint8)
88
+
89
+ for zi in range(z):
90
+ for i in range(new_h):
91
+ for j in range(new_w):
92
+ block = frames[
93
+ zi,
94
+ i*block_size:(i+1)*block_size,
95
+ j*block_size:(j+1)*block_size
96
+ ]
97
+ if block.size > 0:
98
+ out[zi, i, j] = block.mean(axis=(0,1)).astype(np.uint8)
99
+ return out
100
+
101
+ return frames
102
+
103
+ # -------------------------
104
+ # VOXEL MASK
105
+ # -------------------------
106
+ def frames_to_voxels(frames, threshold=10):
107
+ """Convert frames to voxel representation"""
108
+ if frames.size == 0:
109
+ return np.array([])
110
+
111
+ # Ensure we're working with the right dimensions
112
+ if len(frames.shape) == 4:
113
+ return (np.sum(frames, axis=3) > threshold)
114
+ else:
115
+ raise ValueError("Frames must be 4D array (z, h, w, c)")
116
+
117
+ # -------------------------
118
+ # VOXEL β†’ MESH (FIXED COLORS βœ…)
119
+ # -------------------------
120
+ def voxels_to_mesh(frames, voxels, voxel_size=1.0):
121
+ """Convert voxels to mesh with proper color handling"""
122
+ if voxels.size == 0 or frames.size == 0:
123
+ return trimesh.Scene()
124
+
125
+ meshes = []
126
+ z_len, h, w = voxels.shape
127
+
128
+ for z in range(z_len):
129
+ for y in range(h):
130
+ for x in range(w):
131
+ if voxels[z, y, x]:
132
+ # Ensure we have valid frame dimensions
133
+ if z < frames.shape[0] and y < frames.shape[1] and x < frames.shape[2]:
134
+ color = frames[z, frames.shape[1] - 1 - y, x].astype(np.uint8)
135
+
136
+ try:
137
+ cube = trimesh.creation.box(extents=[voxel_size]*3)
138
+ cube.apply_translation([x, y, z])
139
+
140
+ # Apply colors correctly (RGBA uint8)
141
+ rgba = np.append(color, 255)
142
+ cube.visual.face_colors = np.tile(rgba, (12,1))
143
+
144
+ meshes.append(cube)
145
+ except Exception as e:
146
+ logger.warning(f"Could not create cube at position ({x}, {y}, {z}): {str(e)}")
147
+
148
+ if meshes:
149
+ try:
150
+ return trimesh.util.concatenate(meshes)
151
+ except Exception as e:
152
+ logger.warning(f"Could not concatenate meshes: {str(e)}")
153
+ return meshes[0] if meshes else trimesh.Scene()
154
+
155
+ return trimesh.Scene()
156
+
157
+ # -------------------------
158
+ # RANDOM CHECKERBOARD (ONE-TIME COLORS βœ…)
159
+ # -------------------------
160
+ def default_checkerboard():
161
+ """Generate a default checkerboard pattern"""
162
+ global _checkerboard_colors
163
+
164
+ h, w, z_len = 10, 10, 2
165
+ frames = np.zeros((z_len, h, w, 3), dtype=np.uint8)
166
+
167
+ if _checkerboard_colors is None:
168
+ _checkerboard_colors = np.random.randint(
169
+ 0, 256, size=(z_len, h, w, 3), dtype=np.uint8
170
+ )
171
+
172
+ for z in range(z_len):
173
+ for y in range(h):
174
+ for x in range(w):
175
+ if (x + y + z) % 2 == 0:
176
+ frames[z, y, x] = [0, 0, 0]
177
+ else:
178
+ frames[z, y, x] = _checkerboard_colors[z, y, x]
179
+
180
+ try:
181
+ voxels = frames_to_voxels(frames, threshold=1)
182
+ mesh = voxels_to_mesh(frames, voxels, voxel_size=2)
183
+
184
+ tmp = tempfile.gettempdir()
185
+ obj = os.path.join(tmp, "checkerboard.obj")
186
+ glb = os.path.join(tmp, "checkerboard.glb")
187
+
188
+ mesh.export(obj)
189
+ mesh.export(glb)
190
+
191
+ return obj, glb, glb
192
+ except Exception as e:
193
+ logger.error(f"Error creating checkerboard: {str(e)}")
194
+ raise
195
+
196
+ # -------------------------
197
+ # MAIN GENERATOR
198
+ # -------------------------
199
+ def generate_voxel_files(
200
+ video_file,
201
+ start_frame,
202
+ end_frame,
203
+ frame_step,
204
+ block_size,
205
+ downsample_method
206
+ ):
207
+ """Main function to generate voxel files from video"""
208
+ try:
209
+ if video_file is None:
210
+ logger.info("No video file provided, generating checkerboard")
211
+ return default_checkerboard()
212
+
213
+ # Ensure video_file has a valid name attribute
214
+ video_path = getattr(video_file, 'name', video_file)
215
+ if not video_path or not os.path.exists(video_path):
216
+ raise ValueError("Invalid video file path")
217
+
218
+ logger.info(f"Processing video: {video_path}")
219
+
220
+ frames = read_video_frames(
221
+ video_path,
222
+ start=start_frame,
223
+ end=end_frame,
224
+ frame_step=frame_step
225
+ )
226
+
227
+ if frames.size == 0:
228
+ raise ValueError("No frames could be processed")
229
+
230
+ frames = downsample_frames(
231
+ frames,
232
+ block_size=block_size,
233
+ method=downsample_method
234
+ )
235
+
236
+ voxels = frames_to_voxels(frames)
237
+ mesh = voxels_to_mesh(frames, voxels)
238
+
239
+ tmp = tempfile.gettempdir()
240
+ obj = os.path.join(tmp, "output.obj")
241
+ glb = os.path.join(tmp, "output.glb")
242
+
243
+ mesh.export(obj)
244
+ mesh.export(glb)
245
+
246
+ logger.info("Successfully generated voxel files")
247
+ return obj, glb, glb
248
+
249
+ except Exception as e:
250
+ logger.error(f"Error in generate_voxel_files: {str(e)}")
251
+ # Return checkerboard as fallback
252
+ return default_checkerboard()
253
+
254
+ # -------------------------
255
+ # GRADIO 6.0+ UI WITH BLOCKS
256
+ # -------------------------
257
+ def create_interface():
258
+ """Create Gradio 6.0+ compatible interface using Blocks"""
259
+
260
+ with gr.Blocks(
261
+ title="MP4 β†’ Voxels β†’ 3D",
262
+ theme=gr.themes.Soft(),
263
+ css="""
264
+ .gradio-container {max-width: 1200px !important; margin: auto !important;}
265
+ .output-file {margin: 10px 0;}
266
+ """
267
+ ) as interface:
268
+
269
+ gr.Markdown("# πŸ“Ή MP4 β†’ Voxels β†’ 3D")
270
+ gr.Markdown("Convert video files into voxelized 3D meshes. If no file is uploaded, a random-color checkerboard appears.")
271
+
272
+ with gr.Row():
273
+ with gr.Column(scale=1):
274
+ video_input = gr.File(
275
+ label="Upload MP4 Video",
276
+ file_types=["video"],
277
+ file_count="single"
278
+ )
279
+
280
+ gr.Markdown("### Frame Settings")
281
+ start_frame = gr.Slider(
282
+ minimum=0,
283
+ maximum=500,
284
+ value=0,
285
+ step=1,
286
+ label="Start Frame"
287
+ )
288
+
289
+ end_frame = gr.Slider(
290
+ minimum=0,
291
+ maximum=500,
292
+ value=50,
293
+ step=1,
294
+ label="End Frame"
295
+ )
296
+
297
+ frame_step = gr.Slider(
298
+ minimum=1,
299
+ maximum=10,
300
+ value=1,
301
+ step=1,
302
+ label="Frame Step"
303
+ )
304
+
305
+ gr.Markdown("### Processing Settings")
306
+ block_size = gr.Slider(
307
+ minimum=1,
308
+ maximum=32,
309
+ value=1,
310
+ step=1,
311
+ label="Pixel Block Size"
312
+ )
313
+
314
+ downsample_method = gr.Radio(
315
+ choices=["stride", "mean"],
316
+ value="stride",
317
+ label="Downsample Method"
318
+ )
319
+
320
+ process_btn = gr.Button("πŸ”„ Convert to Voxels", variant="primary")
321
+
322
+ with gr.Column(scale=2):
323
+ with gr.Row():
324
+ obj_output = gr.File(label="OBJ File", file_types=[".obj"])
325
+ glb_output = gr.File(label="GLB File", file_types=[".glb"])
326
+
327
+ model_3d = gr.Model3D(
328
+ label="3D Preview",
329
+ height=600,
330
+ camera_position=[0, 0, 0]
331
+ )
332
+
333
+ status = gr.Textbox(
334
+ label="Status",
335
+ value="Ready to process...",
336
+ interactive=False
337
+ )
338
+
339
+ # Event handlers
340
+ def update_status(message):
341
+ return gr.update(value=message)
342
+
343
+ def process_with_status(video_file, start, end, step, block, method):
344
+ status_update = gr.update(value="Processing video...")
345
+ yield [status_update, None, None, None]
346
+
347
+ try:
348
+ result = generate_voxel_files(video_file, start, end, step, block, method)
349
+ if result and len(result) == 3:
350
+ obj_path, glb_path, glb_preview = result
351
+ status_update = gr.update(value="βœ… Processing complete!")
352
+ yield [status_update, obj_path, glb_path, glb_preview]
353
+ else:
354
+ status_update = gr.update(value="❌ Processing failed")
355
+ yield [status_update, None, None, None]
356
+ except Exception as e:
357
+ logger.error(f"Processing error: {str(e)}")
358
+ status_update = gr.update(value=f"❌ Error: {str(e)}")
359
+ yield [status_update, None, None, None]
360
+
361
+ # Connect the button click event
362
+ process_btn.click(
363
+ fn=process_with_status,
364
+ inputs=[
365
+ video_input,
366
+ start_frame,
367
+ end_frame,
368
+ frame_step,
369
+ block_size,
370
+ downsample_method
371
+ ],
372
+ outputs=[
373
+ status,
374
+ obj_output,
375
+ glb_output,
376
+ model_3d
377
+ ]
378
+ )
379
+
380
+ # Auto-process when video is uploaded
381
+ video_input.upload(
382
+ fn=process_with_status,
383
+ inputs=[
384
+ video_input,
385
+ start_frame,
386
+ end_frame,
387
+ frame_step,
388
+ block_size,
389
+ downsample_method
390
+ ],
391
+ outputs=[
392
+ status,
393
+ obj_output,
394
+ glb_output,
395
+ model_3d
396
+ ]
397
+ )
398
+
399
+ # Examples
400
+ gr.Examples(
401
+ examples=[
402
+ [None, 0, 50, 1, 1, "stride"]
403
+ ],
404
+ inputs=[
405
+ video_input,
406
+ start_frame,
407
+ end_frame,
408
+ frame_step,
409
+ block_size,
410
+ downsample_method
411
+ ],
412
+ label="Example Configurations"
413
+ )
414
+
415
+ return interface
416
+
417
+ if __name__ == "__main__":
418
+ try:
419
+ interface = create_interface()
420
+ interface.launch(
421
+ server_name="0.0.0.0",
422
+ server_port=7860,
423
+ debug=True,
424
+ share=False,
425
+ show_error=True
426
+ )
427
+ except Exception as e:
428
+ logger.error(f"Failed to launch Gradio interface: {str(e)}")
429
  raise