Completed
Push — master ( 9ec55e...129c43 )
by Nicolas
06:23
created

VideoTranscoder::set_output_video_size()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 49
Code Lines 21

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 49
rs 6.1404
cc 8
eloc 21
nc 12
nop 2
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
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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, 
0 ignored issues
show
Unused Code introduced by
The parameter $metadata is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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 = "";
0 ignored issues
show
Unused Code introduced by
$watermarkOptions is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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,
0 ignored issues
show
Unused Code introduced by
The parameter $tmpPathInput is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
267
        $pathToInputFile,
268
        $pathToOutputFiles,
269
        $metadata, 
0 ignored issues
show
Unused Code introduced by
The parameter $metadata is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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'}) || 
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
424
            $outputWanted->{'keep_ratio'} == 'true')
425
        {
426
            // FIXME: Improve ratio check
427
            
428
            /* $outputRatio = floatval($this->get_ratio($size)); */
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
429
            /* $inputRatio  = floatval($metadata->{'ratio'}); */
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
430
431
            /* if ($outputRatio != $inputRatio) */
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
432
            /*     throw new CpeSdk\CpeException( */
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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"))) { */
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
582
                    /*     throw new CpeSdk\CpeException("JSON preset file '$presetPath/$presetFile' invalid! Details:\n".$err, */
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
583
                    /*         self::BAD_PRESET_FORMAT); */
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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
}