import os import io import shutil import sqlite3 from pathlib import Path from fastapi import APIRouter, UploadFile, File, Query, HTTPException from fastapi.responses import FileResponse, JSONResponse from storage.files.file_manager import FileManager from storage.common import validate_token router = APIRouter(prefix="/pending_videos", tags=["Pending Videos Manager"]) MEDIA_ROOT = Path("/data/pending_videos") file_manager = FileManager(MEDIA_ROOT) HF_TOKEN = os.getenv("HF_TOKEN") @router.delete("/clear_pending_videos", tags=["Pending Videos Manager"]) def clear_media(token: str = Query(..., description="Token required for authorization")): """ Delete all contents of the /data/pending_videos folder. Steps: - Validate the token. - Ensure the folder exists. - Delete all files and subfolders inside /data/pending_videos. - Return a JSON response confirming the deletion. Warning: This will remove all stored videos, clips, and cast CSV files. """ validate_token(token) if not MEDIA_ROOT.exists() or not MEDIA_ROOT.is_dir(): raise HTTPException(status_code=404, detail="/data/pending_videos folder does not exist") # Delete contents for item in MEDIA_ROOT.iterdir(): try: if item.is_dir(): shutil.rmtree(item) else: item.unlink() except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to delete {item}: {e}") return {"status": "ok", "message": "All pending_videos files deleted successfully"} @router.delete("/clear_pending_video", tags=["Pending Videos Manager"]) def clear_pending_video( sha1: str = Query(..., description="SHA1 folder to delete inside pending_videos"), token: str = Query(..., description="Token required for authorization") ): """ Delete a specific SHA1 folder inside /data/pending_videos. Steps: - Validate the token. - Ensure the folder exists. - Delete the folder and all its contents. - Return a JSON response confirming the deletion. """ validate_token(token) PENDING_ROOT = Path("/data/pending_videos") target_folder = PENDING_ROOT / sha1 if not target_folder.exists() or not target_folder.is_dir(): raise HTTPException(status_code=404, detail=f"Folder {sha1} does not exist in pending_videos") try: shutil.rmtree(target_folder) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to delete {sha1}: {e}") return {"status": "ok", "message": f"Pending video folder {sha1} deleted successfully"} @router.post("/upload_pending_video", tags=["Pending Videos Manager"]) async def upload_video( video: UploadFile = File(...), token: str = Query(..., description="Token required for authorization") ): """ Saves an uploaded video by hashing it with SHA1 and placing it under: /data/media// Behavior: - Compute SHA1 of the uploaded video. - Ensure folder structure exists. - Delete any existing .mp4 files under sha1. - Save the uploaded video in the folder. """ # Read content into memory (needed to compute hash twice) file_bytes = await video.read() # Create an in-memory file handler for hashing file_handler = io.BytesIO(file_bytes) # Compute SHA1 try: sha1 = file_manager.compute_sha1(file_handler) except Exception as exc: raise HTTPException(status_code=500, detail=f"SHA1 computation failed: {exc}") # Ensure /data/media exists MEDIA_ROOT.mkdir(parents=True, exist_ok=True) # Path: /data/media/ video_root = MEDIA_ROOT / sha1 video_root.mkdir(parents=True, exist_ok=True) # Delete old MP4 files try: for old_mp4 in video_root.glob("*.mp4"): old_mp4.unlink() except Exception as exc: raise HTTPException(status_code=500, detail=f"Failed to delete old videos: {exc}") # Save new video path final_path = video_root / video.filename # Save file save_result = file_manager.upload_file(io.BytesIO(file_bytes), final_path) if not save_result["operation_success"]: raise HTTPException(status_code=500, detail=save_result["error"]) return JSONResponse( status_code=200, content={ "status": "ok", "sha1": sha1, "saved_to": str(final_path) } ) @router.get("/download_pending_video", tags=["Pending Videos Manager"]) def download_video( sha1: str, token: str = Query(..., description="Token required for authorization") ): """ Download a stored video by its SHA-1 directory name. This endpoint looks for a video stored under the path: /data/media//clip/ and returns the first MP4 file found in that folder. The method performs the following steps: - Checks if the SHA-1 folder exists inside the media root. - Validates that the "clip" subfolder exists. - Searches for the first .mp4 file inside the clip folder. - Uses the FileManager.get_file method to ensure the file is accessible. - Returns the video directly as a FileResponse. Parameters ---------- sha1 : str The SHA-1 hash corresponding to the directory where the video is stored. Returns ------- FileResponse A streaming response containing the MP4 video. Raises ------ HTTPException - 404 if the SHA-1 folder does not exist. - 404 if the clip folder is missing. - 404 if no MP4 files are found. - 404 if the file cannot be retrieved using FileManager. """ sha1_folder = MEDIA_ROOT / sha1 if not sha1_folder.exists() or not sha1_folder.is_dir(): raise HTTPException(status_code=404, detail="SHA1 folder not found") # Find first MP4 file mp4_files = list(sha1_folder.glob("*.mp4")) if not mp4_files: raise HTTPException(status_code=404, detail="No MP4 files found") video_path = mp4_files[0] # Convert to relative path for FileManager relative_path = video_path.relative_to(MEDIA_ROOT) handler = file_manager.get_file(relative_path) if handler is None: raise HTTPException(status_code=404, detail="Video not accessible") handler.close() return FileResponse( path=video_path, media_type="video/mp4", filename=video_path.name ) @router.get("/list_pending_videos", tags=["Pending Videos Manager"]) def list_all_videos( token: str = Query(..., description="Token required for authorization") ): """ List all videos stored under /data/media. For each SHA1 folder, the endpoint returns: - sha1: folder name - video_files: list of mp4 files inside /clip - latest_video: the most recently modified mp4 - video_count: total number of mp4 files Notes: - Videos may not have a /clip folder. - SHA1 folders without mp4 files are still returned. """ validate_token(token) results = [] # If media root does not exist, return empty list if not MEDIA_ROOT.exists(): return [] for sha1_dir in MEDIA_ROOT.iterdir(): if not sha1_dir.is_dir(): continue # skip non-folders videos = [] latest_video = None if sha1_dir.exists() and sha1_dir.is_dir(): mp4_files = list(sha1_dir.glob("*.mp4")) # Sort by modification time (newest first) mp4_files.sort(key=lambda f: f.stat().st_mtime, reverse=True) videos = [f.name for f in mp4_files] if mp4_files: latest_video = mp4_files[0].name results.append({ "sha1": sha1_dir.name, "video_name": latest_video }) return results