Passed
Pull Request — master (#262)
by Pascal
02:26
created

EncryptsHLSSegments::getEncrypedHLSParameters()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
nc 3
nop 0
dl 0
loc 15
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace ProtoneMedia\LaravelFFMpeg\Exporters;
4
5
use Closure;
6
use Illuminate\Support\Str;
7
use ProtoneMedia\LaravelFFMpeg\FFMpeg\StdListener;
8
use ProtoneMedia\LaravelFFMpeg\Filesystem\Disk;
9
10
trait EncryptsHLSSegments
11
{
12
    /**
13
     * The encryption key.
14
     *
15
     * @var string
16
     */
17
    private $encryptionKey;
18
19
    private $onNewEncryptionKey = null;
20
21
    private $encryptionSecretsDisk  = null;
22
    private $encryptionInfoFilename = null;
23
    private $encryptionIV           = null;
24
    private $rotatingEncryptiongKey = false;
25
    private $segmentsOpenend        = 0;
26
27
    /**
28
     * Creates a new encryption key.
29
     *
30
     * @return string
31
     */
32
    public static function generateEncryptionKey(): string
33
    {
34
        return random_bytes(16);
35
    }
36
37
    /**
38
     * Sets the encryption key with the given value or generates a new one.
39
     *
40
     * @param string $key
41
     * @return self
42
     */
43
    private function setEncryptionKey($key = null): self
44
    {
45
        return $this->encryptionKey = $key ?: static::generateEncryptionKey();
46
    }
47
48
    /**
49
     * Initialises the disk, info and IV for encryption and sets the key.
50
     *
51
     * @param string $key
52
     * @return self
53
     */
54
    public function withEncryptionKey($key = null): self
55
    {
56
        $this->encryptionSecretsDisk  = Disk::makeTemporaryDisk();
57
        $this->encryptionInfoFilename = Str::random(8) . ".keyinfo";
58
        $this->encryptionIV           = bin2hex(static::generateEncryptionKey());
59
60
        return tap($this)->setEncryptionKey($key);
61
    }
62
63
    /**
64
     * Enables encryption with rotating keys.
65
     *
66
     * @return self
67
     */
68
    public function withRotatingEncryptionKey(): self
69
    {
70
        $this->rotatingEncryptiongKey = true;
71
72
        return $this->withEncryptionKey();
73
    }
74
75
    /**
76
     * A callable for each key that is generated.
77
     *
78
     * @param Closure $callback
79
     * @return self
80
     */
81
    public function onNewEncryptionKey(Closure $callback): self
82
    {
83
        $this->onNewEncryptionKey = $callback;
84
85
        return $this;
86
    }
87
88
    private function rotateEncryptionKey()
89
    {
90
        // randomize the encryption key
91
        $this->encryptionSecretsDisk->put(
92
            $keyFilename = Str::random(8) . '.key',
93
            $encryptionKey = $this->setEncryptionKey()
94
        );
95
96
        $keyPath = $this->encryptionSecretsDisk->makeMedia($keyFilename)->getLocalPath();
97
98
        $this->encryptionSecretsDisk->put($this->encryptionInfoFilename, implode(PHP_EOL, [
99
            $keyPath, $keyPath, $this->encryptionIV,
100
        ]));
101
102
        if ($this->onNewEncryptionKey) {
103
            call_user_func($this->onNewEncryptionKey, $keyFilename, $encryptionKey);
104
        }
105
106
        return $this->encryptionSecretsDisk
107
            ->makeMedia($this->encryptionInfoFilename)
108
            ->getLocalPath();
109
    }
110
111
    private function getEncrypedHLSParameters(): array
112
    {
113
        if (!$this->encryptionKey) {
114
            return [];
115
        }
116
117
        $keyInfoPath = $this->rotateEncryptionKey();
118
        $parameters  = ['-hls_key_info_file', $keyInfoPath];
119
120
        if ($this->rotatingEncryptiongKey) {
121
            $parameters[] = '-hls_flags';
122
            $parameters[] = 'periodic_rekey';
123
        }
124
125
        return $parameters;
126
    }
127
128
    private function addHandlerToRotateEncryption()
129
    {
130
        if (!$this->rotatingEncryptiongKey) {
131
            return;
132
        }
133
134
        $this->addListener(new StdListener)->onEvent('listen', function ($line) {
0 ignored issues
show
Bug introduced by
It seems like addListener() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

134
        $this->/** @scrutinizer ignore-call */ 
135
               addListener(new StdListener)->onEvent('listen', function ($line) {
Loading history...
135
            $opensEncryptedSegment = Str::contains($line, "Opening 'crypto:/")
136
                && Str::contains($line, ".ts' for writing");
137
138
            if (!$opensEncryptedSegment) {
139
                return;
140
            }
141
142
            $this->segmentsOpenend++;
143
            $this->rotateEncryptionKey();
144
        });
145
    }
146
147
    private function cleanupHLSEncryption()
148
    {
149
        if (!$this->encryptionSecretsDisk) {
150
            return;
151
        }
152
153
        $paths = $this->encryptionSecretsDisk->allFiles();
154
155
        foreach ($paths as $path) {
156
            $this->encryptionSecretsDisk->delete($path);
157
        }
158
    }
159
}
160