diff --git a/src/Base/Enum/MediaType.php b/src/Base/Enum/MediaType.php index cd8f9e2..3eb1583 100644 --- a/src/Base/Enum/MediaType.php +++ b/src/Base/Enum/MediaType.php @@ -6,4 +6,5 @@ enum MediaType: string { case Movie = 'movies'; case TvShow = 'tvshows'; + case TvEpisode = 'tvepisode'; } diff --git a/src/Base/Util/ImdbMatcher.php b/src/Base/Util/ImdbMatcher.php index 1c7aa78..d817a0c 100644 --- a/src/Base/Util/ImdbMatcher.php +++ b/src/Base/Util/ImdbMatcher.php @@ -6,6 +6,6 @@ class ImdbMatcher { public static function isMatch(string $imdbId): bool { - return preg_match('/^tt\d{7}$/', $imdbId); + return preg_match('/^tt\d{7,20}$/', $imdbId); } } diff --git a/src/Search/Action/Handler/GetMediaInfoHandler.php b/src/Search/Action/Handler/GetMediaInfoHandler.php index 6ed0395..7f4aa4b 100644 --- a/src/Search/Action/Handler/GetMediaInfoHandler.php +++ b/src/Search/Action/Handler/GetMediaInfoHandler.php @@ -2,9 +2,13 @@ namespace App\Search\Action\Handler; +use Aimeos\Map; +use App\Base\Enum\MediaType; use App\Search\Action\Command\GetMediaInfoCommand; use App\Search\Action\Result\GetMediaInfoResult; use App\Tmdb\Tmdb; +use App\Tmdb\TmdbClient; +use App\Tmdb\TmdbResult; use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\HandlerInterface; use OneToMany\RichBundle\Contract\ResultInterface; @@ -13,14 +17,30 @@ use OneToMany\RichBundle\Contract\ResultInterface; class GetMediaInfoHandler implements HandlerInterface { public function __construct( - private readonly Tmdb $tmdb, + private readonly TmdbClient $tmdb, ) {} public function handle(CommandInterface $command): ResultInterface { - $media = $this->tmdb->mediaDetails($command->imdbId, $command->mediaType); + $handlers = [ + MediaType::Movie->value => 'getMovieDetails', + MediaType::TvShow->value => 'getTvshowDetails', + ]; + $handler = $handlers[$command->mediaType]; + $media = $this->$handler($command); $relatedMedia = $this->tmdb->relatedMedia($media->tmdbId, $command->mediaType); return new GetMediaInfoResult($media, $relatedMedia, $command->season, $command->episode); } + + private function getMovieDetails(CommandInterface $command): TmdbResult + { + return $this->tmdb->movieDetails($command->imdbId); + } + + private function getTvshowDetails(CommandInterface $command): TmdbResult + { + $media = $this->tmdb->tvshowDetails($command->imdbId); + return $media; + } } diff --git a/src/Search/Action/Handler/SearchHandler.php b/src/Search/Action/Handler/SearchHandler.php index adf1c15..7752ec6 100644 --- a/src/Search/Action/Handler/SearchHandler.php +++ b/src/Search/Action/Handler/SearchHandler.php @@ -6,6 +6,8 @@ use App\Base\Util\ImdbMatcher; use App\Search\Action\Result\RedirectToMediaResult; use App\Search\Action\Result\SearchResult; use App\Tmdb\Tmdb; +use App\Tmdb\TmdbClient; +use App\Tmdb\TmdbResult; use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\HandlerInterface; use OneToMany\RichBundle\Contract\ResultInterface; @@ -14,13 +16,13 @@ use OneToMany\RichBundle\Contract\ResultInterface; class SearchHandler implements HandlerInterface { public function __construct( - private Tmdb $tmdb, + private TmdbClient $tmdb, ) {} public function handle(CommandInterface $command): ResultInterface { - if (ImdbMatcher::isMatch($command->term)) { - $result = $this->tmdb->findByImdbId($command->term); + $result = $this->tmdb->search($command->term); + if ($result instanceof TmdbResult) { return new RedirectToMediaResult( imdbId: $result->imdbId, mediaType: $result->mediaType, diff --git a/src/Search/Action/Result/GetMediaInfoResult.php b/src/Search/Action/Result/GetMediaInfoResult.php index 7004c0f..e79a355 100644 --- a/src/Search/Action/Result/GetMediaInfoResult.php +++ b/src/Search/Action/Result/GetMediaInfoResult.php @@ -2,6 +2,7 @@ namespace App\Search\Action\Result; +use Aimeos\Map; use App\Tmdb\TmdbResult; use OneToMany\RichBundle\Contract\ResultInterface; @@ -10,7 +11,7 @@ class GetMediaInfoResult implements ResultInterface { public function __construct( public TmdbResult $media, - public array $relatedMedia, + public Map|array $relatedMedia, public ?int $season, public ?int $episode, ) {} diff --git a/src/Search/Action/Result/SearchResult.php b/src/Search/Action/Result/SearchResult.php index 12be602..1574cde 100644 --- a/src/Search/Action/Result/SearchResult.php +++ b/src/Search/Action/Result/SearchResult.php @@ -2,6 +2,7 @@ namespace App\Search\Action\Result; +use Aimeos\Map; use OneToMany\RichBundle\Contract\ResultInterface; /** @implements ResultInterface */ @@ -9,6 +10,6 @@ class SearchResult implements ResultInterface { public function __construct( public string $term = "", - public array $results = [] + public Map|array $results = [], ) {} } diff --git a/src/Tmdb/Framework/Controller/ApiController.php b/src/Tmdb/Framework/Controller/ApiController.php index 524bb64..c56bce7 100644 --- a/src/Tmdb/Framework/Controller/ApiController.php +++ b/src/Tmdb/Framework/Controller/ApiController.php @@ -4,6 +4,7 @@ namespace App\Tmdb\Framework\Controller; use App\Base\Util\ImdbMatcher; use App\Tmdb\Tmdb; +use App\Tmdb\TmdbClient; use App\Tmdb\TmdbResult; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; @@ -13,7 +14,7 @@ use Symfony\Component\Routing\Attribute\Route; class ApiController extends AbstractController { #[Route('/api/tmdb/ajax-search', name: 'api_tmdb_ajax_search', methods: ['GET'])] - public function test(Tmdb $tmdb, Request $request): Response + public function test(TmdbClient $tmdb, Request $request): Response { $results = []; diff --git a/src/Tmdb/Tmdb.php b/src/Tmdb/Tmdb.php deleted file mode 100644 index 1f1291b..0000000 --- a/src/Tmdb/Tmdb.php +++ /dev/null @@ -1,449 +0,0 @@ -client = new Client( - [ - /** @var ApiToken|BearerToken */ - 'api_token' => new BearerToken($apiKey), - 'secure' => true, - 'base_uri' => Client::TMDB_URI, - 'event_dispatcher' => [ - 'adapter' => $this->eventDispatcher, - ], - // We make use of PSR-17 and PSR-18 auto discovery to automatically guess these, but preferably set these explicitly. - 'http' => [ - 'client' => null, - 'request_factory' => null, - 'response_factory' => null, - 'stream_factory' => null, - 'uri_factory' => null, - ], - 'hydration' => [ - 'event_listener_handles_hydration' => true, - 'only_for_specified_models' => [ - Movie::class, - Tv::class, - Tv\Episode::class, - ] - ] - ] - ); - - /** - * Required event listeners and events to be registered with the PSR-14 Event Dispatcher. - */ - $requestListener = new Psr6CachedRequestListener( - $this->client->getHttpClient(), - $this->eventDispatcher, - $cache, - $this->client->getHttpClient()->getPsr17StreamFactory(), - [] - ); - $this->eventDispatcher->addListener(RequestEvent::class, $requestListener); - - $apiTokenListener = new ApiTokenRequestListener($this->client->getToken()); - $this->eventDispatcher->addListener(BeforeRequestEvent::class, $apiTokenListener); - - $acceptJsonListener = new AcceptJsonRequestListener(); - $this->eventDispatcher->addListener(BeforeRequestEvent::class, $acceptJsonListener); - - $jsonContentTypeListener = new ContentTypeJsonRequestListener(); - $this->eventDispatcher->addListener(BeforeRequestEvent::class, $jsonContentTypeListener); - - $userAgentListener = new UserAgentRequestListener(); - $this->eventDispatcher->addListener(BeforeRequestEvent::class, $userAgentListener); - -// $hydrationListener = new HydrationListener($this->eventDispatcher); -// $this->eventDispatcher->addListener(HydrationEvent::class, $hydrationListener); - - $this->movieRepository = new MovieRepository($this->client); - $this->tvRepository = new TvRepository($this->client); - } - - public function popularMovies(int $page = 1, ?int $limit = null) - { - $movies = $this->movieRepository->getPopular(['page' => $page]); - - $movies = $movies->map(function ($movie) use ($movies) { - return $this->parseResult($movies[$movie], "movie"); - }); - - $movies = Map::from($movies->toArray())->filter(function ($movie) { - return $movie !== null - && $movie->imdbId !== null - && $movie->tmdbId !== null - && $movie->title !== null - && $movie->poster !== null - && $movie->description !== null - && $movie->mediaType !== null; - }); - - $movies = array_values($movies->toArray()); - - if (null !== $limit) { - $movies = array_slice($movies, 0, $limit); - } - - return $movies; - } - - public function popularTvShows(int $page = 1, ?int $limit = null) - { - $movies = $this->tvRepository->getPopular(['page' => $page]); - - $movies = $movies->map(function ($movie) use ($movies) { - return $this->parseResult($movies[$movie], "movie"); - }); - - $movies = Map::from($movies->toArray())->filter(function ($movie) { - return $movie !== null - && $movie->imdbId !== null - && $movie->tmdbId !== null - && $movie->title !== null - && $movie->poster !== null - && $movie->description !== null - && $movie->mediaType !== null; - }); - - $movies = array_values($movies->toArray()); - - if (null !== $limit) { - $movies = array_slice($movies, 0, $limit); - } - - return $movies; - } - - public function search(string $term, int $page = 1) - { - $searchRepository = new SearchRepository($this->client); - $searchResults = $searchRepository->searchMulti($term, new KeywordSearchQuery(['page' => $page])); - - $results = []; - foreach ($searchResults as $result) { - if (!$result instanceof Movie && !$result instanceof Tv) { - continue; - } - - $results[] = $this->parseResult($result); - } - - $results = array_filter($results, fn ($result) => null !== $result->imdbId); - - return $results; - } - - public function find(string $id) - { - $finder = new Find($this->client); - $result = $finder->findBy($id, ['external_source' => 'imdb_id']); - - if (count($result['movie_results']) > 0) { - return $result['movie_results'][0]['id']; - } elseif (count($result['tv_results']) > 0) { - return $result['tv_results'][0]['id']; - } elseif (count($result['tv_episode_results']) > 0) { - return $result['tv_episode_results'][0]['show_id']; - } - - throw new \Exception("No results found for $id"); - } - - public function findByImdbId(string $imdbId) - { - $finder = new Find($this->client); - $result = $finder->findBy($imdbId, ['external_source' => 'imdb_id']); - - if (count($result['movie_results']) > 0) { - $result = $result['movie_results'][0]; - $mediaType = MediaType::Movie->value; - } elseif (count($result['tv_results']) > 0) { - $result = $result['tv_results'][0]; - $mediaType = MediaType::TvShow->value; - } elseif (count($result['tv_episode_results']) > 0) { - $result = $result['tv_episode_results'][0]; - $mediaType = MediaType::TvShow->value; - } - - $result['media_type'] = $mediaType; - $result = $this->mediaDetails($imdbId, $result['media_type']); - - return $result; - } - - public function movieDetails(string $id) - { - $client = new MovieRepository($this->client); - $details = $client->getApi()->getMovie($id, ['append_to_response' => 'external_ids']); - return $this->parseResult($details, "movie"); - } - - public function tvDetails(string $id) - { - $client = new TvRepository($this->client); - $details = $client->getApi()->getTvshow($id, ['append_to_response' => 'external_ids,seasons']); - $details = $this->getEpisodesFromSeries($details); - return $this->parseResult($details, "tvshow"); - } - - public function episodeDetails(string $id, string $season, string $episode) - { - $client = new TvEpisodeRepository($this->client); - $result = $client->getApi()->getEpisode($id, $season, $episode, ['append_to_response' => 'external_ids']); - return $this->parseResult($result, "episode"); - } - - public function getEpisodesFromSeries(array $series) - { - $client = new TvSeasonRepository($this->client); - foreach ($series['seasons'] as $season) { - if ($season['episode_count'] <= 0 || $season['name'] === 'Specials') { - continue; - } - - $series['episodes'][$season['season_number']] = Map::from( - $client->getApi()->getSeason($series['id'], $season['season_number'])['episodes'] - )->map(function ($data) { - $data['poster'] = (null !== $data['still_path']) ? self::POSTER_IMG_PATH . $data['still_path'] : null; - return $data; - })->toArray(); - } - return $series; - } - - public function relatedMedia(string $tmdbId, string $mediaType, int $maxResults = 20) - { - $repos = [ - 'movies' => $this->movieRepository, - 'tvshows' => $this->tvRepository, - ]; - $results = $repos[$mediaType]->getRecommendations($tmdbId, ['append_to_response' => 'external_ids']); - - $results = Map::from(array_values($results->toArray())) - ->take($maxResults) - ->map(function ($result) use ($mediaType) { - return $this->parseResult($result, $mediaType); - })->toArray(); - - dd($results); - } - - public function mediaDetails(string $id, string $type) - { - $id = $this->find($id); - - if ($type === "movies") { - return $this->movieDetails($id); - } else { - return $this->tvDetails($id); - } - } - - private function parseResult($result, $mediaType = null) - { - if (is_array($result)) { - return $this->parseFromArray($result, $mediaType); - } else { - return $this->parseFromObject($result); - } - } - - private function parseFromArray($data, $mediaType) - { - if (null === $mediaType) { - throw new \Exception("A media type must be set when parsing from an array."); - } - - if ($mediaType === 'movie') { - $result = $this->parseMovie($data, self::POSTER_IMG_PATH); - } elseif ($mediaType === 'tvshow') { - $result = $this->parseTvShow($data, self::POSTER_IMG_PATH); - } elseif ($mediaType === 'episode') { - $result = $this->parseEpisode($data, self::POSTER_IMG_PATH); - } - - return $result; - } - - private function parseTvShow(array $data, string $posterBasePath): TmdbResult - { - if (!in_array($data['first_air_date'], ['', null,])) { - $airDate = (new \DateTime($data['first_air_date']))->format('Y-m-d'); - } else { - $airDate = null; - } - return new TmdbResult( - imdbId: $data['external_ids']['imdb_id'], - tmdbId: $data['id'], - title: $data['name'], - poster: (null !== $data['poster_path']) ? $posterBasePath . $data['poster_path'] : null, - description: $data['overview'], - year: $airDate, - mediaType: "tvshows", - episodes: $data['episodes'], - ); - } - - private function parseEpisode(array $data, string $posterBasePath): TmdbResult - { - if (!in_array($data['air_date'], ['', null,])) { - $airDate = (new \DateTime($data['air_date']))->format('Y-m-d'); - } else { - $airDate = null; - } - return new TmdbResult( - imdbId: $data['external_ids']['imdb_id'], - tmdbId: $data['id'], - title: $data['name'], - poster: (null !== $data['still_path']) ? $posterBasePath . $data['still_path'] : null, - description: $data['overview'], - year: (new \DateTime($data['air_date']))->format('Y'), - mediaType: "tvshows", - episodes: null, - episodeAirDate: $airDate, - ); - } - - private function parseMovie(array $data, string $posterBasePath): TmdbResult - { - if (!in_array($data['release_date'], ['', null,])) { - $airDate = (new \DateTime($data['release_date']))->format('Y-m-d'); - } else { - $airDate = null; - } - return new TmdbResult( - imdbId: $data['external_ids']['imdb_id'], - tmdbId: $data['id'], - title: $data['title'], - poster: (null !== $data['poster_path']) ? $posterBasePath . $data['poster_path'] : null, - description: $data['overview'], - year: (new \DateTime($data['release_date']))->format('Y'), - mediaType: "movies", - episodeAirDate: $airDate, - ); - } - - - private function parseFromObject($result): TmdbResult - { - $mediaType = $result instanceof Movie ? MediaType::Movie->value : MediaType::TvShow->value; - $tmdbResult = new TmdbResult(); - $tmdbResult->mediaType = $mediaType; - $tmdbResult->tmdbId = $result->getId(); - $tmdbResult->imdbId = $this->getImdbId($result->getId(), $mediaType); - $tmdbResult->title = $this->getTitle($result, $mediaType); - $tmdbResult->poster = self::POSTER_IMG_PATH . $result->getPosterImage(); - $tmdbResult->year = $this->getReleaseDate($result, $mediaType); - $tmdbResult->description = $result->getOverview(); - return $tmdbResult; - } - - public function getImdbId(string $tmdbId, $mediaType) - { - $externalIds = $this->cache->get("tmdb.externalIds.{$tmdbId}", - function (ItemInterface $item) use ($tmdbId, $mediaType) { - switch (MediaType::tryFrom($mediaType)->value) { - case MediaType::Movie->value: - return $this->movieRepository->getExternalIds($tmdbId); - case MediaType::TvShow->value: - return $this->tvRepository->getExternalIds($tmdbId); - default: - return null; - } - }); - - if (null === $externalIds) { - return null; - } - - return $externalIds->getImdbId() !== "" ? $externalIds->getImdbId() : "null"; - } - - public function getImages($tmdbId, $mediaType) - { - return $this->cache->get("tmdb.images.{$tmdbId}", - function (ItemInterface $item) use ($tmdbId, $mediaType) { - switch (MediaType::tryFrom($mediaType)->value) { - case MediaType::Movie->value: - return $this->movieRepository->getImages($tmdbId); - case MediaType::TvShow->value: - return $this->tvRepository->getImages($tmdbId); - default: - return null; - } - }); - } - - private function getReleaseDate($result, $mediaType): string - { - switch (MediaType::tryFrom($mediaType)->value) { - case MediaType::Movie->value: - return ($result->getReleaseDate() instanceof \DateTime) - ? $result->getReleaseDate()->format('Y') - : $result->getReleaseDate(); - case MediaType::TvShow->value: - return ($result->getFirstAirDate() instanceof \DateTime) - ? $result->getFirstAirDate()->format('Y') - : $result->getFirstAirDate(); - default: - return ""; - } - } - - private function getTitle($result, $mediaType): string - { - switch (MediaType::tryFrom($mediaType)->value) { - case MediaType::Movie->value: - return $result->getTitle(); - case MediaType::TvShow->value: - return $result->getName(); - default: - return ""; - } - } -} diff --git a/src/Tmdb/TmdbClient.php b/src/Tmdb/TmdbClient.php index 0734351..c92c263 100644 --- a/src/Tmdb/TmdbClient.php +++ b/src/Tmdb/TmdbClient.php @@ -4,12 +4,14 @@ namespace App\Tmdb; use Aimeos\Map; use App\Base\Enum\MediaType; +use App\Base\Util\ImdbMatcher; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\ObjectMapper\ObjectMapper; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Contracts\Cache\ItemInterface; +use Tmdb\Api\Find; use Tmdb\Client; use Tmdb\Event\BeforeRequestEvent; use Tmdb\Event\HydrationEvent; @@ -22,22 +24,22 @@ use Tmdb\Event\RequestEvent; use Tmdb\Model\Movie; use Tmdb\Model\Tv; use Tmdb\Repository\MovieRepository; +use Tmdb\Repository\SearchRepository; use Tmdb\Repository\TvRepository; +use Tmdb\Repository\TvSeasonRepository; use Tmdb\Token\Api\ApiToken; use Tmdb\Token\Api\BearerToken; class TmdbClient { - protected Client $client; - - protected MovieRepository $movieRepository; - - protected TvRepository $tvRepository; - - protected ObjectMapper $objectMapper; - const POSTER_IMG_PATH = "https://image.tmdb.org/t/p/w500"; + protected Client $client; + protected MovieRepository $movieRepository; + protected TvRepository $tvRepository; + protected TvSeasonRepository $tvSeasonRepository; + protected SearchRepository $searchRepository; + public function __construct( private readonly SerializerInterface $serializer, private readonly CacheItemPoolInterface $cache, @@ -62,12 +64,8 @@ class TmdbClient 'uri_factory' => null, ], 'hydration' => [ - 'event_listener_handles_hydration' => true, - 'only_for_specified_models' => [ - Movie::class, - Tv::class, - Tv\Episode::class, - ] + 'event_listener_handles_hydration' => false, + 'only_for_specified_models' => [] ] ] ); @@ -96,54 +94,102 @@ class TmdbClient $userAgentListener = new UserAgentRequestListener(); $this->eventDispatcher->addListener(BeforeRequestEvent::class, $userAgentListener); -// $hydrationListener = new HydrationListener($this->eventDispatcher); -// $this->eventDispatcher->addListener(HydrationEvent::class, $hydrationListener); - $this->movieRepository = new MovieRepository($this->client); $this->tvRepository = new TvRepository($this->client); + $this->tvSeasonRepository = new TvSeasonRepository($this->client); + $this->searchRepository = new SearchRepository($this->client); } - public function search() {} + public function search(string $term): TmdbResult|Map + { + if (ImdbMatcher::isMatch($term)) { + $handlers = [ + 'movie' => 'movieDetails', + 'tvshow' => 'tvshowDetails', + ]; + $data = $this->findByImdbId($term); + $handler = $handlers[$data['media_type']]; + return $this->$handler($term); + } + return $this->parseListOfResults( + $this->searchRepository->getApi()->searchMulti($term), + "movies" + ); + } - public function find() {} + public function movieDetails(string $imdbId): ?TmdbResult + { + $tmdbId = $this->findByImdbId($imdbId)['id']; + return $this->parseResult( + $this->movieRepository->getApi()->getMovie($tmdbId, ['append_to_response' => 'external_ids']), + MediaType::Movie->value, + $imdbId + ); + } - public function movieDetails() {} + public function tvshowDetails(string $imdbId): ?TmdbResult + { + $tmdbId = $this->findByImdbId($imdbId)['id']; + $media = $this->tvRepository->getApi()->getTvShow($tmdbId, ['append_to_response' => 'external_ids']); + $media['seasons'] = Map::from($media['seasons'])->filter(function ($data) { + return $data['season_number'] !== 0 && + strtolower($data['name']) !== 'specials'; + })->map(function ($data) use ($media) { + return $this->tvSeasonDetails($media['id'], $data['season_number'])['episodes']; + })->toArray(); - public function tvshowDetails() {} + return $this->parseResult( + $media, + MediaType::TvShow->value, + $imdbId + ); + } - public function tvEpisodeDetails() {} + public function tvSeasonDetails(string $tmdbId, int $season): array + { + $result = $this->tvSeasonRepository->getApi()->getSeason($tmdbId, $season); + $result['episodes'] = Map::from($result['episodes'])->map(function ($data) { + $data['still_path'] = self::POSTER_IMG_PATH . $data['still_path']; + $data['poster'] = $data['still_path']; + return $data; + })->toArray(); + return $result; + } - public function relatedMedia() {} + public function relatedMedia(string $tmdbId, string $mediaType, int $resultCount = 6): Map + { + $repos = [ + 'movies' => $this->movieRepository, + 'tvshows' => $this->tvRepository, + ]; + $results = $repos[$mediaType]->getApi()->getRecommendations($tmdbId); + + return $this->parseListOfResults( + $results, + $mediaType, + $resultCount + ); + } public function popularMovies(int $resultCount = 6): Map { - $results = $this->movieRepository->getApi()->getPopular(); - return Map::from($results['results'])->map(function ($result) { - $result['external_ids'] = $this->getExternalIds($result['id'], MediaType::Movie->value); - return $result; - })->filter(function ($result) { - return array_key_exists('imdb_id', $result['external_ids']); - })->map(function ($result) { - $result = $this->serializer->denormalize($result, TmdbResult::class, context: ['media_type' => MediaType::Movie->value]); - return $result; - })->slice(0, $resultCount); + return $this->parseListOfResults( + $this->movieRepository->getApi()->getPopular(), + "movies", + $resultCount + ); } public function popularTvShows(int $resultCount = 6): Map { - $results = $this->tvRepository->getApi()->getPopular(); - return Map::from($results['results'])->map(function ($result) { - $result['external_ids'] = $this->getExternalIds($result['id'], MediaType::Movie->value); - return $result; - })->filter(function ($result) { - return array_key_exists('imdb_id', $result['external_ids']) && $result['external_ids']['imdb_id'] !== null && $result['external_ids']['imdb_id'] !== ""; - })->map(function ($result) { - $result = $this->serializer->denormalize($result, TmdbResult::class, context: ['media_type' => MediaType::TvShow->value]); - return $result; - })->slice(0, $resultCount); + return $this->parseListOfResults( + $this->tvRepository->getApi()->getPopular(), + "tvshows", + $resultCount + ); } - public function getExternalIds(string $tmdbId, $mediaType) + private function getExternalIds(string $tmdbId, $mediaType): ?array { try { switch (MediaType::tryFrom($mediaType)->value) { @@ -163,4 +209,51 @@ class TmdbClient return $externalIds; } + + private function findByImdbId(string $imdbId): array + { + $finder = new Find($this->client); + $result = $finder->findBy($imdbId, ['external_source' => 'imdb_id']); + + if (count($result['movie_results']) > 0) { + return $result['movie_results'][0]; + } elseif (count($result['tv_results']) > 0) { + return $result['tv_results'][0]; + } elseif (count($result['tv_episode_results']) > 0) { + return $result['tv_episode_results'][0]; + } + + throw new \Exception("No results found for $imdbId"); + } + + private function parseResult(array $data, string $mediaType, string $imdbId): TmdbResult + { + if (!array_key_exists('external_ids', $data)) { + $data['external_ids'] = ['imdb_id' => $imdbId]; + } + return $this->serializer->denormalize($data, TmdbResult::class, context: ['media_type' => $mediaType]); + } + + private function parseListOfResults(array $data, string $mediaType, ?int $resultCount = null): Map + { + $results = Map::from($data['results'])->filter( + fn ($result) => array_key_exists('id', $result) + )->map(function ($result) { + $result['external_ids'] = $this->getExternalIds($result['id'], MediaType::Movie->value); + return $result; + })->filter(function ($result) { + return array_key_exists('id', $result) && + array_key_exists('imdb_id', $result['external_ids']) && + $result['external_ids']['imdb_id'] !== null && + $result['external_ids']['imdb_id'] !== ""; + })->map(function ($result) use ($mediaType) { + return $this->serializer->denormalize($result, TmdbResult::class, context: ['media_type' => $mediaType]); + }); + + if (null !== $resultCount) { + $results = $results->slice(0, $resultCount); + } + + return $results; + } } diff --git a/src/Tmdb/TmdbResult.php b/src/Tmdb/TmdbResult.php index 97c293d..14861cc 100644 --- a/src/Tmdb/TmdbResult.php +++ b/src/Tmdb/TmdbResult.php @@ -11,7 +11,6 @@ class TmdbResult public ?string $imdbId = "", #[SerializedPath('[id]')] public ?int $tmdbId = null, - #[SerializedPath('[title]')] public ?string $title = "", #[SerializedPath('[overview]')] public ?string $description = "", @@ -19,6 +18,7 @@ class TmdbResult public ?\DateTimeInterface $premiereDate = null, public ?string $year = null, public ?string $mediaType = "", + #[SerializedPath('[seasons]')] public ?array $episodes = null, public ?string $episodeAirDate = null, ) {} diff --git a/src/Tmdb/TmdbResultDenormalizer.php b/src/Tmdb/TmdbResultDenormalizer.php index 6e7469d..327bf60 100644 --- a/src/Tmdb/TmdbResultDenormalizer.php +++ b/src/Tmdb/TmdbResultDenormalizer.php @@ -2,6 +2,7 @@ namespace App\Tmdb; +use Aimeos\Map; use App\Base\Enum\MediaType; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -25,11 +26,14 @@ class TmdbResultDenormalizer implements DenormalizerInterface $parsers = [ MediaType::Movie->value => 'parseMovie', MediaType::TvShow->value => 'parseTvShow', + 'movie' => 'parseMovie', + 'tv' => 'parseTvShow', 'tvepisodes' => 'parseEpisode', ]; $denormalized = $this->normalizer->denormalize($data, TmdbResult::class, $format, $context); - return $this->{$parsers[$context['media_type']]}($data, $denormalized); + $parser = array_key_exists('media_type', $data) ? $parsers[$data['media_type']] : $parsers[$context['media_type']]; + return $this->$parser($data, $denormalized); } private function parseTvShow(array $data, TmdbResult $result): TmdbResult @@ -40,13 +44,11 @@ class TmdbResultDenormalizer implements DenormalizerInterface $airDate = null; } + $result->title = $data['original_name']; $result->premiereDate = $airDate; $result->poster = (null !== $data['poster_path']) ? self::POSTER_IMG_PATH . $data['poster_path'] : null; $result->year = (null !== $airDate) ? $airDate->format('Y') : null; $result->mediaType = "tvshows"; - if (array_key_exists('episodes', $data)) { - $result->episodes = $data['episodes']; - } return $result; } @@ -59,6 +61,7 @@ class TmdbResultDenormalizer implements DenormalizerInterface $airDate = null; } + $result->title = $data['original_title']; $result->premiereDate = $airDate; $result->poster = (null !== $data['poster_path']) ? self::POSTER_IMG_PATH . $data['poster_path'] : null; $result->year = (null !== $airDate) ? $airDate->format('Y') : null; diff --git a/src/Twig/Components/TvEpisodeList.php b/src/Twig/Components/TvEpisodeList.php index 719fa59..3fa549d 100644 --- a/src/Twig/Components/TvEpisodeList.php +++ b/src/Twig/Components/TvEpisodeList.php @@ -49,7 +49,7 @@ final class TvEpisodeList } $this->reloadCount++; - +// dd(new TvEpisodePaginator()->paginate($results, $this->pageNumber, $this->perPage)); return new TvEpisodePaginator()->paginate($results, $this->pageNumber, $this->perPage); }