false, 'Logger' => null, 'ReleaseImage' => null, 'Settings' => null, 'TMDb' => null, ]; $options += $defaults; $this->pdo = ($options['Settings'] instanceof DB ? $options['Settings'] : new DB()); $this->releaseImage = ($options['ReleaseImage'] instanceof ReleaseImage ? $options['ReleaseImage'] : new ReleaseImage($this->pdo)); $this->client = new Client(); $this->tmdbtoken = new ApiToken(Settings::settingValue('APIs..tmdbkey')); $this->tmdbclient = new TmdbClient( $this->tmdbtoken, [ 'cache' => [ 'enabled' => false, ], ] ); $this->fanartapikey = Settings::settingValue('APIs..fanarttvkey'); $this->fanart = new FanartTV($this->fanartapikey); $this->omdbapikey = Settings::settingValue('APIs..omdbkey'); $this->lookuplanguage = Settings::settingValue('indexer.categorise.imdblanguage') !== '' ? (string) Settings::settingValue('indexer.categorise.imdblanguage') : 'en'; $this->imdburl = Settings::settingValue('indexer.categorise.imdburl') === 0 ? false : true; $this->movieqty = Settings::settingValue('..maximdbprocessed') !== '' ? Settings::settingValue('..maximdbprocessed') : 100; $this->searchEngines = true; $this->showPasswords = Releases::showPasswords(); $this->debug = NN_DEBUG; $this->echooutput = ($options['Echo'] && NN_ECHOCLI && $this->pdo->cli); $this->imgSavePath = NN_COVERS.'movies'.DS; $this->service = ''; $this->catWhere = 'PARTITION (movies)'; if (NN_DEBUG || NN_LOGGING) { $this->debug = true; try { $this->debugging = new Logger(); } catch (LoggerException $error) { $this->_debug = false; } } } /** * @param $imdbId * @return array|bool|\Illuminate\Database\Eloquent\Model|null|static */ public function getMovieInfo($imdbId) { return MovieInfo::query()->where('imdbid', $imdbId)->first(); } /** * Get info for multiple IMDB id's. * * @param array $imdbIDs * * @return array */ public function getMovieInfoMultiImdb($imdbIDs): array { return $this->pdo->query( sprintf( ' SELECT DISTINCT movieinfo.*, releases.imdbid AS relimdb FROM movieinfo LEFT OUTER JOIN releases ON releases.imdbid = movieinfo.imdbid WHERE movieinfo.imdbid IN (%s)', str_replace( ',,', ',', str_replace( ['(,', ' ,', ', )', ',)'], '', implode(',', $imdbIDs) ) ) ), true, NN_CACHE_EXPIRY_MEDIUM ); } /** * Get movie releases with covers for movie browse page. * * @param $cat * @param $start * @param $num * @param $orderBy * @param $maxAge * @param array $excludedCats * * @return array|bool|\PDOStatement */ public function getMovieRange($cat, $start, $num, $orderBy, $maxAge = -1, array $excludedCats = []) { $catsrch = ''; if (count($cat) > 0 && $cat[0] !== -1) { $catsrch = (new Category(['Settings' => $this->pdo]))->getCategorySearch($cat); } $order = $this->getMovieOrder($orderBy); $movies = $this->pdo->queryCalc( 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) ), true, NN_CACHE_EXPIRY_MEDIUM ); $movieIDs = $releaseIDs = false; if (is_array($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 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 categories cp ON cp.id = c.parentid 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) ? implode(',', $movieIDs) : -1), (is_array($releaseIDs) ? implode(',', $releaseIDs) : -1), (! empty($catsrch) ? $catsrch : ''), $order[0], $order[1] ); $return = $this->pdo->query($sql, true, NN_CACHE_EXPIRY_MEDIUM); if (! empty($return)) { $return[0]['_totalcount'] = $movies['total'] ?? 0; } 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 (isset($_REQUEST[$bb]) && ! empty($_REQUEST[$bb])) { $bbv = stripslashes($_REQUEST[$bb]); if ($bb === 'rating') { $bbv .= '.'; } if ($bb === 'imdb') { $browseBy .= sprintf('AND m.%sid = %d', $bb, $bbv); } else { $browseBy .= 'AND m.'.$bb.' '.$this->pdo->likeString($bbv, true, true); } } } return $browseBy; } /** * @var null|TraktTv */ public $traktTv = null; /** * @var OMDbAPI|null */ public $omdbApi = null; /** * Get trailer using IMDB Id. * * @param int $imdbID * * @return bool|string * @throws \Exception */ public function getTrailer($imdbID) { if (! is_numeric($imdbID)) { return false; } $trailer = MovieInfo::query()->where('imdbid', $imdbID)->where('trailer', '!=', '')->first(['trailer']); if ($trailer !== null) { return $trailer['trailer']; } if ($this->traktTv === null) { $this->traktTv = new TraktTv(['Settings' => $this->pdo]); } $data = $this->traktTv->client->movieSummary('tt'.$imdbID, 'full'); if ($data) { $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( 'http://', 'https://', str_ireplace('watch?v=', 'embed/', $data['trailer']) ); return $data['trailer']; } $imdbid = (strpos($data['ids']['imdb'], 'tt') === 0) ? substr($data['ids']['imdb'], 2) : $data['ids']['imdb']; $cover = 0; if (is_file($this->imgSavePath.$imdbid).'-cover.jpg') { $cover = 1; } else { $link = $this->checkTraktValue($data['images']['poster']['thumb']); if ($link) { $cover = $this->releaseImage->saveImage($imdbid.'-cover', $link, $this->imgSavePath); } } $this->update([ 'genres' => $this->checkTraktValue($data['genres']), 'imdbid' => $this->checkTraktValue($imdbid), 'language' => $this->checkTraktValue($data['language']), 'plot' => $this->checkTraktValue($data['overview']), 'rating' => round($this->checkTraktValue($data['rating']), 1), 'tagline' => $this->checkTraktValue($data['tagline']), 'title' => $this->checkTraktValue($data['title']), 'tmdbid' => $this->checkTraktValue($data['ids']['tmdb']), '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 .= (string) $val; } } $value = $temp; } return ! empty($value) ? $value : ''; } /** * Create click-able links to IMDB actors/genres/directors/etc.. * * @param $data * @param $field * * @return string */ public function makeFieldLinks($data, $field): string { if (! isset($data[$field]) || $data[$field] === '') { return ''; } $tmpArr = explode(', ', $data[$field]); $newArr = []; $i = 0; foreach ($tmpArr as $ta) { if (trim($ta) === '') { continue; } if ($i > 5) { break; } //only use first 6 $newArr[] = ''.$ta.''; $i++; } return implode(', ', $newArr); } /** * Get array of column keys, for inserting / updating. * * @return array */ public function getColumnKeys(): array { return [ 'actors', 'backdrop', 'cover', 'director', 'genre', 'imdbid', 'language', 'plot', 'rating', 'tagline', 'title', 'tmdbid', 'trailer', 'type', 'year', ]; } /** * Update movie on movie-edit page. * * @param array $values Array of keys/values to update. See $validKeys * * @return int|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 = $this->pdo->escapeString($value); $query[1] .= "$value, "; $query[2] .= "$key = $value, "; } } if (! $found) { return false; } foreach ($query as $key => $value) { $query[$key] = rtrim($value, ', '); } return $this->pdo->queryInsert($query[0].') '.$query[1].') '.$query[2]); } /** * Check if a variable is set and not a empty string. * * @param $variable * * @return bool */ protected function checkVariable(&$variable): bool { return ! empty($variable) ? true : false; } /** * 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 * * @return array|string */ protected function setVariables(&$variable1, &$variable2, &$variable3, &$variable4) { if ($this->checkVariable($variable1)) { return $variable1; } if ($this->checkVariable($variable2)) { return $variable2; } if ($this->checkVariable($variable3)) { return $variable3; } if ($this->checkVariable($variable4)) { return $variable4; } return ''; } /** * Fetch IMDB/TMDB/TRAKT info for the movie. * * @param $imdbId * * @return bool */ public function updateMovieInfo($imdbId): bool { if ($this->echooutput && $this->service !== '') { ColorCLI::doEcho(ColorCLI::primary('Fetching IMDB info from TMDB and/or Trakt 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); if (! $imdb && ! $tmdb && ! $trakt && ! $omdb) { return false; } // Check FanArt.tv for cover and background images. $fanart = $this->fetchFanartTVProperties($imdbId); $mov = []; $mov['cover'] = $mov['backdrop'] = $mov['banner'] = $movieID = 0; $mov['type'] = $mov['director'] = $mov['actors'] = $mov['language'] = ''; $mov['imdbid'] = $imdbId; $mov['tmdbid'] = (! isset($tmdb['tmdbid']) || $tmdb['tmdbid'] === '') ? 0 : $tmdb['tmdbid']; // Prefer Fanart.tv cover over TMDB,TMDB over IMDB and IMDB over OMDB. if ($this->checkVariable($fanart['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $fanart['cover'], $this->imgSavePath); } elseif ($this->checkVariable($tmdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $tmdb['cover'], $this->imgSavePath); } elseif ($this->checkVariable($imdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $imdb['cover'], $this->imgSavePath); } elseif ($this->checkVariable($omdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $omdb['cover'], $this->imgSavePath); } // Backdrops. if ($this->checkVariable($fanart['backdrop'])) { $mov['backdrop'] = $this->releaseImage->saveImage($imdbId.'-backdrop', $fanart['backdrop'], $this->imgSavePath, 1920, 1024); } elseif ($this->checkVariable($tmdb['backdrop'])) { $mov['backdrop'] = $this->releaseImage->saveImage($imdbId.'-backdrop', $tmdb['backdrop'], $this->imgSavePath, 1920, 1024); } // Banner if ($this->checkVariable($fanart['banner'])) { $mov['banner'] = $this->releaseImage->saveImage($imdbId.'-banner', $fanart['banner'], $this->imgSavePath); } //RottenTomatoes rating from OmdbAPI if ($this->checkVariable($omdb['rtRating'])) { $mov['rtrating'] = $omdb['rtRating']; } $mov['title'] = $this->setVariables($imdb['title'], $tmdb['title'], $trakt['title'], $omdb['title']); $mov['rating'] = $this->setVariables($imdb['rating'], $tmdb['rating'], $trakt['rating'], $omdb['rating']); $mov['plot'] = $this->setVariables($imdb['plot'], $tmdb['plot'], $trakt['overview'], $omdb['plot']); $mov['tagline'] = $this->setVariables($imdb['tagline'], $tmdb['tagline'], $trakt['tagline'], $omdb['tagline']); $mov['year'] = $this->setVariables($imdb['year'], $tmdb['year'], $trakt['year'], $omdb['year']); $mov['genre'] = $this->setVariables($imdb['genre'], $tmdb['genre'], $trakt['genres'], $omdb['genre']); if ($this->checkVariable($imdb['type'])) { $mov['type'] = $imdb['type']; } if ($this->checkVariable($imdb['director'])) { $mov['director'] = is_array($imdb['director']) ? implode(', ', array_unique($imdb['director'])) : $imdb['director']; } elseif ($this->checkVariable($omdb['director'])) { $mov['director'] = is_array($omdb['director']) ? implode(', ', array_unique($omdb['director'])) : $omdb['director']; } if ($this->checkVariable($imdb['actors'])) { $mov['actors'] = is_array($imdb['actors']) ? implode(', ', array_unique($imdb['actors'])) : $imdb['actors']; } elseif ($this->checkVariable($omdb['actors'])) { $mov['actors'] = is_array($omdb['actors']) ? implode(', ', array_unique($omdb['actors'])) : $omdb['actors']; } if ($this->checkVariable($imdb['language'])) { $mov['language'] = is_array($imdb['language']) ? implode(', ', array_unique($imdb['language'])) : $imdb['language']; } elseif ($this->checkVariable($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 ยป/', ' ', $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'], 'type' => html_entity_decode(ucwords(preg_replace('/[\.\_]/', ' ', $mov['type'])), ENT_QUOTES, 'UTF-8'), 'year' => $mov['year'], ]); if ($this->echooutput && $this->service !== '') { ColorCLI::doEcho( ColorCLI::headerOver(($movieID !== 0 ? 'Added/updated movie: ' : 'Nothing to update for movie: ')). ColorCLI::primary( $mov['title']. ' ('. $mov['year']. ') - '. $mov['imdbid'] ) ); } return $movieID !== 0; } /** * Fetch FanArt.tv backdrop / cover / title. * * @param $imdbId * * @return bool|array */ protected function fetchFanartTVProperties($imdbId) { if ($this->fanartapikey !== '') { $art = $this->fanart->getMovieFanart('tt'.$imdbId); if (isset($art) && $art !== false) { if (isset($art['status']) && $art['status'] === 'error') { return false; } $ret = []; if ($this->checkVariable($art['moviebackground'][0]['url'])) { $ret['backdrop'] = $art['moviebackground'][0]['url']; } elseif ($this->checkVariable($art['moviethumb'][0]['url'])) { $ret['backdrop'] = $art['moviethumb'][0]['url']; } if ($this->checkVariable($art['movieposter'][0]['url'])) { $ret['cover'] = $art['movieposter'][0]['url']; } if ($this->checkVariable($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) { ColorCLI::doEcho(ColorCLI::alternateOver('Fanart Found ').ColorCLI::headerOver($ret['title']), true); } return $ret; } } } return false; } /** * Fetch info for IMDB id from TMDB. * * @param $imdbId * @param bool $text * * @return array|bool */ public function fetchTMDBProperties($imdbId, $text = false) { $lookupId = ($text === false ? 'tt'.$imdbId : $imdbId); try { $tmdbLookup = $this->tmdbclient->getMoviesApi()->getMovie($lookupId); } catch (TmdbApiException $e) { return false; } $ret = []; $ret['title'] = $tmdbLookup['original_title']; if ($this->currentTitle !== '') { // Check the similarity. similar_text($this->currentTitle, $ret['title'], $percent); if ($percent < 40) { if ($this->debug) { $this->debugging->log( __CLASS__, __FUNCTION__, 'Found ('. $ret['title']. ') from TMDB, but it\'s only '. $percent. '% similar to ('. $this->currentTitle.')', Logger::LOG_INFO ); } return false; } } $ret['tmdbid'] = $tmdbLookup['id']; $ImdbID = str_replace('tt', '', $tmdbLookup['imdb_id']); $ret['imdb_id'] = $ImdbID; $vote = $tmdbLookup['vote_average']; if (isset($vote)) { $ret['rating'] = ($vote === 0) ? '' : $vote; } $overview = $tmdbLookup['overview']; if (! empty($overview)) { $ret['plot'] = $overview; } $tagline = $tmdbLookup['tagline']; if (! empty($tagline)) { $ret['tagline'] = $tagline; } $released = $tmdbLookup['release_date']; if (! empty($released)) { $ret['year'] = date('Y', strtotime($released)); } $genresa = $tmdbLookup['genres']; if (! empty($genresa) && count($genresa) > 0) { $genres = []; foreach ($genresa as $genre) { $genres[] = $genre['name']; } $ret['genre'] = $genres; } $posterp = $tmdbLookup['poster_path']; if (! empty($posterp)) { $ret['cover'] = 'http://image.tmdb.org/t/p/w185'.$posterp; } $backdrop = $tmdbLookup['backdrop_path']; if (! empty($backdrop)) { $ret['backdrop'] = 'http://image.tmdb.org/t/p/original'.$backdrop; } if ($this->echooutput) { ColorCLI::doEcho(ColorCLI::primaryOver('TMDb Found ').ColorCLI::headerOver($ret['title']), true); } return $ret; } /** * @param $imdbId * * @return array|bool * @throws \Exception */ protected function fetchIMDBProperties($imdbId) { $imdb_regex = [ 'title' => '/
\s*?(.*?)\s*?<\/p>/i', 'rating' => '/"ratingValue">([\d.]+)<\/span>/i', 'year' => '/