diff --git a/Blacklight/Books.php b/Blacklight/Books.php index 4bcde305b..81751cfd3 100755 --- a/Blacklight/Books.php +++ b/Blacklight/Books.php @@ -309,7 +309,7 @@ class Books if ($bookInfo !== false) { if ($this->echooutput) { - $this->colorCli->climate()->info('Looking up: '.$bookInfo); + $this->colorCli->info('Looking up: '.$bookInfo); } // Do a local lookup first @@ -318,7 +318,7 @@ class Books if ($bookCheck === null && \in_array($bookInfo, $this->failCache, false)) { // Lookup recently failed, no point trying again if ($this->echooutput) { - $this->colorCli->climate()->info('Cached previous failure. Skipping.'); + $this->colorCli->info('Cached previous failure. Skipping.'); } $bookId = -2; } elseif ($bookCheck === null) { diff --git a/Blacklight/ColorCLI.php b/Blacklight/ColorCLI.php index 939a629bc..7be6ab264 100755 --- a/Blacklight/ColorCLI.php +++ b/Blacklight/ColorCLI.php @@ -2,122 +2,151 @@ namespace Blacklight; -use League\CLImate\CLImate; +use function Termwind\render; /** * Class ColorCLI. */ class ColorCLI { - protected CLImate $climate; - - /** - * ColorCLI constructor. - */ - public function __construct() - { - $this->climate = new CLImate; - } - public function debug(string $str, bool $newline = false): void { if ($newline) { - $this->climate->br(); + render('
'); } - $this->climate->lightGray()->out($str); + render("
{$str}
"); } public function info(string $str, bool $newline = false): void { if ($newline) { - $this->climate->br(); + render('
'); } - $this->climate->magenta()->out($str); + render("
{$str}
"); } public function notice(string $str, bool $newline = false): void { if ($newline) { - $this->climate->br(); + render('
'); } - $this->climate->blue()->out($str); + render("
{$str}
"); } public function warning(string $str, bool $newline = false): void { if ($newline) { - $this->climate->br(); + render('
'); } - $this->climate->yellow()->out($str); + render("
{$str}
"); } public function error(string $str, bool $newline = false): void { if ($newline) { - $this->climate->br(); + render('
'); } - $this->climate->red()->out($str); + render("
{$str}
"); } public function primary(string $str, bool $newline = false): void { if ($newline) { - $this->climate->br(); + render('
'); } - $this->climate->green()->out($str); + render("
{$str}
"); } public function header(string $str, bool $newline = false): void { if ($newline) { - $this->climate->br(); + render('
'); } - $this->climate->yellow()->out($str); + render("
{$str}
"); } public function alternate(string $str, bool $newline = false): void { if ($newline) { - $this->climate->br(); + render('
'); } - $this->climate->magenta()->bold()->out($str); + render("
{$str}
"); } public function tmuxOrange(string $str, bool $newline = false): void { if ($newline) { - $this->climate->br(); + render('
'); } - $this->climate->yellow()->bold()->out($str); + render("
{$str}
"); } public function primaryOver(string $str): void { - $this->climate->green()->inline($str); + echo "\033[32m{$str}\033[0m"; } public function headerOver(string $str): void { - $this->climate->yellow()->inline($str); + echo "\033[33m{$str}\033[0m"; } public function alternateOver(string $str): void { - $this->climate->magenta()->bold()->inline($str); + echo "\033[1;35m{$str}\033[0m"; } public function warningOver(string $str): void { - $this->climate->red()->inline($str); + echo "\033[31m{$str}\033[0m"; } - public function progress(): mixed + public function progress(): object { - return $this->climate->progress(); - } + return new class + { + private int $total = 0; - public function climate(): CLImate - { - return $this->climate; + private int $current = 0; + + private string $label = ''; + + public function total(int $total): self + { + $this->total = $total; + + return $this; + } + + public function current(int $current, ?string $label = null): void + { + $this->current = $current; + if ($label !== null) { + $this->label = $label; + } + $this->display(); + } + + public function advance(int $step = 1, ?string $label = null): void + { + $this->current += $step; + if ($label !== null) { + $this->label = $label; + } + $this->display(); + } + + private function display(): void + { + $percentage = $this->total > 0 ? (int) (($this->current / $this->total) * 100) : 0; + $bar = str_repeat('=', (int) ($percentage / 2)); + $spaces = str_repeat(' ', 50 - (int) ($percentage / 2)); + $label = $this->label ? " {$this->label}" : ''; + echo "\r[{$bar}{$spaces}] {$percentage}%{$label}"; + if ($this->current >= $this->total) { + echo "\n"; + } + } + }; } } diff --git a/Blacklight/Console.php b/Blacklight/Console.php index 1ff4fc91c..368343f45 100755 --- a/Blacklight/Console.php +++ b/Blacklight/Console.php @@ -745,7 +745,7 @@ class Console if ($gameInfo !== false) { if ($this->echooutput) { - $this->colorCli->climate()->info('Looking up: '.$gameInfo['title'].' ('.$gameInfo['platform'].')'); + $this->colorCli->info('Looking up: '.$gameInfo['title'].' ('.$gameInfo['platform'].')'); } // Check for existing console entry. @@ -754,7 +754,7 @@ class Console if ($gameCheck === false && \in_array($gameInfo['title'].$gameInfo['platform'], $this->failCache, false)) { // Lookup recently failed, no point trying again if ($this->echooutput) { - $this->colorCli->climate()->info('Cached previous failure. Skipping.'); + $this->colorCli->info('Cached previous failure. Skipping.'); } $gameId = -2; } elseif ($gameCheck === false) { diff --git a/Blacklight/Games.php b/Blacklight/Games.php index 56c3c0084..9d830fc10 100755 --- a/Blacklight/Games.php +++ b/Blacklight/Games.php @@ -581,7 +581,7 @@ class Games $gameInfo = $this->parseTitle($arr['searchname']); if ($gameInfo !== false) { if ($this->echoOutput) { - $this->colorCli->climate()->info('Looking up: '.$gameInfo['title'].' (PC)'); + $this->colorCli->info('Looking up: '.$gameInfo['title'].' (PC)'); } // Check for existing games entry. $gameCheck = $this->getGamesInfoByName($gameInfo['title']); diff --git a/Blacklight/Movie.php b/Blacklight/Movie.php index 79edbc4e3..0633efa59 100755 --- a/Blacklight/Movie.php +++ b/Blacklight/Movie.php @@ -661,7 +661,7 @@ class Movie $ret['title'] = $art['name']; } if ($this->echooutput) { - $this->colorCli->climate()->info('Fanart found '.$ret['title']); + $this->colorCli->info('Fanart found '.$ret['title']); } return $ret; @@ -794,7 +794,7 @@ class Movie // Log success if ($this->echooutput) { - $this->colorCli->climate()->info('TMDb found '.$ret['title']); + $this->colorCli->info('TMDb found '.$ret['title']); } // Cache the result @@ -878,7 +878,7 @@ class Movie // Log success if ($this->echooutput) { - $this->colorCli->climate()->info('IMDb found '.$title); + $this->colorCli->info('IMDb found '.$title); } // Cache the successful result @@ -969,7 +969,7 @@ class Movie // Log success if ($this->echooutput) { - $this->colorCli->climate()->info('Trakt found '.$movieData['title']); + $this->colorCli->info('Trakt found '.$movieData['title']); } // Cache the successful result @@ -1069,7 +1069,7 @@ class Movie // Log success if ($this->echooutput) { - $this->colorCli->climate()->info('OMDbAPI Found '.$movieData['title']); + $this->colorCli->info('OMDbAPI Found '.$movieData['title']); } // Cache the successful result @@ -1139,7 +1139,7 @@ class Movie // Log success if ($this->echooutput) { - $this->colorCli->climate()->info('iTunes found '.$movieData['title']); + $this->colorCli->info('iTunes found '.$movieData['title']); } // Cache the successful result @@ -1193,7 +1193,7 @@ class Movie try { $this->service = $service; if ($this->echooutput && $this->service !== '') { - $this->colorCli->climate()->info($this->service.' found IMDBid: tt'.$imdbId); + $this->colorCli->info($this->service.' found IMDBid: tt'.$imdbId); } // Get movie info ID @@ -1334,7 +1334,7 @@ class Movie // Log current lookup if output is enabled if ($this->echooutput) { - $this->colorCli->climate()->info('Looking up: '.$movieName); + $this->colorCli->info('Looking up: '.$movieName); } // Try all available sources to find IMDB ID @@ -1348,7 +1348,7 @@ class Movie if ($foundIMDB) { // Movie was successfully updated by one of the services if ($this->echooutput) { - $this->colorCli->climate()->success('Successfully updated release with IMDB ID'); + $this->colorCli->primary('Successfully updated release with IMDB ID'); } continue; @@ -1357,7 +1357,7 @@ class Movie $releaseCheck = Release::query()->where('id', $arr['id'])->whereNotNull('imdbid')->exists(); if ($releaseCheck) { if ($this->echooutput) { - $this->colorCli->climate()->info('Release already has IMDB ID, skipping'); + $this->colorCli->info('Release already has IMDB ID, skipping'); } continue; @@ -1379,7 +1379,7 @@ class Movie $this->colorCli->header('Failed to find IMDB IDs for '.count($failedIDs).' releases:'); foreach ($failedReleases as $release) { - $this->colorCli->climate()->error("ID: {$release->id} - {$release->searchname}"); + $this->colorCli->error("ID: {$release->id} - {$release->searchname}"); } } @@ -1621,7 +1621,7 @@ class Movie Cache::put($cacheKey, $match['imdbid'], now()->addDays(7)); if ($this->echooutput) { - $this->colorCli->climate()->info("Found local match: {$match['title']} ({$match['imdbid']})"); + $this->colorCli->info("Found local match: {$match['title']} ({$match['imdbid']})"); } return $match['imdbid']; diff --git a/Blacklight/Music.php b/Blacklight/Music.php index 2e566a6f1..35c024646 100755 --- a/Blacklight/Music.php +++ b/Blacklight/Music.php @@ -422,7 +422,7 @@ class Music $newname = $album['name'].' ('.$album['year'].')'; if ($this->echooutput) { - $this->colorCli->climate()->info('Looking up: '.$newname); + $this->colorCli->info('Looking up: '.$newname); } // Do a local lookup first diff --git a/Blacklight/NNTP.php b/Blacklight/NNTP.php index 0c13e6b82..69c9db407 100755 --- a/Blacklight/NNTP.php +++ b/Blacklight/NNTP.php @@ -223,7 +223,7 @@ class NNTP extends \Net_NNTP_Client ': '. $cError; - return $this->throwError($this->colorCli->climate()->error($message)); + return $this->throwError($this->colorCli->error($message)); } // If we are connected, try to authenticate. @@ -261,7 +261,7 @@ class NNTP extends \Net_NNTP_Client $userName. ' ('.$aError.')'; - return $this->throwError($this->colorCli->climate()->error($message)); + return $this->throwError($this->colorCli->error($message)); } } } @@ -285,7 +285,7 @@ class NNTP extends \Net_NNTP_Client // If we somehow got out of the loop, return an error. $message = 'Unable to connect to '.$this->_currentServer.$enc; - return $this->throwError($this->colorCli->climate()->error($message)); + return $this->throwError($this->colorCli->error($message)); } /** @@ -609,7 +609,7 @@ class NNTP extends \Net_NNTP_Client } else { $message = 'Wrong Identifier type, array, int or string accepted. This type of var was passed: '.gettype($identifiers); - return $this->throwError($this->colorCli->climate()->error($message)); + return $this->throwError($this->colorCli->error($message)); } if ($aConnected === true) { @@ -759,7 +759,7 @@ class NNTP extends \Net_NNTP_Client $message = "Code {$data->code}: {$data->message}\nSkipping group: {$group}"; if ($this->_echo) { - $this->colorCli->climate()->error($message); + $this->colorCli->error($message); } $nntp->doQuit(); } @@ -921,7 +921,7 @@ class NNTP extends \Net_NNTP_Client if (! empty($deComp)) { $bytesReceived = \strlen($data); if ($this->_echo && $bytesReceived > 10240) { - $this->colorCli->climate()->primaryOver( + $this->colorCli->primaryOver( 'Received '.round($bytesReceived / 1024). 'KB from group ('.$this->group().').' ); @@ -934,7 +934,7 @@ class NNTP extends \Net_NNTP_Client } $message = 'Decompression of OVER headers failed.'; - return $this->throwError($this->colorCli->climate()->error($message), 1000); + return $this->throwError($this->colorCli->error($message), 1000); } // The buffer was not empty, so we know this was not the real ending, so reset $possibleTerm. $possibleTerm = false; @@ -952,7 +952,7 @@ class NNTP extends \Net_NNTP_Client if (empty($buffer)) { $message = 'Error fetching data from usenet server while downloading OVER headers.'; - return $this->throwError($this->colorCli->climate()->error($message), 1000); + return $this->throwError($this->colorCli->error($message), 1000); } } @@ -968,7 +968,7 @@ class NNTP extends \Net_NNTP_Client $message = 'Unspecified error while downloading OVER headers.'; - return $this->throwError($this->colorCli->climate()->error($message), 1000); + return $this->throwError($this->colorCli->error($message), 1000); } /** diff --git a/Blacklight/NameFixer.php b/Blacklight/NameFixer.php index 9908f8634..e961db631 100755 --- a/Blacklight/NameFixer.php +++ b/Blacklight/NameFixer.php @@ -182,7 +182,7 @@ class NameFixer if ($total > 0) { $this->_totalReleases = $total; - $this->colorCLI->climate()->info(number_format($total).' releases to process.'); + $this->colorCLI->info(number_format($total).' releases to process.'); foreach ($releases as $rel) { $releaseRow = Release::fromQuery( @@ -212,7 +212,7 @@ class NameFixer } $this->_echoFoundCount($echo, ' NFO\'s'); } else { - $this->colorCLI->climate()->info('Nothing to fix.'); + $this->colorCLI->info('Nothing to fix.'); } } @@ -261,7 +261,7 @@ class NameFixer $total = $releases->count(); if ($total > 0) { $this->_totalReleases = $total; - $this->colorCLI->climate()->info(number_format($total).' file names to process.'); + $this->colorCLI->info(number_format($total).' file names to process.'); foreach ($releases as $release) { $this->reset(); @@ -272,7 +272,7 @@ class NameFixer $this->_echoFoundCount($echo, ' files'); } else { - $this->colorCLI->climate()->info('Nothing to fix.'); + $this->colorCLI->info('Nothing to fix.'); } } @@ -321,7 +321,7 @@ class NameFixer $total = $releases->count(); if ($total > 0) { $this->_totalReleases = $total; - $this->colorCLI->climate()->info(number_format($total).' CRC32\'s to process.'); + $this->colorCLI->info(number_format($total).' CRC32\'s to process.'); foreach ($releases as $release) { $this->reset(); @@ -332,7 +332,7 @@ class NameFixer $this->_echoFoundCount($echo, ' crc32\'s'); } else { - $this->colorCLI->climate()->info('Nothing to fix.'); + $this->colorCLI->info('Nothing to fix.'); } } @@ -379,7 +379,7 @@ class NameFixer $total = $releases->count(); if ($total > 0) { $this->_totalReleases = $total; - $this->colorCLI->climate()->info(number_format($total).' xxx file names to process.'); + $this->colorCLI->info(number_format($total).' xxx file names to process.'); foreach ($releases as $release) { $this->reset(); @@ -389,7 +389,7 @@ class NameFixer } $this->_echoFoundCount($echo, ' files'); } else { - $this->colorCLI->climate()->info('Nothing to fix.'); + $this->colorCLI->info('Nothing to fix.'); } } @@ -438,7 +438,7 @@ class NameFixer $total = $releases->count(); if ($total > 0) { $this->_totalReleases = $total; - $this->colorCLI->climate()->info(number_format($total).' srr file extensions to process.'); + $this->colorCLI->info(number_format($total).' srr file extensions to process.'); foreach ($releases as $release) { $this->reset(); @@ -448,7 +448,7 @@ class NameFixer } $this->_echoFoundCount($echo, ' files'); } else { - $this->colorCLI->climate()->info('Nothing to fix.'); + $this->colorCLI->info('Nothing to fix.'); } } @@ -494,7 +494,7 @@ class NameFixer if ($total > 0) { $this->_totalReleases = $total; - $this->colorCLI->climate()->info(number_format($total).' releases to process.'); + $this->colorCLI->info(number_format($total).' releases to process.'); $Nfo = new Nfo; $nzbContents = new NZBContents; @@ -508,7 +508,7 @@ class NameFixer } $this->_echoFoundCount($echo, ' files'); } else { - $this->colorCLI->climate()->info('Nothing to fix.'); + $this->colorCLI->info('Nothing to fix.'); } } @@ -560,7 +560,7 @@ class NameFixer $total = $releases->count(); if ($total > 0) { $this->_totalReleases = $total; - $this->colorCLI->climate()->info(number_format($total).' unique ids to process.'); + $this->colorCLI->info(number_format($total).' unique ids to process.'); foreach ($releases as $rel) { $this->checked++; $this->reset(); @@ -569,7 +569,7 @@ class NameFixer } $this->_echoFoundCount($echo, ' UID\'s'); } else { - $this->colorCLI->climate()->info('Nothing to fix.'); + $this->colorCLI->info('Nothing to fix.'); } } @@ -612,7 +612,7 @@ class NameFixer $total = $releases->count(); if ($total > 0) { $this->_totalReleases = $total; - $this->colorCLI->climate()->info(number_format($total).' mediainfo movie names to process.'); + $this->colorCLI->info(number_format($total).' mediainfo movie names to process.'); foreach ($releases as $rel) { $this->checked++; $this->reset(); @@ -621,7 +621,7 @@ class NameFixer } $this->_echoFoundCount($echo, ' MediaInfo\'s'); } else { - $this->colorCLI->climate()->info('Nothing to fix.'); + $this->colorCLI->info('Nothing to fix.'); } } @@ -678,7 +678,7 @@ class NameFixer $total = $releases->count(); if ($total > 0) { $this->_totalReleases = $total; - $this->colorCLI->climate()->info(number_format($total).' hash_16K to process.'); + $this->colorCLI->info(number_format($total).' hash_16K to process.'); foreach ($releases as $rel) { $this->checked++; $this->reset(); @@ -687,7 +687,7 @@ class NameFixer } $this->_echoFoundCount($echo, ' hashes'); } else { - $this->colorCLI->climate()->info('Nothing to fix.'); + $this->colorCLI->info('Nothing to fix.'); } } @@ -724,7 +724,7 @@ class NameFixer protected function _echoFoundCount(bool|int $echo, string $type): void { if ($echo === true) { - $this->colorCLI->climate()->info( + $this->colorCLI->info( PHP_EOL. number_format($this->fixed). ' releases have had their names changed out of: '. @@ -732,7 +732,7 @@ class NameFixer $type.'.' ); } else { - $this->colorCLI->climate()->info( + $this->colorCLI->info( PHP_EOL. number_format($this->fixed). ' releases could have their names changed. '. @@ -748,7 +748,7 @@ class NameFixer */ protected function _echoStartMessage(int $time, string $type): void { - $this->colorCLI->climate()->info( + $this->colorCLI->info( sprintf( 'Fixing search names %s using %s.', ($time === 1 ? 'in the past 6 hours' : 'since the beginning'), @@ -764,7 +764,7 @@ class NameFixer } if ($show === 2) { - $this->colorCLI->climate()->info( + $this->colorCLI->info( 'Renamed Releases: ['. number_format($this->fixed). '] '. @@ -1084,8 +1084,8 @@ class NameFixer $limit = 'LIMIT 1000000'; } - $this->colorCLI->climate()->info(PHP_EOL.'Match PreFiles '.$args[1].' Started at '.now()); - $this->colorCLI->climate()->info('Matching predb filename to cleaned release_files.name.'); + $this->colorCLI->info(PHP_EOL.'Match PreFiles '.$args[1].' Started at '.now()); + $this->colorCLI->info('Matching predb filename to cleaned release_files.name.'); $counter = $counted = 0; $timeStart = now(); @@ -1114,7 +1114,7 @@ class NameFixer $total = $query->count(); if ($total > 0) { - $this->colorCLI->climate()->info(PHP_EOL.number_format($total).' releases to process.'); + $this->colorCLI->info(PHP_EOL.number_format($total).' releases to process.'); foreach ($query as $row) { $success = $this->matchPreDbFiles($row, true, 1, $show); @@ -1122,12 +1122,12 @@ class NameFixer $counted++; } if ($show === 0) { - $this->colorCLI->climate()->info('Renamed Releases: ['.number_format($counted).'] '.(new ConsoleTools)->percentString(++$counter, $total)); + $this->colorCLI->info('Renamed Releases: ['.number_format($counted).'] '.(new ConsoleTools)->percentString(++$counter, $total)); } } - $this->colorCLI->climate()->info(PHP_EOL.'Renamed '.number_format($counted).' releases in '.now()->diffInSeconds($timeStart, true).' seconds'.'.'); + $this->colorCLI->info(PHP_EOL.'Renamed '.number_format($counted).' releases in '.now()->diffInSeconds($timeStart, true).' seconds'.'.'); } else { - $this->colorCLI->climate()->info('Nothing to do.'); + $this->colorCLI->info('Nothing to do.'); } } } diff --git a/Blacklight/ReleaseImage.php b/Blacklight/ReleaseImage.php index 6308cf8bc..ab11a34f3 100755 --- a/Blacklight/ReleaseImage.php +++ b/Blacklight/ReleaseImage.php @@ -72,7 +72,7 @@ class ReleaseImage $img = false; } catch (\Error $e) { - $this->colorCli->climate()->info($e->getMessage()); + $this->colorCli->info($e->getMessage()); $img = false; } diff --git a/Blacklight/XXX.php b/Blacklight/XXX.php index 68b788b44..e1274a58a 100755 --- a/Blacklight/XXX.php +++ b/Blacklight/XXX.php @@ -545,13 +545,13 @@ class XXX $check = $this->checkXXXInfoExists($this->currentTitle); if ($check === null) { if ($this->echoOutput) { - $this->colorCli->climate()->info('Looking up: '.$this->currentTitle); + $this->colorCli->info('Looking up: '.$this->currentTitle); } - $this->colorCli->climate()->info('Local match not found, checking web!'); + $this->colorCli->info('Local match not found, checking web!'); $idcheck = $this->updateXXXInfo($this->currentTitle); } else { - $this->colorCli->climate()->info('Local match found for XXX Movie: '.$this->currentTitle); + $this->colorCli->info('Local match found for XXX Movie: '.$this->currentTitle); $idcheck = (int) $check['id']; } } else { diff --git a/Blacklight/libraries/Runners/PostProcessRunner.php b/Blacklight/libraries/Runners/PostProcessRunner.php index 4aed7248a..3dd7b237e 100644 --- a/Blacklight/libraries/Runners/PostProcessRunner.php +++ b/Blacklight/libraries/Runners/PostProcessRunner.php @@ -184,7 +184,61 @@ class PostProcessRunner extends BaseRunner $queue = DB::select($sql); $maxProcesses = (int) Settings::settingValue('postthreadsnon'); - $this->runPostProcess($queue, $maxProcesses, 'tv', 'tv postprocessing'); + + // Use pipelined TV processing for better efficiency + $this->runPostProcessTvPipeline($queue, $maxProcesses, 'tv postprocessing (pipelined)', $renamedOnly); + } + + /** + * Run pipelined TV post-processing across multiple GUID buckets in parallel. + * Each parallel process runs the full provider pipeline sequentially. + */ + private function runPostProcessTvPipeline(array $releases, int $maxProcesses, string $desc, bool $renamedOnly): void + { + if (empty($releases)) { + $this->headerNone(); + + return; + } + + // If streaming is enabled, run commands with real-time output + if ((bool) config('nntmux.stream_fork_output', false) === true) { + $commands = []; + foreach ($releases as $release) { + $char = isset($release->id) ? substr((string) $release->id, 0, 1) : ''; + $renamed = isset($release->renamed) ? $release->renamed : ''; + // Use the pipelined TV command + $commands[] = PHP_BINARY.' artisan postprocess:tv-pipeline '.$char.($renamed ? ' '.$renamed : '').' --mode=pipeline'; + } + $this->runStreamingCommands($commands, $maxProcesses, $desc); + + return; + } + + $pool = $this->createPool($maxProcesses); + $count = count($releases); + $this->headerStart('postprocess: '.$desc, $count, $maxProcesses); + + foreach ($releases as $release) { + $char = isset($release->id) ? substr((string) $release->id, 0, 1) : ''; + $renamed = isset($release->renamed) ? $release->renamed : ''; + $pool->add(function () use ($char, $renamed) { + // Use the pipelined TV command for each GUID bucket + return $this->executeCommand(PHP_BINARY.' artisan postprocess:tv-pipeline '.$char.($renamed ? ' '.$renamed : '').' --mode=pipeline'); + }, self::ASYNC_BUFFER_SIZE)->then(function ($output) use (&$count, $desc) { + echo $output; + $this->colorCli->primary('Finished task #'.$count.' for '.$desc); + $count--; + })->catch(function (\Throwable $exception) { + echo $exception->getMessage(); + })->catch(static function (SerializableException $serializableException) { + // swallow + })->timeout(function () use ($desc, &$count) { + $this->colorCli->notice('Task #'.$count.' ('.$desc.'): Timeout occurred.'); + }); + } + + $pool->wait(); } /** diff --git a/Blacklight/processing/PostProcess.php b/Blacklight/processing/PostProcess.php index 2bd2ecd5f..642f48107 100755 --- a/Blacklight/processing/PostProcess.php +++ b/Blacklight/processing/PostProcess.php @@ -194,12 +194,28 @@ class PostProcess * @param string $guidChar (Optional) First letter of a release GUID to use to get work. * @param int|string|null $processTV (Optional) 0 Don't process, 1 process all releases, * 2 process renamed releases only, '' check site setting + * @param string $mode (Optional) Processing mode: 'pipeline' (default) or 'parallel' * * @throws \Exception */ - public function processTv(string $groupID = '', string $guidChar = '', int|string|null $processTV = ''): void + public function processTv(string $groupID = '', string $guidChar = '', int|string|null $processTV = '', string $mode = 'pipeline'): void { - $this->tvProcessor->process($groupID, $guidChar, $processTV); + // If no GUID character specified, use parallel-pipeline processing via Forking + if ($guidChar === '') { + $forking = new \Blacklight\libraries\Forking; + $options = []; + + // Convert processTV setting to renamed-only flag + $processTV = (is_numeric($processTV) ? $processTV : \App\Models\Settings::settingValue('lookuptv')); + if ($processTV == 2) { + $options = [0 => true]; // renamed only + } + + $forking->processWorkType('postProcess_tv', $options); + } else { + // Process single GUID bucket with pipeline + $this->tvProcessor->process($groupID, $guidChar, $processTV, $mode); + } } /** diff --git a/Blacklight/processing/post/AniDB.php b/Blacklight/processing/post/AniDB.php index c31e39f57..d0c4618ee 100755 --- a/Blacklight/processing/post/AniDB.php +++ b/Blacklight/processing/post/AniDB.php @@ -245,7 +245,7 @@ class AniDB // We ignore episode number 0 (Complete Series) for matching episodes but still link the title. if ($this->echooutput) { - $this->colorCli->climate()->info('Looking Up: Title: '.$title.' Episode: '.$epno); + $this->colorCli->info('Looking Up: Title: '.$title.' Episode: '.$epno); } $anidbTitle = $this->getAnidbByName($title); diff --git a/Blacklight/processing/tv/LocalDB.php b/Blacklight/processing/tv/LocalDB.php new file mode 100644 index 000000000..924843c72 --- /dev/null +++ b/Blacklight/processing/tv/LocalDB.php @@ -0,0 +1,146 @@ +getTvReleases($groupID, $guidChar, $process, parent::PROCESS_TVDB); + + $tvCount = \count($res); + $matchedCount = 0; + $notFoundCount = 0; + $episodeMissingCount = 0; + + if ($tvCount === 0) { + return; + } + + foreach ($res as $index => $release) { + $matched = false; + + // Parse release name to extract show info + $showInfo = $this->parseInfo($release->searchname); + + if ($showInfo === false) { + continue; + } + + // Try to find the show in our local database by title + $videoId = $this->getByTitle($showInfo['cleanname'], parent::TYPE_TV, 0); + + if ($videoId !== 0 && $videoId !== false) { + // Found a matching show in local DB + $episodeId = false; + + if (isset($showInfo['season'], $showInfo['episode']) && $showInfo['episode'] !== 'all') { + // Try to find the specific episode + $episodeId = $this->getBySeasonEp( + $videoId, + $showInfo['season'], + $showInfo['episode'], + $showInfo['airdate'] ?? '' + ); + } + + if ($episodeId !== false && $episodeId > 0) { + // Complete match - both show and episode found + $this->setVideoIdFound($videoId, $release->id, $episodeId); + $matched = true; + $matchedCount++; + + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($showInfo['cleanname']); + $this->colorCli->primaryOver(' S'); + $this->colorCli->warningOver(sprintf('%02d', $showInfo['season'] ?? 0)); + $this->colorCli->primaryOver('E'); + $this->colorCli->warningOver(sprintf('%02d', $showInfo['episode'] ?? 0)); + $this->colorCli->primaryOver(' ✓ '); + $this->colorCli->primary('MATCHED (Local DB)'); + } + } elseif ($videoId > 0 && isset($showInfo['season'], $showInfo['episode']) && $showInfo['episode'] !== 'all') { + // Show found but episode not in database yet + $episodeMissingCount++; + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($showInfo['cleanname']); + $this->colorCli->primaryOver(' S'); + $this->colorCli->warningOver(sprintf('%02d', $showInfo['season'] ?? 0)); + $this->colorCli->primaryOver('E'); + $this->colorCli->warningOver(sprintf('%02d', $showInfo['episode'] ?? 0)); + $this->colorCli->headerOver(' → '); + $this->colorCli->notice('Show found, episode missing'); + } + } + } + + if (! $matched && $videoId === false || $videoId === 0) { + $notFoundCount++; + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($showInfo['cleanname']); + if (isset($showInfo['season'], $showInfo['episode'])) { + $this->colorCli->primaryOver(' S'); + $this->colorCli->warningOver(sprintf('%02d', $showInfo['season'] ?? 0)); + $this->colorCli->primaryOver('E'); + $this->colorCli->warningOver(sprintf('%02d', $showInfo['episode'] ?? 0)); + } + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternate('Not in local DB'); + } + } + } + + if ($this->echooutput && $matchedCount > 0) { + echo "\n"; + $this->colorCli->primaryOver(' ✓ Local DB: '); + $this->colorCli->primary(sprintf('%d matched, %d missing episodes, %d not found', $matchedCount, $episodeMissingCount, $notFoundCount)); + } + } + + /** + * These abstract methods are required by parent TV class but not used for local lookups. + */ + protected function getBanner(int $videoID, int $siteId): mixed + { + return false; + } + + protected function getEpisodeInfo(int|string $siteId, int|string $series, int|string $episode): array|bool + { + return false; + } + + protected function getPoster(int $videoId): int + { + return (new ReleaseImage)->saveImage($videoId, '', $this->imgSavePath, '', '', parent::TYPE_TV); + } + + protected function getShowInfo(string $name): bool|array + { + return false; + } + + protected function formatShowInfo($show): array + { + return []; + } + + protected function formatEpisodeInfo($episode): array + { + return []; + } +} diff --git a/Blacklight/processing/tv/TMDB.php b/Blacklight/processing/tv/TMDB.php index ea287cdbe..a81b2a89a 100644 --- a/Blacklight/processing/tv/TMDB.php +++ b/Blacklight/processing/tv/TMDB.php @@ -48,14 +48,19 @@ class TMDB extends TV $tvcount = \count($res); $lookupSetting = true; - if ($this->echooutput && $tvcount > 0) { - $this->colorCli->header('Processing TMDB lookup for '.number_format($tvcount).' release(s).', true); + if ($tvcount === 0) { + + return; } if ($res instanceof \Traversable) { $this->titleCache = []; + $processed = 0; + $matched = 0; + $skipped = 0; foreach ($res as $row) { + $processed++; $siteId = false; $this->posterUrl = ''; @@ -65,11 +70,13 @@ class TMDB extends TV if (\is_array($release) && $release['name'] !== '') { if (\in_array($release['cleanname'], $this->titleCache, false)) { if ($this->echooutput) { - $this->colorCli->headerOver('Title: '). - $this->colorCli->warningOver($release['cleanname']). - $this->colorCli->header(' already failed lookup for this site. Skipping.', true); + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternate('Skipped (previously failed)'); } $this->setVideoNotFound(parent::PROCESS_TRAKT, $row['id']); + $skipped++; continue; } @@ -85,9 +92,10 @@ class TMDB extends TV // If lookups are allowed lets try to get it. if ($videoId === 0 && $lookupSetting) { if ($this->echooutput) { - $this->colorCli->primaryOver('Checking TMDB for previously failed title: '). - $this->colorCli->headerOver($release['cleanname']). - $this->colorCli->primary('.', true); + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->info('Searching TMDB...'); } // Get the show from TMDB @@ -107,9 +115,11 @@ class TMDB extends TV } } } else { - if ($this->echooutput) { - $this->colorCli->climate()->info('Found local TMDB match for: '.$release['cleanname']); - $this->colorCli->climate()->info('. Attempting episode lookup!'); + if ($this->echooutput && $videoId > 0) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->info('Found in DB'); } $siteId = $this->getSiteIDFromVideoID('tmdb', $videoId); } @@ -124,7 +134,13 @@ class TMDB extends TV if ($episodeNo === 'all') { // Set the video ID and leave episode 0 $this->setVideoIdFound($videoId, $row['id'], 0); - $this->colorCli->climate()->info('Found TMDB Match for Full Season!'); + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->primary('Full Season matched'); + } + $matched++; continue; } @@ -155,27 +171,72 @@ class TMDB extends TV // Mark the releases video and episode IDs $this->setVideoIdFound($videoId, $row['id'], $episode); if ($this->echooutput) { - $this->colorCli->climate()->info('Found TMDB Match!'); + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' S'); + $this->colorCli->warningOver(sprintf('%02d', $seriesNo)); + $this->colorCli->primaryOver('E'); + $this->colorCli->warningOver(sprintf('%02d', $episodeNo)); + $this->colorCli->primaryOver(' ✓ '); + $this->colorCli->primary('MATCHED (TMDB)'); } + $matched++; } else { // Processing failed, set the episode ID to the next processing group $this->setVideoIdFound($videoId, $row['id'], 0); $this->setVideoNotFound(parent::PROCESS_TRAKT, $row['id']); + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warning('Episode not found'); + } } } else { // Processing failed, set the episode ID to the next processing group $this->setVideoNotFound(parent::PROCESS_TRAKT, $row['id']); $this->titleCache[] = $release['cleanname'] ?? null; + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warning('Not found'); + } } } else { // Processing failed, set the episode ID to the next processing group $this->setVideoNotFound(parent::PROCESS_TRAKT, $row['id']); $this->titleCache[] = $release['cleanname'] ?? null; + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver(mb_substr($row['searchname'], 0, 50)); + $this->colorCli->primaryOver(' → '); + $this->colorCli->error('Parse failed'); + } } } + + // Display summary + if ($this->echooutput && $matched > 0) { + echo "\n"; + $this->colorCli->primaryOver(' ✓ TMDB: '); + $this->colorCli->primary(sprintf('%d matched, %d skipped', $matched, $skipped)); + } } } + /** + * 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).'...'; + } + /** * Calls the API to perform initial show name match to TMDB title * Returns a formatted array of show data or false if no match. diff --git a/Blacklight/processing/tv/TVDB.php b/Blacklight/processing/tv/TVDB.php index d5a9c25a3..be3a335f8 100755 --- a/Blacklight/processing/tv/TVDB.php +++ b/Blacklight/processing/tv/TVDB.php @@ -67,12 +67,18 @@ class TVDB extends TV $tvCount = \count($res); - if ($this->echooutput && $tvCount > 0) { - $this->colorCli->header('Processing TVDB lookup for '.number_format($tvCount).' release(s).', true); + if ($tvCount === 0) { + + return; } + $this->titleCache = []; + $processed = 0; + $matched = 0; + $skipped = 0; foreach ($res as $row) { + $processed++; $siteId = false; $this->posterUrl = ''; @@ -81,11 +87,13 @@ class TVDB extends TV if (\is_array($release) && $release['name'] !== '') { if (\in_array($release['cleanname'], $this->titleCache, false)) { if ($this->echooutput) { - $this->colorCli->headerOver('Title: '). - $this->colorCli->warningOver($release['cleanname']). - $this->colorCli->header(' already failed lookup for this site. Skipping.', true); + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternate('Skipped (previously failed)'); } $this->setVideoNotFound(parent::PROCESS_TVMAZE, $row['id']); + $skipped++; continue; } @@ -106,7 +114,10 @@ class TVDB extends TV if ($siteId === false && $lookupSetting) { // If it doesn't exist locally and lookups are allowed lets try to get it. if ($this->echooutput) { - $this->colorCli->climate()->error('Video ID for '.$release['cleanname'].' not found in local db, checking web.'); + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->info('Searching TVDB...'); } // Check if we have a valid country and set it in the array @@ -125,7 +136,10 @@ class TVDB extends TV $siteId = (int) $tvdbShow['tvdb']; } } elseif ($this->echooutput && $siteId !== false) { - $this->colorCli->climate()->info('Video ID for '.$release['cleanname'].' found in local db, attempting episode match.'); + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->info('Found in DB'); } if ((int) $videoId > 0 && (int) $siteId > 0) { @@ -148,7 +162,13 @@ class TVDB extends TV if ($episodeNo === 'all') { // Set the video ID and leave episode 0 $this->setVideoIdFound($videoId, $row['id'], 0); - $this->colorCli->climate()->info('Found TVDB Match for Full Season!'); + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->primary('Full Season matched'); + } + $matched++; continue; } @@ -179,24 +199,71 @@ class TVDB extends TV // Mark the releases video and episode IDs $this->setVideoIdFound($videoId, $row['id'], $episode); if ($this->echooutput) { - $this->colorCli->climate()->info('Found TVDB Match!'); + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' S'); + $this->colorCli->warningOver(sprintf('%02d', $seriesNo)); + $this->colorCli->primaryOver('E'); + $this->colorCli->warningOver(sprintf('%02d', $episodeNo)); + $this->colorCli->primaryOver(' ✓ '); + $this->colorCli->primary('MATCHED (TVDB)'); } + $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) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warning('Episode not found'); + } } } 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) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warning('Not found'); + } } } 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) { + $this->colorCli->error(sprintf( + ' ✗ [%d/%d] Parse failed: %s', + $processed, + $tvCount, + mb_substr($row['searchname'], 0, 50) + )); + } } } + + // Display summary + if ($this->echooutput && $matched > 0) { + echo "\n"; + $this->colorCli->primaryOver(' ✓ TVDB: '); + $this->colorCli->primary(sprintf('%d matched, %d skipped', $matched, $skipped)); + } + } + + /** + * 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).'...'; } /** @@ -224,12 +291,12 @@ class TVDB extends TV $response = $this->client->search()->search($name, ['type' => 'series']); } catch (ResourceNotFoundException $e) { $response = false; - $this->colorCli->climate()->error('Show not found on TVDB'); + $this->colorCli->error('Show not found on TVDB'); } catch (UnauthorizedException $e) { try { $this->authorizeTvdb(); } catch (UnauthorizedException $error) { - $this->colorCli->climate()->error('Not authorized to access TVDB'); + $this->colorCli->error('Not authorized to access TVDB'); } } @@ -316,7 +383,7 @@ class TVDB extends TV try { $this->authorizeTvdb(); } catch (UnauthorizedException $error) { - $this->colorCli->climate()->error('Not authorized to access TVDB'); + $this->colorCli->error('Not authorized to access TVDB'); } } } else { @@ -364,13 +431,13 @@ class TVDB extends TV $poster = collect($poster)->where('type', 2)->sortByDesc('score')->first(); $this->posterUrl = ! empty($poster->image) ? $poster->image : ''; } catch (ResourceNotFoundException $e) { - $this->colorCli->climate()->error('Poster image not found on TVDB'); + $this->colorCli->error('Poster image not found on TVDB'); } catch (UnauthorizedException $error) { try { $this->authorizeTvdb(); } catch (UnauthorizedException $error) { - $this->colorCli->climate()->error('Not authorized to access TVDB'); + $this->colorCli->error('Not authorized to access TVDB'); } } @@ -378,9 +445,9 @@ class TVDB extends TV $imdbId = $this->client->series()->extended($show->tvdb_id); preg_match('/tt(?P\d{6,9})$/i', $imdbId->getIMDBId(), $imdb); } catch (ResourceNotFoundException $e) { - $this->colorCli->climate()->error('Show ImdbId not found on TVDB'); + $this->colorCli->error('Show ImdbId not found on TVDB'); } catch (\Exception) { - $this->colorCli->climate()->error('Error on TVDB, aborting'); + $this->colorCli->error('Error on TVDB, aborting'); } return [ diff --git a/Blacklight/processing/tv/TVMaze.php b/Blacklight/processing/tv/TVMaze.php index 5944091c7..1dc0501b0 100644 --- a/Blacklight/processing/tv/TVMaze.php +++ b/Blacklight/processing/tv/TVMaze.php @@ -54,14 +54,19 @@ class TVMaze extends TV $tvCount = \count($res); - if ($this->echooutput && $tvCount > 0) { - $this->colorCli->header('Processing TVMaze lookup for '.number_format($tvCount).' release(s).', true); + if ($tvCount === 0) { + + return; } if ($res instanceof \Traversable) { $this->titleCache = []; + $processed = 0; + $matched = 0; + $skipped = 0; foreach ($res as $row) { + $processed++; $siteId = false; $this->posterUrl = ''; @@ -70,11 +75,13 @@ class TVMaze extends TV if (\is_array($release) && $release['name'] !== '') { if (\in_array($release['cleanname'], $this->titleCache, false)) { if ($this->echooutput) { - $this->colorCli->headerOver('Title: '). - $this->colorCli->warningOver($release['cleanname']). - $this->colorCli->header(' already failed lookup for this site. Skipping.', true); + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternate('Skipped (previously failed)'); } $this->setVideoNotFound(parent::PROCESS_TMDB, $row['id']); + $skipped++; continue; } @@ -89,18 +96,20 @@ class TVMaze extends TV if ($videoId === 0 && $lookupSetting) { // If lookups are allowed lets try to get it. if ($this->echooutput) { - $this->colorCli->primaryOver('Checking TVMaze for previously failed title: '). - $this->colorCli->headerOver($release['cleanname']). - $this->colorCli->primary('.', true); + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->info('Searching TVMaze...'); } // Get the show from TVMaze $tvMazeShow = $this->getShowInfo((string) $release['cleanname']); + $dupeCheck = false; + if (\is_array($tvMazeShow)) { $siteId = (int) $tvMazeShow['tvmaze']; // Check if we have the TVDB ID already, if we do use that Video ID, unless it is 0 - $dupeCheck = false; if ((int) $tvMazeShow['tvdb'] !== 0) { $dupeCheck = $this->getVideoIDFromSiteID('tvdb', $tvMazeShow['tvdb']); } @@ -110,12 +119,16 @@ class TVMaze extends TV $videoId = $dupeCheck; // Update any missing fields and add site IDs $this->update($videoId, $tvMazeShow); - $siteId = $this->getSiteIDFromVideoID('tvmaze', $videoId); } + } else { + $videoId = $dupeCheck; } } else { - if ($this->echooutput) { - $this->colorCli->climate()->info('Found local TVMaze match for: '.$release['cleanname'].'. Attempting episode lookup!'); + if ($this->echooutput && $videoId > 0) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->info('Found in DB'); } $siteId = $this->getSiteIDFromVideoID('tvmaze', $videoId); } @@ -130,7 +143,13 @@ class TVMaze extends TV if ($episodeNo === 'all') { // Set the video ID and leave episode 0 $this->setVideoIdFound($videoId, $row['id'], 0); - $this->colorCli->climate()->info('Found TVMaze Match for Full Season!', true); + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->primary('Full Season matched'); + } + $matched++; continue; } @@ -161,27 +180,72 @@ class TVMaze extends TV // Mark the releases video and episode IDs $this->setVideoIdFound($videoId, $row['id'], $episode); if ($this->echooutput) { - $this->colorCli->climate()->info('Found TVMaze Match!', true); + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' S'); + $this->colorCli->warningOver(sprintf('%02d', $seriesNo)); + $this->colorCli->primaryOver('E'); + $this->colorCli->warningOver(sprintf('%02d', $episodeNo)); + $this->colorCli->primaryOver(' ✓ '); + $this->colorCli->primary('MATCHED (TVMaze)'); } + $matched++; } else { // Processing failed, set the episode ID to the next processing group $this->setVideoIdFound($videoId, $row['id'], 0); $this->setVideoNotFound(parent::PROCESS_TMDB, $row['id']); + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warning('Episode not found'); + } } } else { // Processing failed, set the episode ID to the next processing group $this->setVideoNotFound(parent::PROCESS_TMDB, $row['id']); $this->titleCache[] = $release['cleanname'] ?? null; + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warning('Not found'); + } } } else { // Processing failed, set the episode ID to the next processing group $this->setVideoNotFound(parent::PROCESS_TMDB, $row['id']); $this->titleCache[] = $release['cleanname'] ?? null; + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver(mb_substr($row['searchname'], 0, 50)); + $this->colorCli->primaryOver(' → '); + $this->colorCli->error('Parse failed'); + } } } + + // Display summary + if ($this->echooutput && $matched > 0) { + echo "\n"; + $this->colorCli->primaryOver(' ✓ TVMaze: '); + $this->colorCli->primary(sprintf('%d matched, %d skipped', $matched, $skipped)); + } } } + /** + * 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).'...'; + } + /** * Calls the API to lookup the TvMaze info for a given TVDB or TVRage ID * Returns a formatted array of show data or false if no match. diff --git a/Blacklight/processing/tv/TraktTv.php b/Blacklight/processing/tv/TraktTv.php index 400adb7a3..c86552777 100755 --- a/Blacklight/processing/tv/TraktTv.php +++ b/Blacklight/processing/tv/TraktTv.php @@ -61,12 +61,18 @@ class TraktTv extends TV $tvcount = \count($res); - if ($this->echooutput && $tvcount > 1) { - $this->colorCli->header('Processing TRAKT lookup for '.number_format($tvcount).' release(s).', true); + if ($tvcount === 0) { + + return; } if ($res instanceof \Traversable) { + $processed = 0; + $matched = 0; + $skipped = 0; + foreach ($res as $row) { + $processed++; $traktid = false; $this->posterUrl = $this->fanartUrl = $this->localizedTZ = ''; @@ -75,11 +81,13 @@ class TraktTv extends TV if (\is_array($release) && $release['name'] !== '') { if (\in_array($release['cleanname'], $this->titleCache, false)) { if ($this->echooutput) { - $this->colorCli->headerOver('Title: '). - $this->colorCli->warningOver($release['cleanname']). - $this->colorCli->header(' already failed lookup for this site. Skipping.', true); + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternate('Skipped (previously failed)'); } $this->setVideoNotFound(parent::PROCESS_IMDB, $row['id']); + $skipped++; continue; } @@ -97,9 +105,10 @@ class TraktTv extends TV if ($videoId === 0 && $lookupSetting) { // If it doesn't exist locally and lookups are allowed lets try to get it. if ($this->echooutput) { - $this->colorCli->primaryOver('Checking Trakt for previously failed title: '). - $this->colorCli->headerOver($release['cleanname']). - $this->colorCli->primary('.', true); + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->info('Searching Trakt...'); } // Get the show from TRAKT @@ -110,9 +119,11 @@ class TraktTv extends TV $traktid = (int) $traktShow['trakt']; } } else { - if ($this->echooutput) { - $this->colorCli->climate()->info('Found local TRAKT match for: '.$release['cleanname']); - $this->colorCli->climate()->info(' Attempting episode lookup!'); + if ($this->echooutput && $videoId > 0) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->info('Found in DB'); } $traktid = $this->getSiteIDFromVideoID('trakt', $videoId); $this->localizedTZ = $this->getLocalZoneFromVideoID($videoId); @@ -128,7 +139,13 @@ class TraktTv extends TV if ($episodeNo === 'all') { // Set the video ID and leave episode 0 $this->setVideoIdFound($videoId, $row['id'], 0); - $this->colorCli->climate()->info('Found TRAKT Match for Full Season!'); + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->primary('Full Season matched'); + } + $matched++; continue; } @@ -153,27 +170,72 @@ class TraktTv extends TV // Mark the releases video and episode IDs $this->setVideoIdFound($videoId, $row['id'], $episode); if ($this->echooutput) { - $this->colorCli->climate()->info('Found TRAKT Match!'); + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' S'); + $this->colorCli->warningOver(sprintf('%02d', $seasonNo)); + $this->colorCli->primaryOver('E'); + $this->colorCli->warningOver(sprintf('%02d', $episodeNo)); + $this->colorCli->primaryOver(' ✓ '); + $this->colorCli->primary('MATCHED (Trakt)'); } + $matched++; } else { // Processing failed, set the episode ID to the next processing group $this->setVideoIdFound($videoId, $row['id'], 0); $this->setVideoNotFound(parent::PROCESS_IMDB, $row['id']); + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warning('Episode not found'); + } } } else { // Processing failed, set the episode ID to the next processing group $this->setVideoNotFound(parent::PROCESS_IMDB, $row['id']); $this->titleCache[] = $release['cleanname'] ?? null; + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver($this->truncateTitle($release['cleanname'])); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warning('Not found'); + } } } else { // Processing failed, set the episode ID to the next processing group $this->setVideoNotFound(parent::PROCESS_IMDB, $row['id']); $this->titleCache[] = $release['cleanname'] ?? null; + if ($this->echooutput) { + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver(mb_substr($row['searchname'], 0, 50)); + $this->colorCli->primaryOver(' → '); + $this->colorCli->error('Parse failed'); + } } } + + // Display summary + if ($this->echooutput && $matched > 0) { + echo "\n"; + $this->colorCli->primaryOver(' ✓ Trakt: '); + $this->colorCli->primary(sprintf('%d matched, %d skipped', $matched, $skipped)); + } } } + /** + * 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).'...'; + } + /** * Fetch banner from site. */ diff --git a/app/Console/Commands/PostProcessTvPipeline.php b/app/Console/Commands/PostProcessTvPipeline.php new file mode 100644 index 000000000..f1813dd02 --- /dev/null +++ b/app/Console/Commands/PostProcessTvPipeline.php @@ -0,0 +1,73 @@ +argument('guid'); + $renamed = $this->argument('renamed') ?? ''; + $mode = $this->option('mode') ?? 'pipeline'; + + if (! $this->isValidChar($guid)) { + $this->error('GUID character must be a-f or 0-9.'); + + return self::FAILURE; + } + + if (! in_array($mode, ['pipeline', 'parallel'], true)) { + $this->error('Mode must be either "pipeline" or "parallel".'); + + return self::FAILURE; + } + + try { + $tvProcessor = new TvProcessor(true); // true = echo output + $tvProcessor->process('', $guid, $renamed, $mode); + + return self::SUCCESS; + } catch (\Throwable $e) { + Log::error($e->getTraceAsString()); + $this->error($e->getMessage()); + + return self::FAILURE; + } + } + + /** + * Check if the character contains a-f or 0-9. + */ + private function isValidChar(string $char): bool + { + return \in_array( + $char, + ['a', 'b', 'c', 'd', 'e', 'f', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + true + ); + } +} diff --git a/app/Services/Tmux/TmuxTaskRunner.php b/app/Services/Tmux/TmuxTaskRunner.php index 9d8e0a48b..e15b44732 100644 --- a/app/Services/Tmux/TmuxTaskRunner.php +++ b/app/Services/Tmux/TmuxTaskRunner.php @@ -588,21 +588,20 @@ class TmuxTaskRunner $artisan = PHP_BINARY.' artisan'; $commands = []; - // Only add TV processing if enabled and has work + // TV processing - Uses update:postprocess which now spawns parallel processes with pipelined providers $processTv = (int) ($runVar['settings']['processtvrage'] ?? 0); $hasTvWork = (int) ($runVar['counts']['now']['processtv'] ?? 0) > 0; if ($processTv > 0 && $hasTvWork) { - $commands[] = "nice -n{$niceness} {$artisan} update:postprocess tv true 2>&1 | tee -a {$log}"; + $commands[] = "nice -n{$niceness} {$artisan} update:postprocess tv 2>&1 | tee -a {$log}"; } - // Only add Movies processing if enabled and has work + // Movies processing - Uses single-process command $processMovies = (int) ($runVar['settings']['processmovies'] ?? 0); $hasMoviesWork = (int) ($runVar['counts']['now']['processmovies'] ?? 0) > 0; if ($processMovies > 0 && $hasMoviesWork) { $commands[] = "nice -n{$niceness} {$artisan} update:postprocess movies true 2>&1 | tee -a {$log}"; } - // Only add Anime processing if enabled and has work $processAnime = (int) ($runVar['settings']['processanime'] ?? 0); $hasAnimeWork = (int) ($runVar['counts']['now']['processanime'] ?? 0) > 0; if ($processAnime > 0 && $hasAnimeWork) { diff --git a/app/Services/TvProcessor.php b/app/Services/TvProcessor.php index 71dca80ec..513fbc542 100644 --- a/app/Services/TvProcessor.php +++ b/app/Services/TvProcessor.php @@ -3,6 +3,8 @@ namespace App\Services; use App\Models\Settings; +use Blacklight\ColorCLI; +use Blacklight\processing\tv\LocalDB; use Blacklight\processing\tv\TMDB; use Blacklight\processing\tv\TraktTv; use Blacklight\processing\tv\TVDB; @@ -10,26 +12,237 @@ use Blacklight\processing\tv\TVMaze; class TvProcessor { + // Processing modes + public const MODE_PIPELINE = 'pipeline'; // Sequential processing (efficient, reduces API calls) + + public const MODE_PARALLEL = 'parallel'; // Parallel processing (faster, more API calls) + private bool $echooutput; + private ColorCLI $colorCli; + + private array $stats = [ + 'total' => 0, + 'processed' => 0, + 'matched' => 0, + 'failed' => 0, + 'skipped' => 0, + 'byProvider' => [ + 'Local DB' => ['processed' => 0, 'matched' => 0, 'failed' => 0], + 'TVDB' => ['processed' => 0, 'matched' => 0, 'failed' => 0], + 'TVMaze' => ['processed' => 0, 'matched' => 0, 'failed' => 0], + 'TMDB' => ['processed' => 0, 'matched' => 0, 'failed' => 0], + 'Trakt' => ['processed' => 0, 'matched' => 0, 'failed' => 0], + ], + ]; + public function __construct(bool $echooutput) { $this->echooutput = $echooutput; + $this->colorCli = new ColorCLI; } /** * Process all TV related releases across supported providers. * + * @param string $groupID Group ID to process + * @param string $guidChar GUID character to process * @param int|string|null $processTV 0/1/2 or '' to read from settings + * @param string $mode Processing mode: 'pipeline' (sequential) or 'parallel' (simultaneous) */ - public function process(string $groupID = '', string $guidChar = '', int|string|null $processTV = ''): void + public function process(string $groupID = '', string $guidChar = '', int|string|null $processTV = '', string $mode = self::MODE_PIPELINE): void { $processTV = (is_numeric($processTV) ? $processTV : Settings::settingValue('lookuptv')); - if ($processTV > 0) { - (new TVDB)->processSite($groupID, $guidChar, $processTV); - (new TVMaze)->processSite($groupID, $guidChar, $processTV); - (new TMDB)->processSite($groupID, $guidChar, $processTV); - (new TraktTv)->processSite($groupID, $guidChar, $processTV); + if ($processTV <= 0) { + return; + } + + if ($mode === self::MODE_PARALLEL) { + $this->processParallel($groupID, $guidChar, $processTV); + } else { + $this->processPipeline($groupID, $guidChar, $processTV); } } + + /** + * Process releases through providers in parallel (all providers process all releases). + * This is faster but uses more API calls. Compatible with Forking class. + */ + private function processParallel(string $groupID, string $guidChar, int|string $processTV): void + { + // $this->displayHeaderParallel($guidChar); + + $providers = $this->getProviderPipeline(); + $totalTime = 0; + + foreach ($providers as $index => $provider) { + $this->displayProviderHeader($provider['name'], $index + 1, count($providers)); + + $startTime = microtime(true); + $provider['instance']->processSite($groupID, $guidChar, $processTV); + $elapsedTime = microtime(true) - $startTime; + $totalTime += $elapsedTime; + + $this->displayProviderComplete($provider['name'], $elapsedTime); + } + + // $this->displaySummaryParallel($totalTime); + } + + /** + * Process releases through providers in pipeline (sequential, each processes failures from previous). + * This is more efficient and reduces API calls significantly. + */ + private function processPipeline(string $groupID, string $guidChar, int|string $processTV): void + { + // $this->displayHeader($guidChar); + + // Pipeline: Process releases through each provider in sequence + // Each provider only processes releases that failed in previous steps + $providers = $this->getProviderPipeline(); + + foreach ($providers as $index => $provider) { + $this->displayProviderHeader($provider['name'], $index + 1, count($providers)); + + $startTime = microtime(true); + $provider['instance']->processSite($groupID, $guidChar, $processTV); + $elapsedTime = microtime(true) - $startTime; + + $this->displayProviderComplete($provider['name'], $elapsedTime); + } + + // $this->displaySummary(); + } + + /** + * Get the provider pipeline in order of preference. + */ + private function getProviderPipeline(): array + { + return [ + ['name' => 'Local DB', 'instance' => new LocalDB], + ['name' => 'TVDB', 'instance' => new TVDB], + ['name' => 'TVMaze', 'instance' => new TVMaze], + ['name' => 'TMDB', 'instance' => new TMDB], + ['name' => 'Trakt', 'instance' => new TraktTv], + ]; + } + + /** + * Display the processing header. + */ + private function displayHeader(string $guidChar = ''): void + { + if (! $this->echooutput) { + return; + } + + echo "\n"; + $this->colorCli->headerOver('▶ TV Processing'); + if ($guidChar !== '') { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver('PIPELINE Mode'); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warningOver('Bucket: '); + $this->colorCli->header(strtoupper($guidChar)); + } else { + $this->colorCli->primaryOver(' → '); + $this->colorCli->header('PIPELINE Mode'); + } + echo "\n"; + } + + /** + * Display the processing header for parallel mode. + */ + private function displayHeaderParallel(string $guidChar = ''): void + { + if (! $this->echooutput) { + return; + } + + echo "\n"; + $this->colorCli->headerOver('▶ TV Processing'); + if ($guidChar !== '') { + $this->colorCli->primaryOver(' → '); + $this->colorCli->headerOver('PARALLEL Mode'); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warningOver('Bucket: '); + $this->colorCli->header(strtoupper($guidChar)); + } else { + $this->colorCli->primaryOver(' → '); + $this->colorCli->header('PARALLEL Mode'); + } + echo "\n"; + } + + /** + * Display provider processing header. + */ + private function displayProviderHeader(string $providerName, int $step, int $total): void + { + if (! $this->echooutput) { + return; + } + + echo "\n"; + $this->colorCli->primaryOver(' ['); + $this->colorCli->warningOver($step); + $this->colorCli->primaryOver('/'); + $this->colorCli->warningOver($total); + $this->colorCli->primaryOver('] '); + $this->colorCli->headerOver('→ '); + $this->colorCli->header($providerName); + } + + /** + * Display provider completion message. + */ + private function displayProviderComplete(string $providerName, float $elapsedTime): void + { + if (! $this->echooutput) { + return; + } + + echo "\n"; + $this->colorCli->primaryOver(' ✓ '); + $this->colorCli->primaryOver($providerName); + $this->colorCli->primaryOver(' → '); + $this->colorCli->alternateOver('Completed in '); + $this->colorCli->warning(sprintf('%.2fs', $elapsedTime)); + echo "\n"; + } + + /** + * Display final processing summary. + */ + private function displaySummary(): void + { + if (! $this->echooutput) { + return; + } + + echo "\n"; + $this->colorCli->primaryOver('✓ Pipeline Complete'); + $this->colorCli->primaryOver(' → '); + $this->colorCli->primary('Local DB → TVDB → TVMaze → TMDB → Trakt'); + echo "\n"; + } + + /** + * Display final processing summary for parallel mode. + */ + private function displaySummaryParallel(float $totalTime): void + { + if (! $this->echooutput) { + return; + } + + echo "\n"; + $this->colorCli->primaryOver('✓ Parallel Processing Complete'); + $this->colorCli->primaryOver(' → '); + $this->colorCli->warningOver('Total: '); + $this->colorCli->warning(sprintf('%.2fs', $totalTime)); + echo "\n"; + } } diff --git a/composer.json b/composer.json index b6491b778..0ff5a739d 100644 --- a/composer.json +++ b/composer.json @@ -72,7 +72,6 @@ "laravel/telescope": "^5.5", "laravel/tinker": "^2.10.1", "laravel/ui": "^4.6", - "league/climate": "^3.10", "leventcz/laravel-top": "^1.2", "livewire/livewire": "^3.6", "livewire/volt": "^1.6", diff --git a/composer.lock b/composer.lock index 22786d677..fefd607c9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cbfc1c1787fc6e1b140f4311f80b0563", + "content-hash": "6eaa140f91ed2bbcc198d0d60b7a95cc", "packages": [ { "name": "aharen/omdbapi", @@ -3899,72 +3899,6 @@ }, "time": "2025-01-28T15:15:29+00:00" }, - { - "name": "league/climate", - "version": "3.10.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/climate.git", - "reference": "237f70e1032b16d32ff3f65dcda68706911e1c74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/climate/zipball/237f70e1032b16d32ff3f65dcda68706911e1c74", - "reference": "237f70e1032b16d32ff3f65dcda68706911e1c74", - "shasum": "" - }, - "require": { - "php": "^7.3 || ^8.0", - "psr/log": "^1.0 || ^2.0 || ^3.0", - "seld/cli-prompt": "^1.0" - }, - "require-dev": { - "mikey179/vfsstream": "^1.6.12", - "mockery/mockery": "^1.6.12", - "phpunit/phpunit": "^9.5.10", - "squizlabs/php_codesniffer": "^3.10" - }, - "suggest": { - "ext-mbstring": "If ext-mbstring is not available you MUST install symfony/polyfill-mbstring" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\CLImate\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Joe Tannenbaum", - "email": "hey@joe.codes", - "homepage": "http://joe.codes/", - "role": "Developer" - }, - { - "name": "Craig Duncan", - "email": "git@duncanc.co.uk", - "homepage": "https://github.com/duncan3dc", - "role": "Developer" - } - ], - "description": "PHP's best friend for the terminal. CLImate allows you to easily output colored text, special formats, and more.", - "keywords": [ - "cli", - "colors", - "command", - "php", - "terminal" - ], - "support": { - "issues": "https://github.com/thephpleague/climate/issues", - "source": "https://github.com/thephpleague/climate/tree/3.10.0" - }, - "time": "2024-11-18T09:09:55+00:00" - }, { "name": "league/commonmark", "version": "2.7.1", @@ -8587,61 +8521,6 @@ }, "time": "2025-09-17T19:51:09+00:00" }, - { - "name": "seld/cli-prompt", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/cli-prompt.git", - "reference": "b8dfcf02094b8c03b40322c229493bb2884423c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/b8dfcf02094b8c03b40322c229493bb2884423c5", - "reference": "b8dfcf02094b8c03b40322c229493bb2884423c5", - "shasum": "" - }, - "require": { - "php": ">=5.3" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.63" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Seld\\CliPrompt\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type", - "keywords": [ - "cli", - "console", - "hidden", - "input", - "prompt" - ], - "support": { - "issues": "https://github.com/Seldaek/cli-prompt/issues", - "source": "https://github.com/Seldaek/cli-prompt/tree/1.0.4" - }, - "time": "2020-12-15T21:32:01+00:00" - }, { "name": "sentry/sentry", "version": "4.17.1", @@ -14675,21 +14554,22 @@ }, { "name": "driftingly/rector-laravel", - "version": "2.1.2", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/driftingly/rector-laravel.git", - "reference": "d7cd932cff9e398a43393f1a1a63b27d574e35ef" + "reference": "2f1e9c3997bf45592d58916f0cedd775e844b9c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/d7cd932cff9e398a43393f1a1a63b27d574e35ef", - "reference": "d7cd932cff9e398a43393f1a1a63b27d574e35ef", + "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/2f1e9c3997bf45592d58916f0cedd775e844b9c6", + "reference": "2f1e9c3997bf45592d58916f0cedd775e844b9c6", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "rector/rector": "^2.2.7" + "rector/rector": "^2.2.7", + "webmozart/assert": "^1.11" }, "type": "rector-extension", "autoload": { @@ -14704,9 +14584,9 @@ "description": "Rector upgrades rules for Laravel Framework", "support": { "issues": "https://github.com/driftingly/rector-laravel/issues", - "source": "https://github.com/driftingly/rector-laravel/tree/2.1.2" + "source": "https://github.com/driftingly/rector-laravel/tree/2.1.3" }, - "time": "2025-10-31T21:56:58+00:00" + "time": "2025-11-04T18:32:57+00:00" }, { "name": "ergebnis/composer-normalize", diff --git a/misc/testing/PostProc/updateMovieImages.php b/misc/testing/PostProc/updateMovieImages.php index cebdb83a6..92506c00f 100644 --- a/misc/testing/PostProc/updateMovieImages.php +++ b/misc/testing/PostProc/updateMovieImages.php @@ -27,7 +27,7 @@ foreach ($itr as $filePath) { } else { $run = MovieInfo::query()->where('imdbid', '=', $hit[1])->select(['imdbid'])->get(); if ($run->count() === 0) { - $colorCli->climate()->error($filePath.' not found in db.'); + $colorCli->error($filePath.' not found in db.'); } } } @@ -41,7 +41,7 @@ foreach ($itr as $filePath) { } else { $run = MovieInfo::query()->where('imdbid', $match1[1])->select(['imdbid'])->get(); if ($run->count() === 0) { - $colorCli->climate()->error($filePath->getPathname().' not found in db.'); + $colorCli->error($filePath->getPathname().' not found in db.'); } } } @@ -60,10 +60,10 @@ $qry1 = MovieInfo::query()->where('backdrop', '=', 1)->select(['imdbid'])->get() foreach ($qry1 as $rows) { if (! is_file($path2covers.$rows['imdbid'].'-backdrop.jpg')) { MovieInfo::query()->where('backdrop', '=', 1)->where('imdbid', $rows['imdbid'])->update(['backdrop' => 0]); - $colorCli->climate()->info($path2covers.$rows['imdbid'].'-backdrop.jpg does not exist.'); + $colorCli->info($path2covers.$rows['imdbid'].'-backdrop.jpg does not exist.'); $deleted++; } } -$colorCli->climate()->info($covers.' covers set.'); -$colorCli->climate()->info($updated.' backdrops set.'); -$colorCli->climate()->info($deleted.' movies unset.'); +$colorCli->info($covers.' covers set.'); +$colorCli->info($updated.' backdrops set.'); +$colorCli->info($deleted.' movies unset.');