View file File name : Stack.php Content :<?php namespace WebPConvert\Convert\Converters; use WebPConvert\Convert\ConverterFactory; use WebPConvert\Convert\Converters\AbstractConverter; use WebPConvert\Convert\Exceptions\ConversionFailedException; use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException; use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException; use WebPConvert\Convert\Exceptions\ConversionFailed\ConversionSkippedException; use WebPConvert\Options\OptionFactory; //use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException; /** * Convert images to webp by trying a stack of converters until success. * * @package WebPConvert * @author Bjørn Rosell <it@rosell.dk> * @since Class available since Release 2.0.0 */ class Stack extends AbstractConverter { protected function getUnsupportedDefaultOptions() { return [ 'alpha-quality', 'auto-filter', 'encoding', 'low-memory', 'metadata', 'method', 'near-lossless', 'preset', 'sharp-yuv', 'size-in-percentage', 'skip', 'default-quality', 'quality', 'max-quality', ]; } public function getUniqueOptions($imageType) { return OptionFactory::createOptions([ ['converters', 'array', [ 'title' => 'Converters', 'description' => 'Converters to try, ordered by priority.', 'default' => self::getAvailableConverters(), 'sensitive' => true, 'ui' => [ 'component' => 'multi-select', 'options' => self::getAvailableConverters(), 'advanced' => true ] ]], ['converter-options', 'array', [ 'title' => 'Converter options', 'description' => 'Extra options for specific converters.', 'default' => [], 'sensitive' => true, 'ui' => null ]], ['preferred-converters', 'array', [ 'title' => 'Preferred converters', 'description' => 'With this option you can move specified converters to the top of the stack. ' . 'The converters are specified by id.', 'default' => [], 'ui' => null ]], ['extra-converters', 'array', [ 'title' => 'Extra converters', 'description' => 'Add extra converters to the bottom of the stack', 'default' => [], 'sensitive' => true, 'ui' => null ]], ['shuffle', 'boolean', [ 'title' => 'Shuffle', 'description' => 'Shuffles the converter order on each conversion. ' . 'Can for example be used to spread out requests on multiple cloud converters', 'default' => false, 'ui' => [ 'component' => 'checkbox', 'advanced' => true ] ]], ]); /* return [ new SensitiveArrayOption('converters', self::getAvailableConverters()), new SensitiveArrayOption('converter-options', []), new BooleanOption('shuffle', false), new ArrayOption('preferred-converters', []), new SensitiveArrayOption('extra-converters', []) ];*/ } /** * Get available converters (ids) - ordered by awesomeness. * * @return array An array of ids of converters that comes with this library */ public static function getAvailableConverters() { return [ 'cwebp', 'vips', 'imagick', 'gmagick', 'imagemagick', 'graphicsmagick', 'wpc', 'ffmpeg', 'ewww', 'gd' ]; } /** * Check (general) operationality of imagack converter executable * * @throws SystemRequirementsNotMetException if system requirements are not met */ public function checkOperationality() { if (count($this->options['converters']) == 0) { throw new ConverterNotOperationalException( 'Converter stack is empty! - no converters to try, no conversion can be made!' ); } // TODO: We should test if all converters are found in order to detect problems early //$this->logLn('Stack converter ignited'); } protected function doActualConvert() { $options = $this->options; $beginTimeStack = microtime(true); $anyRuntimeErrors = false; $converters = $options['converters']; if (count($options['extra-converters']) > 0) { $converters = array_merge($converters, $options['extra-converters']); /*foreach ($options['extra-converters'] as $extra) { $converters[] = $extra; }*/ } // preferred-converters if (count($options['preferred-converters']) > 0) { foreach (array_reverse($options['preferred-converters']) as $prioritizedConverter) { foreach ($converters as $i => $converter) { if (is_array($converter)) { $converterId = $converter['converter']; } else { $converterId = $converter; } if ($converterId == $prioritizedConverter) { unset($converters[$i]); array_unshift($converters, $converter); break; } } } // perhaps write the order to the log? (without options) - but this requires some effort } // shuffle if ($options['shuffle']) { shuffle($converters); } //$this->logLn(print_r($converters)); //$options['converters'] = $converters; //$defaultConverterOptions = $options; $defaultConverterOptions = []; foreach ($this->options2->getOptionsMap() as $id => $option) { // Right here, there used to be a check that ensured that unknown options was not passed down to the // converters (" && !($option instanceof GhostOption)"). But well, as the Stack doesn't know about // converter specific options, such as "try-cwebp", these was not passed down (see #259) // I'm not sure why the check was made in the first place, but it does not seem neccessary, as the // converters simply ignore unknown options. So the check has now been removed. if ($option->isValueExplicitlySet()) { $defaultConverterOptions[$id] = $option->getValue(); } } //unset($defaultConverterOptions['converters']); //unset($defaultConverterOptions['converter-options']); $defaultConverterOptions['_skip_input_check'] = true; $defaultConverterOptions['_suppress_success_message'] = true; unset($defaultConverterOptions['converters']); unset($defaultConverterOptions['extra-converters']); unset($defaultConverterOptions['converter-options']); unset($defaultConverterOptions['preferred-converters']); unset($defaultConverterOptions['shuffle']); // $this->logLn('converters: ' . print_r($converters, true)); //return; foreach ($converters as $converter) { if (is_array($converter)) { $converterId = $converter['converter']; $converterOptions = isset($converter['options']) ? $converter['options'] : []; } else { $converterId = $converter; $converterOptions = []; if (isset($options['converter-options'][$converterId])) { // Note: right now, converter-options are not meant to be used, // when you have several converters of the same type $converterOptions = $options['converter-options'][$converterId]; } } $converterOptions = array_merge($defaultConverterOptions, $converterOptions); /* if ($converterId != 'stack') { //unset($converterOptions['converters']); //unset($converterOptions['converter-options']); } else { //$converterOptions['converter-options'] = $this->logLn('STACK'); $this->logLn('converterOptions: ' . print_r($converterOptions, true)); }*/ $beginTime = microtime(true); $this->ln(); $this->logLn($converterId . ' converter ignited', 'bold'); $converter = ConverterFactory::makeConverter( $converterId, $this->source, $this->destination, $converterOptions, $this->logger ); try { $converter->doConvert(); //self::runConverterWithTiming($converterId, $source, $destination, $converterOptions, false, $logger); $this->logLn($converterId . ' succeeded :)'); //throw new ConverterNotOperationalException('...'); return; } catch (ConverterNotOperationalException $e) { $this->logLn($e->getMessage()); } catch (ConversionSkippedException $e) { $this->logLn($e->getMessage()); } catch (ConversionFailedException $e) { $this->logLn($e->getMessage(), 'italic'); $prev = $e->getPrevious(); if (!is_null($prev)) { $this->logLn($prev->getMessage(), 'italic'); $this->logLn(' in ' . $prev->getFile() . ', line ' . $prev->getLine(), 'italic'); $this->ln(); } //$this->logLn($e->getTraceAsString()); $anyRuntimeErrors = true; } $this->logLn($converterId . ' failed in ' . round((microtime(true) - $beginTime) * 1000) . ' ms'); } $this->ln(); $this->logLn('Stack failed in ' . round((microtime(true) - $beginTimeStack) * 1000) . ' ms'); // Hm, Scrutinizer complains that $anyRuntimeErrors is always false. But that is not true! if ($anyRuntimeErrors) { // At least one converter failed throw new ConversionFailedException( 'None of the converters in the stack could convert the image.' ); } else { // All converters threw a SystemRequirementsNotMetException throw new ConverterNotOperationalException('None of the converters in the stack are operational'); } } }