1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file is part of the PHP-FFmpeg-video-streaming package. |
5
|
|
|
* |
6
|
|
|
* (c) Amin Yazdanpanah <[email protected]> |
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 Streaming; |
13
|
|
|
|
14
|
|
|
use FFMpeg\Exception\ExceptionInterface; |
15
|
|
|
use Streaming\Clouds\AWS; |
16
|
|
|
use Streaming\Clouds\Cloud; |
17
|
|
|
use Streaming\Clouds\CloudInterface; |
18
|
|
|
use Streaming\Clouds\GoogleCloudStorage; |
19
|
|
|
use Streaming\Clouds\MicrosoftAzure; |
20
|
|
|
use Streaming\Exception\Exception; |
21
|
|
|
use Streaming\Exception\InvalidArgumentException; |
22
|
|
|
use Streaming\Exception\RuntimeException; |
23
|
|
|
use Streaming\Filters\Filter; |
24
|
|
|
use Streaming\Traits\Formats; |
25
|
|
|
|
26
|
|
|
abstract class Export |
27
|
|
|
{ |
28
|
|
|
use Formats; |
29
|
|
|
|
30
|
|
|
/** @var object */ |
31
|
|
|
protected $media; |
32
|
|
|
|
33
|
|
|
/** @var array */ |
34
|
|
|
protected $path_info; |
35
|
|
|
|
36
|
|
|
/** @var string */ |
37
|
|
|
protected $strict = "-2"; |
38
|
|
|
|
39
|
|
|
/** @var string */ |
40
|
|
|
protected $tmp_dir; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Export constructor. |
44
|
|
|
* @param Media $media |
45
|
|
|
*/ |
46
|
|
|
public function __construct(Media $media) |
47
|
|
|
{ |
48
|
|
|
$this->media = $media; |
49
|
|
|
$this->path_info = pathinfo($media->getPath()); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @param string $path |
54
|
|
|
* @param array $clouds |
55
|
|
|
* @param bool $metadata |
56
|
|
|
* @return mixed |
57
|
|
|
* @throws Exception |
58
|
|
|
*/ |
59
|
|
|
public function save(string $path = null, array $clouds = [], bool $metadata = true) |
60
|
|
|
{ |
61
|
|
|
/** |
62
|
|
|
* Synopsis |
63
|
|
|
* ------------------------------------------------------------------------------ |
64
|
|
|
* 1. Create directory path, path info array, and temporary folders(if it is required). |
65
|
|
|
* 2. Build object and run FFmpeg to package media content and save on the local machine. |
66
|
|
|
* 3. If the cloud is specified, entire packaged files will be uploaded to clouds. |
67
|
|
|
* 4. If files were saved into a tmp folder, then they will be moved to the local path(if the path is specified). |
68
|
|
|
* 5. Return all video and also streams' metadata and save as a json file on the local machine(it won't save metadata to clouds because of some security concerns). |
69
|
|
|
* 6. In the end, clear all tmp files. |
70
|
|
|
* ------------------------------------------------------------------------------ |
71
|
|
|
*/ |
72
|
|
|
|
73
|
|
|
$this->createPathInfoAndTmpDir($path, $clouds); |
74
|
|
|
$this->runFFmpeg(); |
75
|
|
|
$this->saveToClouds($clouds); |
76
|
|
|
$this->moveTmpFolder($path); |
77
|
|
|
|
78
|
|
|
return $metadata ? (new Metadata($this))->extract() : $this; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @param $path |
83
|
|
|
* @param $clouds |
84
|
|
|
* @throws Exception |
85
|
|
|
*/ |
86
|
|
|
private function createPathInfoAndTmpDir($path, $clouds): void |
87
|
|
|
{ |
88
|
|
|
if (null !== $path) { |
89
|
|
|
$this->path_info = pathinfo($path); |
90
|
|
|
FileManager::makeDir($this->path_info["dirname"]); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
if ($clouds) { |
94
|
|
|
$this->tmpDirectory($path); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
if (null === $path && $this->media->isTmp() && !$clouds) { |
98
|
|
|
throw new InvalidArgumentException("You need to specify a path. It is not possible to save to a tmp directory"); |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @param $path |
104
|
|
|
* @throws Exception |
105
|
|
|
*/ |
106
|
|
|
private function tmpDirectory($path) |
107
|
|
|
{ |
108
|
|
|
if (null !== $path) { |
109
|
|
|
$basename = pathinfo($path, PATHINFO_BASENAME); |
110
|
|
|
} else { |
111
|
|
|
$basename = Helper::randomString(); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$this->tmp_dir = FileManager::tmpDir(); |
115
|
|
|
$this->path_info = pathinfo($this->tmp_dir . $basename); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Run FFmpeg to package media content |
120
|
|
|
*/ |
121
|
|
|
private function runFFmpeg(): void |
122
|
|
|
{ |
123
|
|
|
try { |
124
|
|
|
$this->media |
125
|
|
|
->addFilter($this->getFilter()) |
126
|
|
|
->save($this->getFormat(), $this->getPath()); |
127
|
|
|
} catch (ExceptionInterface $e) { |
128
|
|
|
throw new RuntimeException(sprintf("There was an error saving files: \n\n reason: \n %s", $e->getMessage()), |
129
|
|
|
$e->getCode(), |
130
|
|
|
$e |
131
|
|
|
); |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* @return Filter |
137
|
|
|
*/ |
138
|
|
|
abstract protected function getFilter(): Filter; |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* @return string |
142
|
|
|
*/ |
143
|
|
|
private function getPath(): string |
144
|
|
|
{ |
145
|
|
|
$dirname = str_replace("\\", "/", $this->path_info["dirname"]); |
146
|
|
|
$filename = substr($this->path_info["filename"], -100); |
147
|
|
|
$path = ''; |
148
|
|
|
|
149
|
|
|
if ($this instanceof DASH) { |
150
|
|
|
$path = $dirname . "/" . $filename . ".mpd"; |
151
|
|
|
} elseif ($this instanceof HLS) { |
152
|
|
|
$representations = $this->getRepresentations(); |
153
|
|
|
$path = $dirname . "/" . $filename . "_" . end($representations)->getHeight() . "p.m3u8"; |
154
|
|
|
ExportHLSPlaylist::savePlayList($dirname . DIRECTORY_SEPARATOR . $filename . ".m3u8", $this->getRepresentations(), $filename); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
return $path; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @param array $clouds |
162
|
|
|
*/ |
163
|
|
|
private function saveToClouds(array $clouds): void |
164
|
|
|
{ |
165
|
|
|
if ($clouds) { |
|
|
|
|
166
|
|
|
|
167
|
|
|
if (!is_array(current($clouds))) { |
168
|
|
|
$clouds = [$clouds]; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
sleep(1); |
172
|
|
|
|
173
|
|
|
foreach ($clouds as $cloud) { |
174
|
|
|
if (is_array($cloud) && $cloud['cloud'] instanceof CloudInterface) { |
175
|
|
|
$cloud_obj = $cloud['cloud']; |
176
|
|
|
$options = (isset($cloud['options']) && is_array($cloud['options'])) ? $cloud['options'] : []; |
177
|
|
|
|
178
|
|
|
$cloud_obj->uploadDirectory($this->tmp_dir, $options); |
179
|
|
|
} else { |
180
|
|
|
throw new InvalidArgumentException('You must pass an array of clouds to the save method. |
181
|
|
|
and the cloud must be instance of CloudInterface'); |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @param string|null $path |
189
|
|
|
* @throws Exception |
190
|
|
|
*/ |
191
|
|
|
private function moveTmpFolder(?string $path) |
192
|
|
|
{ |
193
|
|
|
if ($this->tmp_dir && $path) { |
194
|
|
|
FileManager::moveDir($this->tmp_dir, pathinfo($path, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR); |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* @return array |
200
|
|
|
*/ |
201
|
|
|
public function getPathInfo(): array |
202
|
|
|
{ |
203
|
|
|
return $this->path_info; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* @return object|Media |
208
|
|
|
*/ |
209
|
|
|
public function getMedia(): Media |
210
|
|
|
{ |
211
|
|
|
return $this->media; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* @param string $strict |
216
|
|
|
* @return Export |
217
|
|
|
*/ |
218
|
|
|
public function setStrict(string $strict): Export |
219
|
|
|
{ |
220
|
|
|
$this->strict = $strict; |
221
|
|
|
return $this; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* @return string |
226
|
|
|
*/ |
227
|
|
|
public function getStrict(): string |
228
|
|
|
{ |
229
|
|
|
return $this->strict; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* clear tmp files |
234
|
|
|
*/ |
235
|
|
|
public function __destruct() |
236
|
|
|
{ |
237
|
|
|
sleep(1); |
238
|
|
|
|
239
|
|
|
if ($this->media->isTmp()) { |
240
|
|
|
@unlink($this->media->getPath()); |
|
|
|
|
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
if ($this->tmp_dir) { |
244
|
|
|
FileManager::deleteDirectory($this->tmp_dir); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
if ($this instanceof HLS && $this->tmp_key_info_file) { |
248
|
|
|
@unlink($this->getHlsKeyInfoFile()); |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* @param string $url |
254
|
|
|
* @param string $name |
255
|
|
|
* @param string|null $path |
256
|
|
|
* @param string $method |
257
|
|
|
* @param array $headers |
258
|
|
|
* @param array $options |
259
|
|
|
* @return mixed |
260
|
|
|
* @throws Exception |
261
|
|
|
* @deprecated this method is deprecated |
262
|
|
|
*/ |
263
|
|
|
// @TODO: should be removed in the next releases. |
264
|
|
|
public function saveToCloud( |
265
|
|
|
string $url, |
266
|
|
|
string $name, |
267
|
|
|
string $path = null, |
268
|
|
|
string $method = 'GET', |
269
|
|
|
array $headers = [], |
270
|
|
|
array $options = [] |
271
|
|
|
) |
272
|
|
|
{ |
273
|
|
|
@trigger_error('saveToCloud method is deprecated and will be removed in a future release. Use Cloud instead', E_USER_DEPRECATED); |
274
|
|
|
if ($this instanceof HLS && $this->getTsSubDirectory()) { |
275
|
|
|
throw new InvalidArgumentException("It is not possible to create subdirectory in a cloud"); |
276
|
|
|
} |
277
|
|
|
$results = $this->saveToTemporaryFolder($path); |
|
|
|
|
278
|
|
|
sleep(1); |
279
|
|
|
|
280
|
|
|
$cloud = new Cloud($url, $method, $options); |
281
|
|
|
$cloud->uploadDirectory($this->tmp_dir, ['name' => $name, 'headers' => $headers]); |
282
|
|
|
|
283
|
|
|
$this->moveTmpFolder($path); |
284
|
|
|
|
285
|
|
|
return $results; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* @param array $config |
290
|
|
|
* @param string $dest |
291
|
|
|
* @param string|null $path |
292
|
|
|
* @return mixed |
293
|
|
|
* @throws Exception |
294
|
|
|
* @deprecated this method is deprecated |
295
|
|
|
*/ |
296
|
|
|
// @TODO: should be removed in the next releases. |
297
|
|
|
public function saveToS3( |
298
|
|
|
array $config, |
299
|
|
|
string $dest, |
300
|
|
|
string $path = null |
301
|
|
|
) |
302
|
|
|
{ |
303
|
|
|
@trigger_error('saveToS3 method is deprecated and will be removed in a future release. Use AWS instead', E_USER_DEPRECATED); |
304
|
|
|
$results = $this->saveToTemporaryFolder($path); |
|
|
|
|
305
|
|
|
sleep(1); |
306
|
|
|
|
307
|
|
|
$aws = new AWS($config); |
308
|
|
|
$aws->uploadDirectory($this->tmp_dir, ['dest' => $dest]); |
309
|
|
|
|
310
|
|
|
$this->moveTmpFolder($path); |
311
|
|
|
|
312
|
|
|
return $results; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* @param array $config |
317
|
|
|
* @param string $bucket |
318
|
|
|
* @param string|null $path |
319
|
|
|
* @param array $options |
320
|
|
|
* @param bool $userProject |
321
|
|
|
* @return mixed |
322
|
|
|
* @throws Exception |
323
|
|
|
* @deprecated this method is deprecated |
324
|
|
|
*/ |
325
|
|
|
// @TODO: should be removed in the next releases. |
326
|
|
|
public function saveToGCS( |
327
|
|
|
array $config, |
328
|
|
|
string $bucket, |
329
|
|
|
string $path = null, |
330
|
|
|
array $options = [], |
331
|
|
|
bool $userProject = false |
332
|
|
|
) |
333
|
|
|
{ |
334
|
|
|
@trigger_error('saveToGCS method is deprecated and will be removed in a future release. Use GoogleCloudStorage instead', E_USER_DEPRECATED); |
335
|
|
|
if ($this instanceof HLS && $this->getTsSubDirectory()) { |
336
|
|
|
throw new InvalidArgumentException("It is not possible to create subdirectory in a cloud"); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
$results = $this->saveToTemporaryFolder($path); |
|
|
|
|
340
|
|
|
sleep(1); |
341
|
|
|
|
342
|
|
|
$google_cloud = new GoogleCloudStorage($config, $bucket, $userProject); |
343
|
|
|
$google_cloud->uploadDirectory($this->tmp_dir, $options); |
344
|
|
|
|
345
|
|
|
$this->moveTmpFolder($path); |
346
|
|
|
|
347
|
|
|
return $results; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* @param string $connectionString |
352
|
|
|
* @param string $container |
353
|
|
|
* @param string|null $path |
354
|
|
|
* @return mixed |
355
|
|
|
* @throws Exception |
356
|
|
|
* @deprecated this method is deprecated |
357
|
|
|
*/ |
358
|
|
|
// @TODO: should be removed in the next releases. |
359
|
|
|
public function saveToMAS( |
360
|
|
|
string $connectionString, |
361
|
|
|
string $container, |
362
|
|
|
string $path = null |
363
|
|
|
) |
364
|
|
|
{ |
365
|
|
|
@trigger_error('saveToMAS method is deprecated and will be removed in a future release. Use MicrosoftAzure instead', E_USER_DEPRECATED); |
366
|
|
|
|
367
|
|
|
if ($this instanceof HLS && $this->getTsSubDirectory()) { |
368
|
|
|
throw new InvalidArgumentException("It is not possible to create subdirectory in a cloud"); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
$results = $this->saveToTemporaryFolder($path); |
|
|
|
|
372
|
|
|
sleep(1); |
373
|
|
|
|
374
|
|
|
$google_cloud = new MicrosoftAzure($connectionString); |
375
|
|
|
$google_cloud->uploadDirectory($this->tmp_dir, ['container' => $container]); |
376
|
|
|
|
377
|
|
|
$this->moveTmpFolder($path); |
378
|
|
|
|
379
|
|
|
return $results; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* @param $path |
384
|
|
|
* @return array |
385
|
|
|
* @throws Exception |
386
|
|
|
* @deprecated this method is deprecated |
387
|
|
|
*/ |
388
|
|
|
// @TODO: should be removed in the next releases. |
389
|
|
|
private function saveToTemporaryFolder($path) |
390
|
|
|
{ |
391
|
|
|
$basename = Helper::randomString(); |
392
|
|
|
|
393
|
|
|
if (null !== $path) { |
394
|
|
|
$basename = pathinfo($path, PATHINFO_BASENAME); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
$this->tmp_dir = FileManager::tmpDir(); |
398
|
|
|
|
399
|
|
|
return $this->save($this->tmp_dir . $basename); |
400
|
|
|
} |
401
|
|
|
} |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.