PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]); $pdo->exec("CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, password_hash VARCHAR(255) NOT NULL)"); $pdo->exec("CREATE TABLE IF NOT EXISTS config (key_name VARCHAR(50) PRIMARY KEY, key_value TEXT NOT NULL)"); $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))"); try { $pdo->exec("ALTER TABLE critiques MODIFY COLUMN rating DECIMAL(3,1) DEFAULT 3.0"); } catch (\Exception $e) {} $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, actors TEXT)"); try { $pdo->exec("ALTER TABLE videotheque ADD COLUMN actors TEXT AFTER description"); } catch (\Exception $e) {} } catch (\PDOException $e) { echo json_encode(["error" => "Erreur BDD : " . $e->getMessage()]); exit; } // ── FONCTIONS UTILITAIRES ── function makeStableId($type, $title, $year) { return (abs(crc32(strtolower(trim($type ?? '')) . '|' . strtolower(trim($title ?? '')) . '|' . trim($year ?? ''))) % 2000000000) + 100000000; } function checkAuth($pdo) { if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) return true; $token = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; if (empty($token) && function_exists('apache_request_headers')) { $headers = apache_request_headers(); $token = $headers['Authorization'] ?? $headers['authorization'] ?? ''; } if ($token !== md5(ENCRYPTION_KEY . 'session')) { http_response_code(403); echo json_encode(["error" => "Accès interdit."]); exit; } } function encryptData($data) { $iv = openssl_random_pseudo_bytes(16); $key = hash('sha256', ENCRYPTION_KEY, true); return base64_encode(openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv) . '::' . $iv); } function decryptData($str) { $decoded = base64_decode($str); if (strpos($decoded, '::') === false) return null; list($enc, $iv) = explode('::', $decoded, 2); return openssl_decrypt($enc, 'AES-256-CBC', hash('sha256', ENCRYPTION_KEY, true), OPENSSL_RAW_DATA, substr($iv, 0, 16)); } function getTmdbApiKey($pdo) { $stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = 'tmdb_api_key'"); $stmt->execute(); $row = $stmt->fetch(); return $row ? decryptData($row['key_value']) : null; } function httpGet($url, $timeout = 3, $ua = null) { if (!$ua) $ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; if (!function_exists('curl_init')) { $ctx = stream_context_create(['http' => [ 'timeout' => $timeout, 'user_agent' => $ua, 'header' => "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: fr-FR,fr;q=0.9\r\n" ]]); return @file_get_contents($url, false, $ctx); } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $timeout, CURLOPT_CONNECTTIMEOUT => 3, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => $ua, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTPHEADER => [ 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language: fr-FR,fr;q=0.9,en;q=0.8', 'Accept-Encoding: identity', 'Connection: keep-alive', ], ]); $res = curl_exec($ch); curl_close($ch); return $res ?: null; } 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); $clean = preg_replace('/(blu-ray|bluray|dvd|4k|ultra hd|combo|vhs|bdrip).*$/i', '', $clean); return trim(preg_replace('/\s{2,}/', ' ', $clean)); } function detectFormat($title, $desc = '') { $t = strtoupper($title . ' ' . $desc); if (strpos($t, '4K') !== false || strpos($t, 'UHD') !== false) return '4K Ultra HD'; if (strpos($t, 'BLU-RAY') !== false || strpos($t, 'BLURAY') !== false) return 'Blu-ray'; if (strpos($t, 'DVD') !== false) return 'DVD'; if (strpos($t, 'VHS') !== false) return 'VHS'; if (strpos($t, 'COFFRET') !== false || strpos($t, 'TRILOGIE') !== false) return 'Coffret'; return 'Blu-ray'; } function extractYear($dateStr) { if (preg_match('/(\d{4})/', $dateStr, $m)) return $m[1]; return ''; } // ── DVDcover.com (scraping précis) ── function fetchDVDCover($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); // Mapping des formats $formatMap = [ '4k ultra hd' => '4k-ultra-hd', '4k' => '4k-ultra-hd', 'blu-ray' => 'blu-ray', 'bluray' => 'blu-ray', 'dvd' => 'dvd', ]; $dcFormat = $formatMap[strtolower($format)] ?? 'blu-ray'; // URL de recherche DVDcover $searchUrl = "https://www.dvdcover.com/?s=" . urlencode($cleanTitle); $html = httpGet($searchUrl, 8, $ua); if (!$html) { error_log("DVDCover: Échec recherche pour '$title'"); return null; } $result = [ 'poster' => '', 'title' => '', 'format' => $format, ]; // DVDcover utilise WordPress - les articles ont la classe "post" // Chercher les liens vers les pages individuelles de covers preg_match_all('/]*class=["\'][^"\']*post[^"\']*["\'][^>]*>.*?]+href=["\']([^"\']+)["\'][^>]*>.*?<\/article>/is', $html, $articles); if (!empty($articles[1])) { // Parcourir les résultats foreach ($articles[1] as $coverPage) { if (strpos($coverPage, 'http') !== 0) { $coverPage = 'https://www.dvdcover.com' . $coverPage; } $coverHtml = httpGet($coverPage, 8, $ua); if (!$coverHtml) continue; // Méthode 1 : Chercher l'image principale dans le contenu de l'article // DVDcover met les jaquettes dans
ou
if (preg_match('/]*>.*?]+src=["\']([^"\']+)["\'][^>]*>/is', $coverHtml, $m)) { $img = $m[1]; // Vérifier que c'est bien une jaquette (pas une image de thème) if (strpos($img, '/wp-content/uploads/') !== false && strpos($img, '/wp-content/themes/') === false && (strpos($img, '.jpg') !== false || strpos($img, '.jpeg') !== false || strpos($img, '.webp') !== false)) { $result['poster'] = $img; } } // Méthode 2 : Chercher dans entry-content if (empty($result['poster']) && preg_match('/]*class=["\'][^"\']*entry-content[^"\']*["\'][^>]*>.*?]+src=["\']([^"\']+)["\'][^>]*>/is', $coverHtml, $m)) { $img = $m[1]; if (strpos($img, '/wp-content/uploads/') !== false && strpos($img, '/wp-content/themes/') === false) { $result['poster'] = $img; } } // Méthode 3 : Chercher toutes les images et prendre la plus grande if (empty($result['poster'])) { preg_match_all('/]+src=["\']([^"\']+\/wp-content\/uploads\/[^"\']+\.(?:jpg|jpeg|webp))["\'][^>]*>/i', $coverHtml, $allImages); if (!empty($allImages[1])) { // Prendre la première image qui n'est pas un logo ou icône foreach ($allImages[1] as $img) { if (strpos($img, 'logo') === false && strpos($img, 'icon') === false && strpos($img, 'banner') === false && strpos($img, 'bg') === false && strpos($img, 'button') === false) { $result['poster'] = $img; break; } } } } // Extraire le titre if (preg_match('/]*class=["\'][^"\']*entry-title[^"\']*["\'][^>]*>([^<]+)<\/h1>/i', $coverHtml, $titleMatch)) { $result['title'] = trim(strip_tags($titleMatch[1])); } elseif (preg_match('/]*>([^<]+)<\/title>/i', $coverHtml, $titleMatch)) { $result['title'] = trim(strip_tags($titleMatch[1])); } // Si on a trouvé une image, on arrête if (!empty($result['poster'])) { break; } } } // Fallback : chercher directement dans la page de résultats if (empty($result['poster'])) { preg_match_all('/]+src=["\']([^"\']+\/wp-content\/uploads\/[^"\']+\.(?:jpg|jpeg|webp))["\'][^>]*>/i', $html, $allImages); if (!empty($allImages[1])) { foreach ($allImages[1] as $img) { if (strpos($img, 'logo') === false && strpos($img, 'icon') === false && strpos($img, 'banner') === false && strpos($img, 'bg') === false) { $result['poster'] = $img; break; } } } } 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); $searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$apiKey}&query=" . urlencode($cleanTitle) . "&year={$year}&language=fr-FR"; $searchRes = httpGet($searchUrl, 5); $searchData = $searchRes ? json_decode($searchRes, true) : []; if (empty($searchData['results'])) { $searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$apiKey}&query=" . urlencode($cleanTitle) . "&language=fr-FR"; $searchRes = httpGet($searchUrl, 5); $searchData = $searchRes ? json_decode($searchRes, true) : []; } if (empty($searchData['results'])) return null; $movieId = $searchData['results'][0]['id']; $detailsUrl = "https://api.themoviedb.org/3/movie/{$movieId}?api_key={$apiKey}&append_to_response=credits,watch/providers,translations&language=fr-FR"; $detailsRes = httpGet($detailsUrl, 5); if (!$detailsRes) return null; $details = json_decode($detailsRes, true); $frenchTitle = $details['title'] ?? ''; if (!empty($details['translations']['translations'])) { foreach ($details['translations']['translations'] as $translation) { if ($translation['iso_3166_1'] === 'FR' && !empty($translation['data']['title'])) { $frenchTitle = $translation['data']['title']; break; } } } $director = ''; if (!empty($details['credits']['crew'])) { $directorsList = []; foreach ($details['credits']['crew'] as $crew) { if ($crew['job'] === 'Director') $directorsList[] = $crew['name']; } $director = implode(', ', $directorsList); } $cast = []; if (!empty($details['credits']['cast'])) { $topCast = array_slice($details['credits']['cast'], 0, 4); foreach ($topCast as $actor) $cast[] = $actor['name']; } $overview = $details['overview'] ?? ''; $streaming = ''; $frProviders = $details['watch/providers']['results']['FR'] ?? []; $platforms = []; if (!empty($frProviders['flatrate'])) { foreach ($frProviders['flatrate'] as $p) $platforms[] = $p['provider_name']; } if (empty($platforms)) { if (!empty($frProviders['rent'])) { foreach ($frProviders['rent'] as $p) $platforms[] = $p['provider_name'] . ' (loc.)'; } if (!empty($frProviders['buy'])) { foreach ($frProviders['buy'] as $p) $platforms[] = $p['provider_name'] . ' (achat)'; } } if (!empty($platforms)) $streaming = implode(', ', array_unique($platforms)); $result = [ 'title' => $frenchTitle, 'year' => !empty($details['release_date']) ? substr($details['release_date'], 0, 4) : '', 'director' => $director, 'poster' => !empty($details['poster_path']) ? "https://image.tmdb.org/t/p/w500" . $details['poster_path'] : '', 'length' => !empty($details['runtime']) ? $details['runtime'] . ' min' : '', 'streaming' => $streaming, 'overview' => $overview, 'cast' => $cast ]; return $result; } // ── ROUTEUR PRINCIPAL ── $action = $_GET['action'] ?? ''; $data = json_decode(file_get_contents('php://input'), true) ?? []; switch ($action) { case 'check_security_status': echo json_encode(["is_blank" => ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0)]); break; case 'login': if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) { echo json_encode(["success" => true, "token" => md5(ENCRYPTION_KEY . 'session'), "blank" => true]); } else { $stmt = $pdo->prepare("SELECT password_hash FROM users WHERE username = 'admin'"); $stmt->execute(); $user = $stmt->fetch(); if ($user && password_verify($data['password'] ?? '', $user['password_hash'])) { echo json_encode(["success" => true, "token" => md5(ENCRYPTION_KEY . 'session'), "blank" => false]); } else { http_response_code(401); echo json_encode(["error" => "Mot de passe incorrect."]); } } break; case 'setup_admin': case 'update_password': checkAuth($pdo); $pwd = $data['password'] ?? $data['new_password'] ?? ''; $stmt = $pdo->prepare("REPLACE INTO users (id, username, password_hash) VALUES (1, 'admin', :pass)"); $stmt->execute([':pass' => password_hash($pwd, PASSWORD_BCRYPT)]); echo json_encode(["success" => true]); break; case 'get_config_keys': checkAuth($pdo); $stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = 'tmdb_api_key'"); $stmt->execute(); $row = $stmt->fetch(); echo json_encode(['tmdb_api_key' => $row ? '••••••••' : '']); break; case 'save_config': checkAuth($pdo); $keyName = $data['key_name'] ?? ''; $keyValue = $data['key_value'] ?? ''; if ($keyName === 'tmdb_api_key' && !empty($keyValue)) { $stmt = $pdo->prepare("REPLACE INTO config (key_name, key_value) VALUES (?, ?)"); $stmt->execute([$keyName, encryptData($keyValue)]); echo json_encode(["success" => true]); } else { http_response_code(400); echo json_encode(["error" => "Données invalides."]); } break; case 'get_films': $sql = " SELECT id, title, year, director, poster, rating, review, NULL AS description, streaming, 'critique' AS type FROM critiques UNION ALL SELECT id, title, year, director, poster, NULL AS rating, NULL AS review, description, NULL AS streaming, 'videotheque' AS type FROM videotheque ORDER BY id DESC "; $result = $pdo->query($sql)->fetchAll(); echo json_encode($result); break; 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' => '', 'poster' => '', 'publisher' => '', 'format' => '', 'length' => '', 'number_of_discs' => 1, 'aspect_ratio' => '', 'actors' => '' ]; // Pour DVDcover, on a besoin d'un titre $tmdbKey = getTmdbApiKey($pdo); $titleForSearch = ''; if ($tmdbKey) { // Essayer de trouver via TMDB d'abord $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); } } } if (empty($titleForSearch)) { echo json_encode(['success' => true, 'data' => $result, 'warning' => 'Titre non trouvé']); exit; } // Récupération jaquette if ($type === 'videotheque') { // DVDcover pour vidéothèque $format = $result['format'] ?: 'Blu-ray'; $dcData = fetchDVDCover($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 { // TMDB pour critiques 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']); } } } echo json_encode(['success' => true, 'data' => $result]); break; case 'save_film': checkAuth($pdo); $type = $data['type'] ?? 'critique'; $id = !empty($data['id']) ? $data['id'] : makeStableId($type, $data['title'] ?? '', $data['year'] ?? '0000'); // 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']; if (empty($data['poster'])) $data['poster'] = $tmdbData['poster']; if (empty($data['length']) && !empty($tmdbData['length'])) $data['length'] = $tmdbData['length']; } } if ($type === 'critique') { $streaming = $data['streaming'] ?? ''; if (empty($streaming)) $streaming = 'Support physique / Cinéma'; $sql = "INSERT INTO critiques (id, title, year, director, poster, rating, review, streaming) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title=VALUES(title), year=VALUES(year), director=VALUES(director), poster=VALUES(poster), rating=VALUES(rating), review=VALUES(review), streaming=VALUES(streaming)"; $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 : DVDcover pour la jaquette if (empty($data['poster']) && !empty($data['title'])) { $format = $data['format'] ?: 'Blu-ray'; $dcData = fetchDVDCover($data['title'], $data['year'] ?? '', $format); if (!empty($dcData['poster'])) { $data['poster'] = $dcData['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'] ?? '']); } echo json_encode(["success" => true]); break; case 'delete_film': checkAuth($pdo); $type = $_GET['type'] ?? 'critique'; $table = ($type === 'videotheque') ? 'videotheque' : 'critiques'; $id = $_GET['id'] ?? null; if (!$id) { http_response_code(400); echo json_encode(["error" => "ID manquant."]); break; } $stmt = $pdo->prepare("DELETE FROM $table WHERE id = ?"); $stmt->execute([$id]); echo json_encode(["success" => true]); break; case 'bulk_delete': checkAuth($pdo); $ids = $data['ids'] ?? []; $type = $data['type'] ?? 'critique'; $table = ($type === 'videotheque') ? 'videotheque' : 'critiques'; if (!empty($ids)) { $placeholders = implode(',', array_fill(0, count($ids), '?')); $stmt = $pdo->prepare("DELETE FROM $table WHERE id IN ($placeholders)"); $stmt->execute($ids); echo json_encode(["success" => true]); } else { http_response_code(400); echo json_encode(["success" => false, "error" => "Aucun élément sélectionné."]); } break; case 'import_batch': checkAuth($pdo); set_time_limit(0); $items = $data['items'] ?? []; $type = $data['type'] ?? 'videotheque'; $tmdbApiKey = getTmdbApiKey($pdo); $imported = 0; $debugLog = []; $pdo->beginTransaction(); try { foreach ($items as $rowData) { $title = $rowData['title'] ?? $rowData['Name'] ?? $rowData['Title'] ?? 'Sans titre'; $publishDate = $rowData['publish_date'] ?? $rowData['Year'] ?? $rowData['year'] ?? $rowData['Date'] ?? ''; $year = extractYear($publishDate); $id = makeStableId($type, $title, $year); if ($type === 'videotheque') { // ── VIDÉOTHÈQUE : DVDcover pour jaquette HD ── $csvActors = $rowData['ensemble'] ?? $rowData['creators'] ?? ''; $actors = ''; if (!empty($csvActors)) { $actorsArray = array_map('trim', explode(',', $csvActors)); $actors = implode(', ', array_slice($actorsArray, 0, 4)); } $ean = $rowData['ean_isbn13'] ?? $rowData['EAN'] ?? ''; if (!empty($ean)) { $eanFloat = floatval($ean); if ($eanFloat > 0) $ean = (string) round($eanFloat); $ean = preg_replace('/[^0-9]/', '', $ean); } $lengthRaw = $rowData['length'] ?? ''; $length = ''; if ($lengthRaw !== '' && $lengthRaw !== null) { $lengthVal = floatval($lengthRaw); if ($lengthVal > 0) $length = (string) round($lengthVal); } $discsRaw = $rowData['number_of_discs'] ?? ''; $discs = (is_numeric($discsRaw) && floatval($discsRaw) > 0) ? (int) round(floatval($discsRaw)) : 1; $description = $rowData['description'] ?? $rowData['Description'] ?? ''; $publisher = $rowData['publisher'] ?? ''; $aspect = $rowData['aspect_ratio'] ?? ''; $format = $rowData['format'] ?? detectFormat($title, $description); $poster = $rowData['poster'] ?? ''; $director = ''; // Récupération jaquette via DVDcover $cleanTitleForDC = cleanTitle($title); if (!empty($cleanTitleForDC)) { $dcData = fetchDVDCover($cleanTitleForDC, $year, $format); if (!empty($dcData)) { if (!empty($dcData['poster'])) { $poster = $dcData['poster']; } if (!empty($dcData['title']) && ($title === 'Sans titre' || empty($title))) { $title = $dcData['title']; } } } // PAS DE TMDB POUR 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 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, $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'] ?? ''; $director = ''; $poster = ''; $streaming = ''; if ($tmdbApiKey && !empty($title)) { $tmdbData = fetchTMDBFull($title, $year, $tmdbApiKey, $pdo); if ($tmdbData) { $director = $tmdbData['director']; $poster = $tmdbData['poster']; $streaming = $tmdbData['streaming']; if (empty($year)) $year = $tmdbData['year']; if (!empty($tmdbData['title'])) $title = $tmdbData['title']; } } if (empty($streaming)) $streaming = 'Support physique / Cinéma'; $sql = "INSERT INTO critiques (id, title, year, director, poster, rating, review, streaming) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title=VALUES(title), year=VALUES(year), rating=VALUES(rating), review=IF(VALUES(review)!='',VALUES(review),review), director=IF(VALUES(director)!='',VALUES(director),director), poster=IF(VALUES(poster)!='',VALUES(poster),poster), streaming=IF(VALUES(streaming)!='',VALUES(streaming),streaming)"; $stmt = $pdo->prepare($sql); $stmt->execute([$id, $title, $year, $director, $poster, $rating, $review, $streaming]); } $imported++; } $pdo->commit(); echo json_encode(["success" => true, "imported" => $imported, "debug" => $debugLog]); } catch (\Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } http_response_code(500); echo json_encode(["success" => false, "error" => "Erreur serveur : " . $e->getMessage(), "debug" => $debugLog]); } break; case 'debug_dvdcover': $title = $_GET['title'] ?? ''; $year = $_GET['year'] ?? ''; $format = $_GET['format'] ?? 'Blu-ray'; if (!$title) { echo json_encode(['error' => 'Titre manquant']); exit; } $result = ['title' => $title, 'year' => $year, 'format' => $format]; $data = fetchDVDCover($title, $year, $format); $result['data'] = $data; $result['status'] = $data ? 'OK' : 'AUCUN_RÉSULTAT'; echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); exit; break; } ?>