feat: use a Intervention/Image for proper image handling

This commit is contained in:
Phan An 2020-04-27 22:32:24 +02:00
parent 70e0f28774
commit 2ecc37bf63
8 changed files with 155 additions and 46 deletions

View file

@ -234,7 +234,8 @@ class FileSynchronizer
// Or, if there's a cover image under the same directory, use it.
if ($cover = $this->getCoverFileUnderSameDirectory()) {
$this->mediaMetadataService->copyAlbumCover($album, $cover);
$extension = pathinfo($cover, PATHINFO_EXTENSION);
$this->mediaMetadataService->writeAlbumCover($album, file_get_contents($cover), $extension);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Services;
use Intervention\Image\Constraint;
use Intervention\Image\ImageManager;
class ImageWriter
{
private const MAX_WIDTH = 500;
private const QUALITY = 80;
private $imageManager;
public function __construct(ImageManager $imageManager)
{
$this->imageManager = $imageManager;
}
public function writeFromBinaryData(string $destination, string $data): void
{
$this->imageManager
->make($data)
->resize(self::MAX_WIDTH, null, static function (Constraint $constraint): void {
$constraint->upsize();
$constraint->aspectRatio();
})
->save($destination, self::QUALITY);
}
}

View file

@ -9,10 +9,12 @@ use Psr\Log\LoggerInterface;
class MediaMetadataService
{
private $imageWriter;
private $logger;
public function __construct(LoggerInterface $logger)
public function __construct(ImageWriter $imageWriter, LoggerInterface $logger)
{
$this->imageWriter = $imageWriter;
$this->logger = $logger;
}
@ -25,21 +27,6 @@ class MediaMetadataService
$this->writeAlbumCover($album, file_get_contents($imageUrl), last($extension));
}
/**
* Copy a cover file from an existing image on the system.
*
* @param string $source The original image's full path.
* @param string $destination The destination path. Automatically generated if empty.
*/
public function copyAlbumCover(Album $album, string $source, string $destination = ''): void
{
$extension = pathinfo($source, PATHINFO_EXTENSION);
$destination = $destination ?: $this->generateAlbumCoverPath($extension);
copy($source, $destination);
$album->update(['cover' => basename($destination)]);
}
/**
* Write an album cover image file with binary data and update the Album with the new cover attribute.
*
@ -50,7 +37,7 @@ class MediaMetadataService
try {
$extension = trim(strtolower($extension), '. ');
$destination = $destination ?: $this->generateAlbumCoverPath($extension);
file_put_contents($destination, $binaryData);
$this->imageWriter->writeFromBinaryData($destination, $binaryData);
$album->update(['cover' => basename($destination)]);
} catch (Exception $e) {
@ -81,7 +68,7 @@ class MediaMetadataService
try {
$extension = trim(strtolower($extension), '. ');
$destination = $destination ?: $this->generateArtistImagePath($extension);
file_put_contents($destination, $binaryData);
$this->imageWriter->writeFromBinaryData($destination, $binaryData);
$artist->update(['image' => basename($destination)]);
} catch (Exception $e) {

View file

@ -22,7 +22,8 @@
"fideloper/proxy": "^4.0",
"daverandom/resume": "^0.0.3",
"laravel/helpers": "^1.0",
"cweagans/composer-patches": "^1.6"
"cweagans/composer-patches": "^1.6",
"intervention/image": "^2.5"
},
"require-dev": {
"filp/whoops": "~2.0",

76
composer.lock generated
View file

@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "d5a9fdbd48600713ea57bdb9afc52118",
"content-hash": "2e707320448c85aba355d6e1722f1a50",
"packages": [
{
"name": "aws/aws-sdk-php",
@ -1013,6 +1013,76 @@
],
"time": "2019-07-01T23:21:34+00:00"
},
{
"name": "intervention/image",
"version": "2.5.1",
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
"reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/abbf18d5ab8367f96b3205ca3c89fb2fa598c69e",
"reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"guzzlehttp/psr7": "~1.1",
"php": ">=5.4.0"
},
"require-dev": {
"mockery/mockery": "~0.9.2",
"phpunit/phpunit": "^4.8 || ^5.7"
},
"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"
},
"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"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@olivervogel.com",
"homepage": "http://olivervogel.com/"
}
],
"description": "Image handling and manipulation library with support for Laravel integration",
"homepage": "http://image.intervention.io/",
"keywords": [
"gd",
"image",
"imagick",
"laravel",
"thumbnail",
"watermark"
],
"time": "2019-11-02T09:15:47+00:00"
},
{
"name": "jackiedo/dotenv-editor",
"version": "1.0.8",
@ -4869,6 +4939,7 @@
"email": "jakub.onderka@gmail.com"
}
],
"abandoned": "php-parallel-lint/php-console-color",
"time": "2018-09-29T17:23:10+00:00"
},
{
@ -4915,6 +4986,7 @@
}
],
"description": "Highlight PHP code in terminal",
"abandoned": "php-parallel-lint/php-console-highlighter",
"time": "2018-09-29T18:48:56+00:00"
},
{

View file

@ -126,6 +126,7 @@ return [
'Tymon\JWTAuth\Providers\JWTAuthServiceProvider', // hard-coding to make it compatible with patching procedure
Aws\Laravel\AwsServiceProvider::class,
Jackiedo\DotenvEditor\DotenvEditorServiceProvider::class,
Intervention\Image\ImageServiceProvider::class,
/*
* Application Service Providers...
@ -189,6 +190,7 @@ return [
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'DotenvEditor' => Jackiedo\DotenvEditor\Facades\DotenvEditor::class,
'Image' => Intervention\Image\Facades\Image::class,
'Util' => App\Facades\Util::class,
'YouTube' => App\Facades\YouTube::class,

20
config/image.php Normal file
View file

@ -0,0 +1,20 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| 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.
|
| Supported: "gd", "imagick"
|
*/
'driver' => extension_loaded('imagick') ? 'imagick' : 'gd',
];

View file

@ -4,9 +4,11 @@ namespace Tests\Integration\Services;
use App\Models\Album;
use App\Models\Artist;
use App\Services\ImageWriter;
use App\Services\MediaMetadataService;
use Illuminate\Log\Logger;
use org\bovigo\vfs\vfsStream;
use Mockery;
use Mockery\MockInterface;
use Tests\TestCase;
class MediaMetadataServiceTest extends TestCase
@ -14,24 +16,15 @@ class MediaMetadataServiceTest extends TestCase
/** @var MediaMetadataService */
private $mediaMetadataService;
/** @var ImageWriter|MockInterface */
private $imageWriter;
public function setUp(): void
{
parent::setUp();
$this->mediaMetadataService = new MediaMetadataService(app(Logger::class));
}
public function testCopyAlbumCover(): void
{
/** @var Album $album */
$album = factory(Album::class)->create();
$root = vfsStream::setup('home');
$imageFile = vfsStream::newFile('foo.jpg')->at($root)->setContent('foo');
$coverPath = vfsStream::url('home/bar.jpg');
$this->mediaMetadataService->copyAlbumCover($album, $imageFile->url(), $coverPath);
$this->assertTrue($root->hasChild('bar.jpg'));
$this->assertEquals('http://localhost/public/img/covers/bar.jpg', Album::find($album->id)->cover);
$this->imageWriter = Mockery::mock(ImageWriter::class);
$this->mediaMetadataService = new MediaMetadataService($this->imageWriter, app(Logger::class));
}
public function testWriteAlbumCover(): void
@ -39,26 +32,29 @@ class MediaMetadataServiceTest extends TestCase
/** @var Album $album */
$album = factory(Album::class)->create();
$coverContent = 'dummy';
$root = vfsStream::setup('home');
$coverPath = vfsStream::url('home/foo.jpg');
$coverPath = '/koel/public/images/album/foo.jpg';
$this->imageWriter
->shouldReceive('writeFromBinaryData')
->once()
->with('/koel/public/images/album/foo.jpg', 'dummy');
$this->mediaMetadataService->writeAlbumCover($album, $coverContent, 'jpg', $coverPath);
$this->assertTrue($root->hasChild('foo.jpg'));
$this->assertEquals('http://localhost/public/img/covers/foo.jpg', Album::find($album->id)->cover);
}
public function testWriteArtistImage(): void
{
/** @var Artist $artist */
$artist = factory(Artist::class)->create();
$imageContent = 'dummy';
$root = vfsStream::setup('home');
$imagePath = vfsStream::url('home/foo.jpg');
$imagePath = '/koel/public/images/artist/foo.jpg';
$this->imageWriter
->shouldReceive('writeFromBinaryData')
->once()
->with('/koel/public/images/artist/foo.jpg', 'dummy');
$this->mediaMetadataService->writeArtistImage($artist, $imageContent, 'jpg', $imagePath);
$this->assertTrue($root->hasChild('foo.jpg'));
$this->assertEquals('http://localhost/public/img/artists/foo.jpg', Artist::find($artist->id)->image);
}
}