Spaces:
Sleeping
Sleeping
Update backend/main.py
Browse files- backend/main.py +46 -105
backend/main.py
CHANGED
@@ -21,8 +21,6 @@ import ffmpeg
|
|
21 |
import torch
|
22 |
import torchvision.transforms as T
|
23 |
from ultralytics import YOLO
|
24 |
-
import ultralytics.nn.tasks
|
25 |
-
import ultralytics.nn.modules.conv
|
26 |
import mediapipe as mp
|
27 |
from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks, Form, Request
|
28 |
from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
|
@@ -41,9 +39,7 @@ torch.serialization.add_safe_globals([
|
|
41 |
torch.nn.modules.activation.SiLU,
|
42 |
torch.nn.modules.container.ModuleList,
|
43 |
torch.nn.modules.upsampling.Upsample,
|
44 |
-
torch.nn.modules.pooling.MaxPool2d
|
45 |
-
ultralytics.nn.tasks.DetectionModel,
|
46 |
-
ultralytics.nn.modules.conv.Conv
|
47 |
])
|
48 |
|
49 |
|
@@ -404,109 +400,48 @@ def format_progress_message(stage, current, total, extras=None):
|
|
404 |
return f"{base} - {', '.join(f'{k}: {v}' for k,v in extras.items())}"
|
405 |
return base
|
406 |
|
407 |
-
def crop_video(
|
408 |
-
timestamp3: str, temp_dir: str, ffmpeg_path: str = 'ffmpeg') -> tuple[str, str]:
|
409 |
"""
|
410 |
-
Crop the video into two clips
|
|
|
|
|
411 |
"""
|
412 |
-
|
413 |
|
414 |
-
# Create temp directory if it doesn't exist
|
415 |
-
temp_dir_path.mkdir(parents=True, exist_ok=True)
|
416 |
-
|
417 |
-
# Generate temporary filenames
|
418 |
-
first_clip_path = temp_dir_path / f"clip1_{uuid.uuid4()}.mp4"
|
419 |
-
second_clip_path = temp_dir_path / f"clip2_{uuid.uuid4()}.mp4"
|
420 |
-
|
421 |
-
def check_cancellation():
|
422 |
-
"""Check if processing was cancelled (replace with your actual progress store)"""
|
423 |
-
# You'll need to import or access your PROGRESS_STORE here
|
424 |
-
if PROGRESS_STORE.get(process_id, {}).get('status') == 'cancelled':
|
425 |
-
raise asyncio.CancelledError("Processing cancelled by user during video cropping")
|
426 |
-
|
427 |
-
def run_ffmpeg_with_cancel_check(command: list, output_file: Path) -> None:
|
428 |
-
"""Run ffmpeg command with cancellation checks"""
|
429 |
-
try:
|
430 |
-
# Start the process
|
431 |
-
process = subprocess.Popen(
|
432 |
-
command,
|
433 |
-
stdout=subprocess.PIPE,
|
434 |
-
stderr=subprocess.PIPE,
|
435 |
-
universal_newlines=True
|
436 |
-
)
|
437 |
-
|
438 |
-
# Poll process while checking for cancellation
|
439 |
-
while True:
|
440 |
-
check_cancellation()
|
441 |
-
if process.poll() is not None: # Process finished
|
442 |
-
break
|
443 |
-
time.sleep(0.5) # Check every 500ms
|
444 |
-
|
445 |
-
# Check final status
|
446 |
-
if process.returncode != 0:
|
447 |
-
raise subprocess.CalledProcessError(
|
448 |
-
process.returncode,
|
449 |
-
command,
|
450 |
-
output=process.stdout,
|
451 |
-
stderr=process.stderr
|
452 |
-
)
|
453 |
-
|
454 |
-
except asyncio.CancelledError:
|
455 |
-
# Cleanup and terminate process
|
456 |
-
if process.poll() is None: # Still running
|
457 |
-
process.terminate()
|
458 |
-
try:
|
459 |
-
process.wait(timeout=5)
|
460 |
-
except subprocess.TimeoutExpired:
|
461 |
-
process.kill()
|
462 |
-
|
463 |
-
# Remove partial output file
|
464 |
-
if output_file.exists():
|
465 |
-
output_file.unlink()
|
466 |
-
|
467 |
-
raise
|
468 |
-
|
469 |
-
# Convert timestamps
|
470 |
ts1 = time_to_seconds(timestamp1)
|
471 |
ts2 = time_to_seconds(timestamp2)
|
472 |
ts3 = time_to_seconds(timestamp3)
|
473 |
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
),
|
485 |
-
|
486 |
-
|
487 |
-
ffmpeg_path, '-y', '-i', video_path,
|
488 |
-
'-ss', str(ts2), '-t', str(ts3 - ts2),
|
489 |
-
'-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
|
490 |
-
'-c:a', 'aac', str(second_clip_path)
|
491 |
-
],
|
492 |
-
second_clip_path
|
493 |
-
)
|
494 |
]
|
495 |
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
|
|
503 |
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
|
511 |
#################################################
|
512 |
# Video Processing Loop
|
@@ -531,8 +466,6 @@ def process_freeplay(process_id: str, freeplay_video: str) -> float:
|
|
531 |
prev_pose = None
|
532 |
|
533 |
for sec in range(int(duration)):
|
534 |
-
if PROGRESS_STORE.get(process_id, {}).get('status') == 'cancelled':
|
535 |
-
raise asyncio.CancelledError("Processing cancelled")
|
536 |
print(f"Processing freeplay frame {sec}")
|
537 |
if PROGRESS_STORE[process_id]["status"] == "cancelled":
|
538 |
break
|
@@ -581,8 +514,6 @@ def process_experiment(process_id: str, experiment_video: str, freeplay_movement
|
|
581 |
prev_pose = None
|
582 |
|
583 |
for sec in range(int(duration)):
|
584 |
-
if PROGRESS_STORE.get(process_id, {}).get('status') == 'cancelled':
|
585 |
-
raise asyncio.CancelledError("Processing cancelled")
|
586 |
print(f"Processing experiment frame {sec}")
|
587 |
if PROGRESS_STORE[process_id]["status"] == "cancelled":
|
588 |
break
|
@@ -737,7 +668,6 @@ async def process_video_async(process_id: str, video_path: Path, session_dir: Pa
|
|
737 |
try:
|
738 |
freeplay_video, experiment_video = await asyncio.to_thread(
|
739 |
crop_video,
|
740 |
-
process_id,
|
741 |
str(video_path),
|
742 |
timestamp1,
|
743 |
timestamp2,
|
@@ -910,6 +840,17 @@ async def serve_frontend(full_path: str):
|
|
910 |
|
911 |
if __name__ == "__main__":
|
912 |
import uvicorn
|
913 |
-
|
914 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
915 |
|
|
|
21 |
import torch
|
22 |
import torchvision.transforms as T
|
23 |
from ultralytics import YOLO
|
|
|
|
|
24 |
import mediapipe as mp
|
25 |
from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks, Form, Request
|
26 |
from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
|
|
|
39 |
torch.nn.modules.activation.SiLU,
|
40 |
torch.nn.modules.container.ModuleList,
|
41 |
torch.nn.modules.upsampling.Upsample,
|
42 |
+
torch.nn.modules.pooling.MaxPool2d
|
|
|
|
|
43 |
])
|
44 |
|
45 |
|
|
|
400 |
return f"{base} - {', '.join(f'{k}: {v}' for k,v in extras.items())}"
|
401 |
return base
|
402 |
|
403 |
+
def crop_video(video_path: str, timestamp1: str, timestamp2: str, timestamp3: str, temp_dir: str, ffmpeg_name: str = 'ffmpeg'):
|
|
|
404 |
"""
|
405 |
+
Crop the video into two clips:
|
406 |
+
- First clip: timestamp1 to timestamp2
|
407 |
+
- Second clip: timestamp2 to timestamp3
|
408 |
"""
|
409 |
+
ffmpeg_path = Path(__file__).parent / 'models' / ffmpeg_name
|
410 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
411 |
ts1 = time_to_seconds(timestamp1)
|
412 |
ts2 = time_to_seconds(timestamp2)
|
413 |
ts3 = time_to_seconds(timestamp3)
|
414 |
|
415 |
+
first_clip_path = os.path.join(temp_dir, f"clip1_{uuid.uuid4()}.mp4")
|
416 |
+
second_clip_path = os.path.join(temp_dir, f"clip2_{uuid.uuid4()}.mp4")
|
417 |
+
|
418 |
+
if not os.path.exists(ffmpeg_path):
|
419 |
+
raise FileNotFoundError(f"ffmpeg binary not found at {ffmpeg_path}")
|
420 |
+
|
421 |
+
# Clip 1
|
422 |
+
dur1 = ts2 - ts1
|
423 |
+
command1 = [
|
424 |
+
ffmpeg_path, '-y', '-i', video_path,
|
425 |
+
'-ss', str(ts1), '-t', str(dur1),
|
426 |
+
'-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
|
427 |
+
'-c:a', 'aac', first_clip_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
428 |
]
|
429 |
|
430 |
+
# Clip 2
|
431 |
+
dur2 = ts3 - ts2
|
432 |
+
command2 = [
|
433 |
+
ffmpeg_path, '-y', '-i', video_path,
|
434 |
+
'-ss', str(ts2), '-t', str(dur2),
|
435 |
+
'-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
|
436 |
+
'-c:a', 'aac', second_clip_path
|
437 |
+
]
|
438 |
|
439 |
+
logger.info("Running command: %s", ' '.join(command1))
|
440 |
+
subprocess.run(command1, check=True)
|
441 |
+
logger.info("Running command: %s", ' '.join(command2))
|
442 |
+
subprocess.run(command2, check=True)
|
443 |
+
|
444 |
+
return first_clip_path, second_clip_path
|
445 |
|
446 |
#################################################
|
447 |
# Video Processing Loop
|
|
|
466 |
prev_pose = None
|
467 |
|
468 |
for sec in range(int(duration)):
|
|
|
|
|
469 |
print(f"Processing freeplay frame {sec}")
|
470 |
if PROGRESS_STORE[process_id]["status"] == "cancelled":
|
471 |
break
|
|
|
514 |
prev_pose = None
|
515 |
|
516 |
for sec in range(int(duration)):
|
|
|
|
|
517 |
print(f"Processing experiment frame {sec}")
|
518 |
if PROGRESS_STORE[process_id]["status"] == "cancelled":
|
519 |
break
|
|
|
668 |
try:
|
669 |
freeplay_video, experiment_video = await asyncio.to_thread(
|
670 |
crop_video,
|
|
|
671 |
str(video_path),
|
672 |
timestamp1,
|
673 |
timestamp2,
|
|
|
840 |
|
841 |
if __name__ == "__main__":
|
842 |
import uvicorn
|
843 |
+
# Pre-create cache dir
|
844 |
+
Path(os.environ['TORCH_HOME']).mkdir(exist_ok=True)
|
845 |
+
|
846 |
+
uvicorn.run(
|
847 |
+
app,
|
848 |
+
host="0.0.0.0",
|
849 |
+
port=7860,
|
850 |
+
workers=1,
|
851 |
+
log_level="info",
|
852 |
+
# Add these parameters
|
853 |
+
limit_concurrency=1, # Reduce memory pressure
|
854 |
+
timeout_graceful_shutdown=30
|
855 |
+
)
|
856 |
|