Completed
Push — master ( 049107...378b85 )
by Nicolas
03:11
created

VideoTranscoder   D

Complexity

Total Complexity 85

Size/Duplication

Total Lines 667
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4
Metric Value
wmc 85
lcom 1
cbo 4
dl 0
loc 667
rs 4.5931

13 Methods

Rating   Name   Duplication   Size   Complexity  
B capture_progression() 0 49 5
B get_preset_values() 0 27 5
C validate_preset() 0 47 10
D transcode_asset() 0 116 10
B craft_ffmpeg_custom_cmd() 0 31 3
C craft_ffmpeg_cmd_video() 0 69 9
C craft_ffmpeg_cmd_thumb() 0 48 7
B get_watermark_options() 0 54 6
B get_watermark_position() 0 19 5
B set_output_video_codec_options() 0 19 5
C set_output_video_size() 0 47 8
C _extractFileInfo() 0 36 8
B get_asset_info() 0 40 4

How to fix   Complexity   

Complex Class

Complex classes like VideoTranscoder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use VideoTranscoder, and based on these observations, apply Extract Interface, too.

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
        // Extract an sanitize metadata
58
        $metadata = $this->_extractFileInfo($metadata);
59
60
        $ffmpegCmd = "";
61
62
        // Custom command
63
        if (isset($outputWanted->{'custom_cmd'}) &&
64
            $outputWanted->{'custom_cmd'}) {
65
            $ffmpegCmd = $this->craft_ffmpeg_custom_cmd(
66
                $tmpPathInput,
67
                $pathToInputFile,
68
                $pathToOutputFiles,
69
                $metadata, 
70
                $outputWanted
71
            );
72
        } else if ($outputWanted->{'type'} == self::VIDEO) {
73
            $ffmpegCmd = $this->craft_ffmpeg_cmd_video(
74
                $tmpPathInput,
75
                $pathToInputFile,
76
                $pathToOutputFiles,
77
                $metadata, 
78
                $outputWanted
79
            );
80
        } else if ($outputWanted->{'type'} == self::THUMB) {
81
            $ffmpegCmd = $this->craft_ffmpeg_cmd_thumb(
82
                $tmpPathInput,
83
                $pathToInputFile,
84
                $pathToOutputFiles,
85
                $metadata, 
86
                $outputWanted
87
            );
88
        }
89
        
90
        $this->cpeLogger->log_out(
91
            "INFO",
92
            basename(__FILE__),
93
            "FFMPEG CMD:\n$ffmpegCmd\n",
94
            $this->activityLogKey
95
        );
96
        
97
        $this->cpeLogger->log_out(
98
            "INFO", 
99
            basename(__FILE__), 
100
            "Start Transcoding Asset '$pathToInputFile' ...",
101
            $this->activityLogKey
102
        );
103
        
104
        if ($metadata)
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata of type array<string,double|integer|array|array> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
105
            $this->cpeLogger->log_out(
106
                "INFO", 
107
                basename(__FILE__), 
108
                "Input Video metadata: " . print_r($metadata, true),
109
                $this->activityLogKey
110
            );
111
        
112
        try {
113
            // Use executer to start FFMpeg command
114
            // 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...
115
            // Pass video 'duration' as parameter
116
            // Sleep 1sec between turns and callback every 10 turns
117
            // Output progression logs (true)
118
            $this->executer->execute(
119
                $ffmpegCmd, 
120
                1, 
121
                array(2 => array("pipe", "w")),
122
                array($this, "capture_progression"), 
123
                $metadata['duration'], 
124
                true, 
125
                10
126
            );
127
128
            // Test if we have an output file !
129
            if (!file_exists($pathToOutputFiles) || 
130
                $this->is_dir_empty($pathToOutputFiles)) {
131
                throw new CpeSdk\CpeException(
132
                    "Output file '$pathToOutputFiles' hasn't been created successfully or is empty !",
133
                    self::TRANSCODE_FAIL
134
                );
135
            }
136
137
            // FFProbe the output file and return its information
138
            $output_info =
139
                $this->get_asset_info($pathToOutputFiles."/".$outputWanted->{'output_file_info'}['basename']);
140
        }
141
        catch (\Exception $e) {
142
            $this->cpeLogger->log_out(
143
                "ERROR", 
144
                basename(__FILE__), 
145
                "Execution of command '".$ffmpegCmd."' failed: " . print_r($metadata, true). ". ".$e->getMessage(),
146
                $this->activityLogKey
147
            );
148
            return false;
149
        }
150
        
151
        // No error. Transcode successful
152
        $this->cpeLogger->log_out(
153
            "INFO", 
154
            basename(__FILE__), 
155
            "Transcoding successfull !",
156
            $this->activityLogKey
157
        );
158
159
        return $output_info;
160
    }
161
162
    // Craft custom command
163
    private function craft_ffmpeg_custom_cmd(
164
        $tmpPathInput,
165
        $pathToInputFile,
166
        $pathToOutputFiles,
167
        $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...
168
        $outputWanted)
169
    {
170
        $ffmpegCmd = $outputWanted->{'custom_cmd'};
171
        
172
        // Replace ${input_file} by input file path
173
        $pathToInputFile = escapeshellarg($pathToInputFile);
174
        $ffmpegCmd = preg_replace('/\$\{input_file\}/', $pathToInputFile, $ffmpegCmd);
175
        
176
        $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...
177
        // Process options for watermark
178
        if (isset($outputWanted->{'watermark'}) && $outputWanted->{'watermark'}) {
179
            $watermarkOptions = 
180
                $this->get_watermark_options(
181
                    $tmpPathInput,
182
                    $outputWanted->{'watermark'});
183
            // Replace ${watermark_options} by watermark options
184
            $ffmpegCmd = preg_replace('/\$\{watermark_options\}/', $watermarkOptions, $ffmpegCmd);
185
        }
186
        
187
        // Append output filename to path
188
        $pathToOutputFiles .= "/" . $outputWanted->{'output_file_info'}['basename'];
189
        // Replace ${output_file} by output filename and path to local disk
190
        $ffmpegCmd = preg_replace('/\$\{output_file\}/', $pathToOutputFiles, $ffmpegCmd);
191
192
        return ($ffmpegCmd);
193
    }
194
    
195
    // Generate FFmpeg command for video transcoding
196
    private function craft_ffmpeg_cmd_video(
197
        $tmpPathInput,
198
        $pathToInputFile,
199
        $pathToOutputFiles,
200
        $metadata, 
201
        $outputWanted)
202
    {
203
        // Check if a size is provided to override preset size
204
        $size = $this->set_output_video_size($metadata, $outputWanted);
205
        $pathToInputFile = escapeshellarg($pathToInputFile);
206
        
207
        $videoCodec = $outputWanted->{'preset_values'}->{'video_codec'};
208
        if (isset($outputWanted->{'video_codec'})) {
209
            $videoCodec = $outputWanted->{'video_codec'};
210
        }
211
        
212
        $audioCodec = $outputWanted->{'preset_values'}->{'audio_codec'};
213
        if (isset($outputWanted->{'audio_codec'})) {
214
            $audioCodec = $outputWanted->{'audio_codec'};
215
        }
216
217
        $videoBitrate = $outputWanted->{'preset_values'}->{'video_bitrate'};
218
        if (isset($outputWanted->{'video_bitrate'})) {
219
            $videoBitrate = $outputWanted->{'video_bitrate'};
220
        }
221
        
222
        $audioBitrate = $outputWanted->{'preset_values'}->{'audio_bitrate'};
223
        if (isset($outputWanted->{'audio_bitrate'})) {
224
            $audioBitrate = $outputWanted->{'audio_bitrate'};
225
        }
226
227
        $frameRate = $outputWanted->{'preset_values'}->{'frame_rate'};
228
        if (isset($outputWanted->{'frame_rate'})) {
229
            $frameRate = $outputWanted->{'frame_rate'};
230
        }
231
232
        $formattedOptions = "";
233
        if (isset($outputWanted->{'preset_values'}->{'video_codec_options'})) {
234
            $formattedOptions = 
235
                $this->set_output_video_codec_options($outputWanted->{'preset_values'}->{'video_codec_options'});
236
        }
237
238
        $watermarkOptions = "";
239
        // Process options for watermark
240
        if (isset($outputWanted->{'watermark'}) && $outputWanted->{'watermark'}) {
241
            $watermarkOptions = 
242
                $this->get_watermark_options(
243
                    $tmpPathInput,
244
                    $outputWanted->{'watermark'});
245
        }
246
        
247
        // Create FFMpeg arguments
248
        $ffmpegArgs =  " -i $pathToInputFile -y -threads 0";
249
        $ffmpegArgs .= " -s $size";
250
        $ffmpegArgs .= " -vcodec $videoCodec";
251
        $ffmpegArgs .= " -acodec $audioCodec";
252
        $ffmpegArgs .= " -b:v $videoBitrate";
253
        $ffmpegArgs .= " -b:a $audioBitrate";
254
        $ffmpegArgs .= " -r $frameRate";
255
        $ffmpegArgs .= " $formattedOptions";
256
        $ffmpegArgs .= " $watermarkOptions";
257
        
258
        // Append output filename to path
259
        $pathToOutputFiles .= "/" . $outputWanted->{'output_file_info'}['basename'];
260
        // Final command
261
        $ffmpegCmd  = "ffmpeg $ffmpegArgs $pathToOutputFiles";
262
            
263
        return ($ffmpegCmd);
264
    }
265
    
266
    // Craft FFMpeg command to generate thumbnails
267
    private function craft_ffmpeg_cmd_thumb(
268
        $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...
269
        $pathToInputFile,
270
        $pathToOutputFiles,
271
        $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...
272
        $outputWanted)
273
    {
274
        // FIXME: Use $metadata to improve the FFMpeg command
275
        // inputAssetInfo contains FFprobe output
276
277
        $frameOptions   = "";
278
        $outputFileInfo = pathinfo($outputWanted->{'file'});
279
        $pathToInputFile = escapeshellarg($pathToInputFile);
280
        if ($outputWanted->{'mode'} == 'snapshot')
281
        {
282
            $snapshot_sec = self::SNAPSHOT_SEC_DEFAULT;
283
            if (isset($outputWanted->{'snapshot_sec'}) &&
284
                $outputWanted->{'snapshot_sec'} > 0) {
285
                $snapshot_sec = $outputWanted->{'snapshot_sec'};
286
            }
287
                
288
            $time = gmdate("H:i:s", $snapshot_sec) . ".000";
289
            $pathToOutputFiles .= "/" . $outputFileInfo['basename'];
290
            $frameOptions = " -ss $time -vframes 1";
291
        }
292
        else if ($outputWanted->{'mode'} == 'intervals')
293
        {
294
            $intervals = self::INTERVALS_DEFAULT;
295
            if (isset($outputWanted->{'intervals'}) &&
296
                $outputWanted->{'intervals'} > 0) {
297
                $intervals = $outputWanted->{'intervals'};
298
            }
299
            
300
            $pathToOutputFiles .= "/" . $outputFileInfo['filename'] . "%06d." 
301
                . $outputFileInfo['extension'];
302
            $frameOptions = " -vf fps=fps=1/$intervals";
303
        }
304
305
        // Create FFMpeg arguments
306
        $ffmpegArgs  =  " -i $pathToInputFile -y -threads 0";
307
        $ffmpegArgs .= " -vf scale=" . $outputWanted->{'size'};
308
        $ffmpegArgs .= " $frameOptions -f image2 -q:v 8";
309
310
        // Final command
311
        $ffmpegCmd   = "ffmpeg $ffmpegArgs $pathToOutputFiles";
312
        
313
        return ($ffmpegCmd);
314
    }
315
316
    // Get watermark info to generate overlay options for ffmpeg
317
    private function get_watermark_options(
318
        $tmpPathInput,
319
        $watermarkOptions)
320
    {
321
        // Get info about the video in order to save the watermark in same location
322
        $watermarkFileInfo = pathinfo($watermarkOptions->{'file'});
323
        $watermarkPath     = $tmpPathInput."/".$watermarkFileInfo['basename'];
324
        $newWatermarkPath  = $tmpPathInput."/new-".$watermarkFileInfo['basename'];
325
        
326
        // Get watermark image from S3
327
        $s3Output = $this->s3Utils->get_file_from_s3(
328
            $watermarkOptions->{'bucket'}, 
329
            $watermarkOptions->{'file'},
330
            $watermarkPath);
331
        
332
        $this->cpeLogger->log_out("INFO",
333
            basename(__FILE__), 
334
            $s3Output['msg'],
335
            $this->activityLogKey);
336
337
        // Transform watermark for opacity
338
        $convertCmd = "convert $watermarkPath -alpha on -channel A -evaluate Multiply " . $watermarkOptions->{'opacity'} . " +channel $newWatermarkPath";
339
340
        try {
341
            $out = $this->executer->execute($convertCmd, 1, 
342
                array(1 => array("pipe", "w"), 2 => array("pipe", "w")),
343
                false, false, 
344
                false, 1);
345
        }
346
        catch (\Exception $e) {
347
            $this->cpeLogger->log_out(
348
                "ERROR", 
349
                basename(__FILE__), 
350
                "Execution of command '".$convertCmd."' failed",
351
                $this->activityLogKey
352
            );
353
            return false;
354
        }
355
        
356
        // Any error ?
357
        if (isset($out['outErr']) && $out['outErr'] != "" &&
358
            (!file_exists($newWatermarkPath) || !filesize($newWatermarkPath))) {
359
            throw new CpeSdk\CpeException(
360
                "Error transforming watermark file '$watermarkPath'!",
361
                self::WATERMARK_ERROR);
362
        }
363
        
364
        // Format options for FFMpeg
365
        $size      = $watermarkOptions->{'size'};
366
        $positions = $this->get_watermark_position($watermarkOptions);
367
        $formattedOptions = "-vf \"movie=$newWatermarkPath, scale=$size [wm]; [in][wm] overlay=" . $positions['x'] . ':' . $positions['y'] . " [out]\"";
368
        
369
        return ($formattedOptions);
370
    }
371
372
    // Generate the command line option to position the watermark
373
    private function get_watermark_position($watermarkOptions)
374
    {
375
        $positions = array('x' => 0, 'y' => 0);
376
        
377
        if ($watermarkOptions->{'x'} >= 0) {
378
            $positions['x'] = $watermarkOptions->{'x'};
379
        }
380
        if ($watermarkOptions->{'y'} >= 0) {
381
            $positions['y'] = $watermarkOptions->{'y'};
382
        }
383
        if ($watermarkOptions->{'x'} < 0) {
384
            $positions['x'] = 'main_w-overlay_w' . $watermarkOptions->{'x'};
385
        }
386
        if ($watermarkOptions->{'y'} < 0) {
387
            $positions['y'] = 'main_h-overlay_h' . $watermarkOptions->{'y'};
388
        }
389
390
        return ($positions);
391
    }
392
393
    // Get Video codec options and format the options properly for ffmpeg
394
    private function set_output_video_codec_options($videoCodecOptions)
395
    {
396
        $formattedOptions = "";
397
        $options = explode(",", $videoCodecOptions);
398
        
399
        foreach ($options as $option)
400
        {
401
            $keyVal = explode("=", $option);
402
            if ($keyVal[0] === 'Profile') {
403
                $formattedOptions .= " -profile:v ".$keyVal[1];
404
            } else if ($keyVal[0] === 'Level') {
405
                $formattedOptions .= " -level ".$keyVal[1];
406
            } else if ($keyVal[0] === 'MaxReferenceFrames') {
407
                $formattedOptions .= " -refs ".$keyVal[1];
408
            }
409
        }
410
411
        return ($formattedOptions);
412
    }
413
414
    // Verify Ratio and Size of output file to ensure it respect restrictions
415
    // Return the output video size
416
    private function set_output_video_size(&$metadata, $outputWanted)
417
    {
418
        // Handle video size
419
        $size = $outputWanted->{'preset_values'}->{'size'};
420
        if (isset($outputWanted->{'size'})) {
421
            $size = $outputWanted->{'size'};
422
        }
423
        
424
        // Ratio check
425
        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...
426
            $outputWanted->{'keep_ratio'} == 'true')
427
        {
428
            // FIXME: Improve ratio check
429
            
430
            /* $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...
431
            /* $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...
432
433
            /* 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...
434
            /*     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...
435
            /*         "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.", */
436
            /*         self::RATIO_ERROR */
437
            /*     ); */
438
        }
439
        
440
        // Enlargement check
441
        if (!isset($outputWanted->{'allow_upscale'}) ||
442
            $outputWanted->{'allow_upscale'} == 'false')
443
        {
444
            $metadata['size'] = $metadata['video']['resolution'];
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
    // Extract Metadata from ffprobe
596
    private function _extractFileInfo($metadata) {
597
598
        $videoStreams;
0 ignored issues
show
Bug introduced by
The variable $videoStreams seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
599
        $audioStreams;
0 ignored issues
show
Bug introduced by
The variable $audioStreams seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
600
601
        foreach ($metadata->streams as $key => $value) {
602
            if ($value->codec_type === 'video') {
603
                $videoStreams = $value;
604
            }
605
            else if ($value->codec_type === 'audio') {
606
                $audioStreams = $value;
607
            }
608
        }
609
610
        $analyse = [
611
            'duration' => isset($metadata->format->duration) ? (float)$metadata->format->duration : 0,
612
            'video' => empty($videoStreams) ? null : [
613
                'codec' => $videoStreams->codec_name,
614
                'color' => $videoStreams->color_space,
615
                'resolution' => $videoStreams->width . 'x' . $videoStreams->height,
616
                'sar' => $videoStreams->sample_aspect_ratio,
617
                'dar' => $videoStreams->display_aspect_ratio,
618
                'framerate' => $videoStreams->r_frame_rate,
619
                'bitrate' => isset($videoStreams->bit_rate) ? (int)$videoStreams->bit_rate : null
620
            ],
621
            'audio' => empty($audioStreams) ? null : [
622
                'codec' => $audioStreams->codec_name,
623
                'frequency' => $audioStreams->sample_rate,
624
                'channels' => (int)$audioStreams->channels,
625
                'depth' => $audioStreams->bits_per_sample,
626
                'bitrate' => (int)$audioStreams->bit_rate
627
            ]
628
        ];
629
630
        return $analyse;
631
    }
632
    
633
    /**************************************
634
     * GET VIDEO INFORMATION AND VALIDATION
635
     * The methods below are used by the ValidationActivity
636
     * We capture as much info as possible on the input video
637
     */
638
639
    // Execute FFMpeg to get video information
640
    public function get_asset_info($pathToInputFile)
641
    {
642
        $pathToInputFile = escapeshellarg($pathToInputFile);
643
        $ffprobeCmd = "ffprobe -v quiet -of json -show_format -show_streams $pathToInputFile";
644
        try {
645
            // Execute FFMpeg to validate and get information about input video
646
            $out = $this->executer->execute(
647
                $ffprobeCmd,
648
                1, 
649
                array(
650
                    1 => array("pipe", "w"),
651
                    2 => array("pipe", "w")
652
                ),
653
                false, false, 
654
                false, 1
655
            );
656
        }
657
        catch (\Exception $e) {
658
            $this->cpeLogger->log_out(
659
                "ERROR", 
660
                basename(__FILE__), 
661
                "Execution of command '".$ffprobeCmd."' failed.",
662
                $this->activityLogKey
663
            );
664
            return false;
665
        }
666
        
667
        if (empty($out)) {
668
            throw new CpeSdk\CpeException("Unable to execute FFProbe to get information about '$pathToInputFile'!",
669
                self::EXEC_VALIDATE_FAILED);
670
        }
671
        
672
        // FFmpeg writes on STDERR ...
673
        if (!($assetInfo = json_decode($out['out']))) {
674
            throw new CpeSdk\CpeException("FFProbe returned invalid JSON!",
675
                self::EXEC_VALIDATE_FAILED);
676
        }
677
        
678
        return ($assetInfo);
679
    }
680
}