Files
torsearch/src/Tmdb/Tmdb.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 "";
}
}
}