mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
first commit
This commit is contained in:
315
app/Services/JellyfinSyncService.php
Normal file
315
app/Services/JellyfinSyncService.php
Normal file
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Movie;
|
||||
use App\Models\TvShow;
|
||||
use App\Models\TvEpisode;
|
||||
use GuzzleHttp\Client;
|
||||
use Exception;
|
||||
|
||||
class JellyfinSyncService extends BaseSyncService
|
||||
{
|
||||
private Client $httpClient;
|
||||
private ?string $apiKey;
|
||||
private string $baseUrl;
|
||||
private int $processedCount = 0;
|
||||
private int $newCount = 0;
|
||||
private int $updatedCount = 0;
|
||||
|
||||
public function __construct(\PDO $pdo, array $source)
|
||||
{
|
||||
parent::__construct($pdo, $source);
|
||||
$this->httpClient = new Client([
|
||||
'timeout' => 30,
|
||||
'headers' => [
|
||||
'User-Agent' => 'MediaCollector/1.0',
|
||||
'X-MediaBrowser-Token' => $source['api_key']
|
||||
]
|
||||
]);
|
||||
$this->apiKey = $source['api_key'];
|
||||
$this->baseUrl = rtrim($source['api_url'], '/');
|
||||
}
|
||||
|
||||
protected function executeSync(string $syncType): void
|
||||
{
|
||||
if (empty($this->apiKey) || empty($this->baseUrl)) {
|
||||
throw new Exception('Jellyfin API key and URL not configured');
|
||||
}
|
||||
|
||||
$this->logProgress('Starting Jellyfin library sync...');
|
||||
$this->logProgress("Jellyfin URL: {$this->baseUrl}");
|
||||
$this->logProgress("API Key: " . (empty($this->apiKey) ? 'NOT SET' : 'SET'));
|
||||
|
||||
try {
|
||||
$userId = $this->getUserId();
|
||||
$this->logProgress("User ID: {$userId}");
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Error getting user ID: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Sync movies
|
||||
try {
|
||||
$this->logProgress('Fetching movies from Jellyfin...');
|
||||
$movies = $this->getJellyfinItems('Movie');
|
||||
$this->logProgress("Found " . count($movies) . " movies in Jellyfin");
|
||||
|
||||
if (empty($movies)) {
|
||||
$this->logProgress('No movies found in Jellyfin library');
|
||||
$this->logProgress("Processed {$this->processedCount} items");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($movies as $movieData) {
|
||||
$this->syncMovie($movieData);
|
||||
$this->processedCount++;
|
||||
}
|
||||
|
||||
$this->logProgress("Successfully processed {$this->processedCount} movies");
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Error syncing movies: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// TODO: Sync TV shows and episodes when TvShow model is implemented
|
||||
// $this->syncTvShows();
|
||||
|
||||
$this->logProgress("Processed {$this->processedCount} items");
|
||||
}
|
||||
|
||||
private function syncMovies(): void
|
||||
{
|
||||
try {
|
||||
$movies = $this->getJellyfinItems('Movie');
|
||||
|
||||
foreach ($movies as $movieData) {
|
||||
$this->syncMovie($movieData);
|
||||
$this->processedCount++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Error syncing movies: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function syncTvShows(): void
|
||||
{
|
||||
try {
|
||||
$tvShows = $this->getJellyfinItems('Series');
|
||||
|
||||
foreach ($tvShows as $showData) {
|
||||
$this->syncTvShow($showData);
|
||||
$this->processedCount++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Error syncing TV shows: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function getJellyfinItems(string $type): array
|
||||
{
|
||||
try {
|
||||
$url = "{$this->baseUrl}/Users/{$this->getUserId()}/Items";
|
||||
$this->logProgress("Fetching {$type} from: {$url}");
|
||||
|
||||
$response = $this->httpClient->get($url, [
|
||||
'query' => [
|
||||
'IncludeItemTypes' => $type,
|
||||
'Recursive' => 'true',
|
||||
'Fields' => 'ProviderIds,Overview,PremiereDate,CommunityRating,OfficialRating,Genres,Studios,RunTimeTicks'
|
||||
]
|
||||
]);
|
||||
|
||||
$httpCode = $response->getStatusCode();
|
||||
$this->logProgress("HTTP Response Code: {$httpCode}");
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
throw new Exception("Jellyfin API returned HTTP {$httpCode}");
|
||||
}
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
$itemCount = count($data['Items'] ?? []);
|
||||
$this->logProgress("Successfully fetched {$itemCount} {$type} items");
|
||||
|
||||
return $data['Items'] ?? [];
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Failed to fetch Jellyfin items: ' . $e->getMessage());
|
||||
throw new Exception('Failed to fetch Jellyfin items: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function getUserId(): string
|
||||
{
|
||||
try {
|
||||
$url = "{$this->baseUrl}/Users";
|
||||
$this->logProgress("Getting user ID from: {$url}");
|
||||
|
||||
$response = $this->httpClient->get($url);
|
||||
$httpCode = $response->getStatusCode();
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
throw new Exception("Jellyfin Users API returned HTTP {$httpCode}");
|
||||
}
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
|
||||
if (empty($data) || !isset($data[0]['Id'])) {
|
||||
throw new Exception('No users found in Jellyfin or invalid response format');
|
||||
}
|
||||
|
||||
$userId = $data[0]['Id'];
|
||||
$this->logProgress("Using Jellyfin user ID: {$userId}");
|
||||
|
||||
return $userId;
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Failed to get Jellyfin user ID: ' . $e->getMessage());
|
||||
throw new Exception('Failed to get Jellyfin user ID: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function syncMovie(array $movieData): void
|
||||
{
|
||||
$movieModel = new Movie($this->pdo);
|
||||
|
||||
// Check if movie already exists
|
||||
$existingMovie = $movieModel->findAll([
|
||||
'tmdb_id' => $movieData['ProviderIds']['Tmdb'] ?? null,
|
||||
'imdb_id' => $movieData['ProviderIds']['Imdb'] ?? null,
|
||||
'source_id' => $this->source['id']
|
||||
]);
|
||||
|
||||
$movieData = [
|
||||
'title' => $movieData['Name'],
|
||||
'overview' => $movieData['Overview'] ?? null,
|
||||
'release_date' => $movieData['PremiereDate'] ? date('Y-m-d', strtotime($movieData['PremiereDate'])) : null,
|
||||
'runtime_minutes' => $movieData['RunTimeTicks'] ? intval($movieData['RunTimeTicks'] / (10000000 * 60)) : null,
|
||||
'rating' => $movieData['CommunityRating'] ?? null,
|
||||
'imdb_id' => $movieData['ProviderIds']['Imdb'] ?? null,
|
||||
'tmdb_id' => $movieData['ProviderIds']['Tmdb'] ?? null,
|
||||
'poster_url' => $this->getImageUrl($movieData['Id'], 'Primary'),
|
||||
'backdrop_url' => $this->getImageUrl($movieData['Id'], 'Backdrop'),
|
||||
'source_id' => $this->source['id'],
|
||||
'metadata' => json_encode([
|
||||
'jellyfin_id' => $movieData['Id'],
|
||||
'genres' => $movieData['Genres'] ?? [],
|
||||
'studios' => $movieData['Studios'] ?? []
|
||||
])
|
||||
];
|
||||
|
||||
if (empty($existingMovie)) {
|
||||
$movieModel->create($movieData);
|
||||
$this->newCount++;
|
||||
} else {
|
||||
$movieModel->update($existingMovie[0]['id'], $movieData);
|
||||
$this->updatedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement when TvShow model is created
|
||||
// private function syncTvShow(array $showData): void
|
||||
// {
|
||||
// $showModel = new TvShow($this->pdo);
|
||||
|
||||
// // Check if show already exists
|
||||
// $existingShow = $showModel->findAll([
|
||||
// 'tmdb_id' => $showData['ProviderIds']['Tmdb'] ?? null,
|
||||
// 'imdb_id' => $showData['ProviderIds']['Imdb'] ?? null,
|
||||
// 'source_id' => $this->source->id
|
||||
// ]);
|
||||
|
||||
// $showData = [
|
||||
// 'title' => $showData['Name'],
|
||||
// 'overview' => $showData['Overview'] ?? null,
|
||||
// 'first_air_date' => $showData['PremiereDate'] ? date('Y-m-d', strtotime($showData['PremiereDate'])) : null,
|
||||
// 'rating' => $showData['CommunityRating'] ?? null,
|
||||
// 'imdb_id' => $showData['ProviderIds']['Imdb'] ?? null,
|
||||
// 'tmdb_id' => $showData['ProviderIds']['Tmdb'] ?? null,
|
||||
// 'poster_url' => $this->getImageUrl($showData['Id'], 'Primary'),
|
||||
// 'backdrop_url' => $this->getImageUrl($showData['Id'], 'Backdrop'),
|
||||
// 'source_id' => $this->source->id,
|
||||
// 'metadata' => json_encode([
|
||||
// 'jellyfin_id' => $showData['Id'],
|
||||
// 'genres' => $showData['Genres'] ?? []
|
||||
// ])
|
||||
// ];
|
||||
|
||||
// if (empty($existingShow)) {
|
||||
// $showId = $showModel->create($showData);
|
||||
// $this->newCount++;
|
||||
// } else {
|
||||
// $showId = $existingShow[0]['id'];
|
||||
// $showModel->update($showId, $showData);
|
||||
// $this->updatedCount++;
|
||||
// }
|
||||
|
||||
// // Sync episodes for this show
|
||||
// $this->syncEpisodes($showId, $showData['Id']);
|
||||
// }
|
||||
|
||||
// TODO: Implement when TvEpisode model is created
|
||||
// private function syncEpisodes(int $showId, string $jellyfinShowId): void
|
||||
// {
|
||||
// try {
|
||||
// $episodes = $this->getShowEpisodes($jellyfinShowId);
|
||||
|
||||
// foreach ($episodes as $episodeData) {
|
||||
// $this->syncEpisode($showId, $episodeData);
|
||||
// }
|
||||
// } catch (Exception $e) {
|
||||
// $this->logProgress('Error syncing episodes for show ' . $jellyfinShowId . ': ' . $e->getMessage());
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO: Implement when TvEpisode model is created
|
||||
// private function syncEpisode(int $showId, array $episodeData): void
|
||||
// {
|
||||
// $episodeModel = new TvEpisode($this->pdo);
|
||||
|
||||
// $episodeData = [
|
||||
// 'title' => $episodeData['Name'],
|
||||
// 'overview' => $episodeData['Overview'] ?? null,
|
||||
// 'season_number' => $episodeData['ParentIndexNumber'] ?? 1,
|
||||
// 'episode_number' => $episodeData['IndexNumber'] ?? 1,
|
||||
// 'air_date' => $episodeData['PremiereDate'] ? date('Y-m-d', strtotime($episodeData['PremiereDate'])) : null,
|
||||
// 'runtime_minutes' => $episodeData['RunTimeTicks'] ? intval($episodeData['RunTimeTicks'] / (10000000 * 60)) : null,
|
||||
// 'rating' => $episodeData['CommunityRating'] ?? null,
|
||||
// 'tv_show_id' => $showId,
|
||||
// 'source_id' => $this->source->id,
|
||||
// 'metadata' => json_encode([
|
||||
// 'jellyfin_id' => $episodeData['Id']
|
||||
// ])
|
||||
// ];
|
||||
|
||||
// $episodeModel->create($episodeData);
|
||||
// }
|
||||
|
||||
private function getImageUrl(string $itemId, string $type): ?string
|
||||
{
|
||||
if (empty($itemId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "{$this->baseUrl}/Items/{$itemId}/Images/{$type}?maxWidth=400";
|
||||
}
|
||||
|
||||
protected function getProcessedCount(): int
|
||||
{
|
||||
return $this->processedCount;
|
||||
}
|
||||
|
||||
protected function getNewCount(): int
|
||||
{
|
||||
return $this->newCount;
|
||||
}
|
||||
|
||||
protected function getUpdatedCount(): int
|
||||
{
|
||||
return $this->updatedCount;
|
||||
}
|
||||
|
||||
protected function getDeletedCount(): int
|
||||
{
|
||||
return 0; // Jellyfin doesn't provide deletion info in this context
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user