Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
56.00% |
42 / 75 |
|
60.00% |
3 / 5 |
CRAP | |
0.00% |
0 / 1 |
Stack | |
56.00% |
42 / 75 |
|
60.00% |
3 / 5 |
73.07 | |
0.00% |
0 / 1 |
getUnsupportedDefaultOptions | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUniqueOptions | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getAvailableConverters | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
checkOperationality | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
doActualConvert | |
51.52% |
34 / 66 |
|
0.00% |
0 / 1 |
60.15 |
1 | <?php |
2 | |
3 | namespace WebPConvert\Convert\Converters; |
4 | |
5 | use WebPConvert\Convert\ConverterFactory; |
6 | use WebPConvert\Convert\Converters\AbstractConverter; |
7 | use WebPConvert\Convert\Exceptions\ConversionFailedException; |
8 | use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException; |
9 | use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException; |
10 | use WebPConvert\Convert\Exceptions\ConversionFailed\ConversionSkippedException; |
11 | use WebPConvert\Options\OptionFactory; |
12 | |
13 | //use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException; |
14 | |
15 | /** |
16 | * Convert images to webp by trying a stack of converters until success. |
17 | * |
18 | * @package WebPConvert |
19 | * @author Bjørn Rosell <it@rosell.dk> |
20 | * @since Class available since Release 2.0.0 |
21 | */ |
22 | class Stack extends AbstractConverter |
23 | { |
24 | |
25 | protected function getUnsupportedDefaultOptions() |
26 | { |
27 | return [ |
28 | 'alpha-quality', |
29 | 'auto-filter', |
30 | 'encoding', |
31 | 'low-memory', |
32 | 'metadata', |
33 | 'method', |
34 | 'near-lossless', |
35 | 'preset', |
36 | 'sharp-yuv', |
37 | 'size-in-percentage', |
38 | 'skip', |
39 | 'default-quality', |
40 | 'quality', |
41 | 'max-quality', |
42 | ]; |
43 | } |
44 | |
45 | public function getUniqueOptions($imageType) |
46 | { |
47 | return OptionFactory::createOptions([ |
48 | ['converters', 'array', [ |
49 | 'title' => 'Converters', |
50 | 'description' => 'Converters to try, ordered by priority.', |
51 | 'default' => self::getAvailableConverters(), |
52 | 'sensitive' => true, |
53 | 'ui' => [ |
54 | 'component' => 'multi-select', |
55 | 'options' => self::getAvailableConverters(), |
56 | 'advanced' => true |
57 | ] |
58 | ]], |
59 | ['converter-options', 'array', [ |
60 | 'title' => 'Converter options', |
61 | 'description' => |
62 | 'Extra options for specific converters.', |
63 | 'default' => [], |
64 | 'sensitive' => true, |
65 | 'ui' => null |
66 | ]], |
67 | ['preferred-converters', 'array', [ |
68 | 'title' => 'Preferred converters', |
69 | 'description' => |
70 | 'With this option you can move specified converters to the top of the stack. ' . |
71 | 'The converters are specified by id.', |
72 | 'default' => [], |
73 | 'ui' => null |
74 | ]], |
75 | ['extra-converters', 'array', [ |
76 | 'title' => 'Extra converters', |
77 | 'description' => |
78 | 'Add extra converters to the bottom of the stack', |
79 | 'default' => [], |
80 | 'sensitive' => true, |
81 | 'ui' => null |
82 | ]], |
83 | ['shuffle', 'boolean', [ |
84 | 'title' => 'Shuffle', |
85 | 'description' => |
86 | 'Shuffles the converter order on each conversion. ' . |
87 | 'Can for example be used to spread out requests on multiple cloud converters', |
88 | 'default' => false, |
89 | 'ui' => [ |
90 | 'component' => 'checkbox', |
91 | 'advanced' => true |
92 | ] |
93 | ]], |
94 | ]); |
95 | |
96 | |
97 | /* |
98 | return [ |
99 | new SensitiveArrayOption('converters', self::getAvailableConverters()), |
100 | new SensitiveArrayOption('converter-options', []), |
101 | new BooleanOption('shuffle', false), |
102 | new ArrayOption('preferred-converters', []), |
103 | new SensitiveArrayOption('extra-converters', []) |
104 | ];*/ |
105 | } |
106 | |
107 | /** |
108 | * Get available converters (ids) - ordered by awesomeness. |
109 | * |
110 | * @return array An array of ids of converters that comes with this library |
111 | */ |
112 | public static function getAvailableConverters() |
113 | { |
114 | return [ |
115 | 'cwebp', 'vips', 'imagick', 'gmagick', 'imagemagick', 'graphicsmagick', 'wpc', 'ffmpeg', 'ewww', 'gd' |
116 | ]; |
117 | } |
118 | |
119 | /** |
120 | * Check (general) operationality of imagack converter executable |
121 | * |
122 | * @throws SystemRequirementsNotMetException if system requirements are not met |
123 | */ |
124 | public function checkOperationality() |
125 | { |
126 | if (count($this->options['converters']) == 0) { |
127 | throw new ConverterNotOperationalException( |
128 | 'Converter stack is empty! - no converters to try, no conversion can be made!' |
129 | ); |
130 | } |
131 | |
132 | // TODO: We should test if all converters are found in order to detect problems early |
133 | |
134 | //$this->logLn('Stack converter ignited'); |
135 | } |
136 | |
137 | protected function doActualConvert() |
138 | { |
139 | $options = $this->options; |
140 | |
141 | $beginTimeStack = microtime(true); |
142 | |
143 | $anyRuntimeErrors = false; |
144 | |
145 | $converters = $options['converters']; |
146 | if (count($options['extra-converters']) > 0) { |
147 | $converters = array_merge($converters, $options['extra-converters']); |
148 | /*foreach ($options['extra-converters'] as $extra) { |
149 | $converters[] = $extra; |
150 | }*/ |
151 | } |
152 | |
153 | // preferred-converters |
154 | if (count($options['preferred-converters']) > 0) { |
155 | foreach (array_reverse($options['preferred-converters']) as $prioritizedConverter) { |
156 | foreach ($converters as $i => $converter) { |
157 | if (is_array($converter)) { |
158 | $converterId = $converter['converter']; |
159 | } else { |
160 | $converterId = $converter; |
161 | } |
162 | if ($converterId == $prioritizedConverter) { |
163 | unset($converters[$i]); |
164 | array_unshift($converters, $converter); |
165 | break; |
166 | } |
167 | } |
168 | } |
169 | // perhaps write the order to the log? (without options) - but this requires some effort |
170 | } |
171 | |
172 | // shuffle |
173 | if ($options['shuffle']) { |
174 | shuffle($converters); |
175 | } |
176 | |
177 | //$this->logLn(print_r($converters)); |
178 | //$options['converters'] = $converters; |
179 | //$defaultConverterOptions = $options; |
180 | $defaultConverterOptions = []; |
181 | |
182 | foreach ($this->options2->getOptionsMap() as $id => $option) { |
183 | // Right here, there used to be a check that ensured that unknown options was not passed down to the |
184 | // converters (" && !($option instanceof GhostOption)"). But well, as the Stack doesn't know about |
185 | // converter specific options, such as "try-cwebp", these was not passed down (see #259) |
186 | // I'm not sure why the check was made in the first place, but it does not seem neccessary, as the |
187 | // converters simply ignore unknown options. So the check has now been removed. |
188 | if ($option->isValueExplicitlySet()) { |
189 | $defaultConverterOptions[$id] = $option->getValue(); |
190 | } |
191 | } |
192 | |
193 | //unset($defaultConverterOptions['converters']); |
194 | //unset($defaultConverterOptions['converter-options']); |
195 | $defaultConverterOptions['_skip_input_check'] = true; |
196 | $defaultConverterOptions['_suppress_success_message'] = true; |
197 | unset($defaultConverterOptions['converters']); |
198 | unset($defaultConverterOptions['extra-converters']); |
199 | unset($defaultConverterOptions['converter-options']); |
200 | unset($defaultConverterOptions['preferred-converters']); |
201 | unset($defaultConverterOptions['shuffle']); |
202 | |
203 | // $this->logLn('converters: ' . print_r($converters, true)); |
204 | |
205 | //return; |
206 | foreach ($converters as $converter) { |
207 | if (is_array($converter)) { |
208 | $converterId = $converter['converter']; |
209 | $converterOptions = isset($converter['options']) ? $converter['options'] : []; |
210 | } else { |
211 | $converterId = $converter; |
212 | $converterOptions = []; |
213 | if (isset($options['converter-options'][$converterId])) { |
214 | // Note: right now, converter-options are not meant to be used, |
215 | // when you have several converters of the same type |
216 | $converterOptions = $options['converter-options'][$converterId]; |
217 | } |
218 | } |
219 | $converterOptions = array_merge($defaultConverterOptions, $converterOptions); |
220 | /* |
221 | if ($converterId != 'stack') { |
222 | //unset($converterOptions['converters']); |
223 | //unset($converterOptions['converter-options']); |
224 | } else { |
225 | //$converterOptions['converter-options'] = |
226 | $this->logLn('STACK'); |
227 | $this->logLn('converterOptions: ' . print_r($converterOptions, true)); |
228 | }*/ |
229 | |
230 | $beginTime = microtime(true); |
231 | |
232 | $this->ln(); |
233 | $this->logLn($converterId . ' converter ignited', 'bold'); |
234 | |
235 | $converter = ConverterFactory::makeConverter( |
236 | $converterId, |
237 | $this->source, |
238 | $this->destination, |
239 | $converterOptions, |
240 | $this->logger |
241 | ); |
242 | |
243 | try { |
244 | $converter->doConvert(); |
245 | |
246 | //self::runConverterWithTiming($converterId, $source, $destination, $converterOptions, false, $logger); |
247 | |
248 | $this->logLn($converterId . ' succeeded :)'); |
249 | //throw new ConverterNotOperationalException('...'); |
250 | return; |
251 | } catch (ConverterNotOperationalException $e) { |
252 | $this->logLn($e->getMessage()); |
253 | } catch (ConversionSkippedException $e) { |
254 | $this->logLn($e->getMessage()); |
255 | } catch (ConversionFailedException $e) { |
256 | $this->logLn($e->getMessage(), 'italic'); |
257 | $prev = $e->getPrevious(); |
258 | if (!is_null($prev)) { |
259 | $this->logLn($prev->getMessage(), 'italic'); |
260 | $this->logLn(' in ' . $prev->getFile() . ', line ' . $prev->getLine(), 'italic'); |
261 | $this->ln(); |
262 | } |
263 | //$this->logLn($e->getTraceAsString()); |
264 | $anyRuntimeErrors = true; |
265 | } |
266 | $this->logLn($converterId . ' failed in ' . round((microtime(true) - $beginTime) * 1000) . ' ms'); |
267 | } |
268 | |
269 | $this->ln(); |
270 | $this->logLn('Stack failed in ' . round((microtime(true) - $beginTimeStack) * 1000) . ' ms'); |
271 | |
272 | // Hm, Scrutinizer complains that $anyRuntimeErrors is always false. But that is not true! |
273 | if ($anyRuntimeErrors) { |
274 | // At least one converter failed |
275 | throw new ConversionFailedException( |
276 | 'None of the converters in the stack could convert the image.' |
277 | ); |
278 | } else { |
279 | // All converters threw a SystemRequirementsNotMetException |
280 | throw new ConverterNotOperationalException('None of the converters in the stack are operational'); |
281 | } |
282 | } |
283 | } |