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

192 lines
6.7 KiB
Python

# -*- coding: utf-8 -*-
# MIT License (see LICENSE.txt or https://opensource.org/licenses/MIT)
"""Implements generic widevine functions used across architectures"""
from __future__ import absolute_import, division, unicode_literals
import os
from time import time
from .. import config
from ..kodiutils import addon_profile, exists, get_setting_int, listdir, localize, log, mkdirs, ok_dialog, open_file, set_setting, translate_path, yesno_dialog
from ..utils import arch, cmd_exists, hardlink, http_download, http_get, remove_tree, run_cmd, store, system_os
from ..unicodes import compat_path, to_unicode
def install_cdm_from_backup(version):
"""Copies files from specified backup version to cdm dir"""
filenames = listdir(os.path.join(backup_path(), version))
for filename in filenames:
backup_fpath = os.path.join(backup_path(), version, filename)
install_fpath = os.path.join(ia_cdm_path(), filename)
hardlink(backup_fpath, install_fpath)
log(0, 'Installed CDM version {version} from backup', version=version)
set_setting('last_modified', time())
remove_old_backups(backup_path())
def widevine_eula():
"""Displays the Widevine EULA and prompts user to accept it."""
cdm_version = latest_widevine_version(eula=True)
if 'x86' in arch():
cdm_os = config.WIDEVINE_OS_MAP[system_os()]
cdm_arch = config.WIDEVINE_ARCH_MAP_X86[arch()]
else: # grab the license from the x86 files
log(0, 'Acquiring Widevine EULA from x86 files.')
cdm_os = 'mac'
cdm_arch = 'x64'
url = config.WIDEVINE_DOWNLOAD_URL.format(version=cdm_version, os=cdm_os, arch=cdm_arch)
downloaded = http_download(url, message=localize(30025), background=True) # Acquiring EULA
if not downloaded:
return False
from zipfile import ZipFile
with ZipFile(compat_path(store('download_path'))) as archive:
with archive.open(config.WIDEVINE_LICENSE_FILE) as file_obj:
eula = file_obj.read().decode().strip().replace('\n', ' ')
return yesno_dialog(localize(30026), eula, nolabel=localize(30028), yeslabel=localize(30027)) # Widevine CDM EULA
def backup_path():
"""Return the path to the cdm backups"""
path = os.path.join(addon_profile(), 'backup', '')
if not exists(path):
mkdirs(path)
return path
def widevine_config_path():
"""Return the full path to the widevine or recovery config file"""
iacdm = ia_cdm_path()
if iacdm is None:
return None
if 'x86' in arch():
return os.path.join(iacdm, config.WIDEVINE_CONFIG_NAME)
return os.path.join(iacdm, 'config.json')
def load_widevine_config():
"""Load the widevine or recovery config in JSON format"""
from json import loads
if exists(widevine_config_path()):
with open_file(widevine_config_path(), 'r') as config_file:
return loads(config_file.read())
return None
def widevinecdm_path():
"""Get full Widevine CDM path"""
widevinecdm_filename = config.WIDEVINE_CDM_FILENAME[system_os()]
if widevinecdm_filename is None:
return None
if ia_cdm_path() is None:
return None
return os.path.join(ia_cdm_path(), widevinecdm_filename)
def has_widevinecdm():
"""Whether a Widevine CDM is installed on the system"""
if system_os() == 'Android': # Widevine CDM is built into Android
return True
widevinecdm = widevinecdm_path()
if widevinecdm is None:
return False
if not exists(widevinecdm):
log(3, 'Widevine CDM is not installed.')
return False
log(0, 'Found Widevine CDM at {path}', path=widevinecdm)
return True
def ia_cdm_path():
"""Return the specified CDM path for inputstream.adaptive, usually ~/.kodi/cdm"""
from xbmcaddon import Addon
try:
addon = Addon('inputstream.adaptive')
except RuntimeError:
return None
cdm_path = translate_path(os.path.join(to_unicode(addon.getSetting('DECRYPTERPATH')), ''))
if not exists(cdm_path):
mkdirs(cdm_path)
return cdm_path
def missing_widevine_libs():
"""Parses ldd output of libwidevinecdm.so and displays dialog if any depending libraries are missing."""
if system_os() != 'Linux': # this should only be needed for linux
return None
if cmd_exists('ldd'):
widevinecdm = widevinecdm_path()
if not os.access(widevinecdm, os.X_OK):
log(0, 'Changing {path} permissions to 744.', path=widevinecdm)
os.chmod(widevinecdm, 0o744)
missing_libs = []
cmd = ['ldd', widevinecdm]
output = run_cmd(cmd, sudo=False)
if output['success']:
for line in output['output'].splitlines():
if '=>' not in str(line):
continue
lib_path = str(line).strip().split('=>')
lib = lib_path[0].strip()
path = lib_path[1].strip()
if path == 'not found':
missing_libs.append(lib)
if missing_libs:
log(4, 'Widevine is missing the following libraries: {libs}', libs=missing_libs)
return missing_libs
log(0, 'There are no missing Widevine libraries! :-)')
return None
log(4, 'Failed to check for missing Widevine libraries.')
return None
def latest_widevine_version(eula=False):
"""Returns the latest available version of Widevine CDM/Chrome OS."""
if eula or 'x86' in arch():
url = config.WIDEVINE_VERSIONS_URL
versions = http_get(url)
return versions.split()[-1]
from .arm import chromeos_config, select_best_chromeos_image
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 ''
return arm_device['version']
def remove_old_backups(bpath):
"""Removes old Widevine backups, if number of allowed backups is exceeded"""
from distutils.version import LooseVersion # pylint: disable=import-error,no-name-in-module,useless-suppression
max_backups = get_setting_int('backups', 4)
versions = sorted([LooseVersion(version) for version in listdir(bpath)])
if len(versions) < 2:
return
installed_version = load_widevine_config()['version']
while len(versions) > max_backups + 1:
remove_version = str(versions[1] if versions[0] == LooseVersion(installed_version) else versions[0])
log(0, 'Removing oldest backup which is not installed: {version}', version=remove_version)
remove_tree(os.path.join(bpath, remove_version))
versions = sorted([LooseVersion(version) for version in listdir(bpath)])
return