false, 'Logger' => null, 'ReleaseImage' => null, 'Settings' => null, 'TMDb' => null, ]; $options += $defaults; $this->releaseImage = ($options['ReleaseImage'] instanceof ReleaseImage ? $options['ReleaseImage'] : new ReleaseImage()); $this->colorCli = new ColorCLI(); $this->traktcheck = Settings::settingValue('APIs..trakttvclientkey'); if ($this->traktcheck !== null) { $this->traktTv = new TraktTv(['Settings' => null]); } $this->client = new Client(); $this->fanartapikey = Settings::settingValue('APIs..fanarttvkey'); if ($this->fanartapikey !== null) { $this->fanart = new FanartTV($this->fanartapikey); } $this->omdbapikey = Settings::settingValue('APIs..omdbkey'); if ($this->omdbapikey !== null) { $this->omdbApi = new OMDbAPI($this->omdbapikey); } $this->lookuplanguage = Settings::settingValue('indexer.categorise.imdblanguage') !== '' ? (string) Settings::settingValue('indexer.categorise.imdblanguage') : 'en'; $this->config = new Config(); $this->config->language = $this->lookuplanguage; $this->config->throwHttpExceptions = false; $cacheDir = resource_path().'/tmp/imdb_cache'; if (! File::isDirectory($cacheDir)) { File::makeDirectory($cacheDir, 0777, false, true); } $this->config->cachedir = $cacheDir; $this->imdburl = (int) Settings::settingValue('indexer.categorise.imdburl') !== 0; $this->movieqty = Settings::settingValue('..maximdbprocessed') !== '' ? (int) Settings::settingValue('..maximdbprocessed') : 100; $this->showPasswords = (new Releases())->showPasswords(); $this->echooutput = ($options['Echo'] && config('nntmux.echocli')); $this->imgSavePath = NN_COVERS.'movies/'; $this->service = ''; } /** * @param $imdbId * * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|null|object */ public function getMovieInfo($imdbId) { return MovieInfo::query()->where('imdbid', $imdbId)->first(); } /** * Get movie releases with covers for movie browse page. * * * @param $page * @param $cat * @param $start * @param $num * @param $orderBy * @param int $maxAge * @param array $excludedCats * * @return array|mixed */ public function getMovieRange($page, $cat, $start, $num, $orderBy, $maxAge = -1, array $excludedCats = []) { $catsrch = ''; if (\count($cat) > 0 && $cat[0] !== -1) { $catsrch = Category::getCategorySearch($cat); } $order = $this->getMovieOrder($orderBy); $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium')); $moviesSql = sprintf( " SELECT SQL_CALC_FOUND_ROWS m.imdbid, GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id FROM movieinfo m LEFT JOIN releases r USING (imdbid) WHERE r.nzbstatus = 1 AND m.title != '' AND m.imdbid != '0000000' AND r.passwordstatus %s %s %s %s %s GROUP BY m.imdbid ORDER BY %s %s %s", $this->showPasswords, $this->getBrowseBy(), (! empty($catsrch) ? $catsrch : ''), ( $maxAge > 0 ? 'AND r.postdate > NOW() - INTERVAL '.$maxAge.'DAY ' : '' ), (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''), $order[0], $order[1], ($start === false ? '' : ' LIMIT '.$num.' OFFSET '.$start) ); $movieCache = Cache::get(md5($moviesSql.$page)); if ($movieCache !== null) { $movies = $movieCache; } else { $data = MovieInfo::fromQuery($moviesSql); $movies = ['total' => DB::select('SELECT FOUND_ROWS() AS total'), 'result' => $data]; Cache::put(md5($moviesSql.$page), $movies, $expiresAt); } $movieIDs = $releaseIDs = []; if (! empty($movies['result'])) { foreach ($movies['result'] as $movie => $id) { $movieIDs[] = $id->imdbid; $releaseIDs[] = $id->grp_release_id; } } $sql = sprintf( " SELECT GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id, GROUP_CONCAT(r.rarinnerfilecount ORDER BY r.postdate DESC SEPARATOR ',') AS grp_rarinnerfilecount, GROUP_CONCAT(r.haspreview ORDER BY r.postdate DESC SEPARATOR ',') AS grp_haspreview, GROUP_CONCAT(r.passwordstatus ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_password, GROUP_CONCAT(r.guid ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_guid, GROUP_CONCAT(rn.releases_id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_nfoid, GROUP_CONCAT(g.name ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grpname, GROUP_CONCAT(r.searchname ORDER BY r.postdate DESC SEPARATOR '#') AS grp_release_name, GROUP_CONCAT(r.postdate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_postdate, GROUP_CONCAT(r.size ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_size, GROUP_CONCAT(r.totalpart ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_totalparts, GROUP_CONCAT(r.comments ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_comments, GROUP_CONCAT(r.grabs ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grabs, GROUP_CONCAT(df.failed ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_failed, GROUP_CONCAT(cp.title, ' > ', c.title ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_catname, m.*, g.name AS group_name, rn.releases_id AS nfoid FROM releases r LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id LEFT OUTER JOIN categories c ON c.id = r.categories_id LEFT OUTER JOIN root_categories cp ON cp.id = c.root_categories_id INNER JOIN movieinfo m ON m.imdbid = r.imdbid WHERE m.imdbid IN (%s) AND r.id IN (%s) %s GROUP BY m.imdbid ORDER BY %s %s", (\is_array($movieIDs) && ! empty($movieIDs) ? implode(',', $movieIDs) : -1), (\is_array($releaseIDs) && ! empty($releaseIDs) ? implode(',', $releaseIDs) : -1), (! empty($catsrch) ? $catsrch : ''), $order[0], $order[1] ); $return = Cache::get(md5($sql.$page)); if ($return !== null) { return $return; } $return = Release::fromQuery($sql); if (\count($return) > 0) { $return[0]->_totalcount = $movies['total'][0]->total ?? 0; } Cache::put(md5($sql.$page), $return, $expiresAt); return $return; } /** * Get the order type the user requested on the movies page. * * @param $orderBy * * @return array */ protected function getMovieOrder($orderBy): array { $orderArr = explode('_', (($orderBy === '') ? 'MAX(r.postdate)' : $orderBy)); switch ($orderArr[0]) { case 'title': $orderField = 'm.title'; break; case 'year': $orderField = 'm.year'; break; case 'rating': $orderField = 'm.rating'; break; case 'posted': default: $orderField = 'MAX(r.postdate)'; break; } return [$orderField, isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1]) ? $orderArr[1] : 'desc']; } /** * Order types for movies page. * * @return array */ public function getMovieOrdering(): array { return ['title_asc', 'title_desc', 'year_asc', 'year_desc', 'rating_asc', 'rating_desc']; } /** * @return string */ protected function getBrowseBy(): string { $browseBy = ' '; $browseByArr = ['title', 'director', 'actors', 'genre', 'rating', 'year', 'imdb']; foreach ($browseByArr as $bb) { if (request()->has($bb) && ! empty(request()->input($bb))) { $bbv = stripslashes(request()->input($bb)); if ($bb === 'rating') { $bbv .= '.'; } if ($bb === 'imdb') { $browseBy .= sprintf('AND m.imdbid = %d', $bbv); } else { $browseBy .= 'AND m.'.$bb.' '.'LIKE '.escapeString('%'.$bbv.'%'); } } } return $browseBy; } /** * Get trailer using IMDB Id. * * @param int $imdbID * * @return bool|string * @throws \Exception */ public function getTrailer($imdbID) { $trailer = MovieInfo::query()->where('imdbid', $imdbID)->where('trailer', '<>', '')->first(['trailer']); if ($trailer !== null) { return $trailer['trailer']; } if ($this->traktcheck !== null) { $data = $this->traktTv->client->movieSummary('tt'.$imdbID, 'full'); if ($data !== false) { $this->parseTraktTv($data); if (! empty($data['trailer'])) { return $data['trailer']; } } } $trailer = Utility::imdb_trailers($imdbID); if ($trailer) { MovieInfo::query()->where('imdbid', $imdbID)->update(['trailer' => $trailer]); return $trailer; } return false; } /** * Parse trakt info, insert into DB. * * @param array $data * * @return mixed */ public function parseTraktTv(&$data) { if (empty($data['ids']['imdb'])) { return false; } if (! empty($data['trailer'])) { $data['trailer'] = str_ireplace( ['watch?v=', 'http://'], ['embed/', 'https://'], $data['trailer'] ); } $imdbid = (strpos($data['ids']['imdb'], 'tt') === 0) ? substr($data['ids']['imdb'], 2) : $data['ids']['imdb']; $cover = 0; if (File::isFile($this->imgSavePath.$imdbid).'-cover.jpg') { $cover = 1; } return $this->update([ 'genres' => $this->checkTraktValue($data['genres']), 'imdbid' => $this->checkTraktValue($imdbid), 'language' => $this->checkTraktValue($data['language']), 'plot' => $this->checkTraktValue($data['overview']), 'rating' => $this->checkTraktValue($data['rating']), 'tagline' => $this->checkTraktValue($data['tagline']), 'title' => $this->checkTraktValue($data['title']), 'tmdbid' => $this->checkTraktValue($data['ids']['tmdb']), 'traktid' => $this->checkTraktValue($data['ids']['trakt']), 'trailer' => $this->checkTraktValue($data['trailer']), 'cover' => $cover, 'year' => $this->checkTraktValue($data['year']), ]); } /** * Checks if the value is set and not empty, returns it, else empty string. * * @param mixed $value * * @return string */ private function checkTraktValue($value): string { if (\is_array($value) && ! empty($value)) { $temp = ''; foreach ($value as $val) { if (! \is_array($val) && ! \is_object($val)) { $temp .= $val; } } $value = $temp; } return ! empty($value) ? $value : ''; } /** * Get array of column keys, for inserting / updating. * * @return array */ public function getColumnKeys(): array { return [ 'actors', 'backdrop', 'cover', 'director', 'genre', 'imdbid', 'language', 'plot', 'rating', 'rtrating', 'tagline', 'title', 'tmdbid', 'traktid', 'trailer', 'type', 'year', ]; } /** * Update movie on movie-edit page. * * @param array $values Array of keys/values to update. See $validKeys * @return bool */ public function update(array $values) { if (! \count($values)) { return false; } $validKeys = $this->getColumnKeys(); $query = [ '0' => 'INSERT INTO movieinfo (updated_at, created_at, ', '1' => ' VALUES (NOW(), NOW(), ', '2' => 'ON DUPLICATE KEY UPDATE updated_at = NOW(), ', ]; $found = 0; foreach ($values as $key => $value) { if (! empty($value) && \in_array($key, $validKeys, false)) { $found++; $query[0] .= "$key, "; if (\in_array($key, ['genre', 'language'], false)) { $value = substr($value, 0, 64); } $value = escapeString($value); $query[1] .= "$value, "; $query[2] .= "$key = $value, "; } } if (! $found) { return false; } foreach ($query as $key => $value) { $query[$key] = rtrim($value, ', '); } MovieInfo::fromQuery($query[0].') '.$query[1].') '.$query[2]); } /** * Returns a tmdb, imdb or trakt variable, the one that is set. Empty string if both not set. * * @param string $variable1 * @param string $variable2 * @param string $variable3 * @param string $variable4 * @param string $variable5 * * @return array|string */ protected function setVariables($variable1, $variable2, $variable3, $variable4, $variable5) { if (! empty($variable1)) { return $variable1; } if (! empty($variable2)) { return $variable2; } if (! empty($variable3)) { return $variable3; } if (! empty($variable4)) { return $variable4; } if (! empty($variable5)) { return $variable5; } return ''; } /** * Fetch IMDB/TMDB/TRAKT/OMDB/iTunes info for the movie. * * @param $imdbId * * @return bool * @throws \Exception */ public function updateMovieInfo($imdbId): bool { if ($this->echooutput && $this->service !== '' && Utility::isCLI()) { $this->colorCli->primary('Fetching IMDB info from TMDB/IMDB/Trakt/OMDB/iTunes using IMDB id: '.$imdbId); } // Check TMDB for IMDB info. $tmdb = $this->fetchTMDBProperties($imdbId); // Check IMDB for movie info. $imdb = $this->fetchIMDBProperties($imdbId); // Check TRAKT for movie info $trakt = $this->fetchTraktTVProperties($imdbId); // Check OMDb for movie info $omdb = $this->fetchOmdbAPIProperties($imdbId); // Check iTunes for movie info as last resort (iTunes do not provide all the info we need) $iTunes = $this->fetchItunesMovieProperties($this->currentTitle); if (! $imdb && ! $tmdb && ! $trakt && ! $omdb && empty($iTunes)) { return false; } // Check FanArt.tv for cover and background images. $fanart = $this->fetchFanartTVProperties($imdbId); $mov = []; $mov['cover'] = $mov['backdrop'] = $mov['banner'] = 0; $mov['type'] = $mov['director'] = $mov['actors'] = $mov['language'] = ''; $mov['imdbid'] = $imdbId; $mov['tmdbid'] = (! isset($tmdb['tmdbid']) || $tmdb['tmdbid'] === '') ? 0 : $tmdb['tmdbid']; $mov['traktid'] = (! isset($trakt['id']) || $trakt['id'] === '') ? 0 : $trakt['id']; // Prefer Fanart.tv cover over TMDB,TMDB over IMDB,IMDB over OMDB and OMDB over iTunes. if (! empty($fanart['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $fanart['cover'], $this->imgSavePath); } elseif (! empty($tmdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $tmdb['cover'], $this->imgSavePath); } elseif (! empty($imdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $imdb['cover'], $this->imgSavePath); } elseif (! empty($omdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $omdb['cover'], $this->imgSavePath); } elseif (! empty($iTunes['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $iTunes['cover'], $this->imgSavePath); } // Backdrops. if (! empty($fanart['backdrop'])) { $mov['backdrop'] = $this->releaseImage->saveImage($imdbId.'-backdrop', $fanart['backdrop'], $this->imgSavePath, 1920, 1024); } elseif (! empty($tmdb['backdrop'])) { $mov['backdrop'] = $this->releaseImage->saveImage($imdbId.'-backdrop', $tmdb['backdrop'], $this->imgSavePath, 1920, 1024); } // Banner if (! empty($fanart['banner'])) { $mov['banner'] = $this->releaseImage->saveImage($imdbId.'-banner', $fanart['banner'], $this->imgSavePath); } // RottenTomatoes rating from OmdbAPI if (! empty($omdb['rtRating'])) { $mov['rtrating'] = $omdb['rtRating']; } $mov['title'] = $this->setVariables($imdb['title'], $tmdb['title'], $trakt['title'], $omdb['title'], $iTunes['title']); $mov['rating'] = $this->setVariables($imdb['rating'] ?? '', $tmdb['rating'] ?? '', $trakt['rating'] ?? '', $omdb['rating'] ?? '', $iTunes['rating'] ?? ''); $mov['plot'] = $this->setVariables($imdb['plot'], $tmdb['plot'], $trakt['overview'], $omdb['plot'], $iTunes['plot']); $mov['tagline'] = $this->setVariables($imdb['tagline'], $tmdb['tagline'], $trakt['tagline'], $omdb['tagline'], $iTunes['tagline']); $mov['year'] = $this->setVariables($imdb['year'], $tmdb['year'], $trakt['year'], $omdb['year'], $iTunes['year']); $mov['genre'] = $this->setVariables($imdb['genre'], $tmdb['genre'], $trakt['genres'], $omdb['genre'], $iTunes['genre']); if (! empty($imdb['type'])) { $mov['type'] = $imdb['type']; } if (! empty($imdb['director'])) { $mov['director'] = \is_array($imdb['director']) ? implode(', ', array_unique($imdb['director'])) : $imdb['director']; } elseif (! empty($omdb['director'])) { $mov['director'] = \is_array($omdb['director']) ? implode(', ', array_unique($omdb['director'])) : $omdb['director']; } elseif (! empty($tmdb['director'])) { $mov['director'] = \is_array($tmdb['director']) ? implode(', ', array_unique($tmdb['director'])) : $tmdb['director']; } if (! empty($imdb['actors'])) { $mov['actors'] = \is_array($imdb['actors']) ? implode(', ', array_unique($imdb['actors'])) : $imdb['actors']; } elseif (! empty($omdb['actors'])) { $mov['actors'] = \is_array($omdb['actors']) ? implode(', ', array_unique($omdb['actors'])) : $omdb['actors']; } elseif (! empty($tmdb['actors'])) { $mov['actors'] = \is_array($tmdb['actors']) ? implode(', ', array_unique($tmdb['actors'])) : $tmdb['actors']; } if (! empty($imdb['language'])) { $mov['language'] = \is_array($imdb['language']) ? implode(', ', array_unique($imdb['language'])) : $imdb['language']; } elseif (! empty($omdb['language'])) { $mov['language'] = \is_array($imdb['language']) ? implode(', ', array_unique($omdb['language'])) : $omdb['language']; } if (\is_array($mov['genre'])) { $mov['genre'] = implode(', ', array_unique($mov['genre'])); } if (\is_array($mov['type'])) { $mov['type'] = implode(', ', array_unique($mov['type'])); } $mov['title'] = html_entity_decode($mov['title'], ENT_QUOTES, 'UTF-8'); $mov['title'] = str_replace(['/', '\\'], '', $mov['title']); $movieID = $this->update([ 'actors' => html_entity_decode($mov['actors'], ENT_QUOTES, 'UTF-8'), 'backdrop' => $mov['backdrop'], 'cover' => $mov['cover'], 'director' => html_entity_decode($mov['director'], ENT_QUOTES, 'UTF-8'), 'genre' => html_entity_decode($mov['genre'], ENT_QUOTES, 'UTF-8'), 'imdbid' => $mov['imdbid'], 'language' => html_entity_decode($mov['language'], ENT_QUOTES, 'UTF-8'), 'plot' => html_entity_decode(preg_replace('/\s+See full summary ยป/u', ' ', $mov['plot']), ENT_QUOTES, 'UTF-8'), 'rating' => round($mov['rating'], 1), 'rtrating' => $mov['rtrating'] ?? 'N/A', 'tagline' => html_entity_decode($mov['tagline'], ENT_QUOTES, 'UTF-8'), 'title' => $mov['title'], 'tmdbid' => $mov['tmdbid'], 'traktid' => $mov['traktid'], 'type' => html_entity_decode(ucwords(preg_replace('/[\.\_]/', ' ', $mov['type'])), ENT_QUOTES, 'UTF-8'), 'year' => $mov['year'], ]); if ($this->echooutput && $this->service !== '' && Utility::isCLI()) { $this->colorCli->headerOver('Added/updated movie: '). $this->colorCli->primary( $mov['title']. ' ('. $mov['year']. ') - '. $mov['imdbid'] ); } return (int) $movieID > 0; } /** * Fetch FanArt.tv backdrop / cover / title. * * @param $imdbId * * @return array|false */ protected function fetchFanartTVProperties($imdbId) { if ($this->fanartapikey !== null) { $art = $this->fanart->getMovieFanArt('tt'.$imdbId); if (! empty($art)) { if (isset($art['status']) && $art['status'] === 'error') { return false; } $ret = []; if (! empty($art['moviebackground'][0]['url'])) { $ret['backdrop'] = $art['moviebackground'][0]['url']; } elseif (! empty($art['moviethumb'][0]['url'])) { $ret['backdrop'] = $art['moviethumb'][0]['url']; } if (! empty($art['movieposter'][0]['url'])) { $ret['cover'] = $art['movieposter'][0]['url']; } if (! empty($art['moviebanner'][0]['url'])) { $ret['banner'] = $art['moviebanner'][0]['url']; } if (isset($ret['backdrop'], $ret['cover'])) { $ret['title'] = $imdbId; if (isset($art['name'])) { $ret['title'] = $art['name']; } if ($this->echooutput && Utility::isCLI()) { $this->colorCli->alternateOver('Fanart Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL; } return $ret; } } } return false; } /** * Fetch info for IMDB id from TMDB. * * * @param $imdbId * @param bool $text * * @return array|false */ public function fetchTMDBProperties($imdbId, $text = false) { $lookupId = $text === false && (\strlen($imdbId) === 7 || strlen($imdbId) === 8) ? 'tt'.$imdbId : $imdbId; try { $tmdbLookup = Tmdb::getMoviesApi()->getMovie($lookupId, ['append_to_response' => 'credits']); } catch (TmdbApiException $error) { if (Utility::isCLI()) { $this->colorCli->error($error->getMessage()); } return false; } if (! empty($tmdbLookup)) { if ($this->currentTitle !== '') { // Check the similarity. similar_text($this->currentTitle, $tmdbLookup['title'], $percent); if ($percent < self::MATCH_PERCENT) { return false; } } if ($this->currentYear !== '') { // Check the similarity. similar_text($this->currentYear, Carbon::parse($tmdbLookup['release_date'])->year, $percent); if ($percent < self::YEAR_MATCH_PERCENT) { return false; } } $ret = []; $ret['title'] = $tmdbLookup['title']; $ret['tmdbid'] = $tmdbLookup['id']; $ret['imdbid'] = str_replace('tt', '', $tmdbLookup['imdb_id']); $vote = $tmdbLookup['vote_average']; if ($vote !== null) { $ret['rating'] = (int) $vote === 0 ? '' : $vote; } else { $ret['rating'] = ''; } $actors = Arr::pluck($tmdbLookup['credits']['cast'], 'name'); if (! empty($actors)) { $ret['actors'] = $actors; } else { $ret['actors'] = ''; } foreach ($tmdbLookup['credits']['crew'] as $crew) { if ($crew['department'] === 'Directing' && $crew['job'] === 'Director') { $ret['director'] = $crew['name']; } } $overview = $tmdbLookup['overview']; if (! empty($overview)) { $ret['plot'] = $overview; } else { $ret['plot'] = ''; } $tagline = $tmdbLookup['tagline']; if (! empty($tagline)) { $ret['tagline'] = $tagline; } else { $ret['tagline'] = ''; } $released = $tmdbLookup['release_date']; if (! empty($released)) { $ret['year'] = Carbon::parse($released)->year; } else { $ret['year'] = ''; } $genresa = $tmdbLookup['genres']; if (! empty($genresa) && \count($genresa) > 0) { $genres = []; foreach ($genresa as $genre) { $genres[] = $genre['name']; } $ret['genre'] = $genres; } else { $ret['genre'] = ''; } $posterp = $tmdbLookup['poster_path']; if (! empty($posterp)) { $ret['cover'] = 'https://image.tmdb.org/t/p/original'.$posterp; } else { $ret['cover'] = ''; } $backdrop = $tmdbLookup['backdrop_path']; if (! empty($backdrop)) { $ret['backdrop'] = 'https://image.tmdb.org/t/p/original'.$backdrop; } else { $ret['backdrop'] = ''; } if ($this->echooutput && Utility::isCLI()) { $this->colorCli->primaryOver('TMDb Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL; } return $ret; } return false; } /** * @param $imdbId * * @return array|false */ public function fetchIMDBProperties($imdbId) { $resultQuery = (new Title($imdbId, $this->config))->real_id(); if (! empty($resultQuery)) { $imdbId = 'tt'.$resultQuery; } $result = new Title($imdbId, $this->config); if (! empty($result->orig_title())) { similar_text($this->currentTitle, $result->orig_title(), $percent); if ($percent >= self::MATCH_PERCENT) { similar_text($this->currentYear, $result->year(), $percent); if ($percent >= self::YEAR_MATCH_PERCENT) { $ret = [ 'title' => $result->orig_title(), 'tagline' => $result->tagline(), 'plot' => Arr::get($result->plot_split(), '0.plot'), 'rating' => ! empty($result->rating()) ? $result->rating() : '', 'year' => $result->year(), 'cover' => $result->photo(), 'genre' => $result->genre(), 'language' => $result->language(), 'type' => $result->movietype(), ]; if ($this->echooutput && Utility::isCLI()) { $this->colorCli->headerOver('IMDb Found ').$this->colorCli->primaryOver($result->orig_title()).PHP_EOL; } return $ret; } return false; } return false; } return false; } /** * Fetch TraktTV backdrop / cover / title. * * @param $imdbId * * @return array|false * @throws \Exception */ public function fetchTraktTVProperties($imdbId) { if ($this->traktcheck !== null) { $resp = $this->traktTv->client->movieSummary('tt'.$imdbId, 'full'); if ($resp !== false) { similar_text($this->currentTitle, $resp['title'], $percent); if ($percent >= self::MATCH_PERCENT) { similar_text($this->currentYear, $resp['year'], $percent); if ($percent >= self::YEAR_MATCH_PERCENT) { $ret = []; if (isset($resp['ids']['trakt'])) { $ret['id'] = $resp['ids']['trakt']; } $ret['overview'] = $resp['overview'] ?? ''; if (isset($resp['title'])) { $ret['title'] = $resp['title']; } else { return false; } if ($this->echooutput && Utility::isCLI()) { $this->colorCli->alternateOver('Trakt Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL; } return $ret; } return false; } return false; } return false; } return false; } /** * Fetch OMDb backdrop / cover / title. * * @param $imdbId * * @return array|false */ public function fetchOmdbAPIProperties($imdbId) { if ($this->omdbapikey !== null) { $resp = $this->omdbApi->fetch('i', 'tt'.$imdbId); if (\is_object($resp) && $resp->message === 'OK' && $resp->data->Response !== 'False') { similar_text($this->currentTitle, $resp->data->Title, $percent); if ($percent >= self::MATCH_PERCENT) { similar_text($this->currentYear, $resp->data->Year, $percent); if ($percent >= self::YEAR_MATCH_PERCENT) { $ret = [ 'title' => $resp->data->Title ?? '', 'cover' => $resp->data->Poster ?? '', 'genre' => $resp->data->Genre ?? '', 'year' => $resp->data->Year ?? '', 'plot' => $resp->data->Plot ?? '', 'rating' => $resp->data->imdbRating ?? '', 'rtRating' => $resp->data->Ratings[1]->Value ?? '', 'tagline' => $resp->data->Tagline ?? '', 'director' => $resp->data->Director ?? '', 'actors' => $resp->data->Actors ?? '', 'language' => $resp->data->Language ?? '', 'boxOffice' => $resp->data->BoxOffice ?? '', ]; if ($this->echooutput && Utility::isCLI()) { $this->colorCli->alternateOver('OMDbAPI Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL; } return $ret; } return false; } return false; } return false; } return false; } /** * @param string $title * * @return array|bool * @throws \DariusIII\ItunesApi\Exceptions\InvalidProviderException * @throws \Exception */ public function fetchItunesMovieProperties($title) { $movie = true; try { $iTunesMovie = iTunes::load('movie')->fetchOneByName($title); } catch (MovieNotFoundException $e) { $movie = false; } catch (SearchNoResultsException $e) { $movie = false; } if ($movie !== false) { similar_text($this->currentTitle, $iTunesMovie->getName(), $percent); if ($percent >= self::MATCH_PERCENT) { $movie = [ 'title' => $iTunesMovie->getName(), 'director' => $iTunesMovie->getDirector() ?? '', 'tagline' => $iTunesMovie->getTagLine() ?? '', 'cover' => str_replace('100x100', '800x800', $iTunesMovie->getCover()), 'genre' => $iTunesMovie->getGenre() ?? '', 'plot' => $iTunesMovie->getDescription() ?? '', 'year' => $iTunesMovie->getReleaseDate()->format('Y'), ]; } else { $movie = false; } } return $movie; } /** * Update a release with a IMDB id. * * @param string $buffer Data to parse a IMDB id/Trakt Id from. * @param string $service Method that called this method. * @param int $id id of the release. * @param int $processImdb To get IMDB info on this IMDB id or not. * * @return string * @throws \Exception */ public function doMovieUpdate($buffer, $service, $id, $processImdb = 1): string { $imdbID = false; if (\is_string($buffer) && preg_match('/(?:imdb.*?)?(?:tt|Title\?)(?P\d{5,7})/i', $buffer, $matches)) { $imdbID = $matches['imdbid']; } if ($imdbID !== false) { $this->service = $service; if ($this->echooutput && $this->service !== '' && Utility::isCLI()) { $this->colorCli->primary($this->service.' found IMDBid: tt'.$imdbID, true); } $movieInfoId = MovieInfo::query()->where('imdbid', $imdbID)->first(['id']); Release::query()->where('id', $id)->update(['imdbid' =>$imdbID, 'movieinfo_id' => $movieInfoId !== null ? $movieInfoId['id'] : null]); // If set, scan for imdb info. if ($processImdb === 1) { $movCheck = $this->getMovieInfo($imdbID); if ($movCheck === null || (isset($movCheck['updated_at']) && (time() - strtotime($movCheck['updated_at'])) > 2592000)) { $info = $this->updateMovieInfo($imdbID); if ($info === false) { Release::query()->where('id', $id)->update(['imdbid' => 0000000]); } elseif ($info === true) { $movieInfoId = MovieInfo::query()->where('imdbid', $imdbID)->first(['id']); Release::query()->where('id', $id)->update(['imdbid' =>$imdbID, 'movieinfo_id' => $movieInfoId !== null ? $movieInfoId['id'] : null]); } } } } return $imdbID; } /** * Process releases with no IMDB id's. * * * @param string $groupID * @param string $guidChar * @param int $lookupIMDB * @throws \Exception */ public function processMovieReleases($groupID = '', $guidChar = '', $lookupIMDB = 1): void { if ($lookupIMDB === 0) { return; } // Get all releases without an IMDB id. $sql = Release::query() ->select(['searchname', 'id']) ->whereBetween('categories_id', [Category::MOVIE_ROOT, Category::MOVIE_OTHER]) ->whereNull('imdbid') ->where('nzbstatus', '=', 1); if ($groupID !== '') { $sql->where('groups_id', $groupID); } if ($guidChar !== '') { $sql->where('leftguid', $guidChar); } if ((int) $lookupIMDB === 2) { $sql->where('isrenamed', '=', 1); } $res = $sql->limit($this->movieqty)->get(); $movieCount = \count($res); if ($movieCount > 0) { if ($this->echooutput && $movieCount > 1) { $this->colorCli->header('Processing '.$movieCount.' movie releases.'); } // Loop over releases. foreach ($res as $arr) { // Try to get a name/year. if ($this->parseMovieSearchName($arr['searchname']) === false) { //We didn't find a name, so set to all 0's so we don't parse again. Release::query()->where('id', $arr['id'])->update(['imdbid' => 0000000]); continue; } $this->currentRelID = $arr['id']; $movieName = $this->currentTitle; if ($this->currentYear !== '') { $movieName .= ' ('.$this->currentYear.')'; } if ($this->echooutput && Utility::isCLI()) { $this->colorCli->primaryOver('Looking up: ').$this->colorCli->headerOver($movieName); } $movieUpdated = false; // Check local DB. $getIMDBid = $this->localIMDBSearch(); if ($getIMDBid !== false) { $imdbID = $this->doMovieUpdate('tt'.$getIMDBid, 'Local DB', $arr['id']); if ($imdbID !== false) { $movieUpdated = true; } } // Check on IMDb first if ($movieUpdated === false) { $imdbSearch = new TitleSearch($this->config); foreach ($imdbSearch->search($this->currentTitle, [TitleSearch::MOVIE]) as $imdbTitle) { if (! empty($imdbTitle->orig_title())) { similar_text($imdbTitle->orig_title(), $this->currentTitle, $percent); if ($percent >= self::MATCH_PERCENT) { similar_text($this->currentYear, $imdbTitle->year(), $percent); if ($percent >= self::YEAR_MATCH_PERCENT) { $getIMDBid = $imdbTitle->imdbid(); $imdbID = $this->doMovieUpdate('tt'.$getIMDBid, 'IMDb', $arr['id']); if ($imdbID !== false) { $movieUpdated = true; } } } } } } // Check on OMDbAPI if ($movieUpdated === false) { $omdbTitle = strtolower(str_replace(' ', '_', $this->currentTitle)); if ($this->omdbapikey !== null) { $buffer = $this->omdbApi->search($omdbTitle, 'movie'); if (\is_object($buffer) && $buffer->message === 'OK' && $buffer->data->Response === 'True') { $getIMDBid = $buffer->data->Search[0]->imdbID; if (! empty($getIMDBid)) { $imdbID = $this->doMovieUpdate($getIMDBid, 'OMDbAPI', $arr['id']); if ($imdbID !== false) { $movieUpdated = true; } } } } } // Check on Trakt. if ($movieUpdated === false && $this->traktcheck !== null) { $data = $this->traktTv->client->movieSummary($movieName, 'full'); if ($data !== false) { $this->parseTraktTv($data); if (! empty($data['ids']['imdb'])) { $imdbID = $this->doMovieUpdate($data['ids']['imdb'], 'Trakt', $arr['id']); if ($imdbID !== false) { $movieUpdated = true; } } } } // Check on The Movie Database. if ($movieUpdated === false) { $data = Tmdb::getSearchApi()->searchMovies($this->currentTitle); if (($data['total_results'] > 0) && ! empty($data['results'])) { foreach ($data['results'] as $result) { if (! empty($result['id'])) { similar_text($this->currentYear, Carbon::parse($result['release_date'])->year, $percent); if ($percent >= self::YEAR_MATCH_PERCENT) { $ret = $this->fetchTMDBProperties($result['id'], true); if ($ret !== false && ! empty($ret['imdbid'])) { $imdbID = $this->doMovieUpdate('tt'.$ret['imdbid'], 'TMDB', $arr['id']); if ($imdbID !== false) { $movieUpdated = true; } } } } else { $movieUpdated = false; } } } else { $movieUpdated = false; } } // We failed to get an IMDB id from all sources. if ($movieUpdated === false) { Release::query()->where('id', $arr['id'])->update(['imdbid' => 0000000]); } } } } /** * @return mixed|false */ protected function localIMDBSearch() { //If we found a year, try looking in a 4 year range. $check = MovieInfo::query() ->where('title', 'like', '%'.$this->currentTitle.'%'); if ($this->currentYear !== '') { $start = Carbon::parse($this->currentYear)->subYears(2)->year; $end = Carbon::parse($this->currentYear)->addYears(2)->year; $check->whereBetween('year', [$start, $end]); } $IMDBCheck = $check->first(['imdbid']); return $IMDBCheck === null ? false : $IMDBCheck->imdbid; } /** * Parse a movie name from a release search name. * * @param string $releaseName * * @return bool */ protected function parseMovieSearchName($releaseName): bool { $name = $year = ''; $followingList = '[^\w]((1080|480|720)p|AC3D|Directors([^\w]CUT)?|DD5\.1|(DVD|BD|BR)(Rip)?|BluRay|divx|HDTV|iNTERNAL|LiMiTED|(Real\.)?Proper|RE(pack|Rip)|Sub\.?(fix|pack)|Unrated|WEB-DL|(x|H)[ ._-]?264|xvid)[^\w]'; /* Initial scan of getting a year/name. * [\w. -]+ Gets 0-9a-z. - characters, most scene movie titles contain these chars. * ie: [61420]-[FULL]-[a.b.foreignEFNet]-[ Coraline.2009.DUTCH.INTERNAL.1080p.BluRay.x264-VeDeTT ]-[21/85] - "vedett-coralien-1080p.r04" yEnc * Then we look up the year, (19|20)\d\d, so $matches[1] would be Coraline $matches[2] 2009 */ if (preg_match('/(?P[\w. -]+)[^\w](?P(19|20)\d\d)/i', $releaseName, $matches)) { $name = $matches['name']; $year = $matches['year']; /* If we didn't find a year, try to get a name anyways. * Try to look for a title before the $followingList and after anything but a-z0-9 two times or more (-[ for example) */ } elseif (preg_match('/([^\w]{2,})?(?P[\w .-]+?)'.$followingList.'/i', $releaseName, $matches)) { $name = $matches['name']; } // Check if we got something. if ($name !== '') { // If we still have any of the words in $followingList, remove them. $name = preg_replace('/'.$followingList.'/i', ' ', $name); // Remove periods, underscored, anything between parenthesis. $name = preg_replace('/\(.*?\)|[._]/i', ' ', $name); // Finally remove multiple spaces and trim leading spaces. $name = trim(preg_replace('/\s{2,}/', ' ', $name)); // Check if the name is long enough and not just numbers. if (\strlen($name) > 4 && ! preg_match('/^\d+$/', $name)) { $this->currentTitle = $name; $this->currentYear = $year; return true; } } return false; } /** * Get IMDB genres. * * @return array */ public function getGenres(): array { return [ 'Action', 'Adventure', 'Animation', 'Biography', 'Comedy', 'Crime', 'Documentary', 'Drama', 'Family', 'Fantasy', 'Film-Noir', 'Game-Show', 'History', 'Horror', 'Music', 'Musical', 'Mystery', 'News', 'Reality-TV', 'Romance', 'Sci-Fi', 'Sport', 'Talk-Show', 'Thriller', 'War', 'Western', ]; } }