Improve jpg samples fetching

This commit is contained in:
DariusIII
2026-01-09 13:37:52 +01:00
parent 9025d610cf
commit 478d25e7a2
2 changed files with 370 additions and 0 deletions
@@ -3,6 +3,7 @@
namespace App\Services\AdditionalProcessing;
use App\Models\Release;
use App\Models\ReleaseFile;
use App\Models\UsenetGroup;
use App\Services\AdditionalProcessing\Config\ProcessingConfiguration;
use App\Services\AdditionalProcessing\DTO\ReleaseProcessingContext;
@@ -229,6 +230,11 @@ class AdditionalProcessingOrchestrator
$this->processExtractedFiles($context);
}
}
// If still no JPG sample, try to fetch JPG from release_files entries
if (! $context->foundJPGSample && $this->config->processJPGSample) {
$this->processJpgFromReleaseFiles($context);
}
}
// Finalize
@@ -562,9 +568,171 @@ class AdditionalProcessingOrchestrator
$this->releaseManager->updateSearchIndex($context->release->id);
}
// Try to extract and process JPG files from the archive file list for sample/preview
if (! $context->foundJPGSample && $this->config->processJPGSample) {
$this->processJpgFromArchiveFileList($compressedData, $result['files'], $context);
}
return $context->totalFileInfo > 0;
}
/**
* Find and process JPG files from archive file list to create sample/preview.
*/
private function processJpgFromArchiveFileList(
string $compressedData,
array $files,
ReleaseProcessingContext $context
): void {
// Find JPG files in the archive file list
$jpgFiles = [];
foreach ($files as $file) {
$name = $file['name'] ?? '';
if (preg_match('/\.jpe?g$/i', $name)) {
$jpgFiles[] = $file;
}
}
if (empty($jpgFiles)) {
return;
}
// Sort by size (prefer larger images, likely better quality) - limit to first 3
usort($jpgFiles, fn ($a, $b) => ($b['size'] ?? 0) <=> ($a['size'] ?? 0));
$jpgFiles = array_slice($jpgFiles, 0, 3);
foreach ($jpgFiles as $jpgFile) {
$jpgFilename = $jpgFile['name'];
// Try to extract this specific JPG from the archive
$jpgData = $this->archiveService->extractSpecificFile(
$compressedData,
$jpgFilename,
$context->tmpPath
);
if ($jpgData === null || empty($jpgData)) {
continue;
}
// Save to temp file and validate it's actually a JPEG
$tempJpgPath = $context->tmpPath.'extracted_'.uniqid('', true).'.jpg';
File::put($tempJpgPath, $jpgData);
if ($this->mediaService->isJpegData($tempJpgPath)) {
if ($this->mediaService->getJPGSample($tempJpgPath, $context->release->guid)) {
$context->foundJPGSample = true;
$this->output->echoJpgSaved();
File::delete($tempJpgPath);
break;
}
}
File::delete($tempJpgPath);
}
}
/**
* Process JPG files from existing release_files entries.
* This fetches the archive from NZB and extracts JPG files that were previously listed.
*/
private function processJpgFromReleaseFiles(ReleaseProcessingContext $context): void
{
// Get JPG files from release_files table for this release
$jpgFiles = ReleaseFile::where('releases_id', $context->release->id)
->where(function ($query) {
$query->where('name', 'like', '%.jpg')
->orWhere('name', 'like', '%.jpeg');
})
->orderByDesc('size')
->limit(3)
->get();
if ($jpgFiles->isEmpty()) {
return;
}
// We need to download a compressed file from the NZB to extract the JPG
if (empty($context->nzbContents)) {
return;
}
// Find compressed files in NZB
foreach ($context->nzbContents as $nzbFile) {
if ($context->foundJPGSample) {
break;
}
$title = $nzbFile['title'] ?? '';
if (! preg_match(
'/(\\.(part0*1|rar|zip|7z))(\\s*\\.rar)*($|[ ")]|-])|"[a-f0-9]{32}\\.[1-9]\\d{1,2}".*\\(\\d+\\/\\d{2,}\\)$/i',
$title
)) {
continue;
}
// Get message IDs for first few segments
$segments = $nzbFile['segments'] ?? [];
if (empty($segments)) {
continue;
}
$messageIDs = [];
$segCount = min(count($segments), $this->config->maximumRarSegments);
for ($i = 0; $i < $segCount; $i++) {
$messageIDs[] = (string) $segments[$i];
}
if (empty($messageIDs)) {
continue;
}
// Download the compressed file
$result = $this->downloadService->downloadCompressedFile(
$messageIDs,
$context->releaseGroupName,
$context->release->id,
$title
);
if (! $result['success'] || empty($result['data'])) {
continue;
}
// Try to extract each JPG file from the archive
foreach ($jpgFiles as $jpgFile) {
if ($context->foundJPGSample) {
break;
}
$jpgData = $this->archiveService->extractSpecificFile(
$result['data'],
$jpgFile->name,
$context->tmpPath
);
if ($jpgData === null || empty($jpgData)) {
continue;
}
// Save to temp file and validate
$tempJpgPath = $context->tmpPath.'release_file_'.uniqid('', true).'.jpg';
File::put($tempJpgPath, $jpgData);
if ($this->mediaService->isJpegData($tempJpgPath)) {
if ($this->mediaService->getJPGSample($tempJpgPath, $context->release->guid)) {
$context->foundJPGSample = true;
$this->output->echoJpgSaved();
File::delete($tempJpgPath);
break 2; // Exit both loops
}
}
File::delete($tempJpgPath);
}
}
}
/**
* Process extracted files from archives.
*/
@@ -783,6 +783,208 @@ class ArchiveExtractionService
return $this->archiveInfo;
}
/**
* Extract a specific file from archive data by filename.
*
* @param string $compressedData The raw archive data
* @param string $filename The filename to extract (exact match)
* @param string $tmpPath Temporary directory path
* @return string|null The extracted file content, or null if extraction failed
*/
public function extractSpecificFile(string $compressedData, string $filename, string $tmpPath): ?string
{
// Try using ArchiveInfo's built-in extraction
if ($this->archiveInfo->setData($compressedData, true)) {
try {
$extracted = $this->archiveInfo->getFileData($filename);
if ($extracted !== false && ! empty($extracted)) {
return $extracted;
}
} catch (\Throwable $e) {
if ($this->config->debugMode) {
Log::debug('ArchiveInfo getFileData failed: '.$e->getMessage());
}
}
}
// Fallback: use external tools to extract to temp directory
$archiveType = $this->detectArchiveType($compressedData);
if ($archiveType === '7z' && $this->config->sevenZipPath) {
return $this->extractFileVia7zip($compressedData, $filename, $tmpPath);
}
// Try using unrar for RAR files
if ($this->config->unrarPath) {
$extracted = $this->extractFileViaUnrar($compressedData, $filename, $tmpPath);
if ($extracted !== null) {
return $extracted;
}
}
// Try using unzip for ZIP files
if ($this->config->unzipPath) {
$extracted = $this->extractFileViaUnzip($compressedData, $filename, $tmpPath);
if ($extracted !== null) {
return $extracted;
}
}
return null;
}
/**
* Extract a specific file using 7zip.
*/
private function extractFileVia7zip(string $compressedData, string $filename, string $tmpPath): ?string
{
try {
$extractDir = $tmpPath.'extract_'.uniqid('', true).'/';
if (! File::isDirectory($extractDir)) {
File::makeDirectory($extractDir, 0777, true, true);
}
$archiveFile = $tmpPath.'archive_'.uniqid('', true).'.7z';
File::put($archiveFile, $compressedData);
// Extract specific file
$cmd = [$this->config->sevenZipPath, 'e', '-y', '-bd', '-o'.$extractDir, $archiveFile, $filename];
$exitCode = 0;
$stdout = null;
$stderr = null;
$this->execCommand($cmd, $exitCode, $stdout, $stderr);
File::delete($archiveFile);
// Look for extracted file
$extractedPath = $extractDir.basename($filename);
if (File::isFile($extractedPath)) {
$content = File::get($extractedPath);
File::deleteDirectory($extractDir);
return $content;
}
// Try to find it with glob (in case path differs)
$files = File::allFiles($extractDir);
foreach ($files as $file) {
if (strtolower($file->getFilename()) === strtolower(basename($filename))) {
$content = File::get($file->getPathname());
File::deleteDirectory($extractDir);
return $content;
}
}
File::deleteDirectory($extractDir);
} catch (\Throwable $e) {
if ($this->config->debugMode) {
Log::debug('7zip extraction failed: '.$e->getMessage());
}
}
return null;
}
/**
* Extract a specific file using unrar.
*/
private function extractFileViaUnrar(string $compressedData, string $filename, string $tmpPath): ?string
{
try {
$extractDir = $tmpPath.'extract_'.uniqid('', true).'/';
if (! File::isDirectory($extractDir)) {
File::makeDirectory($extractDir, 0777, true, true);
}
$archiveFile = $tmpPath.'archive_'.uniqid('', true).'.rar';
File::put($archiveFile, $compressedData);
// Extract specific file using unrar
$killString = $this->config->getKillString();
runCmd($killString.$this->config->unrarPath.'" e -y -c- -inul -p- "'.$archiveFile.'" "'.$filename.'" "'.$extractDir.'"');
File::delete($archiveFile);
// Look for extracted file
$extractedPath = $extractDir.basename($filename);
if (File::isFile($extractedPath)) {
$content = File::get($extractedPath);
File::deleteDirectory($extractDir);
return $content;
}
// Try to find it with glob
$files = File::allFiles($extractDir);
foreach ($files as $file) {
if (strtolower($file->getFilename()) === strtolower(basename($filename))) {
$content = File::get($file->getPathname());
File::deleteDirectory($extractDir);
return $content;
}
}
File::deleteDirectory($extractDir);
} catch (\Throwable $e) {
if ($this->config->debugMode) {
Log::debug('Unrar extraction failed: '.$e->getMessage());
}
}
return null;
}
/**
* Extract a specific file using unzip.
*/
private function extractFileViaUnzip(string $compressedData, string $filename, string $tmpPath): ?string
{
try {
$extractDir = $tmpPath.'extract_'.uniqid('', true).'/';
if (! File::isDirectory($extractDir)) {
File::makeDirectory($extractDir, 0777, true, true);
}
$archiveFile = $tmpPath.'archive_'.uniqid('', true).'.zip';
File::put($archiveFile, $compressedData);
// Extract specific file using unzip
runCmd($this->config->unzipPath.' -j "'.$archiveFile.'" "'.$filename.'" -d "'.$extractDir.'"');
File::delete($archiveFile);
// Look for extracted file
$extractedPath = $extractDir.basename($filename);
if (File::isFile($extractedPath)) {
$content = File::get($extractedPath);
File::deleteDirectory($extractDir);
return $content;
}
// Try to find it with glob
$files = File::allFiles($extractDir);
foreach ($files as $file) {
if (strtolower($file->getFilename()) === strtolower(basename($filename))) {
$content = File::get($file->getPathname());
File::deleteDirectory($extractDir);
return $content;
}
}
File::deleteDirectory($extractDir);
} catch (\Throwable $e) {
if ($this->config->debugMode) {
Log::debug('Unzip extraction failed: '.$e->getMessage());
}
}
return null;
}
/**
* Execute a command with output capture.
*/