Created
September 2, 2020 21:19
-
-
Save krzemienski/59ada206f0763db17bf33f5f8702eeb7 to your computer and use it in GitHub Desktop.
Packaging multi codec DASH and HLS with cenc and cbcs encryption for widevine, playready, and fairplay w/ shaka & bento4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def package_local_targets(input_dir, output_dir): | |
trace = 'package_local_targets' | |
os.chdir(input_dir) | |
try: | |
print(f"{trace}: create directory {output_dir}") | |
os.makedirs(output_dir) | |
except FileExistsError: | |
print(f"delete existing {output_dir}") | |
shutil.rmtree(output_dir) | |
print(f"deleted directory {output_dir}") | |
os.makedirs(output_dir) | |
print(f"created directory {output_dir}") | |
package_dash(input_dir=input_dir, output_dir=output_dir) | |
package_hls(input_dir=input_dir, output_dir=output_dir) | |
def package_dash(input_dir, output_dir): | |
trace = 'package_local_targets_dash' | |
os.chdir(input_dir) | |
try: | |
print(f"{trace}: create directory {output_dir}") | |
os.makedirs(output_dir) | |
except FileExistsError: | |
print(f"{trace}: found existing {output_dir}") | |
pkg_commands = [] | |
packaging_cmd_to_execute = 'packager ' | |
seperator = ' ' | |
video_audio_hevc_avc_transcode_targets = glob.glob(f'{input_dir}/*.mp4') | |
sidecar_targets = glob.glob(f'{input_dir}/*.vtt') | |
packager_video_inputs_outputs_hevc_targets = [] | |
mpd_generator_paths_hevc = [] | |
packager_video_inputs_outputs_avc_targets = [] | |
mpd_generator_paths_avc = [] | |
packager_audio_inputs_outputs_targets = [] | |
mpd_generator_paths_audio = [] | |
packager_subtitle_inputs_outputs_webvtt_targets = [] | |
mpd_generator_paths_subtitle = [] | |
for mp4_file_name in video_audio_hevc_avc_transcode_targets: | |
print(f"{trace}: mp4 {mp4_file_name}") | |
if ('hev1' in mp4_file_name or 'h265' in mp4_file_name) and '.vtt' not in mp4_file_name: | |
mpd_generator_paths_hevc.append(f"{output_dir}/{Path(mp4_file_name).stem}"+"_cenc_protected.mp4.media_info") | |
packager_video_inputs_outputs_hevc_targets.append(format_packager_in_target_audio_video(mp4_file_name, 'video', output_dir, 'MEDIA')) | |
elif ('avc1' in mp4_file_name or 'h264' in mp4_file_name) and ('.vtt' not in mp4_file_name and '_600_' not in mp4_file_name): | |
mpd_generator_paths_avc.append(f"{output_dir}/{Path(mp4_file_name).stem}"+"_cenc_protected.mp4.media_info") | |
packager_video_inputs_outputs_avc_targets.append(format_packager_in_target_audio_video(mp4_file_name, 'video', output_dir, 'MEDIA')) | |
elif 'audio' in mp4_file_name and '.vtt' not in mp4_file_name: | |
mpd_generator_paths_audio.append(f"{output_dir}/{Path(mp4_file_name).stem}"+"_cenc_protected.mp4.media_info") | |
packager_audio_inputs_outputs_targets.append(format_packager_in_target_audio_video(mp4_file_name, 'audio', output_dir, 'MEDIA')) | |
for sidecar in sidecar_targets: | |
##WHEN WE GET TO THE POINT WE ACTUALY KNOW THE LANGUAGE VIA PROVIDER OR ANALYZING PROPERLY WE JUST ADD ANOTHER PARAM | |
##FOR NOW WE WILL JUST ASSUME AND DEFAULT ENGLISH | |
mpd_generator_paths_subtitle.append(f"{output_dir}/{Path(sidecar).stem}"+".vtt.media_info") | |
packager_subtitle_inputs_outputs_webvtt_targets.append(format_packager_in_target_text(sidecar, 'text', output_dir)) | |
packager_audio_videoh264_videoh265_inputs_outputs = seperator.join(packager_video_inputs_outputs_avc_targets) + " " + seperator.join(packager_video_inputs_outputs_hevc_targets) + " " + seperator.join(packager_audio_inputs_outputs_targets) + " " + seperator.join(packager_subtitle_inputs_outputs_webvtt_targets) | |
##APPEND ALL INPUTS TO THE PACKAGING COMMAND | |
packaging_cmd_input_options = packaging_cmd_to_execute + packager_audio_videoh264_videoh265_inputs_outputs | |
#print(f"{trace}: {packaging_cmd_to_execute}") | |
##LAST BUT NOT LEAST WE ADD IN THE ENCRYPTION KEYS TO APPLY DRM TO THE RESULTING PACKAING | |
enable_media_info_output = '--output_media_info' | |
flag_raw_encryption = '--enable_raw_key_encryption' | |
key_and_keyid = f"--keys label={DRM_LABEL_DEFAULT}:key_id={KEY_ID}:key={KEY}" | |
protections = '--protection_systems Widevine,PlayReady' | |
fragment_duration = '' | |
mpd_out = f"--mpd_output {output_dir}/master-avc-hevc.mpd" | |
packaging_cmd_to_execute = f"{packaging_cmd_input_options} {enable_media_info_output} {fragment_duration} {flag_raw_encryption} {key_and_keyid} {protections} {mpd_out}" | |
pkg_commands.append(packaging_cmd_to_execute) | |
#### DUE TO THE NEED FOR DIFFERENT MANIFEST SFOR ALL TYPES OF PLAYER WE WILL GO AHEAD AND REFERENCE THE SAME ENCRYPTED MEDIA BUT | |
#### A FEW DIFFERENT VAARIENTS ON THE PLAYLISTS | |
manifest_seperator = "," | |
infiles_hevc = manifest_seperator.join(mpd_generator_paths_hevc) | |
infiles_avc = manifest_seperator.join(mpd_generator_paths_avc) | |
infiles_audio = manifest_seperator.join(mpd_generator_paths_audio) | |
infiles_text = manifest_seperator.join(mpd_generator_paths_subtitle) | |
build_manifest_h264 = f"/packager/utils/mpd_generator --input {infiles_avc},{infiles_audio},{infiles_text} --output {output_dir}/master.mpd" | |
pkg_commands.append(build_manifest_h264) | |
build_manifest_h265 = f"/packager/utils/mpd_generator --input {infiles_hevc},{infiles_audio},{infiles_text} --output {output_dir}/master-hevc.mpd" | |
pkg_commands.append(build_manifest_h265) | |
for pkg_cmd in pkg_commands: | |
try: | |
##EXECUTE THE PACKAING AND ENCRYPTION COMMAND | |
print(f"{trace}: package_cmd to be executed {pkg_cmd}") | |
with subprocess.Popen(pkg_cmd, env=my_env, shell=True, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) as p: | |
for line in p.stdout: | |
print(f"{trace}: {line}", end='') | |
except (OSError) as exception: | |
print('Exception occured: ' + str(exception)) | |
print(f'{trace} failed') | |
return False | |
# no exception was raised | |
print(f'{trace} - all packaging finished') | |
return True | |
def package_hls(input_dir, output_dir): | |
trace = 'package_hls' | |
seperator = ' ' | |
video_audio_hevc_avc_transcode_targets = glob.glob(f'{input_dir}/*.mp4') | |
subtitle_targets = glob.glob(f'{input_dir}/*.vtt') | |
video_transcode_targets_hevc = [] | |
video_transcode_targets_avc = [] | |
audio_transcode_targets = [] | |
for mp4_file_name in video_audio_hevc_avc_transcode_targets: | |
print(f"{trace}: mp4 {mp4_file_name}") | |
if 'hev1' in mp4_file_name or 'h265' in mp4_file_name and '.vtt' not in mp4_file_name: | |
video_transcode_targets_hevc.append(mp4_file_name) | |
elif ('avc1' in mp4_file_name or 'h264' in mp4_file_name) and '.vtt' not in mp4_file_name: | |
video_transcode_targets_avc.append(mp4_file_name) | |
elif 'audio' in mp4_file_name and '.vtt' not in mp4_file_name: | |
audio_transcode_targets.append(mp4_file_name) | |
video_hevc_for_packaging = seperator.join(video_transcode_targets_hevc) | |
print(f"{trace}: video_hevc_for_packaging {video_hevc_for_packaging}") | |
video_avc_for_packaging = seperator.join(video_transcode_targets_avc) | |
print(f"{trace}: video_avc_for_packaging {video_avc_for_packaging}") | |
audio_for_packaging = seperator.join(audio_transcode_targets) | |
print(f"{trace}: audio_for_packaging {audio_for_packaging}") | |
enriched_subtitle_data = enrich_media_inputs(subtitle_targets, 'webvtt', 'eng') | |
subtitle_targets_for_packaging = seperator.join(enriched_subtitle_data) | |
print(f"{trace}: subtitle_targets_for_packaging {subtitle_targets_for_packaging}") | |
pkg_commands = [] | |
package_cmd_hls_all_codecs = f"python3 /opt/bento4/utils/mp4-dash.py --hls --no-split --use-segment-timeline \ | |
--encryption-cenc-scheme=cbcs --encryption-key={KEY_ID}:{KEY}:{KEY_ID} \ | |
--fairplay-key-uri skd://{KEY_ID} --widevine --widevine-header=#BASE64PSSH \ | |
--playready-version=4.3 --playready --playready-header=LA_URL=http://pr-keyos.licensekeyserver.com/core/rightsmanager.asmx \ | |
--output-dir {output_dir} --mpd-name master-cbcs.mpd --hls-master-playlist-name master.m3u8 \ | |
--language-map=und:eng --media-prefix cbcs_protected --verbose --subtitles --debug -f \ | |
--profiles on-demand {video_avc_for_packaging} {video_hevc_for_packaging} {audio_for_packaging}" | |
pkg_commands.append(package_cmd_hls_all_codecs) | |
print(f"{trace}: package_cmds to be executed") | |
for pkg_cmd in pkg_commands: | |
try: | |
## MAKE THE LAST MINUTE CONCATS FOR SUBTITLES IF WE HAD THEM ORIGINALLY | |
if subtitle_targets: | |
pkg_cmd = pkg_cmd + f" {subtitle_targets_for_packaging}" | |
##EXECUTE THE PACKAING AND ENCRYPTION COMMAND | |
print(f"{trace}: package_cmd to be executed {pkg_cmd}") | |
with subprocess.Popen(pkg_cmd, env=my_env, shell=True, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) as p: | |
for line in p.stdout: | |
print(f"{trace}: {line}") | |
except (OSError) as exception: | |
print('Exception occured: ' + str(exception)) | |
print(f'{trace} failed') | |
return False | |
# no exception was raised | |
print(f'{trace} - all packaging finished') | |
return True | |
def enrich_media_inputs(input_paths, input_format, language): | |
enriched_inputs = [] | |
attribute_tag = f"\[+format={input_format},+language={language}\]" | |
for media_input in input_paths: | |
enriched_media_input = attribute_tag + media_input | |
enriched_inputs.append(enriched_media_input) | |
return enriched_inputs | |
def format_packager_in_target_audio_video(target_path, target_format, target_output_path, target_drm_label): | |
##BUILD COMMAND OUT FOR HOW SHAKA PACKAGER EXPECTS IT PER MEDIA | |
print(f"{format_packager_in_target_audio_video}: {target_path} {target_format} {target_output_path} {target_drm_label}") | |
defined_input_for_target = f'in={target_path},stream={target_format},output={target_output_path}/{Path(target_path).stem}_cenc_protected.mp4,drm_label={target_drm_label}' | |
return defined_input_for_target | |
def format_packager_in_target_text(target_path, target_format, target_output_path, target_lang_label = 'eng'): | |
##BUILD COMMAND OUT FOR HOW SHAKA PACKAGER EXPECTS IT PER MEDIA | |
print(f"{format_packager_in_target_text}: {target_path} {target_format} {target_output_path} {target_lang_label}") | |
defined_input_for_target = f'in={target_path},stream={target_format},output={target_output_path}/{Path(target_path).stem}.vtt,lang={target_lang_label}' | |
return defined_input_for_target |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment