Move tv processing into services

This commit is contained in:
DariusIII
2025-12-19 17:06:51 +01:00
parent ab1562577b
commit dc9c9e3bb9
17 changed files with 91 additions and 183 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ use App\Models\Settings;
use App\Services\FanartTvService;
use App\Services\ImdbScraper;
use App\Services\TmdbClient;
use Blacklight\processing\tv\TraktTv;
use App\Services\TvProcessing\Providers\TraktProvider;
use Blacklight\utility\Utility;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
@@ -90,7 +90,7 @@ class Movie
public string $service;
public TraktTv $traktTv;
public TraktProvider $traktTv;
public ?OMDbAPI $omdbApi;
@@ -111,7 +111,7 @@ class Movie
$this->colorCli = new ColorCLI;
$this->traktcheck = config('nntmux_api.trakttv_api_key');
if ($this->traktcheck !== null) {
$this->traktTv = new TraktTv(['Settings' => null]);
$this->traktTv = new TraktProvider();
}
$this->client = new Client;
$this->fanartapikey = config('nntmux_api.fanarttv_api_key');
@@ -4,7 +4,7 @@ namespace App\Services\TvProcessing\Pipes;
use App\Services\TvProcessing\TvProcessingPassable;
use App\Services\TvProcessing\TvProcessingResult;
use Blacklight\processing\tv\LocalDB;
use App\Services\TvProcessing\Providers\LocalDbProvider;
/**
* Pipe for local database lookups.
@@ -16,7 +16,7 @@ class LocalDbPipe extends AbstractTvProviderPipe
private const TYPE_TV = 0;
protected int $priority = 10;
private ?LocalDB $localDb = null;
private ?LocalDbProvider $localDb = null;
public function getName(): string
{
@@ -31,10 +31,10 @@ class LocalDbPipe extends AbstractTvProviderPipe
/**
* Get or create the LocalDB instance.
*/
private function getLocalDb(): LocalDB
private function getLocalDb(): LocalDbProvider
{
if ($this->localDb === null) {
$this->localDb = new LocalDB();
$this->localDb = new LocalDbProvider();
}
return $this->localDb;
}
@@ -4,7 +4,7 @@ namespace App\Services\TvProcessing\Pipes;
use App\Services\TvProcessing\TvProcessingPassable;
use App\Services\TvProcessing\TvProcessingResult;
use Blacklight\processing\tv\LocalDB;
use App\Services\TvProcessing\Providers\LocalDbProvider;
use Closure;
/**
@@ -14,7 +14,7 @@ use Closure;
class ParseInfoPipe extends AbstractTvProviderPipe
{
protected int $priority = 1;
private ?LocalDB $localDb = null;
private ?LocalDbProvider $localDb = null;
public function getName(): string
{
@@ -29,10 +29,10 @@ class ParseInfoPipe extends AbstractTvProviderPipe
/**
* Get or create the LocalDB instance for parsing.
*/
private function getLocalDb(): LocalDB
private function getLocalDb(): LocalDbProvider
{
if ($this->localDb === null) {
$this->localDb = new LocalDB();
$this->localDb = new LocalDbProvider();
}
return $this->localDb;
}
+4 -4
View File
@@ -4,7 +4,7 @@ namespace App\Services\TvProcessing\Pipes;
use App\Services\TvProcessing\TvProcessingPassable;
use App\Services\TvProcessing\TvProcessingResult;
use Blacklight\processing\tv\TMDB;
use App\Services\TvProcessing\Providers\TmdbProvider;
/**
* Pipe for TMDB API lookups.
@@ -16,7 +16,7 @@ class TmdbPipe extends AbstractTvProviderPipe
private const SOURCE_TMDB = 2;
protected int $priority = 40;
private ?TMDB $tmdb = null;
private ?TmdbProvider $tmdb = null;
public function getName(): string
{
@@ -31,10 +31,10 @@ class TmdbPipe extends AbstractTvProviderPipe
/**
* Get or create the TMDB instance.
*/
private function getTmdb(): TMDB
private function getTmdb(): TmdbProvider
{
if ($this->tmdb === null) {
$this->tmdb = new TMDB();
$this->tmdb = new TmdbProvider();
}
return $this->tmdb;
}
@@ -4,7 +4,7 @@ namespace App\Services\TvProcessing\Pipes;
use App\Services\TvProcessing\TvProcessingPassable;
use App\Services\TvProcessing\TvProcessingResult;
use Blacklight\processing\tv\TraktTv;
use App\Services\TvProcessing\Providers\TraktProvider;
/**
* Pipe for Trakt.tv API lookups.
@@ -16,7 +16,7 @@ class TraktPipe extends AbstractTvProviderPipe
private const SOURCE_TRAKT = 5;
protected int $priority = 50;
private ?TraktTv $trakt = null;
private ?TraktProvider $trakt = null;
public function getName(): string
{
@@ -31,10 +31,10 @@ class TraktPipe extends AbstractTvProviderPipe
/**
* Get or create the Trakt instance.
*/
private function getTrakt(): TraktTv
private function getTrakt(): TraktProvider
{
if ($this->trakt === null) {
$this->trakt = new TraktTv();
$this->trakt = new TraktProvider();
}
return $this->trakt;
}
@@ -4,7 +4,7 @@ namespace App\Services\TvProcessing\Pipes;
use App\Services\TvProcessing\TvProcessingPassable;
use App\Services\TvProcessing\TvProcessingResult;
use Blacklight\processing\tv\TVMaze;
use App\Services\TvProcessing\Providers\TvMazeProvider;
/**
* Pipe for TVMaze API lookups.
@@ -16,7 +16,7 @@ class TvMazePipe extends AbstractTvProviderPipe
private const SOURCE_TVMAZE = 4;
protected int $priority = 30;
private ?TVMaze $tvmaze = null;
private ?TvMazeProvider $tvmaze = null;
public function getName(): string
{
@@ -31,10 +31,10 @@ class TvMazePipe extends AbstractTvProviderPipe
/**
* Get or create the TVMaze instance.
*/
private function getTvMaze(): TVMaze
private function getTvMaze(): TvMazeProvider
{
if ($this->tvmaze === null) {
$this->tvmaze = new TVMaze();
$this->tvmaze = new TvMazeProvider();
}
return $this->tvmaze;
}
+4 -4
View File
@@ -5,7 +5,7 @@ namespace App\Services\TvProcessing\Pipes;
use App\Services\FanartTvService;
use App\Services\TvProcessing\TvProcessingPassable;
use App\Services\TvProcessing\TvProcessingResult;
use Blacklight\processing\tv\TVDB;
use App\Services\TvProcessing\Providers\TvdbProvider;
/**
* Pipe for TVDB API lookups.
@@ -16,7 +16,7 @@ class TvdbPipe extends AbstractTvProviderPipe
private const TYPE_TV = 0;
protected int $priority = 20;
private ?TVDB $tvdb = null;
private ?TvdbProvider $tvdb = null;
private ?FanartTvService $fanart = null;
public function getName(): string
@@ -32,10 +32,10 @@ class TvdbPipe extends AbstractTvProviderPipe
/**
* Get or create the TVDB instance.
*/
private function getTvdb(): TVDB
private function getTvdb(): TvdbProvider
{
if ($this->tvdb === null) {
$this->tvdb = new TVDB();
$this->tvdb = new TvdbProvider();
}
return $this->tvdb;
}
@@ -1,6 +1,6 @@
<?php
namespace Blacklight\processing\tv;
namespace App\Services\TvProcessing\Providers;
use App\Models\Category;
use App\Models\Release;
@@ -9,16 +9,15 @@ use App\Models\TvEpisode;
use App\Models\TvInfo;
use App\Models\Video;
use Blacklight\ColorCLI;
use Blacklight\processing\Videos;
use Blacklight\Releases;
use Blacklight\utility\Country;
use Illuminate\Support\Facades\DB;
/**
* Class TV -- abstract extension of Videos
* Class AbstractTvProvider -- abstract extension of BaseVideoProvider
* Contains functions suitable for re-use in all TV scrapers.
*/
abstract class TV extends Videos
abstract class AbstractTvProvider extends BaseVideoProvider
{
// Television Sources
protected const SOURCE_NONE = 0; // No Scrape source
@@ -71,8 +70,7 @@ abstract class TV extends Videos
protected ColorCLI $colorCli;
/**
* TV constructor.
*
* AbstractTvProvider constructor.
*
* @throws \Exception
*/
@@ -397,17 +395,12 @@ abstract class TV extends Videos
$showInfo += $this->parseSeasonEp($relname);
// --- Post-parse correction for daily talk shows misclassified as Season = Year ---
// Some date-based releases like Show.2025.11.03.* wrongly match the 2009.E01 pattern and set season=2025, episode=11.
// If we detect a full YYYY.MM.DD (or YYYY-MM-DD / YYYY/MM/DD) date in the original release name and season is a 4-digit year
// we convert it to an airdate form (season=0, episode=0, airdate=YYYY-MM-DD) to allow later API season/episode resolution.
if (isset($showInfo['season'], $showInfo['episode']) && ! isset($showInfo['airdate'])) {
if (is_numeric($showInfo['season']) && (int) $showInfo['season'] >= 1900 && (int) $showInfo['season'] <= (int) date('Y') + 1) {
// Look for full date pattern.
if (preg_match('/(?P<year>(19|20)\d{2})[.\-\/](?P<month>\d{2})[.\-\/](?P<day>\d{2})/i', $relname, $dateHits)) {
$year = (int) $dateHits['year'];
$month = (int) $dateHits['month'];
$day = (int) $dateHits['day'];
// Basic sanity checks for month/day ranges.
if ($month >= 1 && $month <= 12 && $day >= 1 && $day <= 31) {
$showInfo['airdate'] = sprintf('%04d-%02d-%02d', $year, $month, $day);
$showInfo['season'] = 0;
@@ -416,15 +409,12 @@ abstract class TV extends Videos
}
}
}
// --- End correction ---
if (isset($showInfo['season'], $showInfo['episode'])) {
if (! isset($showInfo['airdate'])) {
// If year is present in the release name, add it to the cleaned name for title search
if (preg_match('/[^a-z0-9](?P<year>(19|20)(\d{2}))[^a-z0-9]/i', $relname, $yearMatch)) {
$showInfo['cleanname'] .= ' ('.$yearMatch['year'].')';
}
// Check for multi episode release.
if (\is_array($showInfo['episode'])) {
$showInfo['episode'] = $showInfo['episode'][0];
}
@@ -447,23 +437,15 @@ abstract class TV extends Videos
$following = '[^a-z0-9]([(|\[]\w+[)|\]]\s)*?(\d\d-\d\d|\d{1,3}x\d{2,3}|\(?(19|20)\d{2}\)?|(480|720|1080|2160)[ip]|AAC2?|BD-?Rip|Blu-?Ray|D0?\d|DD5|DiVX|DLMux|DTS|DVD(-?Rip)?|E\d{2,3}|[HX][\-_. ]?26[45]|ITA(-ENG)?|HEVC|[HPS]DTV|PROPER|REPACK|Season|Episode|S\d+[^a-z0-9]?((E\d+)[abr]?)*|WEB[\-_. ]?(DL|Rip)|XViD)[^a-z0-9]?';
// For names that don't start with the title.
if (preg_match('/^([^a-z0-9]{2,}|(sample|proof|repost)-)(?P<name>[\w .-]*?)'.$following.'/i', $relname, $hits)) {
$showName = $hits['name'];
} elseif (preg_match('/^(?P<name>[\w+][\s\w\'._-]*?)'.$following.'/i', $relname, $hits)) {
// For names that start with the title.
$showName = $hits['name'];
}
// If we still have any of the words in $following, remove them.
$showName = preg_replace('/'.$following.'/i', ' ', $showName);
// Remove leading date if present
$showName = preg_replace('/^\d{6}/', '', $showName);
// Handle acronyms with dots (e.g., G.R.I.T.S, S.H.I.E.L.D, C.S.I) before removing dots.
// This converts "G.R.I.T.S" to "GRITS" instead of "G R I T S".
$showName = $this->convertAcronyms($showName);
// Remove periods, underscored, anything between parenthesis.
$showName = preg_replace('/\(.*?\)|[._]/i', ' ', $showName);
// Finally remove multiple spaces and trim leading spaces.
$showName = trim(preg_replace('/\s{2,}/', ' ', $showName));
return $showName;
@@ -471,17 +453,12 @@ abstract class TV extends Videos
/**
* Convert acronyms with dots to condensed form.
* E.g., "G.R.I.T.S" becomes "GRITS", "S.H.I.E.L.D" becomes "SHIELD".
* This prevents acronyms from being split into individual letters with spaces.
*/
private function convertAcronyms(string $str): string
{
// Match acronyms: 2 or more single letters separated by dots (optionally ending with a dot)
// Pattern matches things like: G.R.I.T.S, S.H.I.E.L.D., C.S.I, N.C.I.S
return preg_replace_callback(
'/\b((?:[A-Za-z]\.){2,}[A-Za-z]?\.?)\b/',
function ($matches) {
// Remove all dots from the acronym
return str_replace('.', '', $matches[1]);
},
$str
@@ -495,10 +472,8 @@ abstract class TV extends Videos
{
$normalized = strtolower(trim($cleanName));
$aliases = [
// Acronym-based show titles
'grits' => 'Girls Raised in the South',
'shield' => 'Agents of S.H.I.E.L.D.',
// Talk shows
'stephen colbert' => 'The Late Show with Stephen Colbert',
'late show with stephen colbert' => 'The Late Show with Stephen Colbert',
'late show stephen colbert' => 'The Late Show with Stephen Colbert',
@@ -545,7 +520,7 @@ abstract class TV extends Videos
$episodeArr['season'] = (int) $hits[2];
$episodeArr['episode'] = [(int) $hits[3], (int) $hits[4]];
}
// S01E0102 and S01E01E02 - lame no delimit numbering, regex would collide if there was ever 1000 ep season.
// S01E0102 and S01E01E02
elseif (preg_match('/^(.*?)[^a-z0-9]s(\d{2})[^a-z0-9]?e(\d{2})e?(\d{2})[^a-z0-9]?/i', $relname, $hits)) {
$episodeArr['season'] = (int) $hits[2];
$episodeArr['episode'] = (int) $hits[3];
@@ -574,22 +549,21 @@ abstract class TV extends Videos
elseif (preg_match('/^(.*?)[^a-z0-9](?P<airdate>(19|20)(\d{2})[.\/-](\d{2})[.\/-](\d{2}))[^a-z0-9]?/i', $relname, $hits)) {
$episodeArr['season'] = 0;
$episodeArr['episode'] = 0;
$episodeArr['airdate'] = date('Y-m-d', strtotime(preg_replace('/[^0-9]/i', '/', $hits['airdate']))); // yyyy-mm-dd
$episodeArr['airdate'] = date('Y-m-d', strtotime(preg_replace('/[^0-9]/i', '/', $hits['airdate'])));
}
// 01.01.2009
elseif (preg_match('/^(.*?)[^a-z0-9](?P<airdate>(\d{2})[^a-z0-9](\d{2})[^a-z0-9](19|20)(\d{2}))[^a-z0-9]?/i', $relname, $hits)) {
$episodeArr['season'] = 0;
$episodeArr['episode'] = 0;
$episodeArr['airdate'] = date('Y-m-d', strtotime(preg_replace('/[^0-9]/i', '/', $hits['airdate']))); // yyyy-mm-dd
$episodeArr['airdate'] = date('Y-m-d', strtotime(preg_replace('/[^0-9]/i', '/', $hits['airdate'])));
}
// 01.01.09
elseif (preg_match('/^(.*?)[^a-z0-9](\d{2})[^a-z0-9](\d{2})[^a-z0-9](\d{2})[^a-z0-9]?/i', $relname, $hits)) {
// Add extra logic to capture the proper YYYY year
$year = ($hits[4] <= 99 && $hits[4] > 15) ? '19'.$hits[4] : '20'.$hits[4];
$airdate = $year.'/'.$hits[2].'/'.$hits[3];
$episodeArr['season'] = 0;
$episodeArr['episode'] = 0;
$episodeArr['airdate'] = date('Y-m-d', strtotime($airdate)); // yyyy-mm-dd
$episodeArr['airdate'] = date('Y-m-d', strtotime($airdate));
}
// 2009.E01
elseif (preg_match('/^(.*?)[^a-z0-9]20(\d{2})[^a-z0-9](\d{1,3})[^a-z0-9]?/i', $relname, $hits)) {
@@ -606,7 +580,6 @@ abstract class TV extends Videos
$episodeArr['season'] = 1;
$episodeArr['episode'] = (int) $hits[2];
}
// Band.Of.Brothers.EP06.Bastogne.DVDRiP.XviD-DEiTY
elseif (preg_match('/^(.*?)[^a-z0-9]EP?[^a-z0-9]?(\d{1,3})/i', $relname, $hits)) {
$episodeArr['season'] = 1;
@@ -626,7 +599,6 @@ abstract class TV extends Videos
*/
private function parseCountry(string $showName): string
{
// Country or origin matching.
if (preg_match('/[^a-z0-9](US|UK|AU|NZ|CA|NL|Canada|Australia|America|United[^a-z0-9]States|United[^a-z0-9]Kingdom)/i', $showName, $countryMatch)) {
$currentCountry = strtolower($countryMatch[1]);
if ($currentCountry === 'canada') {
@@ -650,7 +622,6 @@ abstract class TV extends Videos
/**
* Supplementary to parseInfo
* Cleans a derived local 'showname' for better matching probability
* Returns the cleaned string.
*/
public function cleanName(string $str): string
{
@@ -666,18 +637,17 @@ abstract class TV extends Videos
$str = str_replace('&', 'and', $str);
$str = preg_replace('/^(history|discovery) channel/i', '', $str);
$str = str_replace(['\'', ':', '!', '"', '#', '*', '', ',', '(', ')', '?'], '', $str);
$str = str_replace(["'", ':', '!', '"', '#', '*', "'", ',', '(', ')', '?'], '', $str);
$str = str_replace('$', 's', $str);
$str = preg_replace('/\s{2,}/', ' ', $str);
$str = trim($str, '\"');
$str = trim($str, '"');
return trim($str);
}
/**
* Simple function that compares two strings of text
* Returns percentage of similarity.
*/
public function checkMatch($ourName, $scrapeName, $probability): float|int
{
@@ -690,13 +660,8 @@ abstract class TV extends Videos
return 0;
}
//
/**
* Convert 2012-24-07 to 2012-07-24, there is probably a better way.
*
* This shouldn't ever happen as I've never heard of a date starting with year being followed by day value.
* Could this be a mistake? i.e. trying to solve the mm-dd-yyyy/dd-mm-yyyy confusion into a yyyy-mm-dd?
* Convert 2012-24-07 to 2012-07-24
*/
public function checkDate(bool|string|null $date): string
{
@@ -715,7 +680,6 @@ abstract class TV extends Videos
/**
* Checks API response returns have all REQUIRED attributes set
* Returns true or false.
*/
public function checkRequiredAttr($array, string $type): bool
{
@@ -735,7 +699,6 @@ abstract class TV extends Videos
$required = ['name', 'season', 'number', 'airdate', 'summary'];
break;
case 'tmdbS':
// TMDB search results use 'name' for the primary title; prefer it over 'original_name'
$required = ['id', 'name', 'overview', 'first_air_date', 'origin_country'];
break;
case 'tmdbE':
@@ -745,7 +708,6 @@ abstract class TV extends Videos
$required = ['title', 'ids', 'overview', 'first_aired', 'airs', 'country'];
break;
case 'traktE':
// Trakt uses 'number' for the episode number in episode summaries
$required = ['title', 'season', 'number', 'overview', 'first_aired'];
break;
}
@@ -762,4 +724,17 @@ abstract class TV extends Videos
return true;
}
/**
* Truncates title for display.
*/
protected function truncateTitle(string $title, int $maxLength = 45): string
{
if (mb_strlen($title) <= $maxLength) {
return $title;
}
return mb_substr($title, 0, $maxLength - 3).'...';
}
}
@@ -21,7 +21,7 @@
* @copyright 2015 nZEDb
*/
namespace Blacklight\processing;
namespace App\Services\TvProcessing\Providers;
use App\Models\TvInfo;
use App\Models\Video;
@@ -31,7 +31,7 @@ use Illuminate\Support\Facades\Cache;
/**
* Parent class for TV/Film and any similar classes to inherit from.
*/
abstract class Videos
abstract class BaseVideoProvider
{
// Video Type Identifiers
protected const TYPE_TV = 0; // Type of video is a TV Programme/Show
@@ -333,3 +333,4 @@ abstract class Videos
return $return->isEmpty() ? false : $return;
}
}
@@ -1,14 +1,14 @@
<?php
namespace Blacklight\processing\tv;
namespace App\Services\TvProcessing\Providers;
use Blacklight\ReleaseImage;
/**
* Class LocalDB -- performs local database lookups before hitting external APIs.
* Class LocalDbProvider -- performs local database lookups before hitting external APIs.
* This reduces unnecessary API calls by matching releases against already scraped data.
*/
class LocalDB extends TV
class LocalDbProvider extends AbstractTvProvider
{
/**
* Main processing director function for local database lookups.
@@ -1,11 +1,11 @@
<?php
namespace Blacklight\processing\tv;
namespace App\Services\TvProcessing\Providers;
use App\Services\TmdbClient;
use Blacklight\ReleaseImage;
class TMDB extends TV
class TmdbProvider extends AbstractTvProvider
{
protected const MATCH_PROBABILITY = 75;
@@ -236,7 +236,7 @@ class TMDB extends TV
/**
* Truncate title for display purposes.
*/
private function truncateTitle(string $title, int $maxLength = 45): string
protected function truncateTitle(string $title, int $maxLength = 45): string
{
if (mb_strlen($title) <= $maxLength) {
return $title;
@@ -1,17 +1,17 @@
<?php
namespace Blacklight\processing\tv;
namespace App\Services\TvProcessing\Providers;
use App\Services\TraktService;
use Blacklight\ReleaseImage;
use Illuminate\Support\Carbon;
/**
* Class TraktTv.
* Class TraktProvider.
*
* Process information retrieved from the Trakt API.
*/
class TraktTv extends TV
class TraktProvider extends AbstractTvProvider
{
private const MATCH_PROBABILITY = 75;
@@ -219,7 +219,7 @@ class TraktTv extends TV
/**
* Truncate title for display purposes.
*/
private function truncateTitle(string $title, int $maxLength = 45): string
protected function truncateTitle(string $title, int $maxLength = 45): string
{
if (mb_strlen($title) <= $maxLength) {
return $title;
@@ -1,16 +1,16 @@
<?php
namespace Blacklight\processing\tv;
namespace App\Services\TvProcessing\Providers;
use Blacklight\ReleaseImage;
use DariusIII\TVMaze\TVMaze as Client;
/**
* Class TVMaze.
* Class TvMazeProvider.
*
* Process information retrieved from the TVMaze API.
*/
class TVMaze extends TV
class TvMazeProvider extends AbstractTvProvider
{
private const MATCH_PROBABILITY = 75;
@@ -237,7 +237,7 @@ class TVMaze extends TV
/**
* Truncate title for display purposes.
*/
private function truncateTitle(string $title, int $maxLength = 45): string
protected function truncateTitle(string $title, int $maxLength = 45): string
{
if (mb_strlen($title) <= $maxLength) {
return $title;
@@ -464,3 +464,4 @@ class TVMaze extends TV
];
}
}
@@ -1,6 +1,6 @@
<?php
namespace Blacklight\processing\tv;
namespace App\Services\TvProcessing\Providers;
use App\Services\FanartTvService;
use Blacklight\ReleaseImage;
@@ -11,9 +11,9 @@ use CanIHaveSomeCoffee\TheTVDbAPI\TheTVDbAPI;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
/**
* Class TVDB -- functions used to post process releases against TVDB.
* Class TvdbProvider -- functions used to post process releases against TVDB.
*/
class TVDB extends TV
class TvdbProvider extends AbstractTvProvider
{
private const MATCH_PROBABILITY = 75;
@@ -39,8 +39,7 @@ class TVDB extends TV
private mixed $fanartapikey;
/**
* TVDB constructor.
*
* TvdbProvider constructor.
*
* @throws \Exception
*/
@@ -66,7 +65,6 @@ class TVDB extends TV
$tvCount = \count($res);
if ($tvCount === 0) {
return;
}
@@ -80,7 +78,6 @@ class TVDB extends TV
$siteId = false;
$this->posterUrl = '';
// Clean the show name for better match probability
$release = $this->parseInfo($row['searchname']);
if (\is_array($release) && $release['name'] !== '') {
if (\in_array($release['cleanname'], $this->titleCache, false)) {
@@ -92,25 +89,21 @@ class TVDB extends TV
}
$this->setVideoNotFound(parent::PROCESS_TVMAZE, $row['id']);
$skipped++;
continue;
}
// Find the Video ID if it already exists by checking the title.
$videoId = $this->getByTitle($release['cleanname'], parent::TYPE_TV);
if ($videoId !== 0) {
$siteId = $this->getSiteByID('tvdb', $videoId);
}
// Force local lookup only
$lookupSetting = true;
if ($local === true || $this->local) {
$lookupSetting = false;
}
if ($siteId === false && $lookupSetting) {
// If it doesn't exist locally and lookups are allowed lets try to get it.
if ($this->echooutput) {
$this->colorCli->primaryOver(' → ');
$this->colorCli->headerOver($this->truncateTitle($release['cleanname']));
@@ -118,14 +111,12 @@ class TVDB extends TV
$this->colorCli->info('Searching TVDB...');
}
// Check if we have a valid country and set it in the array
$country = (
isset($release['country']) && \strlen($release['country']) === 2
? (string) $release['country']
: ''
);
// Get the show from TVDB
$tvdbShow = $this->getShowInfo((string) $release['cleanname']);
if (\is_array($tvdbShow)) {
@@ -141,9 +132,9 @@ class TVDB extends TV
}
if ((int) $videoId > 0 && (int) $siteId > 0) {
if (! empty($tvdbShow['poster'])) { // Use TVDB poster if available
if (! empty($tvdbShow['poster'])) {
$this->getPoster($videoId);
} elseif ($this->fanart->isConfigured()) { // Check Fanart.tv for poster
} elseif ($this->fanart->isConfigured()) {
$posterUrl = $this->fanart->getBestTvPoster($siteId);
if (! empty($posterUrl)) {
$this->posterUrl = $posterUrl;
@@ -156,7 +147,6 @@ class TVDB extends TV
$hasAirdate = ! empty($release['airdate']);
if ($episodeNo === 'all') {
// Set the video ID and leave episode 0
$this->setVideoIdFound($videoId, $row['id'], 0);
if ($this->echooutput) {
$this->colorCli->primaryOver(' → ');
@@ -165,42 +155,30 @@ class TVDB extends TV
$this->colorCli->primary('Full Season matched');
}
$matched++;
continue;
}
// Download all episodes if new show to reduce API/bandwidth usage
if (! $this->countEpsByVideoID($videoId)) {
$this->getEpisodeInfo($siteId, -1, -1, $videoId);
}
// Check if we have the episode for this video ID
$episode = $this->getBySeasonEp($videoId, $seriesNo, $episodeNo, $release['airdate']);
if ($episode === false && $lookupSetting) {
if ($seriesNo !== '' && $episodeNo !== '') {
// Send the request for the episode to TVDB using season/episode numbers
$tvdbEpisode = $this->getEpisodeInfo(
$siteId,
(int) $seriesNo,
(int) $episodeNo,
$videoId
);
$tvdbEpisode = $this->getEpisodeInfo($siteId, (int) $seriesNo, (int) $episodeNo, $videoId);
if ($tvdbEpisode) {
$episode = $this->addEpisode($videoId, $tvdbEpisode);
}
}
if ($episode === false && $hasAirdate) {
// Refresh episode cache and attempt airdate match
$this->getEpisodeInfo($siteId, -1, -1, $videoId);
$episode = $this->getBySeasonEp($videoId, 0, 0, $release['airdate']);
}
}
if ($episode !== false && is_numeric($episode) && $episode > 0) {
// Mark the releases video and episode IDs
$this->setVideoIdFound($videoId, $row['id'], $episode);
if ($this->echooutput) {
$this->colorCli->primaryOver(' → ');
@@ -219,7 +197,6 @@ class TVDB extends TV
}
$matched++;
} else {
// Processing failed, set the episode ID to the next processing group
$this->setVideoIdFound($videoId, $row['id'], 0);
$this->setVideoNotFound(parent::PROCESS_TVMAZE, $row['id']);
if ($this->echooutput) {
@@ -234,7 +211,6 @@ class TVDB extends TV
}
}
} else {
// Processing failed, set the episode ID to the next processing group
$this->setVideoNotFound(parent::PROCESS_TVMAZE, $row['id']);
$this->titleCache[] = $release['cleanname'] ?? null;
if ($this->echooutput) {
@@ -245,7 +221,6 @@ class TVDB extends TV
}
}
} else {
// Parsing failed, take it out of the queue for examination
$this->setVideoNotFound(parent::FAILED_PARSE, $row['id']);
$this->titleCache[] = $release['cleanname'] ?? null;
if ($this->echooutput) {
@@ -259,7 +234,6 @@ class TVDB extends TV
}
}
// Display summary
if ($this->echooutput && $matched > 0) {
echo "\n";
$this->colorCli->primaryOver(' ✓ TVDB: ');
@@ -267,31 +241,12 @@ class TVDB extends TV
}
}
/**
* Truncate title for display purposes.
*/
private function truncateTitle(string $title, int $maxLength = 45): string
{
if (mb_strlen($title) <= $maxLength) {
return $title;
}
return mb_substr($title, 0, $maxLength - 3).'...';
}
/**
* Placeholder for Videos getBanner.
*/
public function getBanner($videoID, $siteId): bool
{
return false;
}
/**
* Calls the API to perform initial show name match to TVDB title
* Returns a formatted array of show data or false if no match.
*
*
* @throws UnauthorizedException
* @throws ParseException
* @throws ExceptionInterface
@@ -318,22 +273,18 @@ class TVDB extends TV
if (\is_array($response)) {
foreach ($response as $show) {
if ($this->checkRequiredAttr($show, 'tvdbS')) {
// Check for exact title match first and then terminate if found
if (strtolower($show->name) === strtolower($name)) {
$highest = $show;
break;
}
// Check each show title for similarity and then find the highest similar value
$matchPercent = $this->checkMatch(strtolower($show->name), strtolower($name), self::MATCH_PROBABILITY);
// If new match has a higher percentage, set as new matched title
if ($matchPercent > $highestMatch) {
$highestMatch = $matchPercent;
$highest = $show;
}
// Check for show aliases and try match those too
if (! empty($show->aliases)) {
foreach ($show->aliases as $akaIndex => $akaName) {
$aliasPercent = $this->checkMatch(strtolower($akaName), strtolower($name), self::MATCH_PROBABILITY);
@@ -353,21 +304,13 @@ class TVDB extends TV
return $return;
}
/**
* Retrieves the poster art for the processed show.
*
* @param int $videoId -- the local Video ID
*/
public function getPoster(int $videoId): int
{
$ri = new ReleaseImage;
$hasCover = 0;
// Try to get the Poster only if we have a non-empty URL
if (! empty($this->posterUrl)) {
$hasCover = $ri->saveImage($videoId, $this->posterUrl, $this->imgSavePath);
// Mark it retrieved if we saved an image
if ($hasCover === 1) {
$this->setCoverFound($videoId);
}
@@ -392,7 +335,6 @@ class TVDB extends TV
} catch (ResourceNotFoundException $error) {
return false;
} catch (UnauthorizedException $error) {
try {
$this->authorizeTvdb();
} catch (UnauthorizedException $error) {
@@ -430,9 +372,6 @@ class TVDB extends TV
}
/**
* Assigns API show response values to a formatted array for insertion
* Returns the formatted array.
*
* @throws ExceptionInterface
* @throws ParseException
*/
@@ -440,13 +379,11 @@ class TVDB extends TV
{
try {
$poster = $this->client->series()->artworks($show->tvdb_id);
// Grab the image with the highest score where type == 2
$poster = collect($poster)->where('type', 2)->sortByDesc('score')->first();
$this->posterUrl = ! empty($poster->image) ? $poster->image : '';
} catch (ResourceNotFoundException $e) {
$this->colorCli->error('Poster image not found on TVDB');
} catch (UnauthorizedException $error) {
try {
$this->authorizeTvdb();
} catch (UnauthorizedException $error) {
@@ -482,10 +419,6 @@ class TVDB extends TV
];
}
/**
* Assigns API episode response values to a formatted array for insertion
* Returns the formatted array.
*/
public function formatEpisodeInfo($episode): array
{
return [
@@ -500,10 +433,7 @@ class TVDB extends TV
protected function authorizeTvdb(): void
{
// Check if we can get the time for API status
// If we cant then we set local to true
$this->token = '';
// Check if we have the tvdb api key and user pin
if (config('tvdb.api_key') === null || config('tvdb.user_pin') === null) {
$this->colorCli->warning('TVDB API key or user pin not set. Running in local mode only!', true);
$this->local = true;
@@ -521,3 +451,4 @@ class TVDB extends TV
}
}
}
+10 -10
View File
@@ -4,12 +4,12 @@ namespace App\Services;
use App\Models\Release;
use App\Models\Settings;
use App\Services\TvProcessing\Providers\LocalDbProvider;
use App\Services\TvProcessing\Providers\TmdbProvider;
use App\Services\TvProcessing\Providers\TraktProvider;
use App\Services\TvProcessing\Providers\TvdbProvider;
use App\Services\TvProcessing\Providers\TvMazeProvider;
use Blacklight\ColorCLI;
use Blacklight\processing\tv\LocalDB;
use Blacklight\processing\tv\TMDB;
use Blacklight\processing\tv\TraktTv;
use Blacklight\processing\tv\TVDB;
use Blacklight\processing\tv\TVMaze;
class TvProcessor
{
@@ -179,11 +179,11 @@ class TvProcessor
private function buildProviderPipeline(): array
{
return [
['name' => 'Local DB', 'factory' => static fn () => new LocalDB, 'status' => 0],
['name' => 'TVDB', 'factory' => static fn () => new TVDB, 'status' => 0],
['name' => 'TVMaze', 'factory' => static fn () => new TVMaze, 'status' => -1],
['name' => 'TMDB', 'factory' => static fn () => new TMDB, 'status' => -2],
['name' => 'Trakt', 'factory' => static fn () => new TraktTv, 'status' => -3],
['name' => 'Local DB', 'factory' => static fn () => new LocalDbProvider, 'status' => 0],
['name' => 'TVDB', 'factory' => static fn () => new TvdbProvider, 'status' => 0],
['name' => 'TVMaze', 'factory' => static fn () => new TvMazeProvider, 'status' => -1],
['name' => 'TMDB', 'factory' => static fn () => new TmdbProvider, 'status' => -2],
['name' => 'Trakt', 'factory' => static fn () => new TraktProvider, 'status' => -3],
];
}
+2 -2
View File
@@ -2,10 +2,10 @@
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use Blacklight\processing\tv\TVDB;
use App\Services\TvProcessing\Providers\TvdbProvider;
$c = new Blacklight\ColorCLI;
$tvDB = new TVDB;
$tvDB = new TvdbProvider;
if (! empty($argv[1]) && isset($argv[2], $argv[3]) && is_numeric($argv[2]) && is_numeric($argv[3])) {
// Test if your TvDB API key and configuration are working
+2 -2
View File
@@ -2,10 +2,10 @@
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use Blacklight\processing\tv\TVMaze;
use App\Services\TvProcessing\Providers\TvMazeProvider;
$c = new Blacklight\ColorCLI;
$tvmaze = new TVMaze;
$tvmaze = new TvMazeProvider;
if (! empty($argv[1]) && is_numeric($argv[2]) && is_numeric($argv[3])) {
// Test if your TVMaze API configuration is working