| 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="/media", tags=["Media Manager"]) |
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
| HF_TOKEN = os.getenv("HF_TOKEN") |
| VALID_VERSIONS = ("Salamandra", "MoE") |
| VALID_SUBTYPES = ("Original", "HITL OK", "HITL Test") |
| AUDIO_EXTENSIONS = ["*.mp3", "*.wav", "*.aac", "*.m4a", "*.ogg", "*.flac"] |
|
|
|
|
| @router.delete("/clear_media", tags=["Media Manager"]) |
| def clear_media(token: str = Query(..., description="Token required for authorization")): |
| """ |
| Delete all contents of the /data/media folder. |
| |
| Steps: |
| - Validate the token. |
| - Ensure the folder exists. |
| - Delete all files and subfolders inside /data/media. |
| - 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/media 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 media files deleted successfully"} |
|
|
| @router.post("/upload_cast_csv", tags=["Media Manager"]) |
| async def upload_cast_csv( |
| sha1: str, |
| cast_file: UploadFile = File(...), |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """ |
| Upload a cast CSV file for a specific video identified by its SHA-1. |
| |
| The CSV will be stored under: |
| /data/media/<sha1>/cast/cast.csv |
| |
| Steps: |
| - Validate the token. |
| - Ensure /data/media/<sha1> exists. |
| - Create /cast folder if missing. |
| - Save the CSV file inside /cast. |
| """ |
| validate_token(token) |
|
|
| base_folder = MEDIA_ROOT / sha1 |
| if not base_folder.exists() or not base_folder.is_dir(): |
| raise HTTPException(status_code=404, detail="SHA1 folder not found") |
|
|
| cast_folder = base_folder / "cast" |
| cast_folder.mkdir(parents=True, exist_ok=True) |
|
|
| final_path = cast_folder / "cast.csv" |
|
|
| file_bytes = await cast_file.read() |
| 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", "saved_to": str(final_path)} |
| ) |
|
|
|
|
| @router.get("/download_cast_csv", tags=["Media Manager"]) |
| def download_cast_csv( |
| sha1: str, |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """ |
| Download the cast CSV for a specific video identified by its SHA-1. |
| |
| The CSV is expected under: |
| /data/media/<sha1>/cast/cast.csv |
| |
| Steps: |
| - Validate the token. |
| - Ensure /data/media/<sha1> and /cast exist. |
| - Return the CSV as a FileResponse. |
| - Raise 404 if any folder or file is missing. |
| """ |
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
| HF_TOKEN = os.getenv("HF_TOKEN") |
| validate_token(token) |
|
|
| base_folder = MEDIA_ROOT / sha1 |
| cast_folder = base_folder / "cast" |
| csv_path = cast_folder / "cast.csv" |
|
|
| if not base_folder.exists() or not base_folder.is_dir(): |
| raise HTTPException(status_code=404, detail="SHA1 folder not found") |
| if not cast_folder.exists() or not cast_folder.is_dir(): |
| raise HTTPException(status_code=404, detail="Cast folder not found") |
| if not csv_path.exists() or not csv_path.is_file(): |
| raise HTTPException(status_code=404, detail="Cast CSV not found") |
|
|
| |
| relative_path = csv_path.relative_to(MEDIA_ROOT) |
| handler = file_manager.get_file(relative_path) |
| if handler is None: |
| raise HTTPException(status_code=404, detail="Cast CSV not accessible") |
| handler.close() |
|
|
| return FileResponse( |
| path=csv_path, |
| media_type="text/csv", |
| filename="cast.csv" |
| ) |
|
|
| @router.post("/upload_original_video", tags=["Media 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>/clip/<original_filename> |
| |
| Behavior: |
| - Compute SHA1 of the uploaded video. |
| - Ensure folder structure exists. |
| - Delete any existing .mp4 files under /clip. |
| - Save the uploaded video in the clip folder. |
| """ |
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
| HF_TOKEN = os.getenv("HF_TOKEN") |
| validate_token(token) |
|
|
| |
| 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) |
|
|
| |
| clip_dir = video_root / "clip" |
| clip_dir.mkdir(parents=True, exist_ok=True) |
|
|
| |
| try: |
| for old_mp4 in clip_dir.glob("*.mp4"): |
| old_mp4.unlink() |
| except Exception as exc: |
| raise HTTPException(status_code=500, detail=f"Failed to delete old videos: {exc}") |
|
|
| |
| final_path = clip_dir / 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_original_video", tags=["Media 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. |
| """ |
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
| HF_TOKEN = os.getenv("HF_TOKEN") |
| validate_token(token) |
|
|
| sha1_folder = MEDIA_ROOT / sha1 |
| clip_folder = sha1_folder / "clip" |
|
|
| if not sha1_folder.exists() or not sha1_folder.is_dir(): |
| raise HTTPException(status_code=404, detail="SHA1 folder not found") |
|
|
| if not clip_folder.exists() or not clip_folder.is_dir(): |
| raise HTTPException(status_code=404, detail="Clip folder not found") |
|
|
| |
| mp4_files = list(clip_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.delete("/delete_video", tags=["Media Manager"]) |
| def delete_media( |
| hash: str, |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """ |
| Delete a stored media directory by its hash. |
| |
| This endpoint removes the folder located at: |
| /data/media/<hash> |
| |
| The method performs the following steps: |
| - Validates the authorization token. |
| - Checks if the hash folder exists inside the media root. |
| - Deletes the entire directory recursively. |
| |
| Parameters |
| ---------- |
| hash : str |
| The hash corresponding to the directory to be deleted. |
| |
| Returns |
| ------- |
| dict |
| A confirmation message indicating successful deletion. |
| |
| Raises |
| ------ |
| HTTPException |
| - 404 if the hash folder does not exist. |
| - 500 if the folder cannot be deleted. |
| """ |
| MEDIA_ROOT = Path("/data/media") |
| validate_token(token) |
|
|
| hash_folder = MEDIA_ROOT / hash |
|
|
| if not hash_folder.exists() or not hash_folder.is_dir(): |
| raise HTTPException(status_code=404, detail="Media folder not found") |
|
|
| try: |
| shutil.rmtree(hash_folder) |
| except Exception as e: |
| raise HTTPException( |
| status_code=500, |
| detail=f"Failed to delete media folder: {str(e)}" |
| ) |
|
|
| return { |
| "status": "ok", |
| "message": f"Media folder '{hash}' deleted successfully" |
| } |
|
|
| @router.post("/upload_video_ad", tags=["Media Manager"]) |
| async def upload_video_ad( |
| sha1: str = Query(..., description="SHA1 associated to the media folder"), |
| version: str = Query(..., description="Version: Salamandra or MoE"), |
| subtype: str = Query(..., description="Subtype: Original, HITL OK or HITL Test"), |
| video: UploadFile = File(...), |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| validate_token(token) |
|
|
| if version not in VALID_VERSIONS: |
| raise HTTPException(status_code=400, detail="Invalid version") |
| if subtype not in VALID_SUBTYPES: |
| raise HTTPException(status_code=400, detail="Invalid subtype") |
|
|
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
|
|
| subtype_dir = MEDIA_ROOT / sha1 / version / subtype |
| subtype_dir.mkdir(parents=True, exist_ok=True) |
|
|
| for f in subtype_dir.glob("*.mp4"): |
| f.unlink() |
|
|
| file_bytes = await video.read() |
| final_path = subtype_dir / 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 { |
| "status": "ok", |
| "sha1": sha1, |
| "version": version, |
| "subtype": subtype, |
| "saved_to": str(final_path) |
| } |
|
|
|
|
| @router.get("/download_video_ad", tags=["Media Manager"]) |
| def download_video_ad( |
| sha1: str, |
| version: str, |
| subtype: str, |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| validate_token(token) |
|
|
| if version not in VALID_VERSIONS: |
| raise HTTPException(status_code=400, detail="Invalid version") |
| if subtype not in VALID_SUBTYPES: |
| raise HTTPException(status_code=400, detail="Invalid subtype") |
|
|
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
|
|
| subtype_dir = MEDIA_ROOT / sha1 / version / subtype |
|
|
| if not subtype_dir.exists() or not subtype_dir.is_dir(): |
| raise HTTPException(status_code=404, detail="Version/subtype folder not found") |
|
|
| mp4_files = list(subtype_dir.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_original_videos", tags=["Media 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 |
|
|
| clip_dir = sha1_dir / "clip" |
|
|
| videos = [] |
| latest_video = None |
|
|
| if clip_dir.exists() and clip_dir.is_dir(): |
| mp4_files = list(clip_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 |
|
|
| @router.post("/upload_original_audio", tags=["Media Manager"]) |
| async def upload_audio( |
| audio: UploadFile = File(...), |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """ |
| Saves an uploaded audio file by hashing it with SHA1 and placing it under: |
| /data/media/<sha1>/audio/<original_filename> |
| |
| Behavior: |
| - Compute SHA1 of the uploaded audio. |
| - Ensure folder structure exists. |
| - Delete any existing audio files under /audio. |
| - Save the uploaded audio in the audio folder. |
| """ |
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
| HF_TOKEN = os.getenv("HF_TOKEN") |
| validate_token(token) |
|
|
| |
| file_bytes = await audio.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) |
|
|
| |
| audio_root = MEDIA_ROOT / sha1 |
| audio_root.mkdir(parents=True, exist_ok=True) |
|
|
| |
| audio_dir = audio_root / "audio" |
| audio_dir.mkdir(parents=True, exist_ok=True) |
|
|
| |
| AUDIO_EXTENSIONS = ("*.mp3", "*.wav", "*.m4a", "*.aac", "*.ogg", "*.flac") |
| try: |
| for pattern in AUDIO_EXTENSIONS: |
| for old_audio in audio_dir.glob(pattern): |
| old_audio.unlink() |
| except Exception as exc: |
| raise HTTPException(status_code=500, detail=f"Failed to delete old audio files: {exc}") |
|
|
| |
| final_path = audio_dir / audio.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_original_audio", tags=["Media Manager"]) |
| def download_audio( |
| sha1: str, |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """ |
| Download a stored audio file by its SHA-1 directory name. |
| |
| This endpoint looks for audio stored under the path: |
| /data/media/<sha1>/audio/ |
| and returns the first audio 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 "audio" subfolder exists. |
| - Searches for the first supported audio file. |
| - Uses FileManager.get_file to ensure the file is accessible. |
| - Returns the audio file as a FileResponse. |
| |
| Parameters |
| ---------- |
| sha1 : str |
| The SHA-1 hash corresponding to the directory where the audio is stored. |
| |
| Returns |
| ------- |
| FileResponse |
| A streaming response containing the audio file. |
| |
| Raises |
| ------ |
| HTTPException |
| - 404 if the SHA-1 folder does not exist. |
| - 404 if the audio folder is missing. |
| - 404 if no audio files are found. |
| - 404 if the file cannot be retrieved using FileManager. |
| """ |
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
| HF_TOKEN = os.getenv("HF_TOKEN") |
| validate_token(token) |
|
|
| sha1_folder = MEDIA_ROOT / sha1 |
| audio_folder = sha1_folder / "audio" |
|
|
| if not sha1_folder.exists() or not sha1_folder.is_dir(): |
| raise HTTPException(status_code=404, detail="SHA1 folder not found") |
|
|
| if not audio_folder.exists() or not audio_folder.is_dir(): |
| raise HTTPException(status_code=404, detail="Audio folder not found") |
|
|
| |
| AUDIO_EXTENSIONS = ["*.mp3", "*.wav", "*.aac", "*.m4a", "*.ogg", "*.flac"] |
|
|
| audio_files = [] |
| for pattern in AUDIO_EXTENSIONS: |
| audio_files.extend(list(audio_folder.glob(pattern))) |
|
|
| if not audio_files: |
| raise HTTPException(status_code=404, detail="No audio files found") |
|
|
| audio_path = audio_files[0] |
|
|
| |
| relative_path = audio_path.relative_to(MEDIA_ROOT) |
|
|
| handler = file_manager.get_file(relative_path) |
| if handler is None: |
| raise HTTPException(status_code=404, detail="Audio file not accessible") |
|
|
| handler.close() |
|
|
| |
| media_type = "audio/" + audio_path.suffix.lstrip(".") |
|
|
| return FileResponse( |
| path=audio_path, |
| media_type=media_type, |
| filename=audio_path.name |
| ) |
|
|
| @router.get("/list_original_audios", tags=["Media Manager"]) |
| def list_all_audios( |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """ |
| List all audio files stored under /data/media. |
| |
| For each SHA1 folder, the endpoint returns: |
| - sha1: folder name |
| - audio_files: list of audio files inside /audio |
| - latest_audio: the most recently modified audio file |
| - audio_count: total number of audio files |
| |
| Notes: |
| - Folders may not have an /audio folder. |
| - SHA1 folders without audio files are still returned. |
| """ |
| validate_token(token) |
|
|
| results = [] |
|
|
| MEDIA_ROOT = Path("/data/media") |
| AUDIO_EXTENSIONS = ["*.mp3", "*.wav", "*.aac", "*.m4a", "*.ogg", "*.flac"] |
|
|
| |
| if not MEDIA_ROOT.exists(): |
| return [] |
|
|
| for sha1_dir in MEDIA_ROOT.iterdir(): |
| if not sha1_dir.is_dir(): |
| continue |
|
|
| audio_dir = sha1_dir / "audio" |
|
|
| audio_files = [] |
| latest_audio = None |
|
|
| if audio_dir.exists() and audio_dir.is_dir(): |
| |
| files = [] |
| for pattern in AUDIO_EXTENSIONS: |
| files.extend(list(audio_dir.glob(pattern))) |
|
|
| |
| files.sort(key=lambda f: f.stat().st_mtime, reverse=True) |
|
|
| audio_files = [f.name for f in files] |
|
|
| if files: |
| latest_audio = files[0].name |
|
|
| results.append({ |
| "sha1": sha1_dir.name, |
| "audio_name": latest_audio, |
| }) |
|
|
| return results |
|
|
|
|
| @router.post("/upload_audio_version", tags=["Media Manager"]) |
| async def upload_audio_version( |
| audio: UploadFile = File(...), |
| sha1: str = Query(..., description="SHA1 of the video folder"), |
| version: str = Query(..., description="Version: Salamandra or MoE"), |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """Upload audio for a given version (Salamandra, MoE). |
| |
| This legacy endpoint keeps its path but now interprets the former |
| `subtype` path component as `version`: |
| - Target folder: /data/media/<sha1>/<version>/ |
| - Deletes any previous audio files |
| - Saves the new audio |
| """ |
| validate_token(token) |
|
|
| if version not in VALID_VERSIONS: |
| raise HTTPException(status_code=400, detail="Invalid version") |
|
|
| MEDIA_ROOT = Path("/data/media") |
| version_dir = MEDIA_ROOT / sha1 / version |
| version_dir.mkdir(parents=True, exist_ok=True) |
|
|
| |
| try: |
| for pattern in AUDIO_EXTENSIONS: |
| for old_audio in version_dir.glob(pattern): |
| old_audio.unlink() |
| except Exception as exc: |
| raise HTTPException(status_code=500, detail=f"Failed to delete old audio files: {exc}") |
|
|
| final_path = version_dir / audio.filename |
|
|
| try: |
| file_bytes = await audio.read() |
| with open(final_path, "wb") as f: |
| f.write(file_bytes) |
| except Exception as exc: |
| raise HTTPException(status_code=500, detail=f"Failed to save audio: {exc}") |
|
|
| return JSONResponse( |
| status_code=200, |
| content={ |
| "status": "ok", |
| "sha1": sha1, |
| "version": version, |
| "saved_to": str(final_path) |
| } |
| ) |
|
|
|
|
| @router.get("/download_audio_version", tags=["Media Manager"]) |
| def download_audio_version( |
| sha1: str, |
| version: str, |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """Download the first audio file for a given version (Salamandra, MoE).""" |
| validate_token(token) |
|
|
| if version not in VALID_VERSIONS: |
| raise HTTPException(status_code=400, detail="Invalid version") |
|
|
| MEDIA_ROOT = Path("/data/media") |
| version_dir = MEDIA_ROOT / sha1 / version |
|
|
| if not version_dir.exists() or not version_dir.is_dir(): |
| raise HTTPException(status_code=404, detail=f"{version} folder not found") |
|
|
| |
| audio_files = [] |
| for pattern in AUDIO_EXTENSIONS: |
| audio_files.extend(list(version_dir.glob(pattern))) |
|
|
| if not audio_files: |
| raise HTTPException(status_code=404, detail="No audio files found") |
|
|
| audio_path = audio_files[0] |
|
|
| return FileResponse( |
| path=audio_path, |
| media_type="audio/" + audio_path.suffix.lstrip("."), |
| filename=audio_path.name |
| ) |
|
|
|
|
| @router.post("/upload_audio_ad", tags=["Media Manager"]) |
| async def upload_audio_ad( |
| audio: UploadFile = File(...), |
| sha1: str = Query(..., description="SHA1 of the video folder"), |
| version: str = Query(..., description="Version: Salamandra or MoE"), |
| subtype: str = Query(..., description="Subtype: Original, HITL OK or HITL Test"), |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| validate_token(token) |
|
|
| if version not in VALID_VERSIONS: |
| raise HTTPException(status_code=400, detail="Invalid version") |
| if subtype not in VALID_SUBTYPES: |
| raise HTTPException(status_code=400, detail="Invalid subtype") |
|
|
| MEDIA_ROOT = Path("/data/media") |
| subtype_dir = MEDIA_ROOT / sha1 / version / subtype |
| subtype_dir.mkdir(parents=True, exist_ok=True) |
|
|
| try: |
| for pattern in AUDIO_EXTENSIONS: |
| for old_audio in subtype_dir.glob(pattern): |
| old_audio.unlink() |
| except Exception as exc: |
| raise HTTPException(status_code=500, detail=f"Failed to delete old audio files: {exc}") |
|
|
| final_path = subtype_dir / audio.filename |
|
|
| try: |
| file_bytes = await audio.read() |
| with open(final_path, "wb") as f: |
| f.write(file_bytes) |
| except Exception as exc: |
| raise HTTPException(status_code=500, detail=f"Failed to save audio: {exc}") |
|
|
| return JSONResponse( |
| status_code=200, |
| content={ |
| "status": "ok", |
| "sha1": sha1, |
| "version": version, |
| "subtype": subtype, |
| "saved_to": str(final_path) |
| } |
| ) |
|
|
|
|
| @router.get("/download_audio_ad", tags=["Media Manager"]) |
| def download_audio_ad( |
| sha1: str, |
| version: str, |
| subtype: str, |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| validate_token(token) |
|
|
| if version not in VALID_VERSIONS: |
| raise HTTPException(status_code=400, detail="Invalid version") |
| if subtype not in VALID_SUBTYPES: |
| raise HTTPException(status_code=400, detail="Invalid subtype") |
|
|
| MEDIA_ROOT = Path("/data/media") |
| subtype_dir = MEDIA_ROOT / sha1 / version / subtype |
|
|
| if not subtype_dir.exists() or not subtype_dir.is_dir(): |
| raise HTTPException(status_code=404, detail="Version/subtype folder not found") |
|
|
| audio_files = [] |
| for pattern in AUDIO_EXTENSIONS: |
| audio_files.extend(list(subtype_dir.glob(pattern))) |
|
|
| if not audio_files: |
| raise HTTPException(status_code=404, detail="No audio files found") |
|
|
| audio_path = audio_files[0] |
|
|
| return FileResponse( |
| path=audio_path, |
| media_type="audio/" + audio_path.suffix.lstrip("."), |
| filename=audio_path.name |
| ) |
|
|
|
|
| @router.get("/list_version_audios", tags=["Media Manager"]) |
| def list_version_audios( |
| sha1: str = Query(..., description="SHA1 of the video folder"), |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """List the most recent audio file for each version (Salamandra, MoE) |
| under /data/media/<sha1>. |
| |
| Returns: |
| - sha1: folder name |
| - version: name of the version |
| - audio_name: latest audio file or None |
| """ |
| validate_token(token) |
|
|
| results = [] |
|
|
| for version in VALID_VERSIONS: |
| version_dir = MEDIA_ROOT / sha1 / version |
|
|
| latest_audio = None |
|
|
| if version_dir.exists() and version_dir.is_dir(): |
| files = [] |
| for pattern in AUDIO_EXTENSIONS: |
| files.extend(list(version_dir.glob(pattern))) |
|
|
| if files: |
| |
| files.sort(key=lambda f: f.stat().st_mtime, reverse=True) |
| latest_audio = files[0].name |
|
|
| results.append({ |
| "sha1": sha1, |
| "version": version, |
| "audio_name": latest_audio |
| }) |
|
|
| return results |
|
|
|
|
| @router.post("/upload_version_video", tags=["Media Manager"]) |
| async def upload_version_video( |
| sha1: str = Query(..., description="SHA1 associated to the media folder"), |
| version: str = Query(..., description="Version: Salamandra or MoE"), |
| video: UploadFile = File(...), |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """Upload a video to /data/media/<sha1>/<version>/. |
| |
| This legacy endpoint keeps its path but now interprets the former |
| `subtype` path component as `version`. |
| Steps: |
| - Validate version. |
| - Create version folder if missing. |
| - Delete existing MP4 files. |
| - Save new MP4. |
| """ |
| validate_token(token) |
|
|
| if version not in VALID_VERSIONS: |
| raise HTTPException(status_code=400, detail="Invalid version") |
|
|
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
|
|
| version_dir = MEDIA_ROOT / sha1 / version |
| version_dir.mkdir(parents=True, exist_ok=True) |
|
|
| |
| for f in version_dir.glob("*.mp4"): |
| f.unlink() |
|
|
| file_bytes = await video.read() |
| final_path = version_dir / 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 { |
| "status": "ok", |
| "sha1": sha1, |
| "version": version, |
| "saved_to": str(final_path) |
| } |
|
|
|
|
| @router.get("/download_version_video", tags=["Media Manager"]) |
| def download_version_video( |
| sha1: str, |
| version: str, |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """Download the video stored under /data/media/<sha1>/<version>. |
| Returns the first MP4 found. |
| """ |
| validate_token(token) |
|
|
| if version not in VALID_VERSIONS: |
| raise HTTPException(status_code=400, detail="Invalid version") |
|
|
| MEDIA_ROOT = Path("/data/media") |
| file_manager = FileManager(MEDIA_ROOT) |
|
|
| version_dir = MEDIA_ROOT / sha1 / version |
|
|
| if not version_dir.exists() or not version_dir.is_dir(): |
| raise HTTPException(status_code=404, detail="Version folder not found") |
|
|
| mp4_files = list(version_dir.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_version_videos", tags=["Media Manager"]) |
| def list_version_videos( |
| sha1: str, |
| token: str = Query(..., description="Token required for authorization") |
| ): |
| """List the most recent .mp4 video for each version (Salamandra, MoE) |
| inside /data/media/<sha1>. |
| |
| Returns: |
| - sha1 |
| - version |
| - video_name (latest mp4 or None) |
| """ |
| validate_token(token) |
|
|
| MEDIA_ROOT = Path("/data/media") |
|
|
| results = [] |
|
|
| for version in VALID_VERSIONS: |
| version_dir = MEDIA_ROOT / sha1 / version |
|
|
| latest_video = None |
|
|
| if version_dir.exists() and version_dir.is_dir(): |
| files = list(version_dir.glob("*.mp4")) |
| if files: |
| files.sort(key=lambda f: f.stat().st_mtime, reverse=True) |
| latest_video = files[0].name |
|
|
| results.append({ |
| "sha1": sha1, |
| "version": version, |
| "video_name": latest_video |
| }) |
|
|
| return results |
|
|