Passed
Push — master ( bb93e2...a22d39 )
by Pascal
02:49
created

HLSExporter::generateMasterPlaylistFilename()   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 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace ProtoneMedia\LaravelFFMpeg\Exporters;
4
5
use Closure;
6
use FFMpeg\Format\FormatInterface;
7
use FFMpeg\Format\Video\DefaultVideo;
8
use FFMpeg\Format\VideoInterface;
9
use Illuminate\Support\Collection;
10
use ProtoneMedia\LaravelFFMpeg\Filesystem\Disk;
11
use ProtoneMedia\LaravelFFMpeg\Filesystem\Media;
12
use ProtoneMedia\LaravelFFMpeg\MediaOpener;
13
14
class HLSExporter extends MediaExporter
15
{
16
    use EncryptsHLSSegments;
17
18
    const HLS_KEY_INFO_FILENAME = 'hls_encryption.keyinfo';
19
20
    /**
21
     * @var integer
22
     */
23
    private $segmentLength = 10;
24
25
    /**
26
    * @var integer
27
    */
28
    private $keyFrameInterval = 48;
29
30
    /**
31
     * @var \Illuminate\Support\Collection
32
     */
33
    private $pendingFormats;
34
35
    /**
36
     * @var \ProtoneMedia\LaravelFFMpeg\Exporters\PlaylistGenerator
37
     */
38
    private $playlistGenerator;
39
40
    /**
41
     * @var \Closure
42
     */
43
    private $segmentFilenameGenerator = null;
44
45
    public function setSegmentLength(int $length): self
46
    {
47
        $this->segmentLength = $length;
48
49
        return $this;
50
    }
51
52
    public function setKeyFrameInterval(int $interval): self
53
    {
54
        $this->keyFrameInterval = $interval;
55
56
        return $this;
57
    }
58
59
    public function withPlaylistGenerator(PlaylistGenerator $playlistGenerator): self
60
    {
61
        $this->playlistGenerator = $playlistGenerator;
62
63
        return $this;
64
    }
65
66
    private function getPlaylistGenerator(): PlaylistGenerator
67
    {
68
        return $this->playlistGenerator ?: new HLSPlaylistGenerator;
69
    }
70
71
    public function useSegmentFilenameGenerator(Closure $callback): self
72
    {
73
        $this->segmentFilenameGenerator = $callback;
74
75
        return $this;
76
    }
77
78
    private function getSegmentFilenameGenerator(): callable
79
    {
80
        return $this->segmentFilenameGenerator ?: function ($name, $format, $key, $segments, $playlist) {
81
            $segments("{$name}_{$key}_{$format->getKiloBitrate()}_%05d.ts");
82
            $playlist("{$name}_{$key}_{$format->getKiloBitrate()}.m3u8");
83
        };
84
    }
85
86
    private function getSegmentPatternAndFormatPlaylistPath(string $baseName, VideoInterface $format, int $key): array
87
    {
88
        $segmentsPattern    = null;
89
        $formatPlaylistPath = null;
90
91
        call_user_func(
92
            $this->getSegmentFilenameGenerator(),
93
            $baseName,
94
            $format,
95
            $key,
96
            function ($path) use (&$segmentsPattern) {
97
                $segmentsPattern = $path;
98
            },
99
            function ($path) use (&$formatPlaylistPath) {
100
                $formatPlaylistPath = $path;
101
            }
102
        );
103
104
        return [$segmentsPattern, $formatPlaylistPath];
105
    }
106
107
    private function addHLSParametersToFormat(DefaultVideo $format, string $segmentsPattern, Disk $disk)
108
    {
109
        $hlsParameters = [
110
            '-sc_threshold',
111
            '0',
112
            '-g',
113
            $this->keyFrameInterval,
114
            '-hls_playlist_type',
115
            'vod',
116
            '-hls_time',
117
            $this->segmentLength,
118
            '-hls_segment_filename',
119
            $disk->makeMedia($segmentsPattern)->getLocalPath(),
120
        ];
121
122
        $format->setAdditionalParameters(array_merge(
123
            $format->getAdditionalParameters() ?: [],
124
            $hlsParameters,
125
            $this->getEncrypedHLSParameters()
126
        ));
127
    }
128
129
    private function applyFiltersCallback(callable $filtersCallback, int $formatKey): array
130
    {
131
        $filtersCallback(
132
            $hlsVideoFilters = new HLSVideoFilters($this->driver, $formatKey)
133
        );
134
135
        $filterCount = $hlsVideoFilters->count();
136
137
        $outs = [$filterCount ? HLSVideoFilters::glue($formatKey, $filterCount) : '0:v'];
138
139
        if ($this->getAudioStream()) {
0 ignored issues
show
Bug introduced by
The method getAudioStream() does not exist on ProtoneMedia\LaravelFFMpeg\Exporters\HLSExporter. 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

139
        if ($this->/** @scrutinizer ignore-call */ getAudioStream()) {
Loading history...
140
            $outs[] = '0:a';
141
        }
142
143
        return $outs;
144
    }
145
146
    public static function generateMasterPlaylistFilename($key): string
147
    {
148
        return "master_playlist_guide_{$key}.m3u8";
149
    }
150
151
    private function cleanupMasterPlaylistGuides(Media $media)
152
    {
153
        $this->pendingFormats->map(function ($formatAndCallback, $key) use ($media) {
154
            $media->getDisk()->delete(
0 ignored issues
show
Bug introduced by
The method delete() does not exist on ProtoneMedia\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

154
            $media->getDisk()->/** @scrutinizer ignore-call */ delete(
Loading history...
155
                $media->getDirectory() . static::generateMasterPlaylistFilename($key)
156
            );
157
        });
158
    }
159
160
    private function prepareSaving(string $path = null): Collection
161
    {
162
        $media = $this->getDisk()->makeMedia($path);
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type null; however, parameter $path of ProtoneMedia\LaravelFFMp...ystem\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

162
        $media = $this->getDisk()->makeMedia(/** @scrutinizer ignore-type */ $path);
Loading history...
163
164
        $baseName = $media->getDirectory() . $media->getFilenameWithoutExtension();
165
166
        return $this->pendingFormats->map(function ($formatAndCallback, $key) use ($baseName, $media) {
0 ignored issues
show
Unused Code introduced by
The import $media is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
167
            $disk = $this->getDisk()->clone();
168
169
            [$format, $filtersCallback] = $formatAndCallback;
170
171
            [$segmentsPattern, $formatPlaylistPath] = $this->getSegmentPatternAndFormatPlaylistPath(
172
                $baseName,
173
                $format,
174
                $key
175
            );
176
177
            $this->addHLSParametersToFormat($format, $segmentsPattern, $disk);
178
179
            $format->setAdditionalParameters(array_merge(
180
                $format->getAdditionalParameters(),
181
                [
182
                    '-master_pl_name',
183
                    $this->generateMasterPlaylistFilename($key),
184
                ]
185
            ));
186
187
            if ($filtersCallback) {
188
                $outs = $this->applyFiltersCallback($filtersCallback, $key);
189
            }
190
191
            $this->addFormatOutputMapping($format, $disk->makeMedia($formatPlaylistPath), $outs ?? ['0']);
192
193
            return $this->getDisk()->makeMedia($formatPlaylistPath);
194
        })->tap(function () {
195
            $this->addHandlerToRotateEncryptionKey();
196
        });
197
    }
198
199
    public function getCommand(string $path = null)
200
    {
201
        $this->prepareSaving($path);
202
203
        return parent::getCommand(null);
204
    }
205
206
    public function save(string $path = null): MediaOpener
207
    {
208
        return $this->prepareSaving($path)->pipe(function ($playlistMedia) use ($path) {
209
            $result = parent::save();
210
211
            $playlist = $this->getPlaylistGenerator()->get(
212
                $playlistMedia->all(),
213
                $this->driver->fresh()
214
            );
215
216
            $this->getDisk()->put($path, $playlist);
0 ignored issues
show
Bug introduced by
The method put() does not exist on ProtoneMedia\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

216
            $this->getDisk()->/** @scrutinizer ignore-call */ put($path, $playlist);
Loading history...
217
218
            $this->replaceAbsolutePathsHLSEncryption($playlistMedia);
219
            $this->cleanupMasterPlaylistGuides($playlistMedia->first());
220
            $this->cleanupHLSEncryption();
221
222
            return $result;
223
        });
224
    }
225
226
    public function addFormat(FormatInterface $format, callable $filtersCallback = null): self
227
    {
228
        if (!$this->pendingFormats) {
229
            $this->pendingFormats = new Collection;
230
        }
231
232
        $this->pendingFormats->push([$format, $filtersCallback]);
233
234
        return $this;
235
    }
236
}
237