51b10438a9
- backend/ uses proper Python src layout (src/media_library_viewer_api/) with pyproject.toml, hatchling build, and PYTHONPATH=src convention - frontend/ is a Vite + React + TypeScript SPA - archive/ preserves the original Streamlit prototype for reference - Cleaned up root to only contain docs, license, and subproject dirs - Updated README for the new dual-subproject architecture
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
"""Media router — index status, build, and query."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
|
|
from media_library_viewer_api.dependencies import get_jellyfin_client, get_user_id
|
|
from media_library_viewer_api.clients.jellyfin import JellyfinClient
|
|
from media_library_viewer_api.services.media_index import MediaIndex, build_media_index
|
|
|
|
router = APIRouter(prefix="/api/media", tags=["media"])
|
|
|
|
|
|
def get_media_index() -> MediaIndex:
|
|
return MediaIndex()
|
|
|
|
|
|
@router.get("/status")
|
|
def get_index_status(index: MediaIndex = Depends(get_media_index)) -> dict[str, Any]:
|
|
"""Return media index status (exists, count, last updated, build duration)."""
|
|
status = index.status()
|
|
return {
|
|
"exists": status.exists,
|
|
"item_count": status.item_count,
|
|
"updated_at": status.updated_at,
|
|
"updated_at_label": status.updated_at_label,
|
|
"build_duration_seconds": status.build_duration_seconds,
|
|
}
|
|
|
|
|
|
@router.post("/build")
|
|
def post_build_index(
|
|
client: JellyfinClient = Depends(get_jellyfin_client),
|
|
user_id: str = Depends(get_user_id),
|
|
index: MediaIndex = Depends(get_media_index),
|
|
) -> dict[str, Any]:
|
|
"""Rebuild the media index from Jellyfin."""
|
|
libraries = client.libraries(user_id)
|
|
count = build_media_index(client, user_id, libraries, index)
|
|
return {"indexed_items": count}
|
|
|
|
|
|
@router.get("/query")
|
|
def query_media(
|
|
libraries: str = Query("", description="Comma-separated library IDs"),
|
|
types: str = Query("Movie,Episode", description="Comma-separated media types"),
|
|
search: str = Query("", description="Search term"),
|
|
hdr_filter: str = Query("All", description="All, HDR only, SDR/unknown only"),
|
|
sort_key: str = Query("title", description="Sort field"),
|
|
sort_order: str = Query("Ascending", description="Ascending or Descending"),
|
|
limit: int = Query(100, ge=1, le=1000),
|
|
offset: int = Query(0, ge=0),
|
|
client: JellyfinClient = Depends(get_jellyfin_client),
|
|
user_id: str = Depends(get_user_id),
|
|
index: MediaIndex = Depends(get_media_index),
|
|
) -> dict[str, Any]:
|
|
"""Query the media index with filters, sorting, and pagination."""
|
|
# If no library IDs provided, use all libraries
|
|
library_ids = [lid.strip() for lid in libraries.split(",") if lid.strip()] if libraries else None
|
|
if not library_ids:
|
|
all_libs = client.libraries(user_id)
|
|
library_ids = [lib["Id"] for lib in all_libs]
|
|
|
|
media_types = [t.strip() for t in types.split(",") if t.strip()]
|
|
|
|
rows, total = index.query(
|
|
library_ids=library_ids,
|
|
media_types=media_types,
|
|
search=search,
|
|
hdr_filter=hdr_filter,
|
|
sort_key=sort_key,
|
|
sort_order=sort_order,
|
|
limit=limit,
|
|
offset=offset,
|
|
)
|
|
|
|
return {
|
|
"items": rows,
|
|
"total": total,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
}
|