axiomatic-systems / Bento4

Full-featured MP4 format, MPEG DASH, HLS, CMAF SDK and tools
http://www.bento4.com
1.96k stars 478 forks source link

Kinda lost with encryption and playback #726

Open ghost opened 2 years ago

ghost commented 2 years ago

Hello,

I'm not sure where to get started. I want to protect my HLS streams using DRM. The problem is that I'm not able to find out what scheme to use to protect my media. Currently, all HLS streams getting packed as fragmented mp4 files (.m4s segments), where I have the following codecs in use: AVC, HEVC, AAC, HE-AAC, HE-AAC-V2 and E-AC3.

I've now checked onto many JS players and either they don't support fmp4 with encryption or they don't support fmp4 at all. The problem for me here is: If I can't play fmp4 segments I also can't play HEVC nor 4K nor HDR content ...

Can somebody maybe share his experience from where to get started now?

Thanks in advance

lfaureyt commented 2 years ago

If you're targetting Safari browser on macOS devices as I suspect, then I guess you're aware that you should use Fairplay Streaming DRM with MPEG CBCS encryption. Have you tried rx-player ? It does support Fairplay-protected HLS contents with fragmented mp4 in directfile mode. https://github.com/canalplus/rx-player

ghost commented 2 years ago

@lfaureyt where do you see that it support HLS ? I only see DASH and SmoothStream... Maybe I simply don't understand what directfile means here. Can you explain.

My focus is currently more on FireFox/Chrome and Widevine. Apple is just optional for me.

ghost commented 2 years ago

This is what I currently do to encrypt streams and push the keys to an S3 bucket. I basically generate a new key for each track and encrypt each track with a new key. But this isn't widevine nor FairPlay, its just no real protection at all. So I first wanted to know what I have to do with mp4encrypt to get my Streams packaged for WideVine in the first instance. If I know that, I can move forward and think about how to implement the key exchange. I will most likely write a very own DRM service backend for this.

        def encrypt_stream(job, files_obj):
            crypto_tracks = []
            job_dir = job['config']['output_dir']
            for root_dir, sub_dirs, files in os.walk(job_dir):
                for track_dir in sub_dirs:
                    if not track_dir.startswith('s-'):  # Exclude subtitle tracks
                        crypto_tracks.append(os.path.join(root_dir, track_dir))

            for index, track in enumerate(crypto_tracks):
                track_init_segment = track + '/' + ''.join([init_segment for init_segment in os.listdir(track + '/') if init_segment.startswith("init-")])
                track_playlist = track + '/' + ''.join([track_playlist for track_playlist in os.listdir(track + '/') if track_playlist.startswith("master.m3u8")])
                key_id = ''.join(random.choices(string.ascii_letters, k=64))
                enc_key = secrets.token_urlsafe(32)
                enc_iv = secrets.token_urlsafe(16)
                enc_packed = str(f'{index}:{enc_key}:{enc_iv}')
                ext_x_key = f'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="https://key.example.com/license/{files_obj}/{key_id}_{index}.key",IV={enc_iv}'
                for segment in os.listdir(track):
                    full_segment_path = f'{str(track)}/{str(segment)}'
                    if segment.endswith('.m4s') and not segment.startswith('init-'):
                        exec_command(
                            f'{options["path_mp4encrypt"]} '
                            f'--method MPEG-CBCS '
                            f'--fragments-info "{track_init_segment}" "{full_segment_path}" "{full_segment_path}" '
                            f'--key {enc_packed}'
                        )

                with open(track_playlist, 'r') as f:
                    lines = f.readlines()
                with open(track_playlist, 'w') as f:
                    for line in lines:
                        if line.startswith('#EXT-X-MEDIA'):
                            f.write(ext_x_key + '\n')
                        f.write(line)
                key = f'{job_dir}/{key_id}_{index}.key'
                with open(key, 'w') as f:
                    f.write(enc_key)
                if os.path.isfile(f'{job_dir}/{key_id}_{index}.key'):
                    placement_path = str(files_obj + '/' + f'{key_id}_{index}.key')
                    key_path_pair = (key, placement_path)
                    while True:
                        try:
                            libera_drm_resource.upload_file(*key_path_pair)
                            libera_client.head_object(Bucket=resource_object.drm_bucket,
                                                      Key=placement_path,
                                                      ChecksumMode='ENABLED')
                            break
                        except ClientError as e:
                            print(f'Integrity check failed with: {e}, DRM key Upload will be retried until completion!')
                            time.sleep(1)
                    os.remove(key)
lfaureyt commented 2 years ago

See documentation for the directfile transport option of the loadVideo method here: https://developers.canal-plus.com/rx-player/doc/api/Loading_a_Content.html

In the case of Safari, the ABR playback logic is built into the native HLS/FPS player: there is no MSE support, hence the need to use the directfile mode (kinda makes the use of a sophisticated ABR player like rx-player less interesting though).

ghost commented 2 years ago

@lfaureyt It's nice to see that directfile works with Safari on OS X as it's currently not working at all using VideoJS. Apple seems to have changed something with their last Safari release, and to me, it seems they only support the Native HLS playback. Anyways, can you give some advice onto the packaging process if I want to use WideVine in the end? Does SAMPLE-AES-CTR makes sense? What --method to use with mp4encrypt?