mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 22:01:33 +00:00
Use first party image support
This commit is contained in:
@@ -218,6 +218,8 @@ TURNSTILE_ENABLED=false
|
||||
|
||||
ITEMS_PER_PAGE=50
|
||||
ITEMS_PER_COVER_PAGE=25
|
||||
# Image processing driver. Use gd when the Imagick PHP extension is unavailable.
|
||||
IMAGE_DRIVER=imagick
|
||||
MAX_PAGER_RESULTS=125000
|
||||
ECHOCLI=true
|
||||
RENAME_PAR2=false
|
||||
|
||||
@@ -4,13 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Image\Image as LaravelImage;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Image;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Intervention\Image\Drivers\Imagick\Driver;
|
||||
use Intervention\Image\Exceptions\NotWritableException;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Spatie\LaravelImageOptimizer\Facades\ImageOptimizer;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Resize/save/delete images to disk.
|
||||
@@ -56,7 +55,7 @@ class ReleaseImageService
|
||||
$this->vidSavePath = storage_path('covers/video/');
|
||||
}
|
||||
|
||||
protected function fetchImage(string $imgLoc): bool|ImageInterface
|
||||
protected function fetchImage(string $imgLoc): bool|LaravelImage
|
||||
{
|
||||
try {
|
||||
// Create context with timeout settings for file_get_contents
|
||||
@@ -107,9 +106,8 @@ class ReleaseImageService
|
||||
return false;
|
||||
}
|
||||
|
||||
$manager = new ImageManager(Driver::class);
|
||||
$img = $manager->read($imageData);
|
||||
} catch (\Exception $e) {
|
||||
return Image::fromBytes($imageData);
|
||||
} catch (Throwable $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
cli()->notice('Data not available on server');
|
||||
} elseif ($e->getCode() === 503) {
|
||||
@@ -118,15 +116,9 @@ class ReleaseImageService
|
||||
Log::debug('Exception fetching image from '.$imgLoc.': '.$e->getMessage());
|
||||
cli()->notice('Unable to fetch image: '.$e->getMessage());
|
||||
}
|
||||
|
||||
$img = false;
|
||||
} catch (\Error $e) {
|
||||
Log::debug('Error fetching image from '.$imgLoc.': '.$e->getMessage());
|
||||
cli()->info($e->getMessage());
|
||||
$img = false;
|
||||
}
|
||||
|
||||
return $img;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,34 +145,38 @@ class ReleaseImageService
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if we need to resize it.
|
||||
if ($imgMaxWidth !== 0 && $imgMaxHeight !== 0) {
|
||||
$width = $cover->width();
|
||||
$height = $cover->height();
|
||||
if ($width !== 0 || $height !== 0) {
|
||||
$ratio = min($imgMaxHeight / $height, $imgMaxWidth / $width);
|
||||
// New dimensions
|
||||
$new_width = (int) ($ratio * $width);
|
||||
$new_height = (int) ($ratio * $height);
|
||||
if ($new_width < $width && $new_width > 10 && $new_height > 10) {
|
||||
$cover->resize($new_width, $new_height);
|
||||
$coverPath = $imgSavePath.$imgName.'.jpg';
|
||||
|
||||
if ($saveThumb) {
|
||||
$cover->toJpeg(100)->save($imgSavePath.$imgName.'_thumb.jpg');
|
||||
// Optimize the thumbnail.
|
||||
ImageOptimizer::optimize($imgSavePath.$imgName.'_thumb.jpg');
|
||||
try {
|
||||
$shouldSaveThumb = false;
|
||||
|
||||
// Check if we need to resize it.
|
||||
if ($imgMaxWidth !== 0 && $imgMaxHeight !== 0) {
|
||||
$width = $cover->width();
|
||||
$height = $cover->height();
|
||||
if ($width !== 0 || $height !== 0) {
|
||||
$ratio = min($imgMaxHeight / $height, $imgMaxWidth / $width);
|
||||
// New dimensions
|
||||
$new_width = (int) ($ratio * $width);
|
||||
$new_height = (int) ($ratio * $height);
|
||||
if ($new_width < $width && $new_width > 10 && $new_height > 10) {
|
||||
$cover = $cover->resize($new_width, $new_height);
|
||||
$shouldSaveThumb = $saveThumb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Store it on the hard drive.
|
||||
$coverPath = $imgSavePath.$imgName.'.jpg';
|
||||
try {
|
||||
$cover->toJpeg(100)->save($coverPath);
|
||||
// Optimize the image.
|
||||
ImageOptimizer::optimize($coverPath);
|
||||
} catch (NotWritableException $e) {
|
||||
Log::debug('NotWritableException saving image to '.$coverPath.': '.$e->getMessage());
|
||||
|
||||
$jpeg = $cover->toJpeg()->quality(100)->toBytes();
|
||||
|
||||
if ($shouldSaveThumb && ! $this->writeOptimizedImage($imgSavePath.$imgName.'_thumb.jpg', $jpeg)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (! $this->writeOptimizedImage($coverPath, $jpeg)) {
|
||||
return 0;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
Log::debug('Unable to process image '.$imgLoc.' for '.$coverPath.': '.$e->getMessage());
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -194,6 +190,25 @@ class ReleaseImageService
|
||||
return 1;
|
||||
}
|
||||
|
||||
private function writeOptimizedImage(string $path, string $contents): bool
|
||||
{
|
||||
if (File::put($path, $contents) === false) {
|
||||
Log::debug('Unable to write image to '.$path);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageOptimizer::optimize($path);
|
||||
|
||||
if (! File::isReadable($path)) {
|
||||
Log::debug('Image was not readable after save: '.$path);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete images for the release.
|
||||
*
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@
|
||||
"dariusiii/tv-maze-php-api": "^2.0.0",
|
||||
"elasticsearch/elasticsearch": "^8.19",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"intervention/image": "^3.11",
|
||||
"intervention/image": "^4.0",
|
||||
"laravel/framework": "^13.0",
|
||||
"laravel/horizon": "^5.45",
|
||||
"laravel/pulse": "^1.7",
|
||||
|
||||
Generated
+20
-20
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "aaf81ade6c4cbf0e3f5d46ebd40b715f",
|
||||
"content-hash": "8c7133089933c92e5fed429b9bc02822",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aharen/omdbapi",
|
||||
@@ -1734,26 +1734,26 @@
|
||||
},
|
||||
{
|
||||
"name": "intervention/gif",
|
||||
"version": "4.2.4",
|
||||
"version": "5.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/gif.git",
|
||||
"reference": "c3598a16ebe7690cd55640c44144a9df383ea73c"
|
||||
"reference": "bb395af960deffe64d70c976b4df9283f68e762d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/gif/zipball/c3598a16ebe7690cd55640c44144a9df383ea73c",
|
||||
"reference": "c3598a16ebe7690cd55640c44144a9df383ea73c",
|
||||
"url": "https://api.github.com/repos/Intervention/gif/zipball/bb395af960deffe64d70c976b4df9283f68e762d",
|
||||
"reference": "bb395af960deffe64d70c976b4df9283f68e762d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
"php": "^8.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
"squizlabs/php_codesniffer": "^4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -1772,7 +1772,7 @@
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "Native PHP GIF Encoder/Decoder",
|
||||
"description": "PHP GIF Encoder/Decoder",
|
||||
"homepage": "https://github.com/intervention/gif",
|
||||
"keywords": [
|
||||
"animation",
|
||||
@@ -1782,7 +1782,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/gif/issues",
|
||||
"source": "https://github.com/Intervention/gif/tree/4.2.4"
|
||||
"source": "https://github.com/Intervention/gif/tree/5.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1798,31 +1798,31 @@
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-04T09:27:23+00:00"
|
||||
"time": "2026-05-03T06:04:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/image",
|
||||
"version": "3.11.8",
|
||||
"version": "4.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/image.git",
|
||||
"reference": "cf04c8dd245697f701057c13d4bfe140d584e738"
|
||||
"reference": "830907fc5397dfc2a51a4e90322d586989fc8364"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/image/zipball/cf04c8dd245697f701057c13d4bfe140d584e738",
|
||||
"reference": "cf04c8dd245697f701057c13d4bfe140d584e738",
|
||||
"url": "https://api.github.com/repos/Intervention/image/zipball/830907fc5397dfc2a51a4e90322d586989fc8364",
|
||||
"reference": "830907fc5397dfc2a51a4e90322d586989fc8364",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"intervention/gif": "^4.2",
|
||||
"php": "^8.1"
|
||||
"intervention/gif": "^5",
|
||||
"php": "^8.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^4"
|
||||
},
|
||||
@@ -1858,7 +1858,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/image/issues",
|
||||
"source": "https://github.com/Intervention/image/tree/3.11.8"
|
||||
"source": "https://github.com/Intervention/image/tree/4.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1874,7 +1874,7 @@
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-01T08:20:10+00:00"
|
||||
"time": "2026-07-09T13:07:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jean85/pretty-package-versions",
|
||||
|
||||
+5
-5
@@ -4,17 +4,17 @@ return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Image Driver
|
||||
| Default Image Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Intervention Image supports "GD Library" and "Imagick" to process images
|
||||
| internally. You may choose one of them according to your PHP
|
||||
| configuration. By default PHP's "GD Library" implementation is used.
|
||||
| This option controls the default image processing driver that will be
|
||||
| used when manipulating or converting images. This driver is always
|
||||
| utilized unless another driver is explicitly specified instead.
|
||||
|
|
||||
| Supported: "gd", "imagick"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => 'imagick',
|
||||
'default' => env('IMAGE_DRIVER', 'imagick'),
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Services\ReleaseImageService;
|
||||
use GdImage;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Spatie\LaravelImageOptimizer\Facades\ImageOptimizer;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ReleaseImageServiceTest extends TestCase
|
||||
{
|
||||
private string $temporaryDirectory;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config(['image.default' => 'gd']);
|
||||
|
||||
$this->temporaryDirectory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'release-image-'.uniqid('', true).DIRECTORY_SEPARATOR;
|
||||
File::makeDirectory($this->temporaryDirectory, 0777, true);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
File::deleteDirectory($this->temporaryDirectory);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_it_converts_and_proportionally_resizes_a_local_image_to_jpeg(): void
|
||||
{
|
||||
$source = $this->createPng('source.png', 400, 200);
|
||||
$destination = $this->temporaryDirectory.'cover.jpg';
|
||||
|
||||
ImageOptimizer::shouldReceive('optimize')->once()->with($destination);
|
||||
|
||||
$result = (new ReleaseImageService)->saveImage('cover', $source, $this->temporaryDirectory, 100, 100);
|
||||
|
||||
$this->assertSame(1, $result);
|
||||
$this->assertSame('image/jpeg', File::mimeType($destination));
|
||||
$this->assertImageDimensions($destination, 100, 50);
|
||||
}
|
||||
|
||||
public function test_it_does_not_upscale_or_create_a_thumbnail_when_no_resize_occurs(): void
|
||||
{
|
||||
$source = $this->createPng('small.png', 40, 20);
|
||||
$destination = $this->temporaryDirectory.'cover.jpg';
|
||||
|
||||
ImageOptimizer::shouldReceive('optimize')->once()->with($destination);
|
||||
|
||||
$result = (new ReleaseImageService)->saveImage('cover', $source, $this->temporaryDirectory, 250, 250, true);
|
||||
|
||||
$this->assertSame(1, $result);
|
||||
$this->assertImageDimensions($destination, 40, 20);
|
||||
$this->assertFileDoesNotExist($this->temporaryDirectory.'cover_thumb.jpg');
|
||||
}
|
||||
|
||||
public function test_it_preserves_the_existing_small_dimension_resize_threshold(): void
|
||||
{
|
||||
$source = $this->createPng('short.png', 400, 20);
|
||||
$destination = $this->temporaryDirectory.'cover.jpg';
|
||||
|
||||
ImageOptimizer::shouldReceive('optimize')->once()->with($destination);
|
||||
|
||||
$result = (new ReleaseImageService)->saveImage('cover', $source, $this->temporaryDirectory, 100, 100);
|
||||
|
||||
$this->assertSame(1, $result);
|
||||
$this->assertImageDimensions($destination, 400, 20);
|
||||
}
|
||||
|
||||
public function test_it_writes_and_optimizes_the_main_image_and_thumbnail(): void
|
||||
{
|
||||
$source = $this->createPng('source.png', 200, 100);
|
||||
$destination = $this->temporaryDirectory.'cover.jpg';
|
||||
$thumbnail = $this->temporaryDirectory.'cover_thumb.jpg';
|
||||
|
||||
ImageOptimizer::shouldReceive('optimize')->once()->with($thumbnail);
|
||||
ImageOptimizer::shouldReceive('optimize')->once()->with($destination);
|
||||
|
||||
$result = (new ReleaseImageService)->saveImage('cover', $source, $this->temporaryDirectory, 100, 100, true);
|
||||
|
||||
$this->assertSame(1, $result);
|
||||
$this->assertImageDimensions($destination, 100, 50);
|
||||
$this->assertImageDimensions($thumbnail, 100, 50);
|
||||
}
|
||||
|
||||
public function test_it_returns_zero_for_empty_missing_invalid_and_unwritable_inputs(): void
|
||||
{
|
||||
ImageOptimizer::shouldReceive('optimize')->never();
|
||||
|
||||
$service = new ReleaseImageService;
|
||||
|
||||
$this->assertSame(0, $service->saveImage('empty', '', $this->temporaryDirectory));
|
||||
$this->assertSame(0, $service->saveImage('missing', $this->temporaryDirectory.'missing.png', $this->temporaryDirectory));
|
||||
|
||||
$invalid = $this->temporaryDirectory.'invalid.png';
|
||||
File::put($invalid, 'not an image');
|
||||
$this->assertSame(0, $service->saveImage('invalid', $invalid, $this->temporaryDirectory));
|
||||
|
||||
$source = $this->createPng('source.png', 100, 50);
|
||||
$notDirectory = $this->temporaryDirectory.'not-a-directory';
|
||||
File::put($notDirectory, 'file');
|
||||
$this->assertSame(0, $service->saveImage('unwritable', $source, $notDirectory.DIRECTORY_SEPARATOR));
|
||||
}
|
||||
|
||||
private function createPng(string $filename, int $width, int $height): string
|
||||
{
|
||||
$path = $this->temporaryDirectory.$filename;
|
||||
$image = imagecreatetruecolor($width, $height);
|
||||
|
||||
$this->assertInstanceOf(GdImage::class, $image);
|
||||
|
||||
$color = imagecolorallocate($image, 50, 100, 150);
|
||||
imagefill($image, 0, 0, $color);
|
||||
imagepng($image, $path);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function assertImageDimensions(string $path, int $width, int $height): void
|
||||
{
|
||||
$dimensions = getimagesize($path);
|
||||
|
||||
$this->assertIsArray($dimensions);
|
||||
$this->assertSame($width, $dimensions[0]);
|
||||
$this->assertSame($height, $dimensions[1]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user