diff --git a/composer.json b/composer.json index c3268a4be..950b594f4 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ "gettext/php-scanner": "^1.3", "gettext/translator": "^1.1", "guzzlehttp/guzzle": "^7.0", - "intervention/image": "^2.6", + "intervention/image": "^3", "james-heinrich/getid3": "v2.0.0-beta6", "lbuchs/webauthn": "^2.1", "league/csv": "^9.6", diff --git a/composer.lock b/composer.lock index 330ff66e5..af34eadc7 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "7436fe8e498c7769c9f8c9135316a6c7", + "content-hash": "937d84fd61dbb782e05b56a7d49ee79e", "packages": [ { "name": "aws/aws-crt-php", @@ -2645,50 +2645,30 @@ "time": "2023-12-03T20:05:35+00:00" }, { - "name": "intervention/image", - "version": "2.7.2", + "name": "intervention/gif", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/Intervention/image.git", - "reference": "04be355f8d6734c826045d02a1079ad658322dad" + "url": "https://github.com/Intervention/gif.git", + "reference": "cfececc760862f075a52acf747031bad08c8301b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad", - "reference": "04be355f8d6734c826045d02a1079ad658322dad", + "url": "https://api.github.com/repos/Intervention/gif/zipball/cfececc760862f075a52acf747031bad08c8301b", + "reference": "cfececc760862f075a52acf747031bad08c8301b", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "guzzlehttp/psr7": "~1.1 || ^2.0", - "php": ">=5.4.0" + "php": "^8.0" }, "require-dev": { - "mockery/mockery": "~0.9.2", - "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15" - }, - "suggest": { - "ext-gd": "to use GD library based image processing.", - "ext-imagick": "to use Imagick based image processing.", - "intervention/imagecache": "Caching extension for the Intervention Image library" + "phpstan/phpstan": "^1", + "phpunit/phpunit": "^9" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - }, - "laravel": { - "providers": [ - "Intervention\\Image\\ImageServiceProvider" - ], - "aliases": { - "Image": "Intervention\\Image\\Facades\\Image" - } - } - }, "autoload": { "psr-4": { - "Intervention\\Image\\": "src/Intervention/Image" + "Intervention\\Gif\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2702,19 +2682,74 @@ "homepage": "https://intervention.io/" } ], - "description": "Image handling and manipulation library with support for Laravel integration", - "homepage": "http://image.intervention.io/", + "description": "Native PHP GIF Encoder/Decoder", + "homepage": "https://github.com/intervention/gif", + "keywords": [ + "animation", + "gd", + "gif", + "image" + ], + "support": { + "issues": "https://github.com/Intervention/gif/issues", + "source": "https://github.com/Intervention/gif/tree/3.0.0" + }, + "time": "2023-11-27T18:54:30+00:00" + }, + { + "name": "intervention/image", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "ac9734129478be387c1914f1c2856d623341128c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/ac9734129478be387c1914f1c2856d623341128c", + "reference": "ac9734129478be387c1914f1c2856d623341128c", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "intervention/gif": "^3", + "php": "^8.1" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "phpstan/phpstan": "^1", + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@intervention.io", + "homepage": "https://intervention.io/" + } + ], + "description": "PHP image manipulation", + "homepage": "https://image.intervention.io/", "keywords": [ "gd", "image", "imagick", - "laravel", + "resize", "thumbnail", "watermark" ], "support": { "issues": "https://github.com/Intervention/image/issues", - "source": "https://github.com/Intervention/image/tree/2.7.2" + "source": "https://github.com/Intervention/image/tree/3.0.2" }, "funding": [ { @@ -2726,7 +2761,7 @@ "type": "github" } ], - "time": "2022-05-21T17:30:32+00:00" + "time": "2023-12-14T13:17:13+00:00" }, { "name": "james-heinrich/getid3", diff --git a/src/Assets/AbstractMultiPatternCustomAsset.php b/src/Assets/AbstractMultiPatternCustomAsset.php index f984f4f7d..668f25832 100644 --- a/src/Assets/AbstractMultiPatternCustomAsset.php +++ b/src/Assets/AbstractMultiPatternCustomAsset.php @@ -4,13 +4,18 @@ declare(strict_types=1); namespace App\Assets; +use Intervention\Image\Interfaces\EncoderInterface; + abstract class AbstractMultiPatternCustomAsset extends AbstractCustomAsset { + /** + * @return array + */ abstract protected function getPatterns(): array; protected function getPattern(): string { - return $this->getPatterns()['default']; + return $this->getPatterns()['default'][0]; } protected function getPathForPattern(string $pattern): string @@ -22,26 +27,26 @@ abstract class AbstractMultiPatternCustomAsset extends AbstractCustomAsset public function getPath(): string { $patterns = $this->getPatterns(); - foreach ($patterns as $pattern) { + foreach ($patterns as [$pattern, $encoder]) { $path = $this->getPathForPattern($pattern); if (is_file($path)) { return $path; } } - return $this->getPathForPattern($patterns['default']); + return $this->getPathForPattern($patterns['default'][0]); } public function delete(): void { - foreach ($this->getPatterns() as $pattern) { + foreach ($this->getPatterns() as [$pattern, $encoder]) { @unlink($this->getPathForPattern($pattern)); } } public function getUrl(): string { - foreach ($this->getPatterns() as $pattern) { + foreach ($this->getPatterns() as [$pattern, $encoder]) { $path = $this->getPathForPattern($pattern); if (is_file($path)) { diff --git a/src/Assets/AlbumArtCustomAsset.php b/src/Assets/AlbumArtCustomAsset.php index 50c3bcb02..ba824ecf5 100644 --- a/src/Assets/AlbumArtCustomAsset.php +++ b/src/Assets/AlbumArtCustomAsset.php @@ -4,17 +4,28 @@ declare(strict_types=1); namespace App\Assets; -use Intervention\Image\Constraint; -use Intervention\Image\Image; +use Intervention\Image\Encoders\JpegEncoder; +use Intervention\Image\Encoders\PngEncoder; +use Intervention\Image\Encoders\WebpEncoder; +use Intervention\Image\Interfaces\ImageInterface; final class AlbumArtCustomAsset extends AbstractMultiPatternCustomAsset { protected function getPatterns(): array { return [ - 'default' => 'album_art%s.jpg', - 'image/png' => 'album_art%s.png', - 'image/webp' => 'album_art%s.webp', + 'default' => [ + 'album_art%s.jpg', + new JpegEncoder(90), + ], + 'image/png' => [ + 'album_art%s.png', + new PngEncoder(), + ], + 'image/webp' => [ + 'album_art%s.webp', + new WebpEncoder(90), + ], ]; } @@ -23,23 +34,19 @@ final class AlbumArtCustomAsset extends AbstractMultiPatternCustomAsset return $this->environment->getAssetUrl() . '/img/generic_song.jpg'; } - public function upload(Image $image): void + public function upload(ImageInterface $image, string $mimeType): void { $newImage = clone $image; - $newImage->resize(1500, 1500, function (Constraint $constraint) { - $constraint->upsize(); - }); + $newImage->resizeDown(1500, 1500); $this->delete(); $patterns = $this->getPatterns(); - $mimeType = $newImage->mime(); - - $pattern = $patterns[$mimeType] ?? $patterns['default']; + [$pattern, $encoder] = $patterns[$mimeType] ?? $patterns['default']; $destPath = $this->getPathForPattern($pattern); $this->ensureDirectoryExists(dirname($destPath)); - $newImage->save($destPath, 90); + $newImage->encode($encoder)->save($destPath); } } diff --git a/src/Assets/BackgroundCustomAsset.php b/src/Assets/BackgroundCustomAsset.php index 8ba956ef5..f5fdbdaae 100644 --- a/src/Assets/BackgroundCustomAsset.php +++ b/src/Assets/BackgroundCustomAsset.php @@ -4,17 +4,28 @@ declare(strict_types=1); namespace App\Assets; -use Intervention\Image\Constraint; -use Intervention\Image\Image; +use Intervention\Image\Encoders\JpegEncoder; +use Intervention\Image\Encoders\PngEncoder; +use Intervention\Image\Encoders\WebpEncoder; +use Intervention\Image\Interfaces\ImageInterface; final class BackgroundCustomAsset extends AbstractMultiPatternCustomAsset { protected function getPatterns(): array { return [ - 'default' => 'background%s.jpg', - 'image/png' => 'background%s.png', - 'image/webp' => 'background%s.webp', + 'default' => [ + 'background%s.jpg', + new JpegEncoder(90), + ], + 'image/png' => [ + 'background%s.png', + new PngEncoder(), + ], + 'image/webp' => [ + 'background%s.webp', + new WebpEncoder(90), + ], ]; } @@ -23,23 +34,19 @@ final class BackgroundCustomAsset extends AbstractMultiPatternCustomAsset return $this->environment->getAssetUrl() . '/img/hexbg.png'; } - public function upload(Image $image): void + public function upload(ImageInterface $image, string $mimeType): void { $newImage = clone $image; - $newImage->resize(3264, 2160, function (Constraint $constraint) { - $constraint->upsize(); - }); + $newImage->resizeDown(3264, 2160); $this->delete(); $patterns = $this->getPatterns(); - $mimeType = $newImage->mime(); - - $pattern = $patterns[$mimeType] ?? $patterns['default']; + [$pattern, $encoder] = $patterns[$mimeType] ?? $patterns['default']; $destPath = $this->getPathForPattern($pattern); $this->ensureDirectoryExists(dirname($destPath)); - $newImage->save($destPath, 90); + $newImage->encode($encoder)->save($destPath); } } diff --git a/src/Assets/BrowserIconCustomAsset.php b/src/Assets/BrowserIconCustomAsset.php index 68ed15519..d757cedec 100644 --- a/src/Assets/BrowserIconCustomAsset.php +++ b/src/Assets/BrowserIconCustomAsset.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace App\Assets; -use Intervention\Image\Image; +use Intervention\Image\Interfaces\ImageInterface; use Symfony\Component\Filesystem\Filesystem; final class BrowserIconCustomAsset extends AbstractCustomAsset @@ -38,7 +38,7 @@ final class BrowserIconCustomAsset extends AbstractCustomAsset return $assetUrl . '/icons/' . $this->environment->getAppEnvironmentEnum()->value . '/original.png'; } - public function upload(Image $image): void + public function upload(ImageInterface $image, string $mimeType): void { $this->delete(); @@ -47,12 +47,12 @@ final class BrowserIconCustomAsset extends AbstractCustomAsset $newImage = clone $image; $newImage->resize(256, 256); - $newImage->save($uploadsDir . '/original.png'); + $newImage->toPng()->save($uploadsDir . '/original.png'); foreach (self::ICON_SIZES as $iconSize) { $newImage = clone $image; $newImage->resize($iconSize, $iconSize); - $newImage->save($uploadsDir . '/' . $iconSize . '.png'); + $newImage->toPng()->save($uploadsDir . '/' . $iconSize . '.png'); } } diff --git a/src/Assets/CustomAssetInterface.php b/src/Assets/CustomAssetInterface.php index 274138850..0c460eef9 100644 --- a/src/Assets/CustomAssetInterface.php +++ b/src/Assets/CustomAssetInterface.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace App\Assets; -use Intervention\Image\Image; +use Intervention\Image\Interfaces\ImageInterface; use Psr\Http\Message\UriInterface; interface CustomAssetInterface @@ -19,7 +19,7 @@ interface CustomAssetInterface public function getUri(): UriInterface; - public function upload(Image $image): void; + public function upload(ImageInterface $image, string $mimeType): void; public function delete(): void; } diff --git a/src/Controller/Api/Admin/CustomAssets/PostCustomAssetAction.php b/src/Controller/Api/Admin/CustomAssets/PostCustomAssetAction.php index 222ddbb5b..a09e5c4b6 100644 --- a/src/Controller/Api/Admin/CustomAssets/PostCustomAssetAction.php +++ b/src/Controller/Api/Admin/CustomAssets/PostCustomAssetAction.php @@ -11,6 +11,7 @@ use App\Entity\Api\Status; use App\Http\Response; use App\Http\ServerRequest; use App\Media\AlbumArt; +use App\Media\MimeType; use App\Service\Flow; use Psr\Http\Message\ResponseInterface; @@ -34,8 +35,10 @@ final class PostCustomAssetAction implements SingleActionInterface } $imageContents = $flowResponse->readAndDeleteUploadedFile(); + $customAsset->upload( - AlbumArt::getImageManager()->make($imageContents) + AlbumArt::getImageManager()->read($imageContents), + MimeType::getMimeTypeDetector()->detectMimeTypeFromBuffer($imageContents) ?? 'image/jpeg' ); return $response->withJson(Status::success()); diff --git a/src/Controller/Api/Stations/CustomAssets/PostCustomAssetAction.php b/src/Controller/Api/Stations/CustomAssets/PostCustomAssetAction.php index 4122ef37f..3b5776149 100644 --- a/src/Controller/Api/Stations/CustomAssets/PostCustomAssetAction.php +++ b/src/Controller/Api/Stations/CustomAssets/PostCustomAssetAction.php @@ -11,6 +11,7 @@ use App\Entity\Api\Status; use App\Http\Response; use App\Http\ServerRequest; use App\Media\AlbumArt; +use App\Media\MimeType; use App\Service\Flow; use Psr\Http\Message\ResponseInterface; @@ -38,7 +39,8 @@ final class PostCustomAssetAction implements SingleActionInterface $imageContents = $flowResponse->readAndDeleteUploadedFile(); $customAsset->upload( - AlbumArt::getImageManager()->make($imageContents) + AlbumArt::getImageManager()->read($imageContents), + MimeType::getMimeTypeDetector()->detectMimeTypeFromBuffer($imageContents) ?? 'image/jpeg' ); return $response->withJson(Status::success()); diff --git a/src/Media/AlbumArt.php b/src/Media/AlbumArt.php index fd9627d47..9425557c0 100644 --- a/src/Media/AlbumArt.php +++ b/src/Media/AlbumArt.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace App\Media; -use Intervention\Image\Constraint; +use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\ImageManager; final class AlbumArt @@ -17,28 +17,21 @@ final class AlbumArt int $height = self::IMAGE_WIDTH, bool $upsize = false, ): string { - $newArtwork = self::getImageManager()->make($rawArtworkString); - $newArtwork->fit( - $width, - $height, - function (Constraint $constraint) use ($upsize) { - if (!$upsize) { - $constraint->upsize(); - } - } - ); + $newArtwork = self::getImageManager()->read($rawArtworkString); - $newArtwork->encode('jpg'); + if ($upsize) { + $newArtwork->cover($width, $height); + } else { + $newArtwork->coverDown($width, $height); + } - return $newArtwork->getEncoded(); + return $newArtwork->toJpeg()->toString(); } public static function getImageManager(): ImageManager { return new ImageManager( - [ - 'driver' => 'gd', - ] + new Driver() ); } }