mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Make webp default format for images
This commit is contained in:
@@ -1,149 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Models\Release as ReleaseModel;
|
||||
use App\Services\Categorization\CategorizationService;
|
||||
use App\Services\MediaProcessingService;
|
||||
use App\Services\ReleaseExtraService;
|
||||
use App\Services\ReleaseImageService;
|
||||
use FFMpeg\FFMpeg;
|
||||
use FFMpeg\FFProbe;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Mhor\MediaInfo\MediaInfo;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MediaProcessingServiceTest extends TestCase
|
||||
{
|
||||
private string $tmpDir;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
// Minimal Facade container for File facade
|
||||
$container = new Container;
|
||||
$container->instance('files', new Filesystem);
|
||||
Facade::setFacadeApplication($container);
|
||||
|
||||
$this->tmpDir = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'mps_'.uniqid().DIRECTORY_SEPARATOR;
|
||||
File::makeDirectory($this->tmpDir, 0777, true, true);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (File::exists($this->tmpDir)) {
|
||||
File::deleteDirectory($this->tmpDir);
|
||||
}
|
||||
Mockery::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
private function makeService(
|
||||
?FFMpeg $ffmpeg = null,
|
||||
?FFProbe $ffprobe = null,
|
||||
?MediaInfo $mediaInfo = null,
|
||||
?ReleaseImageService $releaseImage = null,
|
||||
?ReleaseExtraService $releaseExtra = null,
|
||||
?CategorizationService $categorize = null
|
||||
): MediaProcessingService {
|
||||
$ffmpeg ??= Mockery::mock(FFMpeg::class);
|
||||
$ffprobe ??= Mockery::mock(FFProbe::class);
|
||||
$mediaInfo ??= Mockery::mock(MediaInfo::class);
|
||||
$releaseImage ??= Mockery::mock(ReleaseImageService::class);
|
||||
$releaseExtra ??= Mockery::mock(ReleaseExtraService::class);
|
||||
$categorize ??= Mockery::mock(CategorizationService::class);
|
||||
|
||||
return new MediaProcessingService($ffmpeg, $ffprobe, $mediaInfo, $releaseImage, $releaseExtra, $categorize);
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function test_get_video_time_parses_duration_string(): void
|
||||
{
|
||||
$ffprobe = Mockery::mock(FFProbe::class);
|
||||
$ffprobe->shouldReceive('isValid')->once()->andReturn(true);
|
||||
$format = new class
|
||||
{
|
||||
public function get($key)
|
||||
{
|
||||
return 'time=00:05.10 bitrate=800k';
|
||||
}
|
||||
};
|
||||
$ffprobe->shouldReceive('format')->once()->andReturn($format);
|
||||
$svc = $this->makeService(null, $ffprobe);
|
||||
$out = $svc->getVideoTime($this->tmpDir.'vid.avi');
|
||||
$this->assertSame('00:00:05.09', $out);
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function test_create_sample_image_returns_true_when_saved(): void
|
||||
{
|
||||
$videoFile = $this->tmpDir.'video.avi';
|
||||
File::put($videoFile, 'fake');
|
||||
|
||||
$ffprobe = Mockery::mock(FFProbe::class);
|
||||
$ffprobe->shouldReceive('isValid')->andReturn(true);
|
||||
$format = new class
|
||||
{
|
||||
public function get($key)
|
||||
{
|
||||
return 'time=00:03.10 bitrate=800k';
|
||||
}
|
||||
};
|
||||
$ffprobe->shouldReceive('format')->andReturn($format);
|
||||
|
||||
$frameMock = new class
|
||||
{
|
||||
public function save($path)
|
||||
{
|
||||
\file_put_contents($path, 'x');
|
||||
}
|
||||
};
|
||||
$openMock = new class($frameMock)
|
||||
{
|
||||
public function __construct(private $frame) {}
|
||||
|
||||
public function frame($tc)
|
||||
{
|
||||
return $this->frame;
|
||||
}
|
||||
};
|
||||
$ffmpeg = Mockery::mock(FFMpeg::class);
|
||||
$ffmpeg->shouldReceive('open')->andReturn($openMock);
|
||||
|
||||
$releaseImage = Mockery::mock(ReleaseImageService::class);
|
||||
$releaseImage->imgSavePath = $this->tmpDir;
|
||||
$releaseImage->shouldReceive('saveImage')->andReturn(1);
|
||||
|
||||
$svc = $this->makeService($ffmpeg, $ffprobe, null, $releaseImage);
|
||||
$ok = $svc->createSampleImage('guid123', $videoFile, $this->tmpDir, true);
|
||||
$this->assertTrue($ok);
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function test_add_video_media_info_false_if_file_missing(): void
|
||||
{
|
||||
$svc = $this->makeService();
|
||||
$this->assertFalse($svc->addVideoMediaInfo(1, $this->tmpDir.'nope.avi'));
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function test_add_audio_info_and_sample_returns_true_when_disabled(): void
|
||||
{
|
||||
$svc = $this->makeService();
|
||||
$release = new ReleaseModel;
|
||||
$release->id = 1;
|
||||
$release->guid = 'g';
|
||||
$release->predb_id = 0;
|
||||
$release->categories_id = 0;
|
||||
$release->groups_id = 0;
|
||||
$release->fromname = '';
|
||||
$res = $svc->addAudioInfoAndSample($release, $this->tmpDir.'nofile.mp3', 'MP3', false, false, $this->tmpDir);
|
||||
$this->assertTrue($res['info']);
|
||||
$this->assertTrue($res['sample']);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Enums\ImageAssetProfile;
|
||||
use App\Services\ReleaseImageService;
|
||||
use GdImage;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Spatie\LaravelImageOptimizer\Facades\ImageOptimizer;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ReleaseImageServiceTest extends TestCase
|
||||
@@ -18,7 +19,13 @@ class ReleaseImageServiceTest extends TestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config(['image.default' => 'gd']);
|
||||
config([
|
||||
'image.default' => 'gd',
|
||||
'image.output_format' => 'webp',
|
||||
'image.output_quality' => 82,
|
||||
'image.max_source_bytes' => 20 * 1024 * 1024,
|
||||
'image.max_source_pixels' => 40_000_000,
|
||||
]);
|
||||
|
||||
$this->temporaryDirectory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'release-image-'.uniqid('', true).DIRECTORY_SEPARATOR;
|
||||
File::makeDirectory($this->temporaryDirectory, 0777, true);
|
||||
@@ -31,80 +38,157 @@ class ReleaseImageServiceTest extends TestCase
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_it_converts_and_proportionally_resizes_a_local_image_to_jpeg(): void
|
||||
public function test_it_converts_and_proportionally_resizes_a_local_image_to_webp(): void
|
||||
{
|
||||
$source = $this->createPng('source.png', 400, 200);
|
||||
$destination = $this->temporaryDirectory.'cover.jpg';
|
||||
|
||||
ImageOptimizer::shouldReceive('optimize')->once()->with($destination);
|
||||
$result = (new ReleaseImageService)->saveLocalImage(
|
||||
'cover',
|
||||
$source,
|
||||
$this->temporaryDirectory,
|
||||
ImageAssetProfile::MetadataCover,
|
||||
);
|
||||
|
||||
$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);
|
||||
$this->assertTrue($result->success);
|
||||
$this->assertSame($this->temporaryDirectory.'cover.webp', $result->path);
|
||||
$this->assertSame('image/webp', File::mimeType($result->path));
|
||||
$this->assertImageDimensions($result->path, 250, 125);
|
||||
}
|
||||
|
||||
public function test_it_does_not_upscale_or_create_a_thumbnail_when_no_resize_occurs(): void
|
||||
public function test_it_does_not_upscale_a_small_image(): void
|
||||
{
|
||||
$source = $this->createPng('small.png', 40, 20);
|
||||
$destination = $this->temporaryDirectory.'cover.jpg';
|
||||
|
||||
ImageOptimizer::shouldReceive('optimize')->once()->with($destination);
|
||||
$result = (new ReleaseImageService)->saveLocalImage(
|
||||
'cover',
|
||||
$source,
|
||||
$this->temporaryDirectory,
|
||||
ImageAssetProfile::MetadataCover,
|
||||
);
|
||||
|
||||
$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');
|
||||
$this->assertTrue($result->success);
|
||||
$this->assertImageDimensions($result->path, 40, 20);
|
||||
}
|
||||
|
||||
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
|
||||
public function test_compatibility_wrapper_preserves_custom_bounds_and_thumbnail_name(): 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));
|
||||
$result = $service->saveImage('cover', $source, $this->temporaryDirectory, 100, 100, true);
|
||||
|
||||
$this->assertSame(1, $result);
|
||||
$this->assertImageDimensions($this->temporaryDirectory.'cover.webp', 100, 50);
|
||||
$this->assertImageDimensions($this->temporaryDirectory.'cover_thumb.webp', 100, 50);
|
||||
}
|
||||
|
||||
public function test_it_rejects_invalid_basename_missing_invalid_and_oversized_inputs(): void
|
||||
{
|
||||
$service = new ReleaseImageService;
|
||||
$source = $this->createPng('source.png', 100, 50);
|
||||
$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));
|
||||
$this->assertFalse($service->saveLocalImage('../cover', $source, $this->temporaryDirectory)->success);
|
||||
$this->assertFalse($service->saveLocalImage('missing', $this->temporaryDirectory.'missing.png', $this->temporaryDirectory)->success);
|
||||
$this->assertFalse($service->saveLocalImage('invalid', $invalid, $this->temporaryDirectory)->success);
|
||||
|
||||
config(['image.max_source_bytes' => 2]);
|
||||
$this->assertFalse($service->saveLocalImage('large', $source, $this->temporaryDirectory)->success);
|
||||
}
|
||||
|
||||
public function test_failed_processing_preserves_an_existing_asset(): void
|
||||
{
|
||||
$destination = $this->temporaryDirectory.'cover.webp';
|
||||
File::put($destination, 'existing-image');
|
||||
$invalid = $this->temporaryDirectory.'invalid.png';
|
||||
File::put($invalid, 'not an image');
|
||||
|
||||
$result = (new ReleaseImageService)->saveLocalImage('cover', $invalid, $this->temporaryDirectory);
|
||||
|
||||
$this->assertFalse($result->success);
|
||||
$this->assertSame('existing-image', File::get($destination));
|
||||
$this->assertSame([], File::glob($this->temporaryDirectory.'.cover.*.tmp.webp'));
|
||||
}
|
||||
|
||||
public function test_it_fetches_a_public_remote_image_with_bounded_http_client(): void
|
||||
{
|
||||
$source = $this->createPng('remote.png', 60, 30);
|
||||
$bytes = File::get($source);
|
||||
Http::fake([
|
||||
'https://images.example.test/cover.png' => Http::response($bytes, 200, [
|
||||
'Content-Length' => (string) strlen($bytes),
|
||||
]),
|
||||
]);
|
||||
$service = new ReleaseImageService(static fn (string $host): array => ['93.184.216.34']);
|
||||
|
||||
$result = $service->saveRemoteImage(
|
||||
'remote',
|
||||
'https://images.example.test/cover.png',
|
||||
$this->temporaryDirectory,
|
||||
);
|
||||
|
||||
$this->assertTrue($result->success);
|
||||
$this->assertSame('image/webp', File::mimeType($result->path));
|
||||
Http::assertSentCount(1);
|
||||
}
|
||||
|
||||
public function test_it_rejects_remote_hosts_that_resolve_to_private_addresses(): void
|
||||
{
|
||||
Http::fake();
|
||||
$service = new ReleaseImageService(static fn (string $host): array => ['127.0.0.1']);
|
||||
|
||||
$result = $service->saveRemoteImage(
|
||||
'private',
|
||||
'http://internal.example.test/image.jpg?token=secret',
|
||||
$this->temporaryDirectory,
|
||||
);
|
||||
|
||||
$this->assertFalse($result->success);
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_release_paths_keep_the_existing_storage_relationship(): void
|
||||
{
|
||||
$service = new ReleaseImageService;
|
||||
|
||||
$this->assertSame(storage_path('covers/preview/'), $service->imgSavePath);
|
||||
$this->assertSame(storage_path('covers/sample/'), $service->jpgSavePath);
|
||||
$this->assertSame(storage_path('covers/movies/'), $service->movieImgSavePath);
|
||||
}
|
||||
|
||||
public function test_jpeg_output_remains_available_as_a_rollback_setting(): void
|
||||
{
|
||||
config(['image.output_format' => 'jpg']);
|
||||
$source = $this->createPng('rollback.png', 30, 15);
|
||||
|
||||
$result = (new ReleaseImageService)->saveLocalImage('rollback', $source, $this->temporaryDirectory);
|
||||
|
||||
$this->assertTrue($result->success);
|
||||
$this->assertSame($this->temporaryDirectory.'rollback.jpg', $result->path);
|
||||
$this->assertSame('image/jpeg', File::mimeType($result->path));
|
||||
}
|
||||
|
||||
public function test_delete_removes_webp_and_legacy_release_images(): void
|
||||
{
|
||||
$service = new ReleaseImageService;
|
||||
$guid = 'delete-'.uniqid();
|
||||
$files = [
|
||||
$service->imgSavePath.$guid.'_thumb.webp',
|
||||
$service->imgSavePath.$guid.'_thumb.jpg',
|
||||
$service->jpgSavePath.$guid.'_thumb.webp',
|
||||
$service->jpgSavePath.$guid.'_thumb.jpg',
|
||||
];
|
||||
foreach ($files as $file) {
|
||||
File::ensureDirectoryExists(dirname($file));
|
||||
File::put($file, 'image');
|
||||
}
|
||||
|
||||
$service->delete($guid);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$this->assertFileDoesNotExist($file);
|
||||
}
|
||||
}
|
||||
|
||||
private function createPng(string $filename, int $width, int $height): string
|
||||
@@ -121,8 +205,9 @@ class ReleaseImageServiceTest extends TestCase
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function assertImageDimensions(string $path, int $width, int $height): void
|
||||
private function assertImageDimensions(?string $path, int $width, int $height): void
|
||||
{
|
||||
$this->assertNotNull($path);
|
||||
$dimensions = getimagesize($path);
|
||||
|
||||
$this->assertIsArray($dimensions);
|
||||
|
||||
Reference in New Issue
Block a user