Files
proxy_tmdb/README.md
2026-04-23 08:33:37 +02:00

6.9 KiB

proxytmdb

Proxy/cache local de l'API TMDB enrichi des notes IMDb, avec recherche intelligente par titre/année/épisode. 100% Node.js (Fastify + worker_threads).

Conçu pour servir une base TMDB complète (~1.7M entrées movies + tv) sans appel API à chaque requête : tout est mis en cache sur disque par un cron quotidien et indexé en mémoire.

Stack

  • Node.js 20+ (ESM, fetch natif, worker_threads, Intl.NumberFormat)
  • Fastify 5 + @fastify/secure-session + @fastify/static
  • Aucune base de données — système de fichiers <type>/<floor(id/1000)>/<id>.json

Installation

npm install
cp .env.example .env
# Edite .env : TMDB_API_KEY, PROXYTMDB_PASSWORD, SESSION_SECRET

Démarrage du serveur

npm start

Le .env est chargé automatiquement (Node 20+ --env-file-if-exists).

Variables d'environnement

Obligatoires (sans valeur par défaut) :

Variable Rôle
TMDB_API_KEY Clé API TMDB
PROXYTMDB_PASSWORD Mot de passe page d'index
SESSION_SECRET Clé de signature des sessions (32 chars)

Optionnelles (voir .env.example pour la liste complète) : PORT, HOST, PAGE_TITLE, URLs externes, paramètres de recherche (TITLE_TOLERANCE, LEV_*, YEAR_TOLERANCE...).

Cron (mise à jour des données)

Pipeline complet :

npm run cron

Étapes individuelles disponibles :

npm run cron:imdb        # title.ratings.tsv depuis datasets.imdbws.com
npm run cron:tmdb        # sync incrementale TMDB (movie + tv)
npm run cron:justwatch   # watch providers
npm run cron:tmdb2imdb   # mappings bidirectionnels TMDb <-> IMDb
npm run cron:search      # construction des chunks de recherche
npm run cron:ambiguity   # detection des doublons (CSV)

Crontab système recommandée :

13 13 * * * /home/matt/_WEB/proxytmdb/cron/run.sh > /home/matt/_WEB/proxytmdb/lastcron.txt 2>&1

Le wrapper cron/run.sh charge nvm puis lance Node sur la version nvm alias default — pratique si tu mets à jour Node via nvm, le cron suivra automatiquement.

Si tu n'utilises pas nvm, remplace par :

13 13 * * * cd /home/matt/_WEB/proxytmdb && /usr/bin/node --env-file-if-exists=.env cron/runAll.js > lastcron.txt 2>&1

Endpoints HTTP

GET /api?t=movie&q=<tmdb_id>

Retourne le JSON TMDB complet (cache local) avec les notes IMDb fusionnées (note_imdb, vote_imdb).

GET /api?t=tv&q=<tmdb_id>

Idem pour les séries.

GET /api?t=search&q=<requête>

Recherche par titre/année/épisode. Parse la requête (extraction année, extraction épisode SxxExx/partN/NxN/Exxx), choisit movie ou tv selon la présence d'un épisode, calcule les distances Levenshtein UTF-8 sur les titres FR/EN/VO, retourne les meilleurs résultats triés par score puis popularité puis écart d'année.

Réponse JSON :

{
  "results": [
    {
      "title": "Inception",
      "english_title": "Inception",
      "original_title": "Inception",
      "years": 2010,
      "poster": "https://image.tmdb.org/t/p/w200/...",
      "genres": "Action Science-Fiction Aventure ",
      "countries": "GB US ",
      "runtime": "2 h 28 min",
      "imdb_id": "tt1375666",
      "imdb_url": "https://www.imdb.com/title/tt1375666",
      "note_imdb": 8.8,
      "vote_imdb": 2809792,
      "tmdb_id": 27205,
      "tmdb_url": "https://www.themoviedb.org/movie/27205",
      "note_tmdb": 8.4,
      "vote_tmdb": 39044,
      "budget": "$160,000,000",
      "revenue": "$839,030,630",
      "tagline": "...",
      "overview": "..."
    }
  ]
}

L'ancien chemin /api.php est aussi exposé pour compatibilité.

GET /search?query=<requête>

Mêmes données que /api?t=search mais rendues en HTML (vignettes posters, panneau d'info, liens IMDb/TMDb cliquables). Compat : /search.php.

GET /

Page d'index protégée par mot de passe + listing des fichiers du projet.

Architecture

proxytmdb/
├── server.js                     # Bootstrap Fastify
├── config.js                     # Constantes, ports, chemins
├── lib/
│   ├── paths.js                  # Layout disque <type>/<floor(id/1000)>/<id>.json
│   ├── mbLevenshtein.js          # Levenshtein UTF-8 par codepoint
│   ├── titleFilter.js            # Translit ligatures + filtre Latin/chiffres
│   ├── queryParser.js            # Extraction annee/episode/titre
│   ├── imdbRatings.js            # Index IMDb en memoire (Map)
│   ├── http.js                   # fetch + retry + concurrence limitee
│   ├── format.js                 # Devises et runtime
│   ├── searchEngine.js           # Pool de 8 workers persistants
│   └── searchWorker.js           # Worker thread (1 chunk de la base)
├── routes/
│   ├── index.js                  # Login + listing protege
│   ├── api.js                    # Endpoints JSON
│   └── search.js                 # Vue HTML
├── cron/
│   ├── runAll.js                 # Pipeline complet
│   ├── imdbRatings.js            # title.ratings.tsv.gz
│   ├── tmdbExports.js            # Exports quotidiens TMDB
│   ├── tmdbSync.js               # Sync incrementale via /changes
│   ├── justwatchSync.js          # /watch/providers
│   ├── tmdb2imdb.js              # Mappings bidirectionnels
│   ├── buildSearch.js            # Chunks de recherche
│   └── ambiguity.js              # Detection des doublons
├── test/
│   └── helpers.test.js           # Tests unitaires
├── tmdbintegral/                 # Donnees (gitignore)
└── imdbratings.tsv               # Donnees IMDb (gitignore)

Tests

npm test

13 tests unitaires sur les helpers (Levenshtein UTF-8, filtre titres, parser de requête, paths). La parité contre le PHP original a été validée pendant le portage sur 5 requêtes différentes (films, séries, ASCII, UTF-8, avec et sans année) — top-N IDs identiques dans le même ordre.

Notes

  • Les fichiers searchmovie<i>.json et searchtv<i>.json sont des chunks de recherche générés par le cron. Le serveur en charge un par worker au démarrage (mémoire résidente, ~120 Mo total).
  • L'index IMDb (imdbratings.tsv, ~30 Mo, 1.66M lignes) est chargé une fois en Map au démarrage et rechargé automatiquement quand son mtime change (typiquement après le cron quotidien).
  • Le layout disque <type>/<floor(id/1000)>/<id>.json est conservé à l'identique de l'ancienne version PHP : on peut basculer sans regénérer les ~1700 dossiers x 1000 fichiers existants.
  • Les 8 workers de recherche sont persistants (vs PHP qui forkait 8 processus à chaque requête). Gain mesuré : ~30% sur le temps de réponse.