1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Soluble\MediaTools; |
6
|
|
|
|
7
|
|
|
use Soluble\MediaTools\Config\FFMpegConfigInterface; |
8
|
|
|
use Soluble\MediaTools\Exception\FileNotFoundException; |
9
|
|
|
use Soluble\MediaTools\Exception\ProcessConversionException; |
10
|
|
|
use Soluble\MediaTools\Util\Assert\PathAssertionsTrait; |
11
|
|
|
use Soluble\MediaTools\Video\ConversionParamsInterface; |
12
|
|
|
use Soluble\MediaTools\Video\ConversionServiceInterface; |
13
|
|
|
use Soluble\MediaTools\Video\Converter\FFMpegAdapter; |
14
|
|
|
use Symfony\Component\Process\Exception as SPException; |
15
|
|
|
use Symfony\Component\Process\Process; |
16
|
|
|
|
17
|
|
|
class VideoConversionService implements ConversionServiceInterface |
18
|
|
|
{ |
19
|
|
|
use PathAssertionsTrait; |
20
|
|
|
|
21
|
|
|
/** @var FFMpegConfigInterface */ |
22
|
|
|
protected $ffmpegConfig; |
23
|
|
|
|
24
|
|
|
/** @var FFMpegAdapter */ |
25
|
|
|
protected $converter; |
26
|
|
|
|
27
|
7 |
|
public function __construct(FFMpegConfigInterface $ffmpegConfig) |
28
|
|
|
{ |
29
|
7 |
|
$this->ffmpegConfig = $ffmpegConfig; |
30
|
7 |
|
$this->converter = new FFMpegAdapter($ffmpegConfig); |
31
|
7 |
|
} |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Return ready-to-run symfony process object that you can use |
35
|
|
|
* to `run()` or `start()` programmatically. Useful if you want to make |
36
|
|
|
* things async... |
37
|
|
|
* |
38
|
|
|
* @see https://symfony.com/doc/current/components/process.html |
39
|
|
|
* |
40
|
|
|
* @throws FileNotFoundException when inputFile does not exists |
41
|
|
|
*/ |
42
|
7 |
|
public function getSymfonyProcess(string $inputFile, string $outputFile, VideoConversionParams $convertParams): Process |
43
|
|
|
{ |
44
|
7 |
|
$this->ensureFileExists($inputFile); |
45
|
|
|
|
46
|
6 |
|
if (!$convertParams->hasParam(ConversionParamsInterface::PARAM_THREADS) && $this->ffmpegConfig->getThreads() !== null) { |
47
|
1 |
|
$convertParams = $convertParams->withThreads($this->ffmpegConfig->getThreads()); |
48
|
|
|
} |
49
|
|
|
|
50
|
6 |
|
$arguments = $this->converter->getMappedConversionParams($convertParams); |
51
|
6 |
|
$ffmpegCmd = $this->converter->getCliCommand($arguments, $inputFile, $outputFile); |
52
|
|
|
|
53
|
6 |
|
$process = new Process($ffmpegCmd); |
54
|
6 |
|
$process->setTimeout($this->ffmpegConfig->getTimeout()); |
55
|
6 |
|
$process->setIdleTimeout($this->ffmpegConfig->getIdleTimeout()); |
56
|
6 |
|
$process->setEnv($this->ffmpegConfig->getEnv()); |
57
|
|
|
|
58
|
6 |
|
return $process; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Run a conversion, throw exception on error. |
63
|
|
|
* |
64
|
|
|
* @param callable|null $callback A PHP callback to run whenever there is some |
65
|
|
|
* tmp available on STDOUT or STDERR |
66
|
|
|
* |
67
|
|
|
* @throws FileNotFoundException When inputFile does not exists |
68
|
|
|
* @throws ProcessConversionException When the ffmpeg process conversion failed |
69
|
|
|
*/ |
70
|
5 |
|
public function convert(string $inputFile, string $outputFile, VideoConversionParams $convertParams, ?callable $callback = null): void |
71
|
|
|
{ |
72
|
5 |
|
$process = $this->getSymfonyProcess($inputFile, $outputFile, $convertParams); |
73
|
|
|
|
74
|
|
|
try { |
75
|
4 |
|
$process->mustRun($callback); |
76
|
2 |
|
} catch (SPException\RuntimeException $symfonyProcessException) { |
77
|
|
|
// will include: ProcessFailedException|ProcessTimedOutException|ProcessSignaledException |
|
|
|
|
78
|
2 |
|
throw new ProcessConversionException($process, $symfonyProcessException); |
79
|
|
|
} |
80
|
2 |
|
} |
81
|
|
|
|
82
|
|
|
/* |
|
|
|
|
83
|
|
|
* FOR LATER REFERENCE !!! |
84
|
|
|
public function convertMultiPass(string $videoFile, string $outputFile, VideoConversionParams $convertParams, VideoFilterInterface $videoFilter=null): void { |
85
|
|
|
|
86
|
|
|
$this->ensureFileExists($videoFile); |
87
|
|
|
if ($videoFilter === null) { |
88
|
|
|
$videoFilter = new EmptyVideoFilter(); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
|
92
|
|
|
$threads = $convertParams->getOption(VideoConversionParams::OPTION_THREADS, $this->ffmpegConfig->getThreads()); |
93
|
|
|
|
94
|
|
|
$ffmpegBin = $this->ffmpegConfig->getBinary(); |
95
|
|
|
|
96
|
|
|
$commonArgs = array_merge([ |
97
|
|
|
$ffmpegBin, |
98
|
|
|
sprintf('-i %s', escapeshellarg($videoFile)), // input filename |
99
|
|
|
$videoFilter->getFFMpegCliArgument(), // add -vf yadif,nlmeans |
100
|
|
|
($threads === null) ? '' : sprintf('-threads %s', $threads), |
101
|
|
|
], $convertParams->getFFMpegArguments()); |
102
|
|
|
|
103
|
|
|
$pass1Cmd = implode(' ', array_merge( |
104
|
|
|
$commonArgs, |
105
|
|
|
[ |
106
|
|
|
'-pass 1', |
107
|
|
|
// tells VP9 to encode really fast, sacrificing quality. Useful to speed up the first pass. |
108
|
|
|
'-speed 4', |
109
|
|
|
'-y /dev/null', |
110
|
|
|
] |
111
|
|
|
)); |
112
|
|
|
|
113
|
|
|
$pass2Cmd = implode( ' ', array_merge( |
114
|
|
|
$commonArgs, |
115
|
|
|
[ |
116
|
|
|
'-pass 2', |
117
|
|
|
// speed 1 is a good speed vs. quality compromise. |
118
|
|
|
// Produces tmp quality typically very close to speed 0, but usually encodes much faster. |
119
|
|
|
'-speed 1', |
120
|
|
|
'-y', |
121
|
|
|
sprintf("%s", escapeshellarg($outputFile)) |
122
|
|
|
] |
123
|
|
|
)); |
124
|
|
|
|
125
|
|
|
|
126
|
|
|
$process = new Process($pass1Cmd); |
127
|
|
|
$process->setTimeout(null); |
128
|
|
|
$process->setIdleTimeout(60); // 60 seconds without tmp will stop the process |
129
|
|
|
$process->start(); |
130
|
|
|
foreach ($process as $type => $data) { |
131
|
|
|
if ($process::OUT === $type) { |
132
|
|
|
echo "\nRead from stdout: ".$data; |
133
|
|
|
} else { // $process::ERR === $type |
134
|
|
|
echo "\nRead from stderr: ".$data; |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
$process = new Process($pass2Cmd); |
139
|
|
|
$process->setTimeout(null); |
140
|
|
|
$process->setIdleTimeout(60); // 60 seconds without tmp will stop the process |
141
|
|
|
$process->start(); |
142
|
|
|
foreach ($process as $type => $data) { |
143
|
|
|
if ($process::OUT === $type) { |
144
|
|
|
echo "\nRead from stdout: ".$data; |
145
|
|
|
} else { // $process::ERR === $type |
146
|
|
|
echo "\nRead from stderr: ".$data; |
147
|
|
|
} |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
} |
151
|
|
|
*/ |
152
|
|
|
} |
153
|
|
|
|
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.