<?php
namespace App\Subscriber\Filesystem;
use App\Component\Blobs\BlobManagers\LooseFileBlobManager;
use App\Entity\Filesystem\LooseFile;
use App\Model\Vendor\Blitline\BlitlineJob;
use App\Model\Vendor\Blitline\CropBlitlineFunction;
use App\Model\Vendor\Blitline\NoopBlitlineFunction;
use App\Model\Vendor\Blitline\ResizeToFitBlitlineFunction;
use App\Service\Vendors\Blitline\BlitlineProcessor;
use Cms\CoreBundle\Util\Doctrine\EntityManager;
use Platform\QueueBundle\Event\AsyncEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class LooseFileOptimizationSubscriber implements EventSubscriberInterface
{
private const OPTIMIZATIONS__THUMB = [
'constraint' => 200,
'quality' => 75,
];
private const OPTIMIZATIONS__PREVIEW = [
'constraint' => 1200,
'quality' => 75,
];
private const OPTIMIZATIONS = [
LooseFile::TYPES__IMAGE => [
LooseFileBlobManager::SUFFIXES__HUGE => [
'constraint' => 1000,
'quality' => 75,
],
LooseFileBlobManager::SUFFIXES__LARGE => [
'constraint' => 800,
'quality' => 75,
],
LooseFileBlobManager::SUFFIXES__MEDIUM => [
'constraint' => 600,
'quality' => 75,
],
LooseFileBlobManager::SUFFIXES__SMALL => [
'constraint' => 400,
'quality' => 75,
],
LooseFileBlobManager::SUFFIXES__TINY => [
'constraint' => 300,
'quality' => 75,
],
],
];
// DI
protected EntityManager $em;
protected BlitlineProcessor $blitline;
protected LooseFileBlobManager $blobs;
public function __construct(
EntityManager $em,
BlitlineProcessor $blitline,
LooseFileBlobManager $blobs
)
{
$this->em = $em;
$this->blitline = $blitline;
$this->blobs = $blobs;
}
/**
* {@inheritDoc}
*/
public static function getSubscribedEvents(): array
{
return [
'temp_file_resize' => [
['handleResizing', 0],
],
];
}
/**
* @param AsyncEvent $event
* @return void
*/
public function handleResizing(AsyncEvent $event): void
{
// obtain the file in question
$file = $this->em->getRepository(LooseFile::class)->find($event->getParam('file'));
if ( ! $file) {
// TODO: do we need to throw an error or something?
return;
}
// make sure it is an image file
if ( ! $file->isType(LooseFile::TYPES__IMAGE)) {
return;
}
// get crop info
$crop = $event->getParam('crop') ?: null;
// handle cropping, if no cropping we want to use a different function altogether
// this needs to be a high quality image
$func = ( ! $crop ? new NoopBlitlineFunction() : new CropBlitlineFunction())
->setQuality(100)
->setIdentifier($this->blobs->getCropSuffix($file))
->setS3Destination(
$this->blobs->storage()->bucket(),
$this->blobs->getCropPath($file)
);
if ($func instanceof CropBlitlineFunction) {
$func->setCropBox(
$crop['x'],
$crop['y'],
$crop['width'],
$crop['height']
);
}
// handle the optimization based on the "cropped" image
$func
// do thumb cropping first as that is what is needed first
->appendFunction(
(new ResizeToFitBlitlineFunction())
->setWidth(self::OPTIMIZATIONS__THUMB['constraint'])
->setOnlyShrinkLarger(true)
->setAutoSharpen(true)
->setQuality(self::OPTIMIZATIONS__THUMB['quality'])
->setIdentifier($this->blobs->getThumbSuffix($file))
->setS3Destination(
$this->blobs->storage()->bucket(),
$this->blobs->getThumbPath($file)
)
)
// then do generic preview resizing
->appendFunction(
(new ResizeToFitBlitlineFunction())
->setWidth(self::OPTIMIZATIONS__PREVIEW['constraint'])
->setOnlyShrinkLarger(true)
->setAutoSharpen(true)
->setQuality(self::OPTIMIZATIONS__PREVIEW['quality'])
->setIdentifier($this->blobs->getPreviewSuffix($file))
->setS3Destination(
$this->blobs->storage()->bucket(),
$this->blobs->getPreviewPath($file)
)
)
;
// handle optimizations
if (isset(self::OPTIMIZATIONS[$file->getType()])) {
foreach (self::OPTIMIZATIONS[$file->getType()] as $optimization => $config) {
$func->appendFunction(
(new ResizeToFitBlitlineFunction())
->setWidth($config['constraint'])
->setOnlyShrinkLarger(true)
->setAutoSharpen(true)
->setQuality($config['quality'])
->setIdentifier($this->blobs->getOptimizationSuffix($file, $optimization))
->setS3Destination(
$this->blobs->storage()->bucket(),
$this->blobs->getOptimizationPath($file, $optimization)
)
);
}
}
// create the blitline job
$job = (new BlitlineJob($this->blobs->getUploadUrl($file)))
->appendFunction($func);
// run the job; this is async...
try {
$id = $this->blitline->exec($job);
$event->getOutput()->writeln(sprintf(
'Blitline job #%s is running...',
$id
));
$this->em->save(
$file
->setRemoteJobId($id)
->setStatus(LooseFile::STATUSES__PROCESSED)
);
$event->getOutput()->writeln(array_map(
static function (string $url) {
return sprintf(
'Expecting URL: %s',
$url
);
},
[
$this->blobs->getUploadUrl($file),
$this->blobs->getCropUrl($file),
$this->blobs->getPreviewUrl($file),
$this->blobs->getThumbUrl($file),
...self::OPTIMIZATIONS[$file->getType()] ? array_map(
function (string $optimization) use ($file) {
return $this->blobs->getOptimizationUrl($file, $optimization);
},
array_keys(self::OPTIMIZATIONS[$file->getType()])
) : []
]
));
} catch (\Exception $e) {
$event->getOutput()->writeln(sprintf(
'ERROR: %s',
$e->getMessage()
));
}
}
}