Files
newznab-tmux-NNTmux/app/Services/NameFixing/Extractors/FileNameExtractor.php
T

235 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\NameFixing\Extractors;
use App\Services\NameFixing\Data\NameFixResult;
use App\Services\NameFixing\FileNameCleaner;
/**
* Extracts release names from file names and paths.
*
* Handles various filename patterns for scene releases, streaming services,
* video files, music, games, ebooks, and more.
*/
class FileNameExtractor
{
/**
* PreDB regex pattern for scene release names.
*/
public const PREDB_REGEX = '/([\w.\'()\[\]-]+(?:[\s._-]+[\w.\'()\[\]-]+)+[-.][\w]+)/ui';
protected FileNameCleaner $cleaner;
public function __construct(?FileNameCleaner $cleaner = null)
{
$this->cleaner = $cleaner ?? new FileNameCleaner;
}
/**
* Extract a release name from a filename.
*/
public function extractFromFile(string $filename): ?NameFixResult
{
$result = [];
$cleanedFilename = $this->cleaner->cleanForTitleMatch($filename);
// Try each pattern in order of specificity
if (str_contains($filename, '__NZBSPLIT__')) {
$unwrapped = $this->cleaner->extractNzbSplitName($filename);
if ($unwrapped !== null && $this->cleaner->isPlausibleReleaseTitle($unwrapped)) {
return NameFixResult::fromMatch($unwrapped, 'NZBSPLIT wrapper', 'File');
}
return null;
}
// Scene TV release with group suffix
if (preg_match('/^(.+?(x264|x265|HEVC|XviD|H\.?264|H\.?265)\-[A-Za-z0-9]+)\\\\/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'Scene release with group', 'File');
}
// TVP group format
if (preg_match('/^(.+?(x264|XviD)\-TVP)\\\\/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'TVP', 'File');
}
// Generic TV - SxxExx format with quality/source info
if (preg_match('/^(\\\\|\/)?(.+(\\\\|\/))*(.+?S\d{1,3}[.-_ ]?E\d{1,3}(?:[.-_ ]?E\d{1,3})?[.-_ ].+?(?:720p|1080p|2160p|4K|HDTV|WEB-?DL|WEB-?RIP|BluRay|AMZN|HMAX|NF|DSNP).+?)\.(.+)$/iu', $filename, $result)) {
return NameFixResult::fromMatch($result[4], 'TV SxxExx with quality', 'File');
}
// Generic TV - any SxxExx format
if (preg_match('/^(\\\\|\/)?(.+(\\\\|\/))*(.+?S\d{1,3}[.-_ ]?[ED]\d{1,3}.+)\.(.+)$/iu', $filename, $result)) {
return NameFixResult::fromMatch($result[4], 'Generic TV', 'File');
}
// 4K/UHD Movies - modern formats
if (preg_match('/^(\\\\|\/)?(.+(\\\\|\/))*(.+?[\.\-_ ](19|20)\d\d[\.\-_ ].+?(2160p|4K|UHD).+?(HDR10?\+?|DV|Dolby[\.\-_ ]?Vision)?.+?(HEVC|x265|H\.?265).+?)\.(.+)$/iu', $filename, $result)) {
return NameFixResult::fromMatch($result[4], '4K/UHD Movie', 'File');
}
// HD Movies with modern codecs
if (preg_match('/^(\\\\|\/)?(.+(\\\\|\/))*(.+?[\.\-_ ](19|20)\d\d[\.\-_ ].+?(720p|1080p).+?(BluRay|WEB-?DL|WEB-?RIP|BDRip|REMUX).+?(x264|x265|HEVC|H\.?264|H\.?265|AVC).+?)\.(.+)$/iu', $filename, $result)) {
return NameFixResult::fromMatch($result[4], 'HD Movie modern codec', 'File');
}
// Standard HD Movies
if (preg_match('/^(\\\\|\/)?(.+(\\\\|\/))*(.+?([\.\-_ ]\d{4}[\.\-_ ].+?(BDRip|bluray|DVDRip|XVID|WEB-?DL|HDTV)).+)\.(.+)$/iu', $filename, $result)) {
return NameFixResult::fromMatch($result[4], 'Generic movie 1', 'File');
}
if (preg_match('/^([a-z0-9\.\-_]+(19|20)\d\d[a-z0-9\.\-_]+[\.\-_ ](720p|1080p|2160p|4K|BDRip|bluray|DVDRip|x264|x265|XviD|HEVC)[a-z0-9\.\-_]+)\.[a-z]{2,}$/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'Generic movie 2', 'File');
}
// Streaming service releases
if (preg_match('/^([A-Za-z0-9\.\-_]+[\.\-_ ](AMZN|ATVP|DSNP|HMAX|HULU|iT|NF|PMTP|PCOK|ROKU|STAN|TVNZ|VUDU)[\.\-_ ].+?(WEB-?DL|WEB-?RIP).+?)\.(.+)$/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'Streaming service release', 'File');
}
// Music releases
if (preg_match('/(.+?([\.\-_ ](CD|FM)|[\.\-_ ]\dCD|CDR|FLAC|SAT|WEB).+?(19|20)\d\d.+?)\\\\.+/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'Generic music', 'File');
}
if (preg_match('/^(.+?(19|20)\d\d\-([a-z0-9]{3}|[a-z]{2,}|C4))\\\\/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'music groups', 'File');
}
// FLAC music releases
if (preg_match('/^(.+?[\.\-_ ](FLAC|MP3|AAC|OGG)[\.\-_ ].+?[\.\-_ ]\d{4}[\.\-_ ].+?\-[A-Za-z0-9]+)[\\\\\/.]/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'Music with codec', 'File');
}
// Movie with year in parentheses - AVI format
if (preg_match('/.+\\\\(.+\((19|20)\d\d\)\.avi)$/i', $filename, $result)) {
$newName = str_replace('.avi', ' DVDRip XVID NoGroup', $result[1]);
return NameFixResult::fromMatch($newName, 'Movie (year) avi', 'File');
}
// Movie with year in parentheses - ISO format
if (preg_match('/.+\\\\(.+\((19|20)\d\d\)\.iso)$/i', $filename, $result)) {
$newName = str_replace('.iso', ' DVD NoGroup', $result[1]);
return NameFixResult::fromMatch($newName, 'Movie (year) iso', 'File');
}
// Movie with year in parentheses - MKV format
if (preg_match('/.+\\\\(.+\((19|20)\d\d\)\.(mkv|mp4|m4v))$/i', $filename, $result)) {
$newName = preg_replace('/\.(mkv|mp4|m4v)$/i', ' BDRip x264 NoGroup', $result[1]);
return NameFixResult::fromMatch($newName, 'Movie (year) mkv/mp4', 'File');
}
// RAR file contents - look for release name in RAR path
if (preg_match('/^([A-Za-z0-9][\w.\-]+(?:[\.\-_ ][\w.\-]+)+)[\\\\\\/](?:CD\d|Disc\d|DVD\d|Subs?)?[\\\\\\/]?.+\.(rar|r\d{2,3}|zip|7z)$/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'RAR archive path', 'File');
}
// Scene release in RAR
if (preg_match('/^([A-Za-z0-9][\w.\-]+\-[A-Za-z0-9]+)[\\\\\\/].+\.(rar|r\d{2,3})$/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'Scene RAR release', 'File');
}
// XXX Imagesets
if (preg_match('/^(.+?IMAGESET.+?)\\\\.+/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'XXX Imagesets', 'File');
}
// VIDEOOT releases
if (preg_match('/^VIDEOOT-[A-Z0-9]+\\\\([\w!.,& ()\[\]\'\`-]{8,}?\b.?)([\-_](proof|sample|thumbs?))*(\.part\d*(\.rar)?|\.rar|\.7z)?(\d{1,3}\.rev|\.vol.+?|\.mp4)/', $filename, $result)) {
return NameFixResult::fromMatch($result[1].' XXX DVDRIP XviD-VIDEOOT', 'XXX XviD VIDEOOT', 'File');
}
// XXX SDPORN
if (preg_match('/^.+?SDPORN/i', $filename, $result)) {
return NameFixResult::fromMatch($result[0], 'XXX SDPORN', 'File');
}
// R&C releases
if (preg_match('/\w[\-\w.\',;& ]+1080i[._ -]DD5[._ -]1[._ -]MPEG2-R&C(?=\.ts)$/i', $filename, $result)) {
$newResult = str_replace('MPEG2', 'MPEG2.HDTV', $result[0]);
return NameFixResult::fromMatch($newResult, 'R&C', 'File');
}
// NhaNc3 releases
if (preg_match('/\w[\-\w.\',;& ]+((s\d{1,2}[._ -]?[bde]\d{1,2})|\d{1,2}x\d{2}|ep[._ -]?\d{2})[._ -](480|720|1080)[ip][._ -](BD(-?(25|50|RIP))?|Blu-?Ray ?(3D)?|BRRIP|CAM(RIP)?|DBrip|DTV|DVD\-?(5|9|(R(IP)?|scr(eener)?))?|[HPS]D?(RIP|TV(RIP)?)?|NTSC|PAL|R5|Ripped |S?VCD|scr(eener)?|SAT(RIP)?|TS|VHS(RIP)?|VOD|WEB-DL)[._ -]nSD[._ -](DivX|[HX][._ -]?264|MPEG2|XviD(HD)?|WMV)[._ -]NhaNC3[\-\w.\',;& ]+\w/i', $filename, $result)) {
return NameFixResult::fromMatch($result[0], 'NhaNc3', 'File');
}
// TVP releases (alternate pattern)
if (preg_match('/\wtvp-[\w.\',;]+((s\d{1,2}[._ -]?[bde]\d{1,2})|\d{1,2}x\d{2}|ep[._ -]?\d{2})[._ -](720p|1080p|xvid)(?=\.(avi|mkv))$/i', $filename, $result)) {
$newResult = str_replace('720p', '720p.HDTV.X264', $result[0]);
$newResult = str_replace('1080p', '1080p.Bluray.X264', $newResult);
$newResult = str_replace('xvid', 'XVID.DVDrip', $newResult);
return NameFixResult::fromMatch($newResult, 'tvp', 'File');
}
// LOL releases
if (preg_match('/\w[\-\w.\',;& ]+\d{3,4}\.hdtv-lol\.(avi|mp4|mkv|ts|nfo|nzb)/i', $filename, $result)) {
return NameFixResult::fromMatch($result[0], 'Title.211.hdtv-lol.extension', 'File');
}
// DL releases
if (preg_match('/\w[\-\w.\',;& ]+-S\d{1,2}[EX]\d{1,2}-XVID-DL\.avi/i', $filename, $result)) {
return NameFixResult::fromMatch($result[0], 'Title-SxxExx-XVID-DL.avi', 'File');
}
// Title - SxxExx - Episode title format
if (preg_match('/\S.*[\w.\-\',;]+\s\-\ss\d{2}[ex]\d{2}\s\-\s[\w.\-\',;].+\./i', $filename, $result)) {
return NameFixResult::fromMatch($result[0], 'Title - SxxExx - Eptitle', 'File');
}
// Nintendo DS
if (preg_match('/\w.+?\)\.nds$/i', $filename, $result)) {
return NameFixResult::fromMatch($result[0], ').nds Nintendo DS', 'File');
}
// Nintendo 3DS
if (preg_match('/3DS_\d{4}.+\d{4} - (.+?)\.3ds/i', $filename, $result)) {
return NameFixResult::fromMatch('3DS '.$result[1], '.3ds Nintendo 3DS', 'File');
}
// Nintendo Switch
if (preg_match('/^(.+?)\[[\w]+\]\.(?:nsp|xci|nsz)$/i', $filename, $result)) {
return NameFixResult::fromMatch(trim($result[1]).' Switch', 'Nintendo Switch', 'File');
}
// PlayStation/Xbox game releases
if (preg_match('/^(.+?[\.\-_ ](PS[345P]|PSV|XBOX360|XBOXONE|NSW)[\.\-_ ].+?\-[A-Za-z0-9]+)[\\\\\/.]/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'Console game release', 'File');
}
// EBooks
if (preg_match('/\w.+?\.(epub|mobi|azw3?|opf|fb2|prc|djvu|cb[rz])/i', $filename, $result)) {
$newResult = str_replace('.'.$result[1], ' ('.$result[1].')', $result[0]);
return NameFixResult::fromMatch($newResult, 'EBook', 'File');
}
// Audiobooks
if (preg_match('/^(.+?[\.\-_ ]Audiobook[\.\-_ ].+?)[\\\\\/.]/i', $filename, $result)) {
return NameFixResult::fromMatch($result[1], 'Audiobook', 'File');
}
// Scene release from cleaned filename
if (preg_match('/^([A-Za-z0-9][\w.\-]+\-[A-Za-z0-9]{2,15})$/i', $cleanedFilename, $result) && preg_match(self::PREDB_REGEX, $cleanedFilename)) {
return NameFixResult::fromMatch($result[1], 'Cleaned scene name', 'File');
}
// Folder name fallback
if (preg_match('/\w+[\-\w.\',;& ]+$/i', $filename, $result) && preg_match(self::PREDB_REGEX, $filename)) {
return NameFixResult::fromMatch($result[0], 'Folder name', 'File');
}
return null;
}
}