From 1b053148f0d9121a2117a0aeec8542e7c714db8c Mon Sep 17 00:00:00 2001 From: Lars Behrends Date: Fri, 14 Nov 2025 02:42:44 +0100 Subject: [PATCH] xbvr sync --- app/Services/StashSyncService.php | 54 ++ app/Services/XbvrSyncService.php | 195 +------- resources/views/adult/index.twig | 477 +++++++++--------- resources/views/components/adult-card.twig | 185 +++++++ resources/views/components/game-card.twig | 321 ++++++++++++ resources/views/components/movie-card.twig | 185 +++++++ resources/views/components/tvshow-card.twig | 181 +++++++ resources/views/games/index.twig | 406 ++++++++------- resources/views/movies/index.twig | 414 ++++++++-------- resources/views/tvshows/index.twig | 523 ++++++++------------ setup_stash_ignore_config.php | 116 +++++ test_xbvr_sync.php | 64 +++ 12 files changed, 2035 insertions(+), 1086 deletions(-) create mode 100644 resources/views/components/adult-card.twig create mode 100644 resources/views/components/game-card.twig create mode 100644 resources/views/components/movie-card.twig create mode 100644 resources/views/components/tvshow-card.twig create mode 100644 setup_stash_ignore_config.php create mode 100644 test_xbvr_sync.php diff --git a/app/Services/StashSyncService.php b/app/Services/StashSyncService.php index 3a9bddc..cf9b392 100644 --- a/app/Services/StashSyncService.php +++ b/app/Services/StashSyncService.php @@ -57,6 +57,45 @@ class StashSyncService extends BaseSyncService $this->logProgress("Processed {$this->processedCount} Stash items"); } + /** + * Check if a scene should be ignored based on file paths and ignore patterns in config + */ + private function shouldIgnoreScene(array $sceneData): bool + { + $config = $this->source['config'] ?? null; + if (empty($config)) { + return false; + } + + $configData = json_decode($config, true); + if (json_last_error() !== JSON_ERROR_NONE) { + $this->logProgress('Invalid JSON in source config, skipping ignore check'); + return false; + } + + $ignorePaths = $configData['ignore_paths'] ?? []; + if (empty($ignorePaths) || !is_array($ignorePaths)) { + return false; + } + + $files = $sceneData['files'] ?? []; + foreach ($files as $file) { + $filePath = $file['path'] ?? ''; + if (empty($filePath)) { + continue; + } + + foreach ($ignorePaths as $ignorePattern) { + if (stripos($filePath, $ignorePattern) !== false) { + $this->logProgress("Scene '{$sceneData['title']}' ignored due to file path containing: '{$ignorePattern}'"); + return true; + } + } + } + + return false; + } + private function syncScenes(): void { try { @@ -91,6 +130,20 @@ class StashSyncService extends BaseSyncService foreach ($scenes as $sceneData) { try { $this->logProgress("Processing scene: {$sceneData['title']} (ID: {$sceneData['id']})"); + + // Check if scene should be ignored based on file paths + if ($this->shouldIgnoreScene($sceneData)) { + $this->processedCount++; // Still count as processed + // Update progress for ignored items + $this->updateSyncLog($this->currentSyncLogId, 'running', [ + 'processed_items' => $this->processedCount, + 'new_items' => $this->newCount, + 'updated_items' => $this->updatedCount, + 'message' => "Processed {$this->processedCount} of ~{$totalCount} scenes (ignored)" + ]); + continue; + } + $this->syncScene($sceneData); $this->processedCount++; @@ -210,6 +263,7 @@ class StashSyncService extends BaseSyncService audio_codec width height + path } performers { id diff --git a/app/Services/XbvrSyncService.php b/app/Services/XbvrSyncService.php index 256e30b..d92ecab 100644 --- a/app/Services/XbvrSyncService.php +++ b/app/Services/XbvrSyncService.php @@ -55,6 +55,7 @@ class XbvrSyncService extends BaseSyncService foreach ($scenes as $sceneData) { try { $this->syncScene($sceneData); + $this->logProgress("Success Synced XBVR scene {$sceneData['id']}"); $this->processedCount++; } catch (Exception $e) { $this->logProgress("Error processing XBVR scene {$sceneData['id']}: " . $e->getMessage()); @@ -126,7 +127,7 @@ class XbvrSyncService extends BaseSyncService } // Add small delay to be respectful to the API - usleep(100000); // 0.1 second delay + //usleep(100000); // 0.1 second delay } catch (Exception $e) { $this->logProgress("Error fetching details for video: " . $e->getMessage()); @@ -458,194 +459,44 @@ class XbvrSyncService extends BaseSyncService return $actorData; } - // Try to fetch detailed actor information from XBVR/DeoVR API - // XBVR might have actor detail endpoints, let's try a few possibilities + // XBVR/DeoVR API does not provide actor detail endpoints + // Skip API calls and use basic actor info only + $this->logProgress("XBVR does not provide actor details API, using basic info for: {$actorName}"); - $actorDetails = ['name' => $actorName]; - - // Try different XBVR actor API endpoints - $actorApiUrls = [ - "{$this->baseUrl}/api/actor/search/" . urlencode($actorName), - "{$this->baseUrl}/actor/" . urlencode($actorName), - "{$this->baseUrl}/api/actors?name=" . urlencode($actorName), - ]; - - foreach ($actorApiUrls as $apiUrl) { - try { - $this->logProgress("Trying to fetch actor details from: {$apiUrl}"); - - $response = $this->httpClient->get($apiUrl, [ - 'timeout' => 10, - 'connect_timeout' => 5 - ]); - - if ($response->getStatusCode() === 200) { - $actorApiData = json_decode($response->getBody(), true); - - if (!empty($actorApiData)) { - $this->logProgress("Successfully fetched actor details for: {$actorName}"); - - // Merge API data with basic info - $actorDetails = array_merge($actorDetails, $this->mapActorApiData($actorApiData)); - break; - } - } - } catch (Exception $e) { - // Continue to next API endpoint - $this->logProgress("Actor API endpoint failed: {$apiUrl} - " . $e->getMessage()); - } - } - - // If no detailed data found, try to scrape from web search or use basic info - if (count($actorDetails) <= 1) { - $this->logProgress("No detailed actor data found for {$actorName}, using basic info"); - $actorDetails = $this->scrapeActorInfo($actorName); - } - - return $actorDetails; + return ['name' => $actorName]; } - private function mapActorApiData(array $apiData): array - { - $mapped = []; - // Handle different possible API response formats - if (isset($apiData['actor'])) { - $apiData = $apiData['actor']; - } - - // Map common fields - $fieldMappings = [ - 'id' => 'xbvr_id', - 'name' => 'name', - 'image' => 'image_path', - 'thumbnail' => 'thumbnail_path', - 'bio' => 'biography', - 'biography' => 'biography', - 'birthdate' => 'birth_date', - 'age' => 'age', - 'height' => 'height', - 'weight' => 'weight', - 'measurements' => 'measurements', - 'nationality' => 'nationality', - 'ethnicity' => 'ethnicity', - 'eye_color' => 'eye_color', - 'hair_color' => 'hair_color', - 'tattoos' => 'tattoos', - 'piercings' => 'piercings', - 'aliases' => 'aliases', - 'debut_year' => 'debut_year', - 'retirement_year' => 'retirement_year', - 'active' => 'active', - 'website' => 'website', - 'twitter' => 'twitter', - 'instagram' => 'instagram', - 'scene_count' => 'scene_count' - ]; - - foreach ($fieldMappings as $apiField => $localField) { - if (isset($apiData[$apiField])) { - $mapped[$localField] = $apiData[$apiField]; - } - } - - return $mapped; - } - - private function scrapeActorInfo(string $actorName): array - { - $actorInfo = ['name' => $actorName]; - - // Try to get basic information from web scraping - // This is a fallback when API doesn't provide details - - try { - // Try to search for actor on common adult industry sites - $searchUrls = [ - "https://www.adultempire.com/search.php?query=" . urlencode($actorName), - "https://www.brazzers.com/search/" . urlencode($actorName) . "/", - "https://www.naughtyamerica.com/search/" . urlencode($actorName), - ]; - - foreach ($searchUrls as $searchUrl) { - try { - $response = $this->httpClient->get($searchUrl, [ - 'timeout' => 5, - 'headers' => [ - 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' - ] - ]); - - if ($response->getStatusCode() === 200) { - $html = $response->getBody()->getContents(); - - // Basic HTML parsing to extract information - $actorInfo = array_merge($actorInfo, $this->parseActorHtml($html, $actorName)); - break; - } - } catch (Exception $e) { - continue; - } - } - } catch (Exception $e) { - $this->logProgress("Web scraping failed for {$actorName}: " . $e->getMessage()); - } - - return $actorInfo; - } - - private function parseActorHtml(string $html, string $actorName): array - { - $info = []; - - // Very basic HTML parsing - look for common patterns - // This is quite fragile and would need improvement for production use - - // Look for image URLs - if (preg_match('/]+src=["\']([^"\']*?(?:actor|performer|model)[^"\']*?)["\'][^>]*>/i', $html, $matches)) { - $info['image_path'] = $matches[1]; - } - - // Look for birthdate patterns - if (preg_match('/(?:born|birthdate|birth).*?(\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{4})/i', $html, $matches)) { - $info['birth_date'] = date('Y-m-d', strtotime($matches[1])); - } - - // Look for age - if (preg_match('/age.*?(\d+)/i', $html, $matches)) { - $info['age'] = (int)$matches[1]; - } - - // Look for measurements - if (preg_match('/measurements?.*?(\d+-\d+-\d+)/i', $html, $matches)) { - $info['measurements'] = $matches[1]; - } - - // Look for height - if (preg_match('/height.*?(\d+\'?\d*)/i', $html, $matches)) { - $info['height'] = $matches[1]; - } - - return $info; - } private function getOrCreateActor(array $actorData): ?array { $name = $actorData['name'] ?? ''; if (empty($name)) return null; - // Check if actor already exists by name or alias + // Check if actor already exists by name or alias + // First check by exact name $stmt = $this->pdo->prepare(" SELECT id, name, thumbnail_path, metadata FROM actors WHERE name = :name - OR JSON_CONTAINS(metadata->'$.aliases', :name) "); $stmt->execute(['name' => $name]); $existingActor = $stmt->fetch(\PDO::FETCH_ASSOC); - // If found by alias, log it for debugging - if ($existingActor && $existingActor['name'] !== $name) { - $this->logProgress("Found existing actor '{$existingActor['name']}' by alias '{$name}'"); + // If not found by name, check aliases in PHP + if (!$existingActor) { + $stmt = $this->pdo->prepare("SELECT id, name, thumbnail_path, metadata FROM actors"); + $stmt->execute(); + $allActors = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + foreach ($allActors as $actor) { + $metadata = json_decode($actor['metadata'] ?? '{}', true); + $aliases = $metadata['aliases'] ?? []; + if (is_array($aliases) && in_array($name, $aliases)) { + $existingActor = $actor; + $this->logProgress("Found existing actor '{$actor['name']}' by alias '{$name}'"); + break; + } + } } // Prepare metadata from XBVR actor data diff --git a/resources/views/adult/index.twig b/resources/views/adult/index.twig index fe09a84..f8b59dc 100644 --- a/resources/views/adult/index.twig +++ b/resources/views/adult/index.twig @@ -31,12 +31,12 @@ - -
+ + + +
+ + + + +
+ +
+
+