From 3f2f80de1144e984f34bd621518de65333dddc9c Mon Sep 17 00:00:00 2001 From: Lars Behrends Date: Fri, 7 Nov 2025 13:20:48 +0100 Subject: [PATCH] actors the i dont know xD --- app/Controllers/TvShowController.php | 18 +- app/Models/TvShow.php | 17 +- app/Services/StashSyncService.php | 26 +- app/Services/XbvrSyncService.php | 68 ++++- resources/views/actor/index.twig | 1 - resources/views/layouts/app.twig | 71 ++++- resources/views/search/index.twig | 12 +- resources/views/tvshows/index.twig | 386 +++++++++++++++++++-------- sync_existing_performers.php | 63 +++++ view_missing_actors.php | 75 ++++++ 10 files changed, 602 insertions(+), 135 deletions(-) create mode 100644 sync_existing_performers.php create mode 100644 view_missing_actors.php diff --git a/app/Controllers/TvShowController.php b/app/Controllers/TvShowController.php index de4ea26..3fb9209 100644 --- a/app/Controllers/TvShowController.php +++ b/app/Controllers/TvShowController.php @@ -41,11 +41,23 @@ class TvShowController extends Controller } $years = array_filter($years); - // Get view mode + // Get view mode and sort $viewMode = $queryParams['view'] ?? 'grid'; // grid, list, covers + $sort = $queryParams['sort'] ?? 'title_asc'; - // Get TV shows with pagination and filters - $tvshows = TvShow::getAllWithPagination($this->pdo, $page, $perPage, $search, $genres, $years); + // Validate view mode + if (!in_array($viewMode, ['grid', 'list', 'covers'])) { + $viewMode = 'grid'; + } + + // Validate sort option + $validSorts = ['title_asc', 'title_desc', 'year_asc', 'year_desc', 'rating_asc', 'rating_desc']; + if (!in_array($sort, $validSorts)) { + $sort = 'title_asc'; + } + + // Get TV shows with pagination, filters, and sorting + $tvshows = TvShow::getAllWithPagination($this->pdo, $page, $perPage, $search, $genres, $years, $sort); // Get total count for pagination $totalCount = TvShow::getTotalCount($this->pdo, $search, $genres, $years); diff --git a/app/Models/TvShow.php b/app/Models/TvShow.php index 467c431..888fb18 100644 --- a/app/Models/TvShow.php +++ b/app/Models/TvShow.php @@ -130,7 +130,7 @@ class TvShow extends Model /** * Get all TV shows with pagination and optional search */ - public static function getAllWithPagination(\PDO $pdo, int $page, int $perPage, string $search = '', array $genres = [], array $years = []): array + public static function getAllWithPagination(\PDO $pdo, int $page, int $perPage, string $search = '', array $genres = [], array $years = [], string $sort = 'title_asc'): array { $offset = ($page - 1) * $perPage; @@ -166,7 +166,20 @@ class TvShow extends Model $sql .= $whereClause . " YEAR(first_air_date) IN (" . implode(',', $placeholders) . ")"; } - $sql .= " ORDER BY t.title ASC LIMIT :limit OFFSET :offset"; + // Add sorting + $sortMap = [ + 'title_asc' => 't.title ASC', + 'title_desc' => 't.title DESC', + 'year_desc' => 't.first_air_date DESC NULLS LAST', + 'year_asc' => 't.first_air_date ASC NULLS LAST', + 'rating_desc' => 't.rating DESC NULLS LAST', + 'rating_asc' => 't.rating ASC NULLS LAST', + 'recent' => 't.created_at DESC', + 'oldest' => 't.created_at ASC', + ]; + + $sortClause = $sortMap[$sort] ?? 't.title ASC'; + $sql .= " ORDER BY {$sortClause} LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT); diff --git a/app/Services/StashSyncService.php b/app/Services/StashSyncService.php index ea3ecf8..3a9bddc 100644 --- a/app/Services/StashSyncService.php +++ b/app/Services/StashSyncService.php @@ -656,13 +656,32 @@ class StashSyncService extends BaseSyncService $name = $performer['name'] ?? ''; if (empty($name)) return null; - // Check if actor already exists + // 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 + SELECT id, name, thumbnail_path, metadata FROM actors + WHERE name = :name "); $stmt->execute(['name' => $name]); $existingActor = $stmt->fetch(PDO::FETCH_ASSOC); + // 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 rich metadata from Stash performer data $actorMetadata = [ 'stash_id' => $performer['id'] ?? null, @@ -908,8 +927,9 @@ class StashSyncService extends BaseSyncService try { $this->logProgress('Starting existing performers sync with Stash...'); + echo "Starting existing performers sync with Stash..\n"; // Get all existing actors from database - $stmt = $this->pdo->prepare("SELECT id, name, metadata FROM actors ORDER BY name ASC"); + $stmt = $this->pdo->prepare("SELECT id, name, metadata FROM actors WHERE id IN (SELECT actor_id FROM actor_adult_video) ORDER BY name ASC"); $stmt->execute(); $existingActors = $stmt->fetchAll(\PDO::FETCH_ASSOC); diff --git a/app/Services/XbvrSyncService.php b/app/Services/XbvrSyncService.php index 39fbe52..256e30b 100644 --- a/app/Services/XbvrSyncService.php +++ b/app/Services/XbvrSyncService.php @@ -392,18 +392,65 @@ class XbvrSyncService extends BaseSyncService if (empty($actorName)) continue; - // Try to get detailed actor information from XBVR - $detailedActorData = $this->getActorDetails($actorName, $actorData); + // Handle XBVR "aka:" format (e.g., "aka:Bella Luna,Bella Luna") + $actorNames = $this->parseXbvrActorNames($actorName); - $actor = $this->getOrCreateActor($detailedActorData); - if ($actor) { - $actors[] = $actor; + $foundActor = null; + foreach ($actorNames as $name) { + $detailedActorData = $this->getActorDetails($name, $actorData); + $detailedActorData['name'] = $name; // Ensure the name is set correctly + + $actor = $this->getOrCreateActor($detailedActorData); + if ($actor) { + $foundActor = $actor; + break; // Use the first found actor + } + } + + // If no actor found with any of the names, create one with the first name + if (!$foundActor && !empty($actorNames)) { + $firstName = $actorNames[0]; + $detailedActorData = $this->getActorDetails($firstName, $actorData); + $detailedActorData['name'] = $firstName; + + $foundActor = $this->getOrCreateActor($detailedActorData); + } + + if ($foundActor) { + $actors[] = $foundActor; } } return $actors; } + /** + * Parse XBVR actor names that may contain "aka:" format + * Example: "aka:Bella Luna,Bella Luna" -> ["Bella Luna", "Bella Luna"] + */ + private function parseXbvrActorNames(string $actorName): array + { + // Check if the name starts with "aka:" + if (strpos($actorName, 'aka:') === 0) { + // Remove "aka:" prefix and split by comma + $namesPart = substr($actorName, 4); // Remove "aka:" + $names = array_map('trim', explode(',', $namesPart)); + + // Filter out empty names + $names = array_filter($names, function($name) { + return !empty(trim($name)); + }); + + if (!empty($names)) { + $this->logProgress("Parsed XBVR aka format '{$actorName}' into names: " . implode(', ', $names)); + return $names; + } + } + + // Return the original name if not in aka format + return [$actorName]; + } + private function getActorDetails(string $actorName, $actorData): array { // If we already have detailed actor data from the scene, use it @@ -587,13 +634,20 @@ class XbvrSyncService extends BaseSyncService $name = $actorData['name'] ?? ''; if (empty($name)) return null; - // Check if actor already exists + // Check if actor already exists by name or alias $stmt = $this->pdo->prepare(" - SELECT id, name, thumbnail_path, metadata FROM actors WHERE name = :name + 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}'"); + } + // Prepare metadata from XBVR actor data $actorMetadata = [ 'xbvr_id' => $actorData['xbvr_id'] ?? $actorData['id'] ?? null, diff --git a/resources/views/actor/index.twig b/resources/views/actor/index.twig index ca17527..5ec0b9e 100644 --- a/resources/views/actor/index.twig +++ b/resources/views/actor/index.twig @@ -11,7 +11,6 @@

Actors & Performers

{{ pagination.total_items }} performer{{ pagination.total_items != 1 ? 's' : '' }}

- {{dump(pagination)}} {% if pagination.total_pages > 1 %}
diff --git a/resources/views/layouts/app.twig b/resources/views/layouts/app.twig index a3a9868..04bb3d3 100644 --- a/resources/views/layouts/app.twig +++ b/resources/views/layouts/app.twig @@ -90,9 +90,78 @@
- +
{% block nav_controls %}{% endblock %} + + +
+ + + + +
diff --git a/resources/views/search/index.twig b/resources/views/search/index.twig index 2d846f7..af83c6a 100644 --- a/resources/views/search/index.twig +++ b/resources/views/search/index.twig @@ -105,15 +105,19 @@ {% for item in items %}
+ + + +
{% if item.poster_url %} - {{ item.title or item.name }} + {{ item.title or item.name }} {% elseif item.cover_image %} - {{ item.title or item.name }} + {{ item.title or item.name }} {% elseif item.image_url %} - {{ item.title or item.name }} + {{ item.title or item.name }} {% elseif item.thumbnail %} - {{ item.title or item.name }} + {{ item.title or item.name }} {% else %}
diff --git a/resources/views/tvshows/index.twig b/resources/views/tvshows/index.twig index 15766aa..849b927 100644 --- a/resources/views/tvshows/index.twig +++ b/resources/views/tvshows/index.twig @@ -1,8 +1,8 @@ {% extends "layouts/app.twig" %} {% block nav_controls %} - -
+ +
+
+ {% if search %} + + {% endif %}
- - -
+ +
{% for mode in view_modes %} {% if mode == 'grid' %} @@ -46,6 +57,11 @@ {% endif %} + {% if mode == 'covers' %} + + + + {% endif %} {% endfor %} @@ -168,49 +184,71 @@ {% block content %} -
- -
-

TV Shows

- {% if pagination.total_items > 0 %} -
- {{ pagination.total_items }} TV shows - {% if search %} - matching "{{ search }}" - {% endif %} - {% if filters.genres or filters.years %} - {% if filters.genres %} - {{ filters.genres|join(', ') }} - {% endif %} - {% if filters.years %} - {{ filters.years|join(', ') }} - {% endif %} - {% endif %} +
+
+ +
+
+
+

TV Shows

+ {% if pagination.total_items > 0 %} +
+ {{ pagination.total_items }} TV shows + {% if search %} + matching "{{ search }}" + {% endif %} + {% if filters.genres %} + {{ filters.genres|join(', ') }} + {% endif %} + {% if filters.years %} + {{ filters.years|join(', ') }} + {% endif %} +
+ {% endif %} +
+ +
+
+ + +
+
+
- {% endif %} -
{% if tvshows is empty %} -
- - - -

+
+
+ + + +
+

{% if search or filters.genres or filters.years %} No TV shows found matching your criteria {% else %} No TV shows found {% endif %}

-

+

{% if search or filters.genres or filters.years %} - Try adjusting your search terms or filters. + Try adjusting your search terms or filters to find what you're looking for. {% else %} - Start syncing your TV show libraries to see your TV shows here. + Start syncing your TV show libraries to see your collection here. {% endif %}

{% if search or filters.genres or filters.years %} - + + + + Clear filters {% endif %} @@ -218,51 +256,116 @@ {% else %} {% if view_mode == 'list' %} - -
-
    + +
    +
      {% for tvshow in tvshows %} -
    • -
      -
      - {% if tvshow.poster_url %} - {{ tvshow.title }} - {% else %} -
      - - - -
      - {% endif %} -
      -

      - - {{ tvshow.title }} - -

      -
      - {% if tvshow.first_air_date %} - {{ tvshow.first_air_date|date('Y') }} +
    • +
      +
      +
      + +
      + {% if tvshow.poster_url %} +
      + {{ tvshow.title }} +
      + {% else %} +
      + + + +
      {% endif %} - {% if tvshow.rating %} - ⭐ {{ tvshow.rating }}/10 - {% endif %} - {% if tvshow.number_of_seasons %} - {{ tvshow.number_of_seasons }} season{{ tvshow.number_of_seasons > 1 ? 's' : '' }} - {% endif %} - {% if tvshow.number_of_episodes %} - {{ tvshow.number_of_episodes }} episode{{ tvshow.number_of_episodes > 1 ? 's' : '' }} - {% endif %} - {{ tvshow.source_name }} +
      + + +
      +
      +
      +

      + + {{ tvshow.title }} + +

      + + +
      + {% if tvshow.first_air_date %} +
      + + + + {{ tvshow.first_air_date|date('Y') }} +
      + {% endif %} + + {% if tvshow.rating %} +
      + + + + {{ tvshow.rating }}/10 +
      + {% endif %} + + {% if tvshow.number_of_seasons %} +
      + + + + {{ tvshow.number_of_seasons }} season{{ tvshow.number_of_seasons > 1 ? 's' : '' }} +
      + {% endif %} + + {% if tvshow.number_of_episodes %} +
      + + + + {{ tvshow.number_of_episodes }} episode{{ tvshow.number_of_episodes > 1 ? 's' : '' }} +
      + {% endif %} +
      + + + {% if tvshow.overview %} +

      + {{ tvshow.overview|slice(0, 120) }}{% if tvshow.overview|length > 120 %}...{% endif %} +

      + {% endif %} + + +
      + + + + {{ tvshow.source_name }} +
      +
      +
      -
      -
      - {% if tvshow.is_favorite %} - - Favorite - - {% endif %} + + +
      + {% if tvshow.is_favorite %} + + + + + Favorite + + {% endif %} + + + View Details + + + + +
    • @@ -301,61 +404,116 @@
    {% else %} - -
    + +
    {% for tvshow in tvshows %} -
    -
    -
    +
    +
    +
    +
    {% if tvshow.poster_url %} - {{ tvshow.title }} +
    + {{ tvshow.title }} +
    {% else %} -
    - +
    +
    {% endif %}
    -
    -
    - + + +
    +
    + {{ tvshow.title }}
    -
    + + +
    {% if tvshow.first_air_date %} - {{ tvshow.first_air_date|date('Y') }} +
    + + + + {{ tvshow.first_air_date|date('Y') }} +
    {% endif %} + {% if tvshow.rating %} - ⭐ {{ tvshow.rating }}/10 +
    + + + + {{ tvshow.rating }}/10 +
    {% endif %}
    + + {% if tvshow.source_name %} -

    - {{ tvshow.source_name }} -

    +
    + + + + {{ tvshow.source_name }} +
    {% endif %} -
    -
    - {% if tvshow.overview %} -
    -

    - {{ tvshow.overview|slice(0, 150) }}{% if tvshow.overview|length > 150 %}...{% endif %} -

    -
    - {% endif %} -
    - {% if tvshow.number_of_seasons %} - {{ tvshow.number_of_seasons }} season{{ tvshow.number_of_seasons > 1 ? 's' : '' }} - {% endif %} -
    - {% if tvshow.is_favorite %} - - Favorite - + + + {% if tvshow.overview %} +
    +

    + {{ tvshow.overview|slice(0, 180) }}{% if tvshow.overview|length > 180 %}...{% endif %} +

    +
    {% endif %} + + +
    +
    + {% if tvshow.number_of_seasons %} + + + + + {{ tvshow.number_of_seasons }} season{{ tvshow.number_of_seasons > 1 ? 's' : '' }} + + {% endif %} + + {% if tvshow.number_of_episodes %} + + + + + {{ tvshow.number_of_episodes }} episode{{ tvshow.number_of_episodes > 1 ? 's' : '' }} + + {% endif %} +
    + +
    + {% if tvshow.is_favorite %} + + + + + Favorite + + {% endif %} + + + View + + + + +
    +
    diff --git a/sync_existing_performers.php b/sync_existing_performers.php new file mode 100644 index 0000000..1fc92d7 --- /dev/null +++ b/sync_existing_performers.php @@ -0,0 +1,63 @@ +prepare('SELECT * FROM sources WHERE name = ?'); + $stmt->execute(['stash']); + $stashSource = $stmt->fetch(\PDO::FETCH_ASSOC); + + if (!$stashSource) { + echo "ERROR: Stash source not configured in database\n"; + exit(1); + } + + echo "Found Stash configuration: {$stashSource['display_name']}\n"; + + // Create Stash sync service + $stashSyncService = new \App\Services\StashSyncService($pdo, $stashSource); + + // Run the existing performers sync + echo "Starting sync of existing performers...\n"; + $results = $stashSyncService->syncExistingPerformers(); + + echo "\n=== Sync Results ===\n"; + echo "Processed: {$results['processed']} performers\n"; + echo "Updated: {$results['updated']} performers\n"; + echo "Skipped: {$results['skipped']} performers\n"; + echo "Not found in Stash: " . count($results['not_found_in_stash']) . " performers\n"; + echo "Errors: " . count($results['errors']) . " performers\n"; + + if (!empty($results['not_found_in_stash'])) { + echo "\n=== Actors Not Found in Stash ===\n"; + echo "These actors exist in your local database but were not found in Stash.\n"; + echo "You can create them in Stash for future syncs.\n\n"; + + foreach ($results['not_found_in_stash'] as $missingActor) { + echo "- {$missingActor['name']} (ID: {$missingActor['id']})\n"; + } + + echo "\nA detailed report has been saved to storage/logs/\n"; + } + + if (!empty($results['errors'])) { + echo "\n=== Errors ===\n"; + foreach ($results['errors'] as $error) { + echo "- {$error}\n"; + } + } + + echo "\nSync completed successfully!\n"; + +} catch (Exception $e) { + echo "ERROR: " . $e->getMessage() . "\n"; + echo "Stack trace:\n" . $e->getTraceAsString() . "\n"; + exit(1); +} diff --git a/view_missing_actors.php b/view_missing_actors.php new file mode 100644 index 0000000..74b1bda --- /dev/null +++ b/view_missing_actors.php @@ -0,0 +1,75 @@ + filemtime($a); + }); + + foreach ($files as $index => $file) { + $filename = basename($file); + $fileData = json_decode(file_get_contents($file), true); + + if (!$fileData) { + echo "ERROR: Could not read report file: {$filename}\n"; + continue; + } + + echo "Report #" . ($index + 1) . ": {$filename}\n"; + echo "Generated: " . ($fileData['generated_at'] ?? 'Unknown') . "\n"; + echo "Total Missing Actors: " . ($fileData['total_missing'] ?? 0) . "\n"; + + if (!empty($fileData['description'])) { + echo "Description: " . $fileData['description'] . "\n"; + } + + if (!empty($fileData['missing_actors'])) { + echo "\nMissing Actors:\n"; + foreach ($fileData['missing_actors'] as $actor) { + echo " - {$actor['name']} (ID: {$actor['id']})\n"; + + // Show some local metadata if available + $localMeta = $actor['local_metadata'] ?? []; + if (!empty($localMeta['birth_date'])) { + echo " Birth Date: {$localMeta['birth_date']}\n"; + } + if (!empty($localMeta['nationality'])) { + echo " Nationality: {$localMeta['nationality']}\n"; + } + if (!empty($localMeta['gender'])) { + echo " Gender: {$localMeta['gender']}\n"; + } + echo "\n"; + } + } + + echo str_repeat("-", 50) . "\n\n"; + } + + echo "To create these actors in Stash, use the data above to manually add them.\n"; + echo "After creating them in Stash, run the sync again to match them up.\n"; + +} catch (Exception $e) { + echo "ERROR: " . $e->getMessage() . "\n"; + exit(1); +}