Completed
Branch v7 (f01542)
by Pascal
08:46
created

HLSExporter.php$0 ➔ scale()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
1
<?php
2
3
namespace Pbmedia\LaravelFFMpeg\Exporters;
4
5
use Closure;
6
use FFMpeg\Filters\AdvancedMedia\ComplexFilters;
7
use FFMpeg\Format\FormatInterface;
8
use FFMpeg\Format\Video\DefaultVideo;
9
use FFMpeg\Format\VideoInterface;
10
use Illuminate\Support\Collection;
11
use Illuminate\Support\Fluent;
12
use Pbmedia\LaravelFFMpeg\Filesystem\Disk;
13
use Pbmedia\LaravelFFMpeg\MediaOpener;
14
15
class HLSExporter extends MediaExporter
16
{
17
    private int $segmentLength                    = 10;
18
    private int $keyFrameInterval                 = 48;
19
    private ?Collection $pendingFormats           = null;
20
    private ?PlaylistGenerator $playlistGenerator = null;
21
    private ?Closure $segmentFilenameGenerator    = null;
22
23
    public function setSegmentLength(int $length): self
24
    {
25
        $this->segmentLength = $length;
26
27
        return $this;
28
    }
29
30
    public function setKeyFrameInterval(int $interval): self
31
    {
32
        $this->keyFrameInterval = $interval;
33
34
        return $this;
35
    }
36
37
    public function withPlaylistGenerator(PlaylistGenerator $playlistGenerator): self
38
    {
39
        $this->playlistGenerator = $playlistGenerator;
40
41
        return $this;
42
    }
43
44
    public function useSegmentFilenameGenerator(Closure $callback): self
45
    {
46
        $this->segmentFilenameGenerator = $callback;
47
48
        return $this;
49
    }
50
51
    private function getSegmentPatternAndFormatPlaylistPath(string $baseName, VideoInterface $format, int $key): array
52
    {
53
        $segmentsPattern    = null;
54
        $formatPlaylistPath = null;
55
56
        call_user_func(
57
            $this->segmentFilenameGenerator,
58
            $baseName,
59
            $format,
60
            $key,
61
            function ($path) use (&$segmentsPattern) {
62
                $segmentsPattern = $path;
63
            },
64
            function ($path) use (&$formatPlaylistPath) {
65
                $formatPlaylistPath = $path;
66
            },
67
        );
68
69
        return [$segmentsPattern, $formatPlaylistPath];
70
    }
71
72
    private function addHLSParametersToFormat(DefaultVideo $format, string $segmentsPattern, Disk $disk)
73
    {
74
        $parameters = [
75
            '-sc_threshold',
76
            '0',
77
            '-g',
78
            $this->keyFrameInterval,
79
            '-hls_playlist_type',
80
            'vod',
81
            '-hls_time',
82
            $this->segmentLength,
83
            '-hls_segment_filename',
84
            $disk->makeMedia($segmentsPattern)->getLocalPath(),
85
        ];
86
87
        $format->setAdditionalParameters($parameters);
88
    }
89
90
    private function applyFiltersCallback(callable $filtersCallback, $key): bool
91
    {
92
        $called = new Fluent(['called' => false]);
93
94
        $mediaMock = new class($this->driver, $key, $called) {
95
            private $driver;
96
            private $key;
97
            private $called;
98
99
            public function __construct($driver, $key, $called)
100
            {
101
                $this->driver = $driver;
102
                $this->key    = $key;
103
                $this->called = $called;
104
            }
105
106
            public function addLegacyFilter(...$arguments)
107
            {
108
                $this->driver->addFilterAsComplexFilter('[0]', "[v{$this->key}]", ...$arguments);
109
110
                $this->called['called'] = true;
111
            }
112
113
            public function scale($width, $height)
114
            {
115
                $this->addFilter("scale={$width}:{$height}");
116
            }
117
118
            public function addFilter(...$arguments)
119
            {
120
                $in  = '[0]';
121
                $out = "[v{$this->key}]";
122
123
                if (count($arguments) === 1 && !is_callable($arguments[0])) {
124
                    $this->driver->addFilter($in, $arguments[0], $out);
125
                } else {
126
                    $this->driver->addFilter(function (ComplexFilters $filters) use ($arguments, $in,$out) {
127
                        $arguments[0]($filters, $in, $out);
128
                    });
129
                }
130
131
                $this->called['called'] = true;
132
            }
133
        };
134
135
        $filtersCallback($mediaMock);
136
137
        return $called['called'];
138
    }
139
140
    public function save(string $path = null): MediaOpener
141
    {
142
        $baseName = $this->getDisk()->makeMedia($path)->getFilenameWithoutExtension();
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type null; however, parameter $path of Pbmedia\LaravelFFMpeg\Filesystem\Disk::makeMedia() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

142
        $baseName = $this->getDisk()->makeMedia(/** @scrutinizer ignore-type */ $path)->getFilenameWithoutExtension();
Loading history...
143
144
        if (!$this->segmentFilenameGenerator) {
145
            $this->segmentFilenameGenerator = function ($name, $format, $key, $segments, $playlist) {
146
                $pattern = "{$name}_{$key}_{$format->getKiloBitrate()}";
147
                $segments("{$pattern}_%05d.ts");
148
                $playlist("{$pattern}.m3u8");
149
            };
150
        }
151
152
        return $this->pendingFormats->map(function ($formatAndCallback, $key) use ($baseName) {
0 ignored issues
show
Bug introduced by
The method map() does not exist on null. ( Ignorable by Annotation )

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

152
        return $this->pendingFormats->/** @scrutinizer ignore-call */ map(function ($formatAndCallback, $key) use ($baseName) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
153
            $disk = $this->getDisk()->clone();
154
155
            [$format, $filtersCallback] = $formatAndCallback;
156
157
            [$segmentsPattern, $formatPlaylistPath] = $this->getSegmentPatternAndFormatPlaylistPath(
158
                $baseName,
159
                $format,
160
                $key,
161
            );
162
163
            $this->addHLSParametersToFormat($format, $segmentsPattern, $disk);
164
165
            $keysWithFilters = [];
166
167
            if ($filtersCallback) {
168
                if ($this->applyFiltersCallback($filtersCallback, $key)) {
169
                    $keysWithFilters[$key] = "[v{$key}]";
170
                }
171
            }
172
173
            $this->addFormatOutputMapping($format, $disk->makeMedia($formatPlaylistPath), [$keysWithFilters[$key] ?? '0']);
174
175
            return $this->getDisk()->makeMedia($formatPlaylistPath);
176
        })->pipe(function ($playlistMedia) use ($path) {
177
            $result = parent::save();
178
179
            $this->getDisk()->put($path, $this->makePlaylist($playlistMedia->all()));
0 ignored issues
show
Bug introduced by
The method put() does not exist on Pbmedia\LaravelFFMpeg\Filesystem\Disk. 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

179
            $this->getDisk()->/** @scrutinizer ignore-call */ put($path, $this->makePlaylist($playlistMedia->all()));
Loading history...
180
181
            return $result;
182
        });
183
    }
184
185
    private function makePlaylist(array $playlistMedia): string
186
    {
187
        if (!$this->playlistGenerator) {
188
            $this->withPlaylistGenerator(new HLSPlaylistGenerator);
189
        }
190
191
        return $this->playlistGenerator->get($playlistMedia, $this->driver->fresh());
192
    }
193
194
    public function addFormat(FormatInterface $format, callable $filtersCallback = null)
195
    {
196
        if (!$this->pendingFormats) {
197
            $this->pendingFormats = new Collection;
198
        }
199
200
        $this->pendingFormats->push([$format, $filtersCallback]);
201
202
        return $this;
203
    }
204
}
205