Actualiser api.php

This commit is contained in:
2026-07-01 11:20:34 +02:00
parent 817627dca7
commit f539f0664c
+92 -147
View File
@@ -419,7 +419,6 @@ function fetchFromBlurayCom($ean) {
'publisher' => '', 'format' => 'Blu-ray', 'number_of_discs' => 1, 'publisher' => '', 'format' => 'Blu-ray', 'number_of_discs' => 1,
'aspect_ratio' => '' 'aspect_ratio' => ''
]; ];
$ean = preg_replace('/[^0-9]/', '', (string)$ean); $ean = preg_replace('/[^0-9]/', '', (string)$ean);
if (strlen($ean) < 8) { if (strlen($ean) < 8) {
error_log("Blu-ray.com: ❌ EAN invalide: $ean"); error_log("Blu-ray.com: ❌ EAN invalide: $ean");
@@ -436,8 +435,6 @@ function fetchFromBlurayCom($ean) {
$lastRequest = microtime(true); $lastRequest = microtime(true);
error_log("Blu-ray.com: 🔍 Recherche EAN $ean"); error_log("Blu-ray.com: 🔍 Recherche EAN $ean");
// Recherche par EAN
$searchUrl = "https://www.blu-ray.com/movies/search.php?ean=" . urlencode($ean) . "&action=search"; $searchUrl = "https://www.blu-ray.com/movies/search.php?ean=" . urlencode($ean) . "&action=search";
$ch = curl_init($searchUrl); $ch = curl_init($searchUrl);
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -453,7 +450,6 @@ function fetchFromBlurayCom($ean) {
'Referer: https://www.blu-ray.com/' 'Referer: https://www.blu-ray.com/'
] ]
]); ]);
$searchHtml = curl_exec($ch); $searchHtml = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch); $curlError = curl_error($ch);
@@ -464,20 +460,17 @@ function fetchFromBlurayCom($ean) {
return $empty; return $empty;
} }
// Extraire l'URL du film - regex améliorée // Extraire l'URL du film
if (!preg_match('/href="(https:\/\/www\.blu-ray\.com\/movies\/[^"]+\/(\d+)\/)"/i', $searchHtml, $matches)) { if (!preg_match('/href="(https:\/\/www\.blu-ray\.com\/movies\/[^"]+\/(\d+)\/)"/i', $searchHtml, $matches)) {
error_log("Blu-ray.com: ❌ Film non trouvé pour EAN $ean"); error_log("Blu-ray.com: ❌ Film non trouvé pour EAN $ean");
return $empty; return $empty;
} }
$movieUrl = $matches[1]; $movieUrl = $matches[1];
$movieId = $matches[2]; $movieId = $matches[2];
error_log("Blu-ray.com: ✅ Film trouvé → $movieUrl"); error_log("Blu-ray.com: ✅ Film trouvé → $movieUrl");
// Délai avant la 2ème requête sleep(2); // Délai avant la 2ème requête
sleep(2);
// Récupérer la page du film
$ch2 = curl_init($movieUrl); $ch2 = curl_init($movieUrl);
curl_setopt_array($ch2, [ curl_setopt_array($ch2, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
@@ -491,7 +484,6 @@ function fetchFromBlurayCom($ean) {
'Referer: https://www.blu-ray.com/' 'Referer: https://www.blu-ray.com/'
] ]
]); ]);
$movieHtml = curl_exec($ch2); $movieHtml = curl_exec($ch2);
curl_close($ch2); curl_close($ch2);
@@ -500,26 +492,19 @@ function fetchFromBlurayCom($ean) {
return $empty; return $empty;
} }
// Extraction des données - regex robustes face à la refonte du site // ── EXTRACTION AVEC LES NOUVELLES REGEX ──
// (le site a été redesigné : le <h1> contient désormais un lien <a> imbriqué,
// la div#movie_info / #movie_review_intro qui encadrait les infos a disparu, // Titre (dans <h3> inside #movie_info)
// et le span#runtime a été remplacé par du texte brut dans une cellule de tableau) if (preg_match('/<div[^>]*id="movie_info"[^>]*>.*?<h3>([^<]+)<\/h3>/is', $movieHtml, $m)) {
$empty['title'] = trim($m[1]);
// Titre - le <h1> peut désormais contenir un <a>, donc on autorise les balises } elseif (preg_match('/<h1[^>]*>([^<]+)<\/h1>/i', $movieHtml, $m)) {
// imbriquées avec (.*?) puis on nettoie avec strip_tags
if (preg_match('/<h1[^>]*>(.*?)<\/h1>/is', $movieHtml, $m)) {
$rawTitle = trim(strip_tags($m[1])); $rawTitle = trim(strip_tags($m[1]));
$empty['title'] = trim(preg_replace('/\s*(Blu-ray|4K|3D|DVD|UHD).*$/i', '', $rawTitle)); $empty['title'] = trim(preg_replace('/\s*(Blu-ray|4K|3D|DVD|UHD).*$/i', '', $rawTitle));
} }
if (empty($empty['title']) && preg_match('/<title>([^<]+)<\/title>/i', $movieHtml, $m)) {
// Filet de sécurité : la balise <title> de la page contient toujours le nom du film // Année (juste après le <h3>)
$rawTitle = trim(html_entity_decode(strip_tags($m[1]))); if (preg_match('/<h3[^>]*>([^<]+)<\/h3>\s*(?:&nbsp;)?\((\d{4})\)/i', $movieHtml, $m)) {
$empty['title'] = trim(preg_replace('/\s*(Blu-ray|4K|3D|DVD|UHD).*$/i', '', $rawTitle)); $empty['year'] = $m[2];
}
// Année
if (preg_match('/href="[^"]*year=(\d{4})[^"]*"[^>]*>(\d{4})<\/a>/i', $movieHtml, $m)) {
$empty['year'] = $m[1];
} }
// Studio/Éditeur // Studio/Éditeur
@@ -527,26 +512,21 @@ function fetchFromBlurayCom($ean) {
$empty['publisher'] = trim($m[1]); $empty['publisher'] = trim($m[1]);
} }
// Durée - le span#runtime n'existe plus ; on cherche "NNN min" à proximité // Durée (dans un span avant "Rated")
// du lien "année" déjà trouvé plus haut (ex: "... | 2012 | 123 min | Rated PG | ...") if (preg_match('/(\d+)\s*min<\/span>/i', $movieHtml, $m)) {
if (preg_match('/movies\.php\?year=\d{4}[^>]*>\d{4}<\/a>.{0,80}?(\d{2,3})\s*min\b/is', $movieHtml, $m)) {
$empty['length'] = $m[1] . ' min'; $empty['length'] = $m[1] . ' min';
} elseif (preg_match('/\b(\d{2,3})\s*min\b/i', $movieHtml, $m)) { } elseif (preg_match('/(\d+)\s*min\s*\|\s*Rated/i', $movieHtml, $m)) {
// Filet de sécurité, moins précis
$empty['length'] = $m[1] . ' min'; $empty['length'] = $m[1] . ' min';
} }
// Aspect ratio - "Original aspect ratio: X.XX:1" est plus fiable que le premier // Aspect ratio
// "Aspect ratio:" qui peut lister plusieurs valeurs (plans différents dans le film) if (preg_match('/Aspect[\s-]*ratio:\s*([\d\.]+:[\d\.]+)/i', $movieHtml, $m)) {
if (preg_match('/Original aspect ratio:\s*([\d\.]+:[\d\.]+)/i', $movieHtml, $m)) {
$empty['aspect_ratio'] = trim($m[1]);
} elseif (preg_match('/Aspect[\s-]*ratio:\s*([\d\.]+:[\d\.]+)/i', $movieHtml, $m)) {
$empty['aspect_ratio'] = trim($m[1]); $empty['aspect_ratio'] = trim($m[1]);
} }
// Nombre de disques - plusieurs patterns // Nombre de disques (gère "Three-disc set" en plus des chiffres)
if (preg_match('/(\w+)-disc\s+set/i', $movieHtml, $m)) { if (preg_match('/(\w+)-disc\s+set/i', $movieHtml, $m)) {
$wordToNum = ['single' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5, 'six' => 6]; $wordToNum = ['single' => 1, 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5, 'six' => 6];
$word = strtolower($m[1]); $word = strtolower($m[1]);
$empty['number_of_discs'] = $wordToNum[$word] ?? 1; $empty['number_of_discs'] = $wordToNum[$word] ?? 1;
} elseif (preg_match('/(\d+)-disc\s+set/i', $movieHtml, $m)) { } elseif (preg_match('/(\d+)-disc\s+set/i', $movieHtml, $m)) {
@@ -562,67 +542,43 @@ function fetchFromBlurayCom($ean) {
$empty['format'] = 'Blu-ray'; $empty['format'] = 'Blu-ray';
} }
// Affiche HD - le site charge désormais les jaquettes en JS (lazy-load, // Affiche HD
// le HTML statique ne contient qu'un visuel "Temporary cover art" en placeholder). if (preg_match('/src="(https:\/\/images\.static-bluray\.com\/movies\/covers\/\d+_front\.jpg[^"]*)"/i', $movieHtml, $m)) {
// On essaie d'abord les anciens motifs (au cas où ils réapparaissent sur certaines
// pages), puis on retombe sur l'URL CDN prévisible construite à partir de l'ID du
// film, en la validant par une requête HEAD avant de l'utiliser.
if (preg_match('/(?:src|data-src)="(https:\/\/images\d*\.static-bluray\.com\/movies\/covers\/\d+_(?:front|large)\.jpg[^"]*)"/i', $movieHtml, $m)) {
$empty['poster'] = $m[1]; $empty['poster'] = $m[1];
} elseif (preg_match('/<img[^>]*class="[^"]*coverfront[^"]*"[^>]*(?:src|data-src)="([^"]+)"/i', $movieHtml, $m)) { } elseif (preg_match('/<img[^>]*class="coverfront"[^>]*src="([^"]+)"/i', $movieHtml, $m)) {
$posterUrl = preg_replace('/_large\.jpg/', '_front.jpg', $m[1]); $posterUrl = $m[1];
$posterUrl = preg_replace('/_large\.jpg/', '_front.jpg', $posterUrl);
$empty['poster'] = $posterUrl; $empty['poster'] = $posterUrl;
} else { }
error_log("Blu-ray.com: ⚠️ Jaquette absente du HTML statique (lazy-load JS), tentative CDN prévisible pour ID $movieId");
foreach (['_large.jpg', '_front.jpg'] as $suffix) { // Synopsis, Réalisateur, Acteurs (dans #movie_info)
$candidate = "https://images.static-bluray.com/movies/covers/{$movieId}{$suffix}"; if (preg_match('/<div[^>]*id="movie_info"[^>]*>(.*?)<div[^>]*id="movie_review_intro"/is', $movieHtml, $infoBlock)) {
if (urlExists($candidate)) { $infoHtml = $infoBlock[1];
$empty['poster'] = $candidate;
break; // Synopsis (après la balise <font> vide)
if (preg_match('/<font[^>]*><br><\/font>\s*(.*?)<br><br><br>Directors:/is', $infoHtml, $m)) {
$synopsis = trim(strip_tags($m[1]));
$synopsis = preg_replace('/\s+/', ' ', $synopsis);
$empty['description'] = $synopsis;
}
// Réalisateur (extrait depuis les balises <a>)
if (preg_match('/Directors?:\s*(.*?)(?:<br>|<\/div>)/is', $infoHtml, $m)) {
preg_match_all('/<a[^>]*>([^<]+)<\/a>/i', $m[1], $dirMatches);
if (!empty($dirMatches[1])) {
$empty['director'] = implode(', ', array_map('trim', array_slice($dirMatches[1], 0, 2)));
} }
} }
if (empty($empty['poster'])) {
error_log("Blu-ray.com: ❌ Aucune URL de jaquette CDN valide trouvée pour ID $movieId (fallback TMDB attendu)"); // Acteurs (extrait depuis les balises <a>)
if (preg_match('/Starring:\s*(.*?)(?:<br>|<\/div>)/is', $infoHtml, $m)) {
preg_match_all('/<a[^>]*>([^<]+)<\/a>/i', $m[1], $actorMatches);
if (!empty($actorMatches[1])) {
$empty['actors'] = implode(', ', array_map('trim', array_slice($actorMatches[1], 0, 6)));
}
} }
} }
// Synopsis, Réalisateur, Acteurs
// La div#movie_info / #movie_review_intro qui encadrait ces infos a disparu de la
// refonte du site : "Director:" et "Starring:" apparaissent maintenant directement
// dans le flux HTML de la page, donc on cherche sur $movieHtml en entier plutôt
// que dans un sous-bloc délimité par ces anciens ID.
// Réalisateur
if (preg_match('/Director:\s*<a[^>]*>([^<]+)<\/a>/i', $movieHtml, $m)) {
$empty['director'] = trim(html_entity_decode($m[1]));
} else {
error_log("Blu-ray.com: ⚠️ Réalisateur non trouvé (structure HTML probablement changée)");
}
// Acteurs
if (preg_match('/Starring:\s*(.*?)(?:<br\s*\/?>|<\/(?:p|div|td)>)/is', $movieHtml, $m)) {
preg_match_all('/<a[^>]*>([^<]+)<\/a>/i', $m[1], $actorMatches);
if (!empty($actorMatches[1])) {
$empty['actors'] = implode(', ', array_map(fn($a) => trim(html_entity_decode($a)), array_slice($actorMatches[1], 0, 6)));
}
} else {
error_log("Blu-ray.com: ⚠️ Acteurs non trouvés (structure HTML probablement changée)");
}
// Synopsis - on capture le dernier bloc de texte "libre" (hors balises) qui
// précède immédiatement "Director:", en tolérant quelques balises intermédiaires
if (preg_match('/>([^<]{60,900})<(?:[^>]*>){0,5}\s*Director:/is', $movieHtml, $m)) {
$synopsis = trim(html_entity_decode(strip_tags($m[1])));
$synopsis = preg_replace('/\s+/', ' ', $synopsis);
$empty['description'] = $synopsis;
} else {
error_log("Blu-ray.com: ⚠️ Synopsis non trouvé (structure HTML probablement changée)");
}
if (empty($empty['title'])) {
error_log("Blu-ray.com: ⚠️ Titre non extrait malgré une page trouvée pour ID $movieId — vérifier la structure du <h1>/<title>");
}
error_log("Blu-ray.com: ✅ Données récupérées pour EAN $ean" . $empty['title']); error_log("Blu-ray.com: ✅ Données récupérées pour EAN $ean" . $empty['title']);
return $empty; return $empty;
} }
@@ -634,57 +590,55 @@ function fetchFromMovieCovers($title, $year = '') {
'publisher' => '', 'format' => 'DVD', 'number_of_discs' => 1, 'publisher' => '', 'format' => 'DVD', 'number_of_discs' => 1,
'aspect_ratio' => '' 'aspect_ratio' => ''
]; ];
if (empty($title)) return $empty; if (empty($title)) return $empty;
// Nettoyer le titre pour l'URL : moviecovers.com attend un titre en MAJUSCULES // Nettoyer le titre pour l'URL (les espaces deviennent des +)
// SANS accents ni apostrophes/ponctuation (ex: "L'Étrange Noël" -> "L+ETRANGE+NOEL"). $urlTitle = strtoupper(str_replace(' ', '+', $title));
// C'est la cause principale des échecs silencieux : un titre accentué produit une
// URL qui ne correspond à aucune fiche et retombe sur la page "non trouvé".
$normalized = removeAccentsForUrl($title);
$normalized = str_replace(['œ', 'Œ'], ['OE', 'OE'], $normalized);
$normalized = preg_replace('/[^A-Za-z0-9 ]+/', ' ', $normalized); // apostrophes, ':', '-', etc. -> espace
$normalized = preg_replace('/\s{2,}/', ' ', trim($normalized));
$urlTitle = strtoupper(str_replace(' ', '+', $normalized));
if ($urlTitle === '') {
error_log("MovieCovers: ❌ Titre vide après normalisation pour '$title'");
return $empty;
}
$url = "https://moviecovers.com/film/titre_{$urlTitle}.html"; $url = "https://moviecovers.com/film/titre_{$urlTitle}.html";
error_log("MovieCovers: 🔍 Recherche '$title' → $url");
$html = httpGet($url, 10); $html = httpGet($url, 10);
if (!$html) { if (!$html) return $empty;
error_log("MovieCovers: ❌ Échec HTTP pour $url");
return $empty;
}
// Le site répond en 200 même quand le film n'existe pas ("Je n'ai pas trouvé de film")
if (stripos($html, "n'ai pas trouv") !== false) {
error_log("MovieCovers: ❌ Film non trouvé pour '$title' (URL: $url)");
return $empty;
}
// Extraire le titre // Extraire le titre
if (preg_match('/<TITLE>([^<]+)<\/TITLE>/i', $html, $m)) { if (preg_match('/<meta property="og:title" content="([^"]+)"/i', $html, $m)) {
$empty['title'] = trim($m[1]);
} elseif (preg_match('/<TITLE>([^<]+)<\/TITLE>/i', $html, $m)) {
$empty['title'] = trim($m[1]); $empty['title'] = trim($m[1]);
} elseif (preg_match('/<meta[^>]*property="og:title"[^>]*content="([^"]+)"/i', $html, $m)) {
$empty['title'] = trim(html_entity_decode($m[1]));
} }
// Extraire l'affiche - plusieurs filets de sécurité, l'ordre des attributs // Extraire l'IDMC pour construire l'URL de l'image HD (comme dans votre script Python)
// (src/alt/title) dans le <img> n'étant pas garanti $idmc = '';
if (preg_match('/<img[^>]*src="(https:\/\/moviecovers\.com\/DATA\/thumbs\/[^"]+)"[^>]*title="Recto/i', $html, $m)) { if (preg_match('/IDMC.*?<PRE>([^<]+)<\/PRE>/is', $html, $m)) {
$empty['poster'] = $m[1]; $idmc = trim($m[1]);
} elseif (preg_match('/title="Recto[^"]*"[^>]*src="(https:\/\/moviecovers\.com\/DATA\/thumbs\/[^"]+)"/i', $html, $m)) { } elseif (preg_match('/<meta property="og:image" content="[^"]*\/getjpg\.html\/([^"\']+)\.jpg"/i', $html, $m)) {
// Même image mais avec title AVANT src dans la balise $idmc = urldecode($m[1]);
$empty['poster'] = $m[1]; }
} elseif (preg_match('/<meta[^>]*property="og:image"[^>]*content="([^"]+)"/i', $html, $m)) {
$empty['poster'] = $m[1]; // Essayer de récupérer l'affiche HD via les URLs du script Python
} elseif (preg_match('/<meta[^>]*(?:property|name)="twitter:image"[^>]*content="([^"]+)"/i', $html, $m)) { if ($idmc) {
$empty['poster'] = $m[1]; $idmc_encoded = urlencode($idmc);
$hd_urls = [
"https://moviecovers.com/DATA/zipcache/{$idmc_encoded}.jpg",
"https://moviecovers.com/getjpg.html/{$idmc_encoded}.jpg"
];
foreach ($hd_urls as $hd_url) {
$img_data = httpGet($hd_url, 5);
if ($img_data && strlen($img_data) > 5000) { // Vérifie que c'est une vraie image (>5Ko)
$empty['poster'] = $hd_url;
break;
}
}
}
// Fallback sur le thumbnail si pas d'image HD trouvée
if (empty($empty['poster'])) {
// CORRECTION : La regex gère le cas où title= est avant src=
if (preg_match('/<img[^>]*title="Recto[^"]*"[^>]*src="([^"]+)"/i', $html, $m)) {
$empty['poster'] = $m[1];
} elseif (preg_match('/<img[^>]*src="([^"]+)"[^>]*title="Recto/i', $html, $m)) {
$empty['poster'] = $m[1];
} elseif (preg_match('/<meta[^>]*property="og:image"[^>]*content="([^"]+)"/i', $html, $m)) {
$empty['poster'] = $m[1];
}
} }
// Extraire le réalisateur // Extraire le réalisateur
@@ -712,15 +666,6 @@ function fetchFromMovieCovers($title, $year = '') {
$empty['description'] = html_entity_decode($m[1]); $empty['description'] = html_entity_decode($m[1]);
} }
// Format haute qualité de l'affiche
if ($empty['poster']) {
$empty['poster'] = str_replace('/thumbs/', '/films-l/', $empty['poster']);
} else {
error_log("MovieCovers: ⚠️ Jaquette non trouvée pour '$title' (URL: $url) — structure HTML probablement changée");
}
error_log("MovieCovers: " . (!empty($empty['title']) ? "✅ Données récupérées pour '$title' → " . $empty['title'] : "⚠️ Page trouvée mais titre non extrait pour '$title'"));
return $empty; return $empty;
} }