mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
...
This commit is contained in:
@@ -31,8 +31,9 @@ class StashSyncService extends BaseSyncService
|
||||
'headers' => [
|
||||
'User-Agent' => 'MediaCollector/1.0',
|
||||
'Content-Type' => 'application/json',
|
||||
'ApiKey' => $this->apiKey // Now safe to access
|
||||
]
|
||||
'ApiKey' => $this->apiKey // Stash API key for authentication
|
||||
],
|
||||
'verify' => false // Disable SSL verification for problematic servers
|
||||
]);
|
||||
|
||||
$this->imageDownloader = new ImageDownloader('public/images', $this->apiKey);
|
||||
@@ -60,21 +61,70 @@ class StashSyncService extends BaseSyncService
|
||||
try {
|
||||
$this->logProgress('Fetching Stash scenes...');
|
||||
|
||||
// First, get the total count to determine how many pages we need
|
||||
$totalCount = $this->getStashScenesCount();
|
||||
|
||||
if ($totalCount === 0) {
|
||||
$this->logProgress('No scenes found in Stash');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logProgress("Found {$totalCount} scenes in Stash");
|
||||
|
||||
// Use pagination to handle large libraries
|
||||
$page = 0;
|
||||
$perPage = 50; // Smaller batch size for reliability
|
||||
$totalPages = ceil($totalCount / $perPage);
|
||||
|
||||
do {
|
||||
$scenes = $this->getStashScenes($page * $perPage, $perPage);
|
||||
$this->logProgress("Processing page {$page} with " . count($scenes) . " scenes...");
|
||||
for ($page = 0; $page < $totalPages; $page++) {
|
||||
try {
|
||||
$offset = $page * $perPage;
|
||||
$scenes = $this->getStashScenes($offset, $perPage);
|
||||
|
||||
foreach ($scenes as $sceneData) {
|
||||
$this->syncScene($sceneData);
|
||||
$this->processedCount++;
|
||||
if (empty($scenes)) {
|
||||
$this->logProgress("No scenes returned for page {$page}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->logProgress("Processing page {$page} with " . count($scenes) . " scenes...");
|
||||
|
||||
foreach ($scenes as $sceneData) {
|
||||
try {
|
||||
$this->logProgress("Processing scene: {$sceneData['title']} (ID: {$sceneData['id']})");
|
||||
$this->syncScene($sceneData);
|
||||
$this->processedCount++;
|
||||
|
||||
// Update progress in real-time
|
||||
$this->updateSyncLog($this->currentSyncLogId, 'running', [
|
||||
'processed_items' => $this->processedCount,
|
||||
'new_items' => $this->newCount,
|
||||
'updated_items' => $this->updatedCount,
|
||||
'message' => "Processed {$this->processedCount} of ~{$totalCount} scenes"
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Error processing scene {$sceneData['id']} ({$sceneData['title']}): " . $e->getMessage());
|
||||
$this->processedCount++; // Still count as processed even if failed
|
||||
|
||||
// Update progress even for failed items
|
||||
$this->updateSyncLog($this->currentSyncLogId, 'running', [
|
||||
'processed_items' => $this->processedCount,
|
||||
'message' => "Error on scene {$sceneData['id']}: " . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Error fetching page {$page}: " . $e->getMessage());
|
||||
// Continue with next page even if this page fails
|
||||
$this->updateSyncLog($this->currentSyncLogId, 'running', [
|
||||
'message' => "Failed to fetch page {$page}, continuing with next page"
|
||||
]);
|
||||
}
|
||||
|
||||
$page++;
|
||||
} while (count($scenes) === $perPage); // Continue if we got a full page
|
||||
// Add a small delay between pages to avoid overwhelming the server
|
||||
if ($page < $totalPages - 1) {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logProgress("Completed syncing Stash scenes");
|
||||
} catch (Exception $e) {
|
||||
@@ -83,6 +133,48 @@ class StashSyncService extends BaseSyncService
|
||||
}
|
||||
}
|
||||
|
||||
private function getStashScenesCount(): int
|
||||
{
|
||||
try {
|
||||
$query = '
|
||||
query FindScenes($filter: FindFilterType) {
|
||||
findScenes(filter: $filter) {
|
||||
count
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
$variables = [
|
||||
'filter' => [
|
||||
'per_page' => 1,
|
||||
'page' => 1,
|
||||
'sort' => 'created_at',
|
||||
'direction' => 'DESC'
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->httpClient->post("{$this->baseUrl}/graphql", [
|
||||
'json' => [
|
||||
'query' => $query,
|
||||
'variables' => $variables
|
||||
],
|
||||
'timeout' => 30
|
||||
]);
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
|
||||
if (!isset($data['data']['findScenes']['count'])) {
|
||||
$this->logProgress('No count data in Stash response');
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) $data['data']['findScenes']['count'];
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Failed to get Stash scenes count: ' . $e->getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function getStashScenes(int $offset = 0, int $limit = 50): array
|
||||
{
|
||||
try {
|
||||
@@ -118,15 +210,12 @@ class StashSyncService extends BaseSyncService
|
||||
width
|
||||
height
|
||||
}
|
||||
paths {
|
||||
screenshot
|
||||
}
|
||||
performers {
|
||||
id
|
||||
name
|
||||
disambiguation
|
||||
url
|
||||
gender
|
||||
gender
|
||||
birthdate
|
||||
ethnicity
|
||||
country
|
||||
@@ -166,24 +255,31 @@ class StashSyncService extends BaseSyncService
|
||||
]
|
||||
];
|
||||
|
||||
$this->logProgress("Fetching Stash scenes: offset={$offset}, limit={$limit}");
|
||||
|
||||
$response = $this->httpClient->post("{$this->baseUrl}/graphql", [
|
||||
'json' => [
|
||||
'query' => $query,
|
||||
'variables' => $variables
|
||||
],
|
||||
'timeout' => 30
|
||||
'timeout' => 60, // Increased timeout
|
||||
'connect_timeout' => 30
|
||||
]);
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
|
||||
if (!isset($data['data']['findScenes']['scenes'])) {
|
||||
$this->logProgress('No scenes data in response');
|
||||
$this->logProgress('No scenes data in Stash response');
|
||||
return [];
|
||||
}
|
||||
|
||||
return $data['data']['findScenes']['scenes'];
|
||||
$scenes = $data['data']['findScenes']['scenes'];
|
||||
$this->logProgress("Received " . count($scenes) . " scenes from Stash");
|
||||
|
||||
return $scenes;
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Failed to fetch Stash scenes: ' . $e->getMessage());
|
||||
$this->logProgress('Request details: ' . $e->getMessage());
|
||||
throw new Exception('Failed to fetch Stash scenes: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -309,28 +405,66 @@ class StashSyncService extends BaseSyncService
|
||||
$coverUrl = $screenshotUrl;
|
||||
}
|
||||
|
||||
if (!empty($coverUrl)) {
|
||||
$coverFilename = $this->imageDownloader->generateFilename($coverUrl, 'cover');
|
||||
$localCoverPath = $this->imageDownloader->downloadImage($coverUrl, $coverFilename, 'adult_videos');
|
||||
if ($localCoverPath) {
|
||||
$sceneData['local_cover_path'] = $this->imageDownloader->getPublicUrl($localCoverPath);
|
||||
$this->logProgress("Downloaded cover: " . $localCoverPath);
|
||||
// Check if this is an existing scene and if images already exist
|
||||
$shouldDownloadImages = true;
|
||||
if ($existingScene) {
|
||||
$existingMetadata = json_decode($existingScene['metadata'], true);
|
||||
$hasExistingCover = !empty($existingMetadata['local_cover_path']);
|
||||
$hasExistingScreenshot = !empty($existingMetadata['local_screenshot_path']);
|
||||
|
||||
if ($hasExistingCover && $hasExistingScreenshot) {
|
||||
$shouldDownloadImages = false;
|
||||
$this->logProgress("Scene {$sceneData['id']} already has images, skipping download");
|
||||
} else {
|
||||
$this->logProgress("Failed to download cover from: " . $coverUrl);
|
||||
$this->logProgress("Scene {$sceneData['id']} missing images - downloading");
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($screenshotUrl)) {
|
||||
$screenshotFilename = $this->imageDownloader->generateFilename($screenshotUrl, 'screenshot');
|
||||
$localScreenshotPath = $this->imageDownloader->downloadImage($screenshotUrl, $screenshotFilename, 'adult_videos');
|
||||
if ($localScreenshotPath) {
|
||||
$sceneData['local_screenshot_path'] = $this->imageDownloader->getPublicUrl($localScreenshotPath);
|
||||
$this->logProgress("Downloaded screenshot: " . $localScreenshotPath);
|
||||
} else {
|
||||
$this->logProgress("Failed to download screenshot from: " . $screenshotUrl);
|
||||
if ($shouldDownloadImages) {
|
||||
if (!empty($coverUrl)) {
|
||||
// Validate URL before attempting download
|
||||
if (filter_var($coverUrl, FILTER_VALIDATE_URL)) {
|
||||
try {
|
||||
$coverFilename = $this->imageDownloader->generateFilename($coverUrl, 'cover');
|
||||
$localCoverPath = $this->imageDownloader->downloadImage($coverUrl, $coverFilename, 'adult_videos');
|
||||
if ($localCoverPath) {
|
||||
$sceneData['local_cover_path'] = $this->imageDownloader->getPublicUrl($localCoverPath);
|
||||
$this->logProgress("Downloaded cover: " . $localCoverPath);
|
||||
} else {
|
||||
$this->logProgress("Failed to download cover from: " . $coverUrl);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Exception downloading cover from {$coverUrl}: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
$this->logProgress("Invalid cover URL: " . $coverUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($screenshotUrl)) {
|
||||
// Validate URL before attempting download
|
||||
if (filter_var($screenshotUrl, FILTER_VALIDATE_URL)) {
|
||||
try {
|
||||
$screenshotFilename = $this->imageDownloader->generateFilename($screenshotUrl, 'screenshot');
|
||||
$localScreenshotPath = $this->imageDownloader->downloadImage($screenshotUrl, $screenshotFilename, 'adult_videos');
|
||||
if ($localScreenshotPath) {
|
||||
$sceneData['local_screenshot_path'] = $this->imageDownloader->getPublicUrl($localScreenshotPath);
|
||||
$this->logProgress("Downloaded screenshot: " . $localScreenshotPath);
|
||||
} else {
|
||||
$this->logProgress("Failed to download screenshot from: " . $screenshotUrl);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Exception downloading screenshot from {$screenshotUrl}: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
$this->logProgress("Invalid screenshot URL: " . $screenshotUrl);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use existing image paths
|
||||
$sceneData['local_cover_path'] = $existingMetadata['local_cover_path'] ?? null;
|
||||
$sceneData['local_screenshot_path'] = $existingMetadata['local_screenshot_path'] ?? null;
|
||||
}
|
||||
// Handle performers/actors
|
||||
$performers = $sceneData['performers'] ?? [];
|
||||
$actorNames = [];
|
||||
@@ -366,11 +500,75 @@ class StashSyncService extends BaseSyncService
|
||||
];
|
||||
|
||||
if ($existingScene) {
|
||||
// For existing scenes, check if we need to update images
|
||||
$existingMetadata = json_decode($existingScene['metadata'], true);
|
||||
|
||||
// Only download images if they don't already exist locally
|
||||
if (empty($existingMetadata['local_cover_path']) && !empty($coverUrl)) {
|
||||
// Validate URL before attempting download
|
||||
if (filter_var($coverUrl, FILTER_VALIDATE_URL)) {
|
||||
try {
|
||||
$coverFilename = $this->imageDownloader->generateFilename($coverUrl, 'cover');
|
||||
$localCoverPath = $this->imageDownloader->downloadImage($coverUrl, $coverFilename, 'adult_videos');
|
||||
if ($localCoverPath) {
|
||||
$sceneData['local_cover_path'] = $this->imageDownloader->getPublicUrl($localCoverPath);
|
||||
$this->logProgress("Downloaded cover: " . $localCoverPath);
|
||||
} else {
|
||||
$this->logProgress("Failed to download cover from: " . $coverUrl);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Exception downloading cover from {$coverUrl}: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
$this->logProgress("Invalid cover URL: " . $coverUrl);
|
||||
}
|
||||
} else {
|
||||
// Keep existing local cover path
|
||||
$sceneData['local_cover_path'] = $existingMetadata['local_cover_path'] ?? null;
|
||||
if (!empty($sceneData['local_cover_path'])) {
|
||||
$this->logProgress("Using existing cover: " . $sceneData['local_cover_path']);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($existingMetadata['local_screenshot_path']) && !empty($screenshotUrl)) {
|
||||
// Validate URL before attempting download
|
||||
if (filter_var($screenshotUrl, FILTER_VALIDATE_URL)) {
|
||||
try {
|
||||
$screenshotFilename = $this->imageDownloader->generateFilename($screenshotUrl, 'screenshot');
|
||||
$localScreenshotPath = $this->imageDownloader->downloadImage($screenshotUrl, $screenshotFilename, 'adult_videos');
|
||||
if ($localScreenshotPath) {
|
||||
$sceneData['local_screenshot_path'] = $this->imageDownloader->getPublicUrl($localScreenshotPath);
|
||||
$this->logProgress("Downloaded screenshot: " . $localScreenshotPath);
|
||||
} else {
|
||||
$this->logProgress("Failed to download screenshot from: " . $screenshotUrl);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Exception downloading screenshot from {$screenshotUrl}: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
$this->logProgress("Invalid screenshot URL: " . $screenshotUrl);
|
||||
}
|
||||
} else {
|
||||
// Keep existing local screenshot path
|
||||
$sceneData['local_screenshot_path'] = $existingMetadata['local_screenshot_path'] ?? null;
|
||||
if (!empty($sceneData['local_screenshot_path'])) {
|
||||
$this->logProgress("Using existing screenshot: " . $sceneData['local_screenshot_path']);
|
||||
}
|
||||
}
|
||||
|
||||
$adultVideoModel->update($existingScene['id'], $sceneData);
|
||||
$adultVideoId = $existingScene['id'];
|
||||
$this->updatedCount++;
|
||||
|
||||
// Create actor relationships for existing scene
|
||||
$this->createActorRelationships($adultVideoId, $actors);
|
||||
} else {
|
||||
$adultVideoModel->create($sceneData);
|
||||
$adultVideoId = $this->pdo->lastInsertId();
|
||||
$this->newCount++;
|
||||
|
||||
// Create actor relationships for new scene
|
||||
$this->createActorRelationships($adultVideoId, $actors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,26 +655,38 @@ class StashSyncService extends BaseSyncService
|
||||
// Try to download performer image if available
|
||||
$thumbnailPath = null;
|
||||
if ($imagePath) {
|
||||
// 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/" . $imagePath;
|
||||
}
|
||||
// Validate image path before constructing URL
|
||||
if (!empty(trim($imagePath))) {
|
||||
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/" . $imagePath;
|
||||
}
|
||||
|
||||
$this->logProgress("Performer image URL for {$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);
|
||||
} else {
|
||||
$this->logProgress("Failed to download performer image from: " . $imageUrl);
|
||||
// Validate the constructed URL
|
||||
if (filter_var($imageUrl, FILTER_VALIDATE_URL)) {
|
||||
$this->logProgress("Performer image URL for {$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);
|
||||
} else {
|
||||
$this->logProgress("Failed to download performer image from: " . $imageUrl);
|
||||
}
|
||||
} else {
|
||||
$this->logProgress("Invalid performer image URL constructed: " . $imageUrl);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Exception downloading performer image for {$name} from {$imagePath}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +727,29 @@ class StashSyncService extends BaseSyncService
|
||||
return $this->updatedCount;
|
||||
}
|
||||
|
||||
private function createActorRelationships(int $adultVideoId, array $actors): void
|
||||
{
|
||||
foreach ($actors as $actor) {
|
||||
if (!isset($actor['id'])) continue;
|
||||
|
||||
try {
|
||||
// Insert relationship into pivot table (ignore duplicates)
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT IGNORE INTO actor_adult_video (adult_video_id, actor_id, created_at, updated_at)
|
||||
VALUES (:adult_video_id, :actor_id, NOW(), NOW())
|
||||
");
|
||||
$stmt->execute([
|
||||
'adult_video_id' => $adultVideoId,
|
||||
'actor_id' => $actor['id']
|
||||
]);
|
||||
|
||||
$this->logProgress("Created relationship: Adult Video {$adultVideoId} -> Actor {$actor['name']} ({$actor['id']})");
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress("Failed to create relationship for Adult Video {$adultVideoId} and Actor {$actor['name']}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDeletedCount(): int
|
||||
{
|
||||
return 0; // Stash doesn't provide deletion info in this context
|
||||
|
||||
Reference in New Issue
Block a user