From 0866a875baaf6939665308b0ece7bd874ceba839 Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 5 May 2026 18:02:18 +0200 Subject: [PATCH] feat: TMDB year fallback + PUID/PGID support - tmdb.py: store release_date year in cache, expose as tmdb_year - main.py + app.js: use tmdb_year when subtitle has no year - Dockerfile: add gosu + abc user for PUID/PGID runtime privilege drop - entrypoint.sh: new entrypoint handling PUID/PGID ownership of /app/data Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile | 8 +++++++- README.md | 2 ++ entrypoint.sh | 15 +++++++++++++++ main.py | 2 +- static/app.js | 2 +- tmdb.py | 30 +++++++++++++++++++----------- 6 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index bd1c99a..874760b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,19 @@ FROM python:3.12-slim WORKDIR /app -RUN apt-get update -qq && apt-get install -y -qq ffmpeg && rm -rf /var/lib/apt/lists/* +RUN apt-get update -qq \ + && apt-get install -y -qq ffmpeg gosu \ + && rm -rf /var/lib/apt/lists/* \ + && groupadd -r abc \ + && useradd -r -g abc abc COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . +RUN chmod +x /app/entrypoint.sh EXPOSE 8080 +ENTRYPOINT ["/app/entrypoint.sh"] CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/README.md b/README.md index f1f9121..474d060 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,8 @@ TMDB_API_KEY=xxx docker compose up -d | `TMDB_API_KEY` | — | Clé API TMDB (obligatoire pour posters/backdrops) | | `TZ` | `UTC` | Fuseau horaire | | `AUTO_DL_INTERVAL` | `3600` | Intervalle (secondes) entre deux checks auto-DL | +| `PUID` | `0` | UID Unix du propriétaire des fichiers (Unraid : `99`) | +| `PGID` | `0` | GID Unix du propriétaire des fichiers (Unraid : `100`) | --- diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..9460929 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -e + +PUID=${PUID:-0} +PGID=${PGID:-0} + +if [ "$PUID" != "0" ] || [ "$PGID" != "0" ]; then + groupmod -o -g "$PGID" abc 2>/dev/null || true + usermod -o -u "$PUID" abc 2>/dev/null || true + mkdir -p /app/data + chown -R "$PUID:$PGID" /app/data 2>/dev/null || true + exec gosu abc "$@" +else + exec "$@" +fi diff --git a/main.py b/main.py index fd7033f..286d78a 100644 --- a/main.py +++ b/main.py @@ -32,7 +32,7 @@ async def _run_auto_dl_check() -> int: for c in concerts: if not dm.already_enqueued(c["url"]): m = re.search(r"\b(20\d{2})\b", c.get("subtitle", "")) - year = int(m.group(1)) if m else None + year = int(m.group(1)) if m else c.get("tmdb_year") await dm.enqueue(c["url"], c["title"], c.get("subtitle", ""), year, cat) total += 1 return total diff --git a/static/app.js b/static/app.js index c90f00f..0599139 100644 --- a/static/app.js +++ b/static/app.js @@ -307,7 +307,7 @@ $('btn-download').addEventListener('click', async () => { try { const yearMatch = (c.subtitle || '').match(/\b(20\d{2})\b/); - const year = yearMatch ? parseInt(yearMatch[1]) : null; + const year = yearMatch ? parseInt(yearMatch[1]) : (c.tmdb_year || null); const res = await fetch('/api/download', { method: 'POST', headers: { 'Content-Type': 'application/json' }, diff --git a/tmdb.py b/tmdb.py index 2ba44ab..d15aefc 100644 --- a/tmdb.py +++ b/tmdb.py @@ -33,9 +33,14 @@ def _init_db(): tmdb_id INTEGER, poster TEXT, backdrop TEXT, + year INTEGER, cached_at TEXT NOT NULL ) """) + try: + conn.execute("ALTER TABLE tmdb_cache ADD COLUMN year INTEGER") + except Exception: + pass def _search(query: str) -> list[dict]: @@ -72,39 +77,42 @@ def lookup(arte_id: str, title: str, subtitle: str) -> dict | None: cutoff = (datetime.now() - timedelta(days=_CACHE_DAYS)).isoformat() with sqlite3.connect(_DB) as conn: row = conn.execute( - "SELECT tmdb_id, poster, backdrop FROM tmdb_cache WHERE arte_id=? AND cached_at>?", + "SELECT tmdb_id, poster, backdrop, year FROM tmdb_cache WHERE arte_id=? AND cached_at>?", (arte_id, cutoff), ).fetchone() if row is not None: - tmdb_id, poster, backdrop = row + tmdb_id, poster, backdrop, year = row if tmdb_id is None: return None # cached "no match" - return _build(tmdb_id, poster, backdrop) + return _build(tmdb_id, poster, backdrop, year) # Query TMDB query = f"{title} {subtitle}".strip() results = _search(query) or (_search(title) if subtitle else []) match = _best_match(results, title, subtitle) - tmdb_id = match["id"] if match else None - poster = match.get("poster_path") if match else None - backdrop = match.get("backdrop_path") if match else None + tmdb_id = match["id"] if match else None + poster = match.get("poster_path") if match else None + backdrop = match.get("backdrop_path") if match else None + rd = (match.get("release_date") or "")[:4] if match else "" + year = int(rd) if rd.isdigit() else None with sqlite3.connect(_DB) as conn: conn.execute( - "INSERT OR REPLACE INTO tmdb_cache VALUES (?,?,?,?,?)", - (arte_id, tmdb_id, poster, backdrop, datetime.now().isoformat()), + "INSERT OR REPLACE INTO tmdb_cache VALUES (?,?,?,?,?,?)", + (arte_id, tmdb_id, poster, backdrop, year, datetime.now().isoformat()), ) - return _build(tmdb_id, poster, backdrop) if tmdb_id else None + return _build(tmdb_id, poster, backdrop, year) if tmdb_id else None -def _build(tmdb_id: int, poster: str | None, backdrop: str | None) -> dict: +def _build(tmdb_id: int, poster: str | None, backdrop: str | None, year: int | None = None) -> dict: return { - "tmdb_id": tmdb_id, + "tmdb_id": tmdb_id, "tmdb_poster": f"{_IMG_BASE}/w500{poster}" if poster else None, "tmdb_backdrop": f"{_IMG_BASE}/w1280{backdrop}" if backdrop else None, + "tmdb_year": year, }