| 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")
|
|
|
|
|
| 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/<sha1>/<original_filename>
|
| 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.
|
| """
|
|
|
| file_bytes = await video.read()
|
|
|
|
|
| file_handler = io.BytesIO(file_bytes)
|
|
|
|
|
| try:
|
| sha1 = file_manager.compute_sha1(file_handler)
|
| except Exception as exc:
|
| raise HTTPException(status_code=500, detail=f"SHA1 computation failed: {exc}")
|
|
|
|
|
| MEDIA_ROOT.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
| video_root = MEDIA_ROOT / sha1
|
| video_root.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
| 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}")
|
|
|
|
|
| final_path = video_root / video.filename
|
|
|
|
|
| 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/<sha1>/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")
|
|
|
|
|
| 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]
|
|
|
|
|
| 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 not MEDIA_ROOT.exists():
|
| return []
|
|
|
| for sha1_dir in MEDIA_ROOT.iterdir():
|
| if not sha1_dir.is_dir():
|
| continue
|
|
|
| videos = []
|
| latest_video = None
|
|
|
| if sha1_dir.exists() and sha1_dir.is_dir():
|
| mp4_files = list(sha1_dir.glob("*.mp4"))
|
|
|
|
|
| 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 |