Matching IMDB — Détails techniques¶
Schéma des tables DuckDB, algorithme de matching en 4 passes et pipelines d'enrichissement.
Base DuckDB¶
DuckDB est une base de données analytique locale construite automatiquement par l'application. Elle agrège les datasets publics d'IMDB et un mapping IMDB→TMDB pour le matching des titres.
Datasets importés¶
| Dataset | Source | Table | Filtre |
|---|---|---|---|
| Title Basics | datasets.imdbws.com |
imdb_basics |
Seulement movie, tvSeries, tvMiniSeries, tvMovie ; exclut isAdult=1 |
| Title AKAs | datasets.imdbws.com |
imdb_akas |
Seulement régions FR, BE, CH, CA, LU ou langue fr |
| Title Ratings | datasets.imdbws.com |
imdb_ratings |
Aucun filtre |
| IMDB→TMDB Mapping | GitHub Gist | imdb_tmdb |
IDs sans préfixe tt corrigés |
Schéma des tables¶
-- Titres IMDB (basics)
CREATE TABLE imdb_basics (
tconst VARCHAR PRIMARY KEY, -- ex: tt0111161
title_type VARCHAR NOT NULL, -- movie, tvSeries, tvMiniSeries, tvMovie
primary_title VARCHAR NOT NULL, -- titre original
original_title VARCHAR NOT NULL, -- titre original
start_year INTEGER, -- année de début
norm_primary VARCHAR NOT NULL, -- titre normalisé (pour le matching)
norm_original VARCHAR NOT NULL -- titre original normalisé
);
-- Titres alternatifs (AKAs)
CREATE TABLE imdb_akas (
title_id VARCHAR NOT NULL, -- référence vers imdb_basics.tconst
ordering INTEGER,
title VARCHAR NOT NULL, -- titre alternatif
region VARCHAR, -- FR, BE, CH, CA, LU
language VARCHAR,
norm_title VARCHAR NOT NULL, -- titre normalisé
is_original_title BOOLEAN
);
-- Notes
CREATE TABLE imdb_ratings (
tconst VARCHAR PRIMARY KEY,
average_rating FLOAT,
num_votes INTEGER
);
-- Correspondance IMDB → TMDB
CREATE TABLE imdb_tmdb (
imdb_id VARCHAR PRIMARY KEY, -- ex: tt0111161
tmdb_id VARCHAR -- ex: 278
);
Index¶
| Index | Table | Colonnes |
|---|---|---|
idx_basics_norm_primary |
imdb_basics |
norm_primary, start_year |
idx_basics_norm_original |
imdb_basics |
norm_original, start_year |
idx_basics_prefix |
imdb_basics |
LEFT(norm_primary, 3), norm_primary |
idx_akas_norm |
imdb_akas |
norm_title, title_id |
Normalisation des titres¶
Tous les titres (côté IMDB et côté torrents) sont normalisés avec le même algorithme avant comparaison :
- Suppression du possessif
's("world's" → "world") - Apostrophes → espace ("l'homme" → "l homme")
- Ponctuation
[,!?;:]→ espace - Suppression des accents (NFD + strip combining)
- Minuscules + collapse des espaces
Matching IMDB multi-niveaux¶
Le matching IMDB est le cœur du système d'identification. Il s'effectue en 4 passes successives, chaque passe ne traitant que les torrents non matchés par les passes précédentes.
graph TD
A["Titre torrent normalisé"] --> P1["Passe 1: Exact basics"]
P1 -->|"Match"| OK["IMDB ID + TMDB ID"]
P1 -->|"Pas de match"| P2["Passe 2: Exact AKAs"]
P2 -->|"Match"| OK
P2 -->|"Pas de match"| P3["Passe 3: Flou basics<br/>Jaro-Winkler ≥ 0.85"]
P3 -->|"Match"| OK
P3 -->|"Pas de match"| P4["Passe 4: Flou AKAs<br/>Jaro-Winkler ≥ 0.85"]
P4 -->|"Match"| OK
P4 -->|"Pas de match"| R["Non matché — réessai dans 7j"]
OK --> TMDB["Enrichissement TMDB<br/>via imdb_tmdb"]
style P1 fill:#1b5e20,color:#fff
style P2 fill:#2e7d32,color:#fff
style P3 fill:#33691e,color:#fff
style P4 fill:#827717,color:#fff
style OK fill:#311b92,color:#fff
style TMDB fill:#4a148c,color:#fff
Détail des 4 passes¶
| Passe | Stratégie | Join | Filtres | Disambiguïsation |
|---|---|---|---|---|
| 1 | Exact sur imdb_basics |
norm_primary = norm_title OU norm_original = norm_title |
Année ±1 (ou NULL) ; type contenu | ROW_NUMBER() par hash, tri par num_votes DESC |
| 2 | Exact sur imdb_akas |
imdb_akas.norm_title = norm_title → JOIN imdb_basics |
Année ±1 ; type contenu | ROW_NUMBER() par hash, tri par num_votes DESC |
| 3 | Flou sur imdb_basics |
Pré-filtre LEFT(norm, 3) puis jaro_winkler_similarity ≥ 0.85 |
Année ±1 ; type contenu | ROW_NUMBER() par hash, tri par votes DESC, score DESC |
| 4 | Flou sur imdb_akas |
Pré-filtre LEFT(norm, 3) puis jaro_winkler_similarity ≥ 0.85 |
Année ±1 ; type contenu | ROW_NUMBER() par hash, tri par votes DESC, score DESC |
Enrichissement TMDB¶
Après le matching IMDB, un lookup est effectué dans la table imdb_tmdb pour associer le tmdb_id correspondant, permettant le lien avec l'API TMDB.
Quatre pipelines de matching¶
Le matching IMDB/TMDB est déclenché par quatre pipelines indépendants :
Pipeline A : Enrichissement Meilisearch (imdb_enrich)¶
Match les documents Meilisearch où imdb_matched = false contre DuckDB, puis écrit le imdb_id et tmdb_id résultants directement dans Meilisearch.
Tâche cron : imdb_enrich — fréquence configurable.
Pipeline B : Orphelins PostgreSQL (imdb_orphan_matching)¶
Match les torrent_items PostgreSQL où imdb_id IS NULL contre DuckDB. Possède une intelligence supplémentaire :
- Préfère le
parsed_data.titleRTN quand disponible - Extrait le titre des noms de fichiers bruts via un regex de boundary
- Met
year = Nonepour les séries (l'année encodée ≠ l'année de début IMDB) - Filtre les ebooks (mots-clés + taille < 300 MB)
- Réessaie les non-matchés après 7 jours
- Pre-lookup inverse : si
tmdb_idexiste déjà → résolution directeimdb_idviaimdb_tmdbDuckDB
Tâche cron : imdb_orphan_matching — toutes les 30 minutes.
Pipeline C : Enrichissement DuckDB via TMDB (imdb_tmdb_enrich_duck) ¶
Résout les mappings IMDB→TMDB manquants dans la table imdb_tmdb de DuckDB :
- Identifie les
imdb_idprésents dans DuckDB mais sanstmdb_idassocié - Appelle l'API TMDB (
fetch_external_ids) pour obtenir letmdb_idcorrespondant - Écrit le mapping dans
imdb_tmdb
Ce pipeline enrichit DuckDB pour que les futurs matchings (A, B, D) puissent résoudre les tmdb_id sans appel API.
Tâche cron : imdb_tmdb_enrich_duck — fréquence configurable.
Pipeline D : Backfill Meilisearch local (tmdb_enrich_meili) ¶
Injecte les tmdb_id dans Meilisearch en utilisant uniquement la table imdb_tmdb de DuckDB :
- Parcourt les documents Meilisearch qui ont un
imdb_idmais pas detmdb_id - Lookup dans
imdb_tmdb(DuckDB) pour trouver letmdb_idcorrespondant - Écrit le
tmdb_iddirectement dans Meilisearch
Aucun appel à l'API TMDB — tout est résolu localement via DuckDB. Complémentaire au pipeline A qui fait le matching inverse (titre → imdb_id).
Tâche cron : tmdb_enrich_meili — fréquence configurable.
Cache de disponibilité DuckDB¶
Pour éviter des vérifications coûteuses à chaque requête, l'état de disponibilité de DuckDB est mis en cache.
Vérification au démarrage¶
Lors du lifespan startup, ImdbDAO vérifie que les tables DuckDB (imdb_basics, imdb_akas, imdb_ratings, imdb_tmdb) existent et contiennent des données :
# ImdbDAO._cached_ready — booléen statique au niveau de la classe
ImdbDAO._cached_ready = True # si toutes les tables sont prêtes
Utilisation au runtime¶
Avant chaque matching, les services vérifient le cache :
dao = ImdbDAO(con)
if not dao.is_ready(): # lit _cached_ready
logger.warning("DuckDB IMDB tables not ready — skipping")
return result
Override admin¶
Le statut peut être forcé manuellement depuis l'admin :
GET /admin/meta/duckdb-status— litImdbDAO._cached_readyPOST /admin/meta/duckdb-status— forceready = trueouready = false
Utile pour les situations où DuckDB est partiellement reconstruit ou en maintenance.
Dashboard META¶
Le dashboard /admin/meta affiche l'état DuckDB en temps réel avec :
- Statut
ready(booléen) - Stats des tables (nombre de lignes)
- Stats d'enrichissement TMDB (taux de couverture
imdb_tmdb) - Dernier build (dates de début/fin, statut)
- Liste des dumps disponibles
Sauvegarde et restauration¶
La sauvegarde de DuckDB est critique pour la préservation des données de matching. Consultez la page dédiée :
-
Procédure complète de backup :
CHECKPOINT+ copie de fichier directe. PourquoiEXPORT DATABASEest à éviter. Restauration bit-perfect.