mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
actor sync
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace App\Services;
|
||||
|
||||
use App\Utils\ImageDownloader;
|
||||
use App\Utils\ImageAspectRatioDetector;
|
||||
use App\Models\AdultVideo;
|
||||
use GuzzleHttp\Client;
|
||||
use PDO;
|
||||
@@ -469,9 +470,31 @@ class StashSyncService extends BaseSyncService
|
||||
$performers = $sceneData['performers'] ?? [];
|
||||
$actors = $this->syncActors($performers);
|
||||
|
||||
// Detect aspect ratios for downloaded images
|
||||
$posterAspectRatio = null;
|
||||
$backdropAspectRatio = null;
|
||||
|
||||
if (!empty($sceneData['local_cover_path'])) {
|
||||
$posterAspectRatio = ImageAspectRatioDetector::detectAspectRatio($sceneData['local_cover_path']);
|
||||
if ($posterAspectRatio) {
|
||||
$this->logProgress("Detected poster aspect ratio: {$posterAspectRatio}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($sceneData['local_screenshot_path'])) {
|
||||
$backdropAspectRatio = ImageAspectRatioDetector::detectAspectRatio($sceneData['local_screenshot_path']);
|
||||
if ($backdropAspectRatio) {
|
||||
$this->logProgress("Detected backdrop aspect ratio: {$backdropAspectRatio}");
|
||||
}
|
||||
}
|
||||
|
||||
$sceneData = [
|
||||
'title' => $sceneData['title'] ?: 'Untitled Scene',
|
||||
'overview' => $sceneData['details'] ?? null,
|
||||
'poster_url' => $sceneData['local_cover_path'] ?? null,
|
||||
'poster_aspect_ratio' => $posterAspectRatio,
|
||||
'backdrop_url' => $sceneData['local_screenshot_path'] ?? null,
|
||||
'backdrop_aspect_ratio' => $backdropAspectRatio,
|
||||
'release_date' => $sceneData['date'] ? date('Y-m-d', strtotime($sceneData['date'])) : null,
|
||||
'runtime_minutes' => !empty($sceneData['files'][0]['duration']) ? round($sceneData['files'][0]['duration'] / 60) : null,
|
||||
'rating' => $sceneData['rating100'] ? $sceneData['rating100'] / 100 : null, // Convert from 0-100 to 0-10
|
||||
@@ -869,6 +892,313 @@ class StashSyncService extends BaseSyncService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync existing performers with Stash data
|
||||
*/
|
||||
public function syncExistingPerformers(): array
|
||||
{
|
||||
$results = [
|
||||
'processed' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
'not_found_in_stash' => [],
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
try {
|
||||
$this->logProgress('Starting existing performers sync with Stash...');
|
||||
|
||||
// Get all existing actors from database
|
||||
$stmt = $this->pdo->prepare("SELECT id, name, metadata FROM actors ORDER BY name ASC");
|
||||
$stmt->execute();
|
||||
$existingActors = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$this->logProgress("Found " . count($existingActors) . " existing actors to check");
|
||||
|
||||
foreach ($existingActors as $actor) {
|
||||
try {
|
||||
$this->logProgress("Processing actor: {$actor['name']} (ID: {$actor['id']})");
|
||||
|
||||
// Search for this actor in Stash
|
||||
$stashPerformers = $this->searchStashPerformer($actor['name']);
|
||||
|
||||
if (empty($stashPerformers)) {
|
||||
$this->logProgress("No matching performer found in Stash for: {$actor['name']}");
|
||||
$results['not_found_in_stash'][] = [
|
||||
'id' => $actor['id'],
|
||||
'name' => $actor['name'],
|
||||
'local_metadata' => json_decode($actor['metadata'] ?? '{}', true)
|
||||
];
|
||||
$results['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the best match (exact name match preferred)
|
||||
$bestMatch = null;
|
||||
foreach ($stashPerformers as $performer) {
|
||||
if (strtolower(trim($performer['name'])) === strtolower(trim($actor['name']))) {
|
||||
$bestMatch = $performer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no exact match, use the first result
|
||||
if (!$bestMatch && !empty($stashPerformers)) {
|
||||
$bestMatch = $stashPerformers[0];
|
||||
$this->logProgress("Using closest match for {$actor['name']}: {$bestMatch['name']}");
|
||||
}
|
||||
|
||||
if ($bestMatch) {
|
||||
// Update the actor with Stash data
|
||||
$this->updateActorWithStashData($actor['id'], $bestMatch);
|
||||
$results['updated']++;
|
||||
$this->logProgress("Updated actor {$actor['name']} with Stash data");
|
||||
} else {
|
||||
$results['skipped']++;
|
||||
}
|
||||
|
||||
$results['processed']++;
|
||||
|
||||
// Add a small delay to avoid overwhelming the Stash server
|
||||
usleep(100000); // 0.1 seconds
|
||||
|
||||
} catch (Exception $e) {
|
||||
$errorMsg = "Failed to sync actor {$actor['name']}: " . $e->getMessage();
|
||||
$results['errors'][] = $errorMsg;
|
||||
$this->logProgress("ERROR: " . $errorMsg);
|
||||
$results['processed']++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->logProgress("Existing performers sync completed: {$results['updated']} updated, {$results['skipped']} skipped, " . count($results['errors']) . " errors");
|
||||
|
||||
// Save missing actors report
|
||||
$this->saveMissingActorsReport($results['not_found_in_stash']);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Error during existing performers sync: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a report of actors not found in Stash
|
||||
*/
|
||||
private function saveMissingActorsReport(array $missingActors): void
|
||||
{
|
||||
if (empty($missingActors)) {
|
||||
$this->logProgress("No missing actors to report");
|
||||
return;
|
||||
}
|
||||
|
||||
$reportPath = __DIR__ . '/../../storage/logs/missing_stash_actors_' . date('Y-m-d_H-i-s') . '.json';
|
||||
|
||||
// Create logs directory if it doesn't exist
|
||||
$logDir = dirname($reportPath);
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
$reportData = [
|
||||
'generated_at' => date('Y-m-d H:i:s'),
|
||||
'total_missing' => count($missingActors),
|
||||
'missing_actors' => $missingActors,
|
||||
'description' => 'These actors exist in your local database but were not found in Stash. You can create them in Stash for future syncs.'
|
||||
];
|
||||
|
||||
$jsonReport = json_encode($reportData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
if (file_put_contents($reportPath, $jsonReport)) {
|
||||
$this->logProgress("Missing actors report saved to: {$reportPath}");
|
||||
$this->logProgress("Found " . count($missingActors) . " actors not in Stash");
|
||||
} else {
|
||||
$this->logProgress("Failed to save missing actors report");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a performer in Stash by name
|
||||
*/
|
||||
private function searchStashPerformer(string $name): array
|
||||
{
|
||||
try {
|
||||
$query = '
|
||||
query FindPerformers($filter: FindFilterType) {
|
||||
findPerformers(filter: $filter) {
|
||||
performers {
|
||||
id
|
||||
name
|
||||
disambiguation
|
||||
url
|
||||
gender
|
||||
birthdate
|
||||
ethnicity
|
||||
country
|
||||
eye_color
|
||||
height_cm
|
||||
measurements
|
||||
fake_tits
|
||||
penis_length
|
||||
circumcised
|
||||
career_length
|
||||
tattoos
|
||||
piercings
|
||||
alias_list
|
||||
favorite
|
||||
ignore_auto_tag
|
||||
created_at
|
||||
updated_at
|
||||
details
|
||||
death_date
|
||||
hair_color
|
||||
weight
|
||||
image_path
|
||||
scene_count
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
$variables = [
|
||||
'filter' => [
|
||||
'q' => $name,
|
||||
'per_page' => 5, // Get a few results to find the best match
|
||||
'sort' => 'name',
|
||||
'direction' => 'ASC'
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->httpClient->post("{$this->baseUrl}/graphql", [
|
||||
'json' => [
|
||||
'query' => $query,
|
||||
'variables' => $variables
|
||||
],
|
||||
'timeout' => 30
|
||||
]);
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
|
||||
if (!isset($data['data']['findPerformers']['performers'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $data['data']['findPerformers']['performers'];
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Failed to search Stash for performer: ' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing actor with Stash performer data
|
||||
*/
|
||||
private function updateActorWithStashData(int $actorId, array $performer): void
|
||||
{
|
||||
// Get existing actor data
|
||||
$stmt = $this->pdo->prepare("SELECT metadata FROM actors WHERE id = :id");
|
||||
$stmt->execute(['id' => $actorId]);
|
||||
$existingActor = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$existingActor) {
|
||||
throw new Exception("Actor with ID {$actorId} not found");
|
||||
}
|
||||
|
||||
$existingMetadata = json_decode($existingActor['metadata'] ?? '{}', true);
|
||||
|
||||
// Prepare updated metadata from Stash performer data
|
||||
$updatedMetadata = [
|
||||
'stash_id' => $performer['id'] ?? null,
|
||||
'stash_url' => $performer['url'] ?? null,
|
||||
'disambiguation' => $performer['disambiguation'] ?? '',
|
||||
'gender' => $performer['gender'] ?? null,
|
||||
'birth_date' => $performer['birthdate'] ?? null,
|
||||
'death_date' => $performer['death_date'] ?? null,
|
||||
'ethnicity' => $performer['ethnicity'] ?? null,
|
||||
'country' => $performer['country'] ?? null,
|
||||
'nationality' => $performer['country'] ?? null, // Map country to nationality
|
||||
'eye_color' => $performer['eye_color'] ?? null,
|
||||
'hair_color' => $performer['hair_color'] ?? null,
|
||||
'height' => $performer['height_cm'] ? $performer['height_cm'] . 'cm' : null,
|
||||
'measurements' => $performer['measurements'] ?? null,
|
||||
'cup_size' => $this->extractCupSize($performer['measurements'] ?? ''),
|
||||
'weight' => $performer['weight'] ? $performer['weight'] . 'kg' : null,
|
||||
'piercings' => $performer['piercings'] ?? null,
|
||||
'tattoos' => $performer['tattoos'] ?? null,
|
||||
'fake_tits' => $performer['fake_tits'] ?? null,
|
||||
'penis_length' => $performer['penis_length'] ?? null,
|
||||
'circumcised' => $performer['circumcised'] ?? null,
|
||||
'career_length' => $performer['career_length'] ?? null,
|
||||
'aliases' => $performer['alias_list'] ?? [],
|
||||
'favorite' => $performer['favorite'] ?? false,
|
||||
'ignore_auto_tag' => $performer['ignore_auto_tag'] ?? false,
|
||||
'scene_count' => $performer['scene_count'] ?? 0,
|
||||
'details' => $performer['details'] ?? null,
|
||||
'stash_created_at' => $performer['created_at'] ?? null,
|
||||
'stash_updated_at' => $performer['updated_at'] ?? null,
|
||||
'social_media' => [
|
||||
'website' => $performer['url'] ?? null
|
||||
],
|
||||
'adult_specific' => [
|
||||
'debut_year' => $this->extractDebutYear($performer['career_length'] ?? ''),
|
||||
'retirement_year' => $this->extractRetirementYear($performer['career_length'] ?? ''),
|
||||
'active' => $this->isActivePerformer($performer['career_length'] ?? ''),
|
||||
'genres' => [],
|
||||
'specialties' => []
|
||||
]
|
||||
];
|
||||
|
||||
// Merge with existing metadata, preferring new Stash data but keeping any custom fields
|
||||
$finalMetadata = array_merge($existingMetadata, $updatedMetadata);
|
||||
|
||||
// Try to download/update performer image if available and not already set
|
||||
$thumbnailPath = null;
|
||||
$imagePath = $performer['image_path'] ?? null;
|
||||
if ($imagePath && empty($existingMetadata['local_image_path'])) {
|
||||
try {
|
||||
// Handle different image path formats from Stash
|
||||
if (strpos($imagePath, 'http') === 0) {
|
||||
// Already a full URL
|
||||
$imageUrl = $imagePath;
|
||||
} elseif (strpos($imagePath, '/') === 0) {
|
||||
// Absolute path from Stash root
|
||||
$imageUrl = "{$this->baseUrl}" . $imagePath;
|
||||
} else {
|
||||
// Relative path - assume it's in performer images directory
|
||||
$imageUrl = "{$this->baseUrl}/performer/" . $performer['id'] . "/" . $imagePath;
|
||||
}
|
||||
|
||||
// Validate the constructed URL
|
||||
if (filter_var($imageUrl, FILTER_VALIDATE_URL)) {
|
||||
$this->logProgress("Downloading image for performer {$performer['name']}: " . $imageUrl);
|
||||
$thumbnailFilename = $this->imageDownloader->generateFilename($imageUrl, 'actor');
|
||||
$localImagePath = $this->imageDownloader->downloadImage($imageUrl, $thumbnailFilename, 'actors');
|
||||
if ($localImagePath) {
|
||||
$thumbnailPath = $this->imageDownloader->getPublicUrl($localImagePath);
|
||||
$this->logProgress("Downloaded performer image: " . $localImagePath);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Exception downloading performer image for {$performer['name']}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Update the actor record
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE actors
|
||||
SET thumbnail_path = COALESCE(:thumbnail_path, thumbnail_path),
|
||||
metadata = :metadata,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute([
|
||||
'id' => $actorId,
|
||||
'thumbnail_path' => $thumbnailPath,
|
||||
'metadata' => json_encode($finalMetadata)
|
||||
]);
|
||||
}
|
||||
|
||||
protected function executeCleanup(): void
|
||||
{
|
||||
$this->logProgress("Starting cleanup - detecting deleted media in Stash...");
|
||||
|
||||
Reference in New Issue
Block a user