|
|
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 |
|
|
|