[Torrent911] Inception.2010.TRUEFRENCH.1080p.BluRay.x264-YIFY
Voilà un vrai nom de fichier. L'objectif : en extraire "Inception" et "2010", interroger l'API TMDB (The Movie Database, la référence libre pour les métadonnées de films et séries), et afficher la bonne fiche. Affiche, note, synopsis. Automatiquement, pour une bibliothèque de quelques centaines de fichiers.
Le contexte : un media-server PHP auto-hébergé. Pense Plex, mais allégé, sans cloud, sans
compte. L'interface ressemble à Netflix. La difficulté, c'est la reconnaissance. Les noms de
releases n'obéissent à aucune norme formelle : [Torrent911] peut précéder
le titre, TRUEFRENCH peut l'interrompre, un groupe de release traîne à la fin.
Les films ont une année. Les séries, presque jamais. Et les animés populaires ont des numéros
d'épisodes à quatre chiffres : One.Piece.S01E1164.VOSTFR.
J'avais accumulé 39 188 vrais noms de torrents sur des années : 22 058 films, 17 130 séries. Assez pour construire un banc de test honnête. J'ai utilisé un assistant IA pour rejouer les variantes rapidement et analyser les fuites dans les métriques.
Deux choses se sont passées. D'abord, une optimisation data-driven propre : mesurer, itérer, s'arrêter au bon moment. Ensuite, un piège classique : le benchmark affichait du vert pendant qu'un bug silencieux transformait toutes mes séries en films.
Ce sont deux leçons différentes sur ce que les données mesurent, et ce qu'elles ne mesurent pas.
Un champ de mines dans une chaîne de caractères
Le pipeline a deux étapes distinctes.
La première : extraire un titre propre et une année depuis le nom de fichier. C'est la
fondation. Si elle renvoie "Inception 1080p BluRay x264" au lieu de "Inception", TMDB ne
trouvera rien d'utile. La règle de nommage des releases place le titre en tête et les
métadonnées techniques après, mais les variantes sont nombreuses : préfixes de site
([Torrent911]), tags de langue (VOSTFR, TRUEFRENCH),
codec (x264, H.265), résolution (1080p,
4K), groupes de release.
La deuxième : interroger TMDB avec ce titre et choisir le bon résultat parmi les candidats. Une recherche "Breaking Bad" renvoie des dizaines de résultats, dont des films et des documentaires homonymes. Il faut scorer et trancher.
Pour mesurer l'efficacité de l'extraction, trois métriques : la propreté
(% de titres sans tag technique résiduel : un 1080p, un WEB-DL
ou un S01E02 qui aurait dû être coupé), la couverture (% de
titres non vides), et le rappel d'année (taux d'années correctement
extraites). La propreté est la métrique principale : un titre "sale" génère des
faux positifs ou des zéro-résultats sur TMDB. La couverture était déjà proche de 100 %
dès la V0.
V0 → V4 : mesurer avant d'optimiser
Le banc de test est entièrement offline. Il rejoue l'algorithme d'extraction sur les 39 188 noms du corpus, calcule les métriques, et renvoie un rapport en quelques secondes. Déterministe, 100 % rejouable, sans appel réseau. C'est là que l'assistant IA aide concrètement : construire ce banc, écrire les variantes, analyser les 65 fuites restantes pour identifier les patterns. Pas "l'IA a optimisé l'algo", mais "l'IA a permis d'itérer cinq fois en une heure".
| Version | Approche | Propreté | Fuites |
|---|---|---|---|
| V0 — naïf | Séparateurs seulement (. et _ → espace) |
10,84 % | 34 939 |
| V1 — basique | Couper 8 tags connus (1080p, BluRay…) |
50,23 % | 19 492 |
| V2 — couper au 1er tag | Tronquer dès le premier marqueur technique rencontré | 99,83 % | 65 |
| V3 — fix épisodes | Pattern épisode étendu à 3-4 chiffres (\d{0,4}) |
99,93 % | 28 |
| V4 — expérimental | Normalisation articles, bracket-rescue… | 99,93 % | 29 |
La V0 applique une normalisation basique des séparateurs. 10,84 % de propreté, 34 939 fuites. Baseline catastrophique, mais c'est le point de départ honnête.
La V1 coupe 8 tags techniques connus. 50 %. Le corpus en contient bien plus de huit. Cette approche ne passe pas à l'échelle : chaque site, chaque groupe de release a ses propres conventions.
La V2 introduit l'idée centrale : ne pas lister tous les tags connus, mais tronquer dès le premier tag technique rencontré. La logique de nommage des releases place le titre en tête, les métadonnées techniques après. Couper au premier tag fait bondir la propreté de 10,84 % à 99,83 %. 65 fuites restantes sur 39 000 noms. Ce seul changement de paradigme représente 99 % du résultat total.
// Étape 1 — les préfixes entre crochets partent en premier, sans liste :
// [Torrent911], [HorribleSubs], [FW]... un seul pattern les couvre tous.
$clean = preg_replace('/\[.*?\]/', '', $clean);
$clean = preg_replace('/[._()[\]{}:]+/', ' ', $clean);
// Étape 2 — tout ce qui suit (et inclut) le premier marqueur technique est jeté.
// C'est ce qui fait passer la propreté de 10,8 % à 99,8 %.
$title = preg_replace(
'/\b(multi|vff|truefrench|french|vostfr|bluray|web-?dl|hdtv|x264|x265|hevc'
. '|10bit|remux|2160p|1080p|720p|480p|uhd|hdr|dts|aac|ac3|proper|repack'
. '|extended|directors?-?cut|complete|s\d{1,2}e?\d{0,4}|e\d{2,4})\b.*/i',
'', $clean
);
// s\d{1,2}e?\d{0,4} : le {0,4} (au lieu de {0,2}) capte les épisodes à 3-4 chiffres
// (One Piece S01E1164, Plus Belle La Vie S12E248) — trouvé en analysant 39 000 torrents.
La V3 règle un cas révélé par l'analyse des 65 fuites restantes. Les numéros d'épisodes à
trois ou quatre chiffres n'étaient pas capturés : S01E1164 pour One Piece,
S12E248 pour Plus Belle La Vie. Un seul ajustement dans la regex
(\d{0,2} → \d{0,4}) divise les fuites par deux, de 65 à 28.
Impossible à deviner en regardant dix exemples manuellement. Le corpus les a forcées à
apparaître dans les statistiques.
La V4 teste une normalisation plus agressive. Résultat : 29 fuites, une de plus que V3. Les données disent stop. Ajouter de la complexité pour régresser, c'est non.
Trois leçons de cette partie.
Le gros levier était binaire. Passer de "conserver tout" à "tronquer au premier tag technique" : 10,8 % → 99,8 % de propreté en une seule idée. Le raffinement (V3, V4) vient après, pas avant.
Le big data révèle ce que l'oeil ne voit pas. Sans 39 000 exemples, les numéros d'épisodes à quatre chiffres restaient un angle mort. La masse les a fait apparaître dans les statistiques.
Savoir s'arrêter. La V4 est plus complexe et moins bonne que V3. YAGNI prouvé par les données, pas supposé.
Le matching TMDB et l'optimisation prématurée
Extraire un titre propre, c'est la moitié du travail. L'autre moitié : interroger TMDB et choisir le bon résultat parmi les candidats.
Le scoring combine plusieurs signaux. La similarité de titre compte pour 65 % (comparaison normalisée sur toutes les variantes de titre). L'écart d'année pèse 15 %. La cohérence du type (film ou série) : 10 à 18 % selon la certitude. La popularité TMDB (nombre de votes) : 12 %, pour que les homonymes obscurs perdent face aux oeuvres connues. Au-dessus de 55, c'est un match fort. Entre 35 et 55, mise en quarantaine pour revue manuelle. En dessous, rejeté.
Quand la requête exacte ne donne pas de bon résultat, l'algo la raccourcit mot par mot jusqu'au seuil de confiance. Un titre avec des sous-titres ou une ponctuation inhabituelle peut ne pas matcher directement : on retire les mots par la fin, un par un, et on garde la requête la plus longue qui dépasse le seuil.
// Les releases collent souvent un sous-titre ou une langue après le vrai titre.
// On retire les mots PAR LA FIN, un par un, et on garde la requête la PLUS LONGUE
// qui dépasse le seuil de confiance (55). Le titre est toujours en tête.
$words = explode(' ', trim($title));
$minWords = max(1, count($words) - 4); // au plus 5 essais
$best = null; $bestScore = 0;
for ($n = count($words); $n >= $minWords; $n--) {
$query = implode(' ', array_slice($words, 0, $n));
foreach (tmdb_search($query, $year, $preferTv) as $cand) {
$score = score_candidate($query, $year, $cand, $preferTv);
if ($score > $bestScore) { $bestScore = $score; $best = $cand; }
}
if ($bestScore >= 55) break; // match fiable → on arrête de tronquer
}
Ça règle les cas comme "Avengers: Age of Ultron" (les deux-points disparaissent au nettoyage) ou "Le Voyage de Chihiro" (titre localisé qui ne colle pas au nom de fichier anglais). Chaque itération coûte un appel API, mais elle évite les zéro-match sur des titres avec ponctuation ou caractères spéciaux.
C'est là qu'une "optimisation" s'était glissée dans le code. La variable
$preferTv était censée signaler à TMDB de prioriser les séries. Pour économiser
des appels API, j'avais configuré l'algo pour interroger en priorité le type "probable"
(movie par défaut, sauf si la série était explicitement détectée dans le nom de fichier),
et n'interroger le type tv qu'en l'absence totale de candidats. Le banc de test
réseau affichait des taux flatteurs : 90 % de match pour les films, 96,7 %
pour les séries.
Je l'ai navigué en conditions réelles. Toutes mes séries matchaient un film.
- Breaking Bad → Breaking Bad Wolf (un film indépendant homonyme)
- Game of Thrones → Game of Thrones: The Last Watch (un documentaire HBO)
- The Mandalorian → The Mandalorian and Grogu (un film annoncé)
- One Piece → One Piece: Gold (un long-métrage)
0 série correcte sur 11.
Pourquoi le benchmark affichait du vert
Le banc de test offline ne mesurait que l'extraction. Normal, c'est son périmètre. Il ne savait rien du matching.
Le banc de test réseau, lui, fournissait le type de contenu (movie ou tv) depuis
la catégorie connue de chaque entrée du corpus. Il passait directement $preferTv =
true pour les séries. Il n'exerçait donc jamais la détection automatique de type,
parce qu'il n'en avait pas besoin : la bonne réponse était déjà injectée dans les
paramètres. La détection automatique qui tournait en production n'avait jamais été exercée.
La métrique agrégée était verte sur un chemin que la navigation normale n'empruntait pas.
Un benchmark teste ce qu'on lui demande de tester. Ses angles morts sont silencieux.
Le fix en 4 leviers
1. Toujours interroger les deux types.
Movie et tv sont maintenant interrogés à chaque fois. Le scoring tranche. Breaking Bad (série TMDB, popularité élevée) écrase n'importe quel film homonyme obscur par son seul score de popularité pondéré. C'était la régression centrale : économiser un appel API avait cassé la logique de sélection. Le correctif est littéralement d'enlever la condition qui limitait le deuxième type.
2. La structure du dossier comme signal.
Parser le nom de fichier pour deviner film ou série, c'est fragile. Un dossier structuré
en saisons — "Show/Season 1/", "Show/01/", "Show/02/" — ne contient aucun
S01E01 dans son nom propre. La structure du dossier, elle, ne ment pas.
// Un dossier est une SÉRIE s'il contient plusieurs épisodes (≥ 2 vidéos à plat)
// OU des sous-dossiers de saison / numérotés (Season 1, Saison 3, 01, 02…).
// Un film n'a qu'une seule vidéo. La FORME du dossier bat le parsing du nom de fichier.
$videoCount = 0; $seasonish = 0;
foreach (scandir($folder) as $sub) {
if ($sub[0] === '.') continue;
if (is_dir("$folder/$sub")) {
if (preg_match('/^(s\d{1,2}\b|saison|season|saga|arc|part|\d{1,3}$)/i', $sub))
$seasonish++;
} elseif (in_array(strtolower(pathinfo($sub, PATHINFO_EXTENSION)), $VIDEO_EXTS, true)) {
$videoCount++;
}
}
$isSeries = ($videoCount >= 2 || $seasonish >= 1);
Ce signal est utilisé pour pondérer le bonus de cohérence de type dans le scoring. Il ne remplace pas la double interrogation, il l'affine.
3. Fusion bilingue des titres.
TMDB renvoie plusieurs variantes de titre par résultat : titre original, titre local, titre alternatif. Quand on cherche "Attack on Titan", TMDB peut renvoyer "L'Attaque des Titans" (français) et "Shingeki no Kyojin" (japonais). La déduplication par ID ne gardait que le premier titre rencontré : faible similarité avec la requête anglaise, match potentiellement raté.
Fix : agréger toutes les variantes de titre par ID et scorer contre la meilleure. Bonus inattendu : Spirited Away est passé de match faible à match fort, car "Sen to Chihiro no Kamikakushi" (titre japonais original) était dans les données TMDB et contribuait au score de similarité.
4. Bonus de cohérence de type renforcé.
Quand la structure du dossier confirme clairement une série (sous-dossiers numérotés, dizaines de fichiers vidéo), le bonus de type tv dans le scoring passe de 10 à 18 %. Le signal structurel amplifie la décision du scoring sans la court-circuiter.
Résultat : 11 séries et animés corrects en haute confiance, 18 films restent des films. 0 → 11 sur les séries.
Code source complet
ShareBox est open source. L'algorithme décrit dans cet article se trouve dans functions.php :extract_title_year()(extraction),tmdb_match()(boucle word-removal),tmdb_score_candidate()(scoring).
Conclusion
Le big data a brillamment optimisé l'extraction. De 10,8 % à 99,9 % de propreté en
trois itérations mesurées, sur 39 000 noms. Les données ont révélé un angle mort
(S01E1164) qu'aucun humain ne teste manuellement, et elles ont dit stop quand
une variante plus complexe régressait d'une unité.
Le matching a raté différemment. Une économie d'appels API a causé une régression invisible aux métriques agrégées parce que le benchmark s'alimentait lui-même de la bonne réponse. La navigation en conditions réelles a trouvé le bug en trente secondes.
La vraie leçon : les données optimisent brillamment ce qu'elles mesurent. Un benchmark mesure un chemin. Il ne sait pas qu'il en existe d'autres. La métrique agrégée était un indicateur honnête ; elle n'était pas un indicateur complet. Tester en conditions réelles reste irremplaçable, même quand tout est vert.