def __init__(self, w=OVERLAY_WIDTH, h=OVERLAY_HEIGHT, *args, **kwargs): self.window = xbmcgui.Window(WINDOW_FULLSCREEN_VIDEO) viewport_w, viewport_h = self._get_skin_resolution() # Adjust size based on viewport, we are using 1080p coordinates w = int(w * viewport_w / VIEWPORT_WIDTH) h = int(h * viewport_h / VIEWPORT_HEIGHT) x = (viewport_w - w) / 2 y = (viewport_h - h) / 2 + int( ADDON.getSetting(id="overlay_status_offset")) self._shown = False self._text = "" self._label = xbmcgui.ControlLabel(x, y, w, h, self._text, alignment=XBFONT_CENTER_X | XBFONT_CENTER_Y, *args, **kwargs) self._shadow = xbmcgui.ControlLabel(x + 1, y + 1, w, h, self._text, textColor='0xD0000000', alignment=XBFONT_CENTER_X | XBFONT_CENTER_Y, *args, **kwargs) self._background = xbmcgui.ControlImage( x, y, w, h, os.path.join(ADDON_PATH, "resources", "img", "black.png").encode('utf-8')) self._background.setColorDiffuse("0xD0000000")
def GetAddonInfo(self): info = {} for key in ("author", "changelog", "description", "disclaimer", "fanart", "icon", "id", "name", "profile", "stars", "summary", "type", "version"): info[key] = ADDON.getAddonInfo(key) info['path'] = ADDON_PATH info['home'] = "special://home" info['xbmc'] = "special://xbmc" return info
def get_setting(key, converter=str, choices=None): value = ADDON.getSetting(id=key) if converter is six.text_type or converter is str: return py2_decode(value) elif converter is bool: return value == 'true' elif converter is int: return int(value) elif isinstance(choices, (list, tuple)): return choices[int(value)] else: raise TypeError('Acceptable converters are str, unicode, bool and ' 'int. Acceptable choices are instances of list ' ' or tuple.')
def AddonFailure(self, addonId): if ADDON.getSetting("provider_disable_failing") == u"false": return 0 if addonId in self._failures: self._failures[addonId] += 1 else: self._failures[addonId] = 1 log.warning("Recorded failure %d for %s" % (self._failures[addonId], addonId)) if self._failures[addonId] > int( ADDON.getSetting("provider_allowed_failures")): try: time.sleep(10) notify(getLocalizedString(30111)) urllib_request.urlopen("%s/provider/%s/disable" % (projectxD_HOST, addonId)) except: notify(getLocalizedString(30112)) return 0 return self._failures[addonId]
def GetAllSettings(self): settings = [] settingsFile = os.path.join(ADDON_PATH, "resources", "settings.xml") with open(settingsFile, 'r') as settingsStr: fileContent = settingsStr.read() keyType = re.findall( r".*id=\"(\w+)\".*type=\"(\w+)\"(.*option=\"(\w+)\")?", fileContent) for key, _type, optgroup, option in keyType: settings.append({ "key": key, "type": _type, "option": option, "value": ADDON.getSetting(key) }) return settings
def _get_logger(name): global loggers if loggers.get(name): return loggers.get(name) else: logger = logging.getLogger(ADDON_ID) if ADDON.getSetting("logger_silent") == 'true': logger.setLevel(logging.ERROR) else: logger.setLevel(logging.DEBUG) handler = XBMCHandler() handler.setFormatter(logging.Formatter('[%(name)s] %(message)s')) logger.addHandler(handler) return logger
def set_setting(key, val): return ADDON.setSetting(id=key, value=val)
def getLocalizedString(stringId): try: return ADDON.getLocalizedString(stringId).encode('utf-8', 'ignore') except: return stringId
def get_platform(): try: binary_platform = ADDON.getSetting("binary_platform") except: binary_platform = "auto" pass build = xbmc.getInfoLabel("System.BuildVersion") kodi_version = int(build.split()[0][:2]) if binary_platform and "auto" not in binary_platform.lower(): custom = binary_platform.split('_') if len(custom) > 1: return { "os": custom[0], "arch": custom[1], "fork": True, "version": "", "kodi": kodi_version, "build": build } ret = { "auto_arch": sys.maxsize > 2 ** 32 and "64-bit" or "32-bit", "arch": sys.maxsize > 2 ** 32 and "x64" or "x86", "os": "", "version": "", "kodi": kodi_version, "build": build, "fork": True, "machine": "", "system": "", "platform": "" } try: ret["os"] = platform.release() except: pass try: ret["machine"] = platform.machine() except: # Default 'machine' for Android can be 'arm' if xbmc.getCondVisibility("system.platform.android"): ret["machine"] = "arm" pass try: ret["system"] = platform.system() except: pass try: ret["platform"] = platform.platform() except: pass if xbmc.getCondVisibility("system.platform.android"): ret["os"] = "android" if "arm" in ret["machine"].lower() or "aarch" in ret["machine"].lower(): ret["arch"] = "arm" if "64" in ret["machine"] and ret["auto_arch"] == "64-bit": ret["arch"] = "arm64" elif xbmc.getCondVisibility("system.platform.linux"): ret["os"] = "linux" if "aarch" in ret["machine"].lower() or "arm64" in ret["machine"].lower(): if xbmc.getCondVisibility("system.platform.linux.raspberrypi"): ret["arch"] = "armv7" elif ret["auto_arch"] == "32-bit": ret["arch"] = "armv7" elif ret["auto_arch"] == "64-bit": ret["arch"] = "arm64" # elif platform.architecture()[0].startswith("32"): # ret["arch"] = "armv6" else: ret["arch"] = "armv7" elif "armv7" in ret["machine"]: ret["arch"] = "armv7" elif "arm" in ret["machine"]: cpuarch = "" if "aarch" in ret["machine"].lower() or "arm" in ret["machine"].lower(): info = cpuinfo() for proc in info.keys(): log.info("CPU: %s=%s" % (proc, info[proc])) model = "" if "model name" in info[proc]: model = info[proc]["model name"].lower() elif "Processor" in info[proc]: model = info[proc]["Processor"].lower() if model: log.info("Exploring model: %s" % model) if "aarch" in model or "arm64" in model or "v8l" in model: cpuarch = "arm64" elif "armv7" in model or "v7l" in model: cpuarch = "armv7" break if cpuarch: log.info("Using CPU info arch: %s" % cpuarch) ret["arch"] = cpuarch else: ret["arch"] = "armv6" elif xbmc.getCondVisibility("system.platform.xbox"): ret["os"] = "windows" ret["arch"] = "x64" ret["fork"] = False elif xbmc.getCondVisibility("system.platform.windows"): ret["os"] = "windows" if ret["machine"].endswith('64'): ret["arch"] = "x64" elif ret["system"] == "Darwin": ret["os"] = "darwin" ret["arch"] = "x64" if "AppleTV" in ret["platform"]: ret["os"] = "ios" ret["arch"] = "armv7" ret["fork"] = False if "64bit" in ret["platform"]: ret["arch"] = "arm64" elif xbmc.getCondVisibility("system.platform.ios"): ret["os"] = "ios" ret["arch"] = "armv7" ret["fork"] = False if "64bit" in ret["platform"]: ret["arch"] = "arm64" # elif xbmc.getCondVisibility("system.platform.osx"): # ret["os"] = "darwin" # ret["arch"] = "x64" # elif xbmc.getCondVisibility("system.platform.ios"): # ret["os"] = "ios" # ret["arch"] = "armv7" return ret
def GetLocalizedString(self, *args, **kwargs): return ADDON.getLocalizedString(*args, **kwargs).encode('utf-8', 'ignore')
def get_projectx_binary(): global binary_platform binary_platform = get_platform() binary = "projectx" + (binary_platform["os"] == "windows" and ".exe" or "") binary_dir = os.path.join(ADDON_PATH, "resources", "bin", "%(os)s_%(arch)s" % binary_platform) if binary_platform["os"] == "android": log.info("Detected binary folder: %s" % binary_dir) binary_dir_legacy = binary_dir.replace("/storage/emulated/0", "/storage/emulated/legacy") if os.path.exists(binary_dir_legacy): binary_dir = binary_dir_legacy log.info("Using changed binary folder for Android: %s" % binary_dir) app_id = android_get_current_appid() xbmc_data_path = os.path.join("/data", "data", app_id) if not os.path.exists(xbmc_data_path): log.info("%s path does not exist, so using %s as xbmc_data_path" % (xbmc_data_path, xbmc.translatePath("special://xbmcbin/"))) xbmc_data_path = xbmc.translatePath("special://xbmcbin/") if not os.path.exists(xbmc_data_path): log.info("%s path does not exist, so using %s as xbmc_data_path" % (xbmc_data_path, xbmc.translatePath("special://masterprofile/"))) xbmc_data_path = xbmc.translatePath("special://masterprofile/") dest_binary_dir = os.path.join(xbmc_data_path, "files", ADDON_ID, "bin", "%(os)s_%(arch)s" % binary_platform) else: dest_binary_dir = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")), "bin", "%(os)s_%(arch)s" % binary_platform) binary_path = os.path.join(binary_dir, binary) dest_binary_path = os.path.join(dest_binary_dir, binary) log.info("Binary detection. Source: %s, Destination: %s" % (binary_path, dest_binary_path)) if not os.path.exists(binary_path): # notify((getLocalizedString(30103) + " %(os)s_%(arch)s" % PLATFORM), time=7000) dialog_ok("LOCALIZE[30347];;" + "%(os)s_%(arch)s" % binary_platform) system_information() try: log.info("Source directory (%s):\n%s" % (binary_dir, os.listdir(os.path.join(binary_dir, "..")))) log.info("Destination directory (%s):\n%s" % (dest_binary_dir, os.listdir(os.path.join(dest_binary_dir, "..")))) except Exception: pass return False, False if os.path.isdir(dest_binary_path): log.warning("Destination path is a directory, expected previous binary file, removing...") try: shutil.rmtree(dest_binary_path) except Exception as e: log.error("Unable to remove destination path for update: %s" % e) system_information() return False, False if not os.path.exists(dest_binary_path) or not os.path.exists(binary_path) or get_projectxd_checksum(dest_binary_path) != get_projectxd_checksum(binary_path) or not filecmp.cmp(dest_binary_path, binary_path, shallow=True): log.info("Updating projectx daemon...") try: os.makedirs(dest_binary_dir) except OSError: pass try: shutil.rmtree(dest_binary_dir) except Exception as e: log.error("Unable to remove destination path for update: %s" % e) system_information() pass try: shutil.copytree(binary_dir, dest_binary_dir) except Exception as e: log.error("Unable to copy to destination path for update: %s" % e) system_information() return False, False # Clean stale files in the directory, as this can cause headaches on # Android when they are unreachable dest_files = set(os.listdir(dest_binary_dir)) orig_files = set(os.listdir(binary_dir)) log.info("Deleting stale files %s" % (dest_files - orig_files)) for file_ in (dest_files - orig_files): path = os.path.join(dest_binary_dir, file_) if os.path.isdir(path): shutil.rmtree(path) else: os.remove(path) log.info("Binary detection: [ Source: %s, Destination: %s ]" % (binary_path, dest_binary_path)) return dest_binary_dir, ensure_exec_perms(dest_binary_path)
def run(url_suffix="", retry=0): try: buffer_timeout = int(ADDON.getSetting("buffer_timeout")) if buffer_timeout < 60: buffer_timeout = 60 except: buffer_timeout = 60 buffer_timeout = buffer_timeout * 2 try: preload_timeout = int(ADDON.getSetting("preload_timeout")) if preload_timeout < 1: preload_timeout = 1 except: preload_timeout = 1 socket.setdefaulttimeout(buffer_timeout) urllib_request.install_opener( urllib_request.build_opener(NoRedirectHandler())) # Pause currently playing projectx file to avoid doubling requests try: if xbmc.Player().isPlaying() and ADDON_ID in xbmc.Player( ).getPlayingFile(): xbmc.Player().pause() except: pass url = sys.argv[0].replace("plugin://%s" % ADDON_ID, projectxD_HOST + url_suffix) + sys.argv[2] query_add = "" if len(sys.argv) > 3: app = sys.argv[3].replace(":", "=") # Replacing resume=false with resume=true if item is launched from main window title = xbmc.getInfoLabel('ListItem.Title') label = xbmc.getInfoLabel('ListItem.Label') # if "resume" in app and not title and not label: # app = app.replace("resume=false", "resume=true") # if "resume" not in app and not title and not label: if not title and not label: if app: app = app.replace("resume=false", "") app += "&" if app and "resume" not in app: app += "resume=true" query_add = app elif "play" in url or "links" in url: query_add = "resume=true" if query_add and "resume" not in url: query_add = query_add.replace("resume=", "doresume=") if "?" in url: url += "&" + query_add else: url += "?" + query_add log.debug("Requesting %s from %s" % (url, repr(sys.argv))) try: data = _json(url) except PlayerException as e: redirect_url = e.__str__() log.debug("Launching player with %s" % (redirect_url)) xbmcplugin.endOfDirectory(HANDLE, succeeded=True) xbmc.sleep(500) xbmc.executeJSONRPC( '{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"%s"}},"id":"1"}' % (redirect_url)) return except RedirectException as e: redirect_url = e.__str__() log.debug("Redirecting Kodi with %s" % (redirect_url)) xbmcplugin.endOfDirectory(HANDLE, succeeded=True) xbmc.sleep(500) if "keyboard=1" in sys.argv[0]: xbmc.executebuiltin('Container.Update(%s,replace)' % (redirect_url)) else: xbmc.executebuiltin('Container.Update(%s)' % (redirect_url)) return except urllib_error.URLError as e: # We can retry the request if connection is refused. # For example when plugin has not yet started but is requested by someone. if retry <= 2: time.sleep(preload_timeout) return run(retry=retry + 1) if isinstance(e.reason, IOError) or isinstance( e.reason, OSError) or 'Connection refused' in e.reason: notify(getLocalizedString(30116), time=7000) else: import traceback map(log.error, traceback.format_exc().split("\n")) notify(e.reason, time=7000) return except Exception as e: import traceback map(log.error, traceback.format_exc().split("\n")) try: msg = six.ensure_text(e.__str__(), errors='ignore') except: try: msg = six.ensure_binary(e.__str__(), errors='ignore') except: msg = repr(e) notify(getLocalizedLabel(msg), time=7000) return if not data: return if data["content_type"]: content_type = data["content_type"] if data["content_type"].startswith("menus"): content_type = data["content_type"].split("_")[1] xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_UNSORTED) if content_type != "tvshows": xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) else: xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE) xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_DATE) xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_GENRE) xbmcplugin.setContent(HANDLE, content_type) listitems = list(range(len(data["items"]))) for i, item in enumerate(data["items"]): # Translate labels if item["label"][0:8] == "LOCALIZE": item["label"] = getLocalizedLabel(item["label"]) if isinstance(item["label"], str): item["label"] = six.ensure_text(item["label"], 'utf-8') if item["label2"][0:8] == "LOCALIZE": item["label2"] = getLocalizedLabel(item["label2"]) if PLATFORM['kodi'] >= 19: listItem = xbmcgui.ListItem(label=item["label"], label2=item["label2"]) listItem.setArt({'icon': item["icon"]}) listItem.setArt({'thumb': item["thumbnail"]}) else: listItem = xbmcgui.ListItem(label=item["label"], label2=item["label2"], iconImage=item["icon"], thumbnailImage=item["thumbnail"]) if item.get("info"): listItem.setInfo("video", item["info"]) if item.get("stream_info"): for type_, values in item["stream_info"].items(): listItem.addStreamInfo(type_, values) if item.get("art"): listItem.setArt(item["art"]) elif ADDON.getSetting('default_fanart' ) == 'true' and item["label"] != six.ensure_text( getLocalizedString(30218), 'utf-8'): fanart = os.path.join(ADDON_PATH, "fanart.png") listItem.setArt({'fanart': fanart}) if item.get("context_menu"): # Translate context menus for m, menu in enumerate(item["context_menu"]): if menu[0][0:8] == "LOCALIZE": menu[0] = getLocalizedLabel(menu[0]) listItem.addContextMenuItems(item["context_menu"]) listItem.setProperty("isPlayable", item["is_playable"] and "true" or "false") if item.get("properties"): for k, v in item["properties"].items(): listItem.setProperty(k, v) listitems[i] = (item["path"], listItem, not item["is_playable"]) xbmcplugin.addDirectoryItems(HANDLE, listitems, totalItems=len(listitems)) # Set ViewMode if data["content_type"]: viewMode = ADDON.getSetting("viewmode_%s" % data["content_type"]) if viewMode: try: xbmc.executebuiltin('Container.SetViewMode(%s)' % viewMode) except Exception as e: log.warning("Unable to SetViewMode(%s): %s" % (viewMode, repr(e))) xbmcplugin.endOfDirectory(HANDLE, succeeded=True, updateListing=False, cacheToDisc=False)
def start_projectxd(**kwargs): jsonrpc_failures = 0 while jsonrpc_enabled() is False: jsonrpc_failures += 1 log.warning("Unable to connect to Kodi's JSON-RPC service, retrying...") if jsonrpc_failures > 1: time.sleep(5) if not jsonrpc_enabled(notify=True): log.error("Unable to reach Kodi's JSON-RPC service, aborting...") return False else: break time.sleep(3) projectx_dir, projectx_binary = get_projectx_binary() log.info("Binary dir: %s, item: %s " % (projectx_dir, projectx_binary)) if projectx_dir is False or projectx_binary is False: return False lockfile = os.path.join(ADDON_PATH, ".lockfile") if os.path.exists(lockfile): log.warning("Existing process found from lockfile, killing...") try: with open(lockfile) as lf: pid = int(lf.read().rstrip(" \t\r\n\0")) os.kill(pid, 9) except OSError as e: if e.errno != 3: # Ignore: OSError: [Errno 3] No such process log.error(repr(e)) except Exception as e: log.error(repr(e)) if binary_platform["os"] == "windows": try: library_lockfile = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")).decode('utf-8'), "library.db.lock") log.warning("Removing library.db.lock file at %s ..." % library_lockfile) os.remove(library_lockfile) except Exception as e: log.error(repr(e)) SW_HIDE = 0 STARTF_USESHOWWINDOW = 1 args = [projectx_binary] kwargs["cwd"] = projectx_dir if binary_platform["os"] == "windows": args[0] = getWindowsShortPath(projectx_binary) kwargs["cwd"] = getWindowsShortPath(projectx_dir) si = subprocess.STARTUPINFO() si.dwFlags = STARTF_USESHOWWINDOW si.wShowWindow = SW_HIDE clear_fd_inherit_flags() kwargs["startupinfo"] = si else: env = os.environ.copy() env["LD_LIBRARY_PATH"] = "%s:%s" % (projectx_dir, env.get("LD_LIBRARY_PATH", "")) kwargs["env"] = env kwargs["close_fds"] = True wait_counter = 1 log.debug("Checking for visible") while xbmc.getCondVisibility('Window.IsVisible(10140)') or xbmc.getCondVisibility('Window.IsActive(10140)'): if wait_counter == 1: log.info('Add-on settings currently opened, waiting before starting...') if wait_counter > 300: break time.sleep(1) wait_counter += 1 log.info("projectxd: start args: %s, kw: %s" % (args, kwargs)) if hasSubprocess: return subprocess.Popen(args, **kwargs) return False
def GetSetting(self, *args, **kwargs): return ADDON.getSetting(*args, **kwargs)
def SetSetting(self, *args, **kwargs): return ADDON.setSetting(*args, **kwargs)
def getLocalizedStringMatch(match): try: return ADDON.getLocalizedString(int(match.group(1))) except: return match.group(1)
def notify(message, header=ADDON_NAME, time=5000, image=ADDON_ICON): sound = ADDON.getSetting('do_not_disturb') == 'false' dialog = xbmcgui.Dialog() return dialog.notification(toUtf8(header), toUtf8(message), toUtf8(image), time, sound)