390 lines
14 KiB
PHP
390 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Tmdb;
|
|
|
|
use Aimeos\Map;
|
|
use App\Enum\MediaType;
|
|
use App\ValueObject\ResultFactory;
|
|
use Carbon\Carbon;
|
|
use Psr\Cache\CacheItemPoolInterface;
|
|
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|
use Symfony\Contracts\Cache\CacheInterface;
|
|
use Symfony\Contracts\Cache\ItemInterface;
|
|
use Tmdb\Api\Find;
|
|
use Tmdb\Client;
|
|
use Tmdb\Event\BeforeRequestEvent;
|
|
use Tmdb\Event\Listener\Psr6CachedRequestListener;
|
|
use Tmdb\Event\Listener\Request\AcceptJsonRequestListener;
|
|
use Tmdb\Event\Listener\Request\ApiTokenRequestListener;
|
|
use Tmdb\Event\Listener\Request\ContentTypeJsonRequestListener;
|
|
use Tmdb\Event\Listener\Request\UserAgentRequestListener;
|
|
use Tmdb\Event\Listener\RequestListener;
|
|
use Tmdb\Event\RequestEvent;
|
|
use Tmdb\Model\Movie;
|
|
use Tmdb\Model\Search\SearchQuery\KeywordSearchQuery;
|
|
use Tmdb\Model\Tv;
|
|
use Tmdb\Repository\MovieRepository;
|
|
use Tmdb\Repository\SearchRepository;
|
|
use Tmdb\Repository\TvEpisodeRepository;
|
|
use Tmdb\Repository\TvRepository;
|
|
use Tmdb\Repository\TvSeasonRepository;
|
|
use Tmdb\Token\Api\ApiToken;
|
|
use Tmdb\Token\Api\BearerToken;
|
|
|
|
class Tmdb
|
|
{
|
|
protected Client $client;
|
|
|
|
protected MovieRepository $movieRepository;
|
|
|
|
protected TvRepository $tvRepository;
|
|
|
|
const POSTER_IMG_PATH = "https://image.tmdb.org/t/p/w500";
|
|
|
|
public function __construct(
|
|
private readonly CacheItemPoolInterface $cache,
|
|
private readonly EventDispatcherInterface $eventDispatcher,
|
|
#[Autowire(env: 'TMDB_API')] string $apiKey,
|
|
) {
|
|
$this->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' => false,
|
|
'only_for_specified_models' => []
|
|
]
|
|
]
|
|
);
|
|
|
|
/**
|
|
* 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);
|
|
|
|
$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 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 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
|
|
{
|
|
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: (new \DateTime($data['first_air_date']))->format('Y'),
|
|
mediaType: "tvshows",
|
|
episodes: $data['episodes'],
|
|
);
|
|
}
|
|
|
|
private function parseEpisode(array $data, string $posterBasePath): TmdbResult
|
|
{
|
|
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: (new \DateTime($data['air_date']))->format('m/d/Y'),
|
|
);
|
|
}
|
|
|
|
private function parseMovie(array $data, string $posterBasePath): TmdbResult
|
|
{
|
|
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",
|
|
);
|
|
}
|
|
|
|
|
|
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 "";
|
|
}
|
|
}
|
|
}
|