Completed
Push — master ( 99ade0...004bdb )
by Nicolas
04:25 queued 02:00
created

VideoTranscoder::get_asset_info()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 40
rs 8.5806
cc 4
eloc 26
nc 4
nop 1
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 GET_VIDEO_INFO_FAILED = "GET_VIDEO_INFO_FAILED";
18
    const GET_AUDIO_INFO_FAILED = "GET_AUDIO_INFO_FAILED";
19
    const GET_DURATION_FAILED   = "GET_DURATION_FAILED";
20
    const NO_OUTPUT             = "NO_OUTPUT";
21
    const BAD_OUTPUT            = "BAD_OUTPUT";
22
    const NO_PRESET             = "NO_PRESET";
23
    const BAD_PRESETS_DIR       = "BAD_PRESETS_DIR";
24
    const UNKNOWN_PRESET        = "UNKNOWN_PRESET";
25
    const OPEN_PRESET_FAILED    = "OPEN_PRESET_FAILED";
26
    const BAD_PRESET_FORMAT     = "BAD_PRESET_FORMAT";
27
    const RATIO_ERROR           = "RATIO_ERROR";
28
    const ENLARGEMENT_ERROR     = "ENLARGEMENT_ERROR";
29
    const WATERMARK_ERROR       = "WATERMARK_ERROR";
30
    
31
    const SNAPSHOT_SEC_DEFAULT  = 0;
32
    const INTERVALS_DEFAULT     = 10;
33
    
34
    
35
    /***********************
36
     * TRANSCODE INPUT VIDEO
37
     * Below is the code used to transcode videos based on $outputWanted. 
38
     **********************/
39
40
    // $metadata should contain the ffprobe video stream array.
41
42
    // Start FFmpeg for output transcoding
43
    public function transcode_asset(
44
        $tmpPathInput,
45
        $pathToInputFile, 
46
        $pathToOutputFiles,
47
        $metadata = null, 
48
        $outputWanted)
49
    {
50
        /* if (!$metadata) */
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% 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...
51
        /*     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...
52
        /*         "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.", */
53
        /*         self::TRANSCODE_FAIL */
54
        /*     ); */
55
        
56
        if ($metadata) {
57
            // Extract an sanitize metadata
58
            $metadata = $this->_extractFileInfo($metadata);
59
        }
60
        
61
        $this->cpeLogger->log_out(
62
            "INFO", 
63
            basename(__FILE__), 
64
            "Start Transcoding Asset '$pathToInputFile' ...",
65
            $this->activityLogKey
66
        );
67
        
68 View Code Duplication
        if ($metadata)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
69
            $this->cpeLogger->log_out(
70
                "INFO", 
71
                basename(__FILE__), 
72
                "Input Video metadata: " . print_r($metadata, true),
73
                $this->activityLogKey
74
            );
75
        
76
        try {
77
            $ffmpegCmd = "";
78
79
            // Custom command
80
            if (isset($outputWanted->{'custom_cmd'}) &&
81
                $outputWanted->{'custom_cmd'}) {
82
                $ffmpegCmd = $this->craft_ffmpeg_custom_cmd(
83
                    $tmpPathInput,
84
                    $pathToInputFile,
85
                    $pathToOutputFiles,
86
                    $metadata, 
87
                    $outputWanted
88
                );
89
            } else if ($outputWanted->{'type'} == self::VIDEO) {
90
                $ffmpegCmd = $this->craft_ffmpeg_cmd_video(
91
                    $tmpPathInput,
92
                    $pathToInputFile,
93
                    $pathToOutputFiles,
94
                    $metadata, 
95
                    $outputWanted
96
                );
97
            } else if ($outputWanted->{'type'} == self::THUMB) {
98
                $ffmpegCmd = $this->craft_ffmpeg_cmd_thumb(
99
                    $tmpPathInput,
100
                    $pathToInputFile,
101
                    $pathToOutputFiles,
102
                    $metadata, 
103
                    $outputWanted
104
                );
105
            }
106
        
107
            $this->cpeLogger->log_out(
108
                "INFO",
109
                basename(__FILE__),
110
                "FFMPEG CMD:\n$ffmpegCmd\n",
111
                $this->activityLogKey
112
            );
113
            
114
            // Use executer to start FFMpeg command
115
            // 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...
116
            // Pass video 'duration' as parameter
117
            // Sleep 1sec between turns and callback every 10 turns
118
            // Output progression logs (true)
119
            $this->executer->execute(
120
                $ffmpegCmd, 
121
                1, 
122
                array(2 => array("pipe", "w")),
123
                array($this, "capture_progression"), 
124
                $metadata['duration'], 
125
                true, 
126
                10
127
            );
128
129
            // Test if we have an output file !
130 View Code Duplication
            if (!file_exists($pathToOutputFiles) || 
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131
                $this->is_dir_empty($pathToOutputFiles)) {
132
                throw new CpeSdk\CpeException(
133
                    "Output file '$pathToOutputFiles' hasn't been created successfully or is empty !",
134
                    self::TRANSCODE_FAIL
135
                );
136
            }
137
138
            // FFProbe the output file and return its information
139
            $output_info =
140
                $this->get_asset_info($pathToOutputFiles."/".$outputWanted->{'output_file_info'}['basename']);
141
        }
142
        catch (\Exception $e) {
143
            $this->cpeLogger->log_out(
144
                "ERROR", 
145
                basename(__FILE__), 
146
                "Execution of command '".$ffmpegCmd."' failed: " . print_r($metadata, true). ". ".$e->getMessage(),
147
                $this->activityLogKey
148
            );
149
            return false;
150
        }
151
        
152
        // No error. Transcode successful
153
        $this->cpeLogger->log_out(
154
            "INFO", 
155
            basename(__FILE__), 
156
            "Transcoding successfull !",
157
            $this->activityLogKey
158
        );
159
160
        return $output_info;
161
    }
162
163
    // Craft custom command
164
    private function craft_ffmpeg_custom_cmd(
165
        $tmpPathInput,
166
        $pathToInputFile,
167
        $pathToOutputFiles,
168
        $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...
169
        $outputWanted)
170
    {
171
        $ffmpegCmd = $outputWanted->{'custom_cmd'};
172
        
173
        // Replace ${input_file} by input file path
174
        $pathToInputFile = escapeshellarg($pathToInputFile);
175
        $ffmpegCmd = preg_replace('/\$\{input_file\}/', $pathToInputFile, $ffmpegCmd);
176
        
177
        $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...
178
        // Process options for watermark
179
        if (isset($outputWanted->{'watermark'}) && $outputWanted->{'watermark'}) {
180
            $watermarkOptions = 
181
                $this->get_watermark_options(
182
                    $tmpPathInput,
183
                    $outputWanted->{'watermark'});
184
            // Replace ${watermark_options} by watermark options
185
            $ffmpegCmd = preg_replace('/\$\{watermark_options\}/', $watermarkOptions, $ffmpegCmd);
186
        }
187
        
188
        // Append output filename to path
189
        $pathToOutputFiles .= "/" . $outputWanted->{'output_file_info'}['basename'];
190
        // Replace ${output_file} by output filename and path to local disk
191
        $ffmpegCmd = preg_replace('/\$\{output_file\}/', $pathToOutputFiles, $ffmpegCmd);
192
193
        return ($ffmpegCmd);
194
    }
195
    
196
    // Generate FFmpeg command for video transcoding
197
    private function craft_ffmpeg_cmd_video(
198
        $tmpPathInput,
199
        $pathToInputFile,
200
        $pathToOutputFiles,
201
        $metadata, 
202
        $outputWanted)
203
    {
204
        // Check if a size is provided to override preset size
205
        $size = $this->set_output_video_size($metadata, $outputWanted);
206
        $pathToInputFile = escapeshellarg($pathToInputFile);
207
        
208
        $videoCodec = $outputWanted->{'preset_values'}->{'video_codec'};
209
        if (isset($outputWanted->{'video_codec'})) {
210
            $videoCodec = $outputWanted->{'video_codec'};
211
        }
212
        
213
        $audioCodec = $outputWanted->{'preset_values'}->{'audio_codec'};
214
        if (isset($outputWanted->{'audio_codec'})) {
215
            $audioCodec = $outputWanted->{'audio_codec'};
216
        }
217
218
        $videoBitrate = $outputWanted->{'preset_values'}->{'video_bitrate'};
219
        if (isset($outputWanted->{'video_bitrate'})) {
220
            $videoBitrate = $outputWanted->{'video_bitrate'};
221
        }
222
        
223
        $audioBitrate = $outputWanted->{'preset_values'}->{'audio_bitrate'};
224
        if (isset($outputWanted->{'audio_bitrate'})) {
225
            $audioBitrate = $outputWanted->{'audio_bitrate'};
226
        }
227
228
        $frameRate = $outputWanted->{'preset_values'}->{'frame_rate'};
229
        if (isset($outputWanted->{'frame_rate'})) {
230
            $frameRate = $outputWanted->{'frame_rate'};
231
        }
232
233
        $formattedOptions = "";
234
        if (isset($outputWanted->{'preset_values'}->{'video_codec_options'})) {
235
            $formattedOptions = 
236
                $this->set_output_video_codec_options($outputWanted->{'preset_values'}->{'video_codec_options'});
237
        }
238
239
        $watermarkOptions = "";
240
        // Process options for watermark
241
        if (isset($outputWanted->{'watermark'}) && $outputWanted->{'watermark'}) {
242
            $watermarkOptions = 
243
                $this->get_watermark_options(
244
                    $tmpPathInput,
245
                    $outputWanted->{'watermark'});
246
        }
247
        
248
        // Create FFMpeg arguments
249
        $ffmpegArgs =  " -i $pathToInputFile -y -threads 0";
250
        $ffmpegArgs .= " -s $size";
251
        $ffmpegArgs .= " -vcodec $videoCodec";
252
        $ffmpegArgs .= " -acodec $audioCodec";
253
        $ffmpegArgs .= " -b:v $videoBitrate";
254
        $ffmpegArgs .= " -b:a $audioBitrate";
255
        $ffmpegArgs .= " -r $frameRate";
256
        $ffmpegArgs .= " $formattedOptions";
257
        $ffmpegArgs .= " $watermarkOptions";
258
        
259
        // Append output filename to path
260
        $pathToOutputFiles .= "/" . $outputWanted->{'output_file_info'}['basename'];
261
        // Final command
262
        $ffmpegCmd  = "ffmpeg $ffmpegArgs $pathToOutputFiles";
263
            
264
        return ($ffmpegCmd);
265
    }
266
    
267
    // Craft FFMpeg command to generate thumbnails
268
    private function craft_ffmpeg_cmd_thumb(
269
        $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...
270
        $pathToInputFile,
271
        $pathToOutputFiles,
272
        $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...
273
        $outputWanted)
274
    {
275
        // FIXME: Use $metadata to improve the FFMpeg command
276
        // inputAssetInfo contains FFprobe output
277
278
        $frameOptions   = "";
279
        $outputFileInfo = pathinfo($outputWanted->{'file'});
280
        $pathToInputFile = escapeshellarg($pathToInputFile);
281
        if ($outputWanted->{'mode'} == 'snapshot')
282
        {
283
            $snapshot_sec = self::SNAPSHOT_SEC_DEFAULT;
284
            if (isset($outputWanted->{'snapshot_sec'}) &&
285
                $outputWanted->{'snapshot_sec'} > 0) {
286
                $snapshot_sec = $outputWanted->{'snapshot_sec'};
287
            }
288
                
289
            $time = gmdate("H:i:s", $snapshot_sec) . ".000";
290
            $pathToOutputFiles .= "/" . $outputFileInfo['basename'];
291
            $frameOptions = " -ss $time -vframes 1";
292
        }
293
        else if ($outputWanted->{'mode'} == 'intervals')
294
        {
295
            $intervals = self::INTERVALS_DEFAULT;
296
            if (isset($outputWanted->{'intervals'}) &&
297
                $outputWanted->{'intervals'} > 0) {
298
                $intervals = $outputWanted->{'intervals'};
299
            }
300
            
301
            $pathToOutputFiles .= "/" . $outputFileInfo['filename'] . "%06d." 
302
                . $outputFileInfo['extension'];
303
            $frameOptions = " -vf fps=fps=1/$intervals";
304
        }
305
306
        // Create FFMpeg arguments
307
        $ffmpegArgs  =  " -i $pathToInputFile -y -threads 0";
308
        $ffmpegArgs .= " -vf scale=" . $outputWanted->{'size'};
309
        $ffmpegArgs .= " $frameOptions -f image2 -q:v 8";
310
311
        // Final command
312
        $ffmpegCmd   = "ffmpeg $ffmpegArgs $pathToOutputFiles";
313
        
314
        return ($ffmpegCmd);
315
    }
316
317
    // Get watermark info to generate overlay options for ffmpeg
318
    private function get_watermark_options(
319
        $tmpPathInput,
320
        $watermarkOptions)
321
    {
322
        // Get info about the video in order to save the watermark in same location
323
        $watermarkFileInfo = pathinfo($watermarkOptions->{'file'});
324
        $watermarkPath     = $tmpPathInput."/".$watermarkFileInfo['basename'];
325
        $newWatermarkPath  = $tmpPathInput."/new-".$watermarkFileInfo['basename'];
326
        
327
        // Get watermark image from S3
328
        $s3Output = $this->s3Utils->get_file_from_s3(
329
            $watermarkOptions->{'bucket'}, 
330
            $watermarkOptions->{'file'},
331
            $watermarkPath);
332
        
333
        $this->cpeLogger->log_out("INFO",
334
            basename(__FILE__), 
335
            $s3Output['msg'],
336
            $this->activityLogKey);
337
338
        // Transform watermark for opacity
339
        $convertCmd = "convert $watermarkPath -alpha on -channel A -evaluate Multiply " . $watermarkOptions->{'opacity'} . " +channel $newWatermarkPath";
340
341
        try {
342
            $out = $this->executer->execute($convertCmd, 1, 
343
                array(1 => array("pipe", "w"), 2 => array("pipe", "w")),
344
                false, false, 
345
                false, 1);
346
        }
347
        catch (\Exception $e) {
348
            $this->cpeLogger->log_out(
349
                "ERROR", 
350
                basename(__FILE__), 
351
                "Execution of command '".$convertCmd."' failed",
352
                $this->activityLogKey
353
            );
354
            return false;
355
        }
356
        
357
        // Any error ?
358
        if (isset($out['outErr']) && $out['outErr'] != "" &&
359
            (!file_exists($newWatermarkPath) || !filesize($newWatermarkPath))) {
360
            throw new CpeSdk\CpeException(
361
                "Error transforming watermark file '$watermarkPath'!",
362
                self::WATERMARK_ERROR);
363
        }
364
        
365
        // Format options for FFMpeg
366
        $size      = $watermarkOptions->{'size'};
367
        $positions = $this->get_watermark_position($watermarkOptions);
368
        $formattedOptions = "-vf \"movie=$newWatermarkPath, scale=$size [wm]; [in][wm] overlay=" . $positions['x'] . ':' . $positions['y'] . " [out]\"";
369
        
370
        return ($formattedOptions);
371
    }
372
373
    // Generate the command line option to position the watermark
374
    private function get_watermark_position($watermarkOptions)
375
    {
376
        $positions = array('x' => 0, 'y' => 0);
377
        
378
        if ($watermarkOptions->{'x'} >= 0) {
379
            $positions['x'] = $watermarkOptions->{'x'};
380
        }
381
        if ($watermarkOptions->{'y'} >= 0) {
382
            $positions['y'] = $watermarkOptions->{'y'};
383
        }
384
        if ($watermarkOptions->{'x'} < 0) {
385
            $positions['x'] = 'main_w-overlay_w' . $watermarkOptions->{'x'};
386
        }
387
        if ($watermarkOptions->{'y'} < 0) {
388
            $positions['y'] = 'main_h-overlay_h' . $watermarkOptions->{'y'};
389
        }
390
391
        return ($positions);
392
    }
393
394
    // Get Video codec options and format the options properly for ffmpeg
395
    private function set_output_video_codec_options($videoCodecOptions)
396
    {
397
        $formattedOptions = "";
398
        $options = explode(",", $videoCodecOptions);
399
        
400
        foreach ($options as $option)
401
        {
402
            $keyVal = explode("=", $option);
403
            if ($keyVal[0] === 'Profile') {
404
                $formattedOptions .= " -profile:v ".$keyVal[1];
405
            } else if ($keyVal[0] === 'Level') {
406
                $formattedOptions .= " -level ".$keyVal[1];
407
            } else if ($keyVal[0] === 'MaxReferenceFrames') {
408
                $formattedOptions .= " -refs ".$keyVal[1];
409
            }
410
        }
411
412
        return ($formattedOptions);
413
    }
414
415
    // Verify Ratio and Size of output file to ensure it respect restrictions
416
    // Return the output video size
417
    private function set_output_video_size(&$metadata, $outputWanted)
418
    {
419
        // Handle video size
420
        $size = $outputWanted->{'preset_values'}->{'size'};
421
        if (isset($outputWanted->{'size'})) {
422
            $size = $outputWanted->{'size'};
423
        }
424
        
425
        // Ratio check
426
        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...
427
            $outputWanted->{'keep_ratio'} == 'true')
428
        {
429
            // FIXME: Improve ratio check
430
            
431
            /* $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...
432
            /* $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...
433
434
            /* 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...
435
            /*     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...
436
            /*         "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.", */
437
            /*         self::RATIO_ERROR */
438
            /*     ); */
439
        }
440
        
441
        // Enlargement check
442
        if ($metadata &&
443
            (!isset($outputWanted->{'allow_upscale'})
444
                || $outputWanted->{'allow_upscale'} == 'false'))
445
        {
446
            $metadata['size'] = $metadata['video']['resolution'];
447
            $inputSize        = $metadata['size'];
448
            $inputSizeSplit   = explode("x", $inputSize);
449
            $outputSizeSplit  = explode("x", $size);
450
451
            if (intval($outputSizeSplit[0]) > intval($inputSizeSplit[0]) ||
452
                intval($outputSizeSplit[1]) > intval($inputSizeSplit[1])) {
453
                $this->cpeLogger->log_out(
454
                    "INFO", 
455
                    basename(__FILE__), 
456
                    "Requested transcode size is bigger than the original. `allow_upscale` option not provided",
457
                    $this->activityLogKey
458
                );
459
                $size = $metadata['size'];
460
            }
461
        }
462
463
        return ($size);
464
    }
465
    
466
    // REad ffmpeg output and calculate % progress
467
    // This is a callback called from 'CommandExecuter.php'
468
    // $out and $outErr contain FFmpeg output
469
    public function capture_progression($duration, $out, $outErr)
470
    {
471
        // We also call a callback here ... the 'send_hearbeat' function from the origin activity
472
        // This way we notify SWF that we are alive !
473
        call_user_func(array($this->activityObj, 'send_heartbeat'), 
474
            $this->task);
475
        
476
        $progress = 0;
477
478
        // # get the current time
479
        preg_match_all("/time=(.*?) bitrate/", $outErr, $matches); 
480
481
        $last = array_pop($matches);
482
        // # this is needed if there is more than one match
483
        if (is_array($last)) {
484
            $last = array_pop($last);
485
        }
486
487
        // Perform Time transformation to get seconds
488
        $ar   = array_reverse(explode(":", $last));
489
        $done = floatval($ar[0]);
490
        if (!empty($ar[1])) {
491
            $done += intval($ar[1]) * 60;
492
        }
493
        if (!empty($ar[2])) {
494
            $done += intval($ar[2]) * 60 * 60;
495
        }
496
497
        // # finally, progress is easy
498
        if ($done && $duration) {
499
            $progress = round(($done/$duration)*100);
500
        }
501
        
502
        $this->cpeLogger->log_out(
503
            "INFO", 
504
            basename(__FILE__), 
505
            "Progress: $done / $progress%",
506
            $this->activityLogKey
507
        );
508
509
        // Send progress through SQSUtils to notify client of progress
510
        $this->cpeSqsWriter->activity_progress(
511
            $this->task, 
512
            [
513
                "duration" => $duration,
514
                "done"     => $done,
515
                "progress" => $progress
516
            ]
517
        );
518
    }
519
520
    // Combine preset and custom output settings to generate output settings
521
    public function get_preset_values($output_wanted)
522
    {
523
        if (!$output_wanted) {
524
            throw new CpeSdk\CpeException("No output data provided to transcoder !",
525
                self::NO_OUTPUT);
526
        }
527
528
        if (!isset($output_wanted->{"preset"})) {
529
            throw new CpeSdk\CpeException("No preset selected for output !",
530
                self::BAD_PRESETS_DIR);
531
        }
532
        
533
        $preset     = $output_wanted->{"preset"};
534
        $presetPath = __DIR__ . '/../../../presets/';
535
536
        if (!($presetContent = file_get_contents($presetPath.$preset.".json"))) {
537
            throw new CpeSdk\CpeException("Can't open preset file !",
538
                self::OPEN_PRESET_FAILED);
539
        }
540
        
541
        if (!($decodedPreset = json_decode($presetContent))) {
542
            throw new CpeSdk\CpeException("Bad preset JSON format !",
543
                self::BAD_PRESET_FORMAT);
544
        }
545
        
546
        return ($decodedPreset);
547
    }
548
    
549
    // Check if the preset exists
550
    public function validate_preset($output)
551
    {
552
        if (!isset($output->{"preset"})) {
553
            throw new CpeSdk\CpeException("No preset selected for output !",
554
                self::BAD_PRESETS_DIR);
555
        }
556
557
        $preset     = $output->{"preset"};
558
        $presetPath = __DIR__ . '/../../../presets/';
559
        
560
        if (!($files = scandir($presetPath))) {
561
            throw new CpeSdk\CpeException("Unable to open preset directory '$presetPath' !",
562
                self::BAD_PRESETS_DIR);
563
        }
564
        
565
        foreach ($files as $presetFile)
566
        {
567
            if ($presetFile === '.' || $presetFile === '..') { continue; }
568
            
569
            if (is_file("$presetPath/$presetFile"))
570
            {
571
                if ($preset === pathinfo($presetFile)["filename"])
572
                {
573
                    if (!($presetContent = file_get_contents("$presetPath/$presetFile"))) {
574
                        throw new CpeSdk\CpeException("Can't open preset file '$presetPath/$presetFile'!",
575
                            self::OPEN_PRESET_FAILED);
576
                    }
577
                    
578
                    if (!($decodedPreset = json_decode($presetContent))) {
579
                        throw new CpeSdk\CpeException("Bad preset JSON format '$presetPath/$presetFile'!",
580
                            self::BAD_PRESET_FORMAT);
581
                    }
582
583
                    # Validate against JSON Schemas
584
                    /* 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...
585
                    /*     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...
586
                    /*         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...
587
                    /* } */
588
                    
589
                    return true;
590
                }
591
            }
592
        }
593
        
594
        throw new CpeSdk\CpeException("Unkown preset file '$preset' !",
595
            self::UNKNOWN_PRESET);
596
    }
597
598
    // Extract Metadata from ffprobe
599
    private function _extractFileInfo($metadata) {
600
        
601
        $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...
602
        $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...
603
604
        foreach ($metadata->streams as $key => $value) {
605
            if ($value->codec_type === 'video') {
606
                $videoStreams = $value;
607
            }
608
            else if ($value->codec_type === 'audio') {
609
                $audioStreams = $value;
610
            }
611
        }
612
613
        $analyse = [
614
            'duration' => isset($metadata->format->duration) ? (float)$metadata->format->duration : 0,
615
            'video' => empty($videoStreams) ? null : [
616
                'codec' => $videoStreams->codec_name,
617
                'color' => $videoStreams->color_space,
618
                'resolution' => $videoStreams->width . 'x' . $videoStreams->height,
619
                'sar' => $videoStreams->sample_aspect_ratio,
620
                'dar' => $videoStreams->display_aspect_ratio,
621
                'framerate' => $videoStreams->r_frame_rate,
622
                'bitrate' => isset($videoStreams->bit_rate) ? (int)$videoStreams->bit_rate : null
623
            ],
624
            'audio' => empty($audioStreams) ? null : [
625
                'codec' => $audioStreams->codec_name,
626
                'frequency' => $audioStreams->sample_rate,
627
                'channels' => (int)$audioStreams->channels,
628
                'depth' => $audioStreams->bits_per_sample,
629
                'bitrate' => (int)$audioStreams->bit_rate
630
            ]
631
        ];
632
633
        return $analyse;
634
    }
635
}