feat: archive upload and date-folder endpoints (REQ-171)

This commit is contained in:
Andrew-AAAA committed 2026-09-29 08:46:28 +07:00
1 parent d170cff0e4
commit e2f4a51838
2 files changed
+66

No files matched your search

+27
View File
@@ -151,6 +151,33 @@ def list_library_date(project_id: int, date: str) -> dict:
raise HTTPException(400, str(exc))
@router.post("/{project_id}/library/dates")
def create_library_date(project_id: int, date: str) -> dict:
project = project_or_404(project_id)
try:
library.create_date(project["video_root"], date)
except library.LibraryConflict as exc:
raise HTTPException(409, str(exc))
except library.LibraryError as exc:
raise HTTPException(400, str(exc))
return {"date": date}
@router.post("/{project_id}/library/upload")
def upload_library_video(project_id: int, date: str,
file: UploadFile = File(...)) -> dict:
project = project_or_404(project_id)
try:
rel = library.upload_video(
project["video_root"], date, file.filename or "",
iter(lambda: file.file.read(1 << 20), b""))
except library.LibraryConflict as exc:
raise HTTPException(409, str(exc))
except library.LibraryError as exc:
raise HTTPException(400, str(exc))
return {"rel": rel}
@router.get("/{project_id}/video/info")
def video_info(project_id: int, rel: str) -> dict:
project = project_or_404(project_id)
+39
View File
@@ -99,6 +99,45 @@ def list_videos(video_root: str, date: str, project_id: Optional[int] = None) ->
return videos
class LibraryConflict(LibraryError):
pass
def create_date(video_root: str, date: str) -> str:
"""Make a YYYY-MM-DD folder; never touch an existing one (REQ-171)."""
if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", date):
raise LibraryError("Folder name must be YYYY-MM-DD")
folder = _safe_join(_effective_root(video_root), date)
if os.path.exists(folder):
raise LibraryConflict(f"Folder already exists: {date}")
os.makedirs(folder)
return folder
def upload_video(video_root: str, date: str, filename: str, chunks) -> str:
"""Stream `chunks` into <date>/<filename>; atomic via .part + rename."""
name = os.path.basename(filename or "")
if not name or name != filename or not name.lower().endswith(video.VIDEO_EXTS):
raise LibraryError(f"Unsupported file: {filename}")
root = _effective_root(video_root)
if not os.path.isdir(_safe_join(root, date)):
raise LibraryError(f"No such date in the archive: {date}")
dest = _safe_join(root, date, name)
if os.path.exists(dest):
raise LibraryConflict(f"Already exists: {date}/{name}")
part = dest + ".part"
try:
with open(part, "wb") as handle:
for chunk in chunks:
handle.write(chunk)
os.replace(part, dest)
except BaseException:
if os.path.exists(part):
os.unlink(part)
raise
return f"{date}/{name}"
import threading
_conversion_queue = set()