Passed
Pull Request — v1 (#20)
by
unknown
19:49
created

Transcode::moveAssetToVolume()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 41
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 24
nc 5
nop 2
dl 0
loc 41
rs 8.9137
c 0
b 0
f 0
1
<?php
2
/**
3
 * Transcoder plugin for Craft CMS 3.x
4
 *
5
 * Transcode videos to various formats, and provide thumbnails of the video
6
 *
7
 * @link      https://nystudio107.com
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
8
 * @copyright Copyright (c) 2017 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
9
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
10
11
namespace nystudio107\transcoder\services;
12
13
use craft\records\VolumeFolder;
14
use craft\services\Assets;
15
use nystudio107\transcoder\Transcoder;
16
17
use Craft;
18
use craft\base\Component;
19
use craft\elements\Asset;
20
use craft\events\AssetThumbEvent;
21
use craft\helpers\FileHelper;
22
use craft\helpers\Json as JsonHelper;
23
use craft\volumes\Local;
24
25
use yii\base\Exception;
26
use yii\validators\UrlValidator;
27
28
use mikehaertl\shellcommand\Command as ShellCommand;
29
use yii\base\InvalidConfigException;
30
31
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
32
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
33
 * @package   Transcode
0 ignored issues
show
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
34
 * @since     1.0.0
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
35
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
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
    ];
59
60
    // Mappings for getFileInfo() summary values
61
    const INFO_SUMMARY = [
62
        'format' => [
63
            'filename' => 'filename',
64
            'duration' => 'duration',
65
            'size' => 'size',
66
        ],
67
        'audio' => [
68
            'codec_name' => 'audioEncoder',
69
            'bit_rate' => 'audioBitRate',
70
            'sample_rate' => 'audioSampleRate',
71
            'channels' => 'audioChannels',
72
        ],
73
        'video' => [
74
            'codec_name' => 'videoEncoder',
75
            'bit_rate' => 'videoBitRate',
76
            'avg_frame_rate' => 'videoFrameRate',
77
            'height' => 'height',
78
            'width' => 'width',
79
        ],
80
    ];
81
82
    // Public Methods
83
    // =========================================================================
84
85
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $filePath should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $videoOptions should have a doc-comment as per coding-style.
Loading history...
86
     * Returns a URL to the transcoded video or "" if it doesn't exist (at which
87
     * time it will create it).
88
     *
89
     * @param $filePath     string  path to the original video -OR- an Asset
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
90
     * @param $videoOptions array   of options for the video
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
91
     * @param bool   $generate         whether the video should be encoded
0 ignored issues
show
Coding Style introduced by
Expected 58 spaces after parameter type; 3 found
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter name; 9 found
Loading history...
92
     *
93
     * @return string       URL of the transcoded video or ""
94
     */
95
    public function getVideoUrl($filePath, $videoOptions, $generate = true): string
96
    {
97
98
        $result = '';
99
        $settings = Transcoder::$plugin->getSettings();
100
		$subfolder = '';
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 8 spaces, found 2
Loading history...
101
102
		// sub folder check
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 8 spaces, found 2
Loading history...
103
		if(\is_object($filePath) && ($filePath instanceof Asset) && $settings['createSubfolders']) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 2
Loading history...
Coding Style introduced by
Expected "if (...) {\n"; found "if(...) {\n"
Loading history...
104
			$subfolder = $filePath->folderPath;
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 12 spaces, found 3
Loading history...
105
		}
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 2
Loading history...
106
107
		// file path
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 8 spaces, found 2
Loading history...
108
        $filePath = $this->getAssetPath($filePath);
109
110
        if (!empty($filePath)) {
111
            $destVideoPath = $this->localDestPath('video', $subfolder);
0 ignored issues
show
Bug introduced by
It seems like $subfolder can also be of type null; however, parameter $subfolder of nystudio107\transcoder\s...nscode::localDestPath() 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

111
            $destVideoPath = $this->localDestPath('video', /** @scrutinizer ignore-type */ $subfolder);
Loading history...
112
            $videoOptions = $this->coalesceOptions('defaultVideoOptions', $videoOptions);
113
114
            // Get the video encoder presets to use
115
            $videoEncoders = $settings['videoEncoders'];
116
            $thisEncoder = $videoEncoders[$videoOptions['videoEncoder']];
117
118
            $videoOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
119
120
            // Build the basic command for ffmpeg
121
            $ffmpegCmd = $settings['ffmpegPath']
122
                .' -i '.escapeshellarg($filePath)
123
                .' -vcodec '.$thisEncoder['videoCodec']
124
                .' '.$thisEncoder['videoCodecOptions']
125
                .' -bufsize 1000k'
126
                .' -threads '.$thisEncoder['threads'];
127
128
            // Set the framerate if desired
129
            if (!empty($videoOptions['videoFrameRate'])) {
130
                $ffmpegCmd .= ' -r '.$videoOptions['videoFrameRate'];
131
            }
132
133
            // Set the bitrate if desired
134
            if (!empty($videoOptions['videoBitRate'])) {
135
                $ffmpegCmd .= ' -b:v '.$videoOptions['videoBitRate'].' -maxrate '.$videoOptions['videoBitRate'];
136
            }
137
138
            // Adjust the scaling if desired
139
            $ffmpegCmd = $this->addScalingFfmpegArgs(
140
                $videoOptions,
141
                $ffmpegCmd
142
            );
143
144
            // Handle any audio transcoding
145
            if (empty($videoOptions['audioBitRate'])
146
                && empty($videoOptions['audioSampleRate'])
147
                && empty($videoOptions['audioChannels'])
148
            ) {
149
                // Just copy the audio if no options are provided
150
                $ffmpegCmd .= ' -c:a copy';
151
            } else {
152
                // Do audio transcoding based on the settings
153
                $ffmpegCmd .= ' -acodec '.$thisEncoder['audioCodec'];
154
                if (!empty($videoOptions['audioBitRate'])) {
155
                    $ffmpegCmd .= ' -b:a '.$videoOptions['audioBitRate'];
156
                }
157
                if (!empty($videoOptions['audioSampleRate'])) {
158
                    $ffmpegCmd .= ' -ar '.$videoOptions['audioSampleRate'];
159
                }
160
                if (!empty($videoOptions['audioChannels'])) {
161
                    $ffmpegCmd .= ' -ac '.$videoOptions['audioChannels'];
162
                }
163
                $ffmpegCmd .= ' '.$thisEncoder['audioCodecOptions'];
164
            }
165
166
            // Create the directory if it isn't there already
167
            if (!is_dir($destVideoPath)) {
168
                try {
169
                    FileHelper::createDirectory($destVideoPath);
170
                } catch (Exception $e) {
171
                    Craft::error($e->getMessage(), __METHOD__);
172
                }
173
            }
174
175
            $destVideoFile = $this->getFilename($filePath, $videoOptions);
176
177
            // File to store the video encoding progress in
178
            $progressFile = sys_get_temp_dir().DIRECTORY_SEPARATOR.$destVideoFile.'.progress';
179
180
            // Assemble the destination path and final ffmpeg command
181
            $destVideoPath .= $destVideoFile;
182
            $ffmpegCmd .= ' -f '
183
                .$thisEncoder['fileFormat']
184
                .' -y '.escapeshellarg($destVideoPath)
185
                .' 1> '.$progressFile.' 2>&1 & echo $!';
186
187
            // Make sure there isn't a lockfile for this video already
188
            $lockFile = sys_get_temp_dir().DIRECTORY_SEPARATOR.$destVideoFile.'.lock';
189
            $oldPid = @file_get_contents($lockFile);
190
            if ($oldPid !== false) {
191
                exec("ps $oldPid", $ProcessState);
192
                if (\count($ProcessState) >= 2) {
193
                    return $result;
194
                }
195
                // It's finished transcoding, so delete the lockfile and progress file
196
                @unlink($lockFile);
197
                @unlink($progressFile);
198
            }
199
200
            // If the video file already exists and hasn't been modified, return it.  Otherwise, start it transcoding
201
            if (!$this->shouldGenerateAsset('video', $destVideoPath, $filePath)) {
202
                $url = $settings['transcoderUrls']['video'] ?? $settings['transcoderUrls']['default'];
203
                $result = Craft::getAlias($url).$destVideoFile;
204
205
                if ($remoteUrl = $this->moveAssetToVolume('video', $destVideoPath)) {
206
                    $result = $remoteUrl;
207
                }
208
209
            // skip encoding
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 16 spaces, found 12
Loading history...
210
            } elseif (!$generate) {
211
	            $result = "";
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 16 spaces, found 13
Loading history...
212
            } else {
213
                // Kick off the transcoding
214
                $pid = $this->executeShellCommand($ffmpegCmd);
215
                Craft::info($ffmpegCmd."\nffmpeg PID: ".$pid, __METHOD__);
216
217
                // Create a lockfile in tmp
218
                file_put_contents($lockFile, $pid);
219
            }
220
221
            return $result;
222
        }
223
224
        return $result;
225
    }
226
227
    /**
228
     * Returns a URL to a video thumbnail
229
     *
230
     * @param string $filePath         path to the original video or an Asset
231
     * @param array  $thumbnailOptions of options for the thumbnail
232
     * @param bool   $generate         whether the thumbnail should be
233
     *                                 generated if it doesn't exists
234
     * @param bool   $asPath           Whether we should return a path or not
235
     *
236
     * @return string|false|null URL or path of the video thumbnail
237
     */
238
    public function getVideoThumbnailUrl($filePath, $thumbnailOptions, $generate = true, $asPath = false)
239
    {
240
        $result = null;
241
        $settings = Transcoder::$plugin->getSettings();
242
		$subfolder = '';
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 8 spaces, found 2
Loading history...
243
244
		// sub folder check
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 8 spaces, found 2
Loading history...
245
		if(\is_object($filePath) && ($filePath instanceof Asset) && $settings['createSubfolders']) {
0 ignored issues
show
introduced by
The condition is_object($filePath) is always false.
Loading history...
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 2
Loading history...
Coding Style introduced by
Expected "if (...) {\n"; found "if(...) {\n"
Loading history...
246
			$subfolder = $filePath->folderPath;
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 12 spaces, found 3
Loading history...
247
		}
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 2
Loading history...
248
249
        $filePath = $this->getAssetPath($filePath);
250
251
        if (!empty($filePath)) {
252
            $destThumbnailPath = $this->localDestPath('thumbnail', $subfolder);
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)) {
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 ($this->shouldGenerateAsset('thumbnail', $destThumbnailPath)) {
291
                if ($generate) {
292
                    /** @noinspection PhpUnusedLocalVariableInspection */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
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
301
                } else {
302
                    Craft::info('Thumbnail does not exist, but not asked to generate it: '.$filePath, __METHOD__);
303
304
                    // The file doesn't exist, and we weren't asked to generate it
305
                    return false;
306
                }
307
            }
308
309
            if ($remoteUrl = $this->moveAssetToVolume('thumbnail', $destThumbnailPath)) {
310
                return $remoteUrl;
311
            }
312
313
            // Return either a path or a URL
314
            if ($asPath) {
315
                $result = $destThumbnailPath;
316
            } else {
317
                $url = $settings['transcoderUrls']['thumbnail'] . $subfolder ?? $settings['transcoderUrls']['default'];
318
                $result = Craft::getAlias($url).$destThumbnailFile;
319
            }
320
        }
321
322
        return $result;
323
    }
324
325
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $filePath should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $audioOptions should have a doc-comment as per coding-style.
Loading history...
326
     * Returns a URL to the transcoded audio file or "" if it doesn't exist
327
     * (at which time it will create it).
328
     *
329
     * @param $filePath     string path to the original audio file -OR- an Asset
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
330
     * @param $audioOptions array of options for the audio file
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
331
     *
332
     * @return string       URL of the transcoded audio file or ""
333
     */
334
    public function getAudioUrl($filePath, $audioOptions): string
335
    {
336
        $result = '';
337
        $settings = Transcoder::$plugin->getSettings();
338
		$subfolder = '';
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 8 spaces, found 2
Loading history...
339
340
		// sub folder check
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 8 spaces, found 2
Loading history...
341
		if(\is_object($filePath) && ($filePath instanceof Asset) && $settings['createSubfolders']) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 2
Loading history...
Coding Style introduced by
Expected "if (...) {\n"; found "if(...) {\n"
Loading history...
342
			$subfolder = $filePath->folderPath;
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 12 spaces, found 3
Loading history...
343
		}
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 2
Loading history...
344
345
        $filePath = $this->getAssetPath($filePath);
346
347
        if (!empty($filePath)) {
348
            $destAudioPath = $this->localDestPath('audio', $subfolder);
0 ignored issues
show
Bug introduced by
It seems like $subfolder can also be of type null; however, parameter $subfolder of nystudio107\transcoder\s...nscode::localDestPath() 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

348
            $destAudioPath = $this->localDestPath('audio', /** @scrutinizer ignore-type */ $subfolder);
Loading history...
349
350
            $audioOptions = $this->coalesceOptions('defaultAudioOptions', $audioOptions);
351
352
            // Get the audio encoder presets to use
353
            $audioEncoders = $settings['audioEncoders'];
354
            $thisEncoder = $audioEncoders[$audioOptions['audioEncoder']];
355
356
            $audioOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
357
358
            // Build the basic command for ffmpeg
359
            $ffmpegCmd = $settings['ffmpegPath']
360
                .' -i '.escapeshellarg($filePath)
361
                .' -acodec '.$thisEncoder['audioCodec']
362
                .' '.$thisEncoder['audioCodecOptions']
363
                .' -bufsize 1000k'
364
                .' -threads '.$thisEncoder['threads'];
365
366
            // Set the bitrate if desired
367
            if (!empty($audioOptions['audioBitRate'])) {
368
                $ffmpegCmd .= ' -b:a '.$audioOptions['audioBitRate'];
369
            }
370
            // Set the sample rate if desired
371
            if (!empty($audioOptions['audioSampleRate'])) {
372
                $ffmpegCmd .= ' -ar '.$audioOptions['audioSampleRate'];
373
            }
374
            // Set the audio channels if desired
375
            if (!empty($audioOptions['audioChannels'])) {
376
                $ffmpegCmd .= ' -ac '.$audioOptions['audioChannels'];
377
            }
378
            $ffmpegCmd .= ' '.$thisEncoder['audioCodecOptions'];
379
380
381
            // Create the directory if it isn't there already
382
            if (!is_dir($destAudioPath)) {
383
                try {
384
                    FileHelper::createDirectory($destAudioPath);
385
                } catch (Exception $e) {
386
                    Craft::error($e->getMessage(), __METHOD__);
387
                }
388
            }
389
390
            $destAudioFile = $this->getFilename($filePath, $audioOptions);
391
392
            // File to store the audio encoding progress in
393
            $progressFile = sys_get_temp_dir().DIRECTORY_SEPARATOR.$destAudioFile.'.progress';
394
395
            // Assemble the destination path and final ffmpeg command
396
            $destAudioPath .= $destAudioFile;
397
            $ffmpegCmd .= ' -f '
398
                .$thisEncoder['fileFormat']
399
                .' -y '.escapeshellarg($destAudioPath)
400
                .' 1> '.$progressFile.' 2>&1 & echo $!';
401
402
            // Make sure there isn't a lockfile for this audio file already
403
            $lockFile = sys_get_temp_dir().DIRECTORY_SEPARATOR.$destAudioFile.'.lock';
404
            $oldPid = @file_get_contents($lockFile);
405
            if ($oldPid !== false) {
406
                exec("ps $oldPid", $ProcessState);
407
                if (\count($ProcessState) >= 2) {
408
                    return $result;
409
                }
410
                // It's finished transcoding, so delete the lockfile and progress file
411
                @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

411
                /** @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...
412
                @unlink($progressFile);
413
            }
414
415
            // If the audio file already exists and hasn't been modified, return it.  Otherwise, start it transcoding
416
            if ($this->shouldGenerateAsset('audio', $destAudioPath, $filePath)) {
417
                $url = $settings['transcoderUrls']['audio'] . $subfolder ?? $settings['transcoderUrls']['default'];
418
                $result = Craft::getAlias($url).$destAudioFile;
419
420
                if ($remoteUrl = $this->moveAssetToVolume('audio', $destAudioPath)) {
421
                    $result = $remoteUrl;
422
                }
423
            } else {
424
                // Kick off the transcoding
425
                $pid = $this->executeShellCommand($ffmpegCmd);
426
                Craft::info($ffmpegCmd."\nffmpeg PID: ".$pid, __METHOD__);
427
428
                // Create a lockfile in tmp
429
                file_put_contents($lockFile, $pid);
430
            }
431
        }
432
433
        return $result;
434
    }
435
436
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $filePath should have a doc-comment as per coding-style.
Loading history...
437
     * Extract information from a video/audio file
438
     *
439
     * @param      $filePath
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 6
Loading history...
440
     * @param bool $summary
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 5 spaces after parameter type; 1 found
Loading history...
441
     *
442
     * @return null|array
443
     */
444
    public function getFileInfo($filePath, $summary = false)
445
    {
446
        $result = null;
447
        $settings = Transcoder::$plugin->getSettings();
448
        $filePath = $this->getAssetPath($filePath);
449
450
        if (!empty($filePath)) {
451
            // Build the basic command for ffprobe
452
            $ffprobeOptions = $settings['ffprobeOptions'];
453
            $ffprobeCmd = $settings['ffprobePath']
454
                .' '.$ffprobeOptions
455
                .' '.escapeshellarg($filePath);
456
457
            $shellOutput = $this->executeShellCommand($ffprobeCmd);
458
            Craft::info($ffprobeCmd, __METHOD__);
459
            $result = JsonHelper::decodeIfJson($shellOutput, true);
460
            Craft::info(print_r($result, true), __METHOD__);
461
462
            // Trim down the arrays to just a summary
463
            if ($summary && !empty($result)) {
464
                $summaryResult = [];
465
                foreach ($result as $topLevelKey => $topLevelValue) {
466
                    switch ($topLevelKey) {
467
                        // Format info
468
                        case 'format':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
469
                            foreach (self::INFO_SUMMARY['format'] as $settingKey => $settingValue) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
470
                                if (!empty($topLevelValue[$settingKey])) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
471
                                    $summaryResult[$settingValue] = $topLevelValue[$settingKey];
472
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
473
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
474
                            break;
475
                        // Stream info
476
                        case 'streams':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
477
                            foreach ($topLevelValue as $stream) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
478
                                $infoSummaryType = $stream['codec_type'];
479
                                if (in_array($infoSummaryType, self::INFO_SUMMARY, false)) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
480
                                    foreach (self::INFO_SUMMARY[$infoSummaryType] as $settingKey => $settingValue) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 32 spaces, found 36
Loading history...
481
                                        if (!empty($stream[$settingKey])) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 36 spaces, found 40
Loading history...
482
                                            $summaryResult[$settingValue] = $stream[$settingKey];
483
                                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 36 spaces, found 40
Loading history...
484
                                    }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 32 spaces, found 36
Loading history...
485
                                }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 28 spaces, found 32
Loading history...
486
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
487
                            break;
488
                        // Unknown info
489
                        default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
490
                            break;
491
                    }
492
                }
493
                // Handle cases where the framerate is returned as XX/YY
494
                if (!empty($summaryResult['videoFrameRate'])
495
                    && (strpos($summaryResult['videoFrameRate'], '/') !== false)
496
                ) {
497
                    $parts = explode('/', $summaryResult['videoFrameRate']);
498
                    $summaryResult['videoFrameRate'] = (float)$parts[0] / (float)$parts[1];
499
                }
500
                $result = $summaryResult;
501
            }
502
        }
503
504
        return $result;
505
    }
506
507
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $filePath should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $videoOptions should have a doc-comment as per coding-style.
Loading history...
508
     * Get the name of a video file from a path and options
509
     *
510
     * @param $filePath
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
511
     * @param $videoOptions
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
512
     *
513
     * @return string
514
     */
515
    public function getVideoFilename($filePath, $videoOptions): string
516
    {
517
        $settings = Transcoder::$plugin->getSettings();
518
        $videoOptions = $this->coalesceOptions('defaultVideoOptions', $videoOptions);
519
520
        // Get the video encoder presets to use
521
        $videoEncoders = $settings['videoEncoders'];
522
        $thisEncoder = $videoEncoders[$videoOptions['videoEncoder']];
523
524
        $videoOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
525
526
        return $this->getFilename($filePath, $videoOptions);
527
    }
528
529
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $filePath should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $audioOptions should have a doc-comment as per coding-style.
Loading history...
530
     * Get the name of an audio file from a path and options
531
     *
532
     * @param $filePath
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
533
     * @param $audioOptions
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
534
     *
535
     * @return string
536
     */
537
    public function getAudioFilename($filePath, $audioOptions): string
538
    {
539
        $settings = Transcoder::$plugin->getSettings();
540
        $audioOptions = $this->coalesceOptions('defaultAudioOptions', $audioOptions);
541
542
        // Get the video encoder presets to use
543
        $audioEncoders = $settings['audioEncoders'];
544
        $thisEncoder = $audioEncoders[$audioOptions['audioEncoder']];
545
546
        $audioOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
547
548
        return $this->getFilename($filePath, $audioOptions);
549
    }
550
551
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $filePath should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $gifOptions should have a doc-comment as per coding-style.
Loading history...
552
     * Get the name of a gif video file from a path and options
553
     *
554
     * @param $filePath
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
555
     * @param $gifOptions
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
556
     *
557
     * @return string
558
     */
559
    public function getGifFilename($filePath, $gifOptions): string
560
    {
561
        $settings = Transcoder::$plugin->getSettings();
562
        $gifOptions = $this->coalesceOptions('defaultGifOptions', $gifOptions);
563
564
        // Get the video encoder presets to use
565
        $videoEncoders = $settings['videoEncoders'];
566
        $thisEncoder = $videoEncoders[$gifOptions['videoEncoder']];
567
568
        $gifOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
569
570
        return $this->getFilename($filePath, $gifOptions);
571
    }
572
573
    /**
574
     * Handle generated a thumbnail for the Control Panel
575
     *
576
     * @param AssetThumbEvent $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
577
     *
578
     * @return null|false|string
579
     */
580
    public function handleGetAssetThumbPath(AssetThumbEvent $event)
581
    {
582
        $options = [
583
            'width' => $event->width,
584
            'height' => $event->height,
585
        ];
586
        $thumbPath = $this->getVideoThumbnailUrl($event->asset, $options, $event->generate, true);
587
588
        return $thumbPath;
589
    }
590
591
    // Protected Methods
592
    // =========================================================================
593
594
    /**
595
     * Returns a URL to a encoded GIF file (mp4)
596
     *
597
     * @param string $filePath         path to the original video or an Asset
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 9 found
Loading history...
598
     * @param array  $gifOptions       of options for the GIF file
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 7 found
Loading history...
599
     *
600
     * @return string|false|null URL or path of the GIF file
601
     */
0 ignored issues
show
Coding Style introduced by
There must be no blank lines after the function comment
Loading history...
602
603
    public function getGifUrl($filePath, $gifOptions): string
604
    {
605
        $result = '';
606
        $settings = Transcoder::$plugin->getSettings();
607
        $subfolder = '';
608
609
        // sub folder check
610
        if(\is_object($filePath) && ($filePath instanceof Asset) && $settings['createSubfolders']) {
0 ignored issues
show
introduced by
The condition is_object($filePath) is always false.
Loading history...
Coding Style introduced by
Expected "if (...) {\n"; found "if(...) {\n"
Loading history...
611
            $subfolder = $filePath->folderPath;
612
        }
613
614
        $filePath = $this->getAssetPath($filePath);
615
616
        if (!empty($filePath)) {
617
            // Dest path
618
            $destVideoPath = $this->localDestPath('gif', $subfolder);
619
620
            // Options
621
            $gifOptions = $this->coalesceOptions('defaultGifOptions', $gifOptions);
622
623
            // Get the video encoder presets to use
624
            $videoEncoders = $settings['videoEncoders'];
625
            $thisEncoder = $videoEncoders[$gifOptions['videoEncoder']];
626
            $gifOptions['fileSuffix'] = $thisEncoder['fileSuffix'];
627
628
            // Build the basic command for ffmpeg
629
            $ffmpegCmd = $settings['ffmpegPath']
630
                .' -f gif'
631
                .' -i '.escapeshellarg($filePath)
632
                .' -vcodec '.$thisEncoder['videoCodec']
633
                .' '.$thisEncoder['videoCodecOptions'];
634
635
636
            // Create the directory if it isn't there already
637
            if (!is_dir($destVideoPath)) {
638
                try {
639
                    FileHelper::createDirectory($destVideoPath);
640
                } catch (Exception $e) {
641
                    Craft::error($e->getMessage(), __METHOD__);
642
                }
643
            }
644
645
            $destVideoFile = $this->getFilename($filePath, $gifOptions);
646
647
            // File to store the video encoding progress in
648
            $progressFile = sys_get_temp_dir().DIRECTORY_SEPARATOR.$destVideoFile.'.progress';
649
650
            // Assemble the destination path and final ffmpeg command
651
            $destVideoPath .= $destVideoFile;
652
            $ffmpegCmd .= ' '
653
                .' -y '.escapeshellarg($destVideoPath)
654
                .' 1> '.$progressFile.' 2>&1 & echo $!';
655
656
            // Make sure there isn't a lockfile for this video already
657
            $lockFile = sys_get_temp_dir().DIRECTORY_SEPARATOR.$destVideoFile.'.lock';
658
            $oldPid = @file_get_contents($lockFile);
659
            if ($oldPid !== false) {
660
                exec("ps $oldPid", $ProcessState);
661
                if (\count($ProcessState) >= 2) {
662
                    return $result;
663
                }
664
                // It's finished transcoding, so delete the lockfile and progress file
665
                @unlink($lockFile);
666
                @unlink($progressFile);
667
            }
668
669
            // If the video file already exists and hasn't been modified, return it.  Otherwise, start it transcoding
670
            if ($this->shouldGenerateAsset('gif', $destVideoPath, $filePath)) {
671
                $url = $settings['transcoderUrls']['gif'] . $subfolder ?? $settings['transcoderUrls']['default'];
672
                $result = Craft::getAlias($url).$destVideoFile;
673
674
                if ($remoteUrl = $this->moveAssetToVolume('gif', $destVideoPath)) {
675
                    $result = $remoteUrl;
676
                }
677
            } else {
678
                // Kick off the transcoding
679
                $pid = $this->executeShellCommand($ffmpegCmd);
680
                Craft::info($ffmpegCmd."\nffmpeg PID: ".$pid, __METHOD__);
681
682
                // Create a lockfile in tmp
683
                file_put_contents($lockFile, $pid);
684
            }
685
        }
686
687
        return $result;
688
    }
689
690
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $filePath should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $options should have a doc-comment as per coding-style.
Loading history...
691
     * Get the name of a file from a path and options
692
     *
693
     * @param $filePath
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
694
     * @param $options
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
695
     *
696
     * @return string
697
     */
698
    protected function getFilename($filePath, $options): string
699
    {
700
        $settings = Transcoder::$plugin->getSettings();
701
        $filePath = $this->getAssetPath($filePath);
702
703
        $validator = new UrlValidator();
704
        $error = '';
705
        if ($validator->validate($filePath, $error)) {
706
            $urlParts = parse_url($filePath);
707
            $pathParts = pathinfo($urlParts['path']);
708
        } else {
709
            $pathParts = pathinfo($filePath);
710
        }
711
        $fileName = $pathParts['filename'];
712
713
        // Add our options to the file name
714
        foreach ($options as $key => $value) {
715
            if (!empty($value)) {
716
                $suffix = '';
717
                if (!empty(self::SUFFIX_MAP[$key])) {
718
                    $suffix = self::SUFFIX_MAP[$key];
719
                }
720
                if (\is_bool($value)) {
721
                    $value = $value ? $key : 'no'.$key;
722
                }
723
                if (!\in_array($key, self::EXCLUDE_PARAMS, true)) {
724
                    $fileName .= '_'.$value.$suffix;
725
                }
726
            }
727
        }
728
        // See if we should use a hash instead
729
        if ($settings['useHashedNames']) {
730
            $fileName = $pathParts['filename'].md5($fileName);
731
        }
732
        $fileName .= $options['fileSuffix'];
733
734
        return $fileName;
735
    }
736
737
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $filePath should have a doc-comment as per coding-style.
Loading history...
738
     * Extract a file system path if $filePath is an Asset object
739
     *
740
     * @param $filePath
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
741
     *
742
     * @return string
743
     */
744
    protected function getAssetPath($filePath): string
745
    {
746
        // If we're passed an Asset, extract the path from it
747
        if (\is_object($filePath) && ($filePath instanceof Asset)) {
748
            /** @var Asset $asset */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
749
            $asset = $filePath;
750
            $assetVolume = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $assetVolume is dead and can be removed.
Loading history...
751
            try {
752
                $assetVolume = $asset->getVolume();
753
            } catch (InvalidConfigException $e) {
754
                Craft::error($e->getMessage(), __METHOD__);
755
            }
756
757
            if ($assetVolume) {
0 ignored issues
show
introduced by
$assetVolume is of type craft\base\VolumeInterface, thus it always evaluated to true.
Loading history...
758
                // If it's local, get a path to the file
759
                if ($assetVolume instanceof Local) {
760
                    $sourcePath = rtrim($assetVolume->path, DIRECTORY_SEPARATOR);
761
                    $sourcePath .= '' === $sourcePath ? '': DIRECTORY_SEPARATOR;
762
                    $folderPath = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $folderPath is dead and can be removed.
Loading history...
763
                    try {
764
                        $folderPath = rtrim($asset->getFolder()->path, DIRECTORY_SEPARATOR);
765
                    } catch (InvalidConfigException $e) {
766
                        Craft::error($e->getMessage(), __METHOD__);
767
                    }
768
                    $folderPath .= '' === $folderPath ? '': DIRECTORY_SEPARATOR;
769
770
                    $filePath = $sourcePath.$folderPath.$asset->filename;
771
                } else {
772
                    // Otherwise, get a URL
773
                    $filePath = $asset->getUrl();
774
                }
775
            }
776
        }
777
778
        $filePath = Craft::getAlias($filePath);
779
780
        // Make sure that $filePath is either an existing file, or a valid URL
781
        if (!file_exists($filePath)) {
782
            $validator = new UrlValidator();
783
            $error = '';
784
            if (!$validator->validate($filePath, $error)) {
785
                Craft::error($error, __METHOD__);
786
                $filePath = '';
787
            }
788
        }
789
790
        return $filePath;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $filePath could return the type boolean which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
791
    }
792
793
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $options should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $ffmpegCmd should have a doc-comment as per coding-style.
Loading history...
794
     * Set the width & height if desired
795
     *
796
     * @param $options
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
797
     * @param $ffmpegCmd
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
798
     *
799
     * @return string
800
     */
801
    protected function addScalingFfmpegArgs($options, $ffmpegCmd): string
802
    {
803
        if (!empty($options['width']) && !empty($options['height'])) {
804
            // Handle "none", "crop", and "letterbox" aspectRatios
805
            $aspectRatio = '';
806
            if (!empty($options['aspectRatio'])) {
807
                switch ($options['aspectRatio']) {
808
                    // Scale to the appropriate aspect ratio, padding
809
                    case 'letterbox':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
810
                        $letterboxColor = '';
811
                        if (!empty($options['letterboxColor'])) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
812
                            $letterboxColor = ':color='.$options['letterboxColor'];
813
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
814
                        $aspectRatio = ':force_original_aspect_ratio=decrease'
815
                            .',pad='.$options['width'].':'.$options['height'].':(ow-iw)/2:(oh-ih)/2'
816
                            .$letterboxColor;
817
                        break;
818
                    // Scale to the appropriate aspect ratio, cropping
819
                    case 'crop':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
820
                        $aspectRatio = ':force_original_aspect_ratio=increase'
821
                            .',crop='.$options['width'].':'.$options['height'];
822
                        break;
823
                    // No aspect ratio scaling at all
824
                    default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
825
                        $aspectRatio = ':force_original_aspect_ratio=disable';
826
                        $options['aspectRatio'] = 'none';
827
                        break;
828
                }
829
            }
830
            $sharpen = '';
831
            if (!empty($options['sharpen']) && ($options['sharpen'] !== false)) {
832
                $sharpen = ',unsharp=5:5:1.0:5:5:0.0';
833
            }
834
            $ffmpegCmd .= ' -vf "scale='
835
                .$options['width'].':'.$options['height']
836
                .$aspectRatio
837
                .$sharpen
838
                .'"';
839
        }
840
841
        return $ffmpegCmd;
842
    }
843
844
    // Protected Methods
845
    // =========================================================================
846
847
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $defaultName should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $options should have a doc-comment as per coding-style.
Loading history...
848
     * Combine the options arrays
849
     *
850
     * @param $defaultName
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
851
     * @param $options
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
852
     *
853
     * @return array
854
     */
855
    protected function coalesceOptions($defaultName, $options): array
856
    {
857
        // Default options
858
        $settings = Transcoder::$plugin->getSettings();
859
        $defaultOptions = $settings[$defaultName];
860
861
        // Coalesce the passed in $options with the $defaultOptions
862
        $options = array_merge($defaultOptions, $options);
863
864
        return $options;
865
    }
866
867
    /**
868
     * Execute a shell command
869
     *
870
     * @param string $command
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
871
     *
872
     * @return string
873
     */
874
    protected function executeShellCommand(string $command): string
875
    {
876
        // Create the shell command
877
        $shellCommand = new ShellCommand();
878
        $shellCommand->setCommand($command);
879
880
        // If we don't have proc_open, maybe we've got exec
881
        if (!\function_exists('proc_open') && \function_exists('exec')) {
882
            $shellCommand->useExec = true;
883
        }
884
885
        // Return the result of the command's output or error
886
        if ($shellCommand->execute()) {
887
            $result = $shellCommand->getOutput();
888
        } else {
889
            $result = $shellCommand->getError();
890
        }
891
892
        return $result;
893
    }
894
895
    protected function shouldGenerateAsset(string $type, string $destVideoFilePath, string $originalFilePath = null)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function shouldGenerateAsset()
Loading history...
896
    {
897
        $settings = Transcoder::$plugin->getSettings();
898
        $destSettings = $settings['transcoderPaths'][$type] ?? $settings['transcoderPaths']['default'];
899
900
        if (file_exists($destVideoFilePath) && (@filemtime($destVideoFilePath) >= @filemtime($originalFilePath))) {
901
            return false;
902
        }
903
904
        if (!is_array($destSettings)) {
905
            return true;
906
        }
907
908
        $filename = basename($destVideoFilePath);
909
910
        $volume = Craft::$app->getVolumes()->getVolumeByHandle($destSettings['volume']);
911
        /** @var Asset $asset */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
912
        $asset = Asset::findOne(['volumeId' => $volume->id, 'filename' => $filename]);
0 ignored issues
show
Bug introduced by
Accessing id on the interface craft\base\VolumeInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
913
914
        if ($asset && $asset->dateModified->getTimestamp() >= @filemtime($originalFilePath)) {
0 ignored issues
show
Bug introduced by
The method getTimestamp() does not exist on null. ( Ignorable by Annotation )

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

914
        if ($asset && $asset->dateModified->/** @scrutinizer ignore-call */ getTimestamp() >= @filemtime($originalFilePath)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
915
            return false;
916
        }
917
        return true;
918
    }
919
920
    protected function localDestPath(string $type, string $subfolder) {
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function localDestPath()
Loading history...
Coding Style introduced by
Opening brace should be on a new line
Loading history...
921
        $settings = Transcoder::$plugin->getSettings();
922
        $destSettings = $settings['transcoderPaths'][$type]  ?? $settings['transcoderPaths']['default'];
923
        if (is_array($destSettings)) {
924
            return sys_get_temp_dir().DIRECTORY_SEPARATOR;
925
        }
926
        if (isset($settings['transcoderPaths'][$type])) {
927
            $destSettings .= $subfolder;
928
        }
929
        return Craft::getAlias($destSettings);
930
    }
931
932
    protected function moveAssetToVolume(string $type, string $filePath)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function moveAssetToVolume()
Loading history...
933
    {
934
        $settings = Transcoder::$plugin->getSettings();
935
        $destSettings = $settings['transcoderPaths'][$type] ?? $settings['transcoderPaths']['default'];
936
937
        // check if the setting is a volume
938
        if (!is_array($destSettings) || empty($filePath)) {
939
            return;
940
        }
941
942
        $volume = Craft::$app->getVolumes()->getVolumeByHandle($destSettings['volume']);
943
        if (!$volume) {
944
            $message = sprintf('Volume (handle: "%s") does not exist.', $destSettings['volume']);
945
            throw new \RuntimeException($message);
946
        }
947
948
        $filename = basename($filePath);
949
950
        /** @var Assets $assetService */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
951
        $assetElement = Asset::findOne(['volumeId' => $volume->id, 'filename' => $filename]);
0 ignored issues
show
Bug introduced by
Accessing id on the interface craft\base\VolumeInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
952
        if ($assetElement) {
0 ignored issues
show
introduced by
$assetElement is of type craft\elements\Asset, thus it always evaluated to true.
Loading history...
953
            return $assetElement->getUrl();
954
        }
955
956
        // check if the given folder exists
957
        $volumeFolder = VolumeFolder::findOne(['path' => $destSettings['folder'], 'volumeId' => $volume->id]);
958
        if (!$volumeFolder) {
959
            $message = sprintf('Folder (path: "%s") in volume (handle: "%s") does not exist.', $destSettings['folder'], $destSettings['volume']);
960
            throw new \RuntimeException($message);
961
        }
962
963
        $asset = new Asset();
964
        $asset->volumeId = $volume->id;
965
        $asset->tempFilePath = $filePath;
966
        $asset->filename = $filename;
967
        $asset->folderId = $volumeFolder->id;
968
969
        Craft::$app->getElements()->saveElement($asset);
970
        @unlink($filePath);
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

970
        /** @scrutinizer ignore-unhandled */ @unlink($filePath);

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...
971
972
        return $asset->getUrl();
973
    }
974
}
975