Actualiser api.php

This commit is contained in:
2026-06-25 15:17:14 +02:00
parent 0ab6330069
commit fd91b968fe
+198 -160
View File
@@ -93,59 +93,6 @@ function httpGet($url, $timeout = 3, $ua = null) {
return $res ?: 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) { function cleanTitle($title) {
$clean = preg_replace('/\s*[\[\(].*?[\]\)]\s*/', '', $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); $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 ''; return '';
} }
// ── COVERCENTURY.COM (Version Parallélisée) ── // ── DVDBlurayCovers.com Scraper ──
function fetchCoverCentury($title, $year = '', $format = 'bluray') { function fetchDVDBlurayCovers($title, $year = '', $format = 'bluray') {
if (empty($title)) return null; 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); $cleanTitle = cleanTitle($title);
if (empty($cleanTitle)) return null;
$formatMap = [ // URL de recherche DVDBlurayCovers
'4k ultra hd' => '4kultrahd', $searchUrl = "https://www.dvdbluraycovers.com/?s=" . urlencode($cleanTitle);
'4k' => '4kultrahd',
'blu-ray' => 'bluray',
'bluray' => 'bluray',
'dvd' => 'dvd',
];
$ccFormat = $formatMap[strtolower($format)] ?? 'bluray';
$titleVariants = []; $html = httpGet($searchUrl, 10, $ua);
if (!$html) {
$normalized = trim(preg_replace('/\s+/', '_', preg_replace('/[^a-zA-Z0-9\s]/', '', $cleanTitle))); error_log("DVDBlurayCovers: Échec recherche pour '$title'");
if (!empty($normalized)) $titleVariants[] = $normalized;
$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 (!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";
}
}
// 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";
}
}
}
$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 null;
}
$result = [
'poster' => '',
'title' => '',
'format' => $format,
];
// Parser le HTML avec DOMDocument
$dom = new DOMDocument();
@$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
// 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');
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/")]');
}
$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
}
// 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;
}
}
return (!empty($result['poster'])) ? $result : null;
} }
// ── API TMDB (uniquement pour les critiques) ── // ── API TMDB (uniquement pour les critiques) ──
@@ -384,7 +436,6 @@ switch ($action) {
"; ";
$result = $pdo->query($sql)->fetchAll(); $result = $pdo->query($sql)->fetchAll();
// CORRECTION BUG: L'esperluette "&" est indispensable pour modifier la variable dans le tableau
foreach ($result as &$row) { foreach ($result as &$row) {
if ($row['rating'] !== null) { if ($row['rating'] !== null) {
$ratingVal = (float)$row['rating']; $ratingVal = (float)$row['rating'];
@@ -429,11 +480,12 @@ switch ($action) {
} }
if ($type === 'videotheque') { if ($type === 'videotheque') {
// DVDBlurayCovers pour vidéothèque
$format = $result['format'] ?: 'Blu-ray'; $format = $result['format'] ?: 'Blu-ray';
$ccData = fetchCoverCentury($titleForSearch, $result['year'], $format); $dcData = fetchDVDBlurayCovers($titleForSearch, $result['year'], $format);
if (!empty($ccData)) { if (!empty($dcData)) {
if (!empty($ccData['poster'])) $result['poster'] = $ccData['poster']; if (!empty($dcData['poster'])) $result['poster'] = $dcData['poster'];
if (!empty($ccData['title'])) $result['title'] = $ccData['title']; if (!empty($dcData['title'])) $result['title'] = $dcData['title'];
$result['format'] = $format; $result['format'] = $format;
} }
} else { } else {
@@ -474,11 +526,12 @@ switch ($action) {
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([$id, $data['title'] ?? '', $data['year'] ?? '', $data['director'] ?? '', $data['poster'] ?? '', $data['rating'] ?? 3.0, $data['review'] ?? '', $streaming]); $stmt->execute([$id, $data['title'] ?? '', $data['year'] ?? '', $data['director'] ?? '', $data['poster'] ?? '', $data['rating'] ?? 3.0, $data['review'] ?? '', $streaming]);
} else { } else {
// Vidéothèque : DVDBlurayCovers pour la jaquette
if (empty($data['poster']) && !empty($data['title'])) { if (empty($data['poster']) && !empty($data['title'])) {
$format = $data['format'] ?: 'Blu-ray'; $format = $data['format'] ?: 'Blu-ray';
$ccData = fetchCoverCentury($data['title'], $data['year'] ?? '', $format); $dcData = fetchDVDBlurayCovers($data['title'], $data['year'] ?? '', $format);
if (!empty($ccData['poster'])) { if (!empty($dcData['poster'])) {
$data['poster'] = $ccData['poster']; $data['poster'] = $dcData['poster'];
} }
} }
@@ -516,7 +569,6 @@ switch ($action) {
$pdo->beginTransaction(); $pdo->beginTransaction();
try { 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) $sqlVideotheque = "INSERT INTO videotheque (id, title, year, director, poster, format, length, publisher, ean_isbn13, number_of_discs, aspect_ratio, description, actors)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
@@ -582,20 +634,20 @@ switch ($action) {
$poster = $rowData['poster'] ?? ''; $poster = $rowData['poster'] ?? '';
$director = ''; $director = '';
$cleanTitleForCC = cleanTitle($title); // Récupération jaquette via DVDBlurayCovers
if (!empty($cleanTitleForCC)) { $cleanTitleForDC = cleanTitle($title);
$ccData = fetchCoverCentury($cleanTitleForCC, $year, $format); if (!empty($cleanTitleForDC)) {
if (!empty($ccData)) { $dcData = fetchDVDBlurayCovers($cleanTitleForDC, $year, $format);
if (!empty($ccData['poster'])) { if (!empty($dcData)) {
$poster = $ccData['poster']; if (!empty($dcData['poster'])) {
$poster = $dcData['poster'];
} }
if (!empty($ccData['title']) && ($title === 'Sans titre' || empty($title))) { if (!empty($dcData['title']) && ($title === 'Sans titre' || empty($title))) {
$title = $ccData['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]); $stmtVideotheque->execute([$id, $title, $year, $director, $poster, $format, $length, $publisher, $ean, $discs, $aspect, $description, $actors]);
} else { } else {
@@ -616,7 +668,6 @@ switch ($action) {
} }
if (empty($streaming)) $streaming = 'Support physique / Cinéma'; 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]); $stmtCritiques->execute([$id, $title, $year, $director, $poster, $rating, $review, $streaming]);
} }
$imported++; $imported++;
@@ -634,7 +685,7 @@ switch ($action) {
} }
break; break;
case 'debug_covercentury': case 'debug_dvdbluraycovers':
$title = $_GET['title'] ?? ''; $title = $_GET['title'] ?? '';
$year = $_GET['year'] ?? ''; $year = $_GET['year'] ?? '';
$format = $_GET['format'] ?? 'Blu-ray'; $format = $_GET['format'] ?? 'Blu-ray';
@@ -642,20 +693,7 @@ switch ($action) {
if (!$title) { echo json_encode(['error' => 'Titre manquant']); exit; } if (!$title) { echo json_encode(['error' => 'Titre manquant']); exit; }
$result = ['title' => $title, 'year' => $year, 'format' => $format]; $result = ['title' => $title, 'year' => $year, 'format' => $format];
$data = fetchDVDBlurayCovers($title, $year, $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);
$result['data'] = $data; $result['data'] = $data;
$result['status'] = $data ? 'OK' : 'AUCUN_RÉSULTAT'; $result['status'] = $data ? 'OK' : 'AUCUN_RÉSULTAT';