diff --git a/api.php b/api.php index 7717333..9c3b33c 100644 --- a/api.php +++ b/api.php @@ -93,59 +93,6 @@ function httpGet($url, $timeout = 3, $ua = null) { return $res ?: null; } -// OPTIMISATION MAJEURE : Vérification des URLs en parallèle -function checkUrlsParallel($urls, $timeout = 3) { - if (empty($urls)) return false; - if (!function_exists('curl_multi_init')) return false; - - $multiHandle = curl_multi_init(); - $curlHandles = []; - - foreach ($urls as $url) { - $ch = curl_init($url); - curl_setopt_array($ch, [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_NOBODY => true, // On télécharge juste l'entête, pas l'image - CURLOPT_TIMEOUT => $timeout, - CURLOPT_CONNECTTIMEOUT => 2, - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', - CURLOPT_FOLLOWLOCATION => true, - ]); - curl_multi_add_handle($multiHandle, $ch); - $curlHandles[] = $ch; - } - - $active = null; - $validUrl = false; - - do { - $status = curl_multi_exec($multiHandle, $active); - if ($active) curl_multi_select($multiHandle, 0.1); - - while ($info = curl_multi_info_read($multiHandle)) { - $ch = $info['handle']; - $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - // Si on trouve UNE url valide, on arrête tout immédiatement - if ($code >= 200 && $code < 400) { - $validUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); - break 2; - } - curl_multi_remove_handle($multiHandle, $ch); - } - } while ($active && $status == CURLM_OK); - - // Nettoyage de la mémoire - foreach ($curlHandles as $ch) { - curl_multi_remove_handle($multiHandle, $ch); - curl_close($ch); - } - curl_multi_close($multiHandle); - - return $validUrl; -} - 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); @@ -168,85 +115,190 @@ function extractYear($dateStr) { return ''; } -// ── COVERCENTURY.COM (Version Parallélisée) ── -function fetchCoverCentury($title, $year = '', $format = 'bluray') { +// ── DVDBlurayCovers.com Scraper ── +function fetchDVDBlurayCovers($title, $year = '', $format = 'bluray') { if (empty($title)) return null; + $ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; $cleanTitle = cleanTitle($title); - if (empty($cleanTitle)) return null; - $formatMap = [ - '4k ultra hd' => '4kultrahd', - '4k' => '4kultrahd', - 'blu-ray' => 'bluray', - 'bluray' => 'bluray', - 'dvd' => 'dvd', + // URL de recherche DVDBlurayCovers + $searchUrl = "https://www.dvdbluraycovers.com/?s=" . urlencode($cleanTitle); + + $html = httpGet($searchUrl, 10, $ua); + if (!$html) { + error_log("DVDBlurayCovers: Échec recherche pour '$title'"); + return null; + } + + $result = [ + 'poster' => '', + 'title' => '', + 'format' => $format, ]; - $ccFormat = $formatMap[strtolower($format)] ?? 'bluray'; - $titleVariants = []; + // Parser le HTML avec DOMDocument + $dom = new DOMDocument(); + @$dom->loadHTML($html); + $xpath = new DOMXPath($dom); - $normalized = trim(preg_replace('/\s+/', '_', preg_replace('/[^a-zA-Z0-9\s]/', '', $cleanTitle))); - if (!empty($normalized)) $titleVariants[] = $normalized; + // Chercher les liens vers les pages de covers individuelles + // DVDBlurayCovers utilise des articles avec des liens vers les covers + $links = $xpath->query('//article[contains(@class,"post") or contains(@class,"entry")]//a/@href'); - $withoutArticle = trim(preg_replace('/\s+/', '_', preg_replace('/[^a-zA-Z0-9\s]/', '', preg_replace('/^(The|Le|La|Les|Un|Une|A)\s+/i', '', $cleanTitle)))); - if (!empty($withoutArticle) && $withoutArticle !== $normalized) { - $titleVariants[] = $withoutArticle; + if ($links->length === 0) { + // Fallback : chercher tous les liens qui ressemblent à des pages de covers + $links = $xpath->query('//a[contains(@href,"/cover/") or contains(@href,"/covers/") or contains(@href,"/bluray/") or contains(@href,"/dvd/")]'); } - if (!preg_match('/^The_/i', $normalized)) { - $titleVariants[] = 'The_' . $normalized; - } - - // Groupement 1 : Le format exact (Testé en parallèle) - $primaryCandidates = []; - foreach ($titleVariants as $variant) { - if (empty($variant)) continue; - $firstLetter = strtolower(substr($variant, 0, 1)); - $primaryCandidates[] = "https://www.covercentury.com/covers/{$ccFormat}/{$firstLetter}/{$variant}.jpg"; - for ($i = 1; $i <= 5; $i++) { - $primaryCandidates[] = "https://www.covercentury.com/covers/{$ccFormat}/{$firstLetter}/{$variant}{$i}.jpg"; + $coverPages = []; + foreach ($links as $link) { + $href = $link->nodeValue; + if (strpos($href, 'http') !== 0) { + $href = 'https://www.dvdbluraycovers.com' . $href; } + // Éviter les doublons et les pages de catégories/tags + if (!in_array($href, $coverPages) && + strpos($href, '/category/') === false && + strpos($href, '/tag/') === false && + strpos($href, '/page/') === false) { + $coverPages[] = $href; + } + if (count($coverPages) >= 5) break; // Limiter à 5 résultats max } - // On teste toutes les URLs du format désiré d'un seul coup (ultra rapide) - $validUrl = checkUrlsParallel($primaryCandidates, 2); - - if ($validUrl) { - return [ - 'poster' => $validUrl, - 'title' => $cleanTitle, - 'format' => $format, - ]; - } - - // Groupement 2 : Si échec, on cherche sur les autres formats en parallèle - $fallbackCandidates = []; - $otherFormats = ['dvd', 'bluray', '4kultrahd']; - foreach ($otherFormats as $altFormat) { - if ($altFormat === $ccFormat) continue; - foreach ($titleVariants as $variant) { - if (empty($variant)) continue; - $firstLetter = strtolower(substr($variant, 0, 1)); - $fallbackCandidates[] = "https://www.covercentury.com/covers/{$altFormat}/{$firstLetter}/{$variant}.jpg"; - for ($i = 1; $i <= 3; $i++) { - $fallbackCandidates[] = "https://www.covercentury.com/covers/{$altFormat}/{$firstLetter}/{$variant}{$i}.jpg"; + // Parcourir les pages de covers trouvées + foreach ($coverPages as $coverPage) { + $coverHtml = httpGet($coverPage, 10, $ua); + if (!$coverHtml) continue; + + $domCover = new DOMDocument(); + @$domCover->loadHTML($coverHtml); + $xpathCover = new DOMXPath($domCover); + + // Extraire le titre de la page + $coverTitle = ''; + $titleNodes = $xpathCover->query('//h1[contains(@class,"entry-title") or contains(@class,"post-title")]'); + if ($titleNodes->length > 0) { + $coverTitle = trim($titleNodes->item(0)->textContent); + } else { + $titleNodes = $xpathCover->query('//title'); + if ($titleNodes->length > 0) { + $coverTitle = trim($titleNodes->item(0)->textContent); + // Nettoyer le titre (enlever " - DVDBlurayCovers" etc.) + $coverTitle = preg_replace('/\s*[-|]\s*DVDBlurayCovers.*$/i', '', $coverTitle); } } + + // Vérifier que le titre correspond au film recherché + $coverTitleLower = strtolower($coverTitle); + $searchTitleLower = strtolower($cleanTitle); + + $score = 0; + if (strpos($coverTitleLower, $searchTitleLower) !== false) { + $score += 50; + } + if (!empty($year) && strpos($coverTitle, $year) !== false) { + $score += 30; + } + if (stripos($coverTitle, $format) !== false) { + $score += 20; + } + + // Si le score est trop bas, on ignore ce résultat + if ($score < 30) { + continue; + } + + // Chercher l'image principale de la jaquette + $poster = ''; + + // Méthode 1 : Chercher dans le contenu principal (entry-content) + $contentNodes = $xpathCover->query('//div[contains(@class,"entry-content") or contains(@class,"post-content")]//img'); + if ($contentNodes->length > 0) { + foreach ($contentNodes as $img) { + $src = $img->getAttribute('src'); + if (empty($src)) { + $src = $img->getAttribute('data-src'); + } + if (empty($src)) { + $src = $img->getAttribute('data-lazy-src'); + } + + // Vérifier que c'est une vraie image (pas un logo, icône, etc.) + if (!empty($src) && + strpos($src, 'logo') === false && + strpos($src, 'icon') === false && + strpos($src, 'banner') === false && + strpos($src, 'bg') === false && + strpos($src, 'button') === false && + strpos($src, 'social') === false && + (strpos($src, '.jpg') !== false || strpos($src, '.jpeg') !== false || strpos($src, '.png') !== false || strpos($src, '.webp') !== false)) { + $poster = $src; + break; + } + } + } + + // Méthode 2 : Chercher dans les figures + if (empty($poster)) { + $figureNodes = $xpathCover->query('//figure//img'); + if ($figureNodes->length > 0) { + foreach ($figureNodes as $img) { + $src = $img->getAttribute('src'); + if (empty($src)) { + $src = $img->getAttribute('data-src'); + } + if (!empty($src) && + strpos($src, 'logo') === false && + strpos($src, 'icon') === false && + (strpos($src, '.jpg') !== false || strpos($src, '.jpeg') !== false || strpos($src, '.png') !== false)) { + $poster = $src; + break; + } + } + } + } + + // Méthode 3 : Meta Open Graph + if (empty($poster)) { + $ogNodes = $xpathCover->query('//meta[@property="og:image"]/@content'); + if ($ogNodes->length > 0) { + $poster = $ogNodes->item(0)->nodeValue; + } + } + + // Méthode 4 : Toutes les images dans wp-content/uploads + if (empty($poster)) { + $allImgs = $xpathCover->query('//img[contains(@src,"wp-content/uploads")]'); + if ($allImgs->length > 0) { + foreach ($allImgs as $img) { + $src = $img->getAttribute('src'); + if (strpos($src, 'logo') === false && + strpos($src, 'icon') === false && + strpos($src, '300x') === false && + strpos($src, '150x') === false) { + $poster = $src; + break; + } + } + } + } + + // Si on a trouvé une image, on la garde + if (!empty($poster)) { + // S'assurer que l'URL est absolue + if (strpos($poster, 'http') !== 0) { + $poster = 'https://www.dvdbluraycovers.com' . (strpos($poster, '/') === 0 ? '' : '/') . ltrim($poster, '/'); + } + + $result['poster'] = $poster; + $result['title'] = $coverTitle; + break; + } } - $validUrl = checkUrlsParallel($fallbackCandidates, 2); - - if ($validUrl) { - return [ - 'poster' => $validUrl, - 'title' => $cleanTitle, - 'format' => $format, - ]; - } - - error_log("CoverCentury: Aucune jaquette trouvée pour '$title'"); - return null; + return (!empty($result['poster'])) ? $result : null; } // ── API TMDB (uniquement pour les critiques) ── @@ -384,7 +436,6 @@ switch ($action) { "; $result = $pdo->query($sql)->fetchAll(); - // CORRECTION BUG: L'esperluette "&" est indispensable pour modifier la variable dans le tableau foreach ($result as &$row) { if ($row['rating'] !== null) { $ratingVal = (float)$row['rating']; @@ -429,11 +480,12 @@ switch ($action) { } if ($type === 'videotheque') { + // DVDBlurayCovers pour vidéothèque $format = $result['format'] ?: 'Blu-ray'; - $ccData = fetchCoverCentury($titleForSearch, $result['year'], $format); - if (!empty($ccData)) { - if (!empty($ccData['poster'])) $result['poster'] = $ccData['poster']; - if (!empty($ccData['title'])) $result['title'] = $ccData['title']; + $dcData = fetchDVDBlurayCovers($titleForSearch, $result['year'], $format); + if (!empty($dcData)) { + if (!empty($dcData['poster'])) $result['poster'] = $dcData['poster']; + if (!empty($dcData['title'])) $result['title'] = $dcData['title']; $result['format'] = $format; } } else { @@ -474,11 +526,12 @@ switch ($action) { $stmt = $pdo->prepare($sql); $stmt->execute([$id, $data['title'] ?? '', $data['year'] ?? '', $data['director'] ?? '', $data['poster'] ?? '', $data['rating'] ?? 3.0, $data['review'] ?? '', $streaming]); } else { + // Vidéothèque : DVDBlurayCovers pour la jaquette if (empty($data['poster']) && !empty($data['title'])) { $format = $data['format'] ?: 'Blu-ray'; - $ccData = fetchCoverCentury($data['title'], $data['year'] ?? '', $format); - if (!empty($ccData['poster'])) { - $data['poster'] = $ccData['poster']; + $dcData = fetchDVDBlurayCovers($data['title'], $data['year'] ?? '', $format); + if (!empty($dcData['poster'])) { + $data['poster'] = $dcData['poster']; } } @@ -516,7 +569,6 @@ switch ($action) { $pdo->beginTransaction(); try { - // OPTIMISATION MAJEURE : On prépare les requêtes SQL UNE SEULE FOIS en dehors de la boucle $sqlVideotheque = "INSERT INTO videotheque (id, title, year, director, poster, format, length, publisher, ean_isbn13, number_of_discs, aspect_ratio, description, actors) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE @@ -582,20 +634,20 @@ switch ($action) { $poster = $rowData['poster'] ?? ''; $director = ''; - $cleanTitleForCC = cleanTitle($title); - if (!empty($cleanTitleForCC)) { - $ccData = fetchCoverCentury($cleanTitleForCC, $year, $format); - if (!empty($ccData)) { - if (!empty($ccData['poster'])) { - $poster = $ccData['poster']; + // Récupération jaquette via DVDBlurayCovers + $cleanTitleForDC = cleanTitle($title); + if (!empty($cleanTitleForDC)) { + $dcData = fetchDVDBlurayCovers($cleanTitleForDC, $year, $format); + if (!empty($dcData)) { + if (!empty($dcData['poster'])) { + $poster = $dcData['poster']; } - if (!empty($ccData['title']) && ($title === 'Sans titre' || empty($title))) { - $title = $ccData['title']; + if (!empty($dcData['title']) && ($title === 'Sans titre' || empty($title))) { + $title = $dcData['title']; } } } - // Exécution avec la requête préparée hors de la boucle $stmtVideotheque->execute([$id, $title, $year, $director, $poster, $format, $length, $publisher, $ean, $discs, $aspect, $description, $actors]); } else { @@ -616,7 +668,6 @@ switch ($action) { } if (empty($streaming)) $streaming = 'Support physique / Cinéma'; - // Exécution avec la requête préparée hors de la boucle $stmtCritiques->execute([$id, $title, $year, $director, $poster, $rating, $review, $streaming]); } $imported++; @@ -634,7 +685,7 @@ switch ($action) { } break; - case 'debug_covercentury': + case 'debug_dvdbluraycovers': $title = $_GET['title'] ?? ''; $year = $_GET['year'] ?? ''; $format = $_GET['format'] ?? 'Blu-ray'; @@ -642,20 +693,7 @@ switch ($action) { if (!$title) { echo json_encode(['error' => 'Titre manquant']); exit; } $result = ['title' => $title, 'year' => $year, 'format' => $format]; - - $cleanTitle = cleanTitle($title); - $normalized = preg_replace('/[^a-zA-Z0-9\s]/', '', $cleanTitle); - $normalized = trim(preg_replace('/\s+/', '_', $normalized)); - $withoutArticle = preg_replace('/^(The|Le|La|Les|Un|Une|A)\s+/i', '', $cleanTitle); - $withoutArticle = preg_replace('/[^a-zA-Z0-9\s]/', '', $withoutArticle); - $withoutArticle = trim(preg_replace('/\s+/', '_', $withoutArticle)); - - $result['variants'] = [ - 'normalized' => $normalized, - 'without_article' => $withoutArticle, - ]; - - $data = fetchCoverCentury($title, $year, $format); + $data = fetchDVDBlurayCovers($title, $year, $format); $result['data'] = $data; $result['status'] = $data ? 'OK' : 'AUCUN_RÉSULTAT';