From 2bce817b4ee12749b421142bb47e23796fc7193a Mon Sep 17 00:00:00 2001 From: salirezav Date: Wed, 3 Dec 2025 14:56:18 -0500 Subject: [PATCH] Enhance media API with video file validation and Docker configuration update - Added a function to check if video files are complete and valid using ffprobe, preventing errors during thumbnail generation. - Updated thumbnail generation logic to skip incomplete or corrupted files, improving robustness. - Modified docker-compose.yml to include a restart policy for the camera management API service, ensuring better container reliability. --- docker-compose.yml | 1 + media-api/main.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a303ec1..80ba2e7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ services: context: ./camera-management-api dockerfile: Dockerfile working_dir: /app + restart: unless-stopped # Automatically restart container if it fails or exits volumes: - ./camera-management-api:/app - /mnt/nfs_share:/mnt/nfs_share diff --git a/media-api/main.py b/media-api/main.py index 0816a34..c971f37 100644 --- a/media-api/main.py +++ b/media-api/main.py @@ -138,13 +138,45 @@ def file_id_from_path(p: pathlib.Path) -> str: def path_from_file_id(fid: str) -> pathlib.Path: - rel = urllib.parse.unquote_plus(fid) + # Handle double-encoding: decode until no more changes + rel = fid + while True: + decoded = urllib.parse.unquote_plus(rel) + if decoded == rel: + break + rel = decoded p = (MEDIA_DIR / rel).resolve() if not p.is_file() or MEDIA_DIR not in p.parents: raise HTTPException(status_code=404, detail="Video not found") return p +def is_video_file_complete(p: pathlib.Path) -> bool: + """ + Check if video file is complete and valid using ffprobe. + Returns True if file is complete and can be processed, False otherwise. + """ + try: + # Quick check: use ffprobe to verify file is valid + cmd = [ + "ffprobe", + "-v", "error", + "-show_entries", "format=duration", + "-of", "default=noprint_wrappers=1:nokey=1", + str(p) + ] + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=5 + ) + # If ffprobe succeeds, file is valid + return result.returncode == 0 + except (subprocess.TimeoutExpired, subprocess.CalledProcessError, Exception): + return False + + def generate_thumbnail_background(p: pathlib.Path, width: int = 320, height: int = 180) -> bool: """ Generate thumbnail in background (non-blocking, doesn't raise exceptions). @@ -188,13 +220,19 @@ def generate_thumbnail_background(p: pathlib.Path, width: int = 320, height: int except OSError: return False + # Check if video file is complete and valid before attempting thumbnail generation + # This prevents errors with incomplete/corrupted files + if not is_video_file_complete(p): + return False # File is incomplete or corrupted, skip + # Try to generate thumbnail - try 1s first, then 0s if that fails for seek_time in [1.0, 0.0]: cmd = [ "ffmpeg", "-y", "-ss", str(seek_time), "-i", str(p), - "-vframes", "1", + "-frames:v", "1", + "-update", "1", # Required for single image output with image2 muxer "-vf", f"scale='min({width},iw)':-2,scale={width}:{height}:force_original_aspect_ratio=decrease,pad={width}:{height}:(ow-iw)/2:(oh-ih)/2", str(out) ]