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
Coding Style
introduced
by
Loading history...
|
|||||
8 | * @copyright Copyright (c) 2017 nystudio107 |
||||
0 ignored issues
–
show
|
|||||
9 | */ |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
32 | * @author nystudio107 |
||||
0 ignored issues
–
show
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
|
|||||
33 | * @package Transcode |
||||
0 ignored issues
–
show
|
|||||
34 | * @since 1.0.0 |
||||
0 ignored issues
–
show
|
|||||
35 | */ |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
93 | * Asset |
||||
94 | * @param $videoOptions array of options for the video |
||||
0 ignored issues
–
show
|
|||||
95 | * @param bool $generate whether the video should be encoded |
||||
0 ignored issues
–
show
|
|||||
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
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
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
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
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
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
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
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
|
|||||
230 | * @param array $thumbnailOptions of options for the thumbnail |
||||
0 ignored issues
–
show
|
|||||
231 | * @param bool $generate whether the thumbnail should be |
||||
0 ignored issues
–
show
|
|||||
232 | * generated if it doesn't exists |
||||
0 ignored issues
–
show
|
|||||
233 | * @param bool $asPath Whether we should return a path or not |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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
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
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
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
|
|||||
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
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
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
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
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
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
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
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
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
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
|
|||||
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
|
|||||
462 | * @param bool $summary |
||||
0 ignored issues
–
show
|
|||||
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
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
|
|||||
494 | foreach (self::INFO_SUMMARY['format'] as $settingKey => $settingValue) { |
||||
0 ignored issues
–
show
|
|||||
495 | if (!empty($topLevelValue[$settingKey])) { |
||||
0 ignored issues
–
show
|
|||||
496 | $summaryResult[$settingValue] = $topLevelValue[$settingKey]; |
||||
497 | } |
||||
0 ignored issues
–
show
|
|||||
498 | } |
||||
0 ignored issues
–
show
|
|||||
499 | break; |
||||
500 | // Stream info |
||||
501 | case 'streams': |
||||
0 ignored issues
–
show
|
|||||
502 | foreach ($topLevelValue as $stream) { |
||||
0 ignored issues
–
show
|
|||||
503 | $infoSummaryType = $stream['codec_type']; |
||||
504 | if (in_array($infoSummaryType, self::INFO_SUMMARY, false)) { |
||||
0 ignored issues
–
show
|
|||||
505 | foreach (self::INFO_SUMMARY[$infoSummaryType] as $settingKey => $settingValue) { |
||||
0 ignored issues
–
show
|
|||||
506 | if (!empty($stream[$settingKey])) { |
||||
0 ignored issues
–
show
|
|||||
507 | $summaryResult[$settingValue] = $stream[$settingKey]; |
||||
508 | } |
||||
0 ignored issues
–
show
|
|||||
509 | } |
||||
0 ignored issues
–
show
|
|||||
510 | } |
||||
0 ignored issues
–
show
|
|||||
511 | } |
||||
0 ignored issues
–
show
|
|||||
512 | break; |
||||
513 | // Unknown info |
||||
514 | default: |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
536 | * @param $videoOptions |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
558 | * @param $audioOptions |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
580 | * @param $gifOptions |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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
|
|||||
623 | * @param array $gifOptions of options for the GIF file |
||||
0 ignored issues
–
show
|
|||||
624 | * |
||||
625 | * @return string|false|null URL or path of the GIF file |
||||
626 | */ |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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
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
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
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
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
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
|
|||||
718 | * @param $options |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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
|
|||||
773 | $asset = $filePath; |
||||
774 | $assetVolume = null; |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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
Loading history...
|
|||||
785 | $sourcePath .= '' === $sourcePath ? '' : DIRECTORY_SEPARATOR; |
||||
786 | $folderPath = ''; |
||||
0 ignored issues
–
show
|
|||||
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
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
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
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
|
|||||
815 | } |
||||
816 | |||||
817 | /** |
||||
818 | * Set the width & height if desired |
||||
819 | * |
||||
820 | * @param $options |
||||
0 ignored issues
–
show
|
|||||
821 | * @param $ffmpegCmd |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
834 | $letterboxColor = ''; |
||||
835 | if (!empty($options['letterboxColor'])) { |
||||
0 ignored issues
–
show
|
|||||
836 | $letterboxColor = ':color=' . $options['letterboxColor']; |
||||
837 | } |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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
|
|||||
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
|
|||||
875 | * @param $options |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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 |