feat: archive upload and date-folder endpoints (REQ-171)
This commit is contained in:
1 parent
d170cff0e4
commit
e2f4a51838
2 files changed
+66
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user