actor fetching :)

Stash / ADultVideoAPI
This commit is contained in:
Lars Behrends
2025-11-06 12:07:12 +01:00
parent 1ec6016b10
commit 3f56625205
7 changed files with 1048 additions and 58 deletions

View File

@@ -149,35 +149,135 @@ class ActorController extends Controller
]);
}
// Handle image upload
// Handle image upload/download
$thumbnailPath = $actor['thumbnail_path']; // Keep existing by default
if (!empty($uploadedFiles['thumbnail']) && $uploadedFiles['thumbnail']->getError() === UPLOAD_ERR_OK) {
$uploadedFile = $uploadedFiles['thumbnail'];
$imageSource = $data['image_source'] ?? 'upload';
// Validate file type
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($uploadedFile->getClientMediaType(), $allowedTypes)) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'Invalid image type. Only JPEG, PNG, GIF, and WebP are allowed.'
]);
if ($imageSource === 'upload') {
// Handle file upload
if (!empty($uploadedFiles['thumbnail']) && $uploadedFiles['thumbnail']->getError() === UPLOAD_ERR_OK) {
$uploadedFile = $uploadedFiles['thumbnail'];
// Validate file type
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($uploadedFile->getClientMediaType(), $allowedTypes)) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'Invalid image type. Only JPEG, PNG, GIF, and WebP are allowed.'
]);
}
// Generate filename and move file
$extension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
$filename = 'actor_' . $actorId . '_' . time() . '.' . $extension;
$uploadPath = __DIR__ . '/../../public/images/actors/' . $filename;
// Create directory if it doesn't exist
$uploadDir = dirname($uploadPath);
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$uploadedFile->moveTo($uploadPath);
$thumbnailPath = '/images/actors/' . $filename;
}
} elseif ($imageSource === 'url') {
// Handle URL download
$imageUrl = trim($data['thumbnail_url'] ?? '');
if (!empty($imageUrl)) {
// Validate URL
if (!filter_var($imageUrl, FILTER_VALIDATE_URL)) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'Invalid image URL provided.'
]);
}
// Generate filename and move file
$extension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
$filename = 'actor_' . $actorId . '_' . time() . '.' . $extension;
$uploadPath = __DIR__ . '/../../public/images/actors/' . $filename;
try {
// Download image from URL
$imageData = file_get_contents($imageUrl);
if ($imageData === false) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'Failed to download image from the provided URL.'
]);
}
// Create directory if it doesn't exist
$uploadDir = dirname($uploadPath);
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
// Get image info to validate type and determine extension
$imageInfo = getimagesizefromstring($imageData);
if (!$imageInfo) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'The URL does not point to a valid image.'
]);
}
// Validate MIME type
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($imageInfo['mime'], $allowedTypes)) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'Invalid image type. Only JPEG, PNG, GIF, and WebP are allowed.'
]);
}
// Determine extension from MIME type
$extension = '';
switch ($imageInfo['mime']) {
case 'image/jpeg':
$extension = 'jpg';
break;
case 'image/png':
$extension = 'png';
break;
case 'image/gif':
$extension = 'gif';
break;
case 'image/webp':
$extension = 'webp';
break;
}
// Generate filename and save file
$filename = 'actor_' . $actorId . '_' . time() . '.' . $extension;
$uploadPath = __DIR__ . '/../../public/images/actors/' . $filename;
// Create directory if it doesn't exist
$uploadDir = dirname($uploadPath);
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
// Save the downloaded image
if (file_put_contents($uploadPath, $imageData) === false) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'Failed to save the downloaded image.'
]);
}
$thumbnailPath = '/images/actors/' . $filename;
} catch (Exception $e) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'Error downloading image: ' . $e->getMessage()
]);
}
}
$uploadedFile->moveTo($uploadPath);
$thumbnailPath = '/images/actors/' . $filename;
}
// Prepare metadata
@@ -246,13 +346,156 @@ class ActorController extends Controller
'metadata' => $metadata
]);
}
public function fetchStashData(Request $request, Response $response, $args)
{
ini_set('memory_limit', '2G');
try {
// Parse JSON body for API requests
$rawBody = $request->getBody()->getContents();
$data = json_decode($rawBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return $this->jsonResponse($response->withStatus(400), [
'error' => 'Invalid JSON in request body'
]);
}
$query = trim($data['query'] ?? '');
if (empty($query)) {
return $this->jsonResponse($response->withStatus(400), [
'error' => 'Missing required parameter: query'
]);
}
// Get Stash configuration from database
$stmt = $this->pdo->prepare('SELECT * FROM sources WHERE name = ?');
$stmt->execute(['stash']);
$stashSource = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$stashSource) {
return $this->jsonResponse($response->withStatus(500), [
'error' => 'Stash source not configured in database'
]);
}
$stashUrl = trim($stashSource['api_url'] ?? '');
$stashApiKey = trim($stashSource['api_key'] ?? '');
if (empty($stashUrl)) {
return $this->jsonResponse($response->withStatus(500), [
'error' => 'Stash API URL not configured'
]);
}
// Create HTTP client for Stash API
$client = new \GuzzleHttp\Client([
'timeout' => 30,
'verify' => false,
'headers' => [
'User-Agent' => 'MediaCollector/1.0',
'ApiKey' => $stashApiKey,
'Content-Type' => 'application/json'
]
]);
// Build GraphQL query to search for performers
$graphqlQuery = '
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' => $query,
'per_page' => 10, // Limit results
'sort' => 'name',
'direction' => 'ASC'
]
];
// Make the API call
$apiResponse = $client->post(rtrim($stashUrl, '/') . '/graphql', [
'json' => [
'query' => $graphqlQuery,
'variables' => $variables
]
]);
$result = json_decode($apiResponse->getBody(), true);
if (!isset($result['data']['findPerformers']['performers'])) {
return $this->jsonResponse($response, [
'performers' => [],
'message' => 'No performers found'
]);
}
$performers = $result['data']['findPerformers']['performers'];
return $this->jsonResponse($response, [
'performers' => $performers,
'count' => count($performers),
'stash_url' => $stashUrl
]);
} catch (\GuzzleHttp\Exception\RequestException $e) {
$errorMessage = 'Failed to connect to Stash server';
if ($e->hasResponse()) {
$statusCode = $e->getResponse()->getStatusCode();
$errorMessage .= ": HTTP {$statusCode}";
}
return $this->jsonResponse($response->withStatus(500), [
'error' => $errorMessage
]);
} catch (\Exception $e) {
return $this->jsonResponse($response->withStatus(500), [
'error' => 'Internal server error: ' . $e->getMessage()
]);
}
}
public function index(Request $request, Response $response, $args)
{
$queryParams = $request->getQueryParams();
// Get pagination parameters
$page = max(1, (int)($queryParams['page'] ?? 1));
$perPage = max(12, min(100, (int)($queryParams['per_page'] ?? 24)));
$perPage = max(12, min(100, (int)($queryParams['per_page'] ?? 48)));
// Get search parameters
$search = trim($queryParams['search'] ?? '');
@@ -367,9 +610,51 @@ class ActorController extends Controller
$orderBy = $sortMap[$sort] ?? 'total_media_count DESC, a.name ASC';
$sql .= " ORDER BY {$orderBy}";
// Get total count for pagination
$countSql = str_replace('SELECT a.id, a.name, a.thumbnail_path,', 'SELECT COUNT(*) as count,', $sql);
$countSql = preg_replace('/ORDER BY.*$/', '', $countSql);
// Get total count for pagination - use a subquery to count the results
$countSql = "
SELECT COUNT(*) as count FROM (
SELECT a.id,
COALESCE(adult_counts.adult_video_count, 0) as adult_video_count,
COALESCE(movie_counts.movie_count, 0) as movie_count,
COALESCE(tv_counts.tv_show_count, 0) as tv_show_count
FROM actors a
LEFT JOIN (
SELECT actor_id, COUNT(DISTINCT adult_video_id) as adult_video_count
FROM actor_adult_video
GROUP BY actor_id
) adult_counts ON a.id = adult_counts.actor_id
LEFT JOIN (
SELECT actor_id, COUNT(DISTINCT movie_id) as movie_count
FROM actor_movie
GROUP BY actor_id
) movie_counts ON a.id = movie_counts.actor_id
LEFT JOIN (
SELECT actor_id, COUNT(DISTINCT tv_show_id) as tv_show_count
FROM (
SELECT actor_id, tv_show_id FROM actor_tv_show
UNION
SELECT ate.actor_id, te.tv_show_id
FROM actor_tv_episode ate
JOIN tv_episodes te ON ate.tv_episode_id = te.id
) combined_tv
GROUP BY actor_id
) tv_counts ON a.id = tv_counts.actor_id
";
// Add search filter
if (!empty($whereClauses)) {
$countSql .= ' WHERE ' . implode(' AND ', $whereClauses);
}
$countSql .= " GROUP BY a.id";
// Add HAVING clause for filters
if (!empty($havingClauses)) {
$countSql .= ' HAVING ' . implode(' AND ', $havingClauses);
}
$countSql .= ") as filtered_actors";
$countStmt = $this->pdo->prepare($countSql);
foreach ($params as $key => $value) {
$countStmt->bindValue($key, $value);