mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-29 01:08:56 +00:00
Update nfo files handling
This commit is contained in:
+70
-10
@@ -158,18 +158,78 @@ class NZBContents
|
||||
}
|
||||
|
||||
// --- NFO Detection ---
|
||||
// Check for explicit NFO files first
|
||||
if ($nfoCheck && ! $foundNFO && isset($firstSegmentId) && preg_match('/\.\b(nfo|diz|info?)\b(?![.-])/i', $subject)) {
|
||||
$nfoMessageId = ['hidden' => false, 'id' => $firstSegmentId];
|
||||
$foundNFO = true; // Found an explicit NFO, prioritize this
|
||||
// Check for explicit NFO files first (with enhanced patterns)
|
||||
if ($nfoCheck && ! $foundNFO && isset($firstSegmentId)) {
|
||||
// Standard NFO extensions
|
||||
if (preg_match('/\.\b(nfo|diz|info?)\b(?![.-])/i', $subject)) {
|
||||
$nfoMessageId = ['hidden' => false, 'id' => $firstSegmentId, 'priority' => 1];
|
||||
$foundNFO = true;
|
||||
}
|
||||
// Alternative NFO naming patterns (group-specific or obfuscated)
|
||||
elseif (preg_match('/(?:^|["\s])(?:file(?:_?id)?|readme|release|info(?:rmation)?|about|desc(?:ription)?|notes?|read\.?me|00-|000-|0-|_-_).*?\.(?:txt|nfo|diz)(?:["\s]|$)/i', $subject)) {
|
||||
$nfoMessageId = ['hidden' => false, 'id' => $firstSegmentId, 'priority' => 2];
|
||||
$foundNFO = true;
|
||||
}
|
||||
}
|
||||
// Check for potential "hidden" NFOs (single segment, common name, not other known types)
|
||||
|
||||
// Check for potential "hidden" NFOs with improved detection
|
||||
// Only consider this if an explicit NFO wasn't found yet
|
||||
elseif ($nfoCheck && ! $foundNFO && ! $hiddenNFO && isset($firstSegmentId) && $segmentCountInFile === 1 && preg_match('/\(1\/1\)$/i', $subject)) {
|
||||
// Simplified exclusion: check if it's NOT likely another common file type based on extension pattern
|
||||
if (! preg_match('/\.(?:exe|com|bat|cmd|scr|dll|zip|rar|[rst]\d{2}|[a-z0-9]{3}|7z|ace|tar|gz|bz2|iso|bin|cue|img|mdf|nrg|dmg|vhd|mp3|flac|ogg|aac|wav|wma|avi|mkv|mp4|mov|wmv|mpg|mpeg|ts|vob|jpg|jpeg|png|gif|bmp|tif|tiff|psd|pdf|doc|docx|xls|xlsx|ppt|pptx|txt|log|xml|html|css|js|php|py|java|c|cpp|h|cs|sql|db|dbf|mdb|accdb|par2?|sfv|md5|sha1|sha256|url|lnk|cfg|ini|inf|sys|tmp|bak|msi|pkg|deb|rpm|apk|ipa)\b/i', $subject)) {
|
||||
$nfoMessageId = ['hidden' => true, 'id' => $firstSegmentId];
|
||||
$hiddenNFO = true; // Found a potential hidden NFO
|
||||
if ($nfoCheck && ! $foundNFO && ! $hiddenNFO && isset($firstSegmentId)) {
|
||||
$isHiddenNfoCandidate = false;
|
||||
|
||||
// Pattern 1: Single segment files with (1/1)
|
||||
if ($segmentCountInFile === 1 && preg_match('/\(1\/1\)$/i', $subject)) {
|
||||
$isHiddenNfoCandidate = true;
|
||||
}
|
||||
|
||||
// Pattern 2: Small segment count (1-2) with NFO-like names but no extension
|
||||
if (! $isHiddenNfoCandidate && $segmentCountInFile <= 2 && preg_match('/(?:^|["\s])(?:nfo|info|readme|release|file_?id|about)(?:["\s]|$)/i', $subject)) {
|
||||
$isHiddenNfoCandidate = true;
|
||||
}
|
||||
|
||||
// Pattern 3: Scene-style NFO naming (group-release.nfo without extension visible)
|
||||
if (! $isHiddenNfoCandidate && $segmentCountInFile === 1 && preg_match('/^[a-z0-9._-]+["\s]*\(1\/1\)/i', $subject)) {
|
||||
// Check for scene-like naming pattern
|
||||
if (preg_match('/^[a-z0-9]+[._-][a-z0-9._-]+["\s]*\(1\/1\)/i', $subject)) {
|
||||
$isHiddenNfoCandidate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern 4: Very small files (NFOs are typically small)
|
||||
// Files described as very small in bytes could be NFOs
|
||||
if (! $isHiddenNfoCandidate && $segmentCountInFile === 1 && preg_match('/yEnc\s*\(\d+\)\s*\[1\/1\]/i', $subject)) {
|
||||
$isHiddenNfoCandidate = true;
|
||||
}
|
||||
|
||||
if ($isHiddenNfoCandidate) {
|
||||
// Enhanced exclusion: check if it's NOT likely another common file type
|
||||
$excludedExtensions = '/\.(?:' .
|
||||
// Executables
|
||||
'exe|com|bat|cmd|scr|dll|msi|pkg|deb|rpm|apk|ipa|app|' .
|
||||
// Archives
|
||||
'zip|rar|[rst]\d{2}|7z|ace|tar|gz|bz2|xz|lzma|cab|iso|bin|cue|img|mdf|nrg|dmg|vhd|' .
|
||||
// Audio
|
||||
'mp3|flac|ogg|aac|wav|wma|m4a|opus|ape|wv|mpc|' .
|
||||
// Video
|
||||
'avi|mkv|mp4|mov|wmv|mpg|mpeg|ts|vob|m2ts|webm|flv|ogv|divx|xvid|' .
|
||||
// Images
|
||||
'jpg|jpeg|png|gif|bmp|tif|tiff|psd|webp|svg|ico|raw|cr2|nef|' .
|
||||
// Documents
|
||||
'pdf|doc|docx|xls|xlsx|ppt|pptx|odt|ods|odp|rtf|epub|mobi|azw|' .
|
||||
// Code
|
||||
'html|htm|css|js|php|py|java|c|cpp|h|cs|sql|json|xml|yml|yaml|' .
|
||||
// Data
|
||||
'db|dbf|mdb|accdb|sqlite|csv|' .
|
||||
// Verification
|
||||
'par2?|sfv|md5|sha1|sha256|sha512|crc|' .
|
||||
// Misc
|
||||
'url|lnk|cfg|ini|inf|sys|tmp|bak|log|srt|sub|idx|ass|ssa|vtt' .
|
||||
')\b/i';
|
||||
|
||||
if (! preg_match($excludedExtensions, $subject)) {
|
||||
$nfoMessageId = ['hidden' => true, 'id' => $firstSegmentId, 'priority' => 10];
|
||||
$hiddenNFO = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+258
-1
@@ -140,7 +140,14 @@ class NameFixer
|
||||
/**
|
||||
* Attempts to fix release names using the NFO.
|
||||
*
|
||||
* Enhanced to use the new Nfo class metadata extraction features for better
|
||||
* release name identification from IMDB, TVDB, TMDB, and other media sources.
|
||||
*
|
||||
* @param int|string $time Time limit for query
|
||||
* @param bool $echo Whether to actually update the database
|
||||
* @param int $cats Category filter (2=misc/hashed, 3=predb)
|
||||
* @param bool $nameStatus Whether to update status columns
|
||||
* @param bool $show Whether to show output
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -149,6 +156,9 @@ class NameFixer
|
||||
$this->_echoStartMessage($time, '.nfo files');
|
||||
$type = 'NFO, ';
|
||||
|
||||
// Initialize the Nfo parser
|
||||
$nfoParser = new Nfo();
|
||||
|
||||
// Only select releases we haven't checked here before
|
||||
$preId = false;
|
||||
if ($cats === 3) {
|
||||
@@ -208,7 +218,18 @@ class NameFixer
|
||||
}
|
||||
|
||||
$this->reset();
|
||||
$this->checkName($releaseRow[0], $echo, $type, $nameStatus, $show, $preId);
|
||||
|
||||
// First, try to extract metadata using the enhanced Nfo parser
|
||||
$nfoMetadata = $nfoParser->parseNfoMetadata($releaseRow[0]->textstring);
|
||||
|
||||
// Try to find a better name using extracted media IDs
|
||||
$betterNameFound = $this->tryNfoMetadataRename($releaseRow[0], $nfoMetadata, $echo, $type, $nameStatus, $show, $preId);
|
||||
|
||||
// If metadata extraction didn't find a name, fall back to traditional checks
|
||||
if (! $betterNameFound) {
|
||||
$this->checkName($releaseRow[0], $echo, $type, $nameStatus, $show, $preId);
|
||||
}
|
||||
|
||||
$this->_echoRenamed($show);
|
||||
}
|
||||
$this->_echoFoundCount($echo, ' NFO\'s');
|
||||
@@ -217,6 +238,242 @@ class NameFixer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to rename a release using extracted NFO metadata.
|
||||
*
|
||||
* Uses media IDs (IMDB, TVDB, TMDB) and codec info from NFO to build
|
||||
* a better release name.
|
||||
*
|
||||
* @param object $release The release object
|
||||
* @param array $nfoMetadata Metadata extracted from NFO by Nfo::parseNfoMetadata()
|
||||
* @param bool $echo Whether to update database
|
||||
* @param string $type The type string for logging
|
||||
* @param bool $nameStatus Whether to update status columns
|
||||
* @param bool $show Whether to show output
|
||||
* @param bool $preId Whether processing for PreDB
|
||||
* @return bool True if a better name was found and applied
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function tryNfoMetadataRename(object $release, array $nfoMetadata, bool $echo, string $type, $nameStatus, bool $show, bool $preId = false): bool
|
||||
{
|
||||
// Skip if already processed
|
||||
if ($this->done || $this->relid === (int) $release->releases_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to get a name from media database IDs
|
||||
$mediaIds = $nfoMetadata['media_ids'] ?? [];
|
||||
$codecInfo = $nfoMetadata['codec_info'] ?? [];
|
||||
$releaseGroup = $nfoMetadata['group'] ?? null;
|
||||
|
||||
// Priority order: IMDB (movies/TV), TMDB, TVDB, TVMaze
|
||||
foreach ($mediaIds as $mediaId) {
|
||||
$newName = $this->getNameFromMediaId($mediaId['source'], $mediaId['id']);
|
||||
if ($newName !== null) {
|
||||
// Enhance the name with codec info if available
|
||||
$enhancedName = $this->enhanceNameWithCodecInfo($newName, $codecInfo, $releaseGroup);
|
||||
|
||||
$this->updateRelease(
|
||||
$release,
|
||||
$enhancedName,
|
||||
'nfoCheck: Media ID ('.$mediaId['source'].': '.$mediaId['id'].')',
|
||||
$echo,
|
||||
$type,
|
||||
$nameStatus,
|
||||
$show
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have codec info but no media ID match, try to enhance existing name patterns
|
||||
if (! empty($codecInfo)) {
|
||||
// Check if there's a recognizable title pattern in the NFO
|
||||
$titleFromNfo = $this->extractTitleFromNfoContent($release->textstring);
|
||||
if ($titleFromNfo !== null) {
|
||||
$enhancedName = $this->enhanceNameWithCodecInfo($titleFromNfo, $codecInfo, $releaseGroup);
|
||||
if (strtolower($enhancedName) !== strtolower($release->searchname)) {
|
||||
$this->updateRelease(
|
||||
$release,
|
||||
$enhancedName,
|
||||
'nfoCheck: NFO Title with Codec Info',
|
||||
$echo,
|
||||
$type,
|
||||
$nameStatus,
|
||||
$show
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a release name from a media database ID.
|
||||
*
|
||||
* Queries local database or external APIs to resolve media IDs to titles.
|
||||
*
|
||||
* @param string $source The source database (imdb, thetvdb, tmdb_movie, tmdb_tv, tvmaze, anidb, mal)
|
||||
* @param string $id The media ID
|
||||
* @return string|null The title if found, null otherwise
|
||||
*/
|
||||
protected function getNameFromMediaId(string $source, string $id): ?string
|
||||
{
|
||||
switch ($source) {
|
||||
case 'imdb':
|
||||
// Check if we have this IMDB ID in our movieinfo table
|
||||
$movie = \App\Models\MovieInfo::where('imdbid', ltrim($id, 't'))->first(['title', 'year']);
|
||||
if ($movie !== null) {
|
||||
return $movie->year > 0 ? "{$movie->title} ({$movie->year})" : $movie->title;
|
||||
}
|
||||
// Also check Video table for TV shows with IMDB ID
|
||||
$video = \App\Models\Video::where('imdb', (int) ltrim($id, 't'))->first(['title']);
|
||||
if ($video !== null) {
|
||||
return $video->title;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'thetvdb':
|
||||
// Check Video table for TVDB ID
|
||||
$video = \App\Models\Video::where('tvdb', (int) $id)->first(['title']);
|
||||
if ($video !== null) {
|
||||
return $video->title;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tmdb_movie':
|
||||
// Check local movie database for TMDB ID
|
||||
$movie = \App\Models\MovieInfo::where('tmdbid', (int) $id)->first(['title', 'year']);
|
||||
if ($movie !== null) {
|
||||
return $movie->year > 0 ? "{$movie->title} ({$movie->year})" : $movie->title;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tmdb_tv':
|
||||
// Check Video table for TMDB ID
|
||||
$video = \App\Models\Video::where('tmdb', (int) $id)->first(['title']);
|
||||
if ($video !== null) {
|
||||
return $video->title;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tvmaze':
|
||||
// Check Video table for TVMaze ID
|
||||
$video = \App\Models\Video::where('tvmaze', (int) $id)->first(['title']);
|
||||
if ($video !== null) {
|
||||
return $video->title;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'anidb':
|
||||
// Check AniDB table - uses Video table with anidb column
|
||||
$video = \App\Models\Video::where('anidb', (int) $id)->first(['title']);
|
||||
if ($video !== null) {
|
||||
return $video->title;
|
||||
}
|
||||
// Also check anidb_titles table
|
||||
$anime = \App\Models\AnidbTitle::where('anidbid', (int) $id)->first(['title']);
|
||||
if ($anime !== null) {
|
||||
return $anime->title;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'trakt':
|
||||
// Check Video table for Trakt ID
|
||||
$video = \App\Models\Video::where('trakt', (int) $id)->first(['title']);
|
||||
if ($video !== null) {
|
||||
return $video->title;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mal':
|
||||
// MyAnimeList - currently no direct support in the database
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance a title with codec/resolution information.
|
||||
*
|
||||
* @param string $title The base title
|
||||
* @param array $codecInfo Codec info from Nfo::extractCodecInfo()
|
||||
* @param string|null $releaseGroup The release group name if found
|
||||
* @return string The enhanced title
|
||||
*/
|
||||
protected function enhanceNameWithCodecInfo(string $title, array $codecInfo, ?string $releaseGroup = null): string
|
||||
{
|
||||
$parts = [$title];
|
||||
|
||||
// Add resolution
|
||||
if (! empty($codecInfo['resolution'])) {
|
||||
$parts[] = $codecInfo['resolution'];
|
||||
}
|
||||
|
||||
// Add video codec
|
||||
if (! empty($codecInfo['video'])) {
|
||||
$parts[] = $codecInfo['video'];
|
||||
}
|
||||
|
||||
// Add audio codec
|
||||
if (! empty($codecInfo['audio'])) {
|
||||
$parts[] = $codecInfo['audio'];
|
||||
}
|
||||
|
||||
// Add release group
|
||||
if ($releaseGroup !== null) {
|
||||
$parts[] = '-'.$releaseGroup;
|
||||
|
||||
return implode('.', array_slice($parts, 0, -1)).$parts[count($parts) - 1];
|
||||
}
|
||||
|
||||
return implode('.', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a recognizable title from NFO content.
|
||||
*
|
||||
* Looks for common title patterns like "Title (Year)" or scene-style names.
|
||||
*
|
||||
* @param string $nfoContent The NFO content
|
||||
* @return string|null The extracted title or null if not found
|
||||
*/
|
||||
protected function extractTitleFromNfoContent(string $nfoContent): ?string
|
||||
{
|
||||
// Look for "Title (Year)" pattern - common in movie NFOs
|
||||
if (preg_match('/^[\s\S]*?([A-Z][A-Za-z0-9\s\.\'\-\:]+(?:\s+\((?:19|20)\d{2}\)))/m', $nfoContent, $matches)) {
|
||||
$title = trim($matches[1]);
|
||||
// Validate it's not too short or too long
|
||||
if (strlen($title) >= 5 && strlen($title) <= 150) {
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for release name patterns (Scene style)
|
||||
if (preg_match('/(?:Release|Rls|Name)\s*[:\-]?\s*([A-Za-z0-9][\w.\-]+(?:[\s._-][\w.\-]+)+)/i', $nfoContent, $matches)) {
|
||||
$title = trim($matches[1]);
|
||||
if (strlen($title) >= 5 && strlen($title) <= 150) {
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for title in common NFO header patterns
|
||||
if (preg_match('/(?:presents|proudly brings)\s*[:\-]?\s*([A-Za-z0-9][\w.\s\-\']+(?:[\s._-][\w.\s\-\']+)*)/i', $nfoContent, $matches)) {
|
||||
$title = trim($matches[1]);
|
||||
if (strlen($title) >= 5 && strlen($title) <= 150) {
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to fix release names using the File name.
|
||||
*
|
||||
|
||||
+1044
-66
File diff suppressed because it is too large
Load Diff
@@ -626,12 +626,22 @@ class AdditionalProcessingOrchestrator
|
||||
continue;
|
||||
}
|
||||
|
||||
// NFO files
|
||||
if ($context->releaseHasNoNFO && preg_match('/(\.(nfo|inf|ofn)|info\.txt)$/i', $filePath)) {
|
||||
if ($this->releaseManager->processNfoFile($filePath, $context, $this->downloadService->getNNTP())) {
|
||||
$this->output->echoNfoFound();
|
||||
// NFO files - enhanced detection with multiple patterns
|
||||
if ($context->releaseHasNoNFO) {
|
||||
// Standard NFO extensions
|
||||
if (preg_match('/(\.(nfo|inf|ofn|diz)|info\.txt)$/i', $filePath)) {
|
||||
if ($this->releaseManager->processNfoFile($filePath, $context, $this->downloadService->getNNTP())) {
|
||||
$this->output->echoNfoFound();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Alternative NFO filenames (file_id.diz, readme.txt, etc.)
|
||||
elseif ($this->releaseManager->isNfoFilename($filePath)) {
|
||||
if ($this->releaseManager->processNfoFile($filePath, $context, $this->downloadService->getNNTP())) {
|
||||
$this->output->echoNfoFound();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Audio files
|
||||
|
||||
@@ -574,8 +574,82 @@ class ArchiveExtractionService
|
||||
*/
|
||||
private function getAllowedExtensions(): array
|
||||
{
|
||||
return ['nfo', 'srt', 'mkv', 'mpeg', 'avi', 'jpg', 'jpeg', 'exe', 'mp4', 'mp3', 'm4a',
|
||||
'flac', 'png', 'epub', 'cbz', 'cbr', 'djvu'];
|
||||
return [
|
||||
// NFO and info files (prioritized for extraction)
|
||||
'nfo', 'diz', 'inf', 'txt',
|
||||
// Subtitles
|
||||
'srt', 'sub', 'idx', 'ass', 'ssa', 'vtt',
|
||||
// Video
|
||||
'mkv', 'mpeg', 'avi', 'mp4', 'm4v', 'mov', 'wmv', 'flv', 'ts', 'vob', 'm2ts', 'webm',
|
||||
// Audio
|
||||
'mp3', 'm4a', 'flac', 'ogg', 'aac', 'wav', 'wma', 'opus', 'ape',
|
||||
// Images
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp',
|
||||
// Documents
|
||||
'epub', 'pdf', 'cbz', 'cbr', 'djvu', 'mobi', 'azw', 'azw3',
|
||||
// Executables (for software releases)
|
||||
'exe', 'msi',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is an NFO or info file.
|
||||
*
|
||||
* @param string $filename The filename to check.
|
||||
* @return bool True if it's an NFO-like file.
|
||||
*/
|
||||
public function isNfoFile(string $filename): bool
|
||||
{
|
||||
$basename = strtolower(basename($filename));
|
||||
|
||||
// Standard NFO extensions
|
||||
if (preg_match('/\.(nfo|diz|inf)$/i', $basename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Common NFO alternative names
|
||||
$nfoNames = [
|
||||
'file_id.diz', 'fileid.diz', 'file-id.diz',
|
||||
'readme.txt', 'readme.1st', 'read.me', 'readmenow.txt',
|
||||
'info.txt', 'information.txt', 'about.txt', 'notes.txt',
|
||||
'release.txt', 'release.nfo',
|
||||
];
|
||||
|
||||
if (in_array($basename, $nfoNames, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scene-style NFO naming: 00-groupname.nfo, group-release.nfo
|
||||
if (preg_match('/^(?:00?-[a-z0-9_-]+|[a-z0-9]+-[a-z0-9._-]+)\.(?:nfo|txt)$/i', $basename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort files to prioritize NFO files for processing.
|
||||
*
|
||||
* @param array $files Array of file info arrays.
|
||||
* @return array Sorted array with NFO files first.
|
||||
*/
|
||||
public function sortFilesWithNfoPriority(array $files): array
|
||||
{
|
||||
usort($files, function ($a, $b) {
|
||||
$aIsNfo = $this->isNfoFile($a['name'] ?? '');
|
||||
$bIsNfo = $this->isNfoFile($b['name'] ?? '');
|
||||
|
||||
if ($aIsNfo && ! $bIsNfo) {
|
||||
return -1;
|
||||
}
|
||||
if (! $aIsNfo && $bIsNfo) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -345,7 +345,12 @@ class ReleaseFileManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Process NFO file.
|
||||
* Process NFO file with enhanced detection capabilities.
|
||||
*
|
||||
* Supports multiple NFO naming conventions:
|
||||
* - Standard: .nfo, .diz, .info
|
||||
* - Alternative: file_id.diz, readme.txt, info.txt
|
||||
* - Scene-style: 00-groupname.nfo, groupname-releasename.nfo
|
||||
*/
|
||||
public function processNfoFile(
|
||||
string $fileLocation,
|
||||
@@ -354,6 +359,10 @@ class ReleaseFileManager
|
||||
): bool {
|
||||
try {
|
||||
$data = File::get($fileLocation);
|
||||
|
||||
// Try to detect and convert encoding
|
||||
$data = $this->normalizeNfoEncoding($data);
|
||||
|
||||
if ($this->nfo->isNFO($data, $context->release->guid)
|
||||
&& $this->nfo->addAlternateNfo($data, $context->release, $nntp)
|
||||
) {
|
||||
@@ -367,6 +376,84 @@ class ReleaseFileManager
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a filename looks like an NFO file.
|
||||
*
|
||||
* @param string $filename The filename to check.
|
||||
* @return bool True if the filename matches NFO patterns.
|
||||
*/
|
||||
public function isNfoFilename(string $filename): bool
|
||||
{
|
||||
// Standard NFO extensions
|
||||
if (preg_match('/\.(?:nfo|diz|info?)$/i', $filename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Alternative NFO filenames
|
||||
$nfoPatterns = [
|
||||
'/^(?:file[_-]?id|readme|release|info(?:rmation)?|about|notes?)\.(?:txt|diz)$/i',
|
||||
'/^00-[a-z0-9_-]+\.nfo$/i', // Scene: 00-group.nfo
|
||||
'/^0+-[a-z0-9_-]+\.nfo$/i', // Scene variations
|
||||
'/^[a-z0-9_-]+-[a-z0-9_.-]+\.nfo$/i', // Scene: group-release.nfo
|
||||
'/info\.txt$/i', // info.txt (common alternative)
|
||||
];
|
||||
|
||||
$basename = basename($filename);
|
||||
foreach ($nfoPatterns as $pattern) {
|
||||
if (preg_match($pattern, $basename)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize NFO encoding to UTF-8.
|
||||
*
|
||||
* NFO files often use CP437 (DOS) encoding for ASCII art.
|
||||
* This method attempts to detect and convert various encodings.
|
||||
*
|
||||
* @param string $data Raw NFO data.
|
||||
* @return string UTF-8 encoded NFO data.
|
||||
*/
|
||||
protected function normalizeNfoEncoding(string $data): string
|
||||
{
|
||||
// Check for UTF-8 BOM and remove it
|
||||
if (str_starts_with($data, "\xEF\xBB\xBF")) {
|
||||
$data = substr($data, 3);
|
||||
}
|
||||
|
||||
// Check for UTF-16 BOM
|
||||
if (str_starts_with($data, "\xFF\xFE")) {
|
||||
// UTF-16 LE
|
||||
$data = mb_convert_encoding(substr($data, 2), 'UTF-8', 'UTF-16LE');
|
||||
} elseif (str_starts_with($data, "\xFE\xFF")) {
|
||||
// UTF-16 BE
|
||||
$data = mb_convert_encoding(substr($data, 2), 'UTF-8', 'UTF-16BE');
|
||||
}
|
||||
|
||||
// If already valid UTF-8, return as-is
|
||||
if (mb_check_encoding($data, 'UTF-8')) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Try CP437 (DOS encoding - common for scene NFOs with ASCII art)
|
||||
// Use the utility function if available
|
||||
if (class_exists('\Blacklight\utility\Utility') && method_exists('\Blacklight\utility\Utility', 'cp437toUTF')) {
|
||||
return \Blacklight\utility\Utility::cp437toUTF($data);
|
||||
}
|
||||
|
||||
// Fallback: try ISO-8859-1 (Latin-1)
|
||||
$converted = @mb_convert_encoding($data, 'UTF-8', 'ISO-8859-1');
|
||||
if ($converted !== false) {
|
||||
return $converted;
|
||||
}
|
||||
|
||||
// Last resort: force UTF-8 with error handling
|
||||
return mb_convert_encoding($data, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle release name extraction from RAR file content.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user