MySafeCode commited on
Commit
77ec1fd
Β·
verified Β·
1 Parent(s): d13e896

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +411 -428
app.py CHANGED
@@ -1,429 +1,412 @@
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
 
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 - MINIMAL VERSION
256
+ # -------------------------
257
+ def create_interface():
258
+ """Create minimal Gradio interface compatible with your version"""
259
+
260
+ # Use gr.Blocks without any styling parameters
261
+ with gr.Blocks(title="MP4 β†’ Voxels β†’ 3D") as interface:
262
+
263
+ gr.Markdown("# πŸ“Ή MP4 β†’ Voxels β†’ 3D")
264
+ gr.Markdown("Convert video files into voxelized 3D meshes. If no file is uploaded, a random-color checkerboard appears.")
265
+
266
+ with gr.Row():
267
+ with gr.Column(scale=1):
268
+ video_input = gr.File(
269
+ label="Upload MP4 Video",
270
+ file_types=["video"],
271
+ file_count="single"
272
+ )
273
+
274
+ gr.Markdown("### Frame Settings")
275
+ start_frame = gr.Slider(
276
+ minimum=0,
277
+ maximum=500,
278
+ value=0,
279
+ step=1,
280
+ label="Start Frame"
281
+ )
282
+
283
+ end_frame = gr.Slider(
284
+ minimum=0,
285
+ maximum=500,
286
+ value=50,
287
+ step=1,
288
+ label="End Frame"
289
+ )
290
+
291
+ frame_step = gr.Slider(
292
+ minimum=1,
293
+ maximum=10,
294
+ value=1,
295
+ step=1,
296
+ label="Frame Step"
297
+ )
298
+
299
+ gr.Markdown("### Processing Settings")
300
+ block_size = gr.Slider(
301
+ minimum=1,
302
+ maximum=32,
303
+ value=1,
304
+ step=1,
305
+ label="Pixel Block Size"
306
+ )
307
+
308
+ downsample_method = gr.Radio(
309
+ choices=["stride", "mean"],
310
+ value="stride",
311
+ label="Downsample Method"
312
+ )
313
+
314
+ process_btn = gr.Button("πŸ”„ Convert to Voxels", variant="primary")
315
+
316
+ with gr.Column(scale=2):
317
+ with gr.Row():
318
+ obj_output = gr.File(label="OBJ File", file_types=[".obj"])
319
+ glb_output = gr.File(label="GLB File", file_types=[".glb"])
320
+
321
+ model_3d = gr.Model3D(
322
+ label="3D Preview",
323
+ height=600
324
+ )
325
+
326
+ status = gr.Textbox(
327
+ label="Status",
328
+ value="Ready to process...",
329
+ interactive=False
330
+ )
331
+
332
+ # Event handlers
333
+ def process_with_status(video_file, start, end, step, block, method):
334
+ try:
335
+ result = generate_voxel_files(video_file, start, end, step, block, method)
336
+ if result and len(result) == 3:
337
+ obj_path, glb_path, glb_preview = result
338
+ return (
339
+ gr.update(value="βœ… Processing complete!"),
340
+ obj_path,
341
+ glb_path,
342
+ glb_preview
343
+ )
344
+ else:
345
+ return (
346
+ gr.update(value="❌ Processing failed"),
347
+ None,
348
+ None,
349
+ None
350
+ )
351
+ except Exception as e:
352
+ logger.error(f"Processing error: {str(e)}")
353
+ return (
354
+ gr.update(value=f"❌ Error: {str(e)}"),
355
+ None,
356
+ None,
357
+ None
358
+ )
359
+
360
+ # Connect the button click event
361
+ process_btn.click(
362
+ fn=process_with_status,
363
+ inputs=[
364
+ video_input,
365
+ start_frame,
366
+ end_frame,
367
+ frame_step,
368
+ block_size,
369
+ downsample_method
370
+ ],
371
+ outputs=[
372
+ status,
373
+ obj_output,
374
+ glb_output,
375
+ model_3d
376
+ ]
377
+ )
378
+
379
+ # Auto-process when video is uploaded
380
+ video_input.upload(
381
+ fn=process_with_status,
382
+ inputs=[
383
+ video_input,
384
+ start_frame,
385
+ end_frame,
386
+ frame_step,
387
+ block_size,
388
+ downsample_method
389
+ ],
390
+ outputs=[
391
+ status,
392
+ obj_output,
393
+ glb_output,
394
+ model_3d
395
+ ]
396
+ )
397
+
398
+ return interface
399
+
400
+ if __name__ == "__main__":
401
+ try:
402
+ interface = create_interface()
403
+ interface.launch(
404
+ server_name="0.0.0.0",
405
+ server_port=7860,
406
+ debug=True,
407
+ share=False,
408
+ show_error=True
409
+ )
410
+ except Exception as e:
411
+ logger.error(f"Failed to launch Gradio interface: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  raise