Files
MediaLore-Web-App/backend/app/routers/media.py
2026-05-09 12:34:45 -04:00

86 lines
3.0 KiB
Python

from pathlib import Path
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database import get_db
from app.models import Library, MediaItem, Tag
from app.schemas import MediaItemOut, TagIdList
from app.services.thumbnails import get_or_create_thumbnail
router = APIRouter(prefix="/media", tags=["media"])
async def _get_item_and_lib(media_id: int, db: AsyncSession) -> tuple[MediaItem, Library]:
result = await db.execute(
select(MediaItem).where(MediaItem.id == media_id)
)
item = result.scalars().first()
if not item:
raise HTTPException(404, "Media item not found")
lib_result = await db.execute(select(Library).where(Library.id == item.library_id))
lib = lib_result.scalars().first()
return item, lib
def _resolve_safe(lib: Library, item: MediaItem) -> Path:
root = Path(lib.path)
abs_path = (root / item.rel_path).resolve()
if not str(abs_path).startswith(str(root.resolve())):
raise HTTPException(400, "Invalid path")
return abs_path
@router.get("/{media_id}", response_model=MediaItemOut)
async def get_media_item(media_id: int, db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(MediaItem).where(MediaItem.id == media_id)
)
item = result.scalars().first()
if not item:
raise HTTPException(404, "Media item not found")
# Eagerly load tags
await db.refresh(item, ["tags"])
return item
@router.get("/{media_id}/file")
async def serve_file(media_id: int, db: AsyncSession = Depends(get_db)):
item, lib = await _get_item_and_lib(media_id, db)
if item.missing:
raise HTTPException(404, "File is missing from disk")
abs_path = _resolve_safe(lib, item)
if not abs_path.exists():
raise HTTPException(404, "File not found on disk")
return FileResponse(str(abs_path))
@router.get("/{media_id}/thumbnail")
async def serve_thumbnail(media_id: int, db: AsyncSession = Depends(get_db)):
item, lib = await _get_item_and_lib(media_id, db)
abs_path = _resolve_safe(lib, item)
thumb = get_or_create_thumbnail(media_id, str(abs_path), item.media_type)
if not thumb:
raise HTTPException(404, "Thumbnail could not be generated")
return FileResponse(str(thumb), media_type="image/jpeg")
@router.put("/{media_id}/tags", response_model=MediaItemOut)
async def set_tags(media_id: int, body: TagIdList, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(MediaItem).where(MediaItem.id == media_id))
item = result.scalars().first()
if not item:
raise HTTPException(404, "Media item not found")
tags_result = await db.execute(select(Tag).where(Tag.id.in_(body.tag_ids)))
tags = tags_result.scalars().all()
if len(tags) != len(body.tag_ids):
raise HTTPException(400, "One or more tag IDs not found")
await db.refresh(item, ["tags"])
item.tags = list(tags)
await db.commit()
await db.refresh(item, ["tags"])
return item