Actualiser api.php
This commit is contained in:
@@ -74,6 +74,39 @@ function httpGet($url, $timeout = 10, $headers = []) {
|
||||
return ($code === 200) ? $res : null;
|
||||
}
|
||||
|
||||
// Vérifie qu'une URL d'image répond bien en 200 (utilisé pour valider les URLs
|
||||
// de couverture "devinées" à partir d'un ID, sans avoir à parser du HTML fragile)
|
||||
function urlExists($url) {
|
||||
if (empty($url)) return false;
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_NOBODY => true, // requête HEAD
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0',
|
||||
]);
|
||||
curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
return $code === 200;
|
||||
}
|
||||
|
||||
// Retire les accents/diacritiques et normalise une chaîne pour construire
|
||||
// une URL de type moviecovers.com (ex: "L'Étrange Noël" -> "L ETRANGE NOEL")
|
||||
function removeAccentsForUrl($str) {
|
||||
$str = str_replace(['œ', 'Œ'], ['oe', 'OE'], $str);
|
||||
$str = str_replace(['æ', 'Æ'], ['ae', 'AE'], $str);
|
||||
// Translittère les accents (fonctionne même si l'extension intl est absente)
|
||||
$transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
|
||||
if ($transliterated !== false && $transliterated !== '') {
|
||||
$str = $transliterated;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
function cleanTitle($title) {
|
||||
$clean = preg_replace('/\s*[\[\(].*?[\]\)]\s*/', '', $title);
|
||||
$clean = preg_replace('/\s*-\s*(Édition|Edition|Collector|Simple|Spéciale|Digibook|Ultimate|Intégrale|Combo|SteelBook|Boîtier).*$/i', '', $clean);
|
||||
@@ -435,13 +468,23 @@ function fetchFromBlurayCom($ean) {
|
||||
return $empty;
|
||||
}
|
||||
|
||||
// Extraction des données - regex améliorées
|
||||
// Titre
|
||||
if (preg_match('/<h1[^>]*>([^<]+)<\/h1>/i', $movieHtml, $m)) {
|
||||
// Extraction des données - regex robustes face à la refonte du site
|
||||
// (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,
|
||||
// et le span#runtime a été remplacé par du texte brut dans une cellule de tableau)
|
||||
|
||||
// Titre - le <h1> peut désormais contenir un <a>, donc on autorise les balises
|
||||
// imbriquées avec (.*?) puis on nettoie avec strip_tags
|
||||
if (preg_match('/<h1[^>]*>(.*?)<\/h1>/is', $movieHtml, $m)) {
|
||||
$rawTitle = trim(strip_tags($m[1]));
|
||||
$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
|
||||
$rawTitle = trim(html_entity_decode(strip_tags($m[1])));
|
||||
$empty['title'] = trim(preg_replace('/\s*(Blu-ray|4K|3D|DVD|UHD).*$/i', '', $rawTitle));
|
||||
}
|
||||
|
||||
// Année
|
||||
if (preg_match('/href="[^"]*year=(\d{4})[^"]*"[^>]*>(\d{4})<\/a>/i', $movieHtml, $m)) {
|
||||
$empty['year'] = $m[1];
|
||||
@@ -452,13 +495,20 @@ function fetchFromBlurayCom($ean) {
|
||||
$empty['publisher'] = trim($m[1]);
|
||||
}
|
||||
|
||||
// Durée
|
||||
if (preg_match('/<span[^>]*id="runtime"[^>]*>(\d+)\s*min<\/span>/i', $movieHtml, $m)) {
|
||||
// Durée - le span#runtime n'existe plus ; on cherche "NNN min" à proximité
|
||||
// du lien "année" déjà trouvé plus haut (ex: "... | 2012 | 123 min | Rated PG | ...")
|
||||
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';
|
||||
} elseif (preg_match('/\b(\d{2,3})\s*min\b/i', $movieHtml, $m)) {
|
||||
// Filet de sécurité, moins précis
|
||||
$empty['length'] = $m[1] . ' min';
|
||||
}
|
||||
|
||||
// Aspect ratio
|
||||
if (preg_match('/Aspect[\s-]*ratio:\s*([\d\.]+:[\d\.]+)/i', $movieHtml, $m)) {
|
||||
// Aspect ratio - "Original aspect ratio: X.XX:1" est plus fiable que le premier
|
||||
// "Aspect ratio:" qui peut lister plusieurs valeurs (plans différents dans le film)
|
||||
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]);
|
||||
}
|
||||
|
||||
@@ -480,41 +530,67 @@ function fetchFromBlurayCom($ean) {
|
||||
$empty['format'] = 'Blu-ray';
|
||||
}
|
||||
|
||||
// Affiche HD - plusieurs patterns
|
||||
if (preg_match('/src="(https:\/\/images\.static-bluray\.com\/movies\/covers\/\d+_front\.jpg[^"]*)"/i', $movieHtml, $m)) {
|
||||
// Affiche HD - le site charge désormais les jaquettes en JS (lazy-load,
|
||||
// le HTML statique ne contient qu'un visuel "Temporary cover art" en placeholder).
|
||||
// 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];
|
||||
} elseif (preg_match('/<img[^>]*class="coverfront"[^>]*src="([^"]+)"/i', $movieHtml, $m)) {
|
||||
$posterUrl = $m[1];
|
||||
$posterUrl = preg_replace('/_large\.jpg/', '_front.jpg', $posterUrl);
|
||||
} elseif (preg_match('/<img[^>]*class="[^"]*coverfront[^"]*"[^>]*(?:src|data-src)="([^"]+)"/i', $movieHtml, $m)) {
|
||||
$posterUrl = preg_replace('/_large\.jpg/', '_front.jpg', $m[1]);
|
||||
$empty['poster'] = $posterUrl;
|
||||
}
|
||||
|
||||
// Synopsis, Réalisateur, Acteurs
|
||||
if (preg_match('/<div[^>]*id="movie_info"[^>]*>(.*?)<div[^>]*id="movie_review_intro"/is', $movieHtml, $infoBlock)) {
|
||||
$infoHtml = $infoBlock[1];
|
||||
|
||||
// Synopsis
|
||||
if (preg_match('/<\/center><br>\s*(.*?)<br><br><br>Director:/is', $infoHtml, $m)) {
|
||||
$synopsis = trim(strip_tags($m[1]));
|
||||
$synopsis = preg_replace('/\s+/', ' ', $synopsis);
|
||||
$empty['description'] = $synopsis;
|
||||
}
|
||||
|
||||
// Réalisateur
|
||||
if (preg_match('/Director:\s*<a[^>]*>([^<]+)<\/a>/i', $infoHtml, $m)) {
|
||||
$empty['director'] = trim($m[1]);
|
||||
}
|
||||
|
||||
// Acteurs
|
||||
if (preg_match('/Starring:\s*(.*?)(?:<br>|<\/div>)/is', $infoHtml, $m)) {
|
||||
$actorsHtml = $m[1];
|
||||
preg_match_all('/<a[^>]*>([^<]+)<\/a>/i', $actorsHtml, $actorMatches);
|
||||
if (!empty($actorMatches[1])) {
|
||||
$empty['actors'] = implode(', ', array_map('trim', array_slice($actorMatches[1], 0, 6)));
|
||||
} 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) {
|
||||
$candidate = "https://images.static-bluray.com/movies/covers/{$movieId}{$suffix}";
|
||||
if (urlExists($candidate)) {
|
||||
$empty['poster'] = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($empty['poster'])) {
|
||||
error_log("Blu-ray.com: ❌ Aucune URL de jaquette CDN valide trouvée pour ID $movieId (fallback TMDB attendu)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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']);
|
||||
return $empty;
|
||||
}
|
||||
@@ -528,24 +604,55 @@ function fetchFromMovieCovers($title, $year = '') {
|
||||
];
|
||||
|
||||
if (empty($title)) return $empty;
|
||||
|
||||
// Nettoyer le titre pour l'URL
|
||||
$urlTitle = strtoupper(str_replace(' ', '+', $title));
|
||||
|
||||
// Nettoyer le titre pour l'URL : moviecovers.com attend un titre en MAJUSCULES
|
||||
// SANS accents ni apostrophes/ponctuation (ex: "L'Étrange Noël" -> "L+ETRANGE+NOEL").
|
||||
// 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";
|
||||
|
||||
error_log("MovieCovers: 🔍 Recherche '$title' → $url");
|
||||
|
||||
$html = httpGet($url, 10);
|
||||
if (!$html) return $empty;
|
||||
|
||||
if (!$html) {
|
||||
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
|
||||
if (preg_match('/<TITLE>([^<]+)<\/TITLE>/i', $html, $m)) {
|
||||
$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
|
||||
// Extraire l'affiche - plusieurs filets de sécurité, l'ordre des attributs
|
||||
// (src/alt/title) dans le <img> n'étant pas garanti
|
||||
if (preg_match('/<img[^>]*src="(https:\/\/moviecovers\.com\/DATA\/thumbs\/[^"]+)"[^>]*title="Recto/i', $html, $m)) {
|
||||
$empty['poster'] = $m[1];
|
||||
} elseif (preg_match('/title="Recto[^"]*"[^>]*src="(https:\/\/moviecovers\.com\/DATA\/thumbs\/[^"]+)"/i', $html, $m)) {
|
||||
// Même image mais avec title AVANT src dans la balise
|
||||
$empty['poster'] = $m[1];
|
||||
} elseif (preg_match('/<meta[^>]*property="og:image"[^>]*content="([^"]+)"/i', $html, $m)) {
|
||||
$empty['poster'] = $m[1];
|
||||
} elseif (preg_match('/<meta[^>]*(?:property|name)="twitter:image"[^>]*content="([^"]+)"/i', $html, $m)) {
|
||||
$empty['poster'] = $m[1];
|
||||
}
|
||||
|
||||
// Extraire le réalisateur
|
||||
@@ -576,8 +683,12 @@ function fetchFromMovieCovers($title, $year = '') {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user