Jmfinizio commited on
Commit
a150550
·
verified ·
1 Parent(s): a6cae6a

Update backend/main.py

Browse files
Files changed (1) hide show
  1. 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(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,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
- # 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
 
 
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