feat: TMDB year fallback + PUID/PGID support
Docker / docker (push) Successful in 2m58s

- 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 <noreply@anthropic.com>
This commit is contained in:
dev
2026-05-05 18:02:18 +02:00
parent 9cc8bb771d
commit 0866a875ba
6 changed files with 45 additions and 14 deletions
+7 -1
View File
@@ -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"]
+2
View File
@@ -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`) |
---
+15
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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' },
+15 -7
View File
@@ -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,15 +77,15 @@ 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()
@@ -90,21 +95,24 @@ def lookup(arte_id: str, title: str, subtitle: str) -> dict | 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_poster": f"{_IMG_BASE}/w500{poster}" if poster else None,
"tmdb_backdrop": f"{_IMG_BASE}/w1280{backdrop}" if backdrop else None,
"tmdb_year": year,
}