VideoTranscoder::craft_ffmpeg_cmd_thumb()   C
last analyzed

Complexity

Conditions 7
Paths 5

Size

Total Lines 48
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

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