engine / storage /media_routers.py
VeuReu's picture
Upload 7 files
46cf14b verified
raw
history blame
30.8 kB
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.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