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,
'aspect_ratio' => ''
];
$ean = preg_replace('/[^0-9]/', '', (string)$ean);
if (strlen($ean) < 8) {
error_log("Blu-ray.com: ❌ EAN invalide: $ean");
@@ -436,8 +435,6 @@ function fetchFromBlurayCom($ean) {
$lastRequest = microtime(true);
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";
$ch = curl_init($searchUrl);
curl_setopt_array($ch, [
@@ -453,7 +450,6 @@ function fetchFromBlurayCom($ean) {
'Referer: https://www.blu-ray.com/'
]
]);
$searchHtml = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
@@ -464,20 +460,17 @@ function fetchFromBlurayCom($ean) {
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)) {
error_log("Blu-ray.com: ❌ Film non trouvé pour EAN $ean");
return $empty;
}
$movieUrl = $matches[1];
$movieId = $matches[2];
error_log("Blu-ray.com: ✅ Film trouvé → $movieUrl");
// Délai avant la 2ème requête
sleep(2);
sleep(2); // Délai avant la 2ème requête
// Récupérer la page du film
$ch2 = curl_init($movieUrl);
curl_setopt_array($ch2, [
CURLOPT_RETURNTRANSFER => true,
@@ -491,7 +484,6 @@ function fetchFromBlurayCom($ean) {
'Referer: https://www.blu-ray.com/'
]
]);
$movieHtml = curl_exec($ch2);
curl_close($ch2);
@@ -500,26 +492,19 @@ function fetchFromBlurayCom($ean) {
return $empty;
}
// 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)) {
// ── EXTRACTION AVEC LES NOUVELLES REGEX ──
// Titre (dans <h3> inside #movie_info)
if (preg_match('/<div[^>]*id="movie_info"[^>]*>.*?<h3>([^<]+)<\/h3>/is', $movieHtml, $m)) {
$empty['title'] = trim($m[1]);
} elseif (preg_match('/<h1[^>]*>([^<]+)<\/h1>/i', $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];
// Année (juste après le <h3>)
if (preg_match('/<h3[^>]*>([^<]+)<\/h3>\s*(?:&nbsp;)?\((\d{4})\)/i', $movieHtml, $m)) {
$empty['year'] = $m[2];
}
// Studio/Éditeur
@@ -527,26 +512,21 @@ function fetchFromBlurayCom($ean) {
$empty['publisher'] = trim($m[1]);
}
// 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)) {
// Durée (dans un span avant "Rated")
if (preg_match('/(\d+)\s*min<\/span>/i', $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
} elseif (preg_match('/(\d+)\s*min\s*\|\s*Rated/i', $movieHtml, $m)) {
$empty['length'] = $m[1] . ' min';
}
// 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)) {
// Aspect ratio
if (preg_match('/Aspect[\s-]*ratio:\s*([\d\.]+:[\d\.]+)/i', $movieHtml, $m)) {
$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)) {
$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]);
$empty['number_of_discs'] = $wordToNum[$word] ?? 1;
} elseif (preg_match('/(\d+)-disc\s+set/i', $movieHtml, $m)) {
@@ -562,67 +542,43 @@ function fetchFromBlurayCom($ean) {
$empty['format'] = 'Blu-ray';
}
// 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)) {
// Affiche HD
if (preg_match('/src="(https:\/\/images\.static-bluray\.com\/movies\/covers\/\d+_front\.jpg[^"]*)"/i', $movieHtml, $m)) {
$empty['poster'] = $m[1];
} elseif (preg_match('/<img[^>]*class="[^"]*coverfront[^"]*"[^>]*(?:src|data-src)="([^"]+)"/i', $movieHtml, $m)) {
$posterUrl = preg_replace('/_large\.jpg/', '_front.jpg', $m[1]);
} elseif (preg_match('/<img[^>]*class="coverfront"[^>]*src="([^"]+)"/i', $movieHtml, $m)) {
$posterUrl = $m[1];
$posterUrl = preg_replace('/_large\.jpg/', '_front.jpg', $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) {
$candidate = "https://images.static-bluray.com/movies/covers/{$movieId}{$suffix}";
if (urlExists($candidate)) {
$empty['poster'] = $candidate;
break;
}
// Synopsis, Réalisateur, Acteurs (dans #movie_info)
if (preg_match('/<div[^>]*id="movie_info"[^>]*>(.*?)<div[^>]*id="movie_review_intro"/is', $movieHtml, $infoBlock)) {
$infoHtml = $infoBlock[1];
// 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']);
return $empty;
}
@@ -634,57 +590,55 @@ function fetchFromMovieCovers($title, $year = '') {
'publisher' => '', 'format' => 'DVD', 'number_of_discs' => 1,
'aspect_ratio' => ''
];
if (empty($title)) return $empty;
// 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;
}
// Nettoyer le titre pour l'URL (les espaces deviennent des +)
$urlTitle = strtoupper(str_replace(' ', '+', $title));
$url = "https://moviecovers.com/film/titre_{$urlTitle}.html";
error_log("MovieCovers: 🔍 Recherche '$title' → $url");
$html = httpGet($url, 10);
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;
}
if (!$html) return $empty;
// 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]);
} 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
// (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 l'IDMC pour construire l'URL de l'image HD (comme dans votre script Python)
$idmc = '';
if (preg_match('/IDMC.*?<PRE>([^<]+)<\/PRE>/is', $html, $m)) {
$idmc = trim($m[1]);
} elseif (preg_match('/<meta property="og:image" content="[^"]*\/getjpg\.html\/([^"\']+)\.jpg"/i', $html, $m)) {
$idmc = urldecode($m[1]);
}
// Essayer de récupérer l'affiche HD via les URLs du script Python
if ($idmc) {
$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
@@ -712,15 +666,6 @@ function fetchFromMovieCovers($title, $year = '') {
$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;
}