scratch – Blame information for rev 115
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
115 | office | 1 | <?php |
2 | |||
3 | /* |
||
4 | * This file is part of PHP-FFmpeg. |
||
5 | * |
||
6 | * (c) Alchemy <info@alchemy.fr> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | namespace FFMpeg\Media; |
||
13 | |||
14 | use Alchemy\BinaryDriver\Exception\ExecutionFailureException; |
||
15 | use FFMpeg\Coordinate\TimeCode; |
||
16 | use FFMpeg\Coordinate\Dimension; |
||
17 | use FFMpeg\Filters\Audio\SimpleFilter; |
||
18 | use FFMpeg\Exception\InvalidArgumentException; |
||
19 | use FFMpeg\Exception\RuntimeException; |
||
20 | use FFMpeg\Filters\Video\VideoFilters; |
||
21 | use FFMpeg\Filters\FilterInterface; |
||
22 | use FFMpeg\Format\FormatInterface; |
||
23 | use FFMpeg\Format\ProgressableInterface; |
||
24 | use FFMpeg\Format\AudioInterface; |
||
25 | use FFMpeg\Format\VideoInterface; |
||
26 | use Neutron\TemporaryFilesystem\Manager as FsManager; |
||
27 | |||
28 | class Video extends Audio |
||
29 | { |
||
30 | /** |
||
31 | * {@inheritdoc} |
||
32 | * |
||
33 | * @return VideoFilters |
||
34 | */ |
||
35 | public function filters() |
||
36 | { |
||
37 | return new VideoFilters($this); |
||
38 | } |
||
39 | |||
40 | /** |
||
41 | * {@inheritdoc} |
||
42 | * |
||
43 | * @return Video |
||
44 | */ |
||
45 | public function addFilter(FilterInterface $filter) |
||
46 | { |
||
47 | $this->filters->add($filter); |
||
48 | |||
49 | return $this; |
||
50 | } |
||
51 | |||
52 | /** |
||
53 | * Exports the video in the desired format, applies registered filters. |
||
54 | * |
||
55 | * @param FormatInterface $format |
||
56 | * @param string $outputPathfile |
||
57 | * |
||
58 | * @return Video |
||
59 | * |
||
60 | * @throws RuntimeException |
||
61 | */ |
||
62 | public function save(FormatInterface $format, $outputPathfile) |
||
63 | { |
||
64 | $commands = array('-y', '-i', $this->pathfile); |
||
65 | |||
66 | $filters = clone $this->filters; |
||
67 | $filters->add(new SimpleFilter($format->getExtraParams(), 10)); |
||
68 | |||
69 | if ($this->driver->getConfiguration()->has('ffmpeg.threads')) { |
||
70 | $filters->add(new SimpleFilter(array('-threads', $this->driver->getConfiguration()->get('ffmpeg.threads')))); |
||
71 | } |
||
72 | if ($format instanceof VideoInterface) { |
||
73 | if (null !== $format->getVideoCodec()) { |
||
74 | $filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec()))); |
||
75 | } |
||
76 | } |
||
77 | if ($format instanceof AudioInterface) { |
||
78 | if (null !== $format->getAudioCodec()) { |
||
79 | $filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec()))); |
||
80 | } |
||
81 | } |
||
82 | |||
83 | foreach ($filters as $filter) { |
||
84 | $commands = array_merge($commands, $filter->apply($this, $format)); |
||
85 | } |
||
86 | |||
87 | if ($format instanceof VideoInterface) { |
||
88 | $commands[] = '-b:v'; |
||
89 | $commands[] = $format->getKiloBitrate() . 'k'; |
||
90 | $commands[] = '-refs'; |
||
91 | $commands[] = '6'; |
||
92 | $commands[] = '-coder'; |
||
93 | $commands[] = '1'; |
||
94 | $commands[] = '-sc_threshold'; |
||
95 | $commands[] = '40'; |
||
96 | $commands[] = '-flags'; |
||
97 | $commands[] = '+loop'; |
||
98 | $commands[] = '-me_range'; |
||
99 | $commands[] = '16'; |
||
100 | $commands[] = '-subq'; |
||
101 | $commands[] = '7'; |
||
102 | $commands[] = '-i_qfactor'; |
||
103 | $commands[] = '0.71'; |
||
104 | $commands[] = '-qcomp'; |
||
105 | $commands[] = '0.6'; |
||
106 | $commands[] = '-qdiff'; |
||
107 | $commands[] = '4'; |
||
108 | $commands[] = '-trellis'; |
||
109 | $commands[] = '1'; |
||
110 | } |
||
111 | |||
112 | if ($format instanceof AudioInterface) { |
||
113 | if (null !== $format->getAudioKiloBitrate()) { |
||
114 | $commands[] = '-b:a'; |
||
115 | $commands[] = $format->getAudioKiloBitrate() . 'k'; |
||
116 | } |
||
117 | if (null !== $format->getAudioChannels()) { |
||
118 | $commands[] = '-ac'; |
||
119 | $commands[] = $format->getAudioChannels(); |
||
120 | } |
||
121 | } |
||
122 | |||
123 | // If the user passed some additional parameters |
||
124 | if ($format instanceof VideoInterface) { |
||
125 | if (null !== $format->getAdditionalParameters()) { |
||
126 | foreach ($format->getAdditionalParameters() as $additionalParameter) { |
||
127 | $commands[] = $additionalParameter; |
||
128 | } |
||
129 | } |
||
130 | } |
||
131 | |||
132 | // Merge Filters into one command |
||
133 | $videoFilterVars = $videoFilterProcesses = []; |
||
134 | for($i=0;$i<count($commands);$i++) { |
||
135 | $command = $commands[$i]; |
||
136 | if ( $command == '-vf' ) { |
||
137 | $commandSplits = explode(";", $commands[$i + 1]); |
||
138 | if ( count($commandSplits) == 1 ) { |
||
139 | $commandSplit = $commandSplits[0]; |
||
140 | $command = trim($commandSplit); |
||
141 | if ( preg_match("/^\[in\](.*?)\[out\]$/is", $command, $match) ) { |
||
142 | $videoFilterProcesses[] = $match[1]; |
||
143 | } else { |
||
144 | $videoFilterProcesses[] = $command; |
||
145 | } |
||
146 | } else { |
||
147 | foreach($commandSplits as $commandSplit) { |
||
148 | $command = trim($commandSplit); |
||
149 | if ( preg_match("/^\[[^\]]+\](.*?)\[[^\]]+\]$/is", $command, $match) ) { |
||
150 | $videoFilterProcesses[] = $match[1]; |
||
151 | } else { |
||
152 | $videoFilterVars[] = $command; |
||
153 | } |
||
154 | } |
||
155 | } |
||
156 | unset($commands[$i]); |
||
157 | unset($commands[$i + 1]); |
||
158 | $i++; |
||
159 | } |
||
160 | } |
||
161 | $videoFilterCommands = $videoFilterVars; |
||
162 | $lastInput = 'in'; |
||
163 | foreach($videoFilterProcesses as $i => $process) { |
||
164 | $command = '[' . $lastInput .']'; |
||
165 | $command .= $process; |
||
166 | $lastInput = 'p' . $i; |
||
167 | if ( $i == count($videoFilterProcesses) - 1 ) { |
||
168 | $command .= '[out]'; |
||
169 | } else { |
||
170 | $command .= '[' . $lastInput . ']'; |
||
171 | } |
||
172 | |||
173 | $videoFilterCommands[] = $command; |
||
174 | } |
||
175 | $videoFilterCommand = implode(";", $videoFilterCommands); |
||
176 | |||
177 | if ( $videoFilterCommand ) { |
||
178 | $commands[] = '-vf'; |
||
179 | $commands[] = $videoFilterCommand; |
||
180 | } |
||
181 | |||
182 | $fs = FsManager::create(); |
||
183 | $fsId = uniqid('ffmpeg-passes'); |
||
184 | $passPrefix = $fs->createTemporaryDirectory(0777, 50, $fsId) . '/' . uniqid('pass-'); |
||
185 | $passes = array(); |
||
186 | $totalPasses = $format->getPasses(); |
||
187 | |||
188 | if (1 > $totalPasses) { |
||
189 | throw new InvalidArgumentException('Pass number should be a positive value.'); |
||
190 | } |
||
191 | |||
192 | for ($i = 1; $i <= $totalPasses; $i++) { |
||
193 | $pass = $commands; |
||
194 | |||
195 | if ($totalPasses > 1) { |
||
196 | $pass[] = '-pass'; |
||
197 | $pass[] = $i; |
||
198 | $pass[] = '-passlogfile'; |
||
199 | $pass[] = $passPrefix; |
||
200 | } |
||
201 | |||
202 | $pass[] = $outputPathfile; |
||
203 | |||
204 | $passes[] = $pass; |
||
205 | } |
||
206 | |||
207 | $failure = null; |
||
208 | |||
209 | foreach ($passes as $pass => $passCommands) { |
||
210 | try { |
||
211 | /** add listeners here */ |
||
212 | $listeners = null; |
||
213 | |||
214 | if ($format instanceof ProgressableInterface) { |
||
215 | $listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, $totalPasses); |
||
216 | } |
||
217 | |||
218 | $this->driver->command($passCommands, false, $listeners); |
||
219 | } catch (ExecutionFailureException $e) { |
||
220 | $failure = $e; |
||
221 | break; |
||
222 | } |
||
223 | } |
||
224 | |||
225 | $fs->clean($fsId); |
||
226 | |||
227 | if (null !== $failure) { |
||
228 | throw new RuntimeException('Encoding failed', $failure->getCode(), $failure); |
||
229 | } |
||
230 | |||
231 | return $this; |
||
232 | } |
||
233 | |||
234 | /** |
||
235 | * Gets the frame at timecode. |
||
236 | * |
||
237 | * @param TimeCode $at |
||
238 | * @return Frame |
||
239 | */ |
||
240 | public function frame(TimeCode $at) |
||
241 | { |
||
242 | return new Frame($this, $this->driver, $this->ffprobe, $at); |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * Extracts a gif from a sequence of the video. |
||
247 | * |
||
248 | * @param TimeCode $at |
||
249 | * @param Dimension $dimension |
||
250 | * @param integer $duration |
||
251 | * @return Gif |
||
252 | */ |
||
253 | public function gif(TimeCode $at, Dimension $dimension, $duration = null) |
||
254 | { |
||
255 | return new Gif($this, $this->driver, $this->ffprobe, $at, $dimension, $duration); |
||
256 | } |
||
257 | |||
258 | /** |
||
259 | * Concatenates a list of videos into one unique video. |
||
260 | * |
||
261 | * @param array $sources |
||
262 | * @return Concat |
||
263 | */ |
||
264 | public function concat($sources) |
||
265 | { |
||
266 | return new Concat($sources, $this->driver, $this->ffprobe); |
||
267 | } |
||
268 | } |