1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This class handled Video transcoding |
5
|
|
|
* Here we the input video |
6
|
|
|
* We transcode and generate output videos |
7
|
|
|
* We use ffprobe, ffmpeg and convert to analyse, transcode and manipulate videos and images (watermark) |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
require_once __DIR__ . '/BasicTranscoder.php'; |
11
|
|
|
|
12
|
|
|
use SA\CpeSdk; |
13
|
|
|
|
14
|
|
|
class VideoTranscoder extends BasicTranscoder |
15
|
|
|
{ |
16
|
|
|
// Errors |
17
|
|
|
const EXEC_VALIDATE_FAILED = "EXEC_VALIDATE_FAILED"; |
18
|
|
|
const GET_VIDEO_INFO_FAILED = "GET_VIDEO_INFO_FAILED"; |
19
|
|
|
const GET_AUDIO_INFO_FAILED = "GET_AUDIO_INFO_FAILED"; |
20
|
|
|
const GET_DURATION_FAILED = "GET_DURATION_FAILED"; |
21
|
|
|
const NO_OUTPUT = "NO_OUTPUT"; |
22
|
|
|
const BAD_OUTPUT = "BAD_OUTPUT"; |
23
|
|
|
const NO_PRESET = "NO_PRESET"; |
24
|
|
|
const BAD_PRESETS_DIR = "BAD_PRESETS_DIR"; |
25
|
|
|
const UNKNOWN_PRESET = "UNKNOWN_PRESET"; |
26
|
|
|
const OPEN_PRESET_FAILED = "OPEN_PRESET_FAILED"; |
27
|
|
|
const BAD_PRESET_FORMAT = "BAD_PRESET_FORMAT"; |
28
|
|
|
const RATIO_ERROR = "RATIO_ERROR"; |
29
|
|
|
const ENLARGEMENT_ERROR = "ENLARGEMENT_ERROR"; |
30
|
|
|
const TRANSCODE_FAIL = "TRANSCODE_FAIL"; |
31
|
|
|
const WATERMARK_ERROR = "WATERMARK_ERROR"; |
32
|
|
|
|
33
|
|
|
const SNAPSHOT_SEC_DEFAULT = 0; |
34
|
|
|
const INTERVALS_DEFAULT = 10; |
35
|
|
|
|
36
|
|
|
|
37
|
|
|
/*********************** |
38
|
|
|
* TRANSCODE INPUT VIDEO |
39
|
|
|
* Below is the code used to transcode videos based on the JSON format |
40
|
|
|
**********************/ |
41
|
|
|
|
42
|
|
|
// $metadata should contain the ffprobe video stream array. |
43
|
|
|
|
44
|
|
|
// Start FFmpeg for output transcoding |
45
|
|
|
public function transcode_asset( |
46
|
|
|
$tmpPathInput, |
47
|
|
|
$pathToInputFile, |
48
|
|
|
$pathToOutputFiles, |
49
|
|
|
$metadata = null, |
50
|
|
|
$outputWanted) |
51
|
|
|
{ |
52
|
|
|
if (!$metadata) |
53
|
|
|
throw new CpeSdk\CpeException( |
54
|
|
|
"NO Input Video metadata! We can't transcode an asset without probing it first. Use ValidateAsset activity to probe it and pass a 'metadata' field containing the input metadata to this TranscodeAsset activity.", |
55
|
|
|
self::TRANSCODE_FAIL |
56
|
|
|
); |
57
|
|
|
|
58
|
|
|
$ffmpegCmd = ""; |
59
|
|
|
|
60
|
|
|
// Custom command |
61
|
|
|
if (isset($outputWanted->{'custom_cmd'}) && |
62
|
|
|
$outputWanted->{'custom_cmd'}) { |
63
|
|
|
$ffmpegCmd = $this->craft_ffmpeg_custom_cmd( |
64
|
|
|
$tmpPathInput, |
65
|
|
|
$pathToInputFile, |
66
|
|
|
$pathToOutputFiles, |
67
|
|
|
$metadata, |
68
|
|
|
$outputWanted |
69
|
|
|
); |
70
|
|
|
} else if ($outputWanted->{'type'} == self::VIDEO) { |
71
|
|
|
$ffmpegCmd = $this->craft_ffmpeg_cmd_video( |
72
|
|
|
$tmpPathInput, |
73
|
|
|
$pathToInputFile, |
74
|
|
|
$pathToOutputFiles, |
75
|
|
|
$metadata, |
76
|
|
|
$outputWanted |
77
|
|
|
); |
78
|
|
|
} else if ($outputWanted->{'type'} == self::THUMB) { |
79
|
|
|
$ffmpegCmd = $this->craft_ffmpeg_cmd_thumb( |
80
|
|
|
$tmpPathInput, |
81
|
|
|
$pathToInputFile, |
82
|
|
|
$pathToOutputFiles, |
83
|
|
|
$metadata, |
84
|
|
|
$outputWanted |
85
|
|
|
); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
$this->cpeLogger->log_out( |
89
|
|
|
"INFO", |
90
|
|
|
basename(__FILE__), |
91
|
|
|
"FFMPEG CMD:\n$ffmpegCmd\n", |
92
|
|
|
$this->activityLogKey |
93
|
|
|
); |
94
|
|
|
|
95
|
|
|
$this->cpeLogger->log_out( |
96
|
|
|
"INFO", |
97
|
|
|
basename(__FILE__), |
98
|
|
|
"Start Transcoding Asset '$pathToInputFile' ...", |
99
|
|
|
$this->activityLogKey |
100
|
|
|
); |
101
|
|
|
|
102
|
|
|
if ($metadata) |
103
|
|
|
$this->cpeLogger->log_out( |
104
|
|
|
"INFO", |
105
|
|
|
basename(__FILE__), |
106
|
|
|
"Input Video metadata: " . print_r($metadata, true), |
107
|
|
|
$this->activityLogKey |
108
|
|
|
); |
109
|
|
|
|
110
|
|
|
try { |
111
|
|
|
// Use executer to start FFMpeg command |
112
|
|
|
// Use 'capture_progression' function as callback |
|
|
|
|
113
|
|
|
// Pass video 'duration' as parameter |
114
|
|
|
// Sleep 1sec between turns and callback every 10 turns |
115
|
|
|
// Output progression logs (true) |
116
|
|
|
$this->executer->execute( |
117
|
|
|
$ffmpegCmd, |
118
|
|
|
1, |
119
|
|
|
array(2 => array("pipe", "w")), |
120
|
|
|
array($this, "capture_progression"), |
121
|
|
|
$metadata->{'duration'}, |
122
|
|
|
true, |
123
|
|
|
10 |
124
|
|
|
); |
125
|
|
|
|
126
|
|
|
// Test if we have an output file ! |
127
|
|
|
if (!file_exists($pathToOutputFiles) || |
128
|
|
|
$this->is_dir_empty($pathToOutputFiles)) { |
129
|
|
|
throw new CpeSdk\CpeException( |
130
|
|
|
"Output file '$pathToOutputFiles' hasn't been created successfully or is empty !", |
131
|
|
|
self::TRANSCODE_FAIL |
132
|
|
|
); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
// FFProbe the output file and return its information |
136
|
|
|
$output_info = |
137
|
|
|
$this->get_asset_info($pathToOutputFiles."/".$outputWanted->{'output_file_info'}['basename']); |
138
|
|
|
} |
139
|
|
|
catch (\Exception $e) { |
140
|
|
|
$this->cpeLogger->log_out( |
141
|
|
|
"ERROR", |
142
|
|
|
basename(__FILE__), |
143
|
|
|
"Execution of command '".$ffmpegCmd."' failed: " . print_r($metadata, true). ". ".$e->getMessage(), |
144
|
|
|
$this->activityLogKey |
145
|
|
|
); |
146
|
|
|
return false; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
// No error. Transcode successful |
150
|
|
|
$this->cpeLogger->log_out( |
151
|
|
|
"INFO", |
152
|
|
|
basename(__FILE__), |
153
|
|
|
"Transcoding successfull !", |
154
|
|
|
$this->activityLogKey |
155
|
|
|
); |
156
|
|
|
|
157
|
|
|
return $output_info; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
// Craft custom command |
161
|
|
|
private function craft_ffmpeg_custom_cmd( |
162
|
|
|
$tmpPathInput, |
163
|
|
|
$pathToInputFile, |
164
|
|
|
$pathToOutputFiles, |
165
|
|
|
$metadata, |
|
|
|
|
166
|
|
|
$outputWanted) |
167
|
|
|
{ |
168
|
|
|
$ffmpegCmd = $outputWanted->{'custom_cmd'}; |
169
|
|
|
|
170
|
|
|
// Replace ${input_file} by input file path |
171
|
|
|
$pathToInputFile = escapeshellarg($pathToInputFile); |
172
|
|
|
$ffmpegCmd = preg_replace('/\$\{input_file\}/', $pathToInputFile, $ffmpegCmd); |
173
|
|
|
|
174
|
|
|
$watermarkOptions = ""; |
|
|
|
|
175
|
|
|
// Process options for watermark |
176
|
|
|
if (isset($outputWanted->{'watermark'}) && $outputWanted->{'watermark'}) { |
177
|
|
|
$watermarkOptions = |
178
|
|
|
$this->get_watermark_options( |
179
|
|
|
$tmpPathInput, |
180
|
|
|
$outputWanted->{'watermark'}); |
181
|
|
|
// Replace ${watermark_options} by watermark options |
182
|
|
|
$ffmpegCmd = preg_replace('/\$\{watermark_options\}/', $watermarkOptions, $ffmpegCmd); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
// Append output filename to path |
186
|
|
|
$pathToOutputFiles .= "/" . $outputWanted->{'output_file_info'}['basename']; |
187
|
|
|
// Replace ${output_file} by output filename and path to local disk |
188
|
|
|
$ffmpegCmd = preg_replace('/\$\{output_file\}/', $pathToOutputFiles, $ffmpegCmd); |
189
|
|
|
|
190
|
|
|
return ($ffmpegCmd); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
// Generate FFmpeg command for video transcoding |
194
|
|
|
private function craft_ffmpeg_cmd_video( |
195
|
|
|
$tmpPathInput, |
196
|
|
|
$pathToInputFile, |
197
|
|
|
$pathToOutputFiles, |
198
|
|
|
$metadata, |
199
|
|
|
$outputWanted) |
200
|
|
|
{ |
201
|
|
|
// Check if a size is provided to override preset size |
202
|
|
|
$size = $this->set_output_video_size($metadata, $outputWanted); |
203
|
|
|
$pathToInputFile = escapeshellarg($pathToInputFile); |
204
|
|
|
|
205
|
|
|
$videoCodec = $outputWanted->{'preset_values'}->{'video_codec'}; |
206
|
|
|
if (isset($outputWanted->{'video_codec'})) { |
207
|
|
|
$videoCodec = $outputWanted->{'video_codec'}; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
$audioCodec = $outputWanted->{'preset_values'}->{'audio_codec'}; |
211
|
|
|
if (isset($outputWanted->{'audio_codec'})) { |
212
|
|
|
$audioCodec = $outputWanted->{'audio_codec'}; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
$videoBitrate = $outputWanted->{'preset_values'}->{'video_bitrate'}; |
216
|
|
|
if (isset($outputWanted->{'video_bitrate'})) { |
217
|
|
|
$videoBitrate = $outputWanted->{'video_bitrate'}; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
$audioBitrate = $outputWanted->{'preset_values'}->{'audio_bitrate'}; |
221
|
|
|
if (isset($outputWanted->{'audio_bitrate'})) { |
222
|
|
|
$audioBitrate = $outputWanted->{'audio_bitrate'}; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
$frameRate = $outputWanted->{'preset_values'}->{'frame_rate'}; |
226
|
|
|
if (isset($outputWanted->{'frame_rate'})) { |
227
|
|
|
$frameRate = $outputWanted->{'frame_rate'}; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
$formattedOptions = ""; |
231
|
|
|
if (isset($outputWanted->{'preset_values'}->{'video_codec_options'})) { |
232
|
|
|
$formattedOptions = |
233
|
|
|
$this->set_output_video_codec_options($outputWanted->{'preset_values'}->{'video_codec_options'}); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
$watermarkOptions = ""; |
237
|
|
|
// Process options for watermark |
238
|
|
|
if (isset($outputWanted->{'watermark'}) && $outputWanted->{'watermark'}) { |
239
|
|
|
$watermarkOptions = |
240
|
|
|
$this->get_watermark_options( |
241
|
|
|
$tmpPathInput, |
242
|
|
|
$outputWanted->{'watermark'}); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
// Create FFMpeg arguments |
246
|
|
|
$ffmpegArgs = " -i $pathToInputFile -y -threads 0"; |
247
|
|
|
$ffmpegArgs .= " -s $size"; |
248
|
|
|
$ffmpegArgs .= " -vcodec $videoCodec"; |
249
|
|
|
$ffmpegArgs .= " -acodec $audioCodec"; |
250
|
|
|
$ffmpegArgs .= " -b:v $videoBitrate"; |
251
|
|
|
$ffmpegArgs .= " -b:a $audioBitrate"; |
252
|
|
|
$ffmpegArgs .= " -r $frameRate"; |
253
|
|
|
$ffmpegArgs .= " $formattedOptions"; |
254
|
|
|
$ffmpegArgs .= " $watermarkOptions"; |
255
|
|
|
|
256
|
|
|
// Append output filename to path |
257
|
|
|
$pathToOutputFiles .= "/" . $outputWanted->{'output_file_info'}['basename']; |
258
|
|
|
// Final command |
259
|
|
|
$ffmpegCmd = "ffmpeg $ffmpegArgs $pathToOutputFiles"; |
260
|
|
|
|
261
|
|
|
return ($ffmpegCmd); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
// Craft FFMpeg command to generate thumbnails |
265
|
|
|
private function craft_ffmpeg_cmd_thumb( |
266
|
|
|
$tmpPathInput, |
|
|
|
|
267
|
|
|
$pathToInputFile, |
268
|
|
|
$pathToOutputFiles, |
269
|
|
|
$metadata, |
|
|
|
|
270
|
|
|
$outputWanted) |
271
|
|
|
{ |
272
|
|
|
// FIXME: Use $metadata to improve the FFMpeg command |
273
|
|
|
// inputAssetInfo contains FFprobe output |
274
|
|
|
|
275
|
|
|
$frameOptions = ""; |
276
|
|
|
$outputFileInfo = pathinfo($outputWanted->{'file'}); |
277
|
|
|
$pathToInputFile = escapeshellarg($pathToInputFile); |
278
|
|
|
if ($outputWanted->{'mode'} == 'snapshot') |
279
|
|
|
{ |
280
|
|
|
$snapshot_sec = self::SNAPSHOT_SEC_DEFAULT; |
281
|
|
|
if (isset($outputWanted->{'snapshot_sec'}) && |
282
|
|
|
$outputWanted->{'snapshot_sec'} > 0) { |
283
|
|
|
$snapshot_sec = $outputWanted->{'snapshot_sec'}; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
$time = gmdate("H:i:s", $snapshot_sec) . ".000"; |
287
|
|
|
$pathToOutputFiles .= "/" . $outputFileInfo['basename']; |
288
|
|
|
$frameOptions = " -ss $time -vframes 1"; |
289
|
|
|
} |
290
|
|
|
else if ($outputWanted->{'mode'} == 'intervals') |
291
|
|
|
{ |
292
|
|
|
$intervals = self::INTERVALS_DEFAULT; |
293
|
|
|
if (isset($outputWanted->{'intervals'}) && |
294
|
|
|
$outputWanted->{'intervals'} > 0) { |
295
|
|
|
$intervals = $outputWanted->{'intervals'}; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
$pathToOutputFiles .= "/" . $outputFileInfo['filename'] . "%06d." |
299
|
|
|
. $outputFileInfo['extension']; |
300
|
|
|
$frameOptions = " -vf fps=fps=1/$intervals"; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
// Create FFMpeg arguments |
304
|
|
|
$ffmpegArgs = " -i $pathToInputFile -y -threads 0"; |
305
|
|
|
$ffmpegArgs .= " -vf scale=" . $outputWanted->{'size'}; |
306
|
|
|
$ffmpegArgs .= " $frameOptions -f image2 -q:v 8"; |
307
|
|
|
|
308
|
|
|
// Final command |
309
|
|
|
$ffmpegCmd = "ffmpeg $ffmpegArgs $pathToOutputFiles"; |
310
|
|
|
|
311
|
|
|
return ($ffmpegCmd); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
// Get watermark info to generate overlay options for ffmpeg |
315
|
|
|
private function get_watermark_options( |
316
|
|
|
$tmpPathInput, |
317
|
|
|
$watermarkOptions) |
318
|
|
|
{ |
319
|
|
|
// Get info about the video in order to save the watermark in same location |
320
|
|
|
$watermarkFileInfo = pathinfo($watermarkOptions->{'file'}); |
321
|
|
|
$watermarkPath = $tmpPathInput."/".$watermarkFileInfo['basename']; |
322
|
|
|
$newWatermarkPath = $tmpPathInput."/new-".$watermarkFileInfo['basename']; |
323
|
|
|
|
324
|
|
|
// Get watermark image from S3 |
325
|
|
|
$s3Output = $this->s3Utils->get_file_from_s3( |
326
|
|
|
$watermarkOptions->{'bucket'}, |
327
|
|
|
$watermarkOptions->{'file'}, |
328
|
|
|
$watermarkPath); |
329
|
|
|
|
330
|
|
|
$this->cpeLogger->log_out("INFO", |
331
|
|
|
basename(__FILE__), |
332
|
|
|
$s3Output['msg'], |
333
|
|
|
$this->activityLogKey); |
334
|
|
|
|
335
|
|
|
// Transform watermark for opacity |
336
|
|
|
$convertCmd = "convert $watermarkPath -alpha on -channel A -evaluate Multiply " . $watermarkOptions->{'opacity'} . " +channel $newWatermarkPath"; |
337
|
|
|
|
338
|
|
|
try { |
339
|
|
|
$out = $this->executer->execute($convertCmd, 1, |
340
|
|
|
array(1 => array("pipe", "w"), 2 => array("pipe", "w")), |
341
|
|
|
false, false, |
342
|
|
|
false, 1); |
343
|
|
|
} |
344
|
|
|
catch (\Exception $e) { |
345
|
|
|
$this->cpeLogger->log_out( |
346
|
|
|
"ERROR", |
347
|
|
|
basename(__FILE__), |
348
|
|
|
"Execution of command '".$convertCmd."' failed", |
349
|
|
|
$this->activityLogKey |
350
|
|
|
); |
351
|
|
|
return false; |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
// Any error ? |
355
|
|
|
if (isset($out['outErr']) && $out['outErr'] != "" && |
356
|
|
|
(!file_exists($newWatermarkPath) || !filesize($newWatermarkPath))) { |
357
|
|
|
throw new CpeSdk\CpeException( |
358
|
|
|
"Error transforming watermark file '$watermarkPath'!", |
359
|
|
|
self::WATERMARK_ERROR); |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
// Format options for FFMpeg |
363
|
|
|
$size = $watermarkOptions->{'size'}; |
364
|
|
|
$positions = $this->get_watermark_position($watermarkOptions); |
365
|
|
|
$formattedOptions = "-vf \"movie=$newWatermarkPath, scale=$size [wm]; [in][wm] overlay=" . $positions['x'] . ':' . $positions['y'] . " [out]\""; |
366
|
|
|
|
367
|
|
|
return ($formattedOptions); |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
// Generate the command line option to position the watermark |
371
|
|
|
private function get_watermark_position($watermarkOptions) |
372
|
|
|
{ |
373
|
|
|
$positions = array('x' => 0, 'y' => 0); |
374
|
|
|
|
375
|
|
|
if ($watermarkOptions->{'x'} >= 0) { |
376
|
|
|
$positions['x'] = $watermarkOptions->{'x'}; |
377
|
|
|
} |
378
|
|
|
if ($watermarkOptions->{'y'} >= 0) { |
379
|
|
|
$positions['y'] = $watermarkOptions->{'y'}; |
380
|
|
|
} |
381
|
|
|
if ($watermarkOptions->{'x'} < 0) { |
382
|
|
|
$positions['x'] = 'main_w-overlay_w' . $watermarkOptions->{'x'}; |
383
|
|
|
} |
384
|
|
|
if ($watermarkOptions->{'y'} < 0) { |
385
|
|
|
$positions['y'] = 'main_h-overlay_h' . $watermarkOptions->{'y'}; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
return ($positions); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
// Get Video codec options and format the options properly for ffmpeg |
392
|
|
|
private function set_output_video_codec_options($videoCodecOptions) |
393
|
|
|
{ |
394
|
|
|
$formattedOptions = ""; |
395
|
|
|
$options = explode(",", $videoCodecOptions); |
396
|
|
|
|
397
|
|
|
foreach ($options as $option) |
398
|
|
|
{ |
399
|
|
|
$keyVal = explode("=", $option); |
400
|
|
|
if ($keyVal[0] === 'Profile') { |
401
|
|
|
$formattedOptions .= " -profile:v ".$keyVal[1]; |
402
|
|
|
} else if ($keyVal[0] === 'Level') { |
403
|
|
|
$formattedOptions .= " -level ".$keyVal[1]; |
404
|
|
|
} else if ($keyVal[0] === 'MaxReferenceFrames') { |
405
|
|
|
$formattedOptions .= " -refs ".$keyVal[1]; |
406
|
|
|
} |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
return ($formattedOptions); |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
// Verify Ratio and Size of output file to ensure it respect restrictions |
413
|
|
|
// Return the output video size |
414
|
|
|
private function set_output_video_size(&$metadata, $outputWanted) |
415
|
|
|
{ |
416
|
|
|
// Handle video size |
417
|
|
|
$size = $outputWanted->{'preset_values'}->{'size'}; |
418
|
|
|
if (isset($outputWanted->{'size'})) { |
419
|
|
|
$size = $outputWanted->{'size'}; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
// Ratio check |
423
|
|
|
if (!isset($outputWanted->{'keep_ratio'}) || |
|
|
|
|
424
|
|
|
$outputWanted->{'keep_ratio'} == 'true') |
425
|
|
|
{ |
426
|
|
|
// FIXME: Improve ratio check |
427
|
|
|
|
428
|
|
|
/* $outputRatio = floatval($this->get_ratio($size)); */ |
|
|
|
|
429
|
|
|
/* $inputRatio = floatval($metadata->{'ratio'}); */ |
|
|
|
|
430
|
|
|
|
431
|
|
|
/* if ($outputRatio != $inputRatio) */ |
|
|
|
|
432
|
|
|
/* throw new CpeSdk\CpeException( */ |
|
|
|
|
433
|
|
|
/* "Output video ratio is different from input video: input_ratio: '$inputRatio' / output_ratio: '$outputRatio'. 'keep_ratio' option is enabled (default). Disable it to allow ratio change.", */ |
434
|
|
|
/* self::RATIO_ERROR */ |
435
|
|
|
/* ); */ |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
// Enlargement check |
439
|
|
|
// FIXME: Size information in metadata is not the same anymore |
440
|
|
|
// We need to parse the metadata and extract the VIDEO stream |
441
|
|
|
if (!isset($outputWanted->{'allow_upscale'}) || |
442
|
|
|
$outputWanted->{'allow_upscale'} == 'false') |
443
|
|
|
{ |
444
|
|
|
$metadata->{'size'} = $metadata->width . 'x' . $metadata->height; |
445
|
|
|
$inputSize = $metadata->{'size'}; |
446
|
|
|
$inputSizeSplit = explode("x", $inputSize); |
447
|
|
|
$outputSizeSplit = explode("x", $size); |
448
|
|
|
|
449
|
|
|
if (intval($outputSizeSplit[0]) > intval($inputSizeSplit[0]) || |
450
|
|
|
intval($outputSizeSplit[1]) > intval($inputSizeSplit[1])) { |
451
|
|
|
$this->cpeLogger->log_out( |
452
|
|
|
"INFO", |
453
|
|
|
basename(__FILE__), |
454
|
|
|
"Requested transcode size is bigger than the original. `allow_upscale` option not provided", |
455
|
|
|
$this->activityLogKey |
456
|
|
|
); |
457
|
|
|
$size = $metadata->{'size'}; |
458
|
|
|
} |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
return ($size); |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
// REad ffmpeg output and calculate % progress |
465
|
|
|
// This is a callback called from 'CommandExecuter.php' |
466
|
|
|
// $out and $outErr contain FFmpeg output |
467
|
|
|
public function capture_progression($duration, $out, $outErr) |
468
|
|
|
{ |
469
|
|
|
// We also call a callback here ... the 'send_hearbeat' function from the origin activity |
470
|
|
|
// This way we notify SWF that we are alive ! |
471
|
|
|
call_user_func(array($this->activityObj, 'send_heartbeat'), |
472
|
|
|
$this->task); |
473
|
|
|
|
474
|
|
|
// # get the current time |
475
|
|
|
preg_match_all("/time=(.*?) bitrate/", $outErr, $matches); |
476
|
|
|
|
477
|
|
|
$last = array_pop($matches); |
478
|
|
|
// # this is needed if there is more than one match |
479
|
|
|
if (is_array($last)) { |
480
|
|
|
$last = array_pop($last); |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
// Perform Time transformation to get seconds |
484
|
|
|
$ar = array_reverse(explode(":", $last)); |
485
|
|
|
$done = floatval($ar[0]); |
486
|
|
|
if (!empty($ar[1])) { |
487
|
|
|
$done += intval($ar[1]) * 60; |
488
|
|
|
} |
489
|
|
|
if (!empty($ar[2])) { |
490
|
|
|
$done += intval($ar[2]) * 60 * 60; |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
// # finally, progress is easy |
494
|
|
|
$progress = 0; |
495
|
|
|
if ($done) { |
496
|
|
|
$progress = round(($done/$duration)*100); |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
$this->cpeLogger->log_out( |
500
|
|
|
"INFO", |
501
|
|
|
basename(__FILE__), |
502
|
|
|
"Progress: $done / $progress%", |
503
|
|
|
$this->activityLogKey |
504
|
|
|
); |
505
|
|
|
|
506
|
|
|
// Send progress through SQSUtils to notify client of progress |
507
|
|
|
$this->cpeSqsWriter->activity_progress( |
508
|
|
|
$this->task, |
509
|
|
|
[ |
510
|
|
|
"duration" => $duration, |
511
|
|
|
"done" => $done, |
512
|
|
|
"progress" => $progress |
513
|
|
|
] |
514
|
|
|
); |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
// Combine preset and custom output settings to generate output settings |
518
|
|
|
public function get_preset_values($output_wanted) |
519
|
|
|
{ |
520
|
|
|
if (!$output_wanted) { |
521
|
|
|
throw new CpeSdk\CpeException("No output data provided to transcoder !", |
522
|
|
|
self::NO_OUTPUT); |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
if (!isset($output_wanted->{"preset"})) { |
526
|
|
|
throw new CpeSdk\CpeException("No preset selected for output !", |
527
|
|
|
self::BAD_PRESETS_DIR); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
$preset = $output_wanted->{"preset"}; |
531
|
|
|
$presetPath = __DIR__ . '/../../../presets/'; |
532
|
|
|
|
533
|
|
|
if (!($presetContent = file_get_contents($presetPath.$preset.".json"))) { |
534
|
|
|
throw new CpeSdk\CpeException("Can't open preset file !", |
535
|
|
|
self::OPEN_PRESET_FAILED); |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
if (!($decodedPreset = json_decode($presetContent))) { |
539
|
|
|
throw new CpeSdk\CpeException("Bad preset JSON format !", |
540
|
|
|
self::BAD_PRESET_FORMAT); |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
return ($decodedPreset); |
544
|
|
|
} |
545
|
|
|
|
546
|
|
|
// Check if the preset exists |
547
|
|
|
public function validate_preset($output) |
548
|
|
|
{ |
549
|
|
|
if (!isset($output->{"preset"})) { |
550
|
|
|
throw new CpeSdk\CpeException("No preset selected for output !", |
551
|
|
|
self::BAD_PRESETS_DIR); |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
$preset = $output->{"preset"}; |
555
|
|
|
$presetPath = __DIR__ . '/../../../presets/'; |
556
|
|
|
|
557
|
|
|
if (!($files = scandir($presetPath))) { |
558
|
|
|
throw new CpeSdk\CpeException("Unable to open preset directory '$presetPath' !", |
559
|
|
|
self::BAD_PRESETS_DIR); |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
foreach ($files as $presetFile) |
563
|
|
|
{ |
564
|
|
|
if ($presetFile === '.' || $presetFile === '..') { continue; } |
565
|
|
|
|
566
|
|
|
if (is_file("$presetPath/$presetFile")) |
567
|
|
|
{ |
568
|
|
|
if ($preset === pathinfo($presetFile)["filename"]) |
569
|
|
|
{ |
570
|
|
|
if (!($presetContent = file_get_contents("$presetPath/$presetFile"))) { |
571
|
|
|
throw new CpeSdk\CpeException("Can't open preset file '$presetPath/$presetFile'!", |
572
|
|
|
self::OPEN_PRESET_FAILED); |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
if (!($decodedPreset = json_decode($presetContent))) { |
576
|
|
|
throw new CpeSdk\CpeException("Bad preset JSON format '$presetPath/$presetFile'!", |
577
|
|
|
self::BAD_PRESET_FORMAT); |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
# Validate against JSON Schemas |
581
|
|
|
/* if (($err = ($decodedPreset, "presets.json"))) { */ |
|
|
|
|
582
|
|
|
/* throw new CpeSdk\CpeException("JSON preset file '$presetPath/$presetFile' invalid! Details:\n".$err, */ |
|
|
|
|
583
|
|
|
/* self::BAD_PRESET_FORMAT); */ |
|
|
|
|
584
|
|
|
/* } */ |
585
|
|
|
|
586
|
|
|
return true; |
587
|
|
|
} |
588
|
|
|
} |
589
|
|
|
} |
590
|
|
|
|
591
|
|
|
throw new CpeSdk\CpeException("Unkown preset file '$preset' !", |
592
|
|
|
self::UNKNOWN_PRESET); |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
|
596
|
|
|
/************************************** |
597
|
|
|
* GET VIDEO INFORMATION AND VALIDATION |
598
|
|
|
* The methods below are used by the ValidationActivity |
599
|
|
|
* We capture as much info as possible on the input video |
600
|
|
|
*/ |
601
|
|
|
|
602
|
|
|
// Execute FFMpeg to get video information |
603
|
|
|
public function get_asset_info($pathToInputFile) |
604
|
|
|
{ |
605
|
|
|
$pathToInputFile = escapeshellarg($pathToInputFile); |
606
|
|
|
$ffprobeCmd = "ffprobe -v quiet -of json -show_format -show_streams $pathToInputFile"; |
607
|
|
|
try { |
608
|
|
|
// Execute FFMpeg to validate and get information about input video |
609
|
|
|
$out = $this->executer->execute( |
610
|
|
|
$ffprobeCmd, |
611
|
|
|
1, |
612
|
|
|
array( |
613
|
|
|
1 => array("pipe", "w"), |
614
|
|
|
2 => array("pipe", "w") |
615
|
|
|
), |
616
|
|
|
false, false, |
617
|
|
|
false, 1 |
618
|
|
|
); |
619
|
|
|
} |
620
|
|
|
catch (\Exception $e) { |
621
|
|
|
$this->cpeLogger->log_out( |
622
|
|
|
"ERROR", |
623
|
|
|
basename(__FILE__), |
624
|
|
|
"Execution of command '".$ffprobeCmd."' failed.", |
625
|
|
|
$this->activityLogKey |
626
|
|
|
); |
627
|
|
|
return false; |
628
|
|
|
} |
629
|
|
|
|
630
|
|
|
if (empty($out)) { |
631
|
|
|
throw new CpeSdk\CpeException("Unable to execute FFProbe to get information about '$pathToInputFile'!", |
632
|
|
|
self::EXEC_VALIDATE_FAILED); |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
// FFmpeg writes on STDERR ... |
636
|
|
|
if (!($assetInfo = json_decode($out['out']))) { |
637
|
|
|
throw new CpeSdk\CpeException("FFProbe returned invalid JSON!", |
638
|
|
|
self::EXEC_VALIDATE_FAILED); |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
return ($assetInfo); |
642
|
|
|
} |
643
|
|
|
} |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.