Spaces:
Running
Running
Update server.py
Browse files
server.py
CHANGED
|
@@ -12,7 +12,6 @@ from typing import List, Dict, Optional, Tuple
|
|
| 12 |
|
| 13 |
from fastapi import FastAPI, HTTPException
|
| 14 |
from fastapi.responses import JSONResponse
|
| 15 |
-
from contextlib import asynccontextmanager
|
| 16 |
import uvicorn
|
| 17 |
|
| 18 |
try:
|
|
@@ -33,15 +32,7 @@ if not HF_TOKEN:
|
|
| 33 |
print("Error: Missing HF_TOKEN in .env")
|
| 34 |
exit(1)
|
| 35 |
|
| 36 |
-
|
| 37 |
-
async def lifespan(app: FastAPI):
|
| 38 |
-
"""Load Whisper in background, then kick off video processing."""
|
| 39 |
-
loop = asyncio.get_event_loop()
|
| 40 |
-
await loop.run_in_executor(None, _load_whisper_model)
|
| 41 |
-
asyncio.create_task(scan_and_process_videos())
|
| 42 |
-
yield
|
| 43 |
-
|
| 44 |
-
app = FastAPI(title="Video Processing Service", lifespan=lifespan)
|
| 45 |
|
| 46 |
# Global state
|
| 47 |
processing_state = {
|
|
@@ -244,7 +235,6 @@ def process_video_segment(
|
|
| 244 |
4. Mux processed video with original audio
|
| 245 |
"""
|
| 246 |
ffmpeg_video_proc = None
|
| 247 |
-
cap = None # Declared here so finally block can always release it
|
| 248 |
temp_wav = output_path.replace(".mp4", "_audio.wav")
|
| 249 |
temp_video_path = output_path.replace(".mp4", "_noaudio.mp4")
|
| 250 |
|
|
@@ -334,7 +324,7 @@ def process_video_segment(
|
|
| 334 |
frame = cv2.resize(frame, (target_width, target_height), interpolation=cv2.INTER_LANCZOS4)
|
| 335 |
frame = apply_color_grading_wedding_retro(frame)
|
| 336 |
|
| 337 |
-
# Set caption for this frame (empty if none)
|
| 338 |
current_caption = frame_caption_map.get(processed_frames, "")
|
| 339 |
|
| 340 |
if current_caption:
|
|
@@ -347,9 +337,9 @@ def process_video_segment(
|
|
| 347 |
progress = (processed_frames / target_frames) * 100
|
| 348 |
print(f"Progress: {progress:.1f}%")
|
| 349 |
|
| 350 |
-
# Close stdin and wait for FFmpeg to finish encoding
|
| 351 |
ffmpeg_video_proc.stdin.close()
|
| 352 |
ffmpeg_video_proc.wait()
|
|
|
|
| 353 |
|
| 354 |
if ffmpeg_video_proc.returncode != 0:
|
| 355 |
print(f"✗ FFmpeg video encoding failed (code {ffmpeg_video_proc.returncode})")
|
|
@@ -398,10 +388,6 @@ def process_video_segment(
|
|
| 398 |
return False
|
| 399 |
|
| 400 |
finally:
|
| 401 |
-
# Always release VideoCapture regardless of success or failure
|
| 402 |
-
if cap is not None:
|
| 403 |
-
cap.release()
|
| 404 |
-
# Always clean up temp files
|
| 405 |
for tmp in [temp_video_path, temp_wav]:
|
| 406 |
if tmp and os.path.exists(tmp):
|
| 407 |
try:
|
|
@@ -453,12 +439,11 @@ async def process_movie_segments(movie_name: str) -> bool:
|
|
| 453 |
print(f"No segment JSON files found for {movie_name}")
|
| 454 |
return False
|
| 455 |
|
| 456 |
-
print(f"Found {len(segment_files)} segments
|
| 457 |
temp_dir = tempfile.mkdtemp()
|
| 458 |
|
| 459 |
try:
|
| 460 |
for segment_file in segment_files:
|
| 461 |
-
print(f"\n── Processing file: {segment_file}")
|
| 462 |
try:
|
| 463 |
segment_path = hf_hub_download(
|
| 464 |
repo_id=HF_DATASET_REPO,
|
|
@@ -475,7 +460,7 @@ async def process_movie_segments(movie_name: str) -> bool:
|
|
| 475 |
start_time = segment_data.get("start_time", "00:00:00")
|
| 476 |
end_time = segment_data.get("end_time", "00:10:00")
|
| 477 |
|
| 478 |
-
print(f"
|
| 479 |
|
| 480 |
output_filename = f"segment-{segment_number:02d}.mp4"
|
| 481 |
output_path = os.path.join(temp_dir, output_filename)
|
|
@@ -488,8 +473,7 @@ async def process_movie_segments(movie_name: str) -> bool:
|
|
| 488 |
)
|
| 489 |
|
| 490 |
if not success:
|
| 491 |
-
print(f"
|
| 492 |
-
processing_state["error_count"] += 1
|
| 493 |
continue
|
| 494 |
|
| 495 |
upload_path = f"{READY_VIDEOS_FOLDER}/{movie_name}/{output_filename}"
|
|
@@ -505,15 +489,8 @@ async def process_movie_segments(movie_name: str) -> bool:
|
|
| 505 |
)
|
| 506 |
print(f"✓ Segment {segment_number} uploaded successfully")
|
| 507 |
|
| 508 |
-
# Clean up the output file after successful upload
|
| 509 |
-
if os.path.exists(output_path):
|
| 510 |
-
try:
|
| 511 |
-
os.remove(output_path)
|
| 512 |
-
except Exception:
|
| 513 |
-
pass
|
| 514 |
-
|
| 515 |
except Exception as e:
|
| 516 |
-
print(f"✗ Error processing segment
|
| 517 |
processing_state["error_count"] += 1
|
| 518 |
continue
|
| 519 |
|
|
@@ -529,7 +506,7 @@ async def process_movie_segments(movie_name: str) -> bool:
|
|
| 529 |
except Exception as e:
|
| 530 |
processing_state["error_count"] += 1
|
| 531 |
processing_state["last_error"] = str(e)
|
| 532 |
-
print(f"✗ Error
|
| 533 |
return False
|
| 534 |
|
| 535 |
|
|
@@ -539,7 +516,8 @@ async def scan_and_process_videos():
|
|
| 539 |
print("Video processing already running, skipping...")
|
| 540 |
return
|
| 541 |
|
| 542 |
-
|
|
|
|
| 543 |
print(f"Waiting {startup_delay} seconds before starting video processing...")
|
| 544 |
await asyncio.sleep(startup_delay)
|
| 545 |
|
|
@@ -562,7 +540,7 @@ async def scan_and_process_videos():
|
|
| 562 |
if len(parts) >= 2:
|
| 563 |
movie_folders.add(parts[1])
|
| 564 |
|
| 565 |
-
print(f"Found {len(movie_folders)} movies to process
|
| 566 |
|
| 567 |
for movie_name in sorted(movie_folders):
|
| 568 |
await process_movie_segments(movie_name)
|
|
@@ -575,13 +553,21 @@ async def scan_and_process_videos():
|
|
| 575 |
print("="*80 + "\n")
|
| 576 |
|
| 577 |
except Exception as e:
|
| 578 |
-
print(f"Critical error
|
| 579 |
processing_state["last_error"] = str(e)
|
| 580 |
finally:
|
| 581 |
processing_state["is_running"] = False
|
| 582 |
-
processing_state["current_file"] = None
|
| 583 |
|
| 584 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 585 |
|
| 586 |
@app.get("/")
|
| 587 |
async def health():
|
|
@@ -632,5 +618,5 @@ async def trigger_processing():
|
|
| 632 |
|
| 633 |
if __name__ == "__main__":
|
| 634 |
print("Starting Video Processing Service on port 7860...")
|
| 635 |
-
print("Whisper will load at startup, processing begins
|
| 636 |
-
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
| 12 |
|
| 13 |
from fastapi import FastAPI, HTTPException
|
| 14 |
from fastapi.responses import JSONResponse
|
|
|
|
| 15 |
import uvicorn
|
| 16 |
|
| 17 |
try:
|
|
|
|
| 32 |
print("Error: Missing HF_TOKEN in .env")
|
| 33 |
exit(1)
|
| 34 |
|
| 35 |
+
app = FastAPI(title="Video Processing Service")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
# Global state
|
| 38 |
processing_state = {
|
|
|
|
| 235 |
4. Mux processed video with original audio
|
| 236 |
"""
|
| 237 |
ffmpeg_video_proc = None
|
|
|
|
| 238 |
temp_wav = output_path.replace(".mp4", "_audio.wav")
|
| 239 |
temp_video_path = output_path.replace(".mp4", "_noaudio.mp4")
|
| 240 |
|
|
|
|
| 324 |
frame = cv2.resize(frame, (target_width, target_height), interpolation=cv2.INTER_LANCZOS4)
|
| 325 |
frame = apply_color_grading_wedding_retro(frame)
|
| 326 |
|
| 327 |
+
# Set caption for this frame (empty if none).
|
| 328 |
current_caption = frame_caption_map.get(processed_frames, "")
|
| 329 |
|
| 330 |
if current_caption:
|
|
|
|
| 337 |
progress = (processed_frames / target_frames) * 100
|
| 338 |
print(f"Progress: {progress:.1f}%")
|
| 339 |
|
|
|
|
| 340 |
ffmpeg_video_proc.stdin.close()
|
| 341 |
ffmpeg_video_proc.wait()
|
| 342 |
+
cap.release()
|
| 343 |
|
| 344 |
if ffmpeg_video_proc.returncode != 0:
|
| 345 |
print(f"✗ FFmpeg video encoding failed (code {ffmpeg_video_proc.returncode})")
|
|
|
|
| 388 |
return False
|
| 389 |
|
| 390 |
finally:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
for tmp in [temp_video_path, temp_wav]:
|
| 392 |
if tmp and os.path.exists(tmp):
|
| 393 |
try:
|
|
|
|
| 439 |
print(f"No segment JSON files found for {movie_name}")
|
| 440 |
return False
|
| 441 |
|
| 442 |
+
print(f"Found {len(segment_files)} segments")
|
| 443 |
temp_dir = tempfile.mkdtemp()
|
| 444 |
|
| 445 |
try:
|
| 446 |
for segment_file in segment_files:
|
|
|
|
| 447 |
try:
|
| 448 |
segment_path = hf_hub_download(
|
| 449 |
repo_id=HF_DATASET_REPO,
|
|
|
|
| 460 |
start_time = segment_data.get("start_time", "00:00:00")
|
| 461 |
end_time = segment_data.get("end_time", "00:10:00")
|
| 462 |
|
| 463 |
+
print(f"\nProcessing segment {segment_number}: {start_time} to {end_time}")
|
| 464 |
|
| 465 |
output_filename = f"segment-{segment_number:02d}.mp4"
|
| 466 |
output_path = os.path.join(temp_dir, output_filename)
|
|
|
|
| 473 |
)
|
| 474 |
|
| 475 |
if not success:
|
| 476 |
+
print(f"Failed to process segment {segment_number}")
|
|
|
|
| 477 |
continue
|
| 478 |
|
| 479 |
upload_path = f"{READY_VIDEOS_FOLDER}/{movie_name}/{output_filename}"
|
|
|
|
| 489 |
)
|
| 490 |
print(f"✓ Segment {segment_number} uploaded successfully")
|
| 491 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
except Exception as e:
|
| 493 |
+
print(f"✗ Error processing segment: {e}")
|
| 494 |
processing_state["error_count"] += 1
|
| 495 |
continue
|
| 496 |
|
|
|
|
| 506 |
except Exception as e:
|
| 507 |
processing_state["error_count"] += 1
|
| 508 |
processing_state["last_error"] = str(e)
|
| 509 |
+
print(f"✗ Error: {e}")
|
| 510 |
return False
|
| 511 |
|
| 512 |
|
|
|
|
| 516 |
print("Video processing already running, skipping...")
|
| 517 |
return
|
| 518 |
|
| 519 |
+
# Wait for Space to fully initialize (reduced for testing)
|
| 520 |
+
startup_delay = int(os.getenv("STARTUP_DELAY", 5)) # Default 5 seconds for testing
|
| 521 |
print(f"Waiting {startup_delay} seconds before starting video processing...")
|
| 522 |
await asyncio.sleep(startup_delay)
|
| 523 |
|
|
|
|
| 540 |
if len(parts) >= 2:
|
| 541 |
movie_folders.add(parts[1])
|
| 542 |
|
| 543 |
+
print(f"Found {len(movie_folders)} movies to process")
|
| 544 |
|
| 545 |
for movie_name in sorted(movie_folders):
|
| 546 |
await process_movie_segments(movie_name)
|
|
|
|
| 553 |
print("="*80 + "\n")
|
| 554 |
|
| 555 |
except Exception as e:
|
| 556 |
+
print(f"Critical error: {e}")
|
| 557 |
processing_state["last_error"] = str(e)
|
| 558 |
finally:
|
| 559 |
processing_state["is_running"] = False
|
|
|
|
| 560 |
|
| 561 |
|
| 562 |
+
@app.on_event("startup")
|
| 563 |
+
async def startup_event():
|
| 564 |
+
"""Load Whisper in background, then kick off video processing after 3 min."""
|
| 565 |
+
loop = asyncio.get_event_loop()
|
| 566 |
+
# Load Whisper model in thread so it doesn't block the event loop / health check
|
| 567 |
+
await loop.run_in_executor(None, _load_whisper_model)
|
| 568 |
+
# Kick off processing task (has its own 3-min delay inside)
|
| 569 |
+
asyncio.create_task(scan_and_process_videos())
|
| 570 |
+
|
| 571 |
|
| 572 |
@app.get("/")
|
| 573 |
async def health():
|
|
|
|
| 618 |
|
| 619 |
if __name__ == "__main__":
|
| 620 |
print("Starting Video Processing Service on port 7860...")
|
| 621 |
+
print("Whisper will load at startup, processing begins 3 minutes after")
|
| 622 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|