Passed
Pull Request — master (#243)
by Pascal
02:39
created

PHPFFMpeg::isVideo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace ProtoneMedia\LaravelFFMpeg\Drivers;
4
5
use Closure;
6
use Exception;
7
use FFMpeg\Coordinate\Dimension;
8
use FFMpeg\Coordinate\TimeCode;
9
use FFMpeg\FFMpeg;
10
use FFMpeg\FFProbe\DataMapping\Stream;
11
use FFMpeg\Filters\Audio\SimpleFilter;
12
use FFMpeg\Filters\FilterInterface;
13
use FFMpeg\Filters\Video\ResizeFilter;
14
use FFMpeg\Media\AbstractMediaType;
15
use FFMpeg\Media\AdvancedMedia as BaseAdvancedMedia;
16
use FFMpeg\Media\Concat;
17
use FFMpeg\Media\Frame;
18
use FFMpeg\Media\Video;
19
use Illuminate\Support\Arr;
20
use Illuminate\Support\Collection;
21
use Illuminate\Support\Traits\ForwardsCalls;
22
use ProtoneMedia\LaravelFFMpeg\Exporters\MediaExporter;
23
use ProtoneMedia\LaravelFFMpeg\FFMpeg\AdvancedMedia;
24
use ProtoneMedia\LaravelFFMpeg\FFMpeg\AudioMedia;
25
use ProtoneMedia\LaravelFFMpeg\FFMpeg\FFProbe;
26
use ProtoneMedia\LaravelFFMpeg\FFMpeg\LegacyFilterMapping;
27
use ProtoneMedia\LaravelFFMpeg\FFMpeg\VideoMedia;
28
use ProtoneMedia\LaravelFFMpeg\Filesystem\Media;
29
use ProtoneMedia\LaravelFFMpeg\Filesystem\MediaCollection;
30
use ProtoneMedia\LaravelFFMpeg\Filters\WatermarkFactory;
31
32
/**
33
 * @mixin \FFMpeg\Media\AbstractMediaType
34
 */
35
class PHPFFMpeg
36
{
37
    use ForwardsCalls;
38
39
    /**
40
     * @var \FFMpeg\FFMpeg
41
     */
42
    private $ffmpeg;
43
44
    /**
45
     * @var \ProtoneMedia\LaravelFFMpeg\Filesystem\MediaCollection
46
     */
47
    private $mediaCollection;
48
49
    /**
50
     * @var boolean
51
     */
52
    private $forceAdvanced = false;
53
54
    /**
55
     * @var \Illuminate\Support\Collection
56
     */
57
    private $pendingComplexFilters;
58
59
    /**
60
     * @var \FFMpeg\Media\AbstractMediaType
61
     */
62
    private $media;
63
64
    public function __construct(FFMpeg $ffmpeg)
65
    {
66
        $this->ffmpeg                = $ffmpeg;
67
        $this->pendingComplexFilters = new Collection;
68
    }
69
70
    /**
71
     * Returns a fresh instance of itself with only the underlying FFMpeg instance.
72
     */
73
    public function fresh(): self
74
    {
75
        return new static($this->ffmpeg);
76
    }
77
78
    public function get(): AbstractMediaType
79
    {
80
        return $this->media;
81
    }
82
83
    private function isAdvancedMedia(): bool
84
    {
85
        return $this->get() instanceof BaseAdvancedMedia;
86
    }
87
88
    public function isFrame(): bool
89
    {
90
        return $this->get() instanceof Frame;
91
    }
92
93
    public function isConcat(): bool
94
    {
95
        return $this->get() instanceof Concat;
96
    }
97
98
    public function isVideo(): bool
99
    {
100
        return $this->get() instanceof Video;
101
    }
102
103
    public function getMediaCollection(): MediaCollection
104
    {
105
        return $this->mediaCollection;
106
    }
107
108
    /**
109
     * Opens the MediaCollection if it's not been instanciated yet.
110
     */
111
    public function open(MediaCollection $mediaCollection): self
112
    {
113
        if ($this->media) {
114
            return $this;
115
        }
116
117
        $this->mediaCollection = $mediaCollection;
118
119
        if ($mediaCollection->count() === 1 && !$this->forceAdvanced) {
120
            $media = Arr::first($mediaCollection->collection());
121
122
            $this->ffmpeg->setFFProbe(
123
                FFProbe::make($this->ffmpeg->getFFProbe())->setMedia($media)
124
            );
125
126
            $ffmpegMedia = $this->ffmpeg->open($media->getLocalPath());
127
128
            $this->media = $ffmpegMedia instanceof Video
129
                ? VideoMedia::make($ffmpegMedia)
130
                : AudioMedia::make($ffmpegMedia);
131
132
            $this->media->setHeaders(Arr::first($mediaCollection->getHeaders()) ?: []);
133
        } else {
134
            $ffmpegMedia = $this->ffmpeg->openAdvanced($mediaCollection->getLocalPaths());
135
136
            $this->media = AdvancedMedia::make($ffmpegMedia)
137
                ->setHeaders($mediaCollection->getHeaders());
138
        }
139
140
        return $this;
141
    }
142
143
    public function frame(TimeCode $timecode)
144
    {
145
        if (!$this->isVideo()) {
146
            throw new Exception('Opened media is not a video file.');
147
        }
148
149
        $this->media = $this->media->frame($timecode);
0 ignored issues
show
Bug introduced by
The method frame() does not exist on FFMpeg\Media\AbstractMediaType. It seems like you code against a sub-type of FFMpeg\Media\AbstractMediaType such as FFMpeg\Media\Video. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

149
        /** @scrutinizer ignore-call */ 
150
        $this->media = $this->media->frame($timecode);
Loading history...
150
151
        return $this;
152
    }
153
154
    public function concatWithoutTranscoding()
155
    {
156
        $localPaths = $this->mediaCollection->getLocalPaths();
157
158
        $this->media = $this->ffmpeg->open(Arr::first($localPaths))
159
            ->concat($localPaths);
160
161
        return $this;
162
    }
163
164
    /**
165
     * Force 'openAdvanced' when opening the MediaCollection
166
     */
167
    public function openAdvanced(MediaCollection $mediaCollection): self
168
    {
169
        $this->forceAdvanced = true;
170
171
        return $this->open($mediaCollection);
172
    }
173
174
    public function getStreams(): array
175
    {
176
        if (!$this->isAdvancedMedia()) {
177
            return iterator_to_array($this->media->getStreams());
0 ignored issues
show
Bug introduced by
The method getStreams() does not exist on FFMpeg\Media\AbstractMediaType. It seems like you code against a sub-type of FFMpeg\Media\AbstractMediaType such as FFMpeg\Media\AbstractStreamableMedia. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

177
            return iterator_to_array($this->media->/** @scrutinizer ignore-call */ getStreams());
Loading history...
178
        }
179
180
        return $this->mediaCollection->map(function (Media $media) {
0 ignored issues
show
Bug introduced by
The method map() does not exist on ProtoneMedia\LaravelFFMp...esystem\MediaCollection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

180
        return $this->mediaCollection->/** @scrutinizer ignore-call */ map(function (Media $media) {
Loading history...
181
            return $this->fresh()->open(MediaCollection::make([$media]))->getStreams();
182
        })->collapse()->all();
183
    }
184
185
    public function getFilters(): array
186
    {
187
        return iterator_to_array($this->media->getFiltersCollection());
0 ignored issues
show
Bug introduced by
$this->media->getFiltersCollection() of type FFMpeg\Media\MediaTypeInterface is incompatible with the type Traversable expected by parameter $iterator of iterator_to_array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

187
        return iterator_to_array(/** @scrutinizer ignore-type */ $this->media->getFiltersCollection());
Loading history...
188
    }
189
190
    //
191
192
    public function getDurationInSeconds(): int
193
    {
194
        return round($this->getDurationInMiliseconds() / 1000);
195
    }
196
197
    /**
198
     * Gets the duration of the media from the first stream or from the format.
199
     */
200
    public function getDurationInMiliseconds(): int
201
    {
202
        $stream = Arr::first($this->getStreams());
203
204
        if ($stream->has('duration')) {
205
            return $stream->get('duration') * 1000;
206
        }
207
208
        $format = $this->getFormat();
0 ignored issues
show
Bug introduced by
The method getFormat() does not exist on ProtoneMedia\LaravelFFMpeg\Drivers\PHPFFMpeg. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

208
        /** @scrutinizer ignore-call */ 
209
        $format = $this->getFormat();
Loading history...
209
210
        if ($format->has('duration')) {
0 ignored issues
show
Bug introduced by
The method has() does not exist on ProtoneMedia\LaravelFFMpeg\Drivers\PHPFFMpeg. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

210
        if ($format->/** @scrutinizer ignore-call */ has('duration')) {
Loading history...
211
            return $format->get('duration') * 1000;
0 ignored issues
show
Unused Code introduced by
The call to ProtoneMedia\LaravelFFMp...rivers\PHPFFMpeg::get() has too many arguments starting with 'duration'. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

211
            return $format->/** @scrutinizer ignore-call */ get('duration') * 1000;

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
212
        }
213
    }
214
215
    /**
216
     * Gets the first audio streams of the media.
217
     */
218
    public function getAudioStream(): ?Stream
219
    {
220
        return Arr::first($this->getStreams(), function (Stream $stream) {
221
            return $stream->isAudio();
222
        });
223
    }
224
225
    /**
226
     * Gets the first video streams of the media.
227
     */
228
    public function getVideoStream(): ?Stream
229
    {
230
        return Arr::first($this->getStreams(), function (Stream $stream) {
231
            return $stream->isVideo();
232
        });
233
    }
234
235
    //
236
237
    /**
238
     * Helper method to provide multiple ways to add a filter to the underlying
239
     * media object.
240
     *
241
     * @return self
242
     */
243
    public function addFilter(): self
244
    {
245
        $arguments = func_get_args();
246
247
        // to support '[in]filter[out]' complex filters
248
        if ($this->isAdvancedMedia() && count($arguments) === 3) {
249
            $this->media->filters()->custom(...$arguments);
250
251
            return $this;
252
        }
253
254
        // use a callback to add a filter
255
        if ($arguments[0] instanceof Closure) {
256
            call_user_func_array($arguments[0], [$this->media->filters()]);
257
258
            return $this;
259
        }
260
261
        // use an object to add a filter
262
        if ($arguments[0] instanceof FilterInterface) {
263
            call_user_func_array([$this->media, 'addFilter'], $arguments);
264
265
            return $this;
266
        }
267
268
        // use a single array with parameters to define a filter
269
        if (is_array($arguments[0])) {
270
            $this->media->addFilter(new SimpleFilter($arguments[0]));
0 ignored issues
show
Bug introduced by
The method addFilter() does not exist on FFMpeg\Media\AbstractMediaType. It seems like you code against a sub-type of said class. However, the method does not exist in FFMpeg\Media\AbstractStreamableMedia. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

270
            $this->media->/** @scrutinizer ignore-call */ 
271
                          addFilter(new SimpleFilter($arguments[0]));
Loading history...
271
272
            return $this;
273
        }
274
275
        // use all function arguments as a filter
276
        $this->media->addFilter(new SimpleFilter($arguments));
277
278
        return $this;
279
    }
280
281
    /**
282
     * Calls the callable with a WatermarkFactory instance and
283
     * adds the freshly generated WatermarkFilter.
284
     *
285
     * @param callable $withWatermarkFactory
286
     * @return self
287
     */
288
    public function addWatermark(callable $withWatermarkFactory): self
289
    {
290
        $withWatermarkFactory(
291
            $watermarkFactory = new WatermarkFactory
292
        );
293
294
        return $this->addFilter($watermarkFactory->get());
295
    }
296
297
    /**
298
     * Shortcut for adding a Resize filter.
299
     *
300
     * @param int $width
301
     * @param int $height
302
     * @param string $mode
303
     * @return self
304
     */
305
    public function resize($width, $height, $mode = ResizeFilter::RESIZEMODE_FIT): self
306
    {
307
        $dimension = new Dimension($width, $height);
308
309
        $filter = new ResizeFilter($dimension, $mode);
310
311
        return $this->addFilter($filter);
312
    }
313
314
    /**
315
     * Maps the arguments into a 'LegacyFilterMapping' instance and
316
     * pushed it to the 'pendingComplexFilters' collection. These
317
     * filters will be applied later on by the MediaExporter.
318
     */
319
    public function addFilterAsComplexFilter($in, $out, ...$arguments): self
320
    {
321
        $this->pendingComplexFilters->push(new LegacyFilterMapping(
322
            $in,
323
            $out,
324
            ...$arguments
325
        ));
326
327
        return $this;
328
    }
329
330
    public function getPendingComplexFilters(): Collection
331
    {
332
        return $this->pendingComplexFilters;
333
    }
334
335
    /**
336
     * Returns the underlying media object itself.
337
     */
338
    public function __invoke(): AbstractMediaType
339
    {
340
        return $this->get();
341
    }
342
343
    /**
344
     * Forwards the call to the underling media object and returns the result
345
     * if it's something different than the media object itself.
346
     */
347
    public function __call($method, $arguments)
348
    {
349
        $result = $this->forwardCallTo($media = $this->get(), $method, $arguments);
350
351
        return ($result === $media) ? $this : $result;
352
    }
353
}
354