diff --git a/app/Console/Commands/FetchMovieByImdb.php b/app/Console/Commands/FetchMovieByImdb.php
index d5239566a..7f7bcd088 100644
--- a/app/Console/Commands/FetchMovieByImdb.php
+++ b/app/Console/Commands/FetchMovieByImdb.php
@@ -2,7 +2,7 @@
namespace App\Console\Commands;
-use Blacklight\Movie;
+use App\Services\MovieService;
use Illuminate\Console\Command;
class FetchMovieByImdb extends Command
@@ -32,7 +32,7 @@ class FetchMovieByImdb extends Command
return self::FAILURE;
}
- $movie = app(Movie::class);
+ $movie = app(MovieService::class);
$movie->echooutput = true;
$movie->service = 'console';
diff --git a/app/Http/Controllers/Admin/AdminMovieController.php b/app/Http/Controllers/Admin/AdminMovieController.php
index 60b566edb..0e1d8d645 100644
--- a/app/Http/Controllers/Admin/AdminMovieController.php
+++ b/app/Http/Controllers/Admin/AdminMovieController.php
@@ -5,11 +5,19 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\BasePageController;
use App\Models\MovieInfo;
use App\Models\Release;
-use Blacklight\Movie;
+use App\Services\MovieService;
use Illuminate\Http\Request;
class AdminMovieController extends BasePageController
{
+ protected MovieService $movieService;
+
+ public function __construct(MovieService $movieService)
+ {
+ parent::__construct();
+ $this->movieService = $movieService;
+ }
+
/**
* @throws \Exception
*/
@@ -56,8 +64,7 @@ class AdminMovieController extends BasePageController
}
// Check if movie already exists
- $movie = new Movie(['Settings' => null]);
- $movCheck = $movie->getMovieInfo($id);
+ $movCheck = $this->movieService->getMovieInfo($id);
if ($movCheck !== null) {
return redirect()->to('/admin/movie-edit?id='.$id)
@@ -66,7 +73,7 @@ class AdminMovieController extends BasePageController
// Try to fetch and add the movie from TMDB
try {
- $movieInfo = $movie->updateMovieInfo($id);
+ $movieInfo = $this->movieService->updateMovieInfo($id);
if ($movieInfo) {
// Link any existing releases to this movie
@@ -101,7 +108,6 @@ class AdminMovieController extends BasePageController
*/
public function edit(Request $request)
{
- $movie = new Movie;
$title = 'Movie Edit';
// Check if ID is provided
@@ -111,7 +117,7 @@ class AdminMovieController extends BasePageController
}
$id = $request->input('id');
- $mov = $movie->getMovieInfo($id);
+ $mov = $this->movieService->getMovieInfo($id);
if ($mov === null) {
return redirect()->to('admin/movie-list')
@@ -125,7 +131,7 @@ class AdminMovieController extends BasePageController
\define('STDOUT', fopen('php://stdout', 'wb'));
}
- $movieInfo = $movie->updateMovieInfo($id);
+ $movieInfo = $this->movieService->updateMovieInfo($id);
if ($movieInfo) {
return redirect()->back()
@@ -168,7 +174,7 @@ class AdminMovieController extends BasePageController
$request->merge(['cover' => file_exists($coverLoc) ? 1 : 0]);
$request->merge(['backdrop' => file_exists($backdropLoc) ? 1 : 0]);
- $movie->update([
+ $this->movieService->update([
'actors' => $request->input('actors'),
'backdrop' => $request->input('backdrop'),
'cover' => $request->input('cover'),
diff --git a/app/Http/Controllers/DetailsController.php b/app/Http/Controllers/DetailsController.php
index 4cb3b27cc..21eefd0cd 100644
--- a/app/Http/Controllers/DetailsController.php
+++ b/app/Http/Controllers/DetailsController.php
@@ -12,12 +12,12 @@ use App\Models\ReleaseRegex;
use App\Models\Settings;
use App\Models\UserDownload;
use App\Models\Video;
+use App\Services\MovieService;
use App\Services\Releases\ReleaseSearchService;
use Blacklight\AniDB;
use Blacklight\Books;
use Blacklight\Console;
use Blacklight\Games;
-use Blacklight\Movie;
use Blacklight\Music;
use Blacklight\ReleaseExtra;
use Blacklight\XXX;
@@ -27,10 +27,13 @@ class DetailsController extends BasePageController
{
private ReleaseSearchService $releaseSearchService;
- public function __construct(ReleaseSearchService $releaseSearchService)
+ private MovieService $movieService;
+
+ public function __construct(ReleaseSearchService $releaseSearchService, MovieService $movieService)
{
parent::__construct();
$this->releaseSearchService = $releaseSearchService;
+ $this->movieService = $movieService;
}
public function show(Request $request, string $guid)
@@ -70,15 +73,14 @@ class DetailsController extends BasePageController
$mov = '';
if ($data['imdbid'] !== '' && $data['imdbid'] !== 0000000) {
- $movie = new Movie(['Settings' => $this->settings]);
- $mov = $movie->getMovieInfo($data['imdbid']);
+ $mov = $this->movieService->getMovieInfo($data['imdbid']);
if (! empty($mov['title'])) {
$mov['title'] = str_replace(['/', '\\'], '', $mov['title']);
$mov['actors'] = makeFieldLinks($mov, 'actors', 'movies');
$mov['genre'] = makeFieldLinks($mov, 'genre', 'movies');
$mov['director'] = makeFieldLinks($mov, 'director', 'movies');
if (Settings::settingValue('trailers_display')) {
- $trailer = empty($mov['trailer']) || $mov['trailer'] === '' ? $movie->getTrailer($data['imdbid']) : $mov['trailer'];
+ $trailer = empty($mov['trailer']) || $mov['trailer'] === '' ? $this->movieService->getTrailer($data['imdbid']) : $mov['trailer'];
if ($trailer) {
$mov['trailer'] = sprintf('', Settings::settingValue('trailers_size_x'), Settings::settingValue('trailers_size_y'), $trailer);
}
diff --git a/app/Http/Controllers/MovieController.php b/app/Http/Controllers/MovieController.php
index 37ca1379b..07f2fcbb2 100644
--- a/app/Http/Controllers/MovieController.php
+++ b/app/Http/Controllers/MovieController.php
@@ -3,18 +3,28 @@
namespace App\Http\Controllers;
use App\Models\Category;
-use Blacklight\Movie;
+use App\Services\MovieBrowseService;
+use App\Services\MovieService;
use Illuminate\Http\Request;
class MovieController extends BasePageController
{
+ protected MovieBrowseService $movieBrowseService;
+
+ protected MovieService $movieService;
+
+ public function __construct(MovieBrowseService $movieBrowseService, MovieService $movieService)
+ {
+ parent::__construct();
+ $this->movieBrowseService = $movieBrowseService;
+ $this->movieService = $movieService;
+ }
+
/**
* @throws \Exception
*/
public function showMovies(Request $request, string $id = '')
{
- $movie = new Movie(['Settings' => $this->settings]);
-
$moviecats = Category::getChildren(Category::MOVIE_ROOT)->map(function ($mcat) {
return ['id' => $mcat->id, 'title' => $mcat->title];
});
@@ -31,12 +41,12 @@ class MovieController extends BasePageController
$offset = ($page - 1) * config('nntmux.items_per_cover_page');
$orderby = $request->input('ob', '');
- $ordering = $movie->getMovieOrdering();
+ $ordering = $this->movieBrowseService->getMovieOrdering();
if (! in_array($orderby, $ordering, false)) {
$orderby = '';
}
- $rslt = $movie->getMovieRange($page, $catarray, $offset, config('nntmux.items_per_cover_page'), $orderby, -1, $this->userdata->categoryexclusions);
+ $rslt = $this->movieBrowseService->getMovieRange($page, $catarray, $offset, config('nntmux.items_per_cover_page'), $orderby, -1, $this->userdata->categoryexclusions);
$results = $this->paginate($rslt ?? [], $rslt[0]->_totalcount ?? 0, config('nntmux.items_per_cover_page'), $page, $request->url(), $request->query());
$movies = $results->map(function ($result) {
@@ -67,7 +77,7 @@ class MovieController extends BasePageController
'director' => stripslashes($request->input('director', '')),
'ratings' => range(1, 9),
'rating' => $request->input('rating', ''),
- 'genres' => $movie->getGenres(),
+ 'genres' => $this->movieBrowseService->getGenres(),
'genre' => $request->input('genre', ''),
'years' => $years,
'year' => $request->input('year', ''),
@@ -94,17 +104,15 @@ class MovieController extends BasePageController
*/
public function showMovie(Request $request, string $imdbid)
{
- $movie = new Movie(['Settings' => $this->settings]);
-
// Get movie info
- $movieInfo = $movie->getMovieInfo($imdbid);
+ $movieInfo = $this->movieService->getMovieInfo($imdbid);
if (! $movieInfo) {
return redirect()->route('Movies')->with('error', 'Movie not found');
}
// Get all releases for this movie
- $rslt = $movie->getMovieRange(1, [], 0, 1000, '', -1, $this->userdata->categoryexclusions);
+ $rslt = $this->movieBrowseService->getMovieRange(1, [], 0, 1000, '', -1, $this->userdata->categoryexclusions);
// Filter to only this movie's IMDB ID
$movieData = collect($rslt)->firstWhere('imdbid', $imdbid);
@@ -183,10 +191,8 @@ class MovieController extends BasePageController
*/
public function showTrailer(Request $request)
{
- $movie = new Movie;
-
if ($request->has('id') && ctype_digit($request->input('id'))) {
- $mov = $movie->getMovieInfo($request->input('id'));
+ $mov = $this->movieService->getMovieInfo($request->input('id'));
if (! $mov) {
return response()->json(['message' => 'There is no trailer for this movie.'], 404);
@@ -224,7 +230,6 @@ class MovieController extends BasePageController
*/
public function showTrending(Request $request)
{
- $movie = new Movie(['Settings' => $this->settings]);
// Cache key for trending movies (48 hours)
$cacheKey = 'trending_movies_top_15_48h';
diff --git a/app/Http/Controllers/MyMoviesController.php b/app/Http/Controllers/MyMoviesController.php
index d4db47fc1..cacea2d4c 100644
--- a/app/Http/Controllers/MyMoviesController.php
+++ b/app/Http/Controllers/MyMoviesController.php
@@ -5,24 +5,32 @@ namespace App\Http\Controllers;
use App\Models\Category;
use App\Models\Settings;
use App\Models\UserMovie;
+use App\Services\MovieBrowseService;
+use App\Services\MovieService;
use App\Services\Releases\ReleaseBrowseService;
-use Blacklight\Movie;
use Illuminate\Http\Request;
class MyMoviesController extends BasePageController
{
private ReleaseBrowseService $releaseBrowseService;
- public function __construct(ReleaseBrowseService $releaseBrowseService)
- {
+ private MovieService $movieService;
+
+ private MovieBrowseService $movieBrowseService;
+
+ public function __construct(
+ ReleaseBrowseService $releaseBrowseService,
+ MovieService $movieService,
+ MovieBrowseService $movieBrowseService
+ ) {
parent::__construct();
$this->releaseBrowseService = $releaseBrowseService;
+ $this->movieService = $movieService;
+ $this->movieBrowseService = $movieBrowseService;
}
public function show(Request $request)
{
- $mv = new Movie;
-
$action = $request->input('id') ?? '';
$imdbid = $request->input('imdb') ?? '';
@@ -53,7 +61,7 @@ class MyMoviesController extends BasePageController
return redirect()->to('/mymovies');
}
- $movie = $mv->getMovieInfo($imdbid);
+ $movie = $this->movieService->getMovieInfo($imdbid);
if (! $movie) {
return redirect()->to('/mymovies');
}
@@ -149,11 +157,11 @@ class MyMoviesController extends BasePageController
}
}
- $ordering = $this->releaseBrowseService->getBrowseOrdering();
+ $ordering = $request->input('ob', '');
$page = $request->has('page') && is_numeric($request->input('page')) ? $request->input('page') : 1;
- $results = $mv->getMovieRange($page, $movie['categoryNames'], $offset, config('nntmux.items_per_cover_page'), $ordering, -1, $this->userdata->categoryexclusions);
+ $results = $this->movieBrowseService->getMovieRange($page, [], $offset, config('nntmux.items_per_cover_page'), $ordering, -1, $this->userdata->categoryexclusions);
$this->viewData['covgroup'] = '';
diff --git a/app/Http/Resources/MovieCollection.php b/app/Http/Resources/MovieCollection.php
new file mode 100644
index 000000000..14b0515aa
--- /dev/null
+++ b/app/Http/Resources/MovieCollection.php
@@ -0,0 +1,128 @@
+
+ */
+ public function toArray(Request $request): array
+ {
+ return [
+ 'data' => $this->collection,
+ 'meta' => [
+ 'total' => $this->total(),
+ 'per_page' => $this->perPage(),
+ 'current_page' => $this->currentPage(),
+ 'last_page' => $this->lastPage(),
+ ],
+ 'links' => [
+ 'first' => $this->url(1),
+ 'last' => $this->url($this->lastPage()),
+ 'prev' => $this->previousPageUrl(),
+ 'next' => $this->nextPageUrl(),
+ ],
+ ];
+ }
+
+ /**
+ * Get total items count when available.
+ */
+ protected function total(): int
+ {
+ if (method_exists($this->resource, 'total')) {
+ return $this->resource->total();
+ }
+
+ return $this->collection->count();
+ }
+
+ /**
+ * Get items per page when available.
+ */
+ protected function perPage(): int
+ {
+ if (method_exists($this->resource, 'perPage')) {
+ return $this->resource->perPage();
+ }
+
+ return $this->collection->count();
+ }
+
+ /**
+ * Get current page when available.
+ */
+ protected function currentPage(): int
+ {
+ if (method_exists($this->resource, 'currentPage')) {
+ return $this->resource->currentPage();
+ }
+
+ return 1;
+ }
+
+ /**
+ * Get last page when available.
+ */
+ protected function lastPage(): int
+ {
+ if (method_exists($this->resource, 'lastPage')) {
+ return $this->resource->lastPage();
+ }
+
+ return 1;
+ }
+
+ /**
+ * Get URL for a specific page.
+ */
+ protected function url(int $page): ?string
+ {
+ if (method_exists($this->resource, 'url')) {
+ return $this->resource->url($page);
+ }
+
+ return null;
+ }
+
+ /**
+ * Get previous page URL.
+ */
+ protected function previousPageUrl(): ?string
+ {
+ if (method_exists($this->resource, 'previousPageUrl')) {
+ return $this->resource->previousPageUrl();
+ }
+
+ return null;
+ }
+
+ /**
+ * Get next page URL.
+ */
+ protected function nextPageUrl(): ?string
+ {
+ if (method_exists($this->resource, 'nextPageUrl')) {
+ return $this->resource->nextPageUrl();
+ }
+
+ return null;
+ }
+}
+
diff --git a/app/Http/Resources/MovieResource.php b/app/Http/Resources/MovieResource.php
new file mode 100644
index 000000000..f6beb0273
--- /dev/null
+++ b/app/Http/Resources/MovieResource.php
@@ -0,0 +1,71 @@
+
+ */
+ public function toArray(Request $request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'imdbid' => $this->imdbid,
+ 'tmdbid' => $this->tmdbid,
+ 'traktid' => $this->traktid,
+ 'title' => $this->title,
+ 'year' => $this->year,
+ 'rating' => $this->rating,
+ 'rtrating' => $this->rtrating,
+ 'tagline' => $this->tagline,
+ 'plot' => $this->plot,
+ 'genre' => $this->genre,
+ 'type' => $this->type,
+ 'director' => $this->director,
+ 'actors' => $this->actors,
+ 'language' => $this->language,
+ 'cover' => $this->cover ? true : false,
+ 'backdrop' => $this->backdrop ? true : false,
+ 'trailer' => $this->trailer,
+ 'cover_url' => $this->getCoverUrl(),
+ 'backdrop_url' => $this->getBackdropUrl(),
+ 'imdb_url' => $this->imdbid ? 'https://www.imdb.com/title/tt'.$this->imdbid.'/' : null,
+ 'created_at' => $this->created_at?->toIso8601String(),
+ 'updated_at' => $this->updated_at?->toIso8601String(),
+ ];
+ }
+
+ /**
+ * Get the cover image URL.
+ */
+ protected function getCoverUrl(): ?string
+ {
+ if (! $this->cover || ! $this->imdbid) {
+ return null;
+ }
+
+ return url('/covers/movies/'.$this->imdbid.'-cover.jpg');
+ }
+
+ /**
+ * Get the backdrop image URL.
+ */
+ protected function getBackdropUrl(): ?string
+ {
+ if (! $this->backdrop || ! $this->imdbid) {
+ return null;
+ }
+
+ return url('/covers/movies/'.$this->imdbid.'-backdrop.jpg');
+ }
+}
+
diff --git a/app/Services/MovieBrowseService.php b/app/Services/MovieBrowseService.php
new file mode 100644
index 000000000..66243766f
--- /dev/null
+++ b/app/Services/MovieBrowseService.php
@@ -0,0 +1,184 @@
+showPasswords = app(\App\Services\Releases\ReleaseBrowseService::class)->showPasswords();
+ }
+
+ /**
+ * Get movie releases with covers for movie browse page.
+ */
+ public function getMovieRange(int $page, array $cat, int $start, int $num, string $orderBy, int $maxAge = -1, array $excludedCats = []): mixed
+ {
+ $page = max(1, $page);
+ $start = max(0, $start);
+ $catsrch = '';
+ if (count($cat) > 0 && $cat[0] !== -1) {
+ $catsrch = Category::getCategorySearch($cat);
+ }
+ $order = $this->getMovieOrder($orderBy);
+ $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
+ $whereAge = $maxAge > 0 ? 'AND r.postdate > NOW() - INTERVAL '.$maxAge.' DAY ' : '';
+ $whereExcluded = count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : '';
+ $limitClause = $start === false ? '' : ' LIMIT '.$num.' OFFSET '.$start;
+ $moviesSql = "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 m.title != '' AND m.imdbid != '0000000' "
+ ."AND r.passwordstatus {$this->showPasswords} "
+ .$this->getBrowseBy().' '
+ .(! empty($catsrch) ? $catsrch.' ' : '')
+ .$whereAge
+ .$whereExcluded.' '
+ ."GROUP BY m.imdbid ORDER BY {$order[0]} {$order[1]} {$limitClause}";
+ $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 $id) {
+ $movieIDs[] = $id->imdbid;
+ $releaseIDs[] = $id->grp_release_id;
+ }
+ }
+ $inMovieIds = (is_array($movieIDs) && ! empty($movieIDs)) ? implode(',', $movieIDs) : -1;
+ $inReleaseIds = (is_array($releaseIDs) && ! empty($releaseIDs)) ? implode(',', $releaseIDs) : -1;
+ $sql = '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.adddate ORDER BY r.postdate DESC SEPARATOR ',' ) AS grp_release_adddate, "
+ ."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 ($inMovieIds) AND r.id IN ($inReleaseIds) "
+ .(! empty($catsrch) ? $catsrch.' ' : '')
+ ."GROUP BY m.imdbid ORDER BY {$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.
+ */
+ protected function getMovieOrder(string $orderBy): array
+ {
+ $orderArr = explode('_', (($orderBy === '') ? 'MAX(r.postdate)' : $orderBy));
+ $orderField = match ($orderArr[0]) {
+ 'title' => 'm.title',
+ 'year' => 'm.year',
+ 'rating' => 'm.rating',
+ default => 'MAX(r.postdate)',
+ };
+
+ return [$orderField, isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1]) ? $orderArr[1] : 'desc'];
+ }
+
+ /**
+ * Order types for movies page.
+ */
+ public function getMovieOrdering(): array
+ {
+ return ['title_asc', 'title_desc', 'year_asc', 'year_desc', 'rating_asc', 'rating_desc'];
+ }
+
+ 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 IMDB genres.
+ */
+ 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',
+ ];
+ }
+}
+
diff --git a/Blacklight/Movie.php b/app/Services/MovieService.php
old mode 100755
new mode 100644
similarity index 73%
rename from Blacklight/Movie.php
rename to app/Services/MovieService.php
index 0697f4008..98abc37a0
--- a/Blacklight/Movie.php
+++ b/app/Services/MovieService.php
@@ -1,56 +1,36 @@
releaseImage = new ReleaseImage;
$this->colorCli = new ColorCLI;
$this->traktcheck = config('nntmux_api.trakttv_api_key');
@@ -125,7 +89,6 @@ class Movie
if (! File::isDirectory($cacheDir)) {
File::makeDirectory($cacheDir, 0777, false, true);
}
- // $this->config->cachedir = $cacheDir; // removed imdbphp config
$this->imdburl = (int) Settings::settingValue('imdburl') !== 0;
$this->movieqty = Settings::settingValue('maximdbprocessed') !== '' ? (int) Settings::settingValue('maximdbprocessed') : 100;
@@ -137,151 +100,20 @@ class Movie
}
/**
- * @return Builder|Model|null|object
+ * Get movie info by IMDB ID.
*/
- public function getMovieInfo($imdbId)
+ public function getMovieInfo(string $imdbId): ?MovieInfo
{
return MovieInfo::query()->where('imdbid', $imdbId)->first();
}
- /**
- * Get movie releases with covers for movie browse page.
- *
- *
- * @return array|mixed
- */
- public function getMovieRange($page, $cat, $start, $num, $orderBy, int $maxAge = -1, array $excludedCats = [])
- {
- $page = max(1, $page);
- $start = max(0, $start);
- $catsrch = '';
- if (count($cat) > 0 && $cat[0] !== -1) {
- $catsrch = Category::getCategorySearch($cat);
- }
- $order = $this->getMovieOrder($orderBy);
- $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
- $whereAge = $maxAge > 0 ? 'AND r.postdate > NOW() - INTERVAL '.$maxAge.' DAY ' : '';
- $whereExcluded = count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : '';
- $limitClause = $start === false ? '' : ' LIMIT '.$num.' OFFSET '.$start;
- $moviesSql = "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 m.title != '' AND m.imdbid != '0000000' "
- ."AND r.passwordstatus {$this->showPasswords} "
- .$this->getBrowseBy().' '
- .(! empty($catsrch) ? $catsrch.' ' : '')
- .$whereAge
- .$whereExcluded.' '
- ."GROUP BY m.imdbid ORDER BY {$order[0]} {$order[1]} {$limitClause}";
- $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 $id) {
- $movieIDs[] = $id->imdbid;
- $releaseIDs[] = $id->grp_release_id;
- }
- }
- $inMovieIds = (is_array($movieIDs) && ! empty($movieIDs)) ? implode(',', $movieIDs) : -1;
- $inReleaseIds = (is_array($releaseIDs) && ! empty($releaseIDs)) ? implode(',', $releaseIDs) : -1;
- $sql = '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.adddate ORDER BY r.postdate DESC SEPARATOR ',' ) AS grp_release_adddate, "
- ."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 ($inMovieIds) AND r.id IN ($inReleaseIds) "
- .(! empty($catsrch) ? $catsrch.' ' : '')
- ."GROUP BY m.imdbid ORDER BY {$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.
- */
- protected function getMovieOrder($orderBy): array
- {
- $orderArr = explode('_', (($orderBy === '') ? 'MAX(r.postdate)' : $orderBy));
- $orderField = match ($orderArr[0]) {
- 'title' => 'm.title',
- 'year' => 'm.year',
- 'rating' => 'm.rating',
- default => 'MAX(r.postdate)',
- };
-
- return [$orderField, isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1]) ? $orderArr[1] : 'desc'];
- }
-
- /**
- * Order types for movies page.
- */
- public function getMovieOrdering(): array
- {
- return ['title_asc', 'title_desc', 'year_asc', 'year_desc', 'rating_asc', 'rating_desc'];
- }
-
- 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.
*
- * @return bool|string
- *
* @throws \Exception
* @throws GuzzleException
*/
- public function getTrailer(int $imdbId)
+ public function getTrailer(int $imdbId): string|false
{
$trailer = MovieInfo::query()->where('imdbid', $imdbId)->where('trailer', '<>', '')->first(['trailer']);
if ($trailer !== null) {
@@ -307,10 +139,8 @@ class Movie
/**
* Parse trakt info, insert into DB.
- *
- * @return mixed
*/
- public function parseTraktTv(array &$data)
+ public function parseTraktTv(array &$data): mixed
{
if (empty($data['ids']['imdb'])) {
return false;
@@ -325,7 +155,6 @@ class Movie
}
$imdbId = (str_starts_with($data['ids']['imdb'], 'tt')) ? substr($data['ids']['imdb'], 2) : $data['ids']['imdb'];
$cover = 0;
- // Fix incorrect file existence check (parentheses and concatenation)
if (File::isFile($this->imgSavePath.$imdbId.'-cover.jpg')) {
$cover = 1;
}
@@ -346,10 +175,7 @@ class Movie
]);
}
- /**
- * @return mixed|string
- */
- private function checkTraktValue($value)
+ private function checkTraktValue(mixed $value): mixed
{
if (\is_array($value) && ! empty($value)) {
$temp = '';
@@ -377,10 +203,8 @@ class Movie
/**
* Choose the first non-empty variable from up to five inputs.
- *
- * @return array|string
*/
- protected function setVariables(string|array $variable1, string|array $variable2, string|array $variable3, string|array $variable4, string|array $variable5 = '')
+ protected function setVariables(string|array $variable1, string|array $variable2, string|array $variable3, string|array $variable4, string|array $variable5 = ''): array|string
{
if (! empty($variable1)) {
return $variable1;
@@ -403,8 +227,6 @@ class Movie
/**
* Update movie on movie-edit page.
- *
- * @param array $values Array of keys/values to update. See $validKeys
*/
public function update(array $values): bool
{
@@ -451,10 +273,9 @@ class Movie
/**
* Fetch IMDB/TMDB/TRAKT/OMDB/iTunes info for the movie.
*
- *
* @throws \Exception
*/
- public function updateMovieInfo($imdbId): bool
+ public function updateMovieInfo(string $imdbId): bool
{
if ($this->echooutput && $this->service !== '') {
$this->colorCli->primary('Fetching IMDB info from TMDB/IMDB/Trakt/OMDB/iTunes using IMDB id: '.$imdbId);
@@ -656,7 +477,7 @@ class Movie
/**
* Fetch FanArt.tv backdrop / cover / title.
*/
- protected function fetchFanartTVProperties($imdbId): false|array
+ protected function fetchFanartTVProperties(string $imdbId): false|array
{
if (! $this->fanart->isConfigured()) {
return false;
@@ -681,21 +502,14 @@ class Movie
/**
* Fetch movie information from TMDB using an IMDB ID.
- *
- * @param string $imdbId The IMDB ID to look up
- * @param bool $text Whether the ID is already in text format
- * @return array|false Movie data array or false if not found/matched
*/
public function fetchTMDBProperties(string $imdbId, bool $text = false): array|false
{
- // Format the lookup ID correctly with 'tt' prefix if needed
$lookupId = $text === false && (strlen($imdbId) === 7 || strlen($imdbId) === 8) ? 'tt'.$imdbId : $imdbId;
- // Create a cache key for this request
$cacheKey = 'tmdb_movie_'.md5($lookupId);
- $expiresAt = now()->addDays(7); // Cache for 7 days since movie data rarely changes
+ $expiresAt = now()->addDays(7);
- // Check if we have this cached
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
@@ -707,7 +521,6 @@ class Movie
return false;
}
- // Fetch movie data from TMDB
$tmdbLookup = $tmdbClient->getMovie($lookupId, ['credits']);
if ($tmdbLookup === null || empty($tmdbLookup)) {
@@ -716,7 +529,6 @@ class Movie
return false;
}
- // Title similarity check
$title = TmdbClient::getString($tmdbLookup, 'title');
if ($this->currentTitle !== '' && ! empty($title)) {
similar_text($this->currentTitle, $title, $percent);
@@ -727,7 +539,6 @@ class Movie
}
}
- // Year similarity check
$releaseDate = TmdbClient::getString($tmdbLookup, 'release_date');
if ($this->currentYear !== '' && ! empty($releaseDate)) {
$tmdbYear = Carbon::parse($releaseDate)->year;
@@ -740,7 +551,6 @@ class Movie
}
}
- // Build the return array with proper null handling
$imdbIdFromResponse = TmdbClient::getString($tmdbLookup, 'imdb_id');
$ret = [
'title' => $title,
@@ -757,13 +567,11 @@ class Movie
'backdrop' => '',
];
- // Rating
$vote = TmdbClient::getFloat($tmdbLookup, 'vote_average');
if ($vote > 0) {
$ret['rating'] = $vote;
}
- // Actors
$credits = TmdbClient::getArray($tmdbLookup, 'credits');
$cast = TmdbClient::getArray($credits, 'cast');
if (! empty($cast)) {
@@ -778,7 +586,6 @@ class Movie
}
}
- // Director - get first director only
$crew = TmdbClient::getArray($credits, 'crew');
foreach ($crew as $crewMember) {
if (! is_array($crewMember)) {
@@ -792,12 +599,10 @@ class Movie
}
}
- // Year
if (! empty($releaseDate)) {
$ret['year'] = Carbon::parse($releaseDate)->year;
}
- // Genres
$genresa = TmdbClient::getArray($tmdbLookup, 'genres');
if (! empty($genresa)) {
$genres = [];
@@ -811,7 +616,6 @@ class Movie
}
}
- // Cover and backdrop
$posterPath = TmdbClient::getString($tmdbLookup, 'poster_path');
if (! empty($posterPath)) {
$ret['cover'] = 'https://image.tmdb.org/t/p/original'.$posterPath;
@@ -822,21 +626,16 @@ class Movie
$ret['backdrop'] = 'https://image.tmdb.org/t/p/original'.$backdropPath;
}
- // Log success
if ($this->echooutput) {
$this->colorCli->info('TMDb found '.$ret['title']);
}
- // Cache the result
Cache::put($cacheKey, $ret, $expiresAt);
return $ret;
} catch (\Throwable $e) {
- // Log the error
Log::warning('TMDB API error for '.$lookupId.': '.$e->getMessage());
-
- // Cache the failure but for shorter time
Cache::put($cacheKey, false, now()->addHours(6));
return false;
@@ -845,9 +644,6 @@ class Movie
/**
* Fetch movie information from IMDB.
- *
- * @param string $imdbId The IMDB ID to look up
- * @return array|false Movie data array or false if not found/matched
*/
public function fetchIMDBProperties(string $imdbId): array|false
{
@@ -897,39 +693,30 @@ class Movie
/**
* Fetch movie information from Trakt.tv using IMDB ID.
*
- * @param string $imdbId The IMDB ID without the 'tt' prefix
- * @return array|false Movie data array or false if not found/matched
- *
- * @throws GuzzleException Only if unhandled HTTP errors occur
+ * @throws GuzzleException
*/
public function fetchTraktTVProperties(string $imdbId): array|false
{
- // Skip if Trakt API key isn't configured
if ($this->traktcheck === null) {
return false;
}
- // Create a cache key for this request
$cacheKey = 'trakt_movie_'.md5($imdbId);
- $expiresAt = now()->addDays(7); // Cache for 7 days since movie data rarely changes
+ $expiresAt = now()->addDays(7);
- // Check if we have this cached
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
try {
- // Fetch movie data from Trakt.tv
$resp = $this->traktTv->client->getMovieSummary('tt'.$imdbId, 'full');
- // If no result or no title, cache the failure and return
if ($resp === false || empty($resp['title'])) {
Cache::put($cacheKey, false, now()->addHours(6));
return false;
}
- // Title similarity check
if (! empty($this->currentTitle)) {
similar_text($this->currentTitle, $resp['title'], $percent);
if ($percent < self::MATCH_PERCENT) {
@@ -939,7 +726,6 @@ class Movie
}
}
- // Year similarity check
if (! empty($this->currentYear) && ! empty($resp['year'])) {
similar_text($this->currentYear, $resp['year'], $percent);
if ($percent < self::YEAR_MATCH_PERCENT) {
@@ -949,7 +735,6 @@ class Movie
}
}
- // Build the return data
$movieData = [
'id' => $resp['ids']['trakt'] ?? null,
'title' => $resp['title'],
@@ -964,21 +749,16 @@ class Movie
'trailer' => $resp['trailer'] ?? '',
];
- // Log success
if ($this->echooutput) {
$this->colorCli->info('Trakt found '.$movieData['title']);
}
- // Cache the successful result
Cache::put($cacheKey, $movieData, $expiresAt);
return $movieData;
} catch (\Throwable $e) {
- // Log the error
Log::warning('Trakt API error for '.$imdbId.': '.$e->getMessage());
-
- // Cache the failure but for shorter time
Cache::put($cacheKey, false, now()->addHours(6));
return false;
@@ -987,31 +767,23 @@ class Movie
/**
* Fetch movie information from OMDB API using IMDB ID.
- *
- * @param string $imdbId The IMDB ID without the 'tt' prefix
- * @return array|false Movie data array or false if not found/matched
*/
public function fetchOmdbAPIProperties(string $imdbId): array|false
{
- // Skip if OMDB API key isn't configured
if ($this->omdbapikey === null) {
return false;
}
- // Create a cache key for this request
$cacheKey = 'omdb_movie_'.md5($imdbId);
- $expiresAt = now()->addDays(7); // Cache for 7 days since movie data rarely changes
+ $expiresAt = now()->addDays(7);
- // Check if we have this cached
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
try {
- // Fetch movie data from OMDB
$resp = $this->omdbApi->fetch('i', 'tt'.$imdbId);
- // Validate the response
if (! is_object($resp) ||
$resp->message !== 'OK' ||
Str::contains($resp->data->Response, 'Error:') ||
@@ -1022,7 +794,6 @@ class Movie
return false;
}
- // Title similarity check when we have a title to compare against
if (! empty($this->currentTitle)) {
similar_text($this->currentTitle, $resp->data->Title, $percent);
if ($percent < self::MATCH_PERCENT) {
@@ -1031,7 +802,6 @@ class Movie
return false;
}
- // Year similarity check
if (! empty($this->currentYear)) {
similar_text($this->currentYear, $resp->data->Year, $percent);
if ($percent < self::YEAR_MATCH_PERCENT) {
@@ -1042,13 +812,11 @@ class Movie
}
}
- // Safely extract the Rotten Tomatoes rating
$rtRating = '';
if (isset($resp->data->Ratings) && is_array($resp->data->Ratings) && count($resp->data->Ratings) > 1) {
$rtRating = $resp->data->Ratings[1]->Value ?? '';
}
- // Build the movie data array
$movieData = [
'title' => $resp->data->Title ?? '',
'cover' => $resp->data->Poster ?? '',
@@ -1064,21 +832,16 @@ class Movie
'boxOffice' => $resp->data->BoxOffice ?? '',
];
- // Log success
if ($this->echooutput) {
$this->colorCli->info('OMDbAPI Found '.$movieData['title']);
}
- // Cache the successful result
Cache::put($cacheKey, $movieData, $expiresAt);
return $movieData;
} catch (\Throwable $e) {
- // Log the error
Log::warning('OMDB API error for '.$imdbId.': '.$e->getMessage());
-
- // Cache the failure but for shorter time
Cache::put($cacheKey, false, now()->addHours(6));
return false;
@@ -1088,23 +851,15 @@ class Movie
/**
* Update a release with an IMDB ID and related movie information.
*
- * @param string $buffer Data to parse an IMDB ID from
- * @param string $service Method that called this method
- * @param int $id ID of the release
- * @param int $processImdb Whether to fetch movie info (1) or not (0)
- * @return string|false IMDB ID or false if not found
- *
* @throws \Exception
*/
public function doMovieUpdate(string $buffer, string $service, int $id, int $processImdb = 1): string|false
{
- // First check if this release already has an IMDB ID to avoid duplicate processing
$existingImdbId = Release::query()->where('id', $id)->value('imdbid');
if ($existingImdbId !== null && $existingImdbId !== '' && $existingImdbId !== '0000000') {
return $existingImdbId;
}
- // Extract IMDB ID using regex
$imdbId = false;
if (preg_match('/(?:imdb.*?)?(?:tt|Title\?)(?P\d{5,8})/i', $buffer, $hits)) {
$imdbId = $hits['imdbid'];
@@ -1117,21 +872,17 @@ class Movie
$this->colorCli->info($this->service.' found IMDBid: tt'.$imdbId);
}
- // Get movie info ID
$movieInfoId = MovieInfo::query()->where('imdbid', $imdbId)->first(['id']);
- // Update release with IMDB 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);
- $thirtyDaysInSeconds = 30 * 24 * 60 * 60; // 30 days in seconds
+ $thirtyDaysInSeconds = 30 * 24 * 60 * 60;
- // Check if movie info is missing or outdated
if ($movCheck === null ||
(isset($movCheck['updated_at']) &&
(time() - strtotime($movCheck['updated_at'])) > $thirtyDaysInSeconds)) {
@@ -1139,13 +890,10 @@ class Movie
$info = $this->updateMovieInfo($imdbId);
if ($info === false) {
- // Update failed, mark with invalid IMDB ID
Release::query()->where('id', $id)->update(['imdbid' => '0000000']);
} elseif ($info === true) {
- // Get fresh movie info ID after update
$freshMovieInfo = MovieInfo::query()->where('imdbid', $imdbId)->first(['id']);
- // Update release with movie info ID
Release::query()->where('id', $id)->update([
'movieinfo_id' => $freshMovieInfo !== null ? $freshMovieInfo['id'] : null,
]);
@@ -1155,7 +903,6 @@ class Movie
return $imdbId;
} catch (\Exception $e) {
- // Log the error
Log::error('Error updating movie information: '.$e->getMessage());
return false;
@@ -1168,35 +915,20 @@ class Movie
/**
* Process releases with no IMDB IDs by looking up movie information from various sources.
*
- * Searches for IMDB IDs using multiple services in this order:
- * 1. Local database
- * 2. IMDb API
- * 3. OMDb API
- * 4. Trakt.tv
- * 5. The Movie Database (TMDB)
- *
- * @param string $groupID Optional group ID to filter by
- * @param string $guidChar Optional first character of GUID to filter by
- * @param int $lookupIMDB 0: Skip lookup, 1: Process all, 2: Only renamed releases
- *
* @throws \Exception
* @throws GuzzleException
*/
public function processMovieReleases(string $groupID = '', string $guidChar = '', int $lookupIMDB = 1): void
{
- // Skip processing if lookup is disabled
if ($lookupIMDB === 0) {
return;
}
- // Always query fresh data to avoid processing already-processed releases
- // Build query to get releases without IMDB IDs
$query = Release::query()
->select(['searchname', 'id'])
->whereBetween('categories_id', [Category::MOVIE_ROOT, Category::MOVIE_OTHER])
->whereNull('imdbid');
- // Apply filters if provided
if ($groupID !== '') {
$query->where('groups_id', $groupID);
}
@@ -1209,22 +941,17 @@ class Movie
$query->where('isrenamed', '=', 1);
}
- // Execute the query with limit, ordering by latest releases first
$res = $query->orderByDesc('id')->limit($this->movieqty)->get();
-
$movieCount = count($res);
- $failedIDs = []; // Track IDs that need to be marked as unidentifiable
+ $failedIDs = [];
if ($movieCount > 0) {
- // Log the start of processing if multiple movies
if ($this->echooutput && $movieCount > 1) {
$this->colorCli->header('Processing '.$movieCount.' movie releases.');
}
- // Loop over releases
foreach ($res as $arr) {
- // Try to extract movie name and year
if (! $this->parseMovieSearchName($arr['searchname'])) {
$failedIDs[] = $arr['id'];
@@ -1234,28 +961,23 @@ class Movie
$this->currentRelID = $arr['id'];
$movieName = $this->formatMovieName();
- // Log current lookup if output is enabled
if ($this->echooutput) {
$this->colorCli->info('Looking up: '.$movieName);
}
- // Try all available sources to find IMDB ID
$foundIMDB = $this->searchLocalDatabase($arr['id']) ||
$this->searchIMDb($arr['id']) ||
$this->searchOMDbAPI($arr['id']) ||
$this->searchTraktTV($arr['id'], $movieName) ||
$this->searchTMDB($arr['id']);
- // Double-check if we actually got an IMDB ID
if ($foundIMDB) {
- // Movie was successfully updated by one of the services
if ($this->echooutput) {
$this->colorCli->primary('Successfully updated release with IMDB ID');
}
continue;
} else {
- // Verify the release wasn't actually updated
$releaseCheck = Release::query()->where('id', $arr['id'])->whereNotNull('imdbid')->exists();
if ($releaseCheck) {
if ($this->echooutput) {
@@ -1266,13 +988,10 @@ class Movie
}
}
- // If we get here, all searches failed
$failedIDs[] = $arr['id'];
}
- // Batch update all failed releases at once
if (! empty($failedIDs)) {
- // Get searchnames for failed releases to show in output
if ($this->echooutput) {
$failedReleases = Release::query()
->select(['id', 'searchname'])
@@ -1285,7 +1004,6 @@ class Movie
}
}
- // Use chunk to avoid huge queries for many IDs
foreach (array_chunk($failedIDs, 100) as $chunk) {
Release::query()->whereIn('id', $chunk)->update(['imdbid' => '0000000']);
}
@@ -1293,9 +1011,6 @@ class Movie
}
}
- /**
- * Format current movie name with year if available.
- */
private function formatMovieName(): string
{
$movieName = $this->currentTitle;
@@ -1306,9 +1021,6 @@ class Movie
return $movieName;
}
- /**
- * Search local database for movie.
- */
private function searchLocalDatabase(int $releaseId): bool
{
$getIMDBid = $this->localIMDBSearch();
@@ -1321,12 +1033,8 @@ class Movie
return $imdbId !== false;
}
- /**
- * Search IMDb for movie.
- */
private function searchIMDb(int $releaseId): bool
{
- // Simplified scraper-only search
try {
$scraper = app(ImdbScraper::class);
$matches = $scraper->search($this->currentTitle);
@@ -1357,9 +1065,6 @@ class Movie
return false;
}
- /**
- * Search OMDb API for movie.
- */
private function searchOMDbAPI(int $releaseId): bool
{
if ($this->omdbapikey === null) {
@@ -1369,7 +1074,6 @@ class Movie
$omdbTitle = strtolower(str_replace(' ', '_', $this->currentTitle));
try {
- // Search with year if available, otherwise search without year
$buffer = $this->currentYear !== ''
? $this->omdbApi->search($omdbTitle, 'movie', $this->currentYear)
: $this->omdbApi->search($omdbTitle, 'movie');
@@ -1388,16 +1092,12 @@ class Movie
return $imdbId !== false;
} catch (\Exception $e) {
- // Log error but continue processing
Log::error('OMDb API error: '.$e->getMessage());
return false;
}
}
- /**
- * Search Trakt.tv for movie.
- */
private function searchTraktTV(int $releaseId, string $movieName): bool
{
if ($this->traktcheck === null) {
@@ -1422,9 +1122,6 @@ class Movie
}
}
- /**
- * Search TMDB for movie.
- */
private function searchTMDB(int $releaseId): bool
{
try {
@@ -1446,7 +1143,6 @@ class Movie
continue;
}
- // Skip results without ID or release date
$resultId = TmdbClient::getInt($result, 'id');
$releaseDate = TmdbClient::getString($result, 'release_date');
@@ -1454,7 +1150,6 @@ class Movie
continue;
}
- // Compare release years
similar_text(
$this->currentYear,
(string) Carbon::parse($releaseDate)->year,
@@ -1465,7 +1160,6 @@ class Movie
continue;
}
- // Try to get IMDB ID from TMDB
$ret = $this->fetchTMDBProperties((string) $resultId, true);
if ($ret === false || empty($ret['imdbid'])) {
continue;
@@ -1484,54 +1178,40 @@ class Movie
return false;
}
- /**
- * Search for a movie in the local database by title and year.
- *
- * @return string|false IMDB ID without 'tt' prefix if found, false otherwise
- */
protected function localIMDBSearch(): string|false
{
- // Skip processing if title is empty
if (empty($this->currentTitle)) {
return false;
}
- // Create a cache key for this search
$cacheKey = 'local_imdb_'.md5($this->currentTitle.$this->currentYear);
- // Check cache first
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
- // Build the base query
$query = MovieInfo::query()
->select(['imdbid', 'title'])
->where('title', 'like', '%'.$this->currentTitle.'%');
- // Add year range filter if we have a year
if (! empty($this->currentYear)) {
$start = Carbon::createFromFormat('Y', $this->currentYear)->subYears(2)->year;
$end = Carbon::createFromFormat('Y', $this->currentYear)->addYears(2)->year;
$query->whereBetween('year', [$start, $end]);
}
- // Get potential matches in a single query
$potentialMatches = $query->get();
- // If no matches found, cache the failure and return false
if ($potentialMatches->isEmpty()) {
Cache::put($cacheKey, false, now()->addHours(6));
return false;
}
- // Check each potential match for title similarity
foreach ($potentialMatches as $match) {
similar_text($this->currentTitle, $match['title'], $percent);
if ($percent >= self::MATCH_PERCENT) {
- // Found a good match, cache it and return
Cache::put($cacheKey, $match['imdbid'], now()->addDays(7));
if ($this->echooutput) {
@@ -1542,32 +1222,19 @@ class Movie
}
}
- // No good matches found, cache the failure
Cache::put($cacheKey, false, now()->addHours(6));
return false;
}
- /**
- * Parse a movie title and year from a release search name.
- *
- * This method attempts to extract a clean movie title and year from scene release names
- * which often contain quality indicators, source information, and other metadata.
- *
- * @param string $releaseName The raw release name to parse
- * @return bool True if parsing was successful, false otherwise
- */
protected function parseMovieSearchName(string $releaseName): bool
{
- // Skip empty release names
if (empty(trim($releaseName))) {
return false;
}
- // Create a cache key for this parsing operation
$cacheKey = 'parse_movie_'.md5($releaseName);
- // Check if we have a cached result
if (Cache::has($cacheKey)) {
$result = Cache::get($cacheKey);
if (is_array($result)) {
@@ -1582,55 +1249,35 @@ class Movie
$name = $year = '';
- // Common movie quality, format, and release group patterns to identify boundaries
$followingList = '[^\w]((1080|480|720|2160)p|AC3D|Directors([^\w]CUT)?|DD5\.1|(DVD|BD|BR|UHD)(Rip)?|'
.'BluRay|divx|HDTV|iNTERNAL|LiMiTED|(Real\.)?PROPER|RE(pack|Rip)|Sub\.?(fix|pack)|'
.'Unrated|WEB-?DL|WEBRip|(x|H|HEVC)[ ._-]?26[45]|xvid|AAC|REMUX)[^\w]';
- // First attempt: Find pattern with year - most reliable method
if (preg_match('/(?P[\w. -]+)[^\w](?P(19|20)\d\d)/i', $releaseName, $hits)) {
$name = $hits['name'];
$year = $hits['year'];
- }
- // Second attempt: Look for title followed by common release identifiers
- elseif (preg_match('/([^\w]{2,})?(?P[\w .-]+?)'.$followingList.'/i', $releaseName, $hits)) {
+ } elseif (preg_match('/([^\w]{2,})?(?P[\w .-]+?)'.$followingList.'/i', $releaseName, $hits)) {
$name = $hits['name'];
- }
- // Third attempt: Try to match the start of the string up to a pattern
- elseif (preg_match('/^(?P[\w .-]+?)'.$followingList.'/i', $releaseName, $hits)) {
+ } elseif (preg_match('/^(?P[\w .-]+?)'.$followingList.'/i', $releaseName, $hits)) {
$name = $hits['name'];
- }
- // Fourth attempt: Check if the whole string might be a title (for simple releases)
- elseif (strlen($releaseName) <= 100 && ! preg_match('/\.(rar|zip|avi|mkv|mp4)$/i', $releaseName)) {
+ } elseif (strlen($releaseName) <= 100 && ! preg_match('/\.(rar|zip|avi|mkv|mp4)$/i', $releaseName)) {
$name = $releaseName;
}
- // Process the name if we have one
if (! empty($name)) {
- // Clean up the name by removing common patterns
- // 1. Remove any common movie flags like 1080p, BluRay, etc.
$name = preg_replace('/'.$followingList.'/i', ' ', $name);
-
- // 2. Remove content in parentheses, brackets, and periods/underscores without complex alternation
$name = preg_replace('/\([^)]*\)/i', ' ', $name);
- // Remove [ ... ] blocks without regex escapes to satisfy linter
while (($openPos = strpos($name, '[')) !== false && ($closePos = strpos($name, ']', $openPos)) !== false) {
$name = substr($name, 0, $openPos).' '.substr($name, $closePos + 1);
}
$name = str_replace(['.', '_'], ' ', $name);
-
- // 3. Remove scene group names (typically after a hyphen)
$name = preg_replace('/-[A-Z0-9].*$/i', '', $name);
-
- // 4. Clean up multiple spaces
$name = trim(preg_replace('/\s{2,}/', ' ', $name));
- // Validate the extracted name (at least 2 characters, not just numbers)
if (strlen($name) > 2 && ! preg_match('/^\d+$/', $name)) {
$this->currentTitle = $name;
$this->currentYear = $year;
- // Cache the successful result
Cache::put($cacheKey, [
'title' => $name,
'year' => $year,
@@ -1640,7 +1287,6 @@ class Movie
}
}
- // Cache the failed result but with shorter expiration
Cache::put($cacheKey, false, now()->addHours(24));
return false;
@@ -1683,7 +1329,6 @@ class Movie
protected function hasCover(string $imdbId): bool
{
- // Checks both DB flag and physical file existence.
$record = MovieInfo::query()->select('cover')->where('imdbid', $imdbId)->first();
$dbHas = $record !== null && (int) $record->cover === 1;
$filePath = $this->imgSavePath.$imdbId.'-cover.jpg';
@@ -1694,8 +1339,6 @@ class Movie
protected function fetchAndSaveCoverOnly(string $imdbId): bool
{
- // Attempt sources in priority order without pulling full metadata again if possible.
- // Fanart
try {
$fanart = $this->fetchFanartTVProperties($imdbId);
if (! empty($fanart['cover'])) {
@@ -1706,7 +1349,7 @@ class Movie
} catch (\Throwable $e) {
Log::debug('Fanart cover fetch failed for '.$imdbId.': '.$e->getMessage());
}
- // TMDB
+
try {
$tmdb = $this->fetchTMDBProperties($imdbId);
if (! empty($tmdb['cover'])) {
@@ -1717,7 +1360,7 @@ class Movie
} catch (\Throwable $e) {
Log::debug('TMDB cover fetch failed for '.$imdbId.': '.$e->getMessage());
}
- // IMDB
+
try {
$imdb = $this->fetchIMDBProperties($imdbId);
if (! empty($imdb['cover'])) {
@@ -1728,7 +1371,7 @@ class Movie
} catch (\Throwable $e) {
Log::debug('IMDB cover fetch failed for '.$imdbId.': '.$e->getMessage());
}
- // OMDB
+
try {
$omdb = $this->fetchOmdbAPIProperties($imdbId);
if (! empty($omdb['cover'])) {
@@ -1743,3 +1386,4 @@ class Movie
return false;
}
}
+
diff --git a/app/Services/MoviesProcessor.php b/app/Services/MoviesProcessor.php
index 7ba302519..e9ecc1f2c 100644
--- a/app/Services/MoviesProcessor.php
+++ b/app/Services/MoviesProcessor.php
@@ -3,7 +3,6 @@
namespace App\Services;
use App\Models\Settings;
-use Blacklight\Movie;
use GuzzleHttp\Exception\GuzzleException;
class MoviesProcessor
@@ -24,7 +23,7 @@ class MoviesProcessor
{
$processMovies = (is_numeric($processMovies) ? $processMovies : Settings::settingValue('lookupimdb'));
if ($processMovies > 0) {
- (new Movie)->processMovieReleases($groupID, $guidChar, $processMovies);
+ (new MovieService)->processMovieReleases($groupID, $guidChar, $processMovies);
}
}
}
diff --git a/misc/testing/PostProc/check_covers.php b/misc/testing/PostProc/check_covers.php
index 34b1f714d..c3730cd3b 100644
--- a/misc/testing/PostProc/check_covers.php
+++ b/misc/testing/PostProc/check_covers.php
@@ -5,11 +5,12 @@
// --------------------------------------------------------------
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
+use App\Services\MovieService;
use Blacklight\ColorCLI;
-use Blacklight\Movie;
use Illuminate\Support\Facades\DB;
-$movie = new Movie(['Echo' => true]);
+$movie = new MovieService();
+$movie->echooutput = true;
$colorCli = new ColorCLI;
$path2cover = storage_path('covers/movies/');
@@ -49,7 +50,7 @@ if (isset($argv[1]) && ($argv[1] === 'true' || $argv[1] === 'check')) {
."Releases without covers may be reset for post-processing, thus regenerating them and related meta data.\n\n"
."Useful for recovery after filesystem corruption, or as an alternative re-postprocessing tool.\n\n"
."Optional LIMIT parameter restricts number of releases to be reset.\n\n"
- ."php $argv[0] check [LIMIT] ...: Dry run, displays missing covers.\n"
- ."php $argv[0] true [LIMIT] ...: Re-process releases missing covers.\n");
- exit();
+ ."php $argv[0] true ...:Sets all releases missing covers to be reset for post-processing.\n"
+ ."php $argv[0] check ...:Checks and displays all releases missing covers.\n"
+ ."php $argv[0] true 500 ...:Sets only 500 releases missing covers to be reset for post-processing.\n");
}
diff --git a/misc/testing/PostProc/getImdb.php b/misc/testing/PostProc/getImdb.php
index 1dc0d7776..c7e5192ed 100644
--- a/misc/testing/PostProc/getImdb.php
+++ b/misc/testing/PostProc/getImdb.php
@@ -3,12 +3,13 @@
// This script will update all records in the movieinfo table
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
+use App\Services\MovieService;
use Blacklight\ColorCLI;
-use Blacklight\Movie;
use Illuminate\Support\Facades\DB;
$pdo = DB::connection()->getPdo();
-$movie = new Movie(['Echo' => true]);
+$movie = new MovieService();
+$movie->echooutput = true;
$colorCli = new ColorCLI;
$movies = $pdo->query('SELECT imdbid FROM movieinfo WHERE tmdbid = 0 ORDER BY id ASC');
diff --git a/misc/testing/PostProc/getMovieCovers.php b/misc/testing/PostProc/getMovieCovers.php
index 85d331b46..e31418e82 100644
--- a/misc/testing/PostProc/getMovieCovers.php
+++ b/misc/testing/PostProc/getMovieCovers.php
@@ -4,10 +4,11 @@
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\MovieInfo;
+use App\Services\MovieService;
use Blacklight\ColorCLI;
-use Blacklight\Movie;
-$movie = new Movie(['Echo' => true]);
+$movie = new MovieService();
+$movie->echooutput = true;
$colorCli = new ColorCLI;
$movies = MovieInfo::query()->where('cover', '=', 0)->orderBy('year')->orderByDesc('id')->get(['imdbid']);
diff --git a/misc/testing/PostProc/getTraktData.php b/misc/testing/PostProc/getTraktData.php
index 03b95bd6a..a8856a993 100644
--- a/misc/testing/PostProc/getTraktData.php
+++ b/misc/testing/PostProc/getTraktData.php
@@ -4,11 +4,12 @@
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\MovieInfo;
+use App\Services\MovieService;
+use App\Services\TvProcessing\Providers\TraktProvider;
use Blacklight\ColorCLI;
-use Blacklight\Movie;
-use Blacklight\processing\tv\TraktTv;
-$movie = new Movie(['Echo' => true]);
+$movie = new MovieService();
+$movie->echooutput = true;
$colorCli = new ColorCLI;
$movies = MovieInfo::query()->where('imdbid', '<>', 0)->where('traktid', '=', 0)->get(['imdbid']);
@@ -16,7 +17,7 @@ $count = $movies->count();
if ($count > 0) {
$colorCli->primary('Updating '.number_format($count).' movies for TraktTV id.');
foreach ($movies as $mov) {
- $traktTv = new TraktTv(['Settings' => null]);
+ $traktTv = new TraktProvider();
$traktmovie = $traktTv->client->getMovieSummary('tt'.$mov['imdbid'], 'full');
if ($traktmovie !== false) {
$colorCli->info('Updating IMDb id: tt'.$mov['imdbid']);