Skip to content

Commit 7f8ede3

Browse files
Add key rotation for ts file example
bug fixes and other minor improvements
1 parent 7d63891 commit 7f8ede3

File tree

7 files changed

+95
-16
lines changed

7 files changed

+95
-16
lines changed

README.md

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ This package provides integration with **[PHP-FFMpeg](https://github.com/PHP-FFM
2020
- [Opening a Resource](#opening-a-resource)
2121
- [DASH](#dash)
2222
- [HLS](#hls)
23-
- [Encrypted HLS](#encrypted-hls)
23+
- [DRM (Encrypted HLS)](#drm-encrypted-hls)
2424
- [Transcoding](#transcoding)
2525
- [Saving Files](#saving-files)
2626
- [Metadata Extraction](#metadata-extraction)
@@ -160,10 +160,14 @@ $video->HLS()
160160
```
161161
**NOTE:** You cannot use HEVC and VP9 formats for HLS packaging.
162162

163-
#### Encrypted HLS
163+
#### DRM (Encrypted HLS)
164164
The encryption process requires some kind of secret (key) together with an encryption algorithm. HLS uses AES in cipher block chaining (CBC) mode. This means each block is encrypted using the ciphertext of the preceding block. [Learn more](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
165165

166166
You must specify a path to save a random key to your local machine and also a URL(or a path) to access the key on your website(the key you will save must be accessible from your website). You must pass both these parameters to the `encryption` method:
167+
168+
##### Single Key
169+
The following code generates a key for all TS files.
170+
167171
``` php
168172
//A path you want to save a random key to your server
169173
$save_to = '/home/public_html/PATH_TO_KEY_DIRECTORY/random_key.key';
@@ -179,8 +183,74 @@ $video->HLS()
179183
->autoGenerateRepresentations([1080, 480, 240])
180184
->save('/var/www/media/videos/hls-stream.m3u8');
181185
```
186+
187+
##### Key Rotation
188+
The code below, allows you to encrypt each TS file with a new encryption key. This can improve security and allows for more flexibility. You can also modify the code to use a different key for each set of segments(i.e. if 10 TS files has been generated then rotate the key) or you can generate a new encryption key at every periodic time(i.e. every 10 seconds).
189+
190+
First you need to create a listener class that is extended by `Evenement\EventEmitter` and is implemented by `Alchemy\BinaryDriver\Listeners\ListenerInterface`. This allows you to get all lines of FFmpeg logs regardless of the type of them.
191+
``` php
192+
class LineListener extends Evenement\EventEmitter implements Alchemy\BinaryDriver\Listeners\ListenerInterface
193+
{
194+
private $event;
195+
196+
public function __construct($event = 'line')
197+
{
198+
$this->event = $event;
199+
}
200+
201+
public function handle($type, $data)
202+
{
203+
foreach (explode(PHP_EOL, $data) as $line) {
204+
$this->emit($this->event, [$line]);
205+
}
206+
}
207+
208+
public function forwardedEvents()
209+
{
210+
return [$this->event];
211+
}
212+
}
213+
```
214+
215+
You can also use `Alchemy\BinaryDriver\Listeners\DebugListener` object instead and skip this step.
216+
217+
After that, you should pass an instance of the object to the `listen` method in the `FFMpegDriver` object and get the line of FFmpeg logs. When a new TS file has been created, you should generate a new encryption key and update the key info file.
218+
``` php
219+
$save_to = "/home/public_html/PATH_TO_KEY_DIRECTORY/key_rotation";
220+
$url = "https://www.aminyazdanpanah.com/PATH_TO_KEY_DIRECTORY/key_rotation";
221+
$key_info_path = Streaming\File::tmp();
222+
$ts_files = [];
223+
224+
$update_key_info_file = function ($number) use ($save_to, $url, $key_info_path) {
225+
$u_key_path = $save_to . "_" . $number;
226+
$u_url = $url . "_" . $number;
227+
Streaming\HLSKeyInfo::generate($u_key_path, $u_url, $key_info_path);
228+
};
229+
230+
$ffmpeg = Streaming\FFMpeg::create();
231+
$ffmpeg->getFFMpegDriver()->listen(new LineListener);
232+
$ffmpeg->getFFMpegDriver()->on('line', function ($line) use (&$ts_files, $update_key_info_file) {
233+
// Check if a new TS file is generated or not
234+
if(false !== strpos($line, ".ts' for writing") && !in_array($line, $ts_files)){
235+
// A new TS file has been created! Generate a new encryption key and update the key info file
236+
array_push($ts_files, $line);
237+
call_user_func($update_key_info_file, count($ts_files));
238+
}
239+
});
240+
241+
$video = $ffmpeg->open("path/to/video");
242+
243+
$video->HLS()
244+
->encryption($save_to, $url, $key_info_path)
245+
->setAdditionalParams(['-hls_flags', 'periodic_rekey'])
246+
->X264()
247+
->autoGenerateRepresentations([240])
248+
->save('c:\\test\\encryption_videos\\hls-stream.m3u8');
249+
```
250+
182251
**NOTE:** It is very important to protect your key on your website using a token or a session/cookie(**It is highly recommended**).
183252

253+
**NOTE:** However HLS supports AES encryption, that you can encrypt your streams, it is not a full DRM solution. If you want to use a full DRM solution, I recommend to try **[FairPlay Streaming](https://developer.apple.com/streaming/fps/)** solution which then securely exchange keys, and protect playback on devices.
184254

185255
### Transcoding
186256
A format can also extend `FFMpeg\Format\ProgressableInterface` to get realtime information about the transcoding.
@@ -290,7 +360,7 @@ $stream = $ffmpeg->open('https://www.aminyazdanpanah.com/PATH/TO/DASH-MANIFEST.M
290360
$stream->HLS()
291361
->X264()
292362
->autoGenerateRepresentations([720, 360])
293-
->save('/var/www/media/hls-stream.mpd');
363+
->save('/var/www/media/hls-stream.m3u8');
294364
```
295365

296366
#### 3. Stream(DASH or HLS) To File

src/Clouds/Cloud.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Cloud
2424
*/
2525
public static function uploadDirectory(array $clouds, string $tmp_dir): void
2626
{
27-
if (!is_array(current($clouds))) {
27+
if (isset($clouds['cloud'])) {
2828
$clouds = [$clouds];
2929
}
3030

@@ -40,7 +40,7 @@ public static function uploadDirectory(array $clouds, string $tmp_dir): void
4040
*/
4141
public static function download(array $cloud, string $save_to = null): array
4242
{
43-
list($save_to, $is_tmp) = $save_to ? [$save_to, false] : [File::tmpFile(), true];
43+
list($save_to, $is_tmp) = $save_to ? [$save_to, false] : [File::tmp(), true];
4444
static::transfer($cloud, __FUNCTION__, $save_to);
4545

4646
return [$save_to, $is_tmp];

src/Export.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public function getPathInfo(): array
7575
private function moveTmp(?string $path): void
7676
{
7777
if ($this->isTmpDir() && !is_null($path)) {
78-
File::moveDir($this->tmp_dir, dirname($path));
78+
File::move($this->tmp_dir, dirname($path));
7979
$this->path_info = pathinfo($path);
8080
$this->tmp_dir = '';
8181
}

src/File.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static function directorySize(string $dir): int
5555
/**
5656
* @return string
5757
*/
58-
public static function tmpFile(): string
58+
public static function tmp(): string
5959
{
6060
return tempnam(static::tmpDirPath(), 'stream');
6161
}
@@ -90,7 +90,7 @@ private static function tmpDirPath(): string
9090
* @param string $src
9191
* @param string $dst
9292
*/
93-
public static function moveDir(string $src, string $dst): void
93+
public static function move(string $src, string $dst): void
9494
{
9595
static::filesystem('mirror', [$src, $dst]);
9696
static::remove($src);

src/HLS.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,13 @@ public function setHlsKeyInfoFile(string $hls_key_info_file): HLS
107107
/**
108108
* @param string $save_to
109109
* @param string $url
110+
* @param string|null $key_info_path
110111
* @param int $length
111112
* @return HLS
112113
*/
113-
public function encryption(string $save_to, string $url, int $length = 16): HLS
114+
public function encryption(string $save_to, string $url, string $key_info_path = null, int $length = 16): HLS
114115
{
115-
$this->setHlsKeyInfoFile(HLSKeyInfo::generate($save_to, $url, $length));
116+
$this->setHlsKeyInfoFile(HLSKeyInfo::generate($save_to, $url, $key_info_path, $length));
116117
$this->tmp_key_info_file = true;
117118

118119
return $this;
@@ -199,6 +200,6 @@ protected function getPath(): string
199200
*/
200201
private function savePlaylist($path, $reps)
201202
{
202-
HLSPlaylist::save($this->master_playlist ?? $path, $reps, pathinfo($path, PATHINFO_FILENAME));
203+
HLSPlaylist::save($this->master_playlist ?? $path, $reps, pathinfo($path, PATHINFO_FILENAME));
203204
}
204205
}

src/HLSKeyInfo.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,28 @@
1818
class HLSKeyInfo
1919
{
2020
/**
21-
* @param $url
22-
* @param $path
21+
* @param string $path
22+
* @param string $url
23+
* @param string $key_info_path
2324
* @param int $length
2425
* @return string
2526
*/
26-
public static function generate(string $path, string $url, int $length = 16): string
27+
public static function generate(string $path, string $url, string $key_info_path = null, int $length = 16): string
2728
{
2829
if (!extension_loaded('openssl')) {
2930
throw new RuntimeException('OpenSSL is not installed.');
3031
}
3132

3233
File::makeDir(dirname($path));
3334
file_put_contents($path, openssl_random_pseudo_bytes($length));
34-
file_put_contents($path_f = File::tmpFile(), implode(PHP_EOL, [$url, $path, bin2hex(openssl_random_pseudo_bytes($length))]));
35+
36+
file_put_contents(
37+
$path_f = $key_info_path ?? File::tmp(),
38+
implode(
39+
PHP_EOL,
40+
[$url, $path, bin2hex(openssl_random_pseudo_bytes($length))]
41+
)
42+
);
3543

3644
return $path_f;
3745
}

tests/FileManagerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function testMakeDir()
2525

2626
public function testTmp()
2727
{
28-
$tmp_file = File::tmpFile();
28+
$tmp_file = File::tmp();
2929
$tmp_dir = File::tmpDir();
3030

3131
$this->assertIsString($tmp_file);

0 commit comments

Comments
 (0)