├── Resources └── Private │ └── Library │ ├── .flowFileMonitorIgnore │ ├── .gitignore │ └── package.json ├── composer.json ├── LICENSE ├── Configuration └── Settings.yaml ├── README.md └── Classes └── Aspects └── ThumbnailAspect.php /Resources/Private/Library/.flowFileMonitorIgnore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Resources/Private/Library/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Resources/Private/Library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Libraries", 3 | "description": "Fetch image processing binaries", 4 | "dependencies": { 5 | "gifsicle": "2.0.*", 6 | "jpegtran-bin": "2.0.*", 7 | "optipng-bin": "2.0.*", 8 | "svgo": "0.5.*" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/mocdk/MOC.ImageOptimizer.git" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moc/imageoptimizer", 3 | "type": "neos-package", 4 | "description": "Flow package that optimizes generated thumbnail images (jpg, png, gif, svg) for web presentation.", 5 | "keywords": [ "imageoptimization", "performance", "images", "media", "neos", "flow" ], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Aske Ertmann", 10 | "email": "aske@ertmann.co" 11 | } 12 | ], 13 | "require": { 14 | "neos/media": ">=5" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "MOC\\ImageOptimizer\\": "Classes" 19 | } 20 | }, 21 | "extra": { 22 | "branch-alias": { 23 | "dev-master": "3.0.x-dev" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Neos project contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Configuration/Settings.yaml: -------------------------------------------------------------------------------- 1 | MOC: 2 | ImageOptimizer: 3 | useGlobalBinary: false # use globally installed binaries for all formats instead 4 | globalBinaryPath: '' 5 | formats: 6 | 'image/jpeg': 7 | enabled: true 8 | useGlobalBinary: false # use globally installed binary for the jpeg library instead 9 | library: 'jpegtran' 10 | binaryPath: 'jpegtran-bin/vendor/jpegtran' 11 | arguments: "${'-copy none -optimize ' + (progressive ? '-progressive ' : '') + '-outfile ' + file + ' ' + file}" 12 | parameters: 13 | progressive: true # whether or not to serve progressive jpgs 14 | 15 | 'image/png': 16 | enabled: true 17 | useGlobalBinary: false # use globally installed binary for the png library instead 18 | library: 'optipng' 19 | binaryPath: 'optipng-bin/vendor/optipng' 20 | arguments: "${'-o' + optimizationLevel + ' -strip all -out ' + file + ' ' + file}" 21 | parameters: 22 | optimizationLevel: 2 # optimization level (0-7) 23 | 24 | 'image/gif': 25 | enabled: true 26 | useGlobalBinary: false # use globally installed binary for the gif library instead 27 | library: 'gifsicle' 28 | binaryPath: 'gifsicle/vendor/gifsicle' 29 | arguments: "${'--batch -O' + optimizationLevel + ' ' + file}" 30 | parameters: 31 | optimizationLevel: 2 # optimization level (1-3) 32 | 33 | 'image/svg+xml': 34 | enabled: true 35 | useGlobalBinary: false # use globally installed binary for the svg library instead 36 | library: 'svgo' 37 | binaryPath: 'svgo/bin/svgo' 38 | arguments: "${(pretty ? '--pretty ' : '') + file}" 39 | parameters: 40 | pretty: false # keep the output readable 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MOC.ImageOptimizer 2 | ================== 3 | 4 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mocdk/MOC.ImageOptimizer/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/mocdk/MOC.ImageOptimizer/?branch=master) 5 | [![Latest Stable Version](https://poser.pugx.org/moc/imageoptimizer/v/stable)](https://packagist.org/packages/moc/imageoptimizer) 6 | [![Total Downloads](https://poser.pugx.org/moc/imageoptimizer/downloads)](https://packagist.org/packages/moc/imageoptimizer) 7 | [![License](https://poser.pugx.org/moc/imageoptimizer/license)](https://packagist.org/packages/moc/imageoptimizer) 8 | 9 | Introduction 10 | ------------ 11 | 12 | Neos CMS / Flow framework package that optimizes generated thumbnail images (jpg, png, gif, svg and more) for web presentation. 13 | 14 | Original files are never affected since copies are always created for thumbnails. 15 | 16 | Non-blocking during rendering (asynchronous) optimization. 17 | 18 | Using jpegtran, optipng, gifsicle and svgo or alternative customizible ones for the optimizations. 19 | 20 | Should work with Linux, FreeBSD, OSX, SunOS & Windows (only tested Linux & FreeBSD so far). 21 | 22 | Compatible with Neos 1.x - 5.x / Flow 1.x - 6.x 23 | 24 | ##### Only supports local file system (no CDN support yet) (see #10) 25 | 26 | Installation 27 | ------------ 28 | 29 | Requires npm (node.js) to work out of the box, although binaries can also be installed manually without it. 30 | 31 | `composer require "moc/imageoptimizer" "~4.0"` 32 | 33 | Ensure the image manipulation libraries `jpegtran` (JPG), `optipng` (PNG), `gifsicle` (GIF) and `svgo` (SVG) are installed globally. Libraries can be skipped if desired, just make sure to disable those mimetypes. 34 | 35 | Alternatively install them using `npm`: 36 | ``` 37 | # Globally 38 | npm install -g jpegtran-bin optipng-bin gifsicle svgo 39 | 40 | # Locally 41 | npm install --prefix Packages/Application/MOC.ImageOptimizer/Resources/Private/Library 42 | ``` 43 | 44 | Configuration 45 | ------------- 46 | 47 | Using the `Settings` configuration, multiple options can be adjusted. 48 | 49 | Optimization can be disabled for specific file formats. 50 | 51 | Additionally options such as optimization level (png & gif), progressive (jpg), pretty (svg) can be adjusted depending on optimization library. 52 | 53 | Usage of global available binaries can be configured instead or for specific formats. 54 | 55 | Enable using the setting `MOC.ImageOptimizer.useGlobalBinary` and configure the path in `MOC.ImageOptimizer.globalBinaryPath`. 56 | 57 | Use alternative libraries for optimization 58 | ------------------------------------------ 59 | 60 | You can replace the preconfigured libraries with alternative ones. 61 | 62 | Example: 63 | 64 | Add the following to your `Settings` to use `jpegoptim` instead of `jpegtran`: 65 | 66 | MOC: 67 | ImageOptimizer: 68 | formats: 69 | 'image/jpeg': 70 | enabled: true 71 | library: 'jpegoptim' 72 | binaryPath: 'jpegoptim-bin/vendor/jpegoptim' 73 | arguments: "${'--strip-all --max=' + quality + ' ' + (progressive ? '--all-progressive ' : '') + '-o ' + file}" 74 | parameters: 75 | progressive: true # whether or not to serve progressive jpgs 76 | quality: 80 # quality level (1-100) 77 | 78 | When doing this you have to take care that you provide the necessary library yourself as it's not included 79 | when doing the installation like described above. 80 | 81 | Usage 82 | ----- 83 | 84 | * Clear thumbnails to generate new ones that will automatically be optimized. 85 | 86 | `./flow media:clearthumbnails` 87 | 88 | * See system log for debugging and error output. 89 | -------------------------------------------------------------------------------- /Classes/Aspects/ThumbnailAspect.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 59 | } 60 | 61 | /** 62 | * After a thumbnail has been refreshed the resource is optimized, meaning the 63 | * image is only optimized once when created. 64 | * 65 | * A new resource is generated for every thumbnail, meaning the original is 66 | * never touched. 67 | * 68 | * Only local file system target is supported to keep it from being blocking. 69 | * It would however be possible to create a local copy of the resource, 70 | * process it, import it and set that as the thumbnail resource. 71 | * 72 | * @Flow\AfterReturning("method(Neos\Media\Domain\Model\Thumbnail->refresh())") 73 | * @param JoinPointInterface $joinPoint The current join point 74 | * @return void 75 | * @throws Exception 76 | * @throws UnknownPackageException 77 | */ 78 | public function optimizeThumbnail(JoinPointInterface $joinPoint) 79 | { 80 | /** @var Thumbnail $thumbnail */ 81 | $thumbnail = $joinPoint->getProxy(); 82 | $thumbnailResource = $thumbnail->getResource(); 83 | if (!$thumbnailResource) { 84 | return; 85 | } 86 | 87 | $streamMetaData = stream_get_meta_data($thumbnailResource->getStream()); 88 | $pathAndFilename = $streamMetaData['uri']; 89 | 90 | $useGlobalBinary = $this->settings['useGlobalBinary']; 91 | $binaryRootPath = 'Private/Library/node_modules/'; 92 | $file = escapeshellarg($pathAndFilename); 93 | $imageType = $thumbnailResource->getMediaType(); 94 | 95 | if (!array_key_exists($imageType, $this->settings['formats'])) { 96 | $this->systemLogger->info(sprintf('Unsupported type "%s" skipped in optimizeThumbnail', $imageType), LogEnvironment::fromMethodName(__METHOD__)); 97 | return; 98 | } 99 | 100 | $librarySettings = $this->settings['formats'][$imageType]; 101 | 102 | if ($librarySettings['enabled'] === false) { 103 | return; 104 | } 105 | 106 | if ($librarySettings['useGlobalBinary'] === true) { 107 | $useGlobalBinary = true; 108 | } 109 | 110 | $library = $librarySettings['library']; 111 | $binaryPath = $librarySettings['binaryPath']; 112 | $eelExpression = $librarySettings['arguments']; 113 | $parameters = array_merge($librarySettings['parameters'], ['file' => $file]); 114 | $arguments = Utility::evaluateEelExpression($eelExpression, $this->eelEvaluator, $parameters); 115 | 116 | $binaryPath = $useGlobalBinary === true ? $this->settings['globalBinaryPath'] . $library : $this->packageManager->getPackage('MOC.ImageOptimizer')->getResourcesPath() . $binaryRootPath . $binaryPath; 117 | $cmd = escapeshellcmd($binaryPath) . ' ' . $arguments; 118 | $output = []; 119 | exec($cmd, $output, $result); 120 | $failed = (int)$result !== 0; 121 | 122 | $this->systemLogger->log($failed ? LogLevel::ERROR : LogLevel::INFO, $cmd . ' (' . ($failed ? 'Error: ' . $result : 'OK') . ')', array_merge(LogEnvironment::fromMethodName(__METHOD__), $output)); 123 | } 124 | } 125 | --------------------------------------------------------------------------------