Initial release — MediaTracker v1.0
Dockerized media library scanner for NAS drives. Identifies movies and series from filenames, fetches posters via the public IMDB suggestion API (no key required), and exposes a dark-themed web UI with multilingual search support. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
102
backend/routers/search.py
Normal file
102
backend/routers/search.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from database import Movie, NasSource, Season, Series, get_session
|
||||
from metadata import _imdb_suggest
|
||||
|
||||
router = APIRouter(prefix="/api/search", tags=["search"])
|
||||
|
||||
_IMDB_QID = {"movie": "movie", "series": "tvSeries"}
|
||||
|
||||
|
||||
def _imdb_ids_for_query(q: str, kind: str) -> list[str]:
|
||||
"""Search IMDB with the user query and return matching IMDB IDs."""
|
||||
items = _imdb_suggest(q)
|
||||
qid = _IMDB_QID.get(kind)
|
||||
return [it["id"] for it in items if it.get("qid") == qid and it.get("id")]
|
||||
|
||||
|
||||
def _movie_matches(session, pattern: str, q: str) -> list:
|
||||
"""Return movies matching by title in DB, OR via IMDB cross-reference."""
|
||||
# Direct title match in DB
|
||||
by_title = session.query(Movie).filter(Movie.title.ilike(pattern)).all()
|
||||
found_ids = {m.id for m in by_title}
|
||||
|
||||
# Cross-language: search IMDB and look up matching IMDB IDs in our DB
|
||||
imdb_ids = _imdb_ids_for_query(q, "movie")
|
||||
if imdb_ids:
|
||||
cross = session.query(Movie).filter(
|
||||
Movie.imdb_id.in_(imdb_ids),
|
||||
Movie.id.notin_(found_ids) if found_ids else Movie.id.isnot(None),
|
||||
).all()
|
||||
by_title = by_title + cross
|
||||
|
||||
return sorted(by_title, key=lambda m: m.title)[:50]
|
||||
|
||||
|
||||
def _series_matches(session, pattern: str, q: str) -> list:
|
||||
"""Return series matching by title in DB, OR via IMDB cross-reference."""
|
||||
by_title = session.query(Series).filter(Series.title.ilike(pattern)).all()
|
||||
found_ids = {s.id for s in by_title}
|
||||
|
||||
imdb_ids = _imdb_ids_for_query(q, "series")
|
||||
if imdb_ids:
|
||||
cross = session.query(Series).filter(
|
||||
Series.imdb_id.in_(imdb_ids),
|
||||
Series.id.notin_(found_ids) if found_ids else Series.id.isnot(None),
|
||||
).all()
|
||||
by_title = by_title + cross
|
||||
|
||||
return sorted(by_title, key=lambda s: s.title)[:50]
|
||||
|
||||
|
||||
@router.get("")
|
||||
def search(
|
||||
q: str = Query(..., min_length=1),
|
||||
type: str = Query("all", pattern="^(all|movie|series)$"),
|
||||
):
|
||||
session = get_session()
|
||||
try:
|
||||
pattern = f"%{q}%"
|
||||
results = {"movies": [], "series": []}
|
||||
|
||||
if type in ("all", "movie"):
|
||||
for m in _movie_matches(session, pattern, q):
|
||||
nas = session.query(NasSource).get(m.nas_id)
|
||||
results["movies"].append({
|
||||
"id": m.id,
|
||||
"title": m.title,
|
||||
"year": m.year,
|
||||
"quality": m.quality,
|
||||
"codec": m.codec,
|
||||
"nas_name": nas.name if nas else None,
|
||||
"file_path": m.file_path,
|
||||
"file_size": m.file_size,
|
||||
"poster_url": m.poster_url,
|
||||
"imdb_id": m.imdb_id,
|
||||
})
|
||||
|
||||
if type in ("all", "series"):
|
||||
for s in _series_matches(session, pattern, q):
|
||||
seasons_data = []
|
||||
for season in sorted(s.seasons, key=lambda x: (x.season_number, x.nas_id)):
|
||||
nas = session.query(NasSource).get(season.nas_id)
|
||||
seasons_data.append({
|
||||
"season_number": season.season_number,
|
||||
"episodes_count": len(season.episodes),
|
||||
"nas_name": nas.name if nas else None,
|
||||
"nas_id": season.nas_id,
|
||||
})
|
||||
results["series"].append({
|
||||
"id": s.id,
|
||||
"title": s.title,
|
||||
"year": s.year,
|
||||
"seasons_count": len(s.seasons),
|
||||
"seasons": seasons_data,
|
||||
"poster_url": s.poster_url,
|
||||
"imdb_id": s.imdb_id,
|
||||
})
|
||||
|
||||
total = len(results["movies"]) + len(results["series"])
|
||||
return {"query": q, "results": results, "total": total}
|
||||
finally:
|
||||
session.close()
|
||||
Reference in New Issue
Block a user