Actualiser api.php

This commit is contained in:
2026-06-21 16:05:06 +02:00
parent 3c03275ec9
commit 1eaf3e6275
+121 -76
View File
@@ -4,12 +4,11 @@ header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS"); header("Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization"); header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; } if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
define('ENCRYPTION_KEY', 'MaCleSecreteSuperRobuste123!'); define('ENCRYPTION_KEY', 'MaCleSecreteSuperRobuste123!');
define('TMDB_CACHE_TTL', 86400); // 24h de cache define('TMDB_CACHE_TTL', 604800); // 7 jours de cache
try { try {
$pdo = new PDO("mysql:host=localhost;dbname=mon_cinema;charset=utf8mb4", "root", "", [ $pdo = new PDO("mysql:host=localhost;dbname=mon_cinema;charset=utf8mb4", "root", "", [
@@ -21,9 +20,15 @@ try {
$pdo->exec("CREATE TABLE IF NOT EXISTS critiques (id BIGINT PRIMARY KEY, title VARCHAR(255) NOT NULL, year VARCHAR(10), director VARCHAR(255), poster TEXT, rating DECIMAL(3,1) DEFAULT 3.0, review TEXT, streaming VARCHAR(255))"); $pdo->exec("CREATE TABLE IF NOT EXISTS critiques (id BIGINT PRIMARY KEY, title VARCHAR(255) NOT NULL, year VARCHAR(10), director VARCHAR(255), poster TEXT, rating DECIMAL(3,1) DEFAULT 3.0, review TEXT, streaming VARCHAR(255))");
$pdo->exec("ALTER TABLE critiques MODIFY COLUMN rating DECIMAL(3,1) DEFAULT 3.0;"); $pdo->exec("ALTER TABLE critiques MODIFY COLUMN rating DECIMAL(3,1) DEFAULT 3.0;");
$pdo->exec("CREATE TABLE IF NOT EXISTS videotheque (id BIGINT PRIMARY KEY, title VARCHAR(255) NOT NULL, year VARCHAR(10), director VARCHAR(255), poster TEXT, format VARCHAR(50), length VARCHAR(50), publisher VARCHAR(255), ean_isbn13 VARCHAR(50), number_of_discs INT DEFAULT 1, aspect_ratio VARCHAR(50), description TEXT)"); $pdo->exec("CREATE TABLE IF NOT EXISTS videotheque (id BIGINT PRIMARY KEY, title VARCHAR(255) NOT NULL, year VARCHAR(10), director VARCHAR(255), poster TEXT, format VARCHAR(50), length VARCHAR(50), publisher VARCHAR(255), ean_isbn13 VARCHAR(50), number_of_discs INT DEFAULT 1, aspect_ratio VARCHAR(50), description TEXT)");
// 🆕 Table de cache pour les images (évite les appels répétés)
$pdo->exec("CREATE TABLE IF NOT EXISTS cache_images ( // 🆕 Tables de cache
cache_key VARCHAR(120) PRIMARY KEY, $pdo->exec("CREATE TABLE IF NOT EXISTS cache_tmdb (
cache_key VARCHAR(100) PRIMARY KEY,
data TEXT NOT NULL,
created_at INT NOT NULL
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS cache_ean (
ean VARCHAR(20) PRIMARY KEY,
image_url TEXT, image_url TEXT,
source VARCHAR(20), source VARCHAR(20),
created_at INT NOT NULL created_at INT NOT NULL
@@ -69,100 +74,114 @@ function getTmdbApiKey($pdo) {
return decryptData($row['key_value']); return decryptData($row['key_value']);
} }
// ─ HTTP unifié ── // ─ HTTP unifié avec cURL ──
function httpGet($url, $timeout = 8) { function httpGet($url, $timeout = 8) {
if (function_exists('curl_init')) { if (function_exists('curl_init')) {
$ch = curl_init($url); $ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt_array($ch, [
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); CURLOPT_RETURNTRANSFER => true,
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); CURLOPT_TIMEOUT => $timeout,
curl_setopt($ch, CURLOPT_USERAGENT, 'MonCinema/2.0'); CURLOPT_SSL_VERIFYPEER => false,
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); CURLOPT_USERAGENT => 'MonCinema/3.0',
CURLOPT_FOLLOWLOCATION => true
]);
$res = curl_exec($ch); $res = curl_exec($ch);
curl_close($ch); curl_close($ch);
return $res ?: null; return $res ?: null;
} }
$ctx = stream_context_create(['http' => ['timeout' => $timeout, 'user_agent' => 'MonCinema/2.0']]); $ctx = stream_context_create(['http' => ['timeout' => $timeout, 'user_agent' => 'MonCinema/3.0']]);
return @file_get_contents($url, false, $ctx); return @file_get_contents($url, false, $ctx);
} }
// ── 🎬 RÉCUPÉRATION IMAGE VIA EAN (Open Library API) ─ // ── NETTOYAGE TITRE (crucial pour TMDB) ─
function cleanTitleForTmdb($title) {
// Enlever tout ce qui est entre crochets/parenthèses
$clean = preg_replace('/\s*[\[\(].*?[\]\)]\s*/', '', $title);
// Enlever les mentions d'édition
$clean = preg_replace('/\s*-\s*(Édition|Edition|Collector|Simple|Spéciale|Speciale|Digibook|Ultimate|Intégrale|Integrale).*$/i', '', $clean);
// Enlever "Combo Blu-ray + DVD"
$clean = preg_replace('/\s*\[Combo.*?\]\s*/i', '', $clean);
// Enlever "Blu-ray", "DVD", "4K", "UHD" à la fin
$clean = preg_replace('/\s*(Blu-ray|DVD|4K|UHD|VHS)\s*$/i', '', $clean);
// Enlever " - Édition X DVD"
$clean = preg_replace('/\s*-\s*Édition\s+\d+\s*DVD\s*$/i', '', $clean);
// Nettoyer les espaces multiples et tirets en trop
$clean = preg_replace('/\s*-\s*$/', '', $clean);
$clean = preg_replace('/\s{2,}/', ' ', $clean);
return trim($clean);
}
// ── RÉCUPÉRATION IMAGE VIA EAN (UPCitemdb - spécialisé DVD/Blu-ray) ──
function fetchImageByEAN($ean, $pdo = null) { function fetchImageByEAN($ean, $pdo = null) {
if (empty($ean) || strlen($ean) < 10) return null; if (empty($ean) || strlen($ean) < 10) return null;
// Vérifier le cache // Vérifier le cache
if ($pdo) { if ($pdo) {
try { try {
$stmt = $pdo->prepare("SELECT image_url FROM cache_images WHERE cache_key = ? AND source = 'ean' AND created_at > ?"); $stmt = $pdo->prepare("SELECT image_url FROM cache_ean WHERE ean = ? AND created_at > ?");
$stmt->execute(['ean_' . $ean, time() - TMDB_CACHE_TTL]); $stmt->execute([$ean, time() - TMDB_CACHE_TTL]);
$row = $stmt->fetch(); $row = $stmt->fetch();
if ($row && !empty($row['image_url'])) return $row['image_url']; if ($row && !empty($row['image_url'])) return $row['image_url'];
} catch (\Exception $e) { /* ignore */ } } catch (\Exception $e) { /* ignore */ }
} }
// Open Library API (gratuit, sans clé, spécialisé dans les livres/DVD) // UPCitemdb API (gratuit, spécialisé DVD/Blu-ray/CD)
$url = "https://openlibrary.org/api/books?bibkeys=ISBN:{$ean}&jscmd=data&format=json"; $url = "https://api.upcitemdb.com/prod/trial/lookup?upc={$ean}";
$res = httpGet($url, 6); $res = httpGet($url, 6);
if ($res) {
$data = json_decode($res, true);
if (!empty($data['items'][0]['images'][0])) {
$imageUrl = $data['items'][0]['images'][0];
// Sauvegarder dans le cache
if ($pdo) {
try {
$stmt = $pdo->prepare("REPLACE INTO cache_ean (ean, image_url, source, created_at) VALUES (?, ?, 'upcitemdb', ?)");
$stmt->execute([$ean, $imageUrl, time()]);
} catch (\Exception $e) { /* ignore */ }
}
return $imageUrl;
}
}
// Fallback : Open Library (pour les livres/CD)
$url = "https://openlibrary.org/api/books?bibkeys=ISBN:{$ean}&jscmd=data&format=json";
$res = httpGet($url, 5);
if ($res) { if ($res) {
$data = json_decode($res, true); $data = json_decode($res, true);
$key = "ISBN:{$ean}"; $key = "ISBN:{$ean}";
if (isset($data[$key])) { if (isset($data[$key])) {
$cover = $data[$key]['cover'] ?? []; $cover = $data[$key]['cover'] ?? [];
$imageUrl = $cover['large'] ?? $cover['medium'] ?? $cover['small'] ?? null; $imageUrl = $cover['large'] ?? $cover['medium'] ?? $cover['small'] ?? null;
if ($imageUrl) {
// Sauvegarder dans le cache
if ($imageUrl && $pdo) {
try {
$stmt = $pdo->prepare("REPLACE INTO cache_images (cache_key, image_url, source, created_at) VALUES (?, ?, 'ean', ?)");
$stmt->execute(['ean_' . $ean, $imageUrl, time()]);
} catch (\Exception $e) { /* ignore */ }
}
return $imageUrl;
}
}
// Fallback : Google Books API
$url = "https://www.googleapis.com/books/v1/volumes?q=isbn:{$ean}&maxResults=1";
$res = httpGet($url, 6);
if ($res) {
$data = json_decode($res, true);
if (!empty($data['items'][0]['volumeInfo']['imageLinks']['thumbnail'])) {
$imageUrl = str_replace('http:', 'https:', $data['items'][0]['volumeInfo']['imageLinks']['thumbnail']);
if ($pdo) { if ($pdo) {
try { try {
$stmt = $pdo->prepare("REPLACE INTO cache_images (cache_key, image_url, source, created_at) VALUES (?, ?, 'google', ?)"); $stmt = $pdo->prepare("REPLACE INTO cache_ean (ean, image_url, source, created_at) VALUES (?, ?, 'openlibrary', ?)");
$stmt->execute(['ean_' . $ean, $imageUrl, time()]); $stmt->execute([$ean, $imageUrl, time()]);
} catch (\Exception $e) { /* ignore */ } } catch (\Exception $e) { /* ignore */ }
} }
return $imageUrl; return $imageUrl;
} }
} }
}
return null; return null;
} }
// ── RÉCUPÉRATION TMDB (avec cache) ── // ── TMDB AVEC CACHE + curl_multi ──
function fetchTmdbData($title, $year, $apiKey, $pdo = null) { function fetchTmdbData($title, $year, $apiKey, $pdo = null) {
if (empty($apiKey) || empty($title)) return null; if (empty($apiKey) || empty($title)) return null;
$cleanTitle = preg_replace('/\s*\[.*?\]\s*/', '', $title); $cleanTitle = cleanTitleForTmdb($title);
$cleanTitle = trim($cleanTitle);
$cacheKey = md5(strtolower($cleanTitle) . '|' . $year); $cacheKey = md5(strtolower($cleanTitle) . '|' . $year);
// Vérifier le cache // Vérifier le cache
if ($pdo) { if ($pdo) {
try { try {
$stmt = $pdo->prepare("SELECT image_url FROM cache_images WHERE cache_key = ? AND source = 'tmdb' AND created_at > ?"); $stmt = $pdo->prepare("SELECT data FROM cache_tmdb WHERE cache_key = ? AND created_at > ?");
$stmt->execute(['tmdb_' . $cacheKey, time() - TMDB_CACHE_TTL]); $stmt->execute([$cacheKey, time() - TMDB_CACHE_TTL]);
$row = $stmt->fetch(); $row = $stmt->fetch();
if ($row && !empty($row['image_url'])) { if ($row) return json_decode($row['data'], true);
// Récupérer aussi le directeur et streaming depuis le cache JSON
$stmt2 = $pdo->prepare("SELECT image_url FROM cache_images WHERE cache_key = ?");
$stmt2->execute(['tmdb_full_' . $cacheKey]);
$row2 = $stmt2->fetch();
if ($row2) return json_decode($row2['image_url'], true);
}
} catch (\Exception $e) { /* ignore */ } } catch (\Exception $e) { /* ignore */ }
} }
@@ -171,16 +190,49 @@ function fetchTmdbData($title, $year, $apiKey, $pdo = null) {
if (!$searchRes) return null; if (!$searchRes) return null;
$searchData = json_decode($searchRes, true); $searchData = json_decode($searchRes, true);
if (empty($searchData['results'])) {
// 2ème tentative sans l'année
$searchUrl2 = "https://api.themoviedb.org/3/search/movie?api_key={$apiKey}&query=" . urlencode($cleanTitle) . "&language=fr-FR";
$searchRes2 = httpGet($searchUrl2, 8);
if ($searchRes2) {
$searchData2 = json_decode($searchRes2, true);
if (!empty($searchData2['results'])) $searchData = $searchData2;
}
}
if (empty($searchData['results'])) return null; if (empty($searchData['results'])) return null;
$movie = $searchData['results'][0]; $movie = $searchData['results'][0];
$movieId = $movie['id']; $movieId = $movie['id'];
$poster = !empty($movie['poster_path']) ? "https://image.tmdb.org/t/p/w500" . $movie['poster_path'] : ''; $poster = !empty($movie['poster_path']) ? "https://image.tmdb.org/t/p/w500" . $movie['poster_path'] : '';
// Récupération Réalisateur // Appels parallèles avec curl_multi
$creditsUrl = "https://api.themoviedb.org/3/movie/{$movieId}/credits?api_key={$apiKey}&language=fr-FR";
$creditsRes = httpGet($creditsUrl, 8);
$director = ''; $director = '';
$streaming = '';
if (function_exists('curl_multi_init')) {
$mh = curl_multi_init();
$handles = [];
$ch1 = curl_init("https://api.themoviedb.org/3/movie/{$movieId}/credits?api_key={$apiKey}&language=fr-FR");
$ch2 = curl_init("https://api.themoviedb.org/3/movie/{$movieId}/watch/providers?api_key={$apiKey}");
curl_setopt_array($ch1, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 6, CURLOPT_SSL_VERIFYPEER => false]);
curl_setopt_array($ch2, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 6, CURLOPT_SSL_VERIFYPEER => false]);
curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);
$running = 0;
do { curl_multi_exec($mh, $running); curl_multi_select($mh); } while ($running > 0);
$creditsRes = curl_multi_getcontent($ch1);
$watchRes = curl_multi_getcontent($ch2);
curl_multi_remove_handle($mh, $ch1); curl_close($ch1);
curl_multi_remove_handle($mh, $ch2); curl_close($ch2);
curl_multi_close($mh);
if ($creditsRes) { if ($creditsRes) {
$creditsData = json_decode($creditsRes, true); $creditsData = json_decode($creditsRes, true);
if (!empty($creditsData['crew'])) { if (!empty($creditsData['crew'])) {
@@ -190,10 +242,6 @@ function fetchTmdbData($title, $year, $apiKey, $pdo = null) {
} }
} }
// Récupération Streaming (France)
$streaming = '';
$watchUrl = "https://api.themoviedb.org/3/movie/{$movieId}/watch/providers?api_key={$apiKey}";
$watchRes = httpGet($watchUrl, 8);
if ($watchRes) { if ($watchRes) {
$watchData = json_decode($watchRes, true); $watchData = json_decode($watchRes, true);
$frProviders = $watchData['results']['FR'] ?? []; $frProviders = $watchData['results']['FR'] ?? [];
@@ -205,30 +253,28 @@ function fetchTmdbData($title, $year, $apiKey, $pdo = null) {
} }
if (!empty($platforms)) $streaming = implode(', ', array_unique($platforms)); if (!empty($platforms)) $streaming = implode(', ', array_unique($platforms));
} }
}
$result = ['director' => $director, 'poster' => $poster, 'streaming' => $streaming]; $result = ['director' => $director, 'poster' => $poster, 'streaming' => $streaming];
// Sauvegarder dans le cache // Sauvegarder dans le cache
if ($pdo) { if ($pdo) {
try { try {
$stmt = $pdo->prepare("REPLACE INTO cache_images (cache_key, image_url, source, created_at) VALUES (?, ?, 'tmdb', ?)"); $stmt = $pdo->prepare("REPLACE INTO cache_tmdb (cache_key, data, created_at) VALUES (?, ?, ?)");
$stmt->execute(['tmdb_' . $cacheKey, $poster, time()]); $stmt->execute([$cacheKey, json_encode($result), time()]);
$stmt2 = $pdo->prepare("REPLACE INTO cache_images (cache_key, image_url, source, created_at) VALUES (?, ?, 'tmdb_full', ?)");
$stmt2->execute(['tmdb_full_' . $cacheKey, json_encode($result), time()]);
} catch (\Exception $e) { /* ignore */ } } catch (\Exception $e) { /* ignore */ }
} }
return $result; return $result;
} }
// ── Détection format ──
function detectFormat($title, $description = '') { function detectFormat($title, $description = '') {
$t = strtoupper($title . ' ' . $description); $t = strtoupper($title . ' ' . $description);
if (strpos($t, '4K') !== false || strpos($t, 'UHD') !== false) return 'Blu-ray 4K'; if (strpos($t, '4K') !== false || strpos($t, 'UHD') !== false) return 'Blu-ray 4K';
if (strpos($t, 'BLU-RAY') !== false || strpos($t, 'BLURAY') !== false || strpos($t, 'BLU-RAY') !== false) return 'Blu-ray'; if (strpos($t, 'BLU-RAY') !== false || strpos($t, 'BLURAY') !== false) return 'Blu-ray';
if (strpos($t, 'DVD') !== false) return 'DVD'; if (strpos($t, 'DVD') !== false) return 'DVD';
if (strpos($t, 'VHS') !== false) return 'VHS'; if (strpos($t, 'VHS') !== false) return 'VHS';
if (strpos($t, 'COFFRET') !== false || strpos($t, 'TRILOGIE') !== false || strpos($t, 'INTEGRALE') !== false) return 'Coffret'; if (strpos($t, 'COFFRET') !== false || strpos($t, 'TRILOGIE') !== false) return 'Coffret';
return 'DVD'; return 'DVD';
} }
@@ -321,25 +367,22 @@ switch ($action) {
else { http_response_code(400); echo json_encode(["success" => false, "error" => "Aucun élément sélectionné."]); } else { http_response_code(400); echo json_encode(["success" => false, "error" => "Aucun élément sélectionné."]); }
break; break;
// ── IMPORT PAR LOTS AVEC RÉCUPÉRATION JAQUETTES ─
case 'import_batch': case 'import_batch':
checkAuth($pdo); checkAuth($pdo);
$items = $data['items'] ?? []; $items = $data['items'] ?? [];
$type = $data['type'] ?? 'videotheque'; $type = $data['type'] ?? 'videotheque';
$tmdbApiKey = getTmdbApiKey($pdo); $tmdbApiKey = getTmdbApiKey($pdo);
$imported = 0; $imported = 0;
$stats = ['ean_hits' => 0, 'tmdb_hits' => 0, 'no_image' => 0]; $stats = ['ean_hits' => 0, 'tmdb_hits' => 0, 'no_image' => 0, 'ean_miss' => [], 'tmdb_miss' => []];
$pdo->beginTransaction(); $pdo->beginTransaction();
foreach ($items as $rowData) { foreach ($items as $rowData) {
// ── MAPPING EXACT DES COLONNES DE VOTRE CSV ──
$title = $rowData['title'] ?? $rowData['Name'] ?? 'Sans titre'; $title = $rowData['title'] ?? $rowData['Name'] ?? 'Sans titre';
$firstName = $rowData['first_name'] ?? ''; $firstName = $rowData['first_name'] ?? '';
$lastName = $rowData['last_name'] ?? ''; $lastName = $rowData['last_name'] ?? '';
$creators = $rowData['creators'] ?? ''; $creators = $rowData['creators'] ?? '';
// Réalisateur : priorité first_name + last_name, sinon creators
$director = ''; $director = '';
if (!empty($firstName) && !empty($lastName)) { if (!empty($firstName) && !empty($lastName)) {
$director = trim("$firstName $lastName"); $director = trim("$firstName $lastName");
@@ -347,7 +390,6 @@ switch ($action) {
$director = $creators; $director = $creators;
} }
// Année depuis publish_date
$publishDate = $rowData['publish_date'] ?? $rowData['Year'] ?? $rowData['year'] ?? ''; $publishDate = $rowData['publish_date'] ?? $rowData['Year'] ?? $rowData['year'] ?? '';
$year = extractYear($publishDate); $year = extractYear($publishDate);
@@ -355,33 +397,36 @@ switch ($action) {
$description = $rowData['description'] ?? $rowData['Description'] ?? ''; $description = $rowData['description'] ?? $rowData['Description'] ?? '';
$publisher = $rowData['publisher'] ?? $rowData['Publisher'] ?? ''; $publisher = $rowData['publisher'] ?? $rowData['Publisher'] ?? '';
$length = $rowData['length'] ?? $rowData['Length'] ?? ''; $length = $rowData['length'] ?? $rowData['Length'] ?? '';
$discs = $rowData['number_of_discs'] ?? $rowData['Number of Discs'] ?? 1; $discs = $rowData['number_of_discs'] ?? 1;
$aspect = $rowData['aspect_ratio'] ?? $rowData['Aspect Ratio'] ?? ''; $aspect = $rowData['aspect_ratio'] ?? '';
$format = $rowData['format'] ?? $rowData['Format'] ?? detectFormat($title, $description); $format = $rowData['format'] ?? $rowData['Format'] ?? detectFormat($title, $description);
// ── RÉCUPÉRATION IMAGE : PRIORITÉ EAN (jaquette physique) ──
$poster = $rowData['poster'] ?? $rowData['Poster'] ?? $rowData['image'] ?? ''; $poster = $rowData['poster'] ?? $rowData['Poster'] ?? $rowData['image'] ?? '';
$imageSource = 'none';
// 1. Priorité : EAN via UPCitemdb
if (empty($poster) && !empty($ean)) { if (empty($poster) && !empty($ean)) {
$eanImage = fetchImageByEAN($ean, $pdo); $eanImage = fetchImageByEAN($ean, $pdo);
if ($eanImage) { if ($eanImage) {
$poster = $eanImage; $poster = $eanImage;
$imageSource = 'ean';
$stats['ean_hits']++; $stats['ean_hits']++;
} else {
$stats['ean_miss'][] = $ean;
} }
} }
// ── FALLBACK TMDB (affiche du film) ── // 2. Fallback : TMDB avec titre nettoyé
if (empty($poster) && $tmdbApiKey) { if (empty($poster) && $tmdbApiKey) {
$tmdbData = fetchTmdbData($title, $year, $tmdbApiKey, $pdo); $tmdbData = fetchTmdbData($title, $year, $tmdbApiKey, $pdo);
if ($tmdbData) { if ($tmdbData) {
if (empty($director)) $director = $tmdbData['director']; if (empty($director)) $director = $tmdbData['director'];
if (!empty($tmdbData['poster'])) { if (!empty($tmdbData['poster'])) {
$poster = $tmdbData['poster']; $poster = $tmdbData['poster'];
$imageSource = 'tmdb';
$stats['tmdb_hits']++; $stats['tmdb_hits']++;
} else {
$stats['tmdb_miss'][] = $title;
} }
} else {
$stats['tmdb_miss'][] = $title;
} }
} }