def get_addon(self, drm=True): """Enable and get the inpustream.adaptive add-on Check if inputstream.adaptive is installed, attempt to install if not. """ addon = None req = { 'method': 'Addons.GetAddonDetails', 'params': { 'addonid': 'inputstream.adaptive', 'properties': ['enabled'] } } result = self._execute_json_rpc(**req) if not result: return False # error if 'error' in result: # not installed utils.log('inputstream.adaptive not currently installed') addon = self._install_addon() if not addon: return False # error installing else: # installed but not enabled. let's enable it. if result['result']['addon'].get('enabled') is False: utils.log('inputstream.adaptive not enabled, enabling...') self._enable_addon() addon = self._get_addon() return addon
def _execute_cdm_command(self, plat, filename, cdm_path, home_folder): command = config.UNARCHIVE_COMMAND[plat].format( filename=quote(filename), cdm_path=quote(cdm_path), wvcdm_filename=self._get_wvcdm_filename(), download_folder=quote(home_folder)) utils.log('executing command: {0}'.format(command)) output = os.popen(command).read() utils.log('command output: {0}'.format(output))
def _get_wvcdm(self): """Get the Widevine CDM library Win/Mac: download Chrome extension blob ~2MB and extract widevinecdm.dll Linux: download Chrome package ~50MB and extract libwidevinecdm.so Linux arm: download widevine package ~2MB from 3rd party host """ addon = self.get_addon() if not addon: utils.dialog('inputstream.adaptive not found' 'inputstream.adaptive add-on must be installed ' 'before installing widevide_cdm module') return if self._is_android(): utils.dialog('Not available', 'This module cannot be updated on Android') return cdm_paths = self._get_wvcdm_paths(addon) cdm_path = self._get_wvcdm_path(addon, cdm_paths) plat = self._get_platform() current_cdm_ver = self._get_wvcdm_current_ver() url = config.WIDEVINE_CDM_URL[plat].format(current_cdm_ver) filename = url.split('/')[-1] wv_cdm_fn = self._get_wvcdm_filename() if not os.path.isdir(cdm_path): utils.log('Creating directory: {0}'.format(cdm_path)) os.makedirs(cdm_path) cdm_fn = os.path.join(cdm_path, wv_cdm_fn) if os.path.isfile(cdm_fn): utils.log('Removing existing widevine_cdm: {0}'.format(cdm_fn)) os.remove(cdm_fn) home_folder = self._get_home_folder() download_path = os.path.join(home_folder, filename) if not self._progress_download(url, download_path, wv_cdm_fn): return dp = xbmcgui.DialogProgress() dp.create('Extracting {0}'.format(wv_cdm_fn), 'Extracting {0} from {1}'.format(wv_cdm_fn, filename)) dp.update(0) if self._is_windows(): self._unzip_windows_cdm(download_path, cdm_path) else: self._execute_cdm_command(plat, filename, cdm_path, home_folder) dp.close() # TODO(andy): Test it was actually successful. Can be cancelled utils.dialog( 'Success', '{0} successfully installed at {1}'.format( wv_cdm_fn, os.path.join(cdm_path, wv_cdm_fn))) return True
def _unzip_windows_cdm(self, zpath, cdm_path): """Extract windows widevinecdm.dll from downloaded zip""" cdm_fn = posixpath.join(cdm_path, self._get_wvcdm_filename()) utils.log('unzipping widevinecdm.dll from {0} to {1}' ''.format(zpath, cdm_fn)) with zipfile.ZipFile(zpath) as zf: with open(cdm_fn, 'wb') as f: data = zf.read(self._get_wvcdm_filename()) f.write(data) os.remove(zpath)
def _enable_addon(self): req = { 'method': 'Addons.SetAddonEnabled', 'params': { 'addonid': 'inputstream.adaptive', 'enabled': True } } result = self._execute_json_rpc(**req) if not result: utils.log('Failure in enabling inputstream.adaptive') return False return True
def _install_addon(self): try: # see if there's an installed repo that has it xbmc.executebuiltin('InstallAddon(inputstream.adaptive)', True) addon = self._get_addon() utils.log('inputstream.adaptive installed from repo') return addon except RuntimeError: utils.dialog( 'inputstream.adaptive not installed', 'inputstream.adaptive not installed. This ' 'addon now comes supplied with newer builds ' 'of Kodi 18 for Windows/Mac/LibreELEC/OSMC, ' 'and can be installed from most Linux package ' 'managers eg. "sudo apt install kodi-' 'inputstream-adaptive"')
def _progress_download(self, url, download_path, display_filename=None): """Progress download Download file in Kodi with progress bar """ utils.log('Downloading {0}'.format(url)) try: res = requests.get(url, stream=True, verify=False) res.raise_for_status() except requests.exceptions.HTTPError: utils.dialog('Download failed', 'HTTP ' + str(res.status_code) + ' error') return False except Exception as exc: utils.dialog('Download failed', 'Exception was: {0}'.format(exc)) return False total_length = float(res.headers.get('content-length')) dp = xbmcgui.DialogProgress() if not display_filename: display_filename = download_path.split()[-1] dp.create("Downloading {0}".format(display_filename), "Downloading File", url) with open(download_path, 'wb') as f: chunk_size = 1024 downloaded = 0 for chunk in res.iter_content(chunk_size=chunk_size): f.write(chunk) downloaded += len(chunk) percent = int(downloaded * 100 / total_length) if dp.iscanceled(): dp.close() res.close() dp.update(percent) utils.log('Download {0} bytes complete, saved in {1}'.format( int(total_length), download_path)) dp.close() return True
def test_log(self, mock_log, mock_addon): utils.log('foo') mock_log.assert_called_once_with('[Test Add-on v0.0.1] foo', level=xbmc.LOGNOTICE)
def check_inputstream(self, drm=True): """Check InputStream Adaptive is installed and ready Main function call to check all components required are available for DRM playback before setting the resolved URL in Kodi. drm -- set to false if you just want to check for inputstream.adaptive and not widevine components eg. HLS playback """ # DRM not supported if drm and not self._is_wv_drm_supported(): # TODO(andy): Store something in settings to prevent this message # appearing more than once? utils.dialog( 'Platform not supported', '{0} not supported for DRM playback. ' 'For more information, see our DRM FAQ at {1}' ''.format(self.get_platform_name(), config.DRM_INFO)) return False # Kodi version too old if drm and utils.get_kodi_major_version() < 18: utils.dialog( 'Kodi version not supported for DRM', 'This version of Kodi is not currently supported for viewing ' 'DRM encrypted content. Please upgrade to Kodi v18.') return False addon = self.get_addon() if not addon: utils.dialog( 'Missing inputstream.adaptive add-on', 'inputstream.adaptive VideoPlayer InputStream add-on not ' 'found or not enabled. This add-on is required to view DRM ' 'protected content.') return False utils.log('Found inputstream.adaptive version is {0}'.format( addon.getAddonInfo('version'))) # widevine built into android - not supported on 17 atm though if self._is_android(): utils.log('Running on Android') return True # checking for installation of inputstream.adaptive (eg HLS playback) if not drm: utils.log('DRM checking not requested') return True # only 32bit userspace supported for linux aarch64 no 64bit wvcdm if self._get_platform() == ('Linux', 'aarch64'): if self._get_kodi_arch() == '64bit': utils.dialog( '64 bit build for aarch64 not supported', 'A build of your OS that supports 32 bit userspace ' 'binaries is required for DRM playback. We recommend ' 'CoreELEC to support this.') cdm_fn = self._get_wvcdm_filename() cdm_paths = self._get_wvcdm_paths(addon) if not any(os.path.isfile(os.path.join(p, cdm_fn)) for p in cdm_paths): if utils.dialog_yn( 'Missing Widevine module', '{0} not found in any expected location.'.format(cdm_fn), 'Do you want to attempt downloading the missing ' 'Widevine CDM module to your system for DRM support?'): self._get_wvcdm() else: # TODO(andy): Ask to never attempt again return False return True
def _rename_windows_cdm(self, download_path, cdm_path): """Move windows widevinecdm.dll from download""" cdm_fn = posixpath.join(cdm_path, self._get_wvcdm_filename()) utils.log('moving widevinecdm.dll from {0} to {1}' ''.format(download_path, cdm_fn)) os.rename(download_path, cdm_fn)