Passed
Push — master ( 1d1353...51bc60 )
by Pascal
02:58
created

PHPFFMpeg::get()   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\TimeCode;
8
use FFMpeg\FFMpeg;
9
use FFMpeg\FFProbe\DataMapping\Stream;
10
use FFMpeg\Filters\Audio\SimpleFilter;
11
use FFMpeg\Filters\FilterInterface;
12
use FFMpeg\Media\AbstractMediaType;
13
use FFMpeg\Media\AdvancedMedia;
14
use FFMpeg\Media\Concat;
15
use FFMpeg\Media\Frame;
16
use FFMpeg\Media\Video;
17
use Illuminate\Support\Arr;
18
use Illuminate\Support\Collection;
19
use Illuminate\Support\Traits\ForwardsCalls;
20
use ProtoneMedia\LaravelFFMpeg\FFMpeg\LegacyFilterMapping;
21
use ProtoneMedia\LaravelFFMpeg\Filesystem\Media;
22
use ProtoneMedia\LaravelFFMpeg\Filesystem\MediaCollection;
23
24
/**
25
 * @mixin \FFMpeg\Media\AbstractMediaType
26
 */
27
class PHPFFMpeg
28
{
29
    use ForwardsCalls;
30
31
    /**
32
     * @var \FFMpeg\FFMpeg
33
     */
34
    private $ffmpeg;
35
36
    /**
37
     * @var \ProtoneMedia\LaravelFFMpeg\Filesystem\MediaCollection
38
     */
39
    private $mediaCollection;
40
41
    /**
42
     * @var boolean
43
     */
44
    private $forceAdvanced = false;
45
46
    /**
47
     * @var \Illuminate\Support\Collection
48
     */
49
    private $pendingComplexFilters;
50
51
    /**
52
     * @var \FFMpeg\Media\AbstractMediaType
53
     */
54
    private $media;
55
56
    public function __construct(FFMpeg $ffmpeg)
57
    {
58
        $this->ffmpeg                = $ffmpeg;
59
        $this->pendingComplexFilters = new Collection;
60
    }
61
62
    /**
63
     * Returns a fresh instance of itself with only the underlying FFMpeg instance.
64
     */
65
    public function fresh(): self
66
    {
67
        return new static($this->ffmpeg);
68
    }
69
70
    public function get(): AbstractMediaType
71
    {
72
        return $this->media;
73
    }
74
75
    private function isAdvancedMedia(): bool
76
    {
77
        return $this->get() instanceof AdvancedMedia;
78
    }
79
80
    public function isFrame(): bool
81
    {
82
        return $this->get() instanceof Frame;
83
    }
84
85
    public function isConcat(): bool
86
    {
87
        return $this->get() instanceof Concat;
88
    }
89
90
    public function isVideo(): bool
91
    {
92
        return $this->get() instanceof Video;
93
    }
94
95
    public function getMediaCollection(): MediaCollection
96
    {
97
        return $this->mediaCollection;
98
    }
99
100
    /**
101
     * Opens the MediaCollection if it's not been instanciated yet.
102
     */
103
    public function open(MediaCollection $mediaCollection): self
104
    {
105
        if ($this->media) {
106
            return $this;
107
        }
108
109
        $this->mediaCollection = $mediaCollection;
110
111
        $localPaths = $mediaCollection->getLocalPaths();
112
113
        if (count($localPaths) === 1 && !$this->forceAdvanced) {
114
            $this->media = $this->ffmpeg->open(Arr::first($localPaths));
115
        } else {
116
            $this->media = $this->ffmpeg->openAdvanced($localPaths);
117
        }
118
119
        return $this;
120
    }
121
122
    public function frame(TimeCode $timecode)
123
    {
124
        if (!$this->isVideo()) {
125
            throw new Exception('Opened media is not a video file.');
126
        }
127
128
        $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

128
        /** @scrutinizer ignore-call */ 
129
        $this->media = $this->media->frame($timecode);
Loading history...
129
130
        return $this;
131
    }
132
133
    public function concatWithoutTranscoding()
134
    {
135
        $localPaths = $this->mediaCollection->getLocalPaths();
136
137
        $this->media = $this->ffmpeg->open(Arr::first($localPaths))
138
            ->concat($localPaths);
139
140
        return $this;
141
    }
142
143
    /**
144
     * Force 'openAdvanced' when opening the MediaCollection
145
     */
146
    public function openAdvanced(MediaCollection $mediaCollection): self
147
    {
148
        $this->forceAdvanced = true;
149
150
        return $this->open($mediaCollection);
151
    }
152
153
    public function getStreams(): array
154
    {
155
        if (!$this->isAdvancedMedia()) {
156
            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

156
            return iterator_to_array($this->media->/** @scrutinizer ignore-call */ getStreams());
Loading history...
157
        }
158
159
        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

159
        return $this->mediaCollection->/** @scrutinizer ignore-call */ map(function (Media $media) {
Loading history...
160
            return $this->fresh()->open(MediaCollection::make([$media]))->getStreams();
161
        })->collapse()->all();
162
    }
163
164
    public function getFilters(): array
165
    {
166
        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

166
        return iterator_to_array(/** @scrutinizer ignore-type */ $this->media->getFiltersCollection());
Loading history...
167
    }
168
169
    //
170
171
    public function getDurationInSeconds(): int
172
    {
173
        return round($this->getDurationInMiliseconds() / 1000);
174
    }
175
176
    /**
177
     * Gets the duration of the media from the first stream or from the format.
178
     */
179
    public function getDurationInMiliseconds(): int
180
    {
181
        $stream = Arr::first($this->getStreams());
182
183
        if ($stream->has('duration')) {
184
            return $stream->get('duration') * 1000;
185
        }
186
187
        $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

187
        /** @scrutinizer ignore-call */ 
188
        $format = $this->getFormat();
Loading history...
188
189
        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

189
        if ($format->/** @scrutinizer ignore-call */ has('duration')) {
Loading history...
190
            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

190
            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...
191
        }
192
    }
193
194
    /**
195
     * Gets the first audio streams of the media.
196
     */
197
    public function getAudioStream(): ?Stream
198
    {
199
        return Arr::first($this->getStreams(), function (Stream $stream) {
200
            return $stream->isAudio();
201
        });
202
    }
203
204
    /**
205
     * Gets the first video streams of the media.
206
     */
207
    public function getVideoStream(): ?Stream
208
    {
209
        return Arr::first($this->getStreams(), function (Stream $stream) {
210
            return $stream->isVideo();
211
        });
212
    }
213
214
    //
215
216
    /**
217
     * Helper method to provide multiple ways to add a filter to the underlying
218
     * media object.
219
     *
220
     * @return self
221
     */
222
    public function addFilter(): self
223
    {
224
        $arguments = func_get_args();
225
226
        // to support '[in]filter[out]' complex filters
227
        if ($this->isAdvancedMedia() && count($arguments) === 3) {
228
            $this->media->filters()->custom(...$arguments);
229
230
            return $this;
231
        }
232
233
        // use a callback to add a filter
234
        if ($arguments[0] instanceof Closure) {
235
            call_user_func_array($arguments[0], [$this->media->filters()]);
236
237
            return $this;
238
        }
239
240
        // use an object to add a filter
241
        if ($arguments[0] instanceof FilterInterface) {
242
            call_user_func_array([$this->media, 'addFilter'], $arguments);
243
244
            return $this;
245
        }
246
247
        // use a single array with parameters to define a filter
248
        if (is_array($arguments[0])) {
249
            $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

249
            $this->media->/** @scrutinizer ignore-call */ 
250
                          addFilter(new SimpleFilter($arguments[0]));
Loading history...
250
251
            return $this;
252
        }
253
254
        // use all function arguments as a filter
255
        $this->media->addFilter(new SimpleFilter($arguments));
256
257
        return $this;
258
    }
259
260
    /**
261
     * Maps the arguments into a 'LegacyFilterMapping' instance and
262
     * pushed it to the 'pendingComplexFilters' collection. These
263
     * filters will be applied later on by the MediaExporter.
264
     */
265
    public function addFilterAsComplexFilter($in, $out, ...$arguments): self
266
    {
267
        $this->pendingComplexFilters->push(new LegacyFilterMapping(
268
            $in,
269
            $out,
270
            ...$arguments
271
        ));
272
273
        return $this;
274
    }
275
276
    public function getPendingComplexFilters(): Collection
277
    {
278
        return $this->pendingComplexFilters;
279
    }
280
281
    /**
282
     * Returns the underlying media object itself.
283
     */
284
    public function __invoke(): AbstractMediaType
285
    {
286
        return $this->get();
287
    }
288
289
    /**
290
     * Forwards the call to the underling media object and returns the result
291
     * if it's something different than the media object itself.
292
     */
293
    public function __call($method, $arguments)
294
    {
295
        $result = $this->forwardCallTo($media = $this->get(), $method, $arguments);
296
297
        return ($result === $media) ? $this : $result;
298
    }
299
}
300