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