diff --git a/src/Controller/AbstractImageController.php b/src/Controller/AbstractImageController.php index 0baf0991..4b961562 100644 --- a/src/Controller/AbstractImageController.php +++ b/src/Controller/AbstractImageController.php @@ -7,6 +7,7 @@ use AnzuSystems\CoreDamBundle\Domain\Configuration\ConfigurationProvider; use AnzuSystems\CoreDamBundle\Domain\Configuration\DomainProvider; use AnzuSystems\CoreDamBundle\Domain\Image\Crop\CropFacade; +use AnzuSystems\CoreDamBundle\Domain\Image\Crop\CropProcessor; use AnzuSystems\CoreDamBundle\Entity\AssetFile; use AnzuSystems\CoreDamBundle\Entity\ImageFile; use AnzuSystems\CoreDamBundle\Entity\RegionOfInterest; @@ -16,6 +17,7 @@ use AnzuSystems\CoreDamBundle\Helper\FileNameHelper; use AnzuSystems\CoreDamBundle\Model\Dto\Image\Crop\RequestedCropDto; use AnzuSystems\CoreDamBundle\Repository\ImageFileRepository; +use AnzuSystems\CoreDamBundle\Traits\FileHelperTrait; use Doctrine\ORM\NonUniqueResultException; use League\Flysystem\FilesystemException; use Symfony\Component\HttpFoundation\HeaderUtils; @@ -26,8 +28,7 @@ abstract class AbstractImageController extends AbstractPublicController { - public const string CROP_EXTENSION = 'jpeg'; - public const string DEFAULT_CROP_MIME_TYPE = 'image/jpeg'; + use FileHelperTrait; protected DomainProvider $domainProvider; protected ImageFileRepository $imageFileRepository; @@ -84,14 +85,6 @@ protected function okImageResponse( return $response; } - protected function okResponse(string $content, AssetFile $asset): Response - { - $response = $this->getImageResponse($content, $asset)->setStatusCode(Response::HTTP_OK); - $this->assetFileCacheManager->setCache($response, $asset); - - return $response; - } - protected function notFoundResponse(): Response { $response = new Response('', Response::HTTP_NOT_FOUND); @@ -160,20 +153,20 @@ function () use ($fileStream) { return $response; } - private function getImageResponse(string $content, AssetFile $assetFile): Response + private function getImageResponse(string $content, ImageFile $assetFile): Response { return new Response($content, Response::HTTP_OK, [ - 'Content-Type' => self::DEFAULT_CROP_MIME_TYPE, - 'Content-Disposition' => $this->makeDisposition($assetFile), + 'Content-Type' => CropProcessor::getCropMimeType($assetFile), + 'Content-Disposition' => $this->mageImageCropDisposition($assetFile), 'Content-Length' => strlen($content), ]); } - private function makeDisposition(AssetFile $assetFile): string + private function mageImageCropDisposition(ImageFile $assetFile): string { $fileName = FileNameHelper::changeFileExtension( (string) $assetFile->getId(), - self::CROP_EXTENSION + $this->fileHelper->guessExtension(CropProcessor::getCropMimeType($assetFile)), ); return HeaderUtils::makeDisposition( diff --git a/src/Distribution/Modules/Youtube/YoutubeApiClient.php b/src/Distribution/Modules/Youtube/YoutubeApiClient.php index 40e5932b..d993438a 100644 --- a/src/Distribution/Modules/Youtube/YoutubeApiClient.php +++ b/src/Distribution/Modules/Youtube/YoutubeApiClient.php @@ -6,6 +6,7 @@ use AnzuSystems\CoreDamBundle\Domain\Image\Crop\CropProcessor; use AnzuSystems\CoreDamBundle\Entity\AssetFile; +use AnzuSystems\CoreDamBundle\Entity\ImageFile; use AnzuSystems\CoreDamBundle\Entity\YoutubeDistribution; use AnzuSystems\CoreDamBundle\Exception\DistributionFailedException; use AnzuSystems\CoreDamBundle\Exception\RuntimeException; @@ -140,6 +141,7 @@ public function setPlaylist( public function setThumbnail( string $distributionService, string $distributionId, + ImageFile $imageFile, string $imageData, ): void { $client = $this->clientProvider->getClient($distributionService); @@ -149,7 +151,7 @@ public function setThumbnail( $youtubeService = new Google_Service_YouTube($client); $response = $youtubeService->thumbnails->set($distributionId, [ 'data' => $imageData, - 'mimeType' => CropProcessor::DEFAULT_MIME_TYPE, + 'mimeType' => CropProcessor::getCropMimeType($imageFile), ]); } catch (Throwable $exception) { $this->damLogger->error( diff --git a/src/Distribution/Modules/YoutubeDistributionModule.php b/src/Distribution/Modules/YoutubeDistributionModule.php index 4d26e2ab..7a1ee0e3 100644 --- a/src/Distribution/Modules/YoutubeDistributionModule.php +++ b/src/Distribution/Modules/YoutubeDistributionModule.php @@ -187,6 +187,7 @@ private function setThumbnail(YoutubeDistribution $distribution): void $this->client->setThumbnail( distributionService: $distribution->getDistributionService(), distributionId: $distribution->getExtId(), + imageFile: $imageFile, imageData: $this->cropFacade->applyCropPayloadToDefaultRoi( image: $imageFile, cropPayload: (new RequestedCropDto()) diff --git a/src/Domain/Image/Crop/CropCache.php b/src/Domain/Image/Crop/CropCache.php index 8e726154..269a5054 100644 --- a/src/Domain/Image/Crop/CropCache.php +++ b/src/Domain/Image/Crop/CropCache.php @@ -4,7 +4,6 @@ namespace AnzuSystems\CoreDamBundle\Domain\Image\Crop; -use AnzuSystems\CoreDamBundle\Entity\ExtSystem; use AnzuSystems\CoreDamBundle\Entity\ImageFile; use AnzuSystems\CoreDamBundle\FileSystem\FileSystemProvider; use AnzuSystems\CoreDamBundle\FileSystem\NameGenerator\NameGenerator; @@ -60,7 +59,7 @@ public function get(ImageFile $image, ImageCropDto $imageCrop): string public function removeCache(ImageFile $image): void { $this->removeCacheByOriginFilePath( - $image->getExtSystem(), + $image->getExtSystem()->getSlug(), $image->getAssetAttributes()->getFilePath() ); } @@ -68,10 +67,10 @@ public function removeCache(ImageFile $image): void /** * @throws FilesystemException */ - public function removeCacheByOriginFilePath(ExtSystem $extSystem, string $path): void + public function removeCacheByOriginFilePath(string $extSystemSlug, string $path): void { $this->fileSystemProvider - ->getCropFilesystemByExtSystemSlug($extSystem->getSlug()) + ->getCropFilesystemByExtSystemSlug($extSystemSlug) ->deleteDirectory( $this->getCacheDir($path) ); diff --git a/src/Domain/Image/Crop/CropProcessor.php b/src/Domain/Image/Crop/CropProcessor.php index 5bd97939..0c774435 100644 --- a/src/Domain/Image/Crop/CropProcessor.php +++ b/src/Domain/Image/Crop/CropProcessor.php @@ -16,6 +16,7 @@ use AnzuSystems\CoreDamBundle\Image\Filter\ResizeFilter; use AnzuSystems\CoreDamBundle\Image\ImageManipulatorInterface; use AnzuSystems\CoreDamBundle\Model\Dto\Image\ImageCropDto; +use AnzuSystems\CoreDamBundle\Model\Enum\ImageMimeTypes; use AnzuSystems\CoreDamBundle\Traits\FileHelperTrait; use League\Flysystem\FilesystemException; @@ -23,8 +24,6 @@ final class CropProcessor { use FileHelperTrait; - public const string DEFAULT_MIME_TYPE = 'image/jpeg'; - public function __construct( private readonly FileSystemProvider $fileSystemProvider, private readonly ImageManipulatorInterface $imageManipulator, @@ -70,12 +69,21 @@ public function applyCrop(ImageFile $image, ImageCropDto $imageCrop): string ]) ); - $content = $this->imageManipulator->getContent($this->fileHelper->guessExtension(self::DEFAULT_MIME_TYPE)); + $content = $this->imageManipulator->getContent($this->fileHelper->guessExtension($this->getCropMimeType($image))); $this->cropCache->store($image, $imageCrop, $content); return $content; } + public static function getCropMimeType(ImageFile $image): string + { + if ($image->getAssetAttributes()->getMimeType() === ImageMimeTypes::MimePng->value) { + return ImageMimeTypes::MimePng->value; + } + + return ImageMimeTypes::MimeJpeg->value; + } + /** * @throws DomainException */ diff --git a/src/Domain/Image/ImageRotator.php b/src/Domain/Image/ImageRotator.php index 321ccb0b..af2c082f 100644 --- a/src/Domain/Image/ImageRotator.php +++ b/src/Domain/Image/ImageRotator.php @@ -4,8 +4,8 @@ namespace AnzuSystems\CoreDamBundle\Domain\Image; -use AnzuSystems\CoreDamBundle\Controller\AbstractImageController; use AnzuSystems\CoreDamBundle\Domain\AssetFile\FileStash; +use AnzuSystems\CoreDamBundle\Domain\Image\Crop\CropProcessor; use AnzuSystems\CoreDamBundle\Domain\Image\FileProcessor\OptimalCropsProcessor; use AnzuSystems\CoreDamBundle\Domain\ImageFileOptimalResize\OptimalResizeFactory; use AnzuSystems\CoreDamBundle\Domain\RegionOfInterest\DefaultRegionOfInterestFactory; @@ -17,10 +17,13 @@ use AnzuSystems\CoreDamBundle\Image\Filter\FilterStack; use AnzuSystems\CoreDamBundle\Image\Filter\RotateFilter; use AnzuSystems\CoreDamBundle\Image\ImageManipulatorInterface; +use AnzuSystems\CoreDamBundle\Traits\FileHelperTrait; use League\Flysystem\FilesystemException; final class ImageRotator { + use FileHelperTrait; + public const float CLOCK_ANGLE = 360.0; public const float MIRROR_ANGLE = 180.0; @@ -99,7 +102,10 @@ private function rotateResize(ImageFileOptimalResize $resize, float $angle, floa ->setWidth($this->imageManipulator->getWidth()) ->setHeight($this->imageManipulator->getHeight()); - $path = $tmpFilesystem->getTmpFileName(AbstractImageController::CROP_EXTENSION); + $path = $tmpFilesystem->getTmpFileName( + $this->fileHelper->guessExtension(CropProcessor::getCropMimeType($resize->getImage())) + ); + $this->imageManipulator->writeToFile($tmpFilesystem->extendPath($path)); // move old file to stash and write new file $this->fileStash->add($resize); diff --git a/src/Domain/Image/ImageStatusFacade.php b/src/Domain/Image/ImageStatusFacade.php index 0eed646c..724d9f44 100644 --- a/src/Domain/Image/ImageStatusFacade.php +++ b/src/Domain/Image/ImageStatusFacade.php @@ -47,13 +47,15 @@ protected function processAssetFile(AssetFile $assetFile, AdapterFile $file): As $imageFile = $this->getImage($assetFile); $this->imageManipulator->loadFile($file->getRealPath()); + // TODo most dominant color memory problems $imageFile->getImageAttributes() ->setAnimated($this->imageManipulator->isAnimated()) - ->setMostDominantColor($this->imageManipulator->getMostDominantColor()) +// ->setMostDominantColor($this->imageManipulator->getMostDominantColor()) ; $this->optimalCropsProcessor->process($imageFile, $file); $this->defaultRoiProcessor->process($imageFile, $file); + $this->imageManipulator->clean(); return $imageFile; } diff --git a/src/Domain/ImageFileOptimalResize/OptimalResizeFactory.php b/src/Domain/ImageFileOptimalResize/OptimalResizeFactory.php index cac14c74..35b7e603 100644 --- a/src/Domain/ImageFileOptimalResize/OptimalResizeFactory.php +++ b/src/Domain/ImageFileOptimalResize/OptimalResizeFactory.php @@ -5,7 +5,7 @@ namespace AnzuSystems\CoreDamBundle\Domain\ImageFileOptimalResize; use AnzuSystems\CommonBundle\Domain\AbstractManager; -use AnzuSystems\CoreDamBundle\Controller\AbstractImageController; +use AnzuSystems\CoreDamBundle\Domain\Image\Crop\CropProcessor; use AnzuSystems\CoreDamBundle\Entity\ImageFile; use AnzuSystems\CoreDamBundle\Entity\ImageFileOptimalResize; use AnzuSystems\CoreDamBundle\Exception\ImageManipulatorException; @@ -13,10 +13,13 @@ use AnzuSystems\CoreDamBundle\FileSystem\NameGenerator\NameGenerator; use AnzuSystems\CoreDamBundle\Image\ImageManipulatorInterface; use AnzuSystems\CoreDamBundle\Model\Dto\File\AdapterFile; +use AnzuSystems\CoreDamBundle\Traits\FileHelperTrait; use League\Flysystem\FilesystemException as FilesystemExceptionAlias; final class OptimalResizeFactory extends AbstractManager { + use FileHelperTrait; + public function __construct( private readonly FileSystemProvider $fileSystemProvider, private readonly NameGenerator $nameGenerator, @@ -61,12 +64,14 @@ public function createCrop(ImageFile $imageFile, AdapterFile $file, int $size): // Prepare path and folder for Visp to write crop file $tmpFilesystem = $this->fileSystemProvider->getTmpFileSystem(); - $tmpPath = $tmpFilesystem->getTmpFileName(AbstractImageController::CROP_EXTENSION); + $tmpPath = $tmpFilesystem->getTmpFileName( + $this->fileHelper->guessExtension(CropProcessor::getCropMimeType($imageFile)) + ); $tmpFilesystem->ensureDirectory($tmpPath); // Write rotated crop file $this->imageManipulator->writeToFile($tmpFilesystem->extendPath($tmpPath), false); - // Write file to target storage + // // Write file to target storage $assetFileSystem = $this->fileSystemProvider->getFilesystemByStorable($imageFile); $storagePath = $this->createOptimalCropPath($imageFile, $size, $imageFile->getImageAttributes()->getRotation()); $assetFileSystem->writeStream($storagePath, $tmpFilesystem->readStream($tmpPath)); @@ -80,6 +85,8 @@ public function createCrop(ImageFile $imageFile, AdapterFile $file, int $size): $optimalResize->setImage($imageFile); $imageFile->getResizes()->add($optimalResize); + $this->imageManipulator->clean(); + return $this->optimalResizeManager->create($optimalResize, false); } } diff --git a/src/Elasticsearch/SearchDto/AuthorAdmSearchDto.php b/src/Elasticsearch/SearchDto/AuthorAdmSearchDto.php index 8d41f9d6..b074311b 100644 --- a/src/Elasticsearch/SearchDto/AuthorAdmSearchDto.php +++ b/src/Elasticsearch/SearchDto/AuthorAdmSearchDto.php @@ -21,6 +21,14 @@ final class AuthorAdmSearchDto extends AbstractSearchDto #[Serialize] protected ?string $type = null; + public function __construct() + { + $this->setOrder([ + 'reviewed' => 'desc', + '_score' => 'desc', + ]); + } + public function getIndexName(): string { return Author::getResourceName(); diff --git a/src/Entity/Keyword.php b/src/Entity/Keyword.php index cc9b5fc9..b0b5aa87 100644 --- a/src/Entity/Keyword.php +++ b/src/Entity/Keyword.php @@ -32,7 +32,7 @@ class Keyword implements UuidIdentifiableInterface, UserTrackingInterface, TimeT public const int NAME_MAX_LENGTH = 255; - #[ORM\Column(type: Types::STRING, length: self::NAME_MAX_LENGTH)] + #[ORM\Column(type: Types::STRING, length: self::NAME_MAX_LENGTH, options: ['collation' => 'utf8mb4_bin'])] #[Serialize] #[Assert\Length( min: 2, diff --git a/src/Event/Subscriber/AssetFileDeleteEventSubscriber.php b/src/Event/Subscriber/AssetFileDeleteEventSubscriber.php index 64c9360c..c741e0c9 100644 --- a/src/Event/Subscriber/AssetFileDeleteEventSubscriber.php +++ b/src/Event/Subscriber/AssetFileDeleteEventSubscriber.php @@ -36,7 +36,7 @@ public function deleteAssetFile(AssetFileDeleteEvent $event): void if ($event->getType()->is(AssetType::Image)) { if (false === empty($event->getAssetFile()->getFilePath())) { $this->cropCache->removeCacheByOriginFilePath( - $event->getAssetFile()->getExtSystem(), + $event->getAssetFile()->getExtSystem()->getSlug(), $event->getAssetFile()->getFilePath() ); } diff --git a/src/FileSystem/FileSystemProvider.php b/src/FileSystem/FileSystemProvider.php index b0851bac..adb344fc 100644 --- a/src/FileSystem/FileSystemProvider.php +++ b/src/FileSystem/FileSystemProvider.php @@ -117,6 +117,13 @@ public function getFilesystemByStorable(FileSystemStorableInterface $storable): return $filesystem; } + public function getStorageNameBySlugAndType(string $slug, AssetType $type): string + { + $extSystemConfig = $this->extSystemConfigurationProvider->getAssetConfiguration($slug, $type); + + return $extSystemConfig->getStorageName(); + } + public function getStorageNameByStorable(FileSystemStorableInterface $storable): string { $extSystemConfig = $this->extSystemConfigurationProvider->getAssetConfiguration( diff --git a/src/Image/ImageManipulatorInterface.php b/src/Image/ImageManipulatorInterface.php index 80d3117f..09157841 100644 --- a/src/Image/ImageManipulatorInterface.php +++ b/src/Image/ImageManipulatorInterface.php @@ -60,4 +60,6 @@ public function getStream(string $extension); * @throws ImageManipulatorException */ public function applyFilterStack(FilterStack $filterStack): void; + + public function clean(bool $clean = true): void; } diff --git a/src/Image/VispImageManipulator.php b/src/Image/VispImageManipulator.php index 49ffcf34..5dfd94c9 100644 --- a/src/Image/VispImageManipulator.php +++ b/src/Image/VispImageManipulator.php @@ -10,6 +10,7 @@ use AnzuSystems\CoreDamBundle\Logger\DamLogger; use AnzuSystems\CoreDamBundle\Model\ValueObject\Color; use AnzuSystems\SerializerBundle\Exception\SerializerException; +use Jcupitt\Vips\Config; use Jcupitt\Vips\Exception; use Jcupitt\Vips\Image; use League\Flysystem\FilesystemException; @@ -38,12 +39,19 @@ public function __construct( parent::__construct($filterProcessorStack); } + public function loadThumbnail(string $path, int $width): void + { + $this->disableCache(); + $this->image = Image::thumbnail($path, $width); + } + /** * @throws ImageManipulatorException */ public function loadFile(string $scrPath): void { try { + $this->disableCache(); $this->image = Image::newFromFile($scrPath); } catch (Exception $exception) { throw new ImageManipulatorException(ImageManipulatorException::ERROR_FILE_READ_FAILED, $exception); @@ -53,6 +61,7 @@ public function loadFile(string $scrPath): void public function loadContent(string $resource): void { try { + $this->disableCache(); $this->image = Image::newFromBuffer($resource); } catch (Exception $exception) { throw new ImageManipulatorException(ImageManipulatorException::ERROR_FILE_READ_FAILED, $exception); @@ -101,6 +110,8 @@ public function getMostDominantColor(bool $clean = true): Color (int) round($b), ); } catch (Throwable) { + $this->clean($clean); + $this->damLogger->info( DamLogger::NAMESPACE_VISP, 'Failed compute most dominant color', @@ -192,6 +203,7 @@ public function rotate(float $angle): void public function autorotate(array $options = []): void { + $this->ensureImage(); $this->image = $this->image->autorot($options); } @@ -231,9 +243,11 @@ public function getHeight(): int return (int) $this->image->height; } - public function loadThumbnail(string $path, int $width): void + public function clean(bool $clean = true): void { - $this->image = Image::thumbnail($path, $width); + if (true === $clean) { + $this->image = null; + } } /** @@ -246,10 +260,11 @@ private function ensureImage(): void } } - private function clean(bool $clean = true): void + private function disableCache(): void { - if (true === $clean) { - $this->image = null; - } + Config::CacheSetMax(0); + Config::CacheSetMaxFiles(0); + Config::CacheSetMaxMem(0); + Config::ConcurrencySet(1); } }