diff --git a/Blacklight/Nfo.php b/Blacklight/Nfo.php deleted file mode 100755 index 69f349b43..000000000 --- a/Blacklight/Nfo.php +++ /dev/null @@ -1,1458 +0,0 @@ -echo = (bool) config('nntmux.echocli'); - - // Cache settings to reduce database queries - // Note: Cast after Cache::remember as cached values may be stored as strings - $this->nzbs = (int) Cache::remember('nfo_maxnfoprocessed', self::SETTINGS_CACHE_TTL, function () { - $value = Settings::settingValue('maxnfoprocessed'); - - return $value !== '' ? (int) $value : 100; - }); - - $maxRetries = (int) Cache::remember('nfo_maxnforetries', self::SETTINGS_CACHE_TTL, function () { - return (int) Settings::settingValue('maxnforetries'); - }); - $this->maxRetries = $maxRetries >= 0 ? -($maxRetries + 1) : self::NFO_UNPROC; - $this->maxRetries = max($this->maxRetries, -8); - - $this->maxSize = (int) Cache::remember('nfo_maxsizetoprocessnfo', self::SETTINGS_CACHE_TTL, function () { - return (int) Settings::settingValue('maxsizetoprocessnfo'); - }); - - $this->minSize = (int) Cache::remember('nfo_minsizetoprocessnfo', self::SETTINGS_CACHE_TTL, function () { - return (int) Settings::settingValue('minsizetoprocessnfo'); - }); - - $this->tmpPath = rtrim((string) config('nntmux.tmp_unrar_path'), '/\\') . '/'; - } - - /** - * Look for a TV Show ID or Movie ID in a string. - * - * Supports: TVMaze, IMDB, TVDB (legacy & modern), TMDB, AniDB - * - * @param string $str The string with a Show ID. - * @return array{showid: string, site: string}|false Return array with show ID and site source or false on failure. - */ - public function parseShowId(string $str): array|false - { - // TVMaze - if (preg_match('/tvmaze\.com\/shows\/(\d{1,6})/i', $str, $hits)) { - return ['showid' => trim($hits[1]), 'site' => 'tvmaze']; - } - - // IMDB (movies and TV shows) - if (preg_match('/imdb\.com\/title\/(tt\d{7,8})/i', $str, $hits)) { - return ['showid' => trim($hits[1]), 'site' => 'imdb']; - } - - // TVDB - Legacy URL format - if (preg_match('/thetvdb\.com\/\?tab=series&id=(\d{1,8})/i', $str, $hits)) { - return ['showid' => trim($hits[1]), 'site' => 'thetvdb']; - } - - // TVDB - Modern URL format (series/slug or series/id) - if (preg_match('/thetvdb\.com\/series\/(\d{1,8}|[\w-]+)/i', $str, $hits)) { - return ['showid' => trim($hits[1]), 'site' => 'thetvdb']; - } - - // TMDB - Movie - if (preg_match('/themoviedb\.org\/movie\/(\d{1,8})/i', $str, $hits)) { - return ['showid' => trim($hits[1]), 'site' => 'tmdb_movie']; - } - - // TMDB - TV Show - if (preg_match('/themoviedb\.org\/tv\/(\d{1,8})/i', $str, $hits)) { - return ['showid' => trim($hits[1]), 'site' => 'tmdb_tv']; - } - - // AniDB - if (preg_match('/anidb\.net\/(?:perl-bin\/animedb\.pl\?show=anime&aid=|anime\/)(\d{1,6})/i', $str, $hits)) { - return ['showid' => trim($hits[1]), 'site' => 'anidb']; - } - - // Trakt.tv - if (preg_match('/trakt\.tv\/(?:shows|movies)\/([\w-]+)/i', $str, $hits)) { - return ['showid' => trim($hits[1]), 'site' => 'trakt']; - } - - return false; - } - - /** - * Confirm this is an NFO file. - * - * Uses multiple validation strategies: - * 1. Size validation (too large/small = not NFO) - * 2. Binary header detection (known file signatures) - * 3. File type detection via 'file' command - * 4. PAR2/SFV structure detection - * 5. Binary character content analysis - * 6. NFO keyword/content heuristics - * - * @param bool|string $possibleNFO The nfo content. - * @param string $guid The guid of the release. - * @return bool True if it's likely an NFO, False otherwise. - */ - public function isNFO(bool|string &$possibleNFO, string $guid): bool - { - if ($possibleNFO === false || $possibleNFO === '') { - return false; - } - - $size = \strlen($possibleNFO); - - // Basic size and signature checks using constants - if ($size >= self::MAX_NFO_SIZE || $size < self::MIN_NFO_SIZE) { - return false; - } - - // Quick check for known non-NFO file signatures - if (preg_match($this->_nonNfoHeaderRegex, $possibleNFO)) { - return false; - } - - // Additional binary format checks - if ($this->detectBinaryFormat($possibleNFO)) { - return false; - } - - $tmpPath = $this->tmpPath.$guid.'.nfo'; - $isNfo = false; - - try { - // File/GetId3 work with files, so save to disk. - File::put($tmpPath, $possibleNFO); - - // Use 'file' command via fileInfo if available - $result = fileInfo($tmpPath); - if (! empty($result)) { - if (preg_match($this->_textFileRegex, $result)) { - $isNfo = true; - } elseif (preg_match($this->_binaryFileRegex, $result) || preg_match($this->_binaryCharsRegex, $possibleNFO)) { - $isNfo = false; - } - - // If fileInfo gave a result, apply additional heuristics before returning - if ($isNfo) { - // Additional content validation for text files - $isNfo = $this->validateNfoContent($possibleNFO); - } - - return $isNfo; - } - - // Fallback checks if 'file' command is unavailable or inconclusive - // Check if it's a PAR2 file - $par2info = new Par2Info; - $par2info->setData($possibleNFO); - if (! $par2info->error) { - return false; - } - - // Check if it's an SFV file - $sfv = new SfvInfo; - $sfv->setData($possibleNFO); - if (! $sfv->error) { - return false; - } - - // Check for binary characters - if (preg_match($this->_binaryCharsRegex, $possibleNFO)) { - return false; - } - - // Final content-based validation - $isNfo = $this->validateNfoContent($possibleNFO); - - } catch (Throwable $e) { - Log::error("Error processing potential NFO for GUID {$guid}: ".$e->getMessage()); - $isNfo = false; - } finally { - // Ensure temporary file is always deleted - if (File::exists($tmpPath)) { - try { - File::delete($tmpPath); - } catch (Throwable $e) { - Log::error("Error deleting temporary NFO file {$tmpPath}: ".$e->getMessage()); - } - } - } - - return $isNfo; - } - - /** - * Detect binary file formats by magic bytes. - * - * @param string $data The file content to check. - * @return bool True if binary format detected. - */ - protected function detectBinaryFormat(string $data): bool - { - if (strlen($data) < 4) { - return false; - } - - // Magic bytes for common binary formats - $magicBytes = [ - "\x50\x4B\x03\x04" => 'ZIP', // ZIP/DOCX/XLSX etc. - "\x50\x4B\x05\x06" => 'ZIP_EMPTY', // Empty ZIP - "\x52\x61\x72\x21" => 'RAR', // RAR - "\x37\x7A\xBC\xAF" => '7Z', // 7-Zip - "\x1F\x8B\x08" => 'GZIP', // GZip - "\x42\x5A\x68" => 'BZIP2', // BZip2 - "\xFD\x37\x7A\x58" => 'XZ', // XZ - "\x89\x50\x4E\x47" => 'PNG', // PNG - "\xFF\xD8\xFF" => 'JPEG', // JPEG - "\x47\x49\x46\x38" => 'GIF', // GIF - "\x25\x50\x44\x46" => 'PDF', // PDF - "\x49\x44\x33" => 'MP3_ID3', // MP3 with ID3 - "\xFF\xFB" => 'MP3', // MP3 - "\x4F\x67\x67\x53" => 'OGG', // OGG - "\x66\x4C\x61\x43" => 'FLAC', // FLAC - "\x52\x49\x46\x46" => 'RIFF', // WAV/AVI - "\x00\x00\x01\xBA" => 'MPEG', // MPEG video - "\x00\x00\x01\xB3" => 'MPEG', // MPEG video - "\x1A\x45\xDF\xA3" => 'MKV', // Matroska/WebM - "\x4D\x5A" => 'EXE', // Windows EXE - "\x7F\x45\x4C\x46" => 'ELF', // Linux executable - "\xCA\xFE\xBA\xBE" => 'JAVA', // Java class - "\xD0\xCF\x11\xE0" => 'OLE', // MS Office old format - ]; - - foreach ($magicBytes as $magic => $type) { - if (str_starts_with($data, $magic)) { - return true; - } - } - - // Check for UTF-16 BOM (could be text, but unlikely NFO) - if (str_starts_with($data, "\xFF\xFE") || str_starts_with($data, "\xFE\xFF")) { - // UTF-16 - could be valid, let other checks handle it - return false; - } - - return false; - } - - /** - * Validate NFO content using heuristics. - * - * @param string $content The content to validate. - * @return bool True if content appears to be a valid NFO. - */ - protected function validateNfoContent(string $content): bool - { - $length = strlen($content); - - // Too short to be meaningful - if ($length < 50) { - return false; - } - - // Count printable ASCII characters - $printableCount = preg_match_all('/[\x20-\x7E]/', $content); - $printableRatio = $printableCount / $length; - - // NFOs should be mostly printable characters - if ($printableRatio < 0.7) { - return false; - } - - // Check for minimum text content (words, not just symbols) - $wordCount = preg_match_all('/[A-Za-z]{2,}/', $content); - if ($wordCount < 5) { - return false; - } - - // Check for NFO-like content patterns - $nfoIndicators = 0; - - // Look for common NFO keywords - foreach ($this->_nfoKeywords as $keyword) { - if (stripos($content, $keyword) !== false) { - $nfoIndicators++; - if ($nfoIndicators >= 3) { - return true; // High confidence if multiple keywords found - } - } - } - - // Check for scene-style formatting - if (preg_match('/[-=*]{5,}/', $content)) { - $nfoIndicators++; - } - - // Check for URL presence (common in NFOs) - if (preg_match('/https?:\/\/|www\./i', $content)) { - $nfoIndicators++; - } - - // Check for media IDs - if (preg_match('/imdb\.com|thetvdb\.com|themoviedb\.org|anidb\.net/i', $content)) { - $nfoIndicators += 2; - } - - // Check for field:value patterns - if (preg_match_all('/^[A-Za-z\s]{2,20}\s*[:\.]\s*.+$/m', $content, $matches)) { - $nfoIndicators += min(count($matches[0]) / 3, 2); - } - - return $nfoIndicators >= 2; - } - - /** - * Add an NFO from alternate sources. ex.: PreDB, rar, zip, etc... - * - * @param string $nfo The nfo. - * @param NNTPService $nntp Instance of class NNTPService. - * @return bool True on success, False on failure. - * - * @throws \Exception - */ - public function addAlternateNfo(string &$nfo, $release, NNTPService $nntp): bool - { - if ($release->id > 0 && $this->isNFO($nfo, $release->guid)) { - $check = ReleaseNfo::whereReleasesId($release->id)->first(['releases_id']); - - if ($check === null) { - ReleaseNfo::query()->insert(['releases_id' => $release->id, 'nfo' => "\x1f\x8b\x08\x00".gzcompress($nfo)]); - } - - Release::whereId($release->id)->update(['nfostatus' => self::NFO_FOUND]); - - if (! isset($release->completion)) { - $release->completion = 0; - } - - if ($release->completion === 0) { - $nzbContentsService = app(NzbContentsService::class); - $nzbContentsService->setNntp($nntp); - $nzbContentsService->setNfo($this); - $nzbContentsService->setEchoOutput($this->echo); - $nzbContentsService->parseNzb($release->guid, $release->id, $release->groups_id ?? 0); - } - - return true; - } - - return false; - } - - /** - * Attempt to find NFO files inside the NZB's of releases. - * - * @param NNTPService $nntp The NNTP connection object - * @param string $groupID (optional) Group ID to filter releases by - * @param string $guidChar (optional) First character of the GUID for parallel processing - * @param bool $processImdb (optional) Process IMDB IDs (currently unused) - * @param bool $processTv (optional) Process TV IDs (currently unused) - * @return int Count of successfully processed NFO files - * - * @throws \Exception If NNTP operations fail - */ - public function processNfoFiles(NNTPService $nntp, string $groupID = '', string $guidChar = '', bool $processImdb = true, bool $processTv = true): int - { - $processedCount = 0; - - // Build base query with all filters - $baseQuery = $this->buildNfoProcessingQuery($groupID, $guidChar); - - // Fetch releases to process - $releases = $baseQuery->clone() - ->orderBy('nfostatus') - ->orderByDesc('postdate') - ->limit($this->nzbs) - ->get(['id', 'guid', 'groups_id', 'name']); - - $nfoCount = $releases->count(); - - if ($nfoCount > 0) { - // Display processing information - $this->displayProcessingHeader($guidChar, $groupID, $nfoCount); - - // Show detailed stats if echo is enabled - if ($this->echo) { - $this->displayNfoStatusStats($baseQuery); - } - - // Process each release - $nzbContentsService = app(NzbContentsService::class); - $nzbContentsService->setNntp($nntp); - $nzbContentsService->setNfo($this); - - foreach ($releases as $release) { - try { - $groupName = UsenetGroup::getNameByID($release['groups_id']); - $fetchedBinary = $nzbContentsService->getNfoFromNzb($release['guid'], $release['id'], $release['groups_id'], $groupName); - - if ($fetchedBinary !== false) { - DB::beginTransaction(); - try { - // Only insert if not already present - $exists = ReleaseNfo::whereReleasesId($release['id'])->exists(); - if (! $exists) { - ReleaseNfo::query()->insert([ - 'releases_id' => $release['id'], - 'nfo' => "\x1f\x8b\x08\x00".gzcompress($fetchedBinary), - ]); - } - - // Update status - Release::whereId($release['id'])->update(['nfostatus' => self::NFO_FOUND]); - DB::commit(); - $processedCount++; - } catch (\Exception $e) { - DB::rollBack(); - if ($this->echo) { - cli()->error("Error saving NFO for release {$release['id']}: {$e->getMessage()}"); - } - } - } - } catch (\Exception $e) { - if ($this->echo) { - cli()->error("Error processing release {$release['id']}: {$e->getMessage()}"); - } - } - } - } - - // Process failed NFO attempts - $this->handleFailedNfoAttempts($groupID, $guidChar); - - // Output results - if ($this->echo) { - if ($nfoCount > 0) { - echo PHP_EOL; - } - if ($processedCount > 0) { - cli()->primary($processedCount.' NFO file(s) found/processed.'); - } - } - - return $processedCount; - } - - /** - * Build base query for NFO processing with all common filters - */ - private function buildNfoProcessingQuery(string $groupID, string $guidChar): \Illuminate\Database\Eloquent\Builder - { - $query = Release::query() - ->whereBetween('nfostatus', [$this->maxRetries, self::NFO_UNPROC]); - - if ($guidChar !== '') { - $query->where('leftguid', $guidChar); - } - - if ($groupID !== '') { - $query->where('groups_id', $groupID); - } - - if ($this->maxSize > 0) { - $query->where('size', '<', $this->maxSize * 1073741824); - } - - if ($this->minSize > 0) { - $query->where('size', '>', $this->minSize * 1048576); - } - - return $query; - } - - /** - * Display header information about the NFO processing - */ - private function displayProcessingHeader(string $guidChar, string $groupID, int $nfoCount): void - { - cli()->primary( - PHP_EOL. - ($guidChar === '' ? '' : '['.$guidChar.'] '). - ($groupID === '' ? '' : '['.$groupID.'] '). - 'Processing '.$nfoCount. - ' NFO(s), starting at '.$this->nzbs. - ' * = hidden NFO, + = NFO, - = no NFO, f = download failed.' - ); - } - - /** - * Display statistics about NFO status counts - */ - private function displayNfoStatusStats(\Illuminate\Database\Eloquent\Builder $baseQuery): void - { - $nfoStats = $baseQuery->clone() - ->select(['nfostatus as status', DB::raw('COUNT(id) as count')]) - ->groupBy(['nfostatus']) - ->orderBy('nfostatus') - ->get(); - - if ($nfoStats instanceof \Traversable && $nfoStats->count() > 0) { - $outString = PHP_EOL.'Available to process'; - foreach ($nfoStats as $row) { - $outString .= ', '.$row['status'].' = '.number_format($row['count']); - } - cli()->header($outString.'.'); - } - } - - /** - * Handle releases that have failed too many NFO fetch attempts - */ - private function handleFailedNfoAttempts(string $groupID, string $guidChar): void - { - $failedQuery = Release::query() - ->where('nfostatus', '<', $this->maxRetries) - ->where('nfostatus', '>', self::NFO_FAILED); - - if ($guidChar !== '') { - $failedQuery->where('leftguid', $guidChar); - } - - if ($groupID !== '') { - $failedQuery->where('groups_id', $groupID); - } - - // Process in chunks to avoid memory issues with large result sets - $failedQuery->select(['id'])->chunk(100, function ($releases) { - DB::beginTransaction(); - try { - foreach ($releases as $release) { - // Remove any releasenfo for failed attempts - ReleaseNfo::whereReleasesId($release->id)->delete(); - - // Set release.nfostatus to failed - Release::whereId($release->id)->update(['nfostatus' => self::NFO_FAILED]); - } - DB::commit(); - } catch (\Exception $e) { - DB::rollBack(); - if ($this->echo) { - cli()->error("Error handling failed NFO attempts: {$e->getMessage()}"); - } - } - }); - } - - /** - * Get a string like this: - * "AND r.nfostatus BETWEEN -8 AND -1 AND r.size < 1073741824 AND r.size > 1048576" - * To use in a query. - * - * - * @throws \Exception - * - * @static - */ - public static function NfoQueryString(): string - { - $maxSize = (int) Settings::settingValue('maxsizetoprocessnfo'); - $minSize = (int) Settings::settingValue('minsizetoprocessnfo'); - $dummy = (int) Settings::settingValue('maxnforetries'); - $maxRetries = ($dummy >= 0 ? -($dummy + 1) : self::NFO_UNPROC); - - return sprintf( - 'AND r.nfostatus BETWEEN %d AND %d %s %s', - ($maxRetries < -8 ? -8 : $maxRetries), - self::NFO_UNPROC, - ($maxSize > 0 ? ('AND r.size < '.($maxSize * 1073741824)) : ''), - ($minSize > 0 ? ('AND r.size > '.($minSize * 1048576)) : '') - ); - } - - /** - * Extract URLs from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return array Array of found URLs. - */ - public function extractUrls(string $nfoContent): array - { - $urls = []; - - // Match HTTP/HTTPS URLs - if (preg_match_all('/https?:\/\/[^\s<>"\']+/i', $nfoContent, $matches)) { - $urls = array_merge($urls, $matches[0]); - } - - // Match www URLs without protocol - if (preg_match_all('/(?"\']+/i', $nfoContent, $matches)) { - foreach ($matches[0] as $url) { - $urls[] = 'http://'.$url; - } - } - - return array_unique(array_filter($urls)); - } - - /** - * Extract release group name from NFO content. - * - * Uses multiple detection strategies including: - * - Common presentation phrases - * - Scene-style headers with ASCII borders - * - Greetings sections - * - Footer signatures - * - * @param string $nfoContent The NFO content to parse. - * @return string|null The group name if found, null otherwise. - */ - public function extractGroupName(string $nfoContent): ?string - { - // False positives to filter out - $falsePositives = [ - 'THE', 'AND', 'FOR', 'NFO', 'INFO', 'DVD', 'BLU', 'RAY', 'WEB', 'HDTV', - 'RELEASE', 'GROUP', 'DATE', 'SIZE', 'CODEC', 'VIDEO', 'AUDIO', 'FORMAT', - 'NOTES', 'INSTALL', 'GREETS', 'PRESENTS', 'TEAM', 'SCENE', 'FILE', 'FILES', - ]; - - // Use configured scene group patterns - foreach ($this->_sceneGroupPatterns as $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - $groupName = trim($matches[1]); - if (! in_array(strtoupper($groupName), $falsePositives, true) && strlen($groupName) >= 2 && strlen($groupName) <= 20) { - return $groupName; - } - } - } - - // Additional patterns for group name detection - $additionalPatterns = [ - // "GROUP presents" or "GROUP brings you" - '/\b([A-Z][A-Z0-9]{1,14})\s+(?:presents?|brings?\s+you)/i', - // Common footer format: "--- GROUP ---" - '/[-=]{2,}\s*([A-Z][A-Z0-9]{1,14})\s*[-=]{2,}$/mi', - // Contact section: "irc.server.net #GROUP" - '/irc\.[a-z0-9.-]+\s+#([A-Z][A-Z0-9]{1,14})/i', - // Website: "www.GROUP.com/org/net" - '/www\.([a-z][a-z0-9]{1,14})\.(?:com|org|net|info)/i', - // ASCII art name extraction (common pattern at start) - '/^\s*[^a-zA-Z0-9]*([A-Z][A-Z0-9]{2,14})[^a-zA-Z0-9]*\s*$/mi', - ]; - - foreach ($additionalPatterns as $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - $groupName = trim($matches[1]); - if (! in_array(strtoupper($groupName), $falsePositives, true) && strlen($groupName) >= 2 && strlen($groupName) <= 20) { - return strtoupper($groupName); - } - } - } - - return null; - } - - /** - * Extract release date from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return string|null ISO date string if found, null otherwise. - */ - public function extractReleaseDate(string $nfoContent): ?string - { - $patterns = [ - // DD/MM/YYYY or MM/DD/YYYY - '/(?:date|released?|rls)\s*[:\-]?\s*(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{2,4})/i', - // YYYY-MM-DD - '/(?:date|released?|rls)\s*[:\-]?\s*(\d{4})[\/\-.](\d{1,2})[\/\-.](\d{1,2})/i', - // Month DD, YYYY - '/(?:date|released?|rls)\s*[:\-]?\s*(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+(\d{1,2}),?\s+(\d{4})/i', - ]; - - foreach ($patterns as $index => $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - try { - if ($index === 0) { - // Try both DD/MM and MM/DD formats - $year = strlen($matches[3]) === 2 ? '20'.$matches[3] : $matches[3]; - // Assume DD/MM/YYYY format (more common internationally) - return sprintf('%04d-%02d-%02d', (int) $year, (int) $matches[2], (int) $matches[1]); - } elseif ($index === 1) { - // YYYY-MM-DD - return sprintf('%04d-%02d-%02d', (int) $matches[1], (int) $matches[2], (int) $matches[3]); - } else { - // Month name format - $months = ['jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6, 'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12]; - $month = $months[strtolower(substr($matches[1], 0, 3))] ?? 1; - - return sprintf('%04d-%02d-%02d', (int) $matches[3], $month, (int) $matches[2]); - } - } catch (Throwable) { - continue; - } - } - } - - return null; - } - - /** - * Extract video/audio codec information from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return array{video?: string, audio?: string, resolution?: string} Array with codec info. - */ - public function extractCodecInfo(string $nfoContent): array - { - $result = []; - - // Video codecs - $videoPatterns = [ - '/(?:video|codec)\s*[:\-]?\s*(x264|x265|hevc|h\.?264|h\.?265|xvid|divx|av1|vp9|mpeg[24]?)/i', - '/\b(x264|x265|HEVC|H\.?264|H\.?265|XviD|DivX|AV1|VP9)\b/i', - ]; - foreach ($videoPatterns as $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - $result['video'] = strtoupper(str_replace('.', '', $matches[1])); - break; - } - } - - // Audio codecs - $audioPatterns = [ - '/(?:audio|sound)\s*[:\-]?\s*(aac|ac3|dts(?:-(?:hd|ma|x))?|truehd|atmos|flac|mp3|eac3|dd[+p]?|dolby)/i', - '/\b(AAC|AC3|DTS(?:-(?:HD|MA|X))?|TrueHD|Atmos|FLAC|EAC3|DD[+P]?)\b/i', - ]; - foreach ($audioPatterns as $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - $result['audio'] = strtoupper($matches[1]); - break; - } - } - - // Resolution - $resolutionPatterns = [ - '/(?:resolution|quality)\s*[:\-]?\s*(\d{3,4}[xX×]\d{3,4}|\d{3,4}p|[48]K|UHD|FHD|HD)/i', - '/\b(2160p|1080p|720p|480p|4K|UHD|FHD|HD)\b/i', - '/\b(\d{3,4})\s*[xX×]\s*(\d{3,4})\b/', - ]; - foreach ($resolutionPatterns as $index => $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - if ($index === 2) { - $result['resolution'] = $matches[1].'x'.$matches[2]; - } else { - $result['resolution'] = strtoupper($matches[1]); - } - break; - } - } - - return $result; - } - - /** - * Extract file size information from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return int|null File size in bytes if found, null otherwise. - */ - public function extractFileSize(string $nfoContent): ?int - { - $patterns = [ - '/(?:size|file\s*size)\s*[:\-]?\s*(\d+(?:[.,]\d+)?)\s*(bytes?|[KMGTP]B|[KMGTP]iB)/i', - '/\b(\d+(?:[.,]\d+)?)\s*(GB|GiB|MB|MiB|TB|TiB)\b/i', - ]; - - $multipliers = [ - 'B' => 1, 'BYTE' => 1, 'BYTES' => 1, - 'KB' => 1024, 'KIB' => 1024, - 'MB' => 1024 * 1024, 'MIB' => 1024 * 1024, - 'GB' => 1024 * 1024 * 1024, 'GIB' => 1024 * 1024 * 1024, - 'TB' => 1024 * 1024 * 1024 * 1024, 'TIB' => 1024 * 1024 * 1024 * 1024, - 'PB' => 1024 * 1024 * 1024 * 1024 * 1024, 'PIB' => 1024 * 1024 * 1024 * 1024 * 1024, - ]; - - foreach ($patterns as $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - $value = (float) str_replace(',', '.', $matches[1]); - $unit = strtoupper($matches[2]); - - if (isset($multipliers[$unit])) { - return (int) ($value * $multipliers[$unit]); - } - } - } - - return null; - } - - /** - * Extract all media IDs (IMDB, TVDB, TMDB, etc.) from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return array Array of media IDs with their sources. - */ - public function extractAllMediaIds(string $nfoContent): array - { - $ids = []; - - // IMDB - if (preg_match_all('/imdb\.com\/title\/(tt\d{7,8})/i', $nfoContent, $matches)) { - foreach ($matches[1] as $id) { - $ids[] = ['id' => $id, 'source' => 'imdb']; - } - } - - // TVDB - if (preg_match_all('/thetvdb\.com\/(?:\?tab=series&id=|series\/)(\d{1,8})/i', $nfoContent, $matches)) { - foreach ($matches[1] as $id) { - $ids[] = ['id' => $id, 'source' => 'thetvdb']; - } - } - - // TMDB Movie - if (preg_match_all('/themoviedb\.org\/movie\/(\d{1,8})/i', $nfoContent, $matches)) { - foreach ($matches[1] as $id) { - $ids[] = ['id' => $id, 'source' => 'tmdb_movie']; - } - } - - // TMDB TV - if (preg_match_all('/themoviedb\.org\/tv\/(\d{1,8})/i', $nfoContent, $matches)) { - foreach ($matches[1] as $id) { - $ids[] = ['id' => $id, 'source' => 'tmdb_tv']; - } - } - - // TVMaze - if (preg_match_all('/tvmaze\.com\/shows\/(\d{1,6})/i', $nfoContent, $matches)) { - foreach ($matches[1] as $id) { - $ids[] = ['id' => $id, 'source' => 'tvmaze']; - } - } - - // AniDB - if (preg_match_all('/anidb\.net\/(?:perl-bin\/animedb\.pl\?show=anime&aid=|anime\/)(\d{1,6})/i', $nfoContent, $matches)) { - foreach ($matches[1] as $id) { - $ids[] = ['id' => $id, 'source' => 'anidb']; - } - } - - // MyAnimeList (MAL) - if (preg_match_all('/myanimelist\.net\/anime\/(\d{1,6})/i', $nfoContent, $matches)) { - foreach ($matches[1] as $id) { - $ids[] = ['id' => $id, 'source' => 'mal']; - } - } - - return $ids; - } - - /** - * Parse and extract comprehensive metadata from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return array Associative array with extracted metadata. - */ - public function parseNfoMetadata(string $nfoContent): array - { - return [ - 'urls' => $this->extractUrls($nfoContent), - 'group' => $this->extractGroupName($nfoContent), - 'release_date' => $this->extractReleaseDate($nfoContent), - 'codec_info' => $this->extractCodecInfo($nfoContent), - 'file_size' => $this->extractFileSize($nfoContent), - 'media_ids' => $this->extractAllMediaIds($nfoContent), - 'show_id' => $this->parseShowId($nfoContent), - 'language' => $this->extractLanguage($nfoContent), - 'runtime' => $this->extractRuntime($nfoContent), - 'genre' => $this->extractGenre($nfoContent), - 'software_info' => $this->extractSoftwareInfo($nfoContent), - 'release_title' => $this->extractReleaseTitle($nfoContent), - ]; - } - - /** - * Extract language information from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return array Array of detected languages. - */ - public function extractLanguage(string $nfoContent): array - { - $languages = []; - - // Common language patterns in NFOs - $patterns = [ - '/(?:language|audio|spoken?|dialogue)\s*[:\-]?\s*([A-Za-z]+(?:\s*[,\/&]\s*[A-Za-z]+)*)/i', - '/(?:subs?|subtitles?)\s*[:\-]?\s*([A-Za-z]+(?:\s*[,\/&]\s*[A-Za-z]+)*)/i', - ]; - - // Known language names - $knownLanguages = [ - 'english', 'german', 'french', 'spanish', 'italian', 'dutch', 'portuguese', - 'russian', 'japanese', 'korean', 'chinese', 'mandarin', 'cantonese', - 'swedish', 'norwegian', 'danish', 'finnish', 'polish', 'czech', 'hungarian', - 'turkish', 'arabic', 'hindi', 'thai', 'vietnamese', 'indonesian', 'malay', - 'multi', 'dual', 'english/german', 'eng', 'ger', 'fre', 'spa', 'ita', - ]; - - foreach ($patterns as $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - $langs = preg_split('/[\s,\/&]+/', strtolower($matches[1])); - foreach ($langs as $lang) { - $lang = trim($lang); - if (in_array($lang, $knownLanguages, true) && ! in_array($lang, $languages, true)) { - $languages[] = ucfirst($lang); - } - } - } - } - - return $languages; - } - - /** - * Extract runtime/duration from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return int|null Runtime in minutes, or null if not found. - */ - public function extractRuntime(string $nfoContent): ?int - { - $patterns = [ - // "Runtime: 1h 30m" or "Duration: 90min" - '/(?:runtime|duration|length|playtime)\s*[:\-]?\s*(?:(\d{1,2})\s*h(?:ours?)?\s*)?(\d{1,3})\s*m(?:in(?:utes?)?)?/i', - // "Runtime: 01:30:00" or "1:30:00" - '/(?:runtime|duration|length|playtime)\s*[:\-]?\s*(\d{1,2}):(\d{2})(?::(\d{2}))?/i', - // "90 minutes" standalone - '/\b(\d{2,3})\s*(?:min(?:utes?)?|mins)\b/i', - ]; - - foreach ($patterns as $index => $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - if ($index === 0) { - $hours = ! empty($matches[1]) ? (int) $matches[1] : 0; - $minutes = (int) $matches[2]; - return ($hours * 60) + $minutes; - } elseif ($index === 1) { - $hours = (int) $matches[1]; - $minutes = (int) $matches[2]; - return ($hours * 60) + $minutes; - } else { - return (int) $matches[1]; - } - } - } - - return null; - } - - /** - * Extract genre information from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return array Array of detected genres. - */ - public function extractGenre(string $nfoContent): array - { - $genres = []; - - if (preg_match('/(?:genre|category|type)\s*[:\-]?\s*([^\n\r]+)/i', $nfoContent, $matches)) { - $genreString = $matches[1]; - // Split on common separators - $parts = preg_split('/[\s,\/&|]+/', $genreString); - - // Known valid genres - $validGenres = [ - 'action', 'adventure', 'animation', 'biography', 'comedy', 'crime', - 'documentary', 'drama', 'family', 'fantasy', 'history', 'horror', - 'music', 'musical', 'mystery', 'romance', 'sci-fi', 'scifi', 'sport', - 'thriller', 'war', 'western', 'adult', 'xxx', 'erotic', 'anime', - 'rpg', 'fps', 'strategy', 'simulation', 'puzzle', 'racing', 'sports', - 'rock', 'pop', 'electronic', 'hip-hop', 'rap', 'classical', 'jazz', - ]; - - foreach ($parts as $part) { - $part = strtolower(trim($part)); - if (in_array($part, $validGenres, true) && ! in_array(ucfirst($part), $genres, true)) { - $genres[] = ucfirst($part); - } - } - } - - return $genres; - } - - /** - * Extract software-specific information from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return array Software info including platform, version, protection, etc. - */ - public function extractSoftwareInfo(string $nfoContent): array - { - $info = []; - - // Platform/OS detection - $platformPatterns = [ - '/(?:platform|os|system|requires?)\s*[:\-]?\s*(windows?|linux|mac(?:os)?|unix|android|ios)/i', - ]; - foreach ($platformPatterns as $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - $info['platform'] = ucfirst(strtolower($matches[1])); - break; - } - } - - // Version detection - if (preg_match('/(?:version|ver|v)\s*[:\-]?\s*(\d+(?:\.\d+)*(?:\s*(?:build|b)\s*\d+)?)/i', $nfoContent, $matches)) { - $info['version'] = trim($matches[1]); - } - - // Protection type - $protectionPatterns = [ - '/(?:protection|drm|copy[ -]?protection)\s*[:\-]?\s*([^\n\r]+)/i', - ]; - foreach ($protectionPatterns as $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - $protection = trim($matches[1]); - if (strlen($protection) > 2 && strlen($protection) < 50) { - $info['protection'] = $protection; - } - break; - } - } - - // Crack/Keygen/Serial info - if (preg_match('/\b(cracked|keygen|serial|patch|loader|activator)\b/i', $nfoContent)) { - $info['has_crack'] = true; - } - - return $info; - } - - /** - * Extract release title from NFO content. - * - * @param string $nfoContent The NFO content to parse. - * @return string|null The release title if found. - */ - public function extractReleaseTitle(string $nfoContent): ?string - { - $patterns = [ - // "Title: Movie Name" or "Release: Title.Goes.Here" - '/(?:title|release|name)\s*[:\-]?\s*([^\n\r]{5,100})/i', - // Scene-style title in header - '/(?:^|\n)\s*(?:[\-=*~]{3,}\s*)?([A-Za-z0-9][\w.\-\s]{10,80}?)(?:\s*[\-=*~]{3,})?\s*(?:\n|$)/m', - ]; - - foreach ($patterns as $pattern) { - if (preg_match($pattern, $nfoContent, $matches)) { - $title = trim($matches[1]); - // Filter out common non-title content - if (! preg_match('/^(?:date|size|codec|format|video|audio|language|runtime|genre)\s*:/i', $title) - && strlen($title) >= 5 && strlen($title) <= 100) { - return $title; - } - } - } - - return null; - } - - /** - * Clean and normalize NFO content. - * - * @param string $nfoContent Raw NFO content. - * @return string Cleaned NFO content. - */ - public function cleanNfoContent(string $nfoContent): string - { - // Convert to UTF-8 if needed (CP437 is common for NFOs) - $content = cp437toUTF($nfoContent); - - // Normalize line endings - $content = str_replace(["\r\n", "\r"], "\n", $content); - - // Remove excessive whitespace while preserving NFO art - $lines = explode("\n", $content); - $cleanedLines = []; - $emptyLineCount = 0; - - foreach ($lines as $line) { - if (trim($line) === '') { - $emptyLineCount++; - // Allow max 2 consecutive empty lines - if ($emptyLineCount <= 2) { - $cleanedLines[] = ''; - } - } else { - $emptyLineCount = 0; - $cleanedLines[] = rtrim($line); - } - } - - return implode("\n", $cleanedLines); - } - - /** - * Calculate an NFO quality score based on content analysis. - * - * Scoring factors: - * - Content length (too short or too long penalized) - * - Keyword presence (scene terminology, media info) - * - Media ID presence (IMDB, TVDB, etc.) - * - URL presence - * - Codec information - * - ASCII art detection (scene NFOs often have artistic headers) - * - Structural elements (proper formatting) - * - * @param string $nfoContent The NFO content to analyze. - * @return int Quality score from 0-100. - */ - public function calculateNfoQuality(string $nfoContent): int - { - $score = 50; // Base score - - $length = strlen($nfoContent); - - // Length bonus/penalty - if ($length < 100) { - $score -= 20; - } elseif ($length > 500 && $length < 20000) { - $score += 15; - } elseif ($length >= 20000) { - $score += 5; // Longer NFOs might have too much filler - } - - // Keyword matching - $keywordMatches = 0; - foreach ($this->_nfoKeywords as $keyword) { - if (stripos($nfoContent, $keyword) !== false) { - $keywordMatches++; - } - } - $score += min($keywordMatches * 2, 20); - - // Media ID presence bonus - $mediaIds = $this->extractAllMediaIds($nfoContent); - if (! empty($mediaIds)) { - $score += min(count($mediaIds) * 5, 15); - } - - // URL presence - $urls = $this->extractUrls($nfoContent); - if (! empty($urls)) { - $score += min(count($urls) * 2, 10); - } - - // Codec info presence - $codecInfo = $this->extractCodecInfo($nfoContent); - $score += count(array_filter($codecInfo)) * 3; - - // ASCII art detection (scene NFOs often have decorative borders) - if ($this->hasAsciiArt($nfoContent)) { - $score += 10; - } - - // Structural elements bonus - $structuralScore = $this->analyzeStructure($nfoContent); - $score += $structuralScore; - - // Group name detection bonus - if ($this->extractGroupName($nfoContent) !== null) { - $score += 8; - } - - // Release date detection bonus - if ($this->extractReleaseDate($nfoContent) !== null) { - $score += 5; - } - - // Language info bonus - $languages = $this->extractLanguage($nfoContent); - if (! empty($languages)) { - $score += min(count($languages) * 2, 6); - } - - // Runtime detection bonus - if ($this->extractRuntime($nfoContent) !== null) { - $score += 4; - } - - // Penalty for binary content remnants - if (preg_match_all('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', $nfoContent, $binaryMatches)) { - $score -= min(count($binaryMatches[0]) * 5, 20); - } - - return max(0, min(100, $score)); - } - - /** - * Detect ASCII art in NFO content. - * - * @param string $nfoContent The NFO content to analyze. - * @return bool True if ASCII art is detected. - */ - protected function hasAsciiArt(string $nfoContent): bool - { - // Check for common ASCII art characters in repeated sequences - $asciiArtPatterns = [ - // Decorative borders - '/[-=*~#@]{5,}/', - // Box drawing characters - '/[┌┐└┘├┤┬┴┼│─╔╗╚╝║═]{3,}/', - // Extended ASCII art characters - '/[░▒▓█▄▀■□▪▫]{3,}/', - // Common ASCII art patterns - '/[\/\\|_]{3,}.*[\/\\|_]{3,}/', - // Repeated special chars in artistic patterns - '/(\S)\1{4,}/', - ]; - - foreach ($asciiArtPatterns as $pattern) { - if (preg_match($pattern, $nfoContent)) { - return true; - } - } - - return false; - } - - /** - * Analyze structural elements of NFO content. - * - * @param string $nfoContent The NFO content to analyze. - * @return int Score based on structural quality (0-15). - */ - protected function analyzeStructure(string $nfoContent): int - { - $score = 0; - - // Check for section headers - $sectionPatterns = [ - '/^[ \t]*[-=*]{2,}.*[-=*]{2,}[ \t]*$/m', // Decorative section dividers - '/^[ \t]*\[.*\][ \t]*$/m', // [Section Name] - '/^[ \t]*<.*>[ \t]*$/m', //
- ]; - - foreach ($sectionPatterns as $pattern) { - if (preg_match_all($pattern, $nfoContent, $matches)) { - $score += min(count($matches[0]), 3); - } - } - - // Check for labeled fields (Field: Value format) - if (preg_match_all('/^[ \t]*[A-Za-z][A-Za-z\s]{2,20}\s*[:\.].*$/m', $nfoContent, $matches)) { - $score += min(count($matches[0]) / 2, 5); - } - - // Check for consistent line endings and formatting - $lines = explode("\n", $nfoContent); - $nonEmptyLines = array_filter($lines, fn($line) => trim($line) !== ''); - - if (count($nonEmptyLines) >= 10) { - $score += 2; - } - - return min(15, (int) $score); - } - - /** - * Decompress and retrieve NFO content from a release. - * - * @param int $releaseId The release ID. - * @return string|null The NFO content or null if not found. - */ - public function getNfoContent(int $releaseId): ?string - { - $nfoRecord = ReleaseNfo::getReleaseNfo($releaseId); - - if ($nfoRecord === null || empty($nfoRecord->nfo)) { - return null; - } - - return $nfoRecord->nfo; - } - - /** - * Store NFO content for a release. - * - * @param int $releaseId The release ID. - * @param string $nfoContent The NFO content to store. - * @param bool $compress Whether to compress the content. - * @return bool True on success, false on failure. - */ - public function storeNfoContent(int $releaseId, string $nfoContent, bool $compress = true): bool - { - try { - $data = $compress ? "\x1f\x8b\x08\x00".gzcompress($nfoContent) : $nfoContent; - - ReleaseNfo::updateOrCreate( - ['releases_id' => $releaseId], - ['nfo' => $data] - ); - - Release::whereId($releaseId)->update(['nfostatus' => self::NFO_FOUND]); - - return true; - } catch (Throwable $e) { - Log::error("Failed to store NFO for release {$releaseId}: ".$e->getMessage()); - - return false; - } - } - - /** - * Clear the settings cache. - * - * Useful when settings have been updated and need to be reloaded. - */ - public function clearSettingsCache(): void - { - Cache::forget('nfo_maxnfoprocessed'); - Cache::forget('nfo_maxnforetries'); - Cache::forget('nfo_maxsizetoprocessnfo'); - Cache::forget('nfo_minsizetoprocessnfo'); - } -} diff --git a/app/Console/Commands/PostProcessGuid.php b/app/Console/Commands/PostProcessGuid.php index 6c8dabe3c..1e721a808 100644 --- a/app/Console/Commands/PostProcessGuid.php +++ b/app/Console/Commands/PostProcessGuid.php @@ -6,9 +6,9 @@ namespace App\Console\Commands; use App\Models\Settings; use App\Services\AdditionalProcessing\AdditionalProcessingOrchestrator; +use App\Services\NfoService; use App\Services\PostProcessService; use App\Services\NNTP\NNTPService; -use Blacklight\Nfo; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; @@ -89,7 +89,7 @@ class PostProcessGuid extends Command private function processNfo(string $guid): void { $nntp = $this->getNntp(); - (new Nfo())->processNfoFiles( + (new NfoService())->processNfoFiles( $nntp, '', $guid, diff --git a/app/Console/Commands/ReleasesFixNamesGroup.php b/app/Console/Commands/ReleasesFixNamesGroup.php index 90c86aa1c..3911d6412 100644 --- a/app/Console/Commands/ReleasesFixNamesGroup.php +++ b/app/Console/Commands/ReleasesFixNamesGroup.php @@ -8,10 +8,10 @@ use App\Models\Category; use App\Models\Predb; use App\Models\Release; use App\Services\NameFixing\NameFixingService; +use App\Services\NfoService; use App\Services\Nzb\NzbContentsService; use App\Services\PostProcessService; use App\Services\NNTP\NNTPService; -use Blacklight\Nfo; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; @@ -166,7 +166,7 @@ class ReleasesFixNamesGroup extends Command } // Process NFO - if ((int) $release->nfostatus === Nfo::NFO_FOUND && + if ((int) $release->nfostatus === NfoService::NFO_FOUND && (int) $release->proc_nfo === NameFixingService::PROC_NFO_NONE && ! empty($release->textstring) && ! preg_match('/^=newz\[NZB\]=\w+/', $release->textstring)) { @@ -225,7 +225,7 @@ class ReleasesFixNamesGroup extends Command } $this->warn($errorMessage); } else { - $Nfo = new Nfo(); + $Nfo = new NfoService(); $nzbcontents = app(NzbContentsService::class); $nzbcontents->setNntp($nntp); $nzbcontents->setNfo($Nfo); @@ -367,8 +367,8 @@ class ReleasesFixNamesGroup extends Command LIMIT %s", escapeString($guidChar), NameFixingService::IS_RENAMED_NONE, - Nfo::NFO_UNPROC, - Nfo::NFO_FOUND, + NfoService::NFO_UNPROC, + NfoService::NFO_FOUND, NameFixingService::PROC_NFO_NONE, NameFixingService::PROC_FILES_NONE, NameFixingService::PROC_UID_NONE, diff --git a/app/Console/Commands/UpdatePerGroup.php b/app/Console/Commands/UpdatePerGroup.php index e76aceed5..72e7f3311 100644 --- a/app/Console/Commands/UpdatePerGroup.php +++ b/app/Console/Commands/UpdatePerGroup.php @@ -9,9 +9,9 @@ use App\Models\UsenetGroup; use App\Services\AdditionalProcessing\AdditionalProcessingOrchestrator; use App\Services\Backfill\BackfillService; use App\Services\Binaries\BinariesService; +use App\Services\NfoService; use App\Services\ReleaseProcessingService; use App\Services\NNTP\NNTPService; -use Blacklight\Nfo; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; @@ -80,7 +80,7 @@ class UpdatePerGroup extends Command app(AdditionalProcessingOrchestrator::class)->start($groupId); $this->info("Processing NFO files for group: {$groupMySQL['name']}"); - (new Nfo())->processNfoFiles( + (new NfoService())->processNfoFiles( $nntp, $groupId, '', diff --git a/app/Providers/AdditionalProcessingServiceProvider.php b/app/Providers/AdditionalProcessingServiceProvider.php index f30dab19f..4586f8c93 100644 --- a/app/Providers/AdditionalProcessingServiceProvider.php +++ b/app/Providers/AdditionalProcessingServiceProvider.php @@ -16,7 +16,7 @@ use App\Services\Nzb\NzbService; use App\Services\ReleaseImageService; use App\Services\TempWorkspaceService; use App\Services\NameFixing\NameFixingService; -use Blacklight\Nfo; +use App\Services\NfoService; use App\Services\ReleaseExtraService; use Illuminate\Support\ServiceProvider; @@ -78,7 +78,7 @@ class AdditionalProcessingServiceProvider extends ServiceProvider $app->make(ProcessingConfiguration::class), $app->make(ReleaseExtraService::class), new ReleaseImageService(), - new Nfo(), + new NfoService(), $app->make(NzbService::class), new NameFixingService() ); diff --git a/app/Services/AdditionalProcessing/ReleaseFileManager.php b/app/Services/AdditionalProcessing/ReleaseFileManager.php index 003c8ae2e..0cc168269 100644 --- a/app/Services/AdditionalProcessing/ReleaseFileManager.php +++ b/app/Services/AdditionalProcessing/ReleaseFileManager.php @@ -13,8 +13,8 @@ use App\Services\AdditionalProcessing\Config\ProcessingConfiguration; use App\Services\AdditionalProcessing\DTO\ReleaseProcessingContext; use App\Services\NameFixing\NameFixingService; use App\Services\NameFixing\ReleaseUpdateService; +use App\Services\NfoService; use App\Services\NNTP\NNTPService; -use Blacklight\Nfo; use App\Services\ReleaseExtraService; use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Support\Carbon; @@ -32,7 +32,7 @@ class ReleaseFileManager private readonly ProcessingConfiguration $config, private readonly ReleaseExtraService $releaseExtra, private readonly ReleaseImageService $releaseImage, - private readonly Nfo $nfo, + private readonly NfoService $nfo, private readonly NzbService $nzb, private readonly NameFixingService $nameFixingService ) {} diff --git a/app/Services/ForkingService.php b/app/Services/ForkingService.php index 1af388fc4..2111dafdb 100644 --- a/app/Services/ForkingService.php +++ b/app/Services/ForkingService.php @@ -7,7 +7,6 @@ use App\Services\Runners\BackfillRunner; use App\Services\Runners\BinariesRunner; use App\Services\Runners\PostProcessRunner; use App\Services\Runners\ReleasesRunner; -use Blacklight\Nfo; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Symfony\Component\Process\Process; @@ -40,7 +39,7 @@ class ForkingService $this->minSize = (int) Settings::settingValue('minsizetoprocessnfo'); $this->maxRetries = (int) Settings::settingValue('maxnforetries') >= 0 ? -((int) Settings::settingValue('maxnforetries') + 1) - : Nfo::NFO_UNPROC; + : NfoService::NFO_UNPROC; $this->maxRetries = max($this->maxRetries, -8); // Initialize runners diff --git a/app/Services/NfoProcessor.php b/app/Services/NfoProcessor.php index 9f564b1ea..f885f2bc9 100644 --- a/app/Services/NfoProcessor.php +++ b/app/Services/NfoProcessor.php @@ -4,13 +4,12 @@ namespace App\Services; use App\Models\Settings; use App\Services\NNTP\NNTPService; -use Blacklight\Nfo; class NfoProcessor { - private Nfo $nfo; + private NfoService $nfo; - public function __construct(Nfo $nfo, bool $echooutput) + public function __construct(NfoService $nfo, bool $echooutput) { $this->nfo = $nfo; // echooutput kept for signature parity diff --git a/app/Services/Nzb/NzbContentsService.php b/app/Services/Nzb/NzbContentsService.php index d77630e66..0c0fb8ece 100644 --- a/app/Services/Nzb/NzbContentsService.php +++ b/app/Services/Nzb/NzbContentsService.php @@ -6,9 +6,9 @@ namespace App\Services\Nzb; use App\Models\Release; use App\Models\Settings; +use App\Services\NfoService; use App\Services\NNTP\NNTPService; use App\Services\PostProcessService; -use Blacklight\Nfo; /** * Service for processing NZB contents - extracting NFO files, PAR2 information, @@ -22,7 +22,7 @@ class NzbContentsService protected NNTPService $nntp; - protected Nfo $nfo; + protected NfoService $nfo; protected PostProcessService $postProcessService; @@ -36,14 +36,14 @@ class NzbContentsService ?NzbService $nzbService = null, ?NzbParserService $parserService = null, ?NNTPService $nntp = null, - ?Nfo $nfo = null, + ?NfoService $nfo = null, ?PostProcessService $postProcessService = null ) { $this->echoOutput = (bool) config('nntmux.echocli'); $this->nzbService = $nzbService ?? app(NzbService::class); $this->parserService = $parserService ?? app(NzbParserService::class); $this->nntp = $nntp ?? new NNTPService(); - $this->nfo = $nfo ?? new Nfo(); + $this->nfo = $nfo ?? new NfoService(); $this->postProcessService = $postProcessService ?? app(PostProcessService::class); $this->lookupPar2 = (int) Settings::settingValue('lookuppar2') === 1; $this->alternateNntp = (bool) config('nntmux_nntp.use_alternate_nntp_server'); @@ -71,7 +71,7 @@ class NzbContentsService echo '-'; } // Make sure we set status to NFO_NONFO - Release::query()->where('id', $relID)->update(['nfostatus' => Nfo::NFO_NONFO]); + Release::query()->where('id', $relID)->update(['nfostatus' => NfoService::NFO_NONFO]); return false; } @@ -105,7 +105,7 @@ class NzbContentsService if ($this->echoOutput) { echo '-'; } - Release::query()->where('id', $relID)->update(['nfostatus' => Nfo::NFO_NONFO]); + Release::query()->where('id', $relID)->update(['nfostatus' => NfoService::NFO_NONFO]); return false; } @@ -197,7 +197,7 @@ class NzbContentsService // If NFO check was requested but nothing suitable was found if ($nfoCheck && $nfoMessageId === null) { // Update status to indicate no NFO was found in the NZB structure - Release::query()->where('id', $relID)->update(['nfostatus' => Nfo::NFO_NONFO]); + Release::query()->where('id', $relID)->update(['nfostatus' => NfoService::NFO_NONFO]); return false; } @@ -296,7 +296,7 @@ class NzbContentsService /** * Set NFO handler instance. */ - public function setNfo(Nfo $nfo): void + public function setNfo(NfoService $nfo): void { $this->nfo = $nfo; } diff --git a/app/Services/PostProcessService.php b/app/Services/PostProcessService.php index a3fad9dfb..5c3197c74 100644 --- a/app/Services/PostProcessService.php +++ b/app/Services/PostProcessService.php @@ -7,7 +7,6 @@ namespace App\Services; use App\Services\AdditionalProcessing\AdditionalProcessingOrchestrator; use App\Services\NameFixing\NameFixingService; use App\Services\NNTP\NNTPService; -use Blacklight\Nfo; use dariusiii\rarinfo\Par2Info; use Illuminate\Contracts\Foundation\Application; @@ -29,7 +28,7 @@ final class PostProcessService private readonly NameFixingService $nameFixingService; private readonly Par2Info $par2Info; - private readonly Nfo $nfo; + private readonly NfoService $nfo; private readonly Par2Processor $par2Processor; private readonly TvProcessor $tvProcessor; @@ -45,7 +44,7 @@ final class PostProcessService public function __construct( ?NameFixingService $nameFixingService = null, ?Par2Info $par2Info = null, - ?Nfo $nfo = null, + ?NfoService $nfo = null, ?Par2Processor $par2Processor = null, ?TvProcessor $tvProcessor = null, ?NfoProcessor $nfoProcessor = null, @@ -64,7 +63,7 @@ final class PostProcessService // Core dependencies $this->nameFixingService = $nameFixingService ?? new NameFixingService(); $this->par2Info = $par2Info ?? new Par2Info(); - $this->nfo = $nfo ?? new Nfo(); + $this->nfo = $nfo ?? new NfoService(); // Processors $this->par2Processor = $par2Processor ?? new Par2Processor( diff --git a/app/Services/Runners/PostProcessRunner.php b/app/Services/Runners/PostProcessRunner.php index 3d67fb97c..25442013a 100644 --- a/app/Services/Runners/PostProcessRunner.php +++ b/app/Services/Runners/PostProcessRunner.php @@ -3,6 +3,7 @@ namespace App\Services\Runners; use App\Models\Settings; +use App\Services\NfoService; use Illuminate\Support\Facades\Concurrency; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; @@ -90,7 +91,7 @@ class PostProcessRunner extends BaseRunner return; } - $nfoQuery = \Blacklight\Nfo::NfoQueryString(); + $nfoQuery = NfoService::NfoQueryString(); $checkSql = 'SELECT r.id FROM releases r WHERE 1=1 '.$nfoQuery.' LIMIT 1'; if (count(DB::select($checkSql)) === 0) { diff --git a/app/Services/Tmux/Tmux.php b/app/Services/Tmux/Tmux.php index 614e89c8a..3171a2608 100644 --- a/app/Services/Tmux/Tmux.php +++ b/app/Services/Tmux/Tmux.php @@ -5,7 +5,7 @@ namespace App\Services\Tmux; use App\Models\Category; use App\Models\Settings; use App\Services\NameFixing\NameFixingService; -use Blacklight\Nfo; +use App\Services\NfoService; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; @@ -329,10 +329,10 @@ class Tmux Category::PC_GAMES, Category::XXX_ROOT, Category::XXX_X264, - Nfo::NfoQueryString(), + NfoService::NfoQueryString(), NameFixingService::IS_RENAMED_NONE, - Nfo::NFO_UNPROC, - Nfo::NFO_FOUND, + NfoService::NFO_UNPROC, + NfoService::NFO_FOUND, NameFixingService::PROC_NFO_NONE, NameFixingService::PROC_FILES_NONE, NameFixingService::PROC_PAR2_NONE,