Spaces:
Sleeping
Sleeping
Update backend/main.py
Browse files- backend/main.py +100 -45
backend/main.py
CHANGED
@@ -400,48 +400,109 @@ def format_progress_message(stage, current, total, extras=None):
|
|
400 |
return f"{base} - {', '.join(f'{k}: {v}' for k,v in extras.items())}"
|
401 |
return base
|
402 |
|
403 |
-
def crop_video(
|
|
|
404 |
"""
|
405 |
-
Crop the video into two clips
|
406 |
-
- First clip: timestamp1 to timestamp2
|
407 |
-
- Second clip: timestamp2 to timestamp3
|
408 |
"""
|
409 |
-
|
410 |
|
411 |
-
|
412 |
-
|
413 |
-
ts3 = time_to_seconds(timestamp3)
|
414 |
|
415 |
-
|
416 |
-
|
|
|
417 |
|
418 |
-
|
419 |
-
|
|
|
|
|
|
|
420 |
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
|
|
|
|
429 |
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
437 |
]
|
438 |
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
|
|
|
|
|
|
443 |
|
444 |
-
|
|
|
|
|
|
|
|
|
|
|
445 |
|
446 |
#################################################
|
447 |
# Video Processing Loop
|
@@ -466,6 +527,8 @@ def process_freeplay(process_id: str, freeplay_video: str) -> float:
|
|
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,6 +577,8 @@ def process_experiment(process_id: str, experiment_video: str, freeplay_movement
|
|
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,6 +733,7 @@ async def process_video_async(process_id: str, video_path: Path, session_dir: Pa
|
|
668 |
try:
|
669 |
freeplay_video, experiment_video = await asyncio.to_thread(
|
670 |
crop_video,
|
|
|
671 |
str(video_path),
|
672 |
timestamp1,
|
673 |
timestamp2,
|
@@ -840,17 +906,6 @@ async def serve_frontend(full_path: str):
|
|
840 |
|
841 |
if __name__ == "__main__":
|
842 |
import uvicorn
|
843 |
-
|
844 |
-
|
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 |
|
|
|
400 |
return f"{base} - {', '.join(f'{k}: {v}' for k,v in extras.items())}"
|
401 |
return base
|
402 |
|
403 |
+
def crop_video(process_id: str, video_path: str, timestamp1: str, timestamp2: str,
|
404 |
+
timestamp3: str, temp_dir: str, ffmpeg_path: str = 'ffmpeg') -> tuple[str, str]:
|
405 |
"""
|
406 |
+
Crop the video into two clips with cancellation support
|
|
|
|
|
407 |
"""
|
408 |
+
temp_dir_path = Path(temp_dir)
|
409 |
|
410 |
+
# Create temp directory if it doesn't exist
|
411 |
+
temp_dir_path.mkdir(parents=True, exist_ok=True)
|
|
|
412 |
|
413 |
+
# Generate temporary filenames
|
414 |
+
first_clip_path = temp_dir_path / f"clip1_{uuid.uuid4()}.mp4"
|
415 |
+
second_clip_path = temp_dir_path / f"clip2_{uuid.uuid4()}.mp4"
|
416 |
|
417 |
+
def check_cancellation():
|
418 |
+
"""Check if processing was cancelled (replace with your actual progress store)"""
|
419 |
+
# You'll need to import or access your PROGRESS_STORE here
|
420 |
+
if PROGRESS_STORE.get(process_id, {}).get('status') == 'cancelled':
|
421 |
+
raise asyncio.CancelledError("Processing cancelled by user during video cropping")
|
422 |
|
423 |
+
def run_ffmpeg_with_cancel_check(command: list, output_file: Path) -> None:
|
424 |
+
"""Run ffmpeg command with cancellation checks"""
|
425 |
+
try:
|
426 |
+
# Start the process
|
427 |
+
process = subprocess.Popen(
|
428 |
+
command,
|
429 |
+
stdout=subprocess.PIPE,
|
430 |
+
stderr=subprocess.PIPE,
|
431 |
+
universal_newlines=True
|
432 |
+
)
|
433 |
|
434 |
+
# Poll process while checking for cancellation
|
435 |
+
while True:
|
436 |
+
check_cancellation()
|
437 |
+
if process.poll() is not None: # Process finished
|
438 |
+
break
|
439 |
+
time.sleep(0.5) # Check every 500ms
|
440 |
+
|
441 |
+
# Check final status
|
442 |
+
if process.returncode != 0:
|
443 |
+
raise subprocess.CalledProcessError(
|
444 |
+
process.returncode,
|
445 |
+
command,
|
446 |
+
output=process.stdout,
|
447 |
+
stderr=process.stderr
|
448 |
+
)
|
449 |
+
|
450 |
+
except asyncio.CancelledError:
|
451 |
+
# Cleanup and terminate process
|
452 |
+
if process.poll() is None: # Still running
|
453 |
+
process.terminate()
|
454 |
+
try:
|
455 |
+
process.wait(timeout=5)
|
456 |
+
except subprocess.TimeoutExpired:
|
457 |
+
process.kill()
|
458 |
+
|
459 |
+
# Remove partial output file
|
460 |
+
if output_file.exists():
|
461 |
+
output_file.unlink()
|
462 |
+
|
463 |
+
raise
|
464 |
+
|
465 |
+
# Convert timestamps
|
466 |
+
ts1 = time_to_seconds(timestamp1)
|
467 |
+
ts2 = time_to_seconds(timestamp2)
|
468 |
+
ts3 = time_to_seconds(timestamp3)
|
469 |
+
|
470 |
+
# Build commands
|
471 |
+
commands = [
|
472 |
+
(
|
473 |
+
[
|
474 |
+
ffmpeg_path, '-y', '-i', video_path,
|
475 |
+
'-ss', str(ts1), '-t', str(ts2 - ts1),
|
476 |
+
'-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
|
477 |
+
'-c:a', 'aac', str(first_clip_path)
|
478 |
+
],
|
479 |
+
first_clip_path
|
480 |
+
),
|
481 |
+
(
|
482 |
+
[
|
483 |
+
ffmpeg_path, '-y', '-i', video_path,
|
484 |
+
'-ss', str(ts2), '-t', str(ts3 - ts2),
|
485 |
+
'-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
|
486 |
+
'-c:a', 'aac', str(second_clip_path)
|
487 |
+
],
|
488 |
+
second_clip_path
|
489 |
+
)
|
490 |
]
|
491 |
|
492 |
+
try:
|
493 |
+
# Process both clips
|
494 |
+
for cmd, output_path in commands:
|
495 |
+
logger.info("Running command: %s", ' '.join(cmd))
|
496 |
+
run_ffmpeg_with_cancel_check(cmd, output_path)
|
497 |
+
|
498 |
+
return str(first_clip_path), str(second_clip_path)
|
499 |
|
500 |
+
except asyncio.CancelledError:
|
501 |
+
# Cleanup both files if either was cancelled
|
502 |
+
for path in [first_clip_path, second_clip_path]:
|
503 |
+
if path.exists():
|
504 |
+
path.unlink()
|
505 |
+
raise
|
506 |
|
507 |
#################################################
|
508 |
# Video Processing Loop
|
|
|
527 |
prev_pose = None
|
528 |
|
529 |
for sec in range(int(duration)):
|
530 |
+
if PROGRESS_STORE.get(process_id, {}).get('status') == 'cancelled':
|
531 |
+
raise asyncio.CancelledError("Processing cancelled")
|
532 |
print(f"Processing freeplay frame {sec}")
|
533 |
if PROGRESS_STORE[process_id]["status"] == "cancelled":
|
534 |
break
|
|
|
577 |
prev_pose = None
|
578 |
|
579 |
for sec in range(int(duration)):
|
580 |
+
if PROGRESS_STORE.get(process_id, {}).get('status') == 'cancelled':
|
581 |
+
raise asyncio.CancelledError("Processing cancelled")
|
582 |
print(f"Processing experiment frame {sec}")
|
583 |
if PROGRESS_STORE[process_id]["status"] == "cancelled":
|
584 |
break
|
|
|
733 |
try:
|
734 |
freeplay_video, experiment_video = await asyncio.to_thread(
|
735 |
crop_video,
|
736 |
+
process_id,
|
737 |
str(video_path),
|
738 |
timestamp1,
|
739 |
timestamp2,
|
|
|
906 |
|
907 |
if __name__ == "__main__":
|
908 |
import uvicorn
|
909 |
+
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
910 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
911 |
|