diff --git a/api.php b/api.php index 56b377e..222228e 100644 --- a/api.php +++ b/api.php @@ -139,97 +139,75 @@ function emptyPhysicalResult() { ]; } +// ── FONCTIONS API PHYSIQUE (UPCitemdb → UPCMDB fallback) ── function throttleUpcLookup() { - static $lastLookupAt = 0; - $elapsed = microtime(true) - $lastLookupAt; - if ($lastLookupAt > 0 && $elapsed < 2) { - usleep((int)((2 - $elapsed) * 1000000)); - } - $lastLookupAt = microtime(true); + static $last = 0; + $elapsed = microtime(true) - $last; + if ($last > 0 && $elapsed < 2) usleep((int)((2 - $elapsed) * 1000000)); + $last = microtime(true); } -// ── API GRATUITE UPCitemdb : métadonnées physiques via EAN/UPC ── function fetchPhysicalFromUpcitemdb($ean) { - $empty = emptyPhysicalResult(); + $empty = ['title'=>'','publisher'=>'','format'=>'','length'=>'','number_of_discs'=>1,'aspect_ratio'=>'','year'=>'']; $ean = preg_replace('/[^0-9]/', '', (string)$ean); if (strlen($ean) < 8) return $empty; - throttleUpcLookup(); - - $url = 'https://api.upcitemdb.com/prod/trial/lookup?upc=' . urlencode($ean); - $res = httpGet($url, 10, 'MonPetitCinema/1.0'); + $res = httpGet("https://api.upcitemdb.com/prod/trial/lookup?upc=" . urlencode($ean), 10); if (!$res) return $empty; - $data = json_decode($res, true); if (empty($data['items'][0])) return $empty; - + $item = $data['items'][0]; - $rawTitle = $item['title'] ?? ''; - $title = cleanUpcTitle($rawTitle) ?: trim($rawTitle); - $publisher = trim($item['brand'] ?? ''); - + $raw = $item['title'] ?? ''; + $clean = cleanUpcTitle($raw) ?: $raw; + return [ - 'title' => $title, - 'publisher' => $publisher, - 'format' => detectFormat($rawTitle), - 'length' => '', - 'number_of_discs' => parseDiscCountFromTitle($rawTitle), - 'aspect_ratio' => '', - 'year' => '' + 'title' => $clean, + 'publisher' => trim($item['brand'] ?? $item['manufacturer'] ?? ''), + 'format' => detectFormat($raw), + 'number_of_discs' => parseDiscCountFromTitle($raw), + 'aspect_ratio' => '', + 'year' => '', + 'length' => '' ]; } - -// ── API UPCMDB (clé gratuite) : fallback spécialisé DVD/Blu-ray/4K ── -function fetchPhysicalFromUpcmdb($ean, $pdo) { - $empty = emptyPhysicalResult(); +unction fetchPhysicalFromUpcmdb($ean, $pdo) { + $empty = ['title'=>'','publisher'=>'','format'=>'','length'=>'','number_of_discs'=>1,'aspect_ratio'=>'','year'=>'']; $apiKey = getUpcmdbApiKey($pdo); if (!$apiKey) return $empty; - $ean = preg_replace('/[^0-9]/', '', (string)$ean); if (strlen($ean) < 8) return $empty; - - $url = 'https://upcmdb.com/api/v1/lookup/' . urlencode($ean); - $res = httpGet($url, 10, 'MonPetitCinema/1.0', [ - 'Accept: application/json', - 'X-API-Key: ' . $apiKey - ]); + + $url = "https://upcmdb.com/api/v1/lookup/" . urlencode($ean); + $res = httpGet($url, 10, 'MonPetitCinema/1.0', ['Accept: application/json', 'X-API-Key: ' . $apiKey]); if (!$res || $res[0] === '<') return $empty; - $data = json_decode($res, true); if (($data['status'] ?? '') !== 'success' || empty($data['data'])) return $empty; - + $item = $data['data']; - $rawTitle = trim($item['title'] ?? ''); - if ($rawTitle === '') return $empty; - - $title = cleanUpcTitle($rawTitle) ?: $rawTitle; - $format = trim($item['format'] ?? ''); - $runtime = trim($item['runtime'] ?? ''); - + $raw = trim($item['title'] ?? ''); + if (!$raw) return $empty; + return [ - 'title' => $title, - 'publisher' => trim($item['studio'] ?? $item['publisher'] ?? ''), - 'format' => $format !== '' ? $format : detectFormat($rawTitle), - 'length' => $runtime, - 'number_of_discs' => parseDiscCountFromTitle($rawTitle), - 'aspect_ratio' => trim($item['aspect_ratio'] ?? ''), - 'year' => trim($item['year'] ?? '') + 'title' => cleanUpcTitle($raw) ?: $raw, + 'publisher' => trim($item['studio'] ?? $item['publisher'] ?? ''), + 'format' => trim($item['format'] ?: detectFormat($raw)), + 'length' => trim($item['runtime'] ?? ''), + 'number_of_discs' => (int)($item['discs'] ?? parseDiscCountFromTitle($raw)) ?: 1, + 'aspect_ratio' => trim($item['aspect_ratio'] ?? ''), + 'year' => trim($item['year'] ?? '') ]; } + function fetchPhysicalByEan($ean, $pdo = null) { - $result = fetchPhysicalFromUpcitemdb($ean); - if (!empty($result['title'])) return $result; - + $res = fetchPhysicalFromUpcitemdb($ean); + if (!empty($res['title'])) return $res; if ($pdo) { - $fallback = fetchPhysicalFromUpcmdb($ean, $pdo); - if (!empty($fallback['title'])) { - error_log("UPCMDB: ✅ Fallback trouvé pour EAN {$ean} → {$fallback['title']}"); - return $fallback; - } + $fb = fetchPhysicalFromUpcmdb($ean, $pdo); + if (!empty($fb['title'])) return $fb; } - - return $result; + return $res; } // ── FONCTION POUR RÉCUPÉRER LES AFFICHES DEPUIS TMDB ── @@ -313,82 +291,51 @@ function fetchTmdbSynopsis($title, $year = '', $pdo = null) { return ''; } -// ── FONCTION COMPLÈTE TMDB POUR POSTER + SYNOPSIS ── +// ── FONCTION TMDB COMPLÈTE (Affiche + Métadonnées) ── function fetchTmdbPosterAndSynopsis($title, $year = '', $pdo = null) { - $defaultPoster = 'assets/img/default_physical_media.jpg'; + $default = ['poster'=>'assets/img/default_physical_media.jpg','title'=>'','description'=>'','director'=>'','actors'=>'','length'=>'','year'=>'']; + if (empty($title)) return $default; + $tmdbKey = getTmdbApiKey($pdo); + if (!$tmdbKey) return $default; - $result = [ - 'poster' => $defaultPoster, - 'description' => '', - 'director' => '', - 'actors' => '', - 'length' => '', - 'year' => $year - ]; + $clean = cleanTitle($title); + $searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$tmdbKey}&query=" . urlencode($clean) . "&language=fr-FR"; + if ($year) $searchUrl .= "&year={$year}"; - if (!$tmdbKey || empty($title)) return $result; + $res = httpGet($searchUrl, 5); + $data = $res ? json_decode($res, true) : []; - $cleanTitle = cleanTitle($title); - $searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$tmdbKey}&query=" . urlencode($cleanTitle); - if (!empty($year)) $searchUrl .= "&year={$year}"; - $searchUrl .= "&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={$tmdbKey}&query=" . urlencode($cleanTitle) . "&language=fr-FR"; - $searchRes = httpGet($searchUrl, 5); - $searchData = $searchRes ? json_decode($searchRes, true) : []; + // Retry sans année si échec + if (empty($data['results']) && $year) { + $res = httpGet("https://api.themoviedb.org/3/search/movie?api_key={$tmdbKey}&query=" . urlencode($clean) . "&language=fr-FR", 5); + $data = $res ? json_decode($res, true) : []; } + if (empty($data['results'])) return $default; - if (empty($searchData['results'])) return $result; + $movie = $data['results'][0]; + $movieId = $movie['id']; + $default['poster'] = !empty($movie['poster_path']) ? "https://image.tmdb.org/t/p/w500{$movie['poster_path']}" : $default['poster']; + $default['year'] = !empty($movie['release_date']) ? substr($movie['release_date'], 0, 4) : $year; + $default['description'] = $movie['overview'] ?? ''; + $default['title'] = $clean; - $movieId = $searchData['results'][0]['id']; - $result['poster'] = !empty($searchData['results'][0]['poster_path']) - ? "https://image.tmdb.org/t/p/w500" . $searchData['results'][0]['poster_path'] - : $defaultPoster; - $result['description'] = $searchData['results'][0]['overview'] ?? ''; - - if (!empty($searchData['results'][0]['release_date'])) { - $result['year'] = substr($searchData['results'][0]['release_date'], 0, 4); - } - - // Récupérer les détails pour réalisateur, acteurs, durée + // Détails supplémentaires (réalisateur, acteurs, durée) $detailsUrl = "https://api.themoviedb.org/3/movie/{$movieId}?api_key={$tmdbKey}&append_to_response=credits&language=fr-FR"; - $detailsRes = httpGet($detailsUrl, 5); - if ($detailsRes) { - $details = json_decode($detailsRes, true); + $detRes = httpGet($detailsUrl, 5); + if ($detRes) { + $det = json_decode($detRes, true); + $default['length'] = !empty($det['runtime']) ? "{$det['runtime']} min" : ''; - if (!empty($details['runtime'])) { - $result['length'] = $details['runtime'] . ' min'; + if (!empty($det['credits']['crew'])) { + $dirs = array_filter($det['credits']['crew'], fn($c) => $c['job'] === 'Director'); + $default['director'] = $dirs ? implode(', ', array_map(fn($c) => $c['name'], array_slice($dirs, 0, 2))) : ''; } - - // Réalisateur - if (!empty($details['credits']['crew'])) { - $directors = []; - foreach ($details['credits']['crew'] as $crew) { - if ($crew['job'] === 'Director') $directors[] = $crew['name']; - } - if (!empty($directors)) $result['director'] = implode(', ', $directors); - } - - // Acteurs (top 4) - if (!empty($details['credits']['cast'])) { - $actors = []; - $topCast = array_slice($details['credits']['cast'], 0, 4); - foreach ($topCast as $actor) $actors[] = $actor['name']; - if (!empty($actors)) $result['actors'] = implode(', ', $actors); - } - - // Synopsis complet si pas trouvé dans la recherche - if (empty($result['description']) && !empty($details['overview'])) { - $result['description'] = $details['overview']; + if (!empty($det['credits']['cast'])) { + $default['actors'] = implode(', ', array_map(fn($c) => $c['name'], array_slice($det['credits']['cast'], 0, 5))); } } - - return $result; + return $default; } // ── ROUTEUR PRINCIPAL ── @@ -420,34 +367,31 @@ switch ($action) { echo json_encode(["success" => true]); break; - case 'get_config_keys': - checkAuth($pdo); - $stmt = $pdo->prepare("SELECT key_name, key_value FROM config WHERE key_name IN ('tmdb_api_key', 'fanart_api_key', 'upcmdb_api_key')"); - $stmt->execute(); - $rows = $stmt->fetchAll(); - $config = []; - foreach ($rows as $row) { - $config[$row['key_name']] = $row['key_value'] ? '••••••••' : ''; - } - if (!isset($config['tmdb_api_key'])) $config['tmdb_api_key'] = ''; - if (!isset($config['fanart_api_key'])) $config['fanart_api_key'] = ''; - if (!isset($config['upcmdb_api_key'])) $config['upcmdb_api_key'] = ''; - echo json_encode($config); - break; - - case 'save_config': - checkAuth($pdo); - $keyName = $data['key_name'] ?? ''; - $keyValue = $data['key_value'] ?? ''; - if (in_array($keyName, ['tmdb_api_key', 'fanart_api_key', 'upcmdb_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; +// ── CONFIGURATION ── +case 'get_config_keys': + $keys = ['tmdb_api_key', 'upcmdb_api_key']; + $config = []; + foreach ($keys as $k) { + $stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = ?"); + $stmt->execute([$k]); + $row = $stmt->fetch(); + $config[$k] = $row ? decryptData($row['key_value']) : ''; + } + echo json_encode($config); + break; + +case 'save_config': + checkAuth($pdo); + $name = $data['key_name'] ?? ''; + $val = trim($data['key_value'] ?? ''); + if (!in_array($name, ['tmdb_api_key', 'upcmdb_api_key'])) { + http_response_code(400); echo json_encode(["error" => "Clé invalide."]); break; + } + if (empty($val)) break; // Ne rien écraser si vide + $stmt = $pdo->prepare("REPLACE INTO config (key_name, key_value) VALUES (?, ?)"); + $stmt->execute([$name, encryptData($val)]); + echo json_encode(["success" => true]); + break; case 'get_films': $sql = " @@ -526,75 +470,49 @@ switch ($action) { else { http_response_code(400); echo json_encode(["success" => false, "error" => "Aucun élément sélectionné."]); } break; +// ── IMPORT BATCH (VIDÉOTHÈQUE) ── case 'import_batch': - checkAuth($pdo); -$data = json_decode(file_get_contents("php://input"), true); -$type = $data['type'] ?? 'critique'; -$items = $data['items'] ?? []; -$pdo->beginTransaction(); -$imported = 0; -$skipped = 0; -try { - if ($type === 'videotheque') { - $stmtVideo = $pdo->prepare("INSERT INTO videotheque (id, title, year, format, poster, ean_isbn13, description, length, number_of_discs, aspect_ratio, actors, publisher, director) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - ean_isbn13 = VALUES(ean_isbn13), - poster = VALUES(poster), - description = VALUES(description), - format = VALUES(format), - length = VALUES(length), - number_of_discs = VALUES(number_of_discs), - aspect_ratio = VALUES(aspect_ratio), - actors = VALUES(actors), - publisher = VALUES(publisher), - director = VALUES(director), - year = VALUES(year)"); - - foreach ($items as $item) { - // ── SOURCE 1 : CSV → EAN uniquement ── - $ean = preg_replace('/[^0-9]/', '', (string)($item['ean'] ?? '')); - if (strlen($ean) < 8) { $skipped++; continue; } - - // ── SOURCE 2 : UPCitemdb (gratuit) → métadonnées physiques ── - $physical = fetchPhysicalByEan($ean, $pdo); - $title = $physical['title'] ?? ''; - if (empty($title)) { $skipped++; continue; } - - $year = $physical['year'] ?? ''; - $format = $physical['format'] ?? ''; - $publisher = $physical['publisher'] ?? ''; - $discs = $physical['number_of_discs'] ?? 1; - $aspect = $physical['aspect_ratio'] ?? ''; - $length = $physical['length'] ?? ''; - $id = makeStableId('videotheque', $ean, $title); - - $description = ''; - $director = ''; - $actors = ''; - $poster = 'assets/img/default_physical_media.jpg'; - - // ── SOURCE 3 : TMDB → affiche, synopsis, réalisateur, acteurs ── - $tmdbData = fetchTmdbPosterAndSynopsis($title, $year, $pdo); - if ($tmdbData) { - if ($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') { - $poster = $tmdbData['poster']; - } - if (!empty($tmdbData['description'])) $description = $tmdbData['description']; - if (!empty($tmdbData['director'])) $director = $tmdbData['director']; - if (!empty($tmdbData['actors'])) $actors = $tmdbData['actors']; - if (!empty($tmdbData['length'])) $length = $tmdbData['length']; - if (!empty($tmdbData['year'])) $year = $tmdbData['year']; + checkAuth($pdo); + $data = json_decode(file_get_contents("php://input"), true); + $type = $data['type'] ?? ''; + $items = $data['items'] ?? []; + $pdo->beginTransaction(); + $imported = 0; $skipped = 0; + try { + if ($type === 'videotheque') { + $stmt = $pdo->prepare("INSERT INTO videotheque (id, title, year, format, poster, ean_isbn13, description, length, number_of_discs, aspect_ratio, actors, publisher, director) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE + title=VALUES(title), year=VALUES(year), format=VALUES(format), poster=VALUES(poster), ean_isbn13=VALUES(ean_isbn13), description=VALUES(description), length=VALUES(length), number_of_discs=VALUES(number_of_discs), aspect_ratio=VALUES(aspect_ratio), actors=VALUES(actors), publisher=VALUES(publisher), director=VALUES(director)"); + + foreach ($items as $item) { + $ean = preg_replace('/[^0-9]/', '', (string)($item['ean'] ?? '')); + if (strlen($ean) < 8) { $skipped++; continue; } + + // 1. Données physiques via UPC (UPCitemdb → UPCMDB) + $phys = fetchPhysicalByEan($ean, $pdo); + $title = $phys['title'] ?? ''; + if (empty($title)) { $skipped++; continue; } + + $year = $phys['year'] ?? ''; + $format = $phys['format'] ?: detectFormat($title); + $publisher = $phys['publisher'] ?? ''; + $discs = $phys['number_of_discs'] ?? 1; + $aspect = $phys['aspect_ratio'] ?? ''; + $length = $phys['length'] ?? ''; + + // 2. Données cinéma via TMDB (Affiche, Réalisateur, Acteurs, Synopsis, Durée) + $tmdb = fetchTmdbPosterAndSynopsis($title, $year, $pdo); + $poster = $tmdb['poster']; + $director = $tmdb['director'] ?? ''; + $actors = $tmdb['actors'] ?? ''; + $desc = $tmdb['description'] ?? ''; + if (!empty($tmdb['length'])) $length = $tmdb['length']; + if (empty($year) && !empty($tmdb['year'])) $year = $tmdb['year']; + + $id = makeStableId('videotheque', $title, $year); + $stmt->execute([$id, $title, $year, $format, $poster, $ean, $desc, $length, $discs, $aspect, $actors, $publisher, $director]); + $imported++; } - - if (empty($format)) $format = detectFormat($title, $description); - - $stmtVideo->execute([ - $id, $title, $year, $format, $poster, $ean, $description, - $length, $discs, $aspect, $actors, $publisher, $director - ]); - $imported++; - } } else { // ── IMPORTATION CRITIQUES ── $stmtCritiques = $pdo->prepare("INSERT INTO critiques (id, title, year, director, poster, rating, review, streaming) VALUES (?, ?, ?, ?, ?, ?, ?, ?)