1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Soluble\MediaTools\Video\Detection; |
6
|
|
|
|
7
|
|
|
use Soluble\MediaTools\Common\Assert\PathAssertionsTrait; |
8
|
|
|
use Soluble\MediaTools\Common\Exception\FileNotFoundException; |
9
|
|
|
use Soluble\MediaTools\Common\Exception\UnsupportedParamException; |
10
|
|
|
use Soluble\MediaTools\Common\Exception\UnsupportedParamValueException; |
11
|
|
|
use Soluble\MediaTools\Common\IO\PlatformNullFile; |
12
|
|
|
use Soluble\MediaTools\Common\Process\ProcessFactory; |
13
|
|
|
use Soluble\MediaTools\Common\Process\ProcessParamsInterface; |
14
|
|
|
use Soluble\MediaTools\Video\Config\FFMpegConfigInterface; |
15
|
|
|
use Soluble\MediaTools\Video\ConversionParams; |
16
|
|
|
use Soluble\MediaTools\Video\Exception\DetectionExceptionInterface; |
17
|
|
|
use Soluble\MediaTools\Video\Exception\DetectionProcessExceptionInterface; |
18
|
|
|
use Soluble\MediaTools\Video\Exception\InvalidParamException; |
19
|
|
|
use Soluble\MediaTools\Video\Exception\MissingInputFileException; |
20
|
|
|
use Soluble\MediaTools\Video\Exception\ProcessFailedException; |
21
|
|
|
use Soluble\MediaTools\Video\Exception\RuntimeException; |
22
|
|
|
use Soluble\MediaTools\Video\Filter\IdetVideoFilter; |
23
|
|
|
use Symfony\Component\Process\Exception as SPException; |
24
|
|
|
|
25
|
|
|
class InterlaceDetect |
26
|
|
|
{ |
27
|
|
|
use PathAssertionsTrait; |
28
|
|
|
|
29
|
|
|
public const DEFAULT_INTERLACE_MAX_FRAMES = 1000; |
30
|
|
|
|
31
|
|
|
/** @var FFMpegConfigInterface */ |
32
|
|
|
protected $ffmpegConfig; |
33
|
|
|
|
34
|
2 |
|
public function __construct(FFMpegConfigInterface $ffmpegConfig) |
35
|
|
|
{ |
36
|
2 |
|
$this->ffmpegConfig = $ffmpegConfig; |
37
|
2 |
|
} |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @throws DetectionExceptionInterface |
41
|
|
|
* @throws DetectionProcessExceptionInterface |
42
|
|
|
* @throws ProcessFailedException |
43
|
|
|
* @throws MissingInputFileException |
44
|
|
|
* @throws RuntimeException |
45
|
|
|
*/ |
46
|
2 |
|
public function guessInterlacing(string $file, int $maxFramesToAnalyze = self::DEFAULT_INTERLACE_MAX_FRAMES, ?ProcessParamsInterface $processParams = null): InterlaceDetectGuess |
47
|
|
|
{ |
48
|
2 |
|
$adapter = $this->ffmpegConfig->getAdapter(); |
49
|
2 |
|
$params = (new ConversionParams()) |
50
|
2 |
|
->withVideoFilter(new IdetVideoFilter()) // detect interlaced frames :) |
51
|
2 |
|
->withVideoFrames($maxFramesToAnalyze) |
52
|
2 |
|
->withNoAudio() // speed up the thing |
53
|
2 |
|
->withOutputFormat('rawvideo') |
54
|
2 |
|
->withOverwrite(); |
55
|
|
|
|
56
|
|
|
try { |
57
|
2 |
|
$this->ensureFileExists($file); |
58
|
|
|
|
59
|
1 |
|
$arguments = $adapter->getMappedConversionParams($params); |
60
|
1 |
|
$ffmpegCmd = $adapter->getCliCommand($arguments, $file, new PlatformNullFile()); |
61
|
|
|
|
62
|
1 |
|
$pp = $processParams ?? $this->ffmpegConfig->getProcessParams(); |
63
|
|
|
|
64
|
1 |
|
$process = (new ProcessFactory($ffmpegCmd, $pp))->__invoke(); |
65
|
1 |
|
$process->mustRun(); |
66
|
1 |
|
} catch (FileNotFoundException $e) { |
67
|
1 |
|
throw new MissingInputFileException($e->getMessage()); |
68
|
|
|
} catch (UnsupportedParamValueException | UnsupportedParamException $e) { |
69
|
|
|
throw new InvalidParamException($e->getMessage()); |
70
|
|
|
} catch (SPException\ProcessFailedException | SPException\ProcessTimedOutException | SPException\ProcessSignaledException $e) { |
71
|
|
|
throw new ProcessFailedException($e->getProcess(), $e); |
72
|
|
|
} catch (SPException\RuntimeException $e) { |
73
|
|
|
throw new RuntimeException($e->getMessage()); |
74
|
|
|
} |
75
|
|
|
|
76
|
1 |
|
$stdErr = preg_split("/(\r\n|\n|\r)/", $process->getErrorOutput()); |
77
|
|
|
|
78
|
|
|
// Counted frames |
79
|
1 |
|
$interlaced_tff = 0; |
80
|
1 |
|
$interlaced_bff = 0; |
81
|
1 |
|
$progressive = 0; |
82
|
1 |
|
$undetermined = 0; |
83
|
1 |
|
$total_frames = 0; |
84
|
|
|
|
85
|
1 |
|
if ($stdErr !== false) { |
86
|
1 |
|
foreach ($stdErr as $line) { |
87
|
1 |
|
if (mb_substr($line, 0, 12) !== '[Parsed_idet') { |
88
|
1 |
|
continue; |
89
|
|
|
} |
90
|
|
|
|
91
|
1 |
|
$unspaced = preg_replace('/( )+/', '', $line); |
92
|
1 |
|
$matches = []; |
93
|
1 |
|
if (preg_match_all('/TFF:(\d+)BFF:(\d+)Progressive:(\d+)Undetermined:(\d+)/i', $unspaced, $matches) < 1) { |
94
|
1 |
|
continue; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
//$type = strpos(strtolower($unspaced), 'single') ? 'single' : 'multi'; |
|
|
|
|
98
|
1 |
|
$interlaced_tff += (int) $matches[1][0]; |
99
|
1 |
|
$interlaced_bff += (int) $matches[2][0]; |
100
|
1 |
|
$progressive += (int) $matches[3][0]; |
|
|
|
|
101
|
1 |
|
$undetermined += (int) $matches[4][0]; |
|
|
|
|
102
|
1 |
|
$total_frames += ((int) $matches[1][0] + (int) $matches[2][0] + (int) $matches[3][0] + (int) $matches[4][0]); |
|
|
|
|
103
|
|
|
} |
104
|
|
|
} |
105
|
|
|
|
106
|
1 |
|
return new InterlaceDetectGuess($interlaced_tff, $interlaced_bff, $progressive, $undetermined); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
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.