Issues (306)

src/services/Transcode.php (99 issues)

1
<?php
2
/**
3
 * Transcoder plugin for Craft CMS
4
 *
5
 * Transcode videos to various formats, and provide thumbnails of the video
6
 *
7
 * @link      https://nystudio107.com
0 ignored issues
show
The tag in position 1 should be the @copyright tag
Loading history...
8
 * @copyright Copyright (c) 2017 nystudio107
0 ignored issues
show
@copyright tag must contain a year and the name of the copyright holder
Loading history...
9
 */
0 ignored issues
show
PHP version not specified
Loading history...
Missing @category tag in file comment
Loading history...
Missing @package tag in file comment
Loading history...
Missing @author tag in file comment
Loading history...
Missing @license tag in file comment
Loading history...
10
11
namespace nystudio107\transcoder\services;
12
13
use Craft;
14
use craft\base\Component;
15
use craft\elements\Asset;
16
use craft\events\AssetThumbEvent;
17
use craft\helpers\FileHelper;
18
use craft\helpers\Json as JsonHelper;
19
use craft\volumes\Local;
20
use mikehaertl\shellcommand\Command as ShellCommand;
21
use nystudio107\transcoder\Transcoder;
22
use yii\base\Exception;
23
use yii\base\InvalidConfigException;
24
use yii\validators\UrlValidator;
25
use function count;
26
use function function_exists;
27
use function in_array;
28
use function is_bool;
29
use function is_object;
30
31
/**
0 ignored issues
show
Missing short description in doc comment
Loading history...
32
 * @author    nystudio107
0 ignored issues
show
The tag in position 1 should be the @package tag
Loading history...
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
33
 * @package   Transcode
0 ignored issues
show
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
34
 * @since     1.0.0
0 ignored issues
show
The tag in position 3 should be the @author tag
Loading history...
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
35
 */
0 ignored issues
show
Missing @category tag in class comment
Loading history...
Missing @license tag in class comment
Loading history...
Missing @link tag in class comment
Loading history...
36
class Transcode extends Component
37
{
38
    // Constants
39
    // =========================================================================
40
41
    // Suffixes to add to the generated filename params
42
    const SUFFIX_MAP = [
43
        'videoFrameRate' => 'fps',
44
        'videoBitRate' => 'bps',
45
        'audioBitRate' => 'bps',
46
        'audioChannels' => 'c',
47
        'height' => 'h',
48
        'width' => 'w',
49
        'timeInSecs' => 's',
50
    ];
51
52
    // Params that should be excluded from being part of the generated filename
53
    const EXCLUDE_PARAMS = [
54
        'videoEncoder',
55
        'audioEncoder',
56
        'fileSuffix',
57
        'sharpen',
58
        'synchronous',
59
        'stripMetadata',
60
    ];
61
62
    // Mappings for getFileInfo() summary values
63
    const INFO_SUMMARY = [
64
        'format' => [
65
            'filename' => 'filename',
66
            'duration' => 'duration',
67
            'size' => 'size',
68
        ],
69
        'audio' => [
70
            'codec_name' => 'audioEncoder',
71
            'bit_rate' => 'audioBitRate',
72
            'sample_rate' => 'audioSampleRate',
73
            'channels' => 'audioChannels',
74
        ],
75
        'video' => [
76
            'codec_name' => 'videoEncoder',
77
            'bit_rate' => 'videoBitRate',
78
            'avg_frame_rate' => 'videoFrameRate',
79
            'height' => 'height',
80
            'width' => 'width',
81
        ],
82
    ];
83
84
    // Public Methods
85
    // =========================================================================
86
87
    /**
88
     * Returns a URL to the transcoded video or "" if it doesn't exist (at
89
     * which
90
     * time it will create it).
91
     *
92
     * @param      $filePath     string  path to the original video -OR- an
0 ignored issues
show
Tag value for @param tag indented incorrectly; expected 1 spaces but found 6
Loading history...
93
     *                           Asset
94
     * @param      $videoOptions array   of options for the video
0 ignored issues
show
Tag value for @param tag indented incorrectly; expected 1 spaces but found 6
Loading history...
95
     * @param bool $generate whether the video should be encoded
0 ignored issues
show
Expected 5 spaces after parameter name; 1 found
Loading history...
96
     *
97
     * @return string       URL of the transcoded video or ""
98
     */
99
    public function getVideoUrl($filePath, $videoOptions, $generate = true): string
100
    {
101
        $result = '';
102
        $settings = Transcoder::$plugin->getSettings();
103
        $subfolder = '';
104
105
        // sub folder check
106
        if (is_object($filePath) && ($filePath instanceof Asset) && $settings['createSubfolders']) {
107
            $subfolder = $filePath->folderPath;
108
        }
109
110
        // file path
111
        $filePath = $this->getAssetPath($filePath);
112
113
        if (!empty($filePath)) {
114
            $destVideoPath = $settings['transcoderPaths']['video'] . $subfolder ?? $settings['transcoderPaths']['default'];
115
            $destVideoPath = Craft::parseEnv($destVideoPath);
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

115
            $destVideoPath = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($destVideoPath);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
116
            $videoOptions = $this->coalesceOptions('defaultVideoOptions', $videoOptions);
117
118
            // Get the video encoder presets to use
119
            $videoEncoders = $settings['videoEncoders'];
120
            $thisEncoder = $videoEncoders[$videoOptions['videoEncoder']];
121
122
            $videoOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
123
124
            // Build the basic command for ffmpeg
125
            $ffmpegCmd = $settings['ffmpegPath']
126
                . ' -i ' . escapeshellarg($filePath)
127
                . ' -vcodec ' . $thisEncoder['videoCodec']
128
                . ' ' . $thisEncoder['videoCodecOptions']
129
                . ' -bufsize 1000k'
130
                . ' -threads ' . $thisEncoder['threads'];
131
132
            // Set the framerate if desired
133
            if (!empty($videoOptions['videoFrameRate'])) {
134
                $ffmpegCmd .= ' -r ' . $videoOptions['videoFrameRate'];
135
            }
136
137
            // Set the bitrate if desired
138
            if (!empty($videoOptions['videoBitRate'])) {
139
                $ffmpegCmd .= ' -b:v ' . $videoOptions['videoBitRate'] . ' -maxrate ' . $videoOptions['videoBitRate'];
140
            }
141
142
            // Adjust the scaling if desired
143
            $ffmpegCmd = $this->addScalingFfmpegArgs(
144
                $videoOptions,
145
                $ffmpegCmd
146
            );
147
148
            // Handle any audio transcoding
149
            if (empty($videoOptions['audioBitRate'])
150
                && empty($videoOptions['audioSampleRate'])
151
                && empty($videoOptions['audioChannels'])
152
            ) {
153
                // Just copy the audio if no options are provided
154
                $ffmpegCmd .= ' -c:a copy';
155
            } else {
156
                // Do audio transcoding based on the settings
157
                $ffmpegCmd .= ' -acodec ' . $thisEncoder['audioCodec'];
158
                if (!empty($videoOptions['audioBitRate'])) {
159
                    $ffmpegCmd .= ' -b:a ' . $videoOptions['audioBitRate'];
160
                }
161
                if (!empty($videoOptions['audioSampleRate'])) {
162
                    $ffmpegCmd .= ' -ar ' . $videoOptions['audioSampleRate'];
163
                }
164
                if (!empty($videoOptions['audioChannels'])) {
165
                    $ffmpegCmd .= ' -ac ' . $videoOptions['audioChannels'];
166
                }
167
                $ffmpegCmd .= ' ' . $thisEncoder['audioCodecOptions'];
168
            }
169
170
            // Create the directory if it isn't there already
171
            if (!is_dir($destVideoPath)) {
0 ignored issues
show
It seems like $destVideoPath can also be of type boolean and null; however, parameter $filename of is_dir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

171
            if (!is_dir(/** @scrutinizer ignore-type */ $destVideoPath)) {
Loading history...
172
                try {
173
                    FileHelper::createDirectory($destVideoPath);
174
                } catch (Exception $e) {
175
                    Craft::error($e->getMessage(), __METHOD__);
176
                }
177
            }
178
179
            $destVideoFile = $this->getFilename($filePath, $videoOptions);
180
181
            // File to store the video encoding progress in
182
            $progressFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $destVideoFile . '.progress';
183
184
            // Assemble the destination path and final ffmpeg command
185
            $destVideoPath .= $destVideoFile;
186
            $ffmpegCmd .= ' -f '
187
                . $thisEncoder['fileFormat']
188
                . ' -y ' . escapeshellarg($destVideoPath)
189
                . ' 1> ' . $progressFile . ' 2>&1 & echo $!';
190
191
            // Make sure there isn't a lockfile for this video already
192
            $lockFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $destVideoFile . '.lock';
193
            $oldPid = @file_get_contents($lockFile);
194
            if ($oldPid !== false) {
195
                // See if the process is running, and empty result means the process is still running
196
                // ref: https://stackoverflow.com/questions/3043978/how-to-check-if-a-process-id-pid-exists
197
                exec("kill -0 $oldPid 2>&1", $ProcessState);
198
                if (count($ProcessState) === 0) {
199
                    return $result;
200
                }
201
                // It's finished transcoding, so delete the lockfile and progress file
202
                @unlink($lockFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

202
                /** @scrutinizer ignore-unhandled */ @unlink($lockFile);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
203
                @unlink($progressFile);
204
            }
205
206
            // If the video file already exists and hasn't been modified, return it.  Otherwise, start it transcoding
207
            if (file_exists($destVideoPath) && (@filemtime($destVideoPath) >= @filemtime($filePath))) {
208
                $url = $settings['transcoderUrls']['video'] . $subfolder ?? $settings['transcoderUrls']['default'];
209
                $result = Craft::parseEnv($url) . $destVideoFile;
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

209
                $result = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($url) . $destVideoFile;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
210
                // skip encoding
211
            } elseif (!$generate) {
212
                $result = "";
213
            } else {
214
                // Kick off the transcoding
215
                $pid = $this->executeShellCommand($ffmpegCmd);
216
                Craft::info($ffmpegCmd . "\nffmpeg PID: " . $pid, __METHOD__);
217
218
                // Create a lockfile in tmp
219
                file_put_contents($lockFile, $pid);
220
            }
221
        }
222
223
        return $result;
224
    }
225
226
    /**
227
     * Returns a URL to a video thumbnail
228
     *
229
     * @param string $filePath path to the original video or an Asset
0 ignored issues
show
Expected 9 spaces after parameter name; 1 found
Loading history...
230
     * @param array $thumbnailOptions of options for the thumbnail
0 ignored issues
show
Expected 2 spaces after parameter type; 1 found
Loading history...
231
     * @param bool $generate whether the thumbnail should be
0 ignored issues
show
Expected 3 spaces after parameter type; 1 found
Loading history...
Expected 9 spaces after parameter name; 1 found
Loading history...
232
     *                                 generated if it doesn't exists
0 ignored issues
show
Parameter comment not aligned correctly; expected 23 spaces but found 33
Loading history...
233
     * @param bool $asPath Whether we should return a path or not
0 ignored issues
show
Expected 3 spaces after parameter type; 1 found
Loading history...
Expected 11 spaces after parameter name; 1 found
Loading history...
234
     *
235
     * @return string|false|null URL or path of the video thumbnail
236
     */
237
    public function getVideoThumbnailUrl($filePath, $thumbnailOptions, $generate = true, $asPath = false)
238
    {
239
        $result = null;
240
        $settings = Transcoder::$plugin->getSettings();
241
        $subfolder = '';
242
243
        // sub folder check
244
        if (is_object($filePath) && ($filePath instanceof Asset) && $settings['createSubfolders']) {
0 ignored issues
show
The condition is_object($filePath) is always false.
Loading history...
245
            $subfolder = $filePath->folderPath;
246
        }
247
248
        $filePath = $this->getAssetPath($filePath);
249
250
        if (!empty($filePath)) {
251
            $destThumbnailPath = $settings['transcoderPaths']['thumbnail'] . $subfolder ?? $settings['transcoderPaths']['default'];
252
            $destThumbnailPath = Craft::parseEnv($destThumbnailPath);
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

252
            $destThumbnailPath = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($destThumbnailPath);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
253
254
            $thumbnailOptions = $this->coalesceOptions('defaultThumbnailOptions', $thumbnailOptions);
255
256
            // Build the basic command for ffmpeg
257
            $ffmpegCmd = $settings['ffmpegPath']
258
                . ' -i ' . escapeshellarg($filePath)
259
                . ' -vcodec mjpeg'
260
                . ' -vframes 1';
261
262
            // Adjust the scaling if desired
263
            $ffmpegCmd = $this->addScalingFfmpegArgs(
264
                $thumbnailOptions,
265
                $ffmpegCmd
266
            );
267
268
            // Set the timecode to get the thumbnail from if desired
269
            if (!empty($thumbnailOptions['timeInSecs'])) {
270
                $timeCode = gmdate('H:i:s', $thumbnailOptions['timeInSecs']);
271
                $ffmpegCmd .= ' -ss ' . $timeCode . '.00';
272
            }
273
274
            // Create the directory if it isn't there already
275
            if (!is_dir($destThumbnailPath)) {
0 ignored issues
show
It seems like $destThumbnailPath can also be of type boolean and null; however, parameter $filename of is_dir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

275
            if (!is_dir(/** @scrutinizer ignore-type */ $destThumbnailPath)) {
Loading history...
276
                try {
277
                    FileHelper::createDirectory($destThumbnailPath);
278
                } catch (Exception $e) {
279
                    Craft::error($e->getMessage(), __METHOD__);
280
                }
281
            }
282
283
            $destThumbnailFile = $this->getFilename($filePath, $thumbnailOptions);
284
285
            // Assemble the destination path and final ffmpeg command
286
            $destThumbnailPath .= $destThumbnailFile;
287
            $ffmpegCmd .= ' -f image2 -y ' . escapeshellarg($destThumbnailPath) . ' >/dev/null 2>/dev/null &';
288
289
            // If the thumbnail file already exists, return it.  Otherwise, generate it and return it
290
            if (!file_exists($destThumbnailPath)) {
291
                if ($generate) {
292
                    /** @noinspection PhpUnusedLocalVariableInspection */
0 ignored issues
show
The open comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
The close comment tag must be the only content on the line
Loading history...
293
                    $shellOutput = $this->executeShellCommand($ffmpegCmd);
294
                    Craft::info($ffmpegCmd, __METHOD__);
295
296
                    // if ffmpeg fails which we can't check because the process is ran in the background
297
                    // dont return the future path of the image or else we can't check this in the front end
298
299
                    return false;
300
                } else {
301
                    Craft::info('Thumbnail does not exist, but not asked to generate it: ' . $filePath, __METHOD__);
302
303
                    // The file doesn't exist, and we weren't asked to generate it
304
                    return false;
305
                }
306
            }
307
            // Return either a path or a URL
308
            if ($asPath) {
309
                $result = $destThumbnailPath;
310
            } else {
311
                $url = $settings['transcoderUrls']['thumbnail'] . $subfolder ?? $settings['transcoderUrls']['default'];
312
                $result = Craft::parseEnv($url) . $destThumbnailFile;
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

312
                $result = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($url) . $destThumbnailFile;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
313
            }
314
        }
315
316
        return $result;
317
    }
318
319
    /**
320
     * Returns a URL to the transcoded audio file or "" if it doesn't exist
321
     * (at which time it will create it).
322
     *
323
     * @param $filePath     string path to the original audio file -OR- an Asset
324
     * @param $audioOptions array of options for the audio file
325
     *
326
     * @return string       URL of the transcoded audio file or ""
327
     */
328
    public function getAudioUrl($filePath, $audioOptions): string
329
    {
330
        $result = '';
331
        $settings = Transcoder::$plugin->getSettings();
332
        $subfolder = '';
333
334
        // sub folder check
335
        if (is_object($filePath) && ($filePath instanceof Asset) && $settings['createSubfolders']) {
336
            $subfolder = $filePath->folderPath;
337
        }
338
339
        $filePath = $this->getAssetPath($filePath);
340
341
        if (!empty($filePath)) {
342
            $destAudioPath = $settings['transcoderPaths']['audio'] . $subfolder ?? $settings['transcoderPaths']['default'];
343
            $destAudioPath = Craft::parseEnv($destAudioPath);
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

343
            $destAudioPath = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($destAudioPath);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
344
345
            $audioOptions = $this->coalesceOptions('defaultAudioOptions', $audioOptions);
346
347
            // Get the audio encoder presets to use
348
            $audioEncoders = $settings['audioEncoders'];
349
            $thisEncoder = $audioEncoders[$audioOptions['audioEncoder']];
350
351
            $audioOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
352
353
            // Build the basic command for ffmpeg
354
            $ffmpegCmd = $settings['ffmpegPath']
355
                . ' -i ' . escapeshellarg($filePath)
356
                . ' -acodec ' . $thisEncoder['audioCodec']
357
                . ' ' . $thisEncoder['audioCodecOptions']
358
                . ' -bufsize 1000k'
359
                . ' -vn'
360
                . ' -threads ' . $thisEncoder['threads'];
361
362
            // Set the bitrate if desired
363
            if (!empty($audioOptions['audioBitRate'])) {
364
                $ffmpegCmd .= ' -b:a ' . $audioOptions['audioBitRate'];
365
            }
366
            // Set the sample rate if desired
367
            if (!empty($audioOptions['audioSampleRate'])) {
368
                $ffmpegCmd .= ' -ar ' . $audioOptions['audioSampleRate'];
369
            }
370
            // Set the audio channels if desired
371
            if (!empty($audioOptions['audioChannels'])) {
372
                $ffmpegCmd .= ' -ac ' . $audioOptions['audioChannels'];
373
            }
374
            $ffmpegCmd .= ' ' . $thisEncoder['audioCodecOptions'];
375
376
            if (!empty($audioOptions['seekInSecs'])) {
377
                $ffmpegCmd .= ' -ss ' . $audioOptions['seekInSecs'];
378
            }
379
380
            if (!empty($audioOptions['timeInSecs'])) {
381
                $ffmpegCmd .= ' -t ' . $audioOptions['timeInSecs'];
382
            }
383
384
            // Create the directory if it isn't there already
385
            if (!is_dir($destAudioPath)) {
0 ignored issues
show
It seems like $destAudioPath can also be of type boolean and null; however, parameter $filename of is_dir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

385
            if (!is_dir(/** @scrutinizer ignore-type */ $destAudioPath)) {
Loading history...
386
                try {
387
                    FileHelper::createDirectory($destAudioPath);
388
                } catch (Exception $e) {
389
                    Craft::error($e->getMessage(), __METHOD__);
390
                }
391
            }
392
393
            $destAudioFile = $this->getFilename($filePath, $audioOptions);
394
395
            // File to store the audio encoding progress in
396
            $progressFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $destAudioFile . '.progress';
397
398
            // Assemble the destination path and final ffmpeg command
399
            $destAudioPath .= $destAudioFile;
400
            // Handle the `stripMetadata` setting
401
            $stripMetadata = false;
402
            if (!empty($audioOptions['stripMetadata'])) {
403
                $stripMetadata = $audioOptions['stripMetadata'];
404
            }
405
            if ($stripMetadata) {
406
                $ffmpegCmd .= ' -map_metadata -1 ';
407
            }
408
            // Add the file format
409
            $ffmpegCmd .= ' -f '
410
                . $thisEncoder['fileFormat']
411
                . ' -y ' . escapeshellarg($destAudioPath);
412
            // Handle the `synchronous` setting
413
            $synchronous = false;
414
            if (!empty($audioOptions['synchronous'])) {
415
                $synchronous = $audioOptions['synchronous'];
416
            }
417
            if (!$synchronous) {
418
                $ffmpegCmd .= ' 1> ' . $progressFile . ' 2>&1 & echo $!';
419
                // Make sure there isn't a lockfile for this audio file already
420
                $lockFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $destAudioFile . '.lock';
421
                $oldPid = @file_get_contents($lockFile);
422
                if ($oldPid !== false) {
423
                    // See if the process is running, and empty result means the process is still running
424
                    // ref: https://stackoverflow.com/questions/3043978/how-to-check-if-a-process-id-pid-exists
425
                    exec("kill -0 $oldPid 2>&1", $ProcessState);
426
                    if (count($ProcessState) === 0) {
427
                        return $result;
428
                    }
429
                    // It's finished transcoding, so delete the lockfile and progress file
430
                    @unlink($lockFile);
431
                    @unlink($progressFile);
432
                }
433
            }
434
435
            // If the audio file already exists and hasn't been modified, return it.  Otherwise, start it transcoding
436
            if (file_exists($destAudioPath) && (@filemtime($destAudioPath) >= @filemtime($filePath))) {
437
                $url = $settings['transcoderUrls']['audio'] . $subfolder ?? $settings['transcoderUrls']['default'];
438
                $result = Craft::parseEnv($url) . $destAudioFile;
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

438
                $result = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($url) . $destAudioFile;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
439
            } else {
440
                // Kick off the transcoding
441
                $pid = $this->executeShellCommand($ffmpegCmd);
442
443
                if ($synchronous) {
444
                    Craft::info($ffmpegCmd, __METHOD__);
445
                    $url = $settings['transcoderUrls']['audio'] . $subfolder ?? $settings['transcoderUrls']['default'];
446
                    $result = Craft::parseEnv($url) . $destAudioFile;
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

446
                    $result = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($url) . $destAudioFile;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
447
                } else {
448
                    Craft::info($ffmpegCmd . "\nffmpeg PID: " . $pid, __METHOD__);
449
                    // Create a lockfile in tmp
450
                    file_put_contents($lockFile, $pid);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lockFile does not seem to be defined for all execution paths leading up to this point.
Loading history...
451
                }
452
            }
453
        }
454
455
        return $result;
456
    }
457
458
    /**
459
     * Extract information from a video/audio file
460
     *
461
     * @param      $filePath
0 ignored issues
show
Missing parameter comment
Loading history...
Tag value for @param tag indented incorrectly; expected 1 spaces but found 6
Loading history...
462
     * @param bool $summary
0 ignored issues
show
Missing parameter comment
Loading history...
463
     *
464
     * @return null|array
465
     */
466
    public function getFileInfo($filePath, $summary = false)
467
    {
468
        $result = null;
469
        $settings = Transcoder::$plugin->getSettings();
470
        $filePath = $this->getAssetPath($filePath);
471
472
        if (!empty($filePath)) {
473
            // Build the basic command for ffprobe
474
            $ffprobeOptions = $settings['ffprobeOptions'];
475
            $ffprobeCmd = $settings['ffprobePath']
476
                . ' ' . $ffprobeOptions
477
                . ' ' . escapeshellarg($filePath);
478
479
            $shellOutput = $this->executeShellCommand($ffprobeCmd);
480
            Craft::info($ffprobeCmd, __METHOD__);
481
            $result = JsonHelper::decodeIfJson($shellOutput, true);
482
            Craft::info(print_r($result, true), __METHOD__);
0 ignored issues
show
It seems like print_r($result, true) can also be of type true; however, parameter $message of yii\BaseYii::info() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

482
            Craft::info(/** @scrutinizer ignore-type */ print_r($result, true), __METHOD__);
Loading history...
483
            // Handle the case it not being JSON
484
            if (!is_array($result)) {
485
                $result = [];
486
            }
487
            // Trim down the arrays to just a summary
488
            if ($summary && !empty($result)) {
489
                $summaryResult = [];
490
                foreach ($result as $topLevelKey => $topLevelValue) {
491
                    switch ($topLevelKey) {
492
                        // Format info
493
                        case 'format':
0 ignored issues
show
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
494
                            foreach (self::INFO_SUMMARY['format'] as $settingKey => $settingValue) {
0 ignored issues
show
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
495
                                if (!empty($topLevelValue[$settingKey])) {
0 ignored issues
show
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
496
                                    $summaryResult[$settingValue] = $topLevelValue[$settingKey];
497
                                }
0 ignored issues
show
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
498
                            }
0 ignored issues
show
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
499
                            break;
500
                        // Stream info
501
                        case 'streams':
0 ignored issues
show
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
502
                            foreach ($topLevelValue as $stream) {
0 ignored issues
show
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
503
                                $infoSummaryType = $stream['codec_type'];
504
                                if (in_array($infoSummaryType, self::INFO_SUMMARY, false)) {
0 ignored issues
show
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
505
                                    foreach (self::INFO_SUMMARY[$infoSummaryType] as $settingKey => $settingValue) {
0 ignored issues
show
Line indented incorrectly; expected 32 spaces, found 36
Loading history...
506
                                        if (!empty($stream[$settingKey])) {
0 ignored issues
show
Line indented incorrectly; expected 36 spaces, found 40
Loading history...
507
                                            $summaryResult[$settingValue] = $stream[$settingKey];
508
                                        }
0 ignored issues
show
Line indented incorrectly; expected 36 spaces, found 40
Loading history...
509
                                    }
0 ignored issues
show
Line indented incorrectly; expected 32 spaces, found 36
Loading history...
510
                                }
0 ignored issues
show
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
511
                            }
0 ignored issues
show
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
512
                            break;
513
                        // Unknown info
514
                        default:
0 ignored issues
show
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
515
                            break;
516
                    }
517
                }
518
                // Handle cases where the framerate is returned as XX/YY
519
                if (!empty($summaryResult['videoFrameRate'])
520
                    && (strpos($summaryResult['videoFrameRate'], '/') !== false)
521
                ) {
522
                    $parts = explode('/', $summaryResult['videoFrameRate']);
523
                    $summaryResult['videoFrameRate'] = (float)$parts[0] / (float)$parts[1];
524
                }
525
                $result = $summaryResult;
526
            }
527
        }
528
529
        return $result;
530
    }
531
532
    /**
533
     * Get the name of a video file from a path and options
534
     *
535
     * @param $filePath
0 ignored issues
show
Missing parameter comment
Loading history...
536
     * @param $videoOptions
0 ignored issues
show
Missing parameter comment
Loading history...
537
     *
538
     * @return string
539
     */
540
    public function getVideoFilename($filePath, $videoOptions): string
541
    {
542
        $settings = Transcoder::$plugin->getSettings();
543
        $videoOptions = $this->coalesceOptions('defaultVideoOptions', $videoOptions);
544
545
        // Get the video encoder presets to use
546
        $videoEncoders = $settings['videoEncoders'];
547
        $thisEncoder = $videoEncoders[$videoOptions['videoEncoder']];
548
549
        $videoOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
550
551
        return $this->getFilename($filePath, $videoOptions);
552
    }
553
554
    /**
555
     * Get the name of an audio file from a path and options
556
     *
557
     * @param $filePath
0 ignored issues
show
Missing parameter comment
Loading history...
558
     * @param $audioOptions
0 ignored issues
show
Missing parameter comment
Loading history...
559
     *
560
     * @return string
561
     */
562
    public function getAudioFilename($filePath, $audioOptions): string
563
    {
564
        $settings = Transcoder::$plugin->getSettings();
565
        $audioOptions = $this->coalesceOptions('defaultAudioOptions', $audioOptions);
566
567
        // Get the video encoder presets to use
568
        $audioEncoders = $settings['audioEncoders'];
569
        $thisEncoder = $audioEncoders[$audioOptions['audioEncoder']];
570
571
        $audioOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
572
573
        return $this->getFilename($filePath, $audioOptions);
574
    }
575
576
    /**
577
     * Get the name of a gif video file from a path and options
578
     *
579
     * @param $filePath
0 ignored issues
show
Missing parameter comment
Loading history...
580
     * @param $gifOptions
0 ignored issues
show
Missing parameter comment
Loading history...
581
     *
582
     * @return string
583
     */
584
    public function getGifFilename($filePath, $gifOptions): string
585
    {
586
        $settings = Transcoder::$plugin->getSettings();
587
        $gifOptions = $this->coalesceOptions('defaultGifOptions', $gifOptions);
588
589
        // Get the video encoder presets to use
590
        $videoEncoders = $settings['videoEncoders'];
591
        $thisEncoder = $videoEncoders[$gifOptions['videoEncoder']];
592
593
        $gifOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
594
595
        return $this->getFilename($filePath, $gifOptions);
596
    }
597
598
    /**
599
     * Handle generated a thumbnail for the Control Panel
600
     *
601
     * @param AssetThumbEvent $event
0 ignored issues
show
Missing parameter comment
Loading history...
602
     *
603
     * @return null|false|string
604
     */
605
    public function handleGetAssetThumbPath(AssetThumbEvent $event)
606
    {
607
        $options = [
608
            'width' => $event->width,
609
            'height' => $event->height,
610
        ];
611
        $thumbPath = $this->getVideoThumbnailUrl($event->asset, $options, $event->generate, true);
612
613
        return $thumbPath;
614
    }
615
616
    // Protected Methods
617
    // =========================================================================
618
619
    /**
620
     * Returns a URL to a encoded GIF file (mp4)
621
     *
622
     * @param string $filePath path to the original video or an Asset
0 ignored issues
show
Expected 3 spaces after parameter name; 1 found
Loading history...
623
     * @param array $gifOptions of options for the GIF file
0 ignored issues
show
Expected 2 spaces after parameter type; 1 found
Loading history...
624
     *
625
     * @return string|false|null URL or path of the GIF file
626
     */
0 ignored issues
show
There must be no blank lines after the function comment
Loading history...
627
628
    public function getGifUrl($filePath, $gifOptions): string
629
    {
630
        $result = '';
631
        $settings = Transcoder::$plugin->getSettings();
632
        $subfolder = '';
633
634
        // sub folder check
635
        if (is_object($filePath) && ($filePath instanceof Asset) && $settings['createSubfolders']) {
0 ignored issues
show
The condition is_object($filePath) is always false.
Loading history...
636
            $subfolder = $filePath->folderPath;
637
        }
638
639
        $filePath = $this->getAssetPath($filePath);
640
641
        if (!empty($filePath)) {
642
            // Dest path
643
            $destVideoPath = $settings['transcoderPaths']['gif'] . $subfolder ?? $settings['transcoderPaths']['default'];
644
            $destVideoPath = Craft::parseEnv($destVideoPath);
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

644
            $destVideoPath = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($destVideoPath);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
645
646
            // Options
647
            $gifOptions = $this->coalesceOptions('defaultGifOptions', $gifOptions);
648
649
            // Get the video encoder presets to use
650
            $videoEncoders = $settings['videoEncoders'];
651
            $thisEncoder = $videoEncoders[$gifOptions['videoEncoder']];
652
            $gifOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
653
654
            // Build the basic command for ffmpeg
655
            $ffmpegCmd = $settings['ffmpegPath']
656
                . ' -f gif'
657
                . ' -i ' . escapeshellarg($filePath)
658
                . ' -vcodec ' . $thisEncoder['videoCodec']
659
                . ' ' . $thisEncoder['videoCodecOptions'];
660
661
662
            // Create the directory if it isn't there already
663
            if (!is_dir($destVideoPath)) {
0 ignored issues
show
It seems like $destVideoPath can also be of type boolean and null; however, parameter $filename of is_dir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

663
            if (!is_dir(/** @scrutinizer ignore-type */ $destVideoPath)) {
Loading history...
664
                try {
665
                    FileHelper::createDirectory($destVideoPath);
666
                } catch (Exception $e) {
667
                    Craft::error($e->getMessage(), __METHOD__);
668
                }
669
            }
670
671
            $destVideoFile = $this->getFilename($filePath, $gifOptions);
672
673
            // File to store the video encoding progress in
674
            $progressFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $destVideoFile . '.progress';
675
676
            // Assemble the destination path and final ffmpeg command
677
            $destVideoPath .= $destVideoFile;
678
            $ffmpegCmd .= ' '
679
                . ' -y ' . escapeshellarg($destVideoPath)
680
                . ' 1> ' . $progressFile . ' 2>&1 & echo $!';
681
682
            // Make sure there isn't a lockfile for this video already
683
            $lockFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $destVideoFile . '.lock';
684
            $oldPid = @file_get_contents($lockFile);
685
            if ($oldPid !== false) {
686
                // See if the process is running, and empty result means the process is still running
687
                // ref: https://stackoverflow.com/questions/3043978/how-to-check-if-a-process-id-pid-exists
688
                exec("kill -0 $oldPid 2>&1", $ProcessState);
689
                if (count($ProcessState) === 0) {
690
                    return $result;
691
                }
692
                // It's finished transcoding, so delete the lockfile and progress file
693
                @unlink($lockFile);
694
                @unlink($progressFile);
695
            }
696
697
            // If the video file already exists and hasn't been modified, return it.  Otherwise, start it transcoding
698
            if (file_exists($destVideoPath) && (@filemtime($destVideoPath) >= @filemtime($filePath))) {
699
                $url = $settings['transcoderUrls']['gif'] . $subfolder ?? $settings['transcoderUrls']['default'];
700
                $result = Craft::parseEnv($url) . $destVideoFile;
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

700
                $result = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($url) . $destVideoFile;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
701
            } else {
702
                // Kick off the transcoding
703
                $pid = $this->executeShellCommand($ffmpegCmd);
704
                Craft::info($ffmpegCmd . "\nffmpeg PID: " . $pid, __METHOD__);
705
706
                // Create a lockfile in tmp
707
                file_put_contents($lockFile, $pid);
708
            }
709
        }
710
711
        return $result;
712
    }
713
714
    /**
715
     * Get the name of a file from a path and options
716
     *
717
     * @param $filePath
0 ignored issues
show
Missing parameter comment
Loading history...
718
     * @param $options
0 ignored issues
show
Missing parameter comment
Loading history...
719
     *
720
     * @return string
721
     */
722
    protected function getFilename($filePath, $options): string
723
    {
724
        $settings = Transcoder::$plugin->getSettings();
725
        $filePath = $this->getAssetPath($filePath);
726
727
        $validator = new UrlValidator();
728
        $error = '';
729
        if ($validator->validate($filePath, $error)) {
730
            $urlParts = parse_url($filePath);
731
            $pathParts = pathinfo($urlParts['path']);
732
        } else {
733
            $pathParts = pathinfo($filePath);
734
        }
735
        $fileName = $pathParts['filename'];
736
737
        // Add our options to the file name
738
        foreach ($options as $key => $value) {
739
            if (!empty($value)) {
740
                $suffix = '';
741
                if (!empty(self::SUFFIX_MAP[$key])) {
742
                    $suffix = self::SUFFIX_MAP[$key];
743
                }
744
                if (is_bool($value)) {
745
                    $value = $value ? $key : 'no' . $key;
746
                }
747
                if (!in_array($key, self::EXCLUDE_PARAMS, true)) {
748
                    $fileName .= '_' . $value . $suffix;
749
                }
750
            }
751
        }
752
        // See if we should use a hash instead
753
        if ($settings['useHashedNames']) {
754
            $fileName = $pathParts['filename'] . md5($fileName);
755
        }
756
        $fileName .= $options['fileSuffix'];
757
758
        return $fileName;
759
    }
760
761
    /**
762
     * Extract a file system path if $filePath is an Asset object
763
     *
764
     * @param $filePath
0 ignored issues
show
Missing parameter comment
Loading history...
765
     *
766
     * @return string
767
     */
768
    protected function getAssetPath($filePath): string
769
    {
770
        // If we're passed an Asset, extract the path from it
771
        if (is_object($filePath) && ($filePath instanceof Asset)) {
772
            /** @var Asset $asset */
0 ignored issues
show
The open comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
The close comment tag must be the only content on the line
Loading history...
773
            $asset = $filePath;
774
            $assetVolume = null;
0 ignored issues
show
The assignment to $assetVolume is dead and can be removed.
Loading history...
775
            try {
776
                $assetVolume = $asset->getVolume();
777
            } catch (InvalidConfigException $e) {
778
                Craft::error($e->getMessage(), __METHOD__);
779
            }
780
781
            if ($assetVolume) {
0 ignored issues
show
$assetVolume is of type craft\base\VolumeInterface, thus it always evaluated to true.
Loading history...
782
                // If it's local, get a path to the file
783
                if ($assetVolume instanceof Local) {
784
                    $sourcePath = rtrim($assetVolume->path, DIRECTORY_SEPARATOR);
0 ignored issues
show
It seems like $assetVolume->path can also be of type null; however, parameter $string of rtrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

784
                    $sourcePath = rtrim(/** @scrutinizer ignore-type */ $assetVolume->path, DIRECTORY_SEPARATOR);
Loading history...
785
                    $sourcePath .= '' === $sourcePath ? '' : DIRECTORY_SEPARATOR;
786
                    $folderPath = '';
0 ignored issues
show
The assignment to $folderPath is dead and can be removed.
Loading history...
787
                    try {
788
                        $folderPath = rtrim($asset->getFolder()->path, DIRECTORY_SEPARATOR);
789
                    } catch (InvalidConfigException $e) {
790
                        Craft::error($e->getMessage(), __METHOD__);
791
                    }
792
                    $folderPath .= '' === $folderPath ? '' : DIRECTORY_SEPARATOR;
793
794
                    $filePath = $sourcePath . $folderPath . $asset->filename;
795
                } else {
796
                    // Otherwise, get a URL
797
                    $filePath = $asset->getUrl();
798
                }
799
            }
800
        }
801
802
        $filePath = Craft::parseEnv($filePath);
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

802
        $filePath = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($filePath);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
803
804
        // Make sure that $filePath is either an existing file, or a valid URL
805
        if (!file_exists($filePath)) {
0 ignored issues
show
It seems like $filePath can also be of type boolean and null; however, parameter $filename of file_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

805
        if (!file_exists(/** @scrutinizer ignore-type */ $filePath)) {
Loading history...
806
            $validator = new UrlValidator();
807
            $error = '';
808
            if (!$validator->validate($filePath, $error)) {
809
                Craft::error($error, __METHOD__);
810
                $filePath = '';
811
            }
812
        }
813
814
        return $filePath;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $filePath could return the type boolean|null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
815
    }
816
817
    /**
818
     * Set the width & height if desired
819
     *
820
     * @param $options
0 ignored issues
show
Missing parameter comment
Loading history...
821
     * @param $ffmpegCmd
0 ignored issues
show
Missing parameter comment
Loading history...
822
     *
823
     * @return string
824
     */
825
    protected function addScalingFfmpegArgs($options, $ffmpegCmd): string
826
    {
827
        if (!empty($options['width']) && !empty($options['height'])) {
828
            // Handle "none", "crop", and "letterbox" aspectRatios
829
            $aspectRatio = '';
830
            if (!empty($options['aspectRatio'])) {
831
                switch ($options['aspectRatio']) {
832
                    // Scale to the appropriate aspect ratio, padding
833
                    case 'letterbox':
0 ignored issues
show
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
834
                        $letterboxColor = '';
835
                        if (!empty($options['letterboxColor'])) {
0 ignored issues
show
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
836
                            $letterboxColor = ':color=' . $options['letterboxColor'];
837
                        }
0 ignored issues
show
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
838
                        $aspectRatio = ':force_original_aspect_ratio=decrease'
839
                            . ',pad=' . $options['width'] . ':' . $options['height'] . ':(ow-iw)/2:(oh-ih)/2'
840
                            . $letterboxColor;
841
                        break;
842
                    // Scale to the appropriate aspect ratio, cropping
843
                    case 'crop':
0 ignored issues
show
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
844
                        $aspectRatio = ':force_original_aspect_ratio=increase'
845
                            . ',crop=' . $options['width'] . ':' . $options['height'];
846
                        break;
847
                    // No aspect ratio scaling at all
848
                    default:
0 ignored issues
show
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
849
                        $aspectRatio = ':force_original_aspect_ratio=disable';
850
                        $options['aspectRatio'] = 'none';
851
                        break;
852
                }
853
            }
854
            $sharpen = '';
855
            if (!empty($options['sharpen']) && ($options['sharpen'] !== false)) {
856
                $sharpen = ',unsharp=5:5:1.0:5:5:0.0';
857
            }
858
            $ffmpegCmd .= ' -vf "scale='
859
                . $options['width'] . ':' . $options['height']
860
                . $aspectRatio
861
                . $sharpen
862
                . '"';
863
        }
864
865
        return $ffmpegCmd;
866
    }
867
868
    // Protected Methods
869
    // =========================================================================
870
871
    /**
872
     * Combine the options arrays
873
     *
874
     * @param $defaultName
0 ignored issues
show
Missing parameter comment
Loading history...
875
     * @param $options
0 ignored issues
show
Missing parameter comment
Loading history...
876
     *
877
     * @return array
878
     */
879
    protected function coalesceOptions($defaultName, $options): array
880
    {
881
        // Default options
882
        $settings = Transcoder::$plugin->getSettings();
883
        $defaultOptions = $settings[$defaultName];
884
885
        // Coalesce the passed in $options with the $defaultOptions
886
        $options = array_merge($defaultOptions, $options);
887
888
        return $options;
889
    }
890
891
    /**
892
     * Execute a shell command
893
     *
894
     * @param string $command
0 ignored issues
show
Missing parameter comment
Loading history...
895
     *
896
     * @return string
897
     */
898
    protected function executeShellCommand(string $command): string
899
    {
900
        // Create the shell command
901
        $shellCommand = new ShellCommand();
902
        $shellCommand->setCommand($command);
903
904
        // If we don't have proc_open, maybe we've got exec
905
        if (!function_exists('proc_open') && function_exists('exec')) {
906
            $shellCommand->useExec = true;
907
        }
908
909
        // Return the result of the command's output or error
910
        if ($shellCommand->execute()) {
911
            $result = $shellCommand->getOutput();
912
        } else {
913
            $result = $shellCommand->getError();
914
        }
915
916
        return $result;
917
    }
918
}
919