- 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:
+7
-1
@@ -2,13 +2,19 @@ FROM python:3.12-slim
|
|||||||
|
|
||||||
WORKDIR /app
|
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 .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ TMDB_API_KEY=xxx docker compose up -d
|
|||||||
| `TMDB_API_KEY` | — | Clé API TMDB (obligatoire pour posters/backdrops) |
|
| `TMDB_API_KEY` | — | Clé API TMDB (obligatoire pour posters/backdrops) |
|
||||||
| `TZ` | `UTC` | Fuseau horaire |
|
| `TZ` | `UTC` | Fuseau horaire |
|
||||||
| `AUTO_DL_INTERVAL` | `3600` | Intervalle (secondes) entre deux checks auto-DL |
|
| `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`) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -32,7 +32,7 @@ async def _run_auto_dl_check() -> int:
|
|||||||
for c in concerts:
|
for c in concerts:
|
||||||
if not dm.already_enqueued(c["url"]):
|
if not dm.already_enqueued(c["url"]):
|
||||||
m = re.search(r"\b(20\d{2})\b", c.get("subtitle", ""))
|
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)
|
await dm.enqueue(c["url"], c["title"], c.get("subtitle", ""), year, cat)
|
||||||
total += 1
|
total += 1
|
||||||
return total
|
return total
|
||||||
|
|||||||
+1
-1
@@ -307,7 +307,7 @@ $('btn-download').addEventListener('click', async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const yearMatch = (c.subtitle || '').match(/\b(20\d{2})\b/);
|
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', {
|
const res = await fetch('/api/download', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|||||||
@@ -33,9 +33,14 @@ def _init_db():
|
|||||||
tmdb_id INTEGER,
|
tmdb_id INTEGER,
|
||||||
poster TEXT,
|
poster TEXT,
|
||||||
backdrop TEXT,
|
backdrop TEXT,
|
||||||
|
year INTEGER,
|
||||||
cached_at TEXT NOT NULL
|
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]:
|
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()
|
cutoff = (datetime.now() - timedelta(days=_CACHE_DAYS)).isoformat()
|
||||||
with sqlite3.connect(_DB) as conn:
|
with sqlite3.connect(_DB) as conn:
|
||||||
row = conn.execute(
|
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),
|
(arte_id, cutoff),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
if row is not None:
|
if row is not None:
|
||||||
tmdb_id, poster, backdrop = row
|
tmdb_id, poster, backdrop, year = row
|
||||||
if tmdb_id is None:
|
if tmdb_id is None:
|
||||||
return None # cached "no match"
|
return None # cached "no match"
|
||||||
return _build(tmdb_id, poster, backdrop)
|
return _build(tmdb_id, poster, backdrop, year)
|
||||||
|
|
||||||
# Query TMDB
|
# Query TMDB
|
||||||
query = f"{title} {subtitle}".strip()
|
query = f"{title} {subtitle}".strip()
|
||||||
results = _search(query) or (_search(title) if subtitle else [])
|
results = _search(query) or (_search(title) if subtitle else [])
|
||||||
match = _best_match(results, title, subtitle)
|
match = _best_match(results, title, subtitle)
|
||||||
|
|
||||||
tmdb_id = match["id"] if match else None
|
tmdb_id = match["id"] if match else None
|
||||||
poster = match.get("poster_path") if match else None
|
poster = match.get("poster_path") if match else None
|
||||||
backdrop = match.get("backdrop_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:
|
with sqlite3.connect(_DB) as conn:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT OR REPLACE INTO tmdb_cache VALUES (?,?,?,?,?)",
|
"INSERT OR REPLACE INTO tmdb_cache VALUES (?,?,?,?,?,?)",
|
||||||
(arte_id, tmdb_id, poster, backdrop, datetime.now().isoformat()),
|
(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 {
|
return {
|
||||||
"tmdb_id": tmdb_id,
|
"tmdb_id": tmdb_id,
|
||||||
"tmdb_poster": f"{_IMG_BASE}/w500{poster}" if poster else None,
|
"tmdb_poster": f"{_IMG_BASE}/w500{poster}" if poster else None,
|
||||||
"tmdb_backdrop": f"{_IMG_BASE}/w1280{backdrop}" if backdrop else None,
|
"tmdb_backdrop": f"{_IMG_BASE}/w1280{backdrop}" if backdrop else None,
|
||||||
|
"tmdb_year": year,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user