Actualiser api.php

This commit is contained in:
2026-06-25 09:53:20 +02:00
parent a7aba8915d
commit 2953d9e4fc
+154 -197
View File
@@ -117,144 +117,109 @@ function extractYear($dateStr) {
return '';
}
// ── Scraping Fnac.com ──
function fetchFnac($ean) {
if (empty($ean) || strlen($ean) < 8) return null;
// ── MovieCovers.com (jaquettes HD pour la vidéothèque) ──
function fetchMovieCovers($title, $year = '', $format = 'Blu-ray') {
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);
// MovieCovers utilise des URLs de recherche
$searchUrl = "https://moviecovers.com/search?q=" . urlencode($cleanTitle);
if (!empty($year)) {
$searchUrl .= "+(" . $year . ")";
}
// Étape 1 : Recherche par EAN
$searchUrl = "https://www.fnac.com/SearchResult/ResultList.aspx?Search=" . urlencode($ean);
$html = httpGet($searchUrl, 8, $ua);
if (!$html) {
error_log("Fnac: Échec recherche HTML pour EAN $ean");
error_log("MovieCovers: Échec recherche pour '$title'");
return null;
}
// Étape 2 : Extraire le lien vers la fiche produit
$productUrl = null;
// Chercher un lien vers une fiche produit (format /a12345678/...)
if (preg_match('/href=["\']([^"\']*\/a\d+\/[^"\'\s]+\.html)["\']/i', $html, $matches)) {
$productUrl = $matches[1];
if (strpos($productUrl, 'http') !== 0) {
$productUrl = 'https://www.fnac.com' . $productUrl;
}
}
if (!$productUrl) {
error_log("Fnac: Aucune fiche trouvée pour EAN $ean");
return null;
}
error_log("Fnac: Fiche trouvée - $productUrl");
// Étape 3 : Récupérer la fiche complète
$ficheHtml = httpGet($productUrl, 8, $ua);
if (!$ficheHtml) {
error_log("Fnac: Impossible de charger la fiche");
return null;
}
// Étape 4 : Extraire les données depuis le HTML
$result = [
'title' => '',
'poster' => '',
'publisher' => '',
'format' => '',
'length' => '',
'aspect' => '',
'discs' => '',
'actors' => '',
'description' => '',
'title' => '',
'format' => $format,
];
// Extraction du titre
if (preg_match('/<meta[^>]+property=["\']og:title["\'][^>]+content=["\']([^"\']+)["\']/i', $ficheHtml, $m)) {
$result['title'] = trim($m[1]);
} elseif (preg_match('/<h1[^>]*class=["\']ProductTitle[^"\']*["\'][^>]*>([^<]+)<\/h1>/i', $ficheHtml, $m)) {
$result['title'] = trim(strip_tags($m[1]));
} elseif (preg_match('/<h1[^>]*>([^<]+)<\/h1>/i', $ficheHtml, $m)) {
$result['title'] = trim(strip_tags($m[1]));
// Extraction des liens d'images depuis la page de résultats
// MovieCovers utilise des liens vers des images en haute résolution
preg_match_all('/href=["\']([^"\']*\/covers?\/[^"\']+\.jpg)["\']/i', $html, $matches);
if (!empty($matches[1])) {
// Prendre la première image trouvée (généralement la plus pertinente)
$result['poster'] = $matches[1][0];
// Essayer de trouver une version HD si disponible
foreach ($matches[1] as $imgUrl) {
if (strpos($imgUrl, 'hd') !== false || strpos($imgUrl, 'large') !== false) {
$result['poster'] = $imgUrl;
break;
}
}
}
// Extraction de l'affiche (plusieurs méthodes fallback)
if (preg_match('/<meta[^>]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)["\']/i', $ficheHtml, $m)) {
$result['poster'] = $m[1];
} elseif (preg_match('/<link[^>]+rel=["\']image_src["\'][^>]+href=["\']([^"\']+)["\']/i', $ficheHtml, $m)) {
$result['poster'] = $m[1];
} elseif (preg_match('/src=["\']([^"\']+(?:cdn\.fnac\.com|fnac\.com)[^"\']*\.(?:jpg|jpeg|png|webp))["\']/i', $ficheHtml, $m)) {
$result['poster'] = $m[1];
// Si pas trouvé avec le pattern précédent, chercher dans les balises img
if (empty($result['poster'])) {
preg_match_all('/<img[^>]+src=["\']([^"\']*\/covers?\/[^"\']+\.jpg)["\'][^>]*>/i', $html, $imgMatches);
if (!empty($imgMatches[1])) {
$result['poster'] = $imgMatches[1][0];
}
}
// Extraction des caractéristiques techniques (bloc "Fiche technique")
// On cherche les paires label/valeur dans le tableau de caractéristiques
if (preg_match('/<div[^>]*class=["\'][^"\']*fiche-technique[^"\']*["\'][^>]*>(.*?)<\/div>/is', $ficheHtml, $bloc)) {
$techHtml = $bloc[1];
} else {
$techHtml = $ficheHtml;
// Extraire le titre depuis la page si disponible
if (preg_match('/<title[^>]*>([^<]+) - MovieCovers/i', $html, $titleMatch)) {
$result['title'] = trim($titleMatch[1]);
}
// Éditeur / Distributeur
if (preg_match('/(?:Éditeur|Editeur|Distributeur|Studio|Label)\s*<\/[^>]+>\s*<[^>]+>([^<]+)/i', $techHtml, $m)) {
$result['publisher'] = trim(strip_tags($m[1]));
} elseif (preg_match('/(?:Éditeur|Editeur|Distributeur|Studio|Label)\s*[:<\/][^>]*>\s*([^<\n]+)/i', $ficheHtml, $m)) {
$result['publisher'] = trim(strip_tags($m[1]));
}
// Durée
if (preg_match('/(?:Durée|Duree|Durata)\s*<\/[^>]+>\s*<[^>]+>(\d+)\s*(?:min|mn|h)/i', $techHtml, $m)) {
$result['length'] = trim($m[1]) . ' min';
} elseif (preg_match('/(?:Durée|Duree)\s*[:<\/][^>]*>\s*(\d+)\s*(?:min|mn|h)/i', $ficheHtml, $m)) {
$result['length'] = trim($m[1]) . ' min';
}
// Nombre de disques
if (preg_match('/(?:Nombre\s*de\s*disques?|Nb\s*disques?|Disques?)\s*<\/[^>]+>\s*<[^>]+>(\d+)/i', $techHtml, $m)) {
$result['discs'] = trim($m[1]);
} elseif (preg_match('/(?:Nombre\s*de\s*disques?|Nb\s*disques?)\s*[:<\/][^>]*>\s*(\d+)/i', $ficheHtml, $m)) {
$result['discs'] = trim($m[1]);
}
// Format image / Aspect ratio
if (preg_match('/(?:Format\s*(?:image|vidéo|video)|Ratio|Aspect\s*ratio)\s*<\/[^>]+>\s*<[^>]+>([0-9.]+\s*[:\.]\s*[0-9.]+)/i', $techHtml, $m)) {
$result['aspect'] = trim($m[1]);
}
// Acteurs / Casting
if (preg_match('/(?:Acteurs?|Casting|Avec)\s*<\/[^>]+>\s*<[^>]+>([^<]+)/i', $techHtml, $m)) {
$result['actors'] = trim(strip_tags($m[1]));
} elseif (preg_match('/(?:Avec|Acteurs?)\s*[:<\/][^>]*>\s*([^<\n]+)/i', $ficheHtml, $m)) {
$result['actors'] = trim(strip_tags($m[1]));
}
// Description / Synopsis
if (preg_match('/<div[^>]*class=["\'][^"\']*product-description[^"\']*["\'][^>]*>(.*?)<\/div>/is', $ficheHtml, $m)) {
$result['description'] = trim(strip_tags($m[1]));
} elseif (preg_match('/<meta[^>]+property=["\']og:description["\'][^>]+content=["\']([^"\']+)["\']/i', $ficheHtml, $m)) {
$result['description'] = trim($m[1]);
}
// Format (détection depuis le titre ou la fiche)
$formatText = $result['title'] . ' ' . $ficheHtml;
if (preg_match('/(4K\s*Ultra\s*HD|Ultra\s*HD|4K|Blu[\s-]?Ray|DVD|Coffret)/i', $formatText, $m)) {
$fmt = strtoupper(trim($m[1]));
if (strpos($fmt, '4K') !== false || strpos($fmt, 'ULTRA') !== false) $result['format'] = '4K Ultra HD';
elseif (strpos($fmt, 'BLU') !== false) $result['format'] = 'Blu-ray';
elseif (strpos($fmt, 'DVD') !== false) $result['format'] = 'DVD';
elseif (strpos($fmt, 'COFFRET') !== false) $result['format'] = 'Coffret';
}
// Nettoyage final
foreach ($result as $k => $v) {
$result[$k] = trim(html_entity_decode($v, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
}
return (!empty($result['title']) || !empty($result['poster'])) ? $result : null;
return (!empty($result['poster'])) ? $result : null;
}
// ── API TMDB (SANS CACHE - TITRE FRANÇAIS) ──
// Fonction alternative pour recherche directe par URL si on connaît le format
function fetchMovieCoversDirect($title, $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 = strtolower(trim(preg_replace('/[^a-zA-Z0-9\s]/', '', $title)));
$cleanTitle = preg_replace('/\s+/', '-', $cleanTitle);
// Formats supportés par MovieCovers
$formatMap = [
'4k ultra hd' => '4k',
'4k' => '4k',
'blu-ray' => 'bluray',
'bluray' => 'bluray',
'dvd' => 'dvd',
];
$mcFormat = $formatMap[strtolower($format)] ?? 'bluray';
// URL directe vers la page du cover
$directUrl = "https://moviecovers.com/{$mcFormat}/{$cleanTitle}";
$html = httpGet($directUrl, 8, $ua);
if (!$html) {
// Fallback sur la recherche
return fetchMovieCovers($title, '', $format);
}
$result = [
'poster' => '',
'title' => '',
'format' => $format,
];
// Chercher l'image principale en haute qualité
if (preg_match('/<img[^>]+src=["\']([^"\']+\.jpg)["\'][^>]*class=["\'][^"\']*cover[^"\']*["\']/i', $html, $m)) {
$result['poster'] = $m[1];
} elseif (preg_match('/<meta[^>]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)["\']/i', $html, $m)) {
$result['poster'] = $m[1];
}
return (!empty($result['poster'])) ? $result : null;
}
// ── API TMDB (uniquement pour les critiques) ──
function fetchTMDBFull($title, $year, $apiKey, $pdo) {
if (empty($apiKey) || empty($title)) return null;
$cleanTitle = cleanTitle($title);
@@ -393,6 +358,7 @@ switch ($action) {
case 'search_ean_full':
$ean = $_GET['ean'] ?? '';
$type = $_GET['type'] ?? 'videotheque';
if (!$ean) { echo json_encode(['error' => 'EAN manquant']); exit; }
$result = [
'ean' => $ean, 'title' => '', 'director' => '', 'year' => '',
@@ -400,35 +366,56 @@ switch ($action) {
'length' => '', 'number_of_discs' => 1, 'aspect_ratio' => '', 'actors' => ''
];
// Étape 1 : Scraping Fnac
$fnacData = fetchFnac($ean);
$titleForTmdb = '';
if (!empty($fnacData)) {
if (!empty($fnacData['title'])) $result['title'] = $fnacData['title'];
if (!empty($fnacData['poster'])) $result['poster'] = $fnacData['poster'];
if (!empty($fnacData['publisher'])) $result['publisher'] = $fnacData['publisher'];
if (!empty($fnacData['format'])) $result['format'] = $fnacData['format'];
if (!empty($fnacData['length'])) $result['length'] = $fnacData['length'];
if (!empty($fnacData['aspect'])) $result['aspect_ratio'] = $fnacData['aspect'];
if (!empty($fnacData['discs'])) $result['number_of_discs'] = (int)$fnacData['discs'];
if (!empty($fnacData['actors'])) $result['actors'] = $fnacData['actors'];
// Pour MovieCovers, on a besoin d'un titre
// On essaie d'abord de trouver le titre via TMDB (pour les critiques) ou on utilise l'EAN comme base
$tmdbKey = getTmdbApiKey($pdo);
$titleForSearch = '';
$titleForTmdb = cleanTitle($fnacData['title'] ?? '');
if ($type === 'critique' && $tmdbKey) {
// Pour les critiques, on cherche d'abord sur TMDB
$searchUrl = "https://api.themoviedb.org/3/find/{$ean}?api_key={$tmdbKey}&external_source=imdb_id";
$searchRes = httpGet($searchUrl, 5);
$searchData = $searchRes ? json_decode($searchRes, true) : [];
if (!empty($searchData['movie_results'][0])) {
$titleForSearch = $searchData['movie_results'][0]['title'];
$result['title'] = $titleForSearch;
if (!empty($searchData['movie_results'][0]['release_date'])) {
$result['year'] = substr($searchData['movie_results'][0]['release_date'], 0, 4);
}
}
}
// Étape 2 : TMDB pour métadonnées film (réalisateur, vraie affiche, année)
$tmdbKey = getTmdbApiKey($pdo);
if ($tmdbKey && !empty($titleForTmdb)) {
$tmdbData = fetchTMDBFull($titleForTmdb, '', $tmdbKey, $pdo);
// Si pas de titre trouvé, on utilise l'EAN comme référence (peu probable de trouver sur MovieCovers)
if (empty($titleForSearch)) {
echo json_encode(['success' => true, 'data' => $result, 'warning' => 'Titre non trouvé pour cet EAN']);
exit;
}
// Récupération jaquette via MovieCovers (pour vidéothèque) ou TMDB (pour critiques)
if ($type === 'videotheque') {
$format = $result['format'] ?: 'Blu-ray';
$mcData = fetchMovieCovers($titleForSearch, $result['year'], $format);
if (!empty($mcData)) {
if (!empty($mcData['poster'])) $result['poster'] = $mcData['poster'];
if (!empty($mcData['title'])) $result['title'] = $mcData['title'];
$result['format'] = $format;
}
} else {
// Critiques : TMDB complet
if ($tmdbKey) {
$tmdbData = fetchTMDBFull($titleForSearch, $result['year'], $tmdbKey, $pdo);
if ($tmdbData) {
if (!empty($tmdbData['title'])) $result['title'] = $tmdbData['title'];
if (!empty($tmdbData['year'])) $result['year'] = $tmdbData['year'];
if (!empty($tmdbData['director'])) $result['director'] = $tmdbData['director'];
if (!empty($tmdbData['poster'])) $result['poster'] = $tmdbData['poster'];
if (!empty($tmdbData['length'])) $result['length'] = $tmdbData['length'];
if (!empty($tmdbData['cast'])) $result['actors'] = implode(', ', $tmdbData['cast']);
if (!empty($tmdbData['poster'])) $result['poster'] = $tmdbData['poster']; // TMDB prioritaire
}
}
}
echo json_encode(['success' => true, 'data' => $result]);
break;
@@ -437,7 +424,8 @@ switch ($action) {
$type = $data['type'] ?? 'critique';
$id = !empty($data['id']) ? $data['id'] : makeStableId($type, $data['title'] ?? '', $data['year'] ?? '0000');
if (empty($data['director']) || empty($data['poster'])) {
// TMDB uniquement pour les critiques
if ($type === 'critique' && (empty($data['director']) || empty($data['poster']))) {
$tmdbData = fetchTMDBFull($data['title'] ?? '', $data['year'] ?? '', getTmdbApiKey($pdo), $pdo);
if ($tmdbData) {
if (empty($data['director'])) $data['director'] = $tmdbData['director'];
@@ -453,6 +441,15 @@ 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 : MovieCovers pour la jaquette
if (empty($data['poster']) && !empty($data['title'])) {
$format = $data['format'] ?: 'Blu-ray';
$mcData = fetchMovieCovers($data['title'], $data['year'] ?? '', $format);
if (!empty($mcData['poster'])) {
$data['poster'] = $mcData['poster'];
}
}
$sql = "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 title=VALUES(title), year=VALUES(year), director=IF(VALUES(director)!='', VALUES(director), director), poster=IF(VALUES(poster)!='', VALUES(poster), poster), format=IF(VALUES(format)!='', VALUES(format), format), length=IF(VALUES(length)!='', VALUES(length), length), publisher=IF(VALUES(publisher)!='', VALUES(publisher), publisher), ean_isbn13=IF(VALUES(ean_isbn13)!='', VALUES(ean_isbn13), ean_isbn13), number_of_discs=IF(VALUES(number_of_discs)!=1, VALUES(number_of_discs), number_of_discs), aspect_ratio=IF(VALUES(aspect_ratio)!='', VALUES(aspect_ratio), aspect_ratio), description=IF(VALUES(description)!='', VALUES(description), description), actors=IF(VALUES(actors)!='', VALUES(actors), actors)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id, $data['title'] ?? '', $data['year'] ?? '', $data['director'] ?? '', $data['poster'] ?? '', $data['format'] ?? '', $data['length'] ?? '', $data['publisher'] ?? '', $data['ean_isbn13'] ?? '', $data['number_of_discs'] ?? 1, $data['aspect_ratio'] ?? '', $data['description'] ?? '', $data['actors'] ?? '']);
@@ -494,6 +491,7 @@ switch ($action) {
$id = makeStableId($type, $title, $year);
if ($type === 'videotheque') {
// ── VIDÉOTHÈQUE : MovieCovers pour jaquette HD, PAS de TMDB ──
$csvActors = $rowData['ensemble'] ?? $rowData['creators'] ?? '';
$actors = '';
if (!empty($csvActors)) {
@@ -525,66 +523,21 @@ switch ($action) {
$poster = $rowData['poster'] ?? '';
$director = '';
// Enrichissement via Fnac si on a un EAN
if (!empty($ean)) {
$fnacData = fetchFnac($ean);
if (!empty($fnacData)) {
if ((empty($title) || $title === 'Sans titre') && !empty($fnacData['title'])) {
$title = cleanTitle($fnacData['title']);
// Récupération jaquette via MovieCovers
$cleanTitleForMC = cleanTitle($title);
if (!empty($cleanTitleForMC)) {
$mcData = fetchMovieCovers($cleanTitleForMC, $year, $format);
if (!empty($mcData)) {
if (!empty($mcData['poster'])) {
$poster = $mcData['poster'];
}
if (empty($publisher) && !empty($fnacData['publisher'])) {
$publisher = $fnacData['publisher'];
}
if (empty($format) && !empty($fnacData['format'])) {
$format = $fnacData['format'];
}
if (empty($length) && !empty($fnacData['length'])) {
$length = $fnacData['length'];
}
if (empty($aspect) && !empty($fnacData['aspect'])) {
$aspect = $fnacData['aspect'];
}
if ($discs === 1 && !empty($fnacData['discs'])) {
$discs = (int)$fnacData['discs'];
}
if (empty($actors) && !empty($fnacData['actors'])) {
$actors = $fnacData['actors'];
}
if (empty($poster) && !empty($fnacData['poster'])) {
$poster = $fnacData['poster'];
}
if (empty($description) && !empty($fnacData['description'])) {
$description = $fnacData['description'];
if (!empty($mcData['title']) && ($title === 'Sans titre' || empty($title))) {
$title = $mcData['title'];
}
}
}
// Enrichissement via TMDB
if ($tmdbApiKey && !empty($title)) {
$tmdbTitle = $title;
$tmdbTitle = preg_replace('/\s*[\[\(].*?[\]\)]\s*/', '', $tmdbTitle);
$tmdbTitle = preg_replace('/\s*-\s*(Édition|Edition|Collector|Simple|Spéciale|Digibook|Ultimate|Intégrale|Combo|SteelBook|Boîtier|Coffret).*$/i', '', $tmdbTitle);
$tmdbTitle = preg_replace('/\s*(Blu-ray|Bluray|DVD|4K|Ultra HD|Combo|VHS|BDRip|\[.*\]).*$/i', '', $tmdbTitle);
$tmdbTitle = preg_replace('/\s*(Coffret|Trilogie|Quadrilogie|Collection|Anthologie).*$/i', '', $tmdbTitle);
$tmdbTitle = preg_split('/\s*(\/|\+|:)\s*/', $tmdbTitle)[0];
$tmdbTitle = explode(' - ', $tmdbTitle)[0];
$tmdbTitle = trim($tmdbTitle);
$tmdbData = fetchTMDBFull($tmdbTitle, $year, $tmdbApiKey, $pdo);
if (!$tmdbData && $tmdbTitle !== $title) {
$tmdbData = fetchTMDBFull($title, $year, $tmdbApiKey, $pdo);
}
if ($tmdbData) {
if (!empty($tmdbData['title'])) $title = $tmdbData['title'];
if (empty($director)) $director = $tmdbData['director'] ?? '';
if (empty($year) && !empty($tmdbData['year'])) $year = $tmdbData['year'];
if (empty($length) && !empty($tmdbData['length'])) $length = $tmdbData['length'];
if (!empty($tmdbData['overview'])) $description = $tmdbData['overview'];
if (!empty($tmdbData['cast'])) $actors = implode(', ', $tmdbData['cast']);
if (!empty($tmdbData['poster'])) $poster = $tmdbData['poster']; // TMDB prioritaire
}
}
// PAS D'APPEL TMDB POUR LA VIDÉOTHÈQUE
$sql = "INSERT INTO videotheque (id, title, year, director, poster, format, length, publisher, ean_isbn13, number_of_discs, aspect_ratio, description, actors)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -604,6 +557,7 @@ switch ($action) {
$stmt->execute([$id, $title, $year, $director, $poster, $format, $length, $publisher, $ean, $discs, $aspect, $description, $actors]);
} else {
// ── CRITIQUES : TMDB complet ──
$ratingRaw = $rowData['Rating'] ?? $rowData['rating'] ?? '';
$rating = ($ratingRaw !== '' && $ratingRaw !== null) ? (float)$ratingRaw : null;
$review = $rowData['Review'] ?? $rowData['review'] ?? '';
@@ -648,12 +602,15 @@ switch ($action) {
}
break;
case 'debug_fnac':
$ean = $_GET['ean'] ?? '';
if (!$ean) { echo json_encode(['error' => 'EAN manquant']); exit; }
case 'debug_moviecovers':
$title = $_GET['title'] ?? '';
$year = $_GET['year'] ?? '';
$format = $_GET['format'] ?? 'Blu-ray';
$result = ['ean' => $ean];
$data = fetchFnac($ean);
if (!$title) { echo json_encode(['error' => 'Titre manquant']); exit; }
$result = ['title' => $title, 'year' => $year, 'format' => $format];
$data = fetchMovieCovers($title, $year, $format);
$result['data'] = $data;
$result['status'] = $data ? 'OK' : 'AUCUN_RÉSULTAT';