mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
actors / poster images
This commit is contained in:
@@ -34,6 +34,30 @@ class AdultController extends Controller
|
||||
// Get adult videos with pagination and search
|
||||
$adultVideos = AdultVideo::getAllWithPagination($this->pdo, $page, $perPage, $search);
|
||||
|
||||
// Process metadata to extract local image paths for template compatibility
|
||||
foreach ($adultVideos as &$video) {
|
||||
if (!empty($video['metadata'])) {
|
||||
$metadata = json_decode($video['metadata'], true);
|
||||
|
||||
// Use local cover path if available, otherwise fall back to original URL
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$video['poster_url'] = '/public/images/'.$metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$video['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
|
||||
// Add other local paths if needed
|
||||
if (!empty($metadata['local_screenshot_path'])) {
|
||||
$video['screenshot_url'] = $metadata['local_screenshot_path'];
|
||||
}
|
||||
|
||||
// Add actors data if available
|
||||
if (!empty($metadata['actors'])) {
|
||||
$video['actors'] = $metadata['actors'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get total count for pagination
|
||||
$totalCount = AdultVideo::getTotalCount($this->pdo, $search);
|
||||
|
||||
@@ -82,6 +106,17 @@ class AdultController extends Controller
|
||||
// Decode metadata for display
|
||||
$metadata = json_decode($adultVideo['metadata'], true);
|
||||
|
||||
// Add local image paths to the video data for template compatibility
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$adultVideo['poster_url'] = '/public/images/'.$metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$adultVideo['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
|
||||
if (!empty($metadata['local_screenshot_path'])) {
|
||||
$adultVideo['screenshot_url'] = '/public/images/'.$metadata['local_screenshot_path'];
|
||||
}
|
||||
|
||||
return $this->view->render($response, 'adult/show.twig', [
|
||||
'title' => $adultVideo['title'],
|
||||
'movie' => $adultVideo, // Keep same variable name for template compatibility
|
||||
|
||||
@@ -21,16 +21,21 @@ class StashSyncService extends BaseSyncService
|
||||
public function __construct(PDO $pdo, array $source)
|
||||
{
|
||||
parent::__construct($pdo, $source);
|
||||
|
||||
// Initialize properties first before using them
|
||||
$this->apiKey = $source['api_key'];
|
||||
$this->baseUrl = rtrim($source['api_url'], '/');
|
||||
|
||||
$this->httpClient = new Client([
|
||||
'timeout' => 60, // Stash can be slow
|
||||
'headers' => [
|
||||
'User-Agent' => 'MediaCollector/1.0',
|
||||
'Content-Type' => 'application/json'
|
||||
'Content-Type' => 'application/json',
|
||||
'ApiKey' => $this->apiKey // Now safe to access
|
||||
]
|
||||
]);
|
||||
$this->apiKey = $source['api_key'];
|
||||
$this->baseUrl = rtrim($source['api_url'], '/');
|
||||
$this->imageDownloader = new ImageDownloader();
|
||||
|
||||
$this->imageDownloader = new ImageDownloader('public/images', $this->apiKey);
|
||||
}
|
||||
|
||||
protected function executeSync(string $syncType): void
|
||||
@@ -282,12 +287,24 @@ class StashSyncService extends BaseSyncService
|
||||
|
||||
// Stash provides paths.screenshot for screenshot
|
||||
if (!empty($sceneData['paths']['screenshot'])) {
|
||||
// Convert relative path to full URL
|
||||
$screenshotUrl = "{$this->baseUrl}/" . ltrim($sceneData['paths']['screenshot'], '/');
|
||||
$screenshotPath = $sceneData['paths']['screenshot'];
|
||||
|
||||
// Handle different path formats from Stash
|
||||
if (strpos($screenshotPath, 'http') === 0) {
|
||||
// Already a full URL
|
||||
$screenshotUrl = $screenshotPath;
|
||||
} elseif (strpos($screenshotPath, '/') === 0) {
|
||||
// Absolute path from Stash root
|
||||
$screenshotUrl = "{$this->baseUrl}" . $screenshotPath;
|
||||
} else {
|
||||
// Relative path - assume it's in a standard location
|
||||
$screenshotUrl = "{$this->baseUrl}/scene/" . $sceneData['id'] . "/" . $screenshotPath;
|
||||
}
|
||||
|
||||
$this->logProgress("Screenshot URL: " . $screenshotUrl);
|
||||
}
|
||||
|
||||
// For cover, we might need to use a different approach or check if there's a primary image
|
||||
// For now, we'll use the screenshot as cover if available
|
||||
// For cover, we'll use the screenshot as cover if available
|
||||
if ($screenshotUrl) {
|
||||
$coverUrl = $screenshotUrl;
|
||||
}
|
||||
@@ -297,6 +314,9 @@ class StashSyncService extends BaseSyncService
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,6 +325,9 @@ class StashSyncService extends BaseSyncService
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,11 +457,26 @@ class StashSyncService extends BaseSyncService
|
||||
// Try to download performer image if available
|
||||
$thumbnailPath = null;
|
||||
if ($imagePath) {
|
||||
$imageUrl = "{$this->baseUrl}/" . ltrim($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;
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,11 @@ class XbvrSyncService extends BaseSyncService
|
||||
public function __construct(\PDO $pdo, array $source)
|
||||
{
|
||||
parent::__construct($pdo, $source);
|
||||
|
||||
// Initialize properties first before using them
|
||||
$this->apiKey = $source['api_key'];
|
||||
$this->baseUrl = rtrim($source['api_url'], '/');
|
||||
|
||||
$this->httpClient = new Client([
|
||||
'timeout' => 30,
|
||||
'headers' => [
|
||||
@@ -27,9 +32,8 @@ class XbvrSyncService extends BaseSyncService
|
||||
'X-API-Key' => $source['api_key']
|
||||
]
|
||||
]);
|
||||
$this->apiKey = $source['api_key'];
|
||||
$this->baseUrl = rtrim($source['api_url'], '/');
|
||||
$this->imageDownloader = new ImageDownloader();
|
||||
|
||||
$this->imageDownloader = new ImageDownloader('public/images', $this->apiKey);
|
||||
}
|
||||
|
||||
protected function executeSync(string $syncType): void
|
||||
@@ -78,27 +82,6 @@ class XbvrSyncService extends BaseSyncService
|
||||
}
|
||||
}
|
||||
|
||||
private function syncScene(array $sceneData): void
|
||||
{
|
||||
$adultVideoModel = new AdultVideo($this->pdo);
|
||||
|
||||
// Check if scene already exists by xbvr_id in metadata
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT id, metadata FROM adult_videos
|
||||
WHERE source_id = :source_id
|
||||
");
|
||||
$stmt->execute(['source_id' => $this->source['id']]);
|
||||
$existingScenes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$existingScene = null;
|
||||
foreach ($existingScenes as $scene) {
|
||||
$metadata = json_decode($scene['metadata'], true);
|
||||
if (isset($metadata['xbvr_id']) && $metadata['xbvr_id'] === $sceneData['id']) {
|
||||
$existingScene = $scene;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function syncScene(array $sceneData): void
|
||||
{
|
||||
$adultVideoModel = new AdultVideo($this->pdo);
|
||||
@@ -124,19 +107,39 @@ class XbvrSyncService extends BaseSyncService
|
||||
$coverFilename = null;
|
||||
$screenshotFilename = null;
|
||||
|
||||
// Extract image URLs from XBVR API response
|
||||
$coverUrl = null;
|
||||
$screenshotUrl = null;
|
||||
|
||||
if (!empty($sceneData['cover_url'])) {
|
||||
$coverFilename = $this->imageDownloader->generateFilename($sceneData['cover_url'], 'cover');
|
||||
$localCoverPath = $this->imageDownloader->downloadImage($sceneData['cover_url'], $coverFilename, 'adult_videos');
|
||||
if ($localCoverPath) {
|
||||
$sceneData['local_cover_path'] = $this->imageDownloader->getPublicUrl($localCoverPath);
|
||||
}
|
||||
$coverUrl = $sceneData['cover_url'];
|
||||
$this->logProgress("Cover URL: " . $coverUrl);
|
||||
}
|
||||
|
||||
if (!empty($sceneData['screenshot_url'])) {
|
||||
$screenshotFilename = $this->imageDownloader->generateFilename($sceneData['screenshot_url'], 'screenshot');
|
||||
$localScreenshotPath = $this->imageDownloader->downloadImage($sceneData['screenshot_url'], $screenshotFilename, 'adult_videos');
|
||||
$screenshotUrl = $sceneData['screenshot_url'];
|
||||
$this->logProgress("Screenshot URL: " . $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);
|
||||
} else {
|
||||
$this->logProgress("Failed to download cover from: " . $coverUrl);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,19 @@ class ImageDownloader
|
||||
private Client $httpClient;
|
||||
private string $basePath;
|
||||
|
||||
public function __construct(string $basePath = 'public/images')
|
||||
public function __construct(string $basePath = 'public/images', ?string $apiKey = null)
|
||||
{
|
||||
$headers = [
|
||||
'User-Agent' => 'MediaCollector/1.0'
|
||||
];
|
||||
|
||||
if ($apiKey) {
|
||||
$headers['ApiKey'] = $apiKey;
|
||||
}
|
||||
|
||||
$this->httpClient = new Client([
|
||||
'timeout' => 30,
|
||||
'headers' => [
|
||||
'User-Agent' => 'MediaCollector/1.0'
|
||||
]
|
||||
'headers' => $headers
|
||||
]);
|
||||
$this->basePath = rtrim($basePath, '/');
|
||||
}
|
||||
@@ -27,6 +33,7 @@ class ImageDownloader
|
||||
public function downloadImage(string $url, string $filename, string $subfolder = ''): ?string
|
||||
{
|
||||
if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
error_log("Invalid URL provided: {$url}");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -48,10 +55,45 @@ class ImageDownloader
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
$response = $this->httpClient->get($url, ['sink' => $filePath]);
|
||||
error_log("Downloading image from: {$url} to: {$filePath}");
|
||||
|
||||
if ($response->getStatusCode() === 200) {
|
||||
return $filePath;
|
||||
$response = $this->httpClient->get($url, [
|
||||
'sink' => $filePath,
|
||||
'headers' => [
|
||||
'User-Agent' => 'MediaCollector/1.0',
|
||||
'Accept' => 'image/*',
|
||||
]
|
||||
]);
|
||||
|
||||
$statusCode = $response->getStatusCode();
|
||||
$contentType = $response->getHeaderLine('content-type');
|
||||
|
||||
error_log("Download response - Status: {$statusCode}, Content-Type: {$contentType}");
|
||||
|
||||
if ($statusCode === 200) {
|
||||
$fileSize = filesize($filePath);
|
||||
error_log("Successfully downloaded image. Size: {$fileSize} bytes");
|
||||
|
||||
// Check if file is actually an image and not empty
|
||||
if ($fileSize > 0) {
|
||||
$imageInfo = getimagesize($filePath);
|
||||
if ($imageInfo !== false) {
|
||||
error_log("Valid image downloaded: {$imageInfo[0]}x{$imageInfo[1]} {$imageInfo['mime']}");
|
||||
return $filePath;
|
||||
} else {
|
||||
error_log("Downloaded file is not a valid image");
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error_log("Downloaded file is empty");
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error_log("Failed to download image. HTTP Status: {$statusCode}");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
{% if movie.poster_url %}
|
||||
<img class="rounded me-3" style="width: 64px; height: 96px; object-fit: cover;" src="{{ movie.poster_url }}" alt="{{ movie.title }}">
|
||||
<img class="rounded me-3" style="width: 64px; height: 96px; object-fit: contain; background-color: #f8f9fa;" src="{{ movie.poster_url }}" alt="{{ movie.title }}">
|
||||
{% else %}
|
||||
<div class="bg-light rounded me-3 d-flex align-items-center justify-content-center" style="width: 64px; height: 96px;">
|
||||
<svg class="text-muted" width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -157,8 +157,8 @@
|
||||
<div class="col-6 col-sm-4 col-md-3 col-lg-2">
|
||||
<div class="card h-100">
|
||||
{% if movie.poster_url %}
|
||||
<div class="position-relative" style="aspect-ratio: 2/3; overflow: hidden;">
|
||||
<img src="{{ movie.poster_url }}" alt="{{ movie.title }}" class="card-img-top" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<div class="position-relative" style="background-color: #f8f9fa; border-radius: 0.375rem; overflow: hidden;">
|
||||
<img src="{{ movie.poster_url }}" alt="{{ movie.title }}" class="card-img-top w-100" style="max-height: 300px; object-fit: contain;">
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="d-flex align-items-center justify-content-center bg-light" style="aspect-ratio: 2/3; min-height: 200px;">
|
||||
@@ -192,7 +192,7 @@
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-shrink-0">
|
||||
{% if movie.poster_url %}
|
||||
<img class="rounded" style="width: 64px; height: 96px; object-fit: cover;" src="{{ movie.poster_url }}" alt="{{ movie.title }}">
|
||||
<img class="rounded" style="width: 64px; height: 96px; object-fit: contain; background-color: #f8f9fa;" src="{{ movie.poster_url }}" alt="{{ movie.title }}">
|
||||
{% else %}
|
||||
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 64px; height: 96px;">
|
||||
<svg class="text-muted" width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<!-- Video poster -->
|
||||
<div class="col-md-4">
|
||||
<div class="card-body">
|
||||
<div style="aspect-ratio: 2/3; background-color: #f8f9fa; border-radius: 0.375rem; overflow: hidden;">
|
||||
<div style="background-color: #f8f9fa; border-radius: 0.375rem; overflow: hidden;">
|
||||
{% if movie.poster_url %}
|
||||
<img src="{{ movie.poster_url }}" alt="{{ movie.title }}" class="w-100 h-100" style="object-fit: cover;">
|
||||
<img src="{{ movie.poster_url }}" alt="{{ movie.title }}" class="w-100" style="max-height: 400px; object-fit: contain;">
|
||||
{% else %}
|
||||
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||
<svg class="text-muted" width="96" height="96" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -111,7 +111,7 @@
|
||||
<!-- Additional details -->
|
||||
<div class="row g-3">
|
||||
<!-- Cast & Crew -->
|
||||
{% if movie.cast or movie.director or movie.writer %}
|
||||
{% if movie.cast or movie.director or movie.writer or movie.actors %}
|
||||
<div class="col-md-6">
|
||||
<h3 class="h6 fw-semibold text-dark mb-3">Cast & Crew</h3>
|
||||
<dl class="row g-2">
|
||||
@@ -134,6 +134,29 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
<!-- Actors with thumbnails -->
|
||||
{% if movie.actors %}
|
||||
<div class="mt-3">
|
||||
<h4 class="h6 fw-semibold text-dark mb-2">Performers</h4>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
{% for actor in movie.actors %}
|
||||
<div class="d-flex flex-column align-items-center" style="width: 60px;">
|
||||
{% if actor.thumbnail_path %}
|
||||
<img src="{{ actor.thumbnail_path }}" alt="{{ actor.name }}" class="rounded-circle mb-1" style="width: 40px; height: 40px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-light d-flex align-items-center justify-content-center mb-1" style="width: 40px; height: 40px;">
|
||||
<svg class="text-muted" width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
<span class="small text-muted text-center" style="font-size: 0.75rem;">{{ actor.name }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user