mirror of
https://github.com/koel/koel
synced 2025-02-17 13:58:28 +00:00
feat: use a Intervention/Image for proper image handling
This commit is contained in:
parent
70e0f28774
commit
2ecc37bf63
8 changed files with 155 additions and 46 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
30
app/Services/ImageWriter.php
Normal file
30
app/Services/ImageWriter.php
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
76
composer.lock
generated
|
@ -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"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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
20
config/image.php
Normal 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',
|
||||
|
||||
];
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue