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

Update backend/main.py

Browse files
Files changed (1) hide show
  1. 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(process_id: str, video_path: str, timestamp1: str, timestamp2: str,
408
- timestamp3: str, temp_dir: str, ffmpeg_path: str = 'ffmpeg') -> tuple[str, str]:
409
  """
410
- Crop the video into two clips with cancellation support
 
 
411
  """
412
- temp_dir_path = Path(temp_dir)
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
- # Build commands
475
- commands = [
476
- (
477
- [
478
- ffmpeg_path, '-y', '-i', video_path,
479
- '-ss', str(ts1), '-t', str(ts2 - ts1),
480
- '-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
481
- '-c:a', 'aac', str(first_clip_path)
482
- ],
483
- first_clip_path
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
- try:
497
- # Process both clips
498
- for cmd, output_path in commands:
499
- logger.info("Running command: %s", ' '.join(cmd))
500
- run_ffmpeg_with_cancel_check(cmd, output_path)
501
-
502
- return str(first_clip_path), str(second_clip_path)
 
503
 
504
- except asyncio.CancelledError:
505
- # Cleanup both files if either was cancelled
506
- for path in [first_clip_path, second_clip_path]:
507
- if path.exists():
508
- path.unlink()
509
- raise
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
- uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
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