engine / storage /media_routers.py
VeuReu's picture
Update storage/media_routers.py
f3e7f3d verified
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")
# Delete contents
for item in MEDIA_ROOT.iterdir():
try:
if item.is_dir():
shutil.rmtree(item)
else:
item.unlink()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to delete {item}: {e}")
return {"status": "ok", "message": "All 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")
# Convert to relative path for FileManager
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)
# Read content into memory (needed to compute hash twice)
file_bytes = await video.read()
# Create an in-memory file handler for hashing
file_handler = io.BytesIO(file_bytes)
# Compute SHA1
try:
sha1 = file_manager.compute_sha1(file_handler)
except Exception as exc:
raise HTTPException(status_code=500, detail=f"SHA1 computation failed: {exc}")
# Ensure /data/media exists
MEDIA_ROOT.mkdir(parents=True, exist_ok=True)
# Path: /data/media/<sha1>
video_root = MEDIA_ROOT / sha1
video_root.mkdir(parents=True, exist_ok=True)
# Path: /data/media/<sha1>/clip
clip_dir = video_root / "clip"
clip_dir.mkdir(parents=True, exist_ok=True)
# Delete old MP4 files
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}")
# Save new video path
final_path = clip_dir / video.filename
# Save file
save_result = file_manager.upload_file(io.BytesIO(file_bytes), final_path)
if not save_result["operation_success"]:
raise HTTPException(status_code=500, detail=save_result["error"])
return JSONResponse(
status_code=200,
content={
"status": "ok",
"sha1": sha1,
"saved_to": str(final_path)
}
)
@router.get("/download_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")
# Find first MP4 file
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]
# Convert to relative path for FileManager
relative_path = video_path.relative_to(MEDIA_ROOT)
handler = file_manager.get_file(relative_path)
if handler is None:
raise HTTPException(status_code=404, detail="Video not accessible")
handler.close()
return FileResponse(
path=video_path,
media_type="video/mp4",
filename=video_path.name
)
@router.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 media root does not exist, return empty list
if not MEDIA_ROOT.exists():
return []
for sha1_dir in MEDIA_ROOT.iterdir():
if not sha1_dir.is_dir():
continue # skip non-folders
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"))
# Sort by modification time (newest first)
mp4_files.sort(key=lambda f: f.stat().st_mtime, reverse=True)
videos = [f.name for f in mp4_files]
if mp4_files:
latest_video = mp4_files[0].name
results.append({
"sha1": sha1_dir.name,
"video_name": latest_video
})
return results
@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)
# Read content into memory (needed to compute hash twice)
file_bytes = await audio.read()
# Create an in-memory file handler for hashing
file_handler = io.BytesIO(file_bytes)
# Compute SHA1
try:
sha1 = file_manager.compute_sha1(file_handler)
except Exception as exc:
raise HTTPException(status_code=500, detail=f"SHA1 computation failed: {exc}")
# Ensure /data/media exists
MEDIA_ROOT.mkdir(parents=True, exist_ok=True)
# Path: /data/media/<sha1>
audio_root = MEDIA_ROOT / sha1
audio_root.mkdir(parents=True, exist_ok=True)
# Path: /data/media/<sha1>/audio
audio_dir = audio_root / "audio"
audio_dir.mkdir(parents=True, exist_ok=True)
# Delete old audio files
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 save path
final_path = audio_dir / audio.filename
# Save file
save_result = file_manager.upload_file(io.BytesIO(file_bytes), final_path)
if not save_result["operation_success"]:
raise HTTPException(status_code=500, detail=save_result["error"])
return JSONResponse(
status_code=200,
content={
"status": "ok",
"sha1": sha1,
"saved_to": str(final_path)
}
)
@router.get("/download_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")
# Supported audio extensions
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]
# Convert to relative path for FileManager
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()
# Guess media type based on extension (simple)
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 media root does not exist, return empty list
if not MEDIA_ROOT.exists():
return []
for sha1_dir in MEDIA_ROOT.iterdir():
if not sha1_dir.is_dir():
continue # skip non-folders
audio_dir = sha1_dir / "audio"
audio_files = []
latest_audio = None
if audio_dir.exists() and audio_dir.is_dir():
# Collect all audio files with supported extensions
files = []
for pattern in AUDIO_EXTENSIONS:
files.extend(list(audio_dir.glob(pattern)))
# Sort by modification time (newest first)
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)
# Delete old audio files
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")
# Find audio files
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:
# Sort by modification time (newest first)
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)
# Remove old mp4 files
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