astrXbian/.install/.kodi/addons/script.module.inputstreamhe.../lib/inputstreamhelper/widevine/arm.py

303 lines
13 KiB
Python

# -*- coding: utf-8 -*-
# MIT License (see LICENSE.txt or https://opensource.org/licenses/MIT)
"""Implements ARM specific widevine functions"""
from __future__ import absolute_import, division, unicode_literals
import os
import json
from time import time
from .. import config
from ..kodiutils import browsesingle, copy, exists, localize, log, mkdir, ok_dialog, open_file, progress_dialog, yesno_dialog
from ..utils import cmd_exists, diskspace, http_download, http_get, run_cmd, sizeof_fmt, store, system_os, temp_path, update_temp_path
from ..unicodes import compat_path, to_unicode
from .arm_chromeos import ChromeOSImage
def mnt_path(make=True):
"""Return mount path, usually ~/.kodi/userdata/addon_data/script.module.inputstreamhelper/temp/mnt/"""
mount_path = os.path.join(temp_path(), 'mnt', '')
if make and not exists(mount_path):
mkdir(mount_path)
return mount_path
def check_loop():
"""Check if loop module needs to be loaded into system."""
if not run_cmd(['modinfo', 'loop'])['success']:
log(0, 'loop is built in the kernel.')
return True # assume loop is built in the kernel
store('modprobe_loop', True)
cmd = ['modprobe', '-q', 'loop']
output = run_cmd(cmd, sudo=True)
return output['success']
def set_loop_dev():
"""Set an unused loop device that's available for use."""
cmd = ['losetup', '-f']
output = run_cmd(cmd, sudo=False)
if output['success']:
store('loop_dev', output['output'].strip())
log(0, 'Found free loop device: {device}', device=store('loop_dev'))
return True
log(4, 'Failed to find free loop device.')
return False
def losetup(bin_path):
"""Setup Chrome OS loop device."""
cos_offset = str(ChromeOSImage(bin_path).chromeos_offset())
cmd = ['losetup', '-o', cos_offset, store('loop_dev'), bin_path]
output = run_cmd(cmd, sudo=True)
if output['success']:
store('attached_loop_dev', True)
return True
return False
def mnt_loop_dev():
"""Mount loop device to mnt_path()"""
cmd = ['mount', '-t', 'ext2', '-o', 'ro', store('loop_dev'), compat_path(mnt_path())]
output = run_cmd(cmd, sudo=True)
if output['success']:
return True
return False
def select_best_chromeos_image(devices):
"""Finds the newest and smallest of the ChromeOS images given"""
log(0, 'Find best ARM image to use from the Chrome OS recovery.json')
best = None
for device in devices:
# Select ARM hardware only
for arm_hwid in config.CHROMEOS_RECOVERY_ARM_HWIDS:
if '^{0} '.format(arm_hwid) in device['hwidmatch']:
hwid = arm_hwid
break # We found an ARM device, rejoice !
else:
continue # Not ARM, skip this device
device['hwid'] = hwid
# Select the first ARM device
if best is None:
best = device
continue # Go to the next device
# Skip identical hwid
if hwid == best['hwid']:
continue
# Select the newest version
from distutils.version import LooseVersion # pylint: disable=import-error,no-name-in-module,useless-suppression
if LooseVersion(device['version']) > LooseVersion(best['version']):
log(0, '{device[hwid]} ({device[version]}) is newer than {best[hwid]} ({best[version]})',
device=device,
best=best)
best = device
# Select the smallest image (disk space requirement)
elif LooseVersion(device['version']) == LooseVersion(best['version']):
if int(device['filesize']) + int(device['zipfilesize']) < int(best['filesize']) + int(best['zipfilesize']):
log(0, '{device[hwid]} ({device_size}) is smaller than {best[hwid]} ({best_size})',
device=device,
best=best,
device_size=int(device['filesize']) + int(device['zipfilesize']),
best_size=int(best['filesize']) + int(best['zipfilesize']))
best = device
return best
def chromeos_config():
"""Reads the Chrome OS recovery configuration"""
return json.loads(http_get(config.CHROMEOS_RECOVERY_URL))
def install_widevine_arm(backup_path):
"""Installs Widevine CDM on ARM-based architectures."""
devices = chromeos_config()
arm_device = select_best_chromeos_image(devices)
if arm_device is None:
log(4, 'We could not find an ARM device in the Chrome OS recovery.json')
ok_dialog(localize(30004), localize(30005))
return False
# Estimated required disk space: takes into account an extra 20 MiB buffer
required_diskspace = 20971520 + int(arm_device['zipfilesize'])
if yesno_dialog(localize(30001), # Due to distributing issues, this takes a long time
localize(30006, diskspace=sizeof_fmt(required_diskspace))):
if system_os() != 'Linux':
ok_dialog(localize(30004), localize(30019, os=system_os()))
return False
while required_diskspace >= diskspace():
if yesno_dialog(localize(30004), localize(30055)): # Not enough space, alternative path?
update_temp_path(browsesingle(3, localize(30909), 'files')) # Temporary path
continue
ok_dialog(localize(30004), # Not enough free disk space
localize(30018, diskspace=sizeof_fmt(required_diskspace)))
return False
log(2, 'Downloading best ChromeOS image for Widevine: {hwid} ({version})'.format(**arm_device))
url = arm_device['url']
downloaded = http_download(url, message=localize(30022), checksum=arm_device['sha1'], hash_alg='sha1',
dl_size=int(arm_device['zipfilesize'])) # Downloading the recovery image
if downloaded:
progress = progress_dialog()
progress.create(heading=localize(30043), message=localize(30044)) # Extracting Widevine CDM
extracted = ChromeOSImage(store('download_path')).extract_file(
filename=config.WIDEVINE_CDM_FILENAME[system_os()],
extract_path=os.path.join(backup_path, arm_device['version']),
progress=progress)
if not extracted:
log(3, 'Directly extracting widevine from the zip failed, using legacy method.')
progress.close()
if yesno_dialog(heading=localize(30004),
message='{line1}\n{line2}\n{line3}'.format(
line1=localize(30016),
line2=localize(30830, url=config.ISSUE_URL),
line3=localize(30059))):
return install_wv_arm_legacy(backup_path)
return False
recovery_file = os.path.join(backup_path, arm_device['version'], os.path.basename(config.CHROMEOS_RECOVERY_URL))
config_file = os.path.join(backup_path, arm_device['version'], 'config.json')
with open_file(recovery_file, 'w') as reco_file:
reco_file.write(json.dumps(devices, indent=4))
with open_file(config_file, 'w') as conf_file:
conf_file.write(json.dumps(arm_device))
return (progress, arm_device['version'])
return False
def install_wv_arm_legacy(backup_path):
"""Legacy method to install Widevine CDM on ARM-based architectures."""
root_cmds = ['mount', 'umount', 'losetup', 'modprobe']
devices = chromeos_config()
arm_device = select_best_chromeos_image(devices)
if arm_device is None:
log(4, 'We could not find an ARM device in the Chrome OS recovery.json')
ok_dialog(localize(30004), localize(30005))
return False
# Estimated required disk space: takes into account an extra 20 MiB buffer
required_diskspace = 20971520 + int(arm_device['zipfilesize']) + int(arm_device['filesize'])
if yesno_dialog(localize(30001), # Due to distributing issues, this takes a long time
localize(30006, diskspace=sizeof_fmt(required_diskspace))):
if system_os() != 'Linux':
ok_dialog(localize(30004), localize(30019, os=system_os()))
return False
while required_diskspace >= diskspace():
if yesno_dialog(localize(30004), localize(30055)): # Not enough space, alternative path?
update_temp_path(browsesingle(3, localize(30909), 'files')) # Temporary path
continue
ok_dialog(localize(30004), # Not enough free disk space
localize(30018, diskspace=sizeof_fmt(required_diskspace)))
return False
if not cmd_exists('fdisk') and not cmd_exists('parted'):
ok_dialog(localize(30004), localize(30020, command1='fdisk', command2='parted')) # Commands are missing
return False
if not cmd_exists('mount'):
ok_dialog(localize(30004), localize(30021, command='mount')) # Mount command is missing
return False
if not cmd_exists('losetup'):
ok_dialog(localize(30004), localize(30021, command='losetup')) # Losetup command is missing
return False
if os.getuid() != 0 and not yesno_dialog(localize(30001), # Ask for permission to run cmds as root
localize(30030, cmds=', '.join(root_cmds)),
nolabel=localize(30028), yeslabel=localize(30027)):
return False
url = arm_device['url']
progress = progress_dialog()
progress.create(heading=localize(30043), message=localize(30044)) # Extracting Widevine CDM
bin_filename = url.split('/')[-1].replace('.zip', '')
bin_path = compat_path(os.path.join(temp_path(), bin_filename))
starttime = time()
progress.update(
0,
message='{line1}\n{line2}\n{line3}'.format(
line1=localize(30045), # Uncompressing image
line2=localize(30058, mins=0, secs=0), # Time remaining
line3=localize(30047)) # Please do not interrupt this process
)
from zipfile import ZipFile
with ZipFile(compat_path(store('download_path'))) as zip_obj:
bin_size = zip_obj.getinfo(bin_filename).file_size
chunksize = 1024**2
with zip_obj.open(bin_filename) as member:
with open(bin_path, 'wb') as bin_file:
bytes_to_read = bin_size
while bytes_to_read > 0:
chunk = member.read(chunksize)
bytes_to_read -= chunksize
bin_file.write(chunk)
percent = 100 * (1 - bytes_to_read / bin_size) - 5
time_left = int(bytes_to_read * (time() - starttime) / (bin_size - bytes_to_read))
progress.update(
int(percent),
message='{line1}\n{line2}\n{line3}'.format(
line1=localize(30045), # Uncompressing image
line2=localize(30058, mins=time_left // 60, secs=time_left % 60), # Time remaining
line3=localize(30047)) # Please do not interrupt this process
)
if check_loop() and set_loop_dev() and losetup(bin_path) and mnt_loop_dev():
progress.update(96, message=localize(30048)) # Extracting Widevine CDM
extract_widevine_from_img(os.path.join(backup_path, arm_device['version'], ''))
json_file = os.path.join(backup_path, arm_device['version'], os.path.basename(config.CHROMEOS_RECOVERY_URL))
with open_file(json_file, 'w') as config_file:
config_file.write(json.dumps(devices, indent=4))
return (progress, arm_device['version'])
progress.close()
return False
def extract_widevine_from_img(backup_path):
"""Extract the Widevine CDM binary from the mounted Chrome OS image"""
for root, _, files in os.walk(compat_path(mnt_path())):
if compat_path('libwidevinecdm.so') not in files:
continue
cdm_path = os.path.join(to_unicode(root), 'libwidevinecdm.so')
log(0, 'Found libwidevinecdm.so in {path}', path=cdm_path)
if not exists(backup_path):
mkdir(backup_path)
copy(cdm_path, os.path.join(backup_path, 'libwidevinecdm.so'))
return True
log(4, 'Failed to find Widevine CDM binary in Chrome OS image.')
return False
def unmount():
"""Unmount mountpoint if mounted"""
while os.path.ismount(compat_path(mnt_path(make=False))):
log(0, 'Unmount {mountpoint}', mountpoint=mnt_path())
umount_output = run_cmd(['umount', compat_path(mnt_path())], sudo=True)
if not umount_output['success']:
break