Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.43% covered (warning)
78.43%
40 / 51
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
FFMpeg
78.43% covered (warning)
78.43%
40 / 51
25.00% covered (danger)
25.00%
2 / 8
29.78
0.00% covered (danger)
0.00%
0 / 1
 getUnsupportedDefaultOptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUniqueOptions
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPath
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 isInstalled
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isWebPDelegateInstalled
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 checkOperationality
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
4.12
 createCommandLineOptions
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
6.01
 doActualConvert
78.57% covered (warning)
78.57%
11 / 14
0.00% covered (danger)
0.00%
0 / 1
6.35
1<?php
2
3namespace WebPConvert\Convert\Converters;
4
5use WebPConvert\Convert\Converters\AbstractConverter;
6use WebPConvert\Convert\Converters\ConverterTraits\ExecTrait;
7use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
8use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
9use WebPConvert\Convert\Exceptions\ConversionFailedException;
10use WebPConvert\Options\OptionFactory;
11use ExecWithFallback\ExecWithFallback;
12
13//use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
14
15/**
16 * Convert images to webp by calling imagemagick binary.
17 *
18 * @package    WebPConvert
19 * @author     Bjørn Rosell <it@rosell.dk>
20 * @since      Class available since Release 2.0.0
21 */
22class FFMpeg extends AbstractConverter
23{
24    use ExecTrait;
25    use EncodingAutoTrait;
26
27    protected function getUnsupportedDefaultOptions()
28    {
29        return [
30            'alpha-quality',
31            'auto-filter',
32            'low-memory',
33            'metadata',
34            'near-lossless',
35            'sharp-yuv',
36            'size-in-percentage',
37        ];
38    }
39
40    /**
41     * Get the options unique for this converter
42     *
43     * @return  array  Array of options
44     */
45    public function getUniqueOptions($imageType)
46    {
47        return OptionFactory::createOptions([
48            self::niceOption()
49        ]);
50    }
51
52    private function getPath()
53    {
54        if (defined('WEBPCONVERT_FFMPEG_PATH')) {
55            return constant('WEBPCONVERT_FFMPEG_PATH');
56        }
57        if (!empty(getenv('WEBPCONVERT_FFMPEG_PATH'))) {
58            return getenv('WEBPCONVERT_FFMPEG_PATH');
59        }
60        return 'ffmpeg';
61    }
62
63    public function isInstalled()
64    {
65        ExecWithFallback::exec($this->getPath() . ' -version 2>&1', $output, $returnCode);
66        return ($returnCode == 0);
67    }
68
69    // Check if webp delegate is installed
70    public function isWebPDelegateInstalled()
71    {
72        ExecWithFallback::exec($this->getPath() . ' -version 2>&1', $output, $returnCode);
73        foreach ($output as $line) {
74            if (preg_match('# --enable-libwebp#i', $line)) {
75                return true;
76            }
77        }
78        return false;
79    }
80
81    /**
82     * Check (general) operationality of imagack converter executable
83     *
84     * @throws SystemRequirementsNotMetException  if system requirements are not met
85     */
86    public function checkOperationality()
87    {
88        $this->checkOperationalityExecTrait();
89
90        if (!$this->isInstalled()) {
91            throw new SystemRequirementsNotMetException(
92                'ffmpeg is not installed (cannot execute: "' . $this->getPath() . '")'
93            );
94        }
95        if (!$this->isWebPDelegateInstalled()) {
96            throw new SystemRequirementsNotMetException('ffmpeg was compiled without libwebp');
97        }
98    }
99
100    /**
101     * Build command line options
102     *
103     * @return string
104     */
105    private function createCommandLineOptions()
106    {
107        // PS: Available webp options for ffmpeg are documented here:
108        // https://www.ffmpeg.org/ffmpeg-codecs.html#libwebp
109
110        $commandArguments = [];
111
112        $commandArguments[] = '-i';
113        $commandArguments[] = escapeshellarg($this->source);
114
115        // preset. Appears first in the list as recommended in the cwebp docs
116        if (!is_null($this->options['preset'])) {
117            if ($this->options['preset'] != 'none') {
118                $commandArguments[] = '-preset ' . $this->options['preset'];
119            }
120        }
121
122        // Overwrite existing files?, yes!
123        $commandArguments[] = '-y';
124
125        if ($this->isQualityDetectionRequiredButFailing()) {
126            // quality:auto was specified, but could not be determined.
127            // we cannot apply the max-quality logic, but we can provide auto quality
128            // simply by not specifying the quality option.
129        } else {
130            $commandArguments[] = '-qscale ' . escapeshellarg($this->getCalculatedQuality());
131        }
132        if ($this->options['encoding'] == 'lossless') {
133            $commandArguments[] = '-lossless 1';
134        } else {
135            $commandArguments[] = '-lossless 0';
136        }
137
138        if ($this->options['metadata'] == 'none') {
139            // Unfortunately there seems to be no easy solution available for removing all metadata.
140        }
141
142        // compression_level maps to method, according to https://www.ffmpeg.org/ffmpeg-codecs.html#libwebp
143        $commandArguments[] = '-compression_level ' . $this->options['method'];
144
145        $commandArguments[] = escapeshellarg($this->destination);
146
147
148        return implode(' ', $commandArguments);
149    }
150
151    protected function doActualConvert()
152    {
153        //$this->logLn($this->getVersion());
154
155        $command = $this->getPath() . ' ' . $this->createCommandLineOptions() . ' 2>&1';
156
157        $useNice = ($this->options['use-nice'] && $this->checkNiceSupport());
158        if ($useNice) {
159            $command = 'nice ' . $command;
160        }
161        $this->logLn('Executing command: ' . $command);
162        ExecWithFallback::exec($command, $output, $returnCode);
163
164        $this->logExecOutput($output);
165        if ($returnCode == 0) {
166            $this->logLn('success');
167        } else {
168            $this->logLn('return code: ' . $returnCode);
169        }
170
171        if ($returnCode == 127) {
172            throw new SystemRequirementsNotMetException('ffmpeg is not installed');
173        }
174        if ($returnCode != 0) {
175            throw new SystemRequirementsNotMetException('The exec() call failed');
176        }
177    }
178}