def get_repo_resourceaddons(filterstr=""): '''helper to retrieve all available resource addons on the kodi repo''' result = [] simplecache = SimpleCache() for item in xbmcvfs.listdir("addons://all/kodi.resource.images/")[1]: if not filterstr or item.lower().startswith(filterstr.lower()): addoninfo = get_repo_addoninfo(item, simplecache) if not addoninfo.get("name"): addoninfo = {"addonid": item, "name": item, "author": ""} addoninfo["thumbnail"] = "http://mirrors.kodi.tv/addons/krypton/%s/icon.png" % item addoninfo["path"] = "resource://%s/" % item result.append(addoninfo) simplecache.close() return result
def __init__(self): '''Initialize our Module''' self.cache = SimpleCache() self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.log_msg("Initialized")
def __init__(self, simplecache=None): '''Initialize - optionaly provide simplecache object''' if not simplecache: from simplecache import SimpleCache self.cache = SimpleCache() else: self.cache = simplecache
def __init__(self, *args, **kwargs): self.cache = SimpleCache() self.artutils = kwargs.get("artutils") self.win = kwargs.get("win") self.kodimonitor = kwargs.get("monitor") self.event = threading.Event() threading.Thread.__init__(self, *args)
def __init__(self): self.net = net.Net() self.cache = SimpleCache() self.region = self.getRegion() self.filter = False if self.region == 'US' else True self.categoryMenu = self.getCategories() self.mediaType = self.getMediaTypes() log('__init__, region = ' + self.region)
def __init__(self, simplecache=None): '''Initialize - optionaly provide simplecache object''' if not simplecache: from simplecache import SimpleCache self.cache = SimpleCache() else: self.cache = simplecache addon = xbmcaddon.Addon(id=ADDON_ID) self.api_key = addon.getSetting("tmdb_apikey") del addon
def __init__(self, api_key=None): '''Initialize our Module''' if api_key: self.api_key = api_key self.cache = SimpleCache() self._win = xbmcgui.Window(10000) self._addon = xbmcaddon.Addon(ADDON_ID) addonversion = self._addon.getAddonInfo('version').decode("utf-8") self.cache.global_checksum = "%s%s" % (addonversion, KODI_LANGUAGE) self._log_msg("Initialized")
class Newsy(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheResponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheResponse: request = urllib2.Request(url) request.add_header('User-Agent','Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)') response = urllib2.urlopen(request, timeout=TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, response, expiration=datetime.timedelta(hours=6)) return self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) except urllib2.URLError, e: log("openURL Failed! " + str(e), xbmc.LOGERROR) except socket.timeout, e: log("openURL Failed! " + str(e), xbmc.LOGERROR)
def test_simple_cache(): cache = SimpleCache(0.1) assert cache.get("hamster") == None cache.save("yumyum", "hamster") assert cache.get("hamster") == "yumyum" sleep(0.1) assert cache.get("hamster") == None
def __init__(self, simplecache=None, kodidb=None): '''Initialize - optionaly provide SimpleCache and KodiDb object''' if not kodidb: from kodidb import KodiDb self.kodidb = KodiDb() else: self.kodidb = kodidb if not simplecache: from simplecache import SimpleCache self.cache = SimpleCache() else: self.cache = simplecache
def __init__(self): self.cache = SimpleCache() self.kodi_db = KodiDb() self.win = xbmcgui.Window(10000) try: self.params = dict(urlparse.parse_qsl(sys.argv[2].replace("?", "").lower().decode("utf-8"))) log_msg("plugin called with parameters: %s" % self.params) self.main() except Exception as exc: log_exception(__name__, exc) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) # cleanup when done processing self.close()
def __init__(self): '''Initialize and load all our helpers''' self._studiologos_path = "" self.cache = SimpleCache() self.addon = xbmcaddon.Addon(ADDON_ID) self.kodidb = KodiDb() self.omdb = Omdb(self.cache) self.tmdb = Tmdb(self.cache) self.channellogos = ChannelLogos(self.kodidb) self.fanarttv = FanartTv(self.cache) self.imdb = Imdb(self.cache) self.google = GoogleImages(self.cache) self.studiologos = StudioLogos(self.cache) self.animatedart = AnimatedArt(self.cache, self.kodidb) self.thetvdb = TheTvDb() self.musicart = MusicArtwork(self) self.pvrart = PvrArtwork(self) log_msg("Initialized")
def __init__(self): '''Initialization and main code run''' self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.kodidb = KodiDb() self.cache = SimpleCache() self.params = self.get_params() log_msg("MainModule called with parameters: %s" % self.params) action = self.params.get("action", "") # launch module for action provided by this script try: getattr(self, action)() except AttributeError: log_exception(__name__, "No such action: %s" % action) except Exception as exc: log_exception(__name__, exc) finally: xbmc.executebuiltin("dialog.Close(busydialog)") # do cleanup self.close()
def __init__(self): log('__init__') self.cache = SimpleCache() self.ydl = YoutubeDL()
class Tmdb(object): """get metadata from tmdb""" api_key = None # public var to be set by the calling addon def __init__(self, simplecache=None, api_key=None): """Initialize - optionaly provide simplecache object""" if not simplecache: from simplecache import SimpleCache self.cache = SimpleCache() else: self.cache = simplecache addon = xbmcaddon.Addon(id=ADDON_ID) # personal api key (preferred over provided api key) api_key = addon.getSetting("tmdb_apikey") if api_key: self.api_key = api_key del addon def search_movie(self, title, year="", manual_select=False, ignore_cache=False): """ Search tmdb for a specific movie, returns full details of best match parameters: title: (required) the title of the movie to search for year: (optional) the year of the movie to search for (enhances search result if supplied) manual_select: (optional) if True will show select dialog with all results """ details = self.select_best_match(self.search_movies(title, year), manual_select=manual_select) if details: details = self.get_movie_details(details["id"]) return details @use_cache(30) def search_movieset(self, title): """search for movieset details providing the title of the set""" details = {} params = {"query": title, "language": KODI_LANGUAGE} result = self.get_data("search/collection", params) if result: set_id = result[0]["id"] details = self.get_movieset_details(set_id) return details @use_cache(4) def search_tvshow(self, title, year="", manual_select=False, ignore_cache=False): """ Search tmdb for a specific movie, returns full details of best match parameters: title: (required) the title of the movie to search for year: (optional) the year of the movie to search for (enhances search result if supplied) manual_select: (optional) if True will show select dialog with all results """ details = self.select_best_match(self.search_tvshows(title, year), manual_select=manual_select) if details: details = self.get_tvshow_details(details["id"]) return details @use_cache(4) def search_video(self, title, prefyear="", preftype="", manual_select=False, ignore_cache=False): """ Search tmdb for a specific entry (can be movie or tvshow), returns full details of best match parameters: title: (required) the title of the movie/tvshow to search for prefyear: (optional) prefer result if year matches preftype: (optional) prefer result if type matches manual_select: (optional) if True will show select dialog with all results """ results = self.search_videos(title) details = self.select_best_match(results, prefyear=prefyear, preftype=preftype, preftitle=title, manual_select=manual_select) if details and details["media_type"] == "movie": details = self.get_movie_details(details["id"]) elif details and "tv" in details["media_type"]: details = self.get_tvshow_details(details["id"]) return details @use_cache(4) def search_videos(self, title): """ Search tmdb for a specific entry (can be movie or tvshow), parameters: title: (required) the title of the movie/tvshow to search for """ results = [] page = 1 maxpages = 5 while page < maxpages: params = {"query": title, "language": KODI_LANGUAGE, "page": page} subresults = self.get_data("search/multi", params) page += 1 if subresults: for item in subresults: if item["media_type"] in ["movie", "tv"]: results.append(item) else: break return results @use_cache(4) def search_movies(self, title, year=""): """ Search tmdb for a specific movie, returns a list of all closest matches parameters: title: (required) the title of the movie to search for year: (optional) the year of the movie to search for (enhances search result if supplied) """ params = {"query": title, "language": KODI_LANGUAGE} if year: params["year"] = try_parse_int(year) return self.get_data("search/movie", params) @use_cache(4) def search_tvshows(self, title, year=""): """ Search tmdb for a specific tvshow, returns a list of all closest matches parameters: title: (required) the title of the tvshow to search for year: (optional) the first air date year of the tvshow to search for (enhances search result if supplied) """ params = {"query": title, "language": KODI_LANGUAGE} if year: params["first_air_date_year"] = try_parse_int(year) return self.get_data("search/tv", params) def get_actor(self, name): """ Search tmdb for a specific actor/person, returns the best match as kodi compatible dict required parameter: name --> the name of the person """ params = {"query": name, "language": KODI_LANGUAGE} result = self.get_data("search/person", params) if result: result = result[0] cast_thumb = "https://image.tmdb.org/t/p/original%s" % result[ "profile_path"] if result["profile_path"] else "" item = { "name": result["name"], "thumb": cast_thumb, "roles": [ item["title"] if item.get("title") else item["name"] for item in result["known_for"] ] } return item else: return {} def get_movie_details(self, movie_id): """get all moviedetails""" params = { "append_to_response": "keywords,videos,credits,images", "include_image_language": "%s,en" % KODI_LANGUAGE, "language": KODI_LANGUAGE } return self.map_details(self.get_data("movie/%s" % movie_id, params), "movie") def get_movieset_details(self, movieset_id): """get all moviesetdetails""" details = {"art": {}} params = {"language": KODI_LANGUAGE} result = self.get_data("collection/%s" % movieset_id, params) if result: details["title"] = result["name"] details["plot"] = result["overview"] details["tmdb_id"] = result["id"] details["art"][ "poster"] = "https://image.tmdb.org/t/p/original%s" % result[ "poster_path"] details["art"][ "fanart"] = "https://image.tmdb.org/t/p/original%s" % result[ "backdrop_path"] details["totalmovies"] = len(result["parts"]) return details def get_tvshow_details(self, tvshow_id): """get all tvshowdetails""" params = { "append_to_response": "keywords,videos,external_ids,credits,images", "include_image_language": "%s,en" % KODI_LANGUAGE, "language": KODI_LANGUAGE } return self.map_details(self.get_data("tv/%s" % tvshow_id, params), "tvshow") def get_videodetails_by_externalid(self, extid, extid_type): """get metadata by external ID (like imdbid)""" params = {"external_source": extid_type, "language": KODI_LANGUAGE} results = self.get_data("find/%s" % extid, params) if results and results["movie_results"]: return self.get_movie_details(results["movie_results"][0]["id"]) elif results and results["tv_results"]: return self.get_tvshow_details(results["tv_results"][0]["id"]) return {} def get_data(self, endpoint, params): """helper method to get data from tmdb json API""" if self.api_key: # addon provided or personal api key params["api_key"] = self.api_key rate_limit = None expiration = datetime.timedelta(days=7) else: # fallback api key (rate limited !) params["api_key"] = "80246691939720672db3fc71c74e0ef2" # without personal (or addon specific) api key = rate limiting and older info from cache rate_limit = ("themoviedb.org", 5) expiration = datetime.timedelta(days=60) if sys.version_info.major == 3: cachestr = "tmdb.%s" % list(params.values()) else: cachestr = "tmdb.%s" % iter(params.values()) cache = self.cache.get(cachestr) if cache: # data obtained from cache result = cache else: # no cache, grab data from API url = 'https://api.themoviedb.org/3/%s' % endpoint result = get_json(url, params, ratelimit=rate_limit) # make sure that we have a plot value (if localized value fails, fallback to english) if result and "language" in params and "overview" in result: if not result["overview"] and params["language"] != "en": params["language"] = "en" result2 = get_json(url, params) if result2 and result2.get("overview"): result = result2 self.cache.set(url, result, expiration=expiration) return result def map_details(self, data, media_type): """helper method to map the details received from tmdb to kodi compatible formatting""" if not data: return {} details = {} details["tmdb_id"] = data["id"] details["rating"] = data["vote_average"] details["votes"] = data["vote_count"] details["rating.tmdb"] = data["vote_average"] details["votes.tmdb"] = data["vote_count"] details["popularity"] = data["popularity"] details["popularity.tmdb"] = data["popularity"] details["plot"] = data["overview"] details["genre"] = [item["name"] for item in data["genres"]] details["homepage"] = data["homepage"] details["status"] = data["status"] details["cast"] = [] details["castandrole"] = [] details["writer"] = [] details["director"] = [] details["media_type"] = media_type # cast if "credits" in data: if "cast" in data["credits"]: for cast_member in data["credits"]["cast"]: cast_thumb = "" if cast_member["profile_path"]: cast_thumb = "https://image.tmdb.org/t/p/original%s" % cast_member[ "profile_path"] details["cast"].append({ "name": cast_member["name"], "role": cast_member["character"], "thumbnail": cast_thumb }) details["castandrole"].append( (cast_member["name"], cast_member["character"])) # crew (including writers and directors) if "crew" in data["credits"]: for crew_member in data["credits"]["crew"]: cast_thumb = "" if crew_member["profile_path"]: cast_thumb = "https://image.tmdb.org/t/p/original%s" % crew_member[ "profile_path"] if crew_member["job"] in ["Author", "Writer"]: details["writer"].append(crew_member["name"]) if crew_member["job"] in [ "Producer", "Executive Producer" ]: details["director"].append(crew_member["name"]) if crew_member["job"] in [ "Producer", "Executive Producer", "Author", "Writer" ]: details["cast"].append({ "name": crew_member["name"], "role": crew_member["job"], "thumbnail": cast_thumb }) # artwork details["art"] = {} if data.get("images"): if data["images"].get("backdrops"): fanarts = self.get_best_images(data["images"]["backdrops"]) details["art"]["fanarts"] = fanarts details["art"]["fanart"] = fanarts[0] if fanarts else "" if data["images"].get("posters"): posters = self.get_best_images(data["images"]["posters"]) details["art"]["posters"] = posters details["art"]["poster"] = posters[0] if posters else "" if not details["art"].get("poster") and data.get("poster_path"): details["art"][ "poster"] = "https://image.tmdb.org/t/p/original%s" % data[ "poster_path"] if not details["art"].get("fanart") and data.get("backdrop_path"): details["art"][ "fanart"] = "https://image.tmdb.org/t/p/original%s" % data[ "backdrop_path"] # movies only if media_type == "movie": details["title"] = data["title"] details["originaltitle"] = data["original_title"] if data["belongs_to_collection"]: details["set"] = data["belongs_to_collection"].get("name", "") if data.get("release_date"): details["premiered"] = data["release_date"] details["year"] = try_parse_int( data["release_date"].split("-")[0]) details["tagline"] = data["tagline"] if data["runtime"]: details["runtime"] = data["runtime"] * 60 details["imdbnumber"] = data["imdb_id"] details["budget"] = data["budget"] details["budget.formatted"] = int_with_commas(data["budget"]) details["revenue"] = data["revenue"] details["revenue.formatted"] = int_with_commas(data["revenue"]) if data.get("production_companies"): details["studio"] = [ item["name"] for item in data["production_companies"] ] if data.get("production_countries"): details["country"] = [ item["name"] for item in data["production_countries"] ] if data.get("keywords"): details["tag"] = [ item["name"] for item in data["keywords"]["keywords"] ] # tvshows only if media_type == "tvshow": details["title"] = data["name"] details["originaltitle"] = data["original_name"] if data.get("created_by"): details["director"] += [ item["name"] for item in data["created_by"] ] if data.get("episode_run_time"): details["runtime"] = data["episode_run_time"][0] * 60 if data.get("first_air_date"): details["premiered"] = data["first_air_date"] details["year"] = try_parse_int( data["first_air_date"].split("-")[0]) if "last_air_date" in data: details["lastaired"] = data["last_air_date"] if data.get("networks"): details["studio"] = [item["name"] for item in data["networks"]] if "origin_country" in data: details["country"] = data["origin_country"] if "number_of_seasons" in data: details["Seasons"] = data["number_of_seasons"] if "number_of_episodes" in data: details["Episodes"] = data["number_of_episodes"] if data.get("seasons"): tmdboverviewdetails = data["seasons"] seasons = [] for count, item in enumerate(tmdboverviewdetails): seasons.append(item["overview"]) details["seasons.formatted.%s" % count] = "%s %s[CR]%s[CR]" % (item["name"], item["air_date"], item["overview"]) details["seasons.formatted"] = "[CR]".join(seasons) if data.get("external_ids"): details["imdbnumber"] = data["external_ids"].get("imdb_id", "") details["tvdb_id"] = data["external_ids"].get("tvdb_id", "") if "results" in data["keywords"]: details["tag"] = [ item["name"] for item in data["keywords"]["results"] ] # trailer for video in data["videos"]["results"]: if video["site"] == "YouTube" and video["type"] == "Trailer": details[ "trailer"] = 'plugin://plugin.video.youtube/?action=play_video&videoid=%s' % video[ "key"] break return details @staticmethod def get_best_images(images): """get the best 5 images based on number of likes and the language""" for image in images: score = 0 score += image["vote_count"] score += image["vote_average"] * 10 score += image["height"] if "iso_639_1" in image: if image["iso_639_1"] == KODI_LANGUAGE: score += 1000 image["score"] = score if not image["file_path"].startswith("https"): image[ "file_path"] = "https://image.tmdb.org/t/p/original%s" % image[ "file_path"] images = sorted(images, key=itemgetter("score"), reverse=True) return [image["file_path"] for image in images] @staticmethod def select_best_match(results, prefyear="", preftype="", preftitle="", manual_select=False): """helper to select best match or let the user manually select the best result from the search""" details = {} # score results if one or more preferences are given if results and (prefyear or preftype or preftitle): newdata = [] preftitle = preftitle.lower() for item in results: item["score"] = 0 itemtitle = item["title"] if item.get( "title") else item["name"] itemtitle = itemtitle.lower() itemorgtitle = item["original_title"] if item.get( "original_title") else item["original_name"] itemorgtitle = itemorgtitle.lower() # high score if year matches if prefyear: if item.get("first_air_date" ) and prefyear in item["first_air_date"]: item["score"] += 800 # matches preferred year if item.get("release_date" ) and prefyear in item["release_date"]: item["score"] += 800 # matches preferred year # find exact match on title if preftitle and preftitle == itemtitle: item["score"] += 1000 # exact match! if preftitle and preftitle == itemorgtitle: item["score"] += 1000 # exact match! # match title by replacing some characters if preftitle and get_compare_string( preftitle) == get_compare_string(itemtitle): item["score"] += 750 if preftitle and get_compare_string( preftitle) == get_compare_string(itemorgtitle): item["score"] += 750 # add SequenceMatcher score to the results if preftitle: stringmatchscore = SM( None, preftitle, itemtitle).ratio() + SM( None, preftitle, itemorgtitle).ratio() if stringmatchscore > 1.6: item["score"] += stringmatchscore * 250 # higher score if result ALSO matches our preferred type or native language # (only when we already have a score) if item["score"]: if preftype and (item["media_type"] in preftype) or ( preftype in item["media_type"]): item["score"] += 250 # matches preferred type if item["original_language"] == KODI_LANGUAGE: item["score"] += 500 # native language! if KODI_LANGUAGE.upper() in item.get("origin_country", []): item["score"] += 500 # native language! if KODI_LANGUAGE in item.get("languages", []): item["score"] += 500 # native language! if item["score"] > 500 or manual_select: newdata.append(item) results = sorted(newdata, key=itemgetter("score"), reverse=True) if results and manual_select: # show selectdialog to manually select the item results_list = [] for item in results: title = item["name"] if "name" in item else item["title"] if item.get("premiered"): year = item["premiered"].split("-")[0] else: year = item.get("first_air_date", "").split("-")[0] if item["poster_path"]: thumb = "https://image.tmdb.org/t/p/original%s" % item[ "poster_path"] else: thumb = "" label = "%s (%s) - %s" % (title, year, item["media_type"]) listitem = xbmcgui.ListItem(label=label, label2=item["overview"]) listitem.setArt({'icon': thumb}) results_list.append(listitem) if manual_select and results_list: dialog = DialogSelect("DialogSelect.xml", "", listing=results_list, window_title="%s - TMDB" % xbmc.getLocalizedString(283)) dialog.doModal() selected_item = dialog.result del dialog if selected_item != -1: details = results[selected_item] else: results = [] if not details and results: # just grab the first item as best match details = results[0] return details
class Locast(object): def __init__(self, sysARG=sys.argv): log('__init__, sysARG = %s' % (sysARG)) self.sysARG = sysARG self.cache = SimpleCache() self.token = (REAL_SETTINGS.getSetting('User_Token') or None) self.lastDMA = 0 self.now = datetime.datetime.now() self.lat, self.lon = self.setRegion() if self.login(REAL_SETTINGS.getSetting('User_Email'), REAL_SETTINGS.getSetting('User_Password')) == False: sys.exit() else: self.city = self.getRegion() def reset(self): self.__init__() def buildMenu(self): MAIN_MENU = [(LANGUAGE(30003), (getLive, self.city)), (LANGUAGE(49011), (getLiveFavs, self.city)), (LANGUAGE(30004), (getChannels, self.city))] for item in MAIN_MENU: self.addDir(*item) def getStations(self, city, name='', opt=''): log("getStations, city = %s, opt = %s, name = %s" % (city, opt, name)) stations = self.getEPG(city) for station in stations: if station['active'] == False: continue path = str(station['id']) thumb = (station.get('logoUrl', '') or station.get('logo226Url', '') or ICON) listings = station['listings'] label = (station.get('affiliateName', '') or station.get('affiliate', '') or station.get('callSign', '') or station.get('name', '')) stnum = re.sub('[^\d\.]+', '', label) stname = '' favorite = None if stnum: stname = re.compile('[^a-zA-Z]').sub('', label) stlabel = '%s| %s' % (stnum, stname) favorite = isFavorite(stnum) else: stlabel = label if opt in ['live', 'favorites']: self.buildListings(listings, label, thumb, path, opt) elif opt == 'lineup' and (name.lower() == stlabel.lower()): self.buildListings(listings, label, thumb, path, opt) elif opt == 'play' and (name.lower() == station.get('name', '').lower()): self.buildListings(listings, label, thumb, path, opt) elif opt == 'channels': chnum = re.sub('[^\d\.]+', '', label) if chnum: chname = re.compile('[^a-zA-Z]').sub('', label) label = '%s| %s' % (chnum, chname) else: label = chname self.addDir(label, (getLineup, city, urllib.parse.quote(label)), infoList={ "favorite": favorite, "chnum": stnum, "chname": stname, "mediatype": "video", "label": label, "title": label }, infoArt={ "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON }) else: continue def buildListings(self, listings, chname, chlogo, path, opt=''): log('buildListings, chname = %s, opt = %s' % (chname, opt)) now = datetime.datetime.now() for listing in listings: try: starttime = datetime.datetime.fromtimestamp( int(str(listing['startTime'])[:-3])) except: continue duration = listing.get('duration', 0) endtime = starttime + datetime.timedelta(seconds=duration) label = listing['title'] favorite = None chnum = -1 # if listing['isNew']: label = '*%s'%label try: aired = datetime.datetime.fromtimestamp( int(str(listing['airdate'])[:-3])) except: aired = starttime try: type = {'Series': 'episode'}[listing.get('showType', 'Series')] except: type = 'video' plot = (listing.get('description', '') or listing.get('shortDescription', '') or label) if now > endtime: continue elif opt in ['live', 'favorites', 'play']: chnum = re.sub('[^\d\.]+', '', chname) if chnum: favorite = isFavorite(chnum) chname = re.compile('[^a-zA-Z]').sub('', chname) chname = '%s| %s' % (chnum, chname) label = '%s : [B] %s[/B]' % (chname, label) else: label = chname if opt == 'favorites' and not favorite: continue elif opt == 'lineup': if now >= starttime and now < endtime: label = '%s - [B]%s[/B]' % ( starttime.strftime('%I:%M %p').lstrip('0'), label) else: label = '%s - %s' % ( starttime.strftime('%I:%M %p').lstrip('0'), label) path = 'NEXT_SHOW' thumb = (listing.get('preferredImage', '') or chlogo) infoLabels = { "favorite": favorite, "chnum": chnum, "chname": chname, "mediatype": type, "label": label, "title": label, 'duration': duration, 'plot': plot, 'genre': listing.get('genres', []), "aired": aired.strftime('%Y-%m-%d') } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": chlogo, "logo": chlogo } infoVideo = { } #todo added more meta from listings, ie mpaa, isNew, video/audio codec infoAudio = { } #todo added more meta from listings, ie mpaa, isNew, video/audio codec if type == 'episode': infoLabels['tvshowtitle'] = listing.get('title', label) if listing.get('seasonNumber', None): infoLabels['season'] = listing.get('seasonNumber', 0) infoLabels['episode'] = listing.get('episodeNumber', 0) seaep = '%sx%s' % ( str(listing.get('seasonNumber', '')).zfill(2), str(listing.get('episodeNumber', '')).zfill(2)) label = '%s - %s %s' % (label, seaep, listing.get('episodeTitle', '')) else: label = '%s %s' % (label, listing.get('episodeTitle', '')) infoLabels['title'] = label infoLabels['label'] = label if opt in ['live', 'favorites']: if now >= starttime and now < endtime: return self.addLink(label, (playChannel, path), infoLabels, infoArt, infoVideo, infoAudio, total=len(listings)) else: continue if opt == 'play': if starttime <= now and endtime > now: infoLabels['duration'] = ((endtime) - now).seconds self.addPlaylist(label, path, infoLabels, infoArt, infoVideo, infoAudio) else: self.addLink(label, (playChannel, path), infoLabels, infoArt, infoVideo, infoAudio, total=len(listings)) def setRegion(self): try: geo_data = json.load(urllib.request.urlopen(GEO_URL)) except: geo_data = {'lat': 0.0, 'lon': 0.0} okDialog(LANGUAGE(30025) % (GEO_URL)) return float('{0:.7f}'.format(geo_data['lat'])), float( '{0:.7f}'.format(geo_data['lon'])) def getURL(self, url, param={}, header={'Content-Type': 'application/json'}, life=datetime.timedelta(minutes=5)): log('getURL, url = %s, header = %s' % (url, header)) cacheresponse = self.cache.get(ADDON_NAME + '.getURL, url = %s.%s.%s' % (url, param, header)) if not cacheresponse: try: req = requests.get(url, param, headers=header) try: cacheresponse = req.json() except: return {} req.close() self.cache.set(ADDON_NAME + '.getURL, url = %s.%s.%s' % (url, param, header), json.dumps(cacheresponse), expiration=life) return cacheresponse except Exception as e: log("getURL, Failed! %s" % (e), xbmc.LOGERROR) notificationDialog(LANGUAGE(30001)) return {} else: return json.loads(cacheresponse) def postURL(self, url, param={}, header={'Content-Type': 'application/json'}, life=datetime.timedelta(minutes=5)): log('postURL, url = %s, header = %s' % (url, header)) cacheresponse = self.cache.get(ADDON_NAME + '.postURL, url = %s.%s.%s' % (url, param, header)) cacheresponse = None if not cacheresponse: try: req = requests.post(url, param, headers=header) cacheresponse = req.json() req.close() self.cache.set(ADDON_NAME + '.postURL, url = %s.%s.%s' % (url, param, header), json.dumps(cacheresponse), expiration=life) return cacheresponse except Exception as e: log("postURL, Failed! %s" % (e), xbmc.LOGERROR) notificationDialog(LANGUAGE(30001)) return {} else: return json.loads(cacheresponse) def buildHeader(self): header_dict = {} header_dict[ 'Accept'] = 'application/json, text/javascript, */*; q=0.01' header_dict['Content-Type'] = 'application/json' header_dict['Connection'] = 'keep-alive' header_dict['Origin'] = BASE_URL header_dict['Referer'] = BASE_URL header_dict['Authorization'] = "Bearer %s" % self.token header_dict[ 'User-Agent'] = 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36' return header_dict def chkUser(self, user): state = False try: self.userdata = self.getURL(BASE_API + '/user/me', header=self.buildHeader()) except: self.userdata = {} if self.userdata.get('email', '').lower() == user.lower(): state = True log('chkUser = %s' % (state)) return state def login(self, user, password): log('login') if len(user) > 0: if self.chkUser(user): return True data = self.postURL(BASE_API + '/user/login', param='{"username":"******","password":"******"}') '''{u'token': u''}''' if data and 'token' in data: self.token = data['token'] if REAL_SETTINGS.getSetting('User_Token') != self.token: REAL_SETTINGS.setSetting('User_Token', self.token) if self.chkUser(user): try: REAL_SETTINGS.setSetting( 'User_Donate', str(self.userdata.get('didDonate', 'False'))) REAL_SETTINGS.setSetting( 'User_totalDonations', str(self.userdata.get('totalDonations', '0'))) REAL_SETTINGS.setSetting( 'User_LastLogin', datetime.datetime.fromtimestamp( float( str(self.userdata.get('lastlogin', '')).rstrip('L')) / 1000).strftime("%Y-%m-%d %I:%M %p")) REAL_SETTINGS.setSetting( 'User_DonateExpire', datetime.datetime.fromtimestamp( float( str(self.userdata.get( 'donationExpire', '')).rstrip('L')) / 1000).strftime("%Y-%m-%d %I:%M %p")) except: pass self.lastDMA = self.userdata.get('lastDmaUsed', '') notificationDialog( LANGUAGE(30021) % (self.userdata.get('name', ''))) return True elif data.get('message'): notificationDialog(data.get('message')) else: notificationDialog(LANGUAGE(30017)) else: #firstrun wizard if yesnoDialog(LANGUAGE(30008), nolabel=LANGUAGE(30009), yeslabel=LANGUAGE(30010)): user = inputDialog(LANGUAGE(30006)) password = inputDialog(LANGUAGE(30007), opt=xbmcgui.ALPHANUM_HIDE_INPUT) REAL_SETTINGS.setSetting('User_Email', user) REAL_SETTINGS.setSetting('User_Password', password) xbmc.sleep(2000) #wait for setsetting write return self.reset() else: okDialog(LANGUAGE(30012)) return False def genClientID(self): return urllib.parse.quote(''.join( random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits + '=' + '+') for _ in range(24))) def getEPG(self, city): log("getEPG, city = %s" % city) '''[{"id":104,"dma":501,"name":"WCBSDT (WCBS-DT)","callSign":"WCBS","logoUrl":"https://fans.tmsimg.com/h5/NowShowing/28711/s28711_h5_aa.png","active":true,"affiliate":"CBS","affiliateName":"CBS", "listings":[{"stationId":104,"startTime":1535410800000,"duration":1800,"isNew":true,"audioProperties":"CC, HD 1080i, HDTV, New, Stereo","videoProperties":"CC, HD 1080i, HDTV, New, Stereo","programId":"EP000191906491","title":"Inside Edition","description":"Primary stories and alternative news.","entityType":"Episode","airdate":1535328000000,"genres":"Newsmagazine","showType":"Series"}]}''' now = ('{0:.23s}{1:s}'.format( datetime.datetime.now().strftime('%Y-%m-%dT00:00:00'), '-05:00')) return self.getURL(BASE_API + '/watch/epg/%s' % (city), param={'start_time': urllib.parse.quote(now)}, header=self.buildHeader(), life=datetime.timedelta(minutes=45)) def getAll(self): return self.getURL(BASE_API + '/dma', header=self.buildHeader()) def getCity(self): log("getCity") '''{u'active': True, u'DMA': u'501', u'small_url': u'https://s3.us-east-2.amazonaws.com/static.locastnet.org/cities/new-york.jpg', u'large_url': u'https://s3.us-east-2.amazonaws.com/static.locastnet.org/cities/background/new-york.jpg', u'name': u'New York'}''' try: city = self.getURL(BASE_API + '/watch/dma/%s/%s' % (self.lat, self.lon), header=self.buildHeader()) if city and 'DMA' not in city: okDisable(city.get('message')) else: REAL_SETTINGS.setSetting('User_City', str(city['name'])) return city except: okDisable(LANGUAGE(30013)) def getRegion(self): log("getRegion") try: return self.getCity()['DMA'] except: return self.lastDMA if self.lastDMA > 0 else sys.exit() def poolList(self, method, items=None, args=None, chunk=25): log("poolList") results = [] if ENABLE_POOL: pool = ThreadPool(CORES) if args is not None: results = pool.map(method, zip(items, repeat(args))) elif items: results = pool.map(method, items) #, chunksize=chunk) pool.close() pool.join() else: if args is not None: results = [method((item, args)) for item in items] elif items: results = [method(item) for item in items] return filter(None, results) def getChannels(self): log('getChannels') # https://github.com/add-ons/service.iptv.manager/wiki/JSON-STREAMS-format stations = self.getEPG(self.getRegion()) return list(self.poolList(self.buildStation, stations, 'channel')) def getGuide(self): log('getGuide') # https://github.com/add-ons/service.iptv.manager/wiki/JSON-EPG-format stations = self.getEPG(self.getRegion()) return { k: v for x in self.poolList(self.buildStation, stations, 'programmes') for k, v in x.items() } def buildStation(self, data): station, opt = data if station['active'] == False: return None label = (station.get('affiliateName', '') or station.get('affiliate', '') or station.get('callSign', '') or station.get('name', '')) stnum = re.sub('[^\d\.]+', '', label) stname = re.compile('[^a-zA-Z]').sub('', label) favorite = isFavorite(stnum) channel = { "name": stname, "stream": "plugin://%s/play/pvr/%s" % (ADDON_ID, station['id']), "id": "%s.%s@%s" % (stnum, slugify(stname), slugify(ADDON_NAME)), "logo": (station.get('logoUrl', '') or station.get('logo226Url', '') or ICON), "preset": stnum, "group": ADDON_NAME, "radio": False } #, # "kodiprops":{"inputstream":"inputstream.adaptive", # "inputstream.adaptive.manifest_type":"hls", # "inputstream.adaptive.media_renewal_url":"plugin://%s/play/pvr/%s"%(ADDON_ID,station['id']), # "inputstream.adaptive.media_renewal_time":"900"}} if favorite: channel['group'] = ';'.join([LANGUAGE(49012), ADDON_NAME]) if REAL_SETTINGS.getSettingBool('Build_Favorites') and not favorite: return None elif opt == 'channel': return channel else: programmes = {channel['id']: []} listings = station.get('listings', []) for listing in listings: try: starttime = datetime.datetime.fromtimestamp( int(str(listing['startTime'])[:-3])) + UTC_OFFSET except: continue try: aired = datetime.datetime.fromtimestamp( int(str(listing['airdate'])[:-3])) except: aired = starttime program = { "start": starttime.strftime(DTFORMAT), "stop": (starttime + datetime.timedelta(seconds=listing.get('duration', 0)) ).strftime(DTFORMAT), "title": listing.get('title', channel['name']), "description": (listing.get('description', '') or listing.get('shortDescription', '') or xbmc.getLocalizedString(161)), "subtitle": listing.get('episodeTitle', ''), "episode": "S%sE%s" % (str(listing.get('seasonNumber', 0)).zfill(2), str(listing.get('episodeNumber', 0)).zfill(2)), "genre": listing.get('genres', ""), "image": (listing.get('preferredImage', '') or channel['logo']), "date": aired.strftime('%Y-%m-%d'), "credits": "", "stream": "" } programmes[channel['id']].append(program) return programmes def resolveURL(self, id, opt): log('resolveURL, id = %s, opt = %s' % (id, opt)) self.listitems = [] self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) self.playlist.clear() '''{u'dma': 501, u'streamUrl': u'https://acdn.locastnet.org/variant/E27GYubZwfUs.m3u8', u'name': u'WNBCDT2', u'sequence': 50, u'stationId': u'44936', u'callSign': u'4.2 COZITV', u'logo226Url': u'https://fans.tmsimg.com/assets/s78851_h3_aa.png', u'logoUrl': u'https://fans.tmsimg.com/assets/s78851_h3_aa.png', u'active': True, u'id': 1574529688491L}''' data = self.getURL(BASE_API + '/watch/station/%s/%s/%s' % (id, self.lat, self.lon), header=self.buildHeader(), life=datetime.timedelta(seconds=5)) url = data.get('streamUrl') liz = xbmcgui.ListItem(data.get('name'), path=url) liz.setProperty('IsPlayable', 'true') liz.setProperty('IsInternetStream', 'true') if opt != 'pvr': self.getStations(data.get('dma'), name=data.get('name'), opt='play') [ self.playlist.add(url, lz, idx) for idx, lz in enumerate(self.listitems) ] liz = self.listitems.pop(0) liz.setPath(path=url) return liz def playLive(self, id, opt='live'): log('playLive, id = %s, opt = %s' % (id, opt)) if id == 'NEXT_SHOW': found = False liz = xbmcgui.ListItem(LANGUAGE(30029)) notificationDialog(LANGUAGE(30029), time=4000) else: found = True liz = self.resolveURL(id, opt) if opt != 'pvr' and 'm3u8' in liz.getPath().lower( ) and inputstreamhelper.Helper('hls').check_inputstream(): liz.setProperty('inputstream', 'inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type', 'hls') # liz.setProperty('inputstream.adaptive.media_renewal_url', 'plugin://%s/play/%s/%s'%(ADDON_ID,opt,id)) # liz.setProperty('inputstream.adaptive.media_renewal_time', '900') #todo debug pvr (IPTV Simple) not playing with inputstream! temp. use kodiprops in m3u? xbmcplugin.setResolvedUrl(ROUTER.handle, found, liz) def addPlaylist(self, name, path='', infoList={}, infoArt={}, infoVideo={}, infoAudio={}, infoType='video'): log('addPlaylist, name = %s' % name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') liz.setProperty('IsInternetStream', 'true') if infoList: liz.setInfo(type=infoType, infoLabels=infoList) else: liz.setInfo(type=infoType, infoLabels={ "mediatype": infoType, "label": name, "title": name }) if infoArt: liz.setArt(infoArt) else: liz.setArt({'thumb': ICON, 'fanart': FANART}) if infoVideo: liz.addStreamInfo('video', infoVideo) if infoAudio: liz.addStreamInfo('audio', infoAudio) self.listitems.append(liz) def addLink(self, name, uri=(''), infoList={}, infoArt={}, infoVideo={}, infoAudio={}, infoType='video', total=0): log('addLink, name = %s' % name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') liz.setProperty('IsInternetStream', 'true') if infoList: liz.setInfo(type=infoType, infoLabels=infoList) else: liz.setInfo(type=infoType, infoLabels={ "mediatype": infoType, "label": name, "title": name }) if infoArt: liz.setArt(infoArt) else: liz.setArt({'thumb': ICON, 'fanart': FANART}) if infoVideo: liz.addStreamInfo('video', infoVideo) if infoAudio: liz.addStreamInfo('audio', infoAudio) if infoList.get('favorite', None) is not None: liz = self.addContextMenu(liz, infoList) xbmcplugin.addDirectoryItem(ROUTER.handle, ROUTER.url_for(*uri), liz, isFolder=False, totalItems=total) def addDir(self, name, uri=(''), infoList={}, infoArt={}, infoType='video'): log('addDir, name = %s' % name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList: liz.setInfo(type=infoType, infoLabels=infoList) else: liz.setInfo(type=infoType, infoLabels={ "mediatype": infoType, "label": name, "title": name }) if infoArt: liz.setArt(infoArt) else: liz.setArt({'thumb': ICON, 'fanart': FANART}) if infoList.get('favorite', None) is not None: liz = self.addContextMenu(liz, infoList) xbmcplugin.addDirectoryItem(ROUTER.handle, ROUTER.url_for(*uri), liz, isFolder=True) def addContextMenu(self, liz, infoList={}): log('addContextMenu') if infoList['favorite']: liz.addContextMenuItems([ (LANGUAGE(49010), 'RunScript(special://home/addons/%s/favorites.py, %s)' % (ADDON_ID, urllib.parse.quote( json.dumps({ "chnum": infoList.pop('chnum'), "chname": infoList.pop('chname'), "mode": "del" })))) ]) else: liz.addContextMenuItems([ (LANGUAGE(49009), 'RunScript(special://home/addons/%s/favorites.py, %s)' % (ADDON_ID, urllib.parse.quote( json.dumps({ "chnum": infoList.pop('chnum'), "chname": infoList.pop('chname'), "mode": "add" })))) ]) return liz def run(self): ROUTER.run() xbmcplugin.setContent(ROUTER.handle, CONTENT_TYPE) xbmcplugin.addSortMethod(ROUTER.handle, xbmcplugin.SORT_METHOD_UNSORTED) xbmcplugin.addSortMethod(ROUTER.handle, xbmcplugin.SORT_METHOD_NONE) xbmcplugin.addSortMethod(ROUTER.handle, xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.addSortMethod(ROUTER.handle, xbmcplugin.SORT_METHOD_TITLE) xbmcplugin.endOfDirectory(ROUTER.handle, cacheToDisc=DISC_CACHE)
class ListItemMonitor(threading.Thread): '''Our main class monitoring the kodi listitems and providing additional information''' event = None exit = False delayed_task_interval = 1795 listitem_details = {} all_window_props = [] cur_listitem = "" last_folder = "" last_listitem = "" foldercontent = {} screensaver_setting = None screensaver_disabled = False lookup_busy = {} enable_extendedart = False enable_musicart = False enable_animatedart = False enable_extrafanart = False enable_pvrart = False enable_forcedviews = False def __init__(self, *args, **kwargs): self.cache = SimpleCache() self.artutils = kwargs.get("artutils") self.win = kwargs.get("win") self.kodimonitor = kwargs.get("monitor") self.event = threading.Event() threading.Thread.__init__(self, *args) def stop(self): '''called when the thread has to stop working''' log_msg("ListItemMonitor - stop called") self.exit = True self.event.set() def run(self): '''our main loop monitoring the listitem and folderpath changes''' log_msg("ListItemMonitor - started") self.get_settings() while not self.exit: # check screensaver and OSD self.check_screensaver() self.check_osd() # do some background stuff every 30 minutes if (self.delayed_task_interval >= 1800) and not self.exit: thread.start_new_thread(self.do_background_work, ()) self.delayed_task_interval = 0 # skip if any of the artwork context menus is opened if self.win.getProperty("SkinHelper.Artwork.ManualLookup"): self.reset_win_props() self.last_listitem = "" self.listitem_details = {} self.kodimonitor.waitForAbort(3) self.delayed_task_interval += 3 # skip when modal dialogs are opened (e.g. textviewer in musicinfo dialog) or container scrolling elif xbmc.getCondVisibility( "Window.IsActive(DialogSelect.xml) | Window.IsActive(progressdialog) | " "Window.IsActive(contextmenu) | Window.IsActive(busydialog) | Container.Scrolling"): self.kodimonitor.waitForAbort(2) self.delayed_task_interval += 2 self.last_listitem = "" # media window is opened or widgetcontainer set - start listitem monitoring! elif xbmc.getCondVisibility("Window.IsMedia | " "!IsEmpty(Window(Home).Property(SkinHelper.WidgetContainer))"): self.monitor_listitem() self.kodimonitor.waitForAbort(0.15) self.delayed_task_interval += 0.15 # flush any remaining window properties elif self.all_window_props: self.reset_win_props() self.win.clearProperty("SkinHelper.ContentHeader") self.win.clearProperty("contenttype") self.win.clearProperty("curlistitem") self.last_listitem = "" # other window active - do nothing else: self.kodimonitor.waitForAbort(1) self.delayed_task_interval += 1 def get_settings(self): '''collect our skin settings that control the monitoring''' self.enable_extendedart = xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.EnableExtendedArt)") == 1 self.enable_musicart = xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.EnableMusicArt)") == 1 self.enable_animatedart = xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.EnableAnimatedPosters)") == 1 self.enable_extrafanart = xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.EnableExtraFanart)") == 1 self.enable_pvrart = xbmc.getCondVisibility( "Skin.HasSetting(SkinHelper.EnablePVRThumbs) + PVR.HasTVChannels") == 1 self.enable_forcedviews = xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.ForcedViews.Enabled)") == 1 studiologos_path = xbmc.getInfoLabel("Skin.String(SkinHelper.StudioLogos.Path)").decode("utf-8") if studiologos_path != self.artutils.studiologos_path: self.listitem_details = {} self.artutils.studiologos_path = studiologos_path # set additional window props to control contextmenus as using the skinsetting gives unreliable results for skinsetting in ["EnableAnimatedPosters", "EnableMusicArt", "EnablePVRThumbs"]: if xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.%s)" % skinsetting): self.win.setProperty("SkinHelper.%s" % skinsetting, "enabled") else: self.win.clearProperty("SkinHelper.%s" % skinsetting) def monitor_listitem(self): '''Monitor listitem details''' cur_folder, cont_prefix = self.get_folderandprefix() li_label = xbmc.getInfoLabel("%sListItem.Label" % cont_prefix).decode('utf-8') # perform actions if the container path has changed if cur_folder != self.last_folder: self.reset_win_props() self.get_settings() self.last_folder = cur_folder content_type = self.get_content_type(cur_folder, li_label, cont_prefix) # additional actions to perform when we have a valid contenttype and no widget container if not cont_prefix and content_type: self.set_forcedview(content_type) self.set_content_header(content_type) else: content_type = self.get_content_type(cur_folder, li_label, cont_prefix) if self.exit: return # only perform actions when the listitem has actually changed li_title = xbmc.getInfoLabel("%sListItem.Title" % cont_prefix).decode('utf-8') li_dbid = xbmc.getInfoLabel( "$INFO[%sListItem.DBID]$INFO[%sListItem.Property(DBID)]" % (cont_prefix, cont_prefix)).decode('utf-8') cur_listitem = "%s--%s--%s--%s--%s" % (cur_folder, li_label, li_title, content_type, li_dbid) if cur_listitem and content_type and cur_listitem != self.last_listitem: self.last_listitem = cur_listitem # clear all window props first self.reset_win_props() self.set_win_prop(("curlistitem", cur_listitem)) self.set_forcedview(content_type) if not li_label == "..": # set listitem details in background thread thread.start_new_thread( self.set_listitem_details, (cur_listitem, content_type, cont_prefix)) def get_folderandprefix(self): '''get the current folder and prefix''' cur_folder = "" cont_prefix = "" try: widget_container = self.win.getProperty("SkinHelper.WidgetContainer").decode('utf-8') if xbmc.getCondVisibility("Window.IsActive(movieinformation)"): cont_prefix = "" cur_folder = xbmc.getInfoLabel( "movieinfo-$INFO[Container.FolderPath]" "$INFO[Container.NumItems]" "$INFO[Container.Content]").decode('utf-8') elif widget_container: cont_prefix = "Container(%s)." % widget_container cur_folder = xbmc.getInfoLabel( "widget-%s-$INFO[Container(%s).NumItems]-$INFO[Container(%s).ListItemAbsolute(1).Label]" % (widget_container, widget_container, widget_container)).decode('utf-8') else: cont_prefix = "" cur_folder = xbmc.getInfoLabel( "$INFO[Container.FolderPath]$INFO[Container.NumItems]$INFO[Container.Content]").decode( 'utf-8') except Exception as exc: log_exception(__name__, exc) cur_folder = "" cont_prefix = "" return (cur_folder, cont_prefix) def get_content_type(self, cur_folder, li_label, cont_prefix): '''get contenttype for current folder''' content_type = "" if cur_folder in self.foldercontent: content_type = self.foldercontent[cur_folder] elif cur_folder and li_label: # always wait for the content_type because some listings can be slow for i in range(20): content_type = get_current_content_type(cont_prefix) if self.exit: return "" if content_type: break else: xbmc.sleep(250) self.foldercontent[cur_folder] = content_type self.win.setProperty("contenttype", content_type) return content_type def check_screensaver(self): '''Allow user to disable screensaver on fullscreen music playback''' if xbmc.getCondVisibility( "Window.IsActive(visualisation) + Skin.HasSetting(SkinHelper.DisableScreenSaverOnFullScreenMusic)"): if not self.screensaver_disabled: # disable screensaver when fullscreen music active self.screensaver_setting = kodi_json('Settings.GetSettingValue', '{"setting":"screensaver.mode"}') kodi_json('Settings.SetSettingValue', {"setting": "screensaver.mode", "value": None}) self.screensaver_disabled = True log_msg( "Disabled screensaver while fullscreen music playback - previous setting: %s" % self.screensaver_setting) elif self.screensaver_setting and self.screensaver_disabled: # enable screensaver again after fullscreen music playback was ended kodi_json('Settings.SetSettingValue', {"setting": "screensaver.mode", "value": self.screensaver_setting}) self.screensaver_disabled = False log_msg("fullscreen music playback ended - restoring screensaver: %s" % self.screensaver_setting) @staticmethod def check_osd(): '''Allow user to set a default close timeout for the OSD panels''' if xbmc.getCondVisibility("[Window.IsActive(videoosd) + Skin.String(SkinHelper.AutoCloseVideoOSD)] | " "[Window.IsActive(musicosd) + Skin.String(SkinHelper.AutoCloseMusicOSD)]"): if xbmc.getCondVisibility("Window.IsActive(videoosd)"): seconds = xbmc.getInfoLabel("Skin.String(SkinHelper.AutoCloseVideoOSD)") window = "videoosd" elif xbmc.getCondVisibility("Window.IsActive(musicosd)"): seconds = xbmc.getInfoLabel("Skin.String(SkinHelper.AutoCloseMusicOSD)") window = "musicosd" else: seconds = "" if seconds and seconds != "0": while xbmc.getCondVisibility("Window.IsActive(%s)" % window): if xbmc.getCondVisibility("System.IdleTime(%s)" % seconds): if xbmc.getCondVisibility("Window.IsActive(%s)" % window): xbmc.executebuiltin("Dialog.Close(%s)" % window) else: xbmc.sleep(500) def set_listitem_details(self, cur_listitem, content_type, prefix): '''set the window properties based on the current listitem''' try: if cur_listitem in self.listitem_details: # data already in memory all_props = self.listitem_details[cur_listitem] else: # prefer listitem's contenttype over container's contenttype dbtype = xbmc.getInfoLabel("%sListItem.DBTYPE" % prefix) if not dbtype: dbtype = xbmc.getInfoLabel("%sListItem.Property(DBTYPE)" % prefix) if dbtype: content_type = dbtype + "s" # collect all details from listitem listitem = self.get_listitem_details(content_type, prefix) if prefix and cur_listitem == self.last_listitem: # for widgets we immediately set all normal properties as window prop self.set_win_props(prepare_win_props(listitem)) # if another lookup for the same listitem already in progress... wait for it to complete while self.lookup_busy.get(cur_listitem): xbmc.sleep(250) if self.exit: return self.lookup_busy[cur_listitem] = True # music content if content_type in ["albums", "artists", "songs"] and self.enable_musicart: listitem = extend_dict(listitem, self.artutils.get_music_artwork( listitem["artist"], listitem["album"], listitem["title"], listitem["discnumber"])) # moviesets elif listitem["path"].startswith("videodb://movies/sets/") and listitem["dbid"]: listitem = extend_dict(listitem, self.artutils.get_moviesetdetails(listitem["dbid"])) content_type = "sets" # video content elif content_type in ["movies", "setmovies", "tvshows", "seasons", "episodes", "musicvideos"]: # get imdb and tvdbid listitem["imdbnumber"], tvdbid = self.artutils.get_imdbtvdb_id( listitem["title"], content_type, listitem["year"], listitem["imdbnumber"], listitem["tvshowtitle"]) # generic video properties (studio, streamdetails, omdb, top250) listitem = extend_dict(listitem, self.get_directors_writers(listitem["director"], listitem["writer"])) if self.enable_extrafanart: if not listitem["filenameandpath"]: listitem["filenameandpath"] = listitem["path"] listitem = extend_dict(listitem, self.artutils.get_extrafanart(listitem["filenameandpath"])) listitem = extend_dict(listitem, self.get_genres(listitem["genre"])) listitem = extend_dict(listitem, self.artutils.get_duration(listitem["duration"])) listitem = extend_dict(listitem, self.artutils.get_studio_logo(listitem["studio"])) listitem = extend_dict(listitem, self.artutils.get_omdb_info(listitem["imdbnumber"])) listitem = extend_dict( listitem, self.get_streamdetails( listitem["dbid"], listitem["path"], content_type)) if self.exit: return listitem = extend_dict(listitem, self.artutils.get_top250_rating(listitem["imdbnumber"])) if self.enable_extendedart: if not (listitem["art"]["clearlogo"] or listitem["art"]["landscape"]): listitem = extend_dict(listitem, self.artutils.get_extended_artwork( listitem["imdbnumber"], tvdbid, content_type)) if self.exit: return # tvshows-only properties (tvdb) if content_type in ["tvshows", "seasons", "episodes"]: listitem = extend_dict(listitem, self.artutils.get_tvdb_details(listitem["imdbnumber"], tvdbid)) # movies-only properties (tmdb, animated art) if content_type in ["movies", "setmovies"]: listitem = extend_dict(listitem, self.artutils.get_tmdb_details(listitem["imdbnumber"])) if listitem["imdbnumber"] and self.enable_animatedart: listitem = extend_dict(listitem, self.artutils.get_animated_artwork(listitem["imdbnumber"])) if self.exit: return # monitor listitem props when PVR is active elif content_type in ["tvchannels", "tvrecordings", "channels", "recordings", "timers", "tvtimers"]: listitem = self.get_pvr_artwork(listitem, prefix) # process all properties all_props = prepare_win_props(listitem) if content_type not in ["weathers", "systeminfos", "sets"]: self.listitem_details[cur_listitem] = all_props self.lookup_busy.pop(cur_listitem, None) if cur_listitem == self.last_listitem: self.set_win_props(all_props) except Exception as exc: log_exception(__name__, exc) def do_background_work(self): '''stuff that's processed in the background''' try: if self.exit: return log_msg("Started Background worker...") self.set_generic_props() self.listitem_details = {} self.cache.check_cleanup() log_msg("Ended Background worker...") except Exception as exc: log_exception(__name__, exc) def set_generic_props(self): '''set some genric window props with item counts''' # GET TOTAL ADDONS COUNT addons_count = len(kodi_json('Addons.GetAddons')) self.win.setProperty("SkinHelper.TotalAddons", "%s" % addons_count) addontypes = [] addontypes.append(("executable", "SkinHelper.TotalProgramAddons")) addontypes.append(("video", "SkinHelper.TotalVideoAddons")) addontypes.append(("audio", "SkinHelper.TotalAudioAddons")) addontypes.append(("image", "SkinHelper.TotalPicturesAddons")) for addontype in addontypes: media_array = kodi_json('Addons.GetAddons', {"content": addontype[0]}) self.win.setProperty(addontype[1], str(len(media_array))) # GET FAVOURITES COUNT favs = kodi_json('Favourites.GetFavourites') if favs: self.win.setProperty("SkinHelper.TotalFavourites", "%s" % len(favs)) # GET TV CHANNELS COUNT if xbmc.getCondVisibility("Pvr.HasTVChannels"): tv_channels = kodi_json('PVR.GetChannels', {"channelgroupid": "alltv"}) self.win.setProperty("SkinHelper.TotalTVChannels", "%s" % len(tv_channels)) # GET MOVIE SETS COUNT movieset_movies_count = 0 moviesets = kodi_json('VideoLibrary.GetMovieSets') for item in moviesets: for item in kodi_json('VideoLibrary.GetMovieSetDetails', {"setid": item["setid"]}): movieset_movies_count += 1 self.win.setProperty("SkinHelper.TotalMovieSets", "%s" % len(moviesets)) self.win.setProperty("SkinHelper.TotalMoviesInSets", "%s" % movieset_movies_count) # GET RADIO CHANNELS COUNT if xbmc.getCondVisibility("Pvr.HasRadioChannels"): radio_channels = kodi_json('PVR.GetChannels', {"channelgroupid": "allradio"}) self.win.setProperty("SkinHelper.TotalRadioChannels", "%s" % len(radio_channels)) def reset_win_props(self): '''reset all window props set by the script...''' for prop in self.all_window_props: self.win.clearProperty(prop) self.all_window_props = [] def set_win_prop(self, prop_tuple): '''sets a window property based on the given tuple of key-value''' if prop_tuple[1] and not prop_tuple[0] in self.all_window_props: self.all_window_props.append(prop_tuple[0]) self.win.setProperty(prop_tuple[0], prop_tuple[1]) def set_win_props(self, prop_tuples): '''set multiple window properties from list of tuples''' process_method_on_list(self.set_win_prop, prop_tuples) def set_content_header(self, content_type): '''sets a window propery which can be used as headertitle''' self.win.clearProperty("SkinHelper.ContentHeader") itemscount = xbmc.getInfoLabel("Container.NumItems") if itemscount: if xbmc.getInfoLabel("Container.ListItemNoWrap(0).Label").startswith( "*") or xbmc.getInfoLabel("Container.ListItemNoWrap(1).Label").startswith("*"): itemscount = int(itemscount) - 1 headerprefix = "" if content_type == "movies": headerprefix = xbmc.getLocalizedString(36901) elif content_type == "tvshows": headerprefix = xbmc.getLocalizedString(36903) elif content_type == "seasons": headerprefix = xbmc.getLocalizedString(36905) elif content_type == "episodes": headerprefix = xbmc.getLocalizedString(36907) elif content_type == "sets": headerprefix = xbmc.getLocalizedString(36911) elif content_type == "albums": headerprefix = xbmc.getLocalizedString(36919) elif content_type == "songs": headerprefix = xbmc.getLocalizedString(36921) elif content_type == "artists": headerprefix = xbmc.getLocalizedString(36917) if headerprefix: self.win.setProperty("SkinHelper.ContentHeader", "%s %s" % (itemscount, headerprefix)) @staticmethod def get_genres(li_genre): '''get formatted genre string from actual genre''' details = {} genres = li_genre.split(" / ") details['genres'] = "[CR]".join(genres) for count, genre in enumerate(genres): details["genre.%s" % count] = genre return details @staticmethod def get_directors_writers(director, writer): '''get a formatted string with directors/writers from the actual string''' directors = director.split(" / ") writers = writer.split(" / ") return { 'Directors': "[CR]".join(directors), 'Writers': "[CR]".join(writers)} @staticmethod def get_listitem_details(content_type, prefix): '''collect all listitem properties/values we need''' # collect all the infolabels we need listitem_details = {"art": {}} props = ["label", "title", "filenameandpath", "year", "genre", "path", "folderpath", "art(fanart)", "art(poster)", "art(clearlogo)", "art(clearart)", "art(landscape)", "fileextension", "duration", "plot", "plotoutline", "icon", "thumb", "label2", "dbtype", "dbid", "art(thumb)", "art(banner)" ] if content_type in ["movies", "tvshows", "seasons", "episodes", "musicvideos", "setmovies"]: props += ["art(characterart)", "studio", "tvshowtitle", "premiered", "director", "writer", "firstaired", "videoresolution", "audiocodec", "audiochannels", "videocodec", "videoaspect", "subtitlelanguage", "audiolanguage", "mpaa", "isstereoscopic", "video3dformat", "tagline", "rating", "imdbnumber"] if content_type in ["episodes"]: props += ["season", "episode", "art(tvshow.landscape)", "art(tvshow.clearlogo)", "art(tvshow.poster)", "art(tvshow.fanart)", "art(tvshow.banner)"] elif content_type in ["musicvideos", "artists", "albums", "songs"]: props += ["artist", "album", "rating", "albumartist", "discnumber"] elif content_type in ["tvchannels", "tvrecordings", "channels", "recordings", "timers", "tvtimers"]: props += ["channel", "startdatetime", "datetime", "date", "channelname", "starttime", "startdate", "endtime", "enddate"] for prop in props: propvalue = xbmc.getInfoLabel('%sListItem.%s' % (prefix, prop)).decode('utf-8') if not propvalue or propvalue == "-1": propvalue = xbmc.getInfoLabel('%sListItem.Property(%s)' % (prefix, prop)).decode('utf-8') if "art(" in prop: prop = prop.replace("art(", "").replace(")", "").replace("tvshow.", "") propvalue = get_clean_image(propvalue) listitem_details["art"][prop] = propvalue else: listitem_details[prop] = propvalue # fix for folderpath if not listitem_details.get("path"): listitem_details["path"] = listitem_details["folderpath"] return listitem_details def get_streamdetails(self, li_dbid, li_path, content_type): '''get the streamdetails for the current video''' details = {} if li_dbid and content_type in ["movies", "episodes", "musicvideos"] and not li_path.startswith("videodb://movies/sets/"): details = self.artutils.get_streamdetails(li_dbid, content_type) return details def set_forcedview(self, content_type): '''helper to force the view in certain conditions''' if self.enable_forcedviews: cur_forced_view = xbmc.getInfoLabel("Skin.String(SkinHelper.ForcedViews.%s)" % content_type) if xbmc.getCondVisibility("Control.IsVisible(%s) | IsEmpty(Container.Viewmode)" % cur_forced_view): # skip if the view is already visible or if we're not in an actual media window return if (content_type and cur_forced_view and cur_forced_view != "None" and not xbmc.getCondVisibility("Window.IsActive(MyPvrGuide.xml)")): self.win.setProperty("SkinHelper.ForcedView", cur_forced_view) count = 0 while not xbmc.getCondVisibility("Control.HasFocus(%s)" % cur_forced_view): xbmc.sleep(100) xbmc.executebuiltin("Container.SetViewMode(%s)" % cur_forced_view) xbmc.executebuiltin("SetFocus(%s)" % cur_forced_view) count += 1 if count == 50: break else: self.win.clearProperty("SkinHelper.ForcedView") else: self.win.clearProperty("SkinHelper.ForcedView") def get_pvr_artwork(self, listitem, prefix): '''get pvr artwork from artwork module''' if self.enable_pvrart: if xbmc.getCondVisibility("%sListItem.IsFolder" % prefix) and not listitem[ "channelname"] and not listitem["title"]: listitem["title"] = listitem["label"] listitem = extend_dict( listitem, self.artutils.get_pvr_artwork( listitem["title"], listitem["channelname"], listitem["genre"]), ["title", "genre", "genres", "thumb"]) # pvr channellogo if listitem["channelname"]: listitem["ChannelLogo"] = self.artutils.get_channellogo(listitem["channelname"]) elif listitem.get("pvrchannel"): listitem["ChannelLogo"] = self.artutils.get_channellogo(listitem["pvrchannel"]) return listitem
class Mubi(object): _URL_MUBI = "https://mubi.com" _mubi_urls = { "login": urljoin(_URL_MUBI, "api/v1/sessions"), "films": urljoin(_URL_MUBI, "services/android/films"), "film": urljoin(_URL_MUBI, "services/android/films/%s"), "set_reel": urljoin(_URL_MUBI, "api/v1/films/%s/viewing/set_reel"), "get_url": urljoin(_URL_MUBI, "api/v1/films/%s/reels/%s/secure_url"), "startup": urljoin(_URL_MUBI, "services/android/app_startup") } def __init__(self, username, password): self._username = username self._password = password self._cache_id = "plugin.video.mubi.filminfo.%s" self._simplecache = SimpleCache() # Need a 20 digit id, hash username to make it predictable self._udid = int(hashlib.sha1(username).hexdigest(), 32) % (10**20) self._token = None self._userid = None self._country = None self._headers = { 'client': 'android', 'client-app': 'mubi', 'client-version': '4.46', 'client-device-identifier': str(self._udid) } self.login() def login(self): payload = {'email': self._username, 'password': self._password} xbmc.log( "Logging in with username: %s and udid: %s" % (self._username, self._udid), 2) r = requests.post(self._mubi_urls["login"], headers=self._headers, data=payload) result = (''.join(r.text)).encode('utf-8') if r.status_code == 200: self._token = json.loads(result)['token'] self._userid = json.loads(result)['user']['id'] self._headers['authorization'] = 'Bearer ' + self._token xbmc.log( "Login Successful with token=%s and userid=%s" % (self._token, self._userid), 2) else: xbmc.log("Login Failed with result: %s" % result, 4) self.app_startup() return r.status_code def app_startup(self): payload = { 'udid': self._udid, 'token': self._token, 'client': 'android', 'client_version': '4.46' } r = requests.post(self._mubi_urls['startup'] + "?client=android", data=payload) result = (''.join(r.text)).encode('utf-8') if r.status_code == 200: self._country = json.loads(result)['country'] xbmc.log("Successfully got country as %s" % self._country, 2) else: xbmc.log("Failed to get country: %s" % result, 4) return def get_film_page(self, film_id): cached = self._simplecache.get(self._cache_id % film_id) if cached: return json.loads(cached) args = "?client=android&country=%s&token=%s&udid=%s&client_version=4.46" % ( self._country, self._token, self._udid) r = requests.get((self._mubi_urls['film'] % str(film_id)) + args) if r.status_code != 200: xbmc.log( "Invalid status code %s getting film info for %s" % (r.status_code, film_id), 4) self._simplecache.set(self._cache_id % film_id, r.text, expiration=datetime.timedelta(days=32)) return json.loads(r.text) def get_film_metadata(self, film_overview): film_id = film_overview['id'] available_at = dateutil.parser.parse(film_overview['available_at']) expires_at = dateutil.parser.parse(film_overview['expires_at']) # Check film is valid, has not expired and is not preview now = datetime.datetime.now(available_at.tzinfo) if available_at > now: xbmc.log("Film %s is not yet available" % film_id, 2) return None elif expires_at < now: xbmc.log("Film %s has expired" % film_id, 2) return None hd = film_overview['hd'] drm = film_overview['reels'][0]['drm'] audio_lang = film_overview['reels'][0]['audio_language'] subtitle_lang = film_overview['reels'][0]['subtitle_language'] # Build plot field. Place lang info in here since there is nowhere else for it to go drm_string = "" #"Protected by DRM\n" if drm else "" lang_string = ("Language: %s" % audio_lang) + ( (", Subtitles: %s\n" % subtitle_lang) if subtitle_lang else "\n") plot_string = "Synopsis: %s\n\nOur take: %s" % ( film_overview['excerpt'], film_overview['editorial']) # Get detailed look at film to get cast info film_page = self.get_film_page(film_id) cast = [(m['name'], m['credits']) for m in film_page['cast']] # Build film metadata object metadata = Metadata( title=film_overview['title'], director=film_overview['directors'], year=film_overview['year'], duration=film_overview['duration'] * 60, # This is in seconds country=film_overview['country'], plot=drm_string + lang_string + plot_string, overlay=6 if hd else 0, genre=', '.join(film_overview['genres']), originaltitle=film_overview['original_title'], # Out of 5, kodi uses 10 rating=film_overview['average_rating'] * 2 if film_overview['average_rating'] is not None else None, votes=film_overview['number_of_ratings'], castandrole=cast, trailer=film_overview['trailer_url']) listview_title = film_overview['title'] + (" [HD]" if hd else "") return Film(listview_title, film_id, film_overview['stills']['standard'], metadata) def get_now_showing_json(self): # Get list of available films args = "?client=android&country=%s&token=%s&udid=%s&client_version=4.46" % ( self._country, self._token, self._udid) r = requests.get(self._mubi_urls['films'] + args) if r.status_code != 200: xbmc.log("Invalid status code %s getting list of films", 4) return r.text def now_showing(self): films = [ self.get_film_metadata(film) for film in json.loads(self.get_now_showing_json()) ] return [f for f in films if f] def get_default_reel_id_is_drm(self, film_id): reel_id = [(f['reels'][0]['id'], f['reels'][0]['drm']) for f in json.loads(self.get_now_showing_json()) if str(f['id']) == str(film_id)] if len(reel_id) == 1: return reel_id[0] elif reel_id: xbmc.log( "Multiple default_reel's returned for film %s: %s" % (film_id, ', '.join(reel_id)), 3) return reel_id[0] else: xbmc.log("Could not find default reel id for film %s" % film_id, 4) return None def get_play_url(self, film_id): (reel_id, is_drm) = self.get_default_reel_id_is_drm(film_id) # set reel payload = {'reel_id': reel_id, 'sidecar_subtitle_language_id': 20} r = requests.put((self._mubi_urls['set_reel'] % str(film_id)), data=payload, headers=self._headers) result = (''.join(r.text)).encode('utf-8') xbmc.log("Set reel response: %s" % result, 2) # get film url args = "?country=%s&download=false" % (self._country) r = requests.get( (self._mubi_urls['get_url'] % (str(film_id), str(reel_id))) + args, headers=self._headers) result = (''.join(r.text)).encode('utf-8') if r.status_code != 200: xbmc.log( "Could not get secure URL for film %s with reel_id=%s" % (film_id, reel_id), 4) xbmc.log("Response was: %s" % result, 2) url = json.loads(result)["url"] # For DRM you will have to find the following info: # {"userId": long(result['username']), "sessionId": result['transaction'], "merchant": result['accountCode']} # This might need optdata in header however looking in requests during browser negotiation I don't see it # https://stackoverflow.com/questions/35792897/http-request-header-field-optdata # The best conversation for this is: # https://github.com/emilsvennesson/kodi-viaplay/issues/9 # You can pick this conversation up using Android Packet Capture item_result = { 'url': url, 'is_mpd': "mpd" in url, 'is_drm': is_drm, 'drm_header': base64.b64encode('{"userId":' + str(self._userid) + ',"sessionId":"' + self._token + '","merchant":"mubi"}') } xbmc.log("Got video info as: '%s'" % json.dumps(item_result), 2) return item_result
class PlutoTV(object): def __init__(self, sysARG): log('__init__, sysARG = ' + str(sysARG)) self.sysARG = sysARG self.net = net.Net() self.cache = SimpleCache() def login(self): log('login') if USER_EMAIL == LANGUAGE(30009): return #ignore, using guest login if len(USER_EMAIL) > 0: header_dict = {} header_dict[ 'Accept'] = 'application/json, text/javascript, */*; q=0.01' header_dict['Host'] = 'api.pluto.tv' header_dict['Connection'] = 'keep-alive' header_dict['Referer'] = 'http://pluto.tv/' header_dict['Origin'] = 'http://pluto.tv' header_dict[ 'User-Agent'] = 'Mozilla/5.0 (Windows NT 6.2; rv:24.0) Gecko/20100101 Firefox/24.0' form_data = ({ 'optIn': 'true', 'password': PASSWORD, 'synced': 'false', 'userIdentity': USER_EMAIL }) self.net.set_cookies(COOKIE_JAR) try: loginlink = json.loads( self.net.http_POST( LOGIN_URL, form_data=form_data, headers=header_dict).content.encode("utf-8").rstrip()) if loginlink and loginlink['email'].lower( ) == USER_EMAIL.lower(): notificationDialog(LANGUAGE(30006) % (loginlink['displayName']), time=4000) self.net.save_cookies(COOKIE_JAR) else: notificationDialog(LANGUAGE(30007), time=4000) except Exception as e: log('login, failed! ' + str(e), xbmc.LOGERROR) else: #firstrun wizard if yesnoDialog(LANGUAGE(30008), no=LANGUAGE(30009), yes=LANGUAGE(30010)): REAL_SETTINGS.setSetting('User_Email', inputDialog(LANGUAGE(30001))) REAL_SETTINGS.setSetting('User_Password', inputDialog(LANGUAGE(30002))) else: REAL_SETTINGS.setSetting('User_Email', LANGUAGE(30009)) def openURL(self, url, life=datetime.timedelta(minutes=15)): log('openURL, url = ' + url) try: header_dict = {} header_dict[ 'Accept'] = 'application/json, text/javascript, */*; q=0.01' header_dict['Host'] = 'api.pluto.tv' header_dict['Connection'] = 'keep-alive' header_dict['Referer'] = 'http://pluto.tv/' header_dict['Origin'] = 'http://pluto.tv' header_dict[ 'User-Agent'] = 'Mozilla/5.0 (Windows NT 6.2; rv:24.0) Gecko/20100101 Firefox/24.0' self.net.set_cookies(COOKIE_JAR) trans_table = ''.join([chr(i) for i in range(128)] + [' '] * 128) cacheResponse = self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) if not cacheResponse: try: cacheResponse = self.net.http_GET( url, headers=header_dict).content.encode("utf-8", 'ignore') except: cacheResponse = (self.net.http_GET( url, headers=header_dict).content.translate(trans_table) ).encode("utf-8") self.net.save_cookies(COOKIE_JAR) self.cache.set(ADDON_NAME + '.openURL, url = %s' % url, cacheResponse, expiration=life) if isinstance(cacheResponse, basestring): cacheResponse = json.loads(cacheResponse) return cacheResponse except Exception as e: log('openURL, Unable to open url ' + str(e), xbmc.LOGERROR) notificationDialog(LANGUAGE(30028), time=4000) return {} def mainMenu(self): log('mainMenu') self.login() for item in PLUTO_MENU: self.addDir(*item) def browseMenu(self): log('browseMenu') categoryMenu = self.getCategories() for item in categoryMenu: self.addDir(*item) def getOndemand(self): devid = LANGUAGE(30022) % (REAL_SETTINGS.getSetting("sid"), REAL_SETTINGS.getSetting("deviceId")) return self.openURL(BASE_VOD % (devid), life=datetime.timedelta(hours=4)) def getVOD(self, epid): devid = LANGUAGE(30022) % (REAL_SETTINGS.getSetting("sid"), REAL_SETTINGS.getSetting("deviceId")) return self.openURL(SEAONS_VOD % (epid, devid), life=datetime.timedelta(hours=4)) def getClips(self, epid): return self.openURL(BASE_CLIPS % (epid), life=datetime.timedelta(hours=4)) def getChannels(self): return sorted(self.openURL(BASE_LINEUP, life=datetime.timedelta(hours=2)), key=lambda i: i['number']) def getGuidedata(self): tz = str(timezone()) start = datetime.datetime.now().strftime('%Y-%m-%dT%H:00:00').replace( 'T', '%20').replace(':00:00', '%3A00%3A00.000' + tz) stop = ( datetime.datetime.now() + datetime.timedelta(hours=4)).strftime('%Y-%m-%dT%H:00:00').replace( 'T', '%20').replace(':00:00', '%3A00%3A00.000' + tz) devid = 'sid=%s&deviceId=%s' % (REAL_SETTINGS.getSetting("sid"), REAL_SETTINGS.getSetting("deviceId")) return sorted((self.openURL(BASE_GUIDE % (start, stop, devid), life=datetime.timedelta(hours=1))), key=lambda i: i['number']) def getCategories(self): log('getCategories') collect = [] data = self.getChannels() for channel in data: collect.append(channel['category']) counter = collections.Counter(collect) for key, value in sorted(counter.iteritems()): yield (key, 'categories', 0) def getMediaTypes(self): mediaType = {} categoryMenu = self.getCategories() for type in categoryMenu: type = type[0] if type == 'Movies': mediaType[type] = 'movie' elif type == 'TV': mediaType[type] = 'episodes' elif type == 'Music + Radio': mediaType[type] = 'musicvideo' else: mediaType[type] = 'video' return mediaType def pagination(self, seq, rowlen): for start in xrange(0, len(seq), rowlen): yield seq[start:start + rowlen] def buildGuide(self, data): channel, name, opt = data log('buildGuide, name=%s,opt=%s' % (name, opt)) urls = [] guidedata = [] newChannel = {} mtype = 'videos' chid = channel.get('_id', '') chname = channel.get('name', '') chnum = channel.get('number', '') chplot = (channel.get('description', '') or channel.get('summary', '')) chgeo = channel.get('visibility', 'everyone') != 'everyone' chcat = (channel.get('category', '') or channel.get('genre', '')) chfanart = channel.get('featuredImage', {}).get('path', FANART) chthumb = channel.get('thumbnail', {}).get('path', ICON) chlogo = channel.get('logo', {}).get('path', ICON) ondemand = channel.get('onDemand', 'false') == 'true' featured = channel.get('featured', 'false') == 'true' favorite = channel.get('favorite', 'false') == 'true' timelines = channel.get('timelines', []) if name == 'featured' and not featured: return None elif name == 'favorite' and not favorite: return None elif name == 'categories' and chcat != opt: return None elif name == 'lineup' and chid != opt: return None elif name == 'live': DISC_CACHE = False if name in ['channels', 'categories', 'ondemand', 'season']: if name == 'season': seasons = (channel.get('seasons', {})) vodimages = channel.get('covers', []) try: vodlogo = [ image.get('url', []) for image in vodimages if image.get('aspectRatio', '') == '1:1' ][0] except: vodlogo = ICON try: vodfanart = [ image.get('url', []) for image in vodimages if image.get('aspectRatio', '') == '16:9' ][0] except: vodfanart = FANART for season in seasons: mtype = 'episodes' label = 'Season %s' % (season['number']) infoLabels = { "mediatype": mtype, "label": label, "label2": label, "title": chname, "plot": chplot, "code": chid, "genre": [chcat] } infoArt = { "thumb": vodlogo, "poster": vodlogo, "fanart": vodfanart, "icon": vodlogo, "logo": vodlogo, "clearart": chthumb } self.addDir(label, chid, 5, infoLabels, infoArt) else: if name == 'ondemand': mode = 3 label = chname else: mode = 1 label = '%s| %s' % (chnum, chname) infoLabels = { "mediatype": mtype, "label": label, "label2": label, "title": label, "plot": chplot, "code": chid, "genre": [chcat] } infoArt = { "thumb": chthumb, "poster": chthumb, "fanart": chfanart, "icon": chlogo, "logo": chlogo, "clearart": chthumb } self.addDir(label, chid, mode, infoLabels, infoArt) else: newChannel['channelname'] = chname newChannel['channelnumber'] = chnum newChannel['channellogo'] = chlogo newChannel['isfavorite'] = favorite urls = channel.get('stitched', {}).get('urls', []) if not timelines: name = 'ondemand' timelines = (channel.get('items', []) or channel.get('episodes', [])) now = datetime.datetime.now() totstart = now tz = (timezone() // 100) * 60 * 60 for item in timelines: episode = (item.get('episode', {}) or item) series = (episode.get('series', {}) or item) urls = (item.get('stitched', {}).get('urls', []) or urls) epdur = int(episode.get('duration', '0') or '0') // 1000 try: start = strpTime(item['start'], '%Y-%m-%dT%H:%M:00.000Z' ) + datetime.timedelta(seconds=tz) stop = start + datetime.timedelta(seconds=epdur) except: start = totstart stop = start + datetime.timedelta(seconds=epdur) totstart = stop type = series.get('type', '') tvtitle = series.get('name', '' or chname) title = (item.get('title', '')) tvplot = (series.get('description', '') or series.get('summary', '') or chplot) tvoutline = (series.get('summary', '') or series.get('description', '') or chplot) tvthumb = (series.get('title', {}).get('path', '') or chthumb) tvfanart = (series.get('featuredImage', {}).get('path', '') or chfanart) epid = episode['_id'] epnumber = episode.get('number', 0) epseason = episode.get('season', 0) epname = (episode['name']) epplot = (episode.get('description', '') or tvplot or epname) epgenre = (episode.get('genre', '') or chcat) eptag = episode.get('subGenre', '') epmpaa = episode.get('rating', '') vodimages = episode.get('covers', []) vodposter = vodfanart = vodthumb = vodlogo = '' if vodimages: try: vodposter = [ image.get('url', []) for image in vodimages if image.get('aspectRatio', '') == '347:500' ][0] except: pass try: vodfanart = [ image.get('url', []) for image in vodimages if image.get('aspectRatio', '') == '16:9' ][0] except: pass try: vodthumb = [ image.get('url', []) for image in vodimages if image.get('aspectRatio', '') == '4:3' ][0] except: pass try: vodlogo = [ image.get('url', []) for image in vodimages if image.get('aspectRatio', '') == '1:1' ][0] except: pass chlogo = (vodlogo or chlogo) epposter = (episode.get('poster', {}).get('path', '') or vodlogo or vodposter or vodthumb or tvthumb) epthumb = (episode.get('thumbnail', {}).get('path', '') or vodlogo or vodthumb or vodposter or tvthumb) epfanart = (episode.get('featuredImage', {}).get('path', '') or vodfanart or tvfanart) epislive = episode.get('liveBroadcast', 'false') == 'true' label = title thumb = chthumb if type in ['movie', 'film']: mtype = 'movies' thumb = epposter elif type in ['tv', 'episode', 'series']: mtype = 'episodes' thumb = epposter if epseason > 0 and epnumber > 0: label = '%sx%s' % (epseason, epnumber) label = '%s - %s' % (label, epname) # else: label = '%s - %s'%(tvtitle, label) else: label = epname epname = label if name == 'live': if stop < now or start > now: continue label = '%s| %s' % (chnum, chname) if type in ['movie', 'film']: mtype = 'movies' thumb = epposter label = '%s :[B]%s[/B]' % (label, title) elif type in ['tv', 'series']: mtype = 'episodes' thumb = epposter label = "%s :[B]%s - %s[/B]" % (label, tvtitle, epname) epname = label elif name == 'lineup': if now > stop: continue if now >= start and now < stop: label = '%s - [B]%s[/B]' % ( start.strftime('%I:%M %p').lstrip('0'), label) else: label = '%s - %s' % ( start.strftime('%I:%M %p').lstrip('0'), label) epname = label if len(urls) == 0: continue if isinstance(urls, list): urls = [ url['url'] for url in urls if url['type'].lower() == 'hls' ][0] tmpdata = { "mediatype": mtype, "label": label, "title": label, 'duration': epdur, 'plot': epplot, 'genre': [epgenre], 'season': epseason, 'episode': epnumber } tmpdata['starttime'] = time.mktime((start).timetuple()) tmpdata['url'] = self.sysARG[0] + '?mode=9&name=%s&url=%s' % ( title, urls) tmpdata['art'] = { "thumb": thumb, "poster": epposter, "fanart": epfanart, "icon": chlogo, "logo": chlogo, "clearart": chthumb } guidedata.append(tmpdata) if name == 'ondemand' and type == "series": mtype = 'seasons' infoLabels = { "mediatype": mtype, "label": label, "label2": label, "title": label, "plot": epplot, "code": chid, "genre": [epgenre] } infoArt = { "thumb": epthumb, "poster": epposter, "fanart": epfanart, "icon": chlogo, "logo": chlogo, "clearart": chthumb } self.addDir(label, epid, 4, infoLabels, infoArt) elif name != 'guide': infoLabels = { "mediatype": mtype, "label": label, "label2": label, "tvshowtitle": tvtitle, "title": epname, "plot": epplot, "code": epid, "genre": [epgenre], "duration": epdur, 'season': epseason, 'episode': epnumber } infoArt = { "thumb": thumb, "poster": epposter, "fanart": epfanart, "icon": chlogo, "logo": chlogo, "clearart": chthumb } self.addLink(title, urls, 9, infoLabels, infoArt) CONTENT_TYPE = mtype if len(guidedata) > 0: newChannel['guidedata'] = guidedata return newChannel def uEPG(self): log('uEPG') data = self.getGuidedata() return urllib.quote( json.dumps( list( self.poolList(self.buildGuide, zip(data, repeat('guide'), repeat('')))))) def browseGuide(self, name, opt=None, data=None): log('browseGuide, name=%s, opt=%s' % (name, opt)) if data is None: data = self.getGuidedata() if opt == 'categories': opt = name name = 'categories' self.poolList(self.buildGuide, zip(data, repeat(name.lower()), repeat(opt))) def browseLineup(self, name, opt=None): log('browseLineup, opt=%s' % opt) if opt is None: name = 'channels' else: name = 'lineup' self.browseGuide(name, opt) def browseOndemand(self, opt=None): log('browseOndemand') data = self.getOndemand()['categories'] if opt is None: name = 'ondemand' else: name = 'lineup' self.browseGuide(name, opt, data) def browseSeason(self, opt=None): log('browseSeason') data = [self.getVOD(opt)] self.browseGuide('season', opt, data) def browseEpisodes(self, name, opt=None): log('browseEpisodes') season = int(name.split('Season ')[1]) data = [self.getVOD(opt).get('seasons', [])[season - 1]] self.browseGuide('episode', opt, data) def browseCategories(self): log('browseCategories') data = list(self.getCategories()) for item in data: self.addDir(*item) def playVideo(self, name, url, liz=None): if LANGUAGE(30019) not in url: url = LANGUAGE(30021) % (url, LANGUAGE(30020) % (REAL_SETTINGS.getSetting("sid"), REAL_SETTINGS.getSetting("deviceId"))) log('playVideo, url = %s' % url) if liz is None: liz = xbmcgui.ListItem(name, path=url) if 'm3u8' in url.lower() and inputstreamhelper.Helper( 'hls').check_inputstream() and not DEBUG: liz.setProperty('inputstreamaddon', 'inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type', 'hls') xbmcplugin.setResolvedUrl(int(self.sysARG[1]), True, liz) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = self.sysARG[0] + "?url=" + urllib.quote(u) + "&mode=" + str( mode) + "&name=" + urllib.quote(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=u, listitem=liz, totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = self.sysARG[0] + "?url=" + urllib.quote(u) + "&mode=" + str( mode) + "&name=" + urllib.quote(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=u, listitem=liz, isFolder=True) def poolList(self, method, items): results = [] if ENABLE_POOL and not DEBUG: pool = ThreadPool(CORES) results = pool.imap_unordered(method, items, chunksize=25) pool.close() pool.join() else: results = [method(item) for item in items] results = filter(None, results) return results def getParams(self): return dict(parse_qsl(self.sysARG[2][1:])) def run(self): params = self.getParams() try: url = urllib.unquote_plus(params["url"]) except: url = None try: name = urllib.unquote_plus(params["name"]) except: name = None try: mode = int(params["mode"]) except: mode = None log("Mode: " + str(mode)) log("URL : " + str(url)) log("Name: " + str(name)) if mode == None: self.mainMenu() elif mode == 0: self.browseGuide(name, url) elif mode == 1: self.browseLineup(name, url) elif mode == 2: self.browseCategories() elif mode == 3: self.browseOndemand(url) elif mode == 4: self.browseSeason(url) elif mode == 5: self.browseEpisodes(name, url) elif mode == 9: self.playVideo(name, url) elif mode == 20: xbmc.executebuiltin( "RunScript(script.module.uepg,json=%s&skin_path=%s&refresh_path=%s&refresh_interval=%s&row_count=%s&include_hdhr=false)" % (self.uEPG(), urllib.quote(ADDON_PATH), urllib.quote(self.sysARG[0] + "?mode=20"), "7200", "5")) xbmcplugin.setContent(int(self.sysARG[1]), CONTENT_TYPE) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_UNSORTED) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_NONE) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_TITLE) xbmcplugin.endOfDirectory(int(self.sysARG[1]), cacheToDisc=DISC_CACHE)
class NewsBlender(object): def __init__(self): self.cache = SimpleCache() self.sources = self.openURL(SOURCES_URL).get('sources','') def openURL(self, url): log('openURL, url = ' + url) try: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheresponse: request = urllib2.Request(url) request.add_header('User-Agent','Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)') request.add_header('Accept-type', 'application/json') response = urllib2.urlopen(request, timeout = TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, response, expiration=datetime.timedelta(hours=1)) return json.loads(self.cache.get(ADDON_NAME + '.openURL, url = %s'%url)) except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self): for idx, item in enumerate(MAIN_MENU): self.addDir(item,'',idx) def buildCategory(self): category = collections.Counter([x['category'] for x in self.sources]) for category, value in sorted(category.iteritems()): self.addDir(category.title(),category,4) def buildCountry(self): countries = collections.Counter([x['country'] for x in self.sources]) for country, value in sorted(countries.iteritems()): self.addDir(getRegionName(country),country,6) def buildLanguage(self): languages = collections.Counter([x['language'] for x in self.sources]) for language, value in sorted(languages.iteritems()): self.addDir(getLanguageName(language),language,7) def buildSource(self, items=None): if items is None: items = self.sources for source in items: label = source['name'] thumb = (LOGO_URL%source['url'] or ICON) infoLabels = {"mediatype":"files","label":label,"title":label,"genre":source.get('category','news'),"plot":source.get('description','news')} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addDir(label, source['id'], 5, infoLabels, infoArt) def browseCategory(self, url): self.buildSource(self.openURL(SOURCES_URL + '&category=%s'%url).get('sources','')) def browseCountry(self, url): self.buildSource(self.openURL(SOURCES_URL + '&country=%s'%url).get('sources','')) def browseLanguage(self, url): self.buildSource(self.openURL(SOURCES_URL + '&language=%s'%url).get('sources','')) def browseTop(self, url): self.browse(self.newsArticles.get_by_top(url).get('sources','')) def browseLatest(self, url): self.browse(self.newsArticles.get_by_latest(url).get('sources','')) def browsePopular(self, url): self.browse(self.newsArticles.get_by_popular(url).get('sources','')) def search(self, name, url): kb = xbmc.Keyboard('', LANGUAGE(30005)%name) xbmc.sleep(1000) kb.doModal() if kb.isConfirmed(): url = (EVRYTHING_URL + '&q=%s&sources=%s'%(urllib.quote_plus(kb.getText()),url)).split('|')[0] try: self.browseArticles(name, url, self.openURL(url).get('articles',''), False) except Exception as e: log('search, failed ' + str(e), xbmc.LOGERROR) def buildArticles(self, name, url): self.browseArticles(name, url, self.openURL(HEADLINE_URL + '&sources=%s'%url).get('articles','')) def browseArticles(self, name, url, items, search=True): tmpList = [] for idx, item in enumerate(items): info = self.getVideo(item['url']) if info is None or len(info) == 0: continue source = item['source']['name'] label = item['title'] thumb = item['urlToImage'] plot = item['description'] try: aired = item['publishedAt'].split('T')[0] except: aired = (datetime.datetime.now()).strftime('%Y-%m-%d') tmpList.append((source, label, thumb, plot, aired, info)) dlg = busyDialog(0) for idx, data in enumerate(tmpList): busyDialog(idx * 100 // len(tmpList),dlg) try: source, label, thumb, plot, aired, info = data url = info[0]['xbmc_url'] try: if 'subtitles' in info[0]['ytdl_format']: liz.setSubtitles([x['url'] for x in info[0]['ytdl_format']['subtitles'].get('en','') if 'url' in x]) except: pass infoLabels = {"mediatype":"episode","label":label ,"title":label,"duration":info[0]['ytdl_format'].get('duration',0),"aired":aired,"plot":plot,"genre":"News"} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addLink(label, url, 9, infoLabels, infoArt) except: pass busyDialog(100,dlg) if len(tmpList) == 0: self.addLink((LANGUAGE(30003)%name), "", "") elif search: self.addSearch(name, url) def getVideo(self, url): cacheresponse = self.cache.get(ADDON_NAME + '.getVideo, url = %s'%url) if not cacheresponse: info = getVideoInfo(url,QUALITY,True) if info is not None: info = info.streams() self.cache.set(ADDON_NAME + '.getVideo, url = %s'%url, json.dumps(info), expiration=datetime.timedelta(days=14)) return json.loads(self.cache.get(ADDON_NAME + '.getVideo, url = %s'%url)) def playVideo(self, name, url, liz=None): log('playVideo') if liz is None: liz = xbmcgui.ListItem(name, path=url) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addSearch(self, name, url): self.addDir((LANGUAGE(30004)%name), url, 8) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':LOGO_URL%urllib.quote_plus(name),'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) #LOGO_URL%urllib.quote_plus(name) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
class ListItemMonitor(threading.Thread): '''Our main class monitoring the kodi listitems and providing additional information''' event = None exit = False delayed_task_interval = 1795 listitem_details = {} all_window_props = {} cur_listitem = "" last_folder = "" last_listitem = "" foldercontent = {} screensaver_setting = None screensaver_disabled = False lookup_busy = {} enable_extendedart = False enable_musicart = False enable_animatedart = False enable_extrafanart = False enable_extraposter = False enable_pvrart = False enable_forcedviews = False def __init__(self, *args, **kwargs): self.cache = SimpleCache() self.metadatautils = kwargs.get("metadatautils") self.win = kwargs.get("win") self.kodimonitor = kwargs.get("monitor") self.event = threading.Event() threading.Thread.__init__(self, *args) def stop(self): '''called when the thread has to stop working''' log_msg("ListItemMonitor - stop called") self.exit = True self.cache.close() self.event.set() self.event.clear() self.join(1) def run(self): '''our main loop monitoring the listitem and folderpath changes''' log_msg("ListItemMonitor - started") self.get_settings() while not self.exit: # check screensaver and OSD self.check_screensaver() self.check_osd() # do some background stuff every 30 minutes if (self.delayed_task_interval >= 1800) and not self.exit: thread.start_new_thread(self.do_background_work, ()) self.delayed_task_interval = 0 # skip if any of the artwork context menus is opened if self.win.getProperty("SkinHelper.Artwork.ManualLookup"): self.reset_win_props() self.last_listitem = "" self.listitem_details = {} self.kodimonitor.waitForAbort(3) self.delayed_task_interval += 3 # skip when modal dialogs are opened (e.g. textviewer in musicinfo dialog) elif getCondVisibility( "Window.IsActive(DialogSelect.xml) | Window.IsActive(progressdialog) | " "Window.IsActive(contextmenu) | Window.IsActive(busydialog)" ): self.kodimonitor.waitForAbort(2) self.delayed_task_interval += 2 self.last_listitem = "" # skip when container scrolling elif getCondVisibility( "Container.OnScrollNext | Container.OnScrollPrevious | Container.Scrolling" ): self.kodimonitor.waitForAbort(1) self.delayed_task_interval += 1 self.last_listitem = "" # media window is opened or widgetcontainer set - start listitem monitoring! elif getCondVisibility( "Window.IsMedia | " "!IsEmpty(Window(Home).Property(SkinHelper.WidgetContainer))" ): self.monitor_listitem() self.kodimonitor.waitForAbort(0.15) self.delayed_task_interval += 0.15 # flush any remaining window properties elif self.all_window_props: self.reset_win_props() self.win.clearProperty("SkinHelper.ContentHeader") self.win.clearProperty("contenttype") self.win.clearProperty("curlistitem") self.last_listitem = "" # other window active - do nothing else: self.kodimonitor.waitForAbort(1) self.delayed_task_interval += 1 def get_settings(self): '''collect our skin settings that control the monitoring''' self.enable_extendedart = getCondVisibility( "Skin.HasSetting(SkinHelper.EnableExtendedArt)") == 1 self.enable_musicart = getCondVisibility( "Skin.HasSetting(SkinHelper.EnableMusicArt)") == 1 self.enable_animatedart = getCondVisibility( "Skin.HasSetting(SkinHelper.EnableAnimatedPosters)") == 1 self.enable_extrafanart = getCondVisibility( "Skin.HasSetting(SkinHelper.EnableExtraFanart)") == 1 self.enable_extraposter = getCondVisibility( "Skin.HasSetting(SkinHelper.EnableExtraPoster)") == 1 self.enable_pvrart = getCondVisibility( "Skin.HasSetting(SkinHelper.EnablePVRThumbs) + PVR.HasTVChannels" ) == 1 self.enable_forcedviews = getCondVisibility( "Skin.HasSetting(SkinHelper.ForcedViews.Enabled)") == 1 studiologos_path = xbmc.getInfoLabel( "Skin.String(SkinHelper.StudioLogos.Path)") if studiologos_path != self.metadatautils.studiologos_path: self.listitem_details = {} self.metadatautils.studiologos_path = studiologos_path # set additional window props to control contextmenus as using the skinsetting gives unreliable results for skinsetting in [ "EnableAnimatedPosters", "EnableMusicArt", "EnablePVRThumbs" ]: if getCondVisibility("Skin.HasSetting(SkinHelper.%s)" % skinsetting): self.win.setProperty("SkinHelper.%s" % skinsetting, "enabled") else: self.win.clearProperty("SkinHelper.%s" % skinsetting) def monitor_listitem(self): '''Monitor listitem details''' cur_folder, cont_prefix = self.get_folderandprefix() # identify current listitem - prefer parent folder (tvshows, music) cur_listitem = try_decode( xbmc.getInfoLabel( "$INFO[%sListItem.TvshowTitle]$INFO[%sListItem.Artist]$INFO[%sListItem.Album]" % (cont_prefix, cont_prefix, cont_prefix))) if not cur_listitem: # fallback to generic approach cur_listitem = try_decode( xbmc.getInfoLabel( "$INFO[%sListItem.Label]$INFO[%sListItem.DBID]$INFO[%sListItem.Title]" % (cont_prefix, cont_prefix, cont_prefix))) if self.exit: return # perform actions if the container path has changed if cur_folder != self.last_folder and not ( cur_listitem and cur_listitem == self.last_listitem): self.reset_win_props() self.get_settings() self.last_folder = cur_folder self.last_listitem = None content_type = self.get_content_type(cur_folder, cur_listitem, cont_prefix) # additional actions to perform when we have a valid contenttype and no widget container if not cont_prefix and content_type: self.set_forcedview(content_type) self.set_content_header(content_type) else: content_type = self.get_content_type(cur_folder, cur_listitem, cont_prefix) if self.exit: return # only perform actions when the listitem has actually changed if cur_listitem != self.last_listitem: self.last_listitem = cur_listitem self.win.setProperty("curlistitem", cur_listitem) if cur_listitem and cur_listitem != "..": # set listitem details in background thread thread.start_new_thread( self.set_listitem_details, (cur_listitem, content_type, cont_prefix)) def get_folderandprefix(self): '''get the current folder and prefix''' cur_folder = "" cont_prefix = "" try: widget_container = try_decode( self.win.getProperty("SkinHelper.WidgetContainer")) if getCondVisibility("Window.IsActive(movieinformation)"): cont_prefix = "" cur_folder = try_decode( xbmc.getInfoLabel( "$INFO[Window.Property(xmlfile)]$INFO[Container.FolderPath]" "$INFO[Container.NumItems]$INFO[Container.Content]")) elif widget_container: cont_prefix = "Container(%s)." % widget_container cur_folder = try_decode( xbmc.getInfoLabel( "widget-%s-$INFO[Container(%s).NumItems]-$INFO[Container(%s).ListItemAbsolute(1).Label]" % (widget_container, widget_container, widget_container))) else: cont_prefix = "" cur_folder = try_decode( xbmc.getInfoLabel( "$INFO[Window.Property(xmlfile)]$INFO[Container.FolderPath]$INFO[Container.NumItems]$INFO[Container.Content]" )) except Exception as exc: log_exception(__name__, exc) cur_folder = "" cont_prefix = "" return (cur_folder, cont_prefix) def get_content_type(self, cur_folder, cur_listitem, cont_prefix): '''get contenttype for current folder''' content_type = "" if cur_folder in self.foldercontent: content_type = self.foldercontent[cur_folder] elif cur_folder and cur_listitem: # always wait for the content_type because some listings can be slow for i in range(20): content_type = get_current_content_type(cont_prefix) if self.exit: return "" if content_type: break else: xbmc.sleep(250) self.foldercontent[cur_folder] = content_type self.win.setProperty("contenttype", content_type) return content_type def check_screensaver(self): '''Allow user to disable screensaver on fullscreen music playback''' if getCondVisibility( "Window.IsActive(visualisation) + Skin.HasSetting(SkinHelper.DisableScreenSaverOnFullScreenMusic)" ): if not self.screensaver_disabled: # disable screensaver when fullscreen music active self.screensaver_disabled = True screensaver_setting = kodi_json( 'Settings.GetSettingValue', '{"setting":"screensaver.mode"}') if screensaver_setting: self.screensaver_setting = screensaver_setting kodi_json('Settings.SetSettingValue', { "setting": "screensaver.mode", "value": None }) log_msg( "Disabled screensaver while fullscreen music playback - previous setting: %s" % self.screensaver_setting, xbmc.LOGNOTICE) elif self.screensaver_disabled and self.screensaver_setting: # enable screensaver again after fullscreen music playback was ended kodi_json('Settings.SetSettingValue', { "setting": "screensaver.mode", "value": self.screensaver_setting }) self.screensaver_disabled = False self.screensaver_setting = None log_msg( "fullscreen music playback ended - restoring screensaver: %s" % self.screensaver_setting, xbmc.LOGNOTICE) @staticmethod def check_osd(): '''Allow user to set a default close timeout for the OSD panels''' if getCondVisibility( "[Window.IsActive(videoosd) + Skin.String(SkinHelper.AutoCloseVideoOSD)] | " "[Window.IsActive(musicosd) + Skin.String(SkinHelper.AutoCloseMusicOSD)]" ): if getCondVisibility("Window.IsActive(videoosd)"): seconds = xbmc.getInfoLabel( "Skin.String(SkinHelper.AutoCloseVideoOSD)") window = "videoosd" elif getCondVisibility("Window.IsActive(musicosd)"): seconds = xbmc.getInfoLabel( "Skin.String(SkinHelper.AutoCloseMusicOSD)") window = "musicosd" else: seconds = "" if seconds and seconds != "0": while getCondVisibility("Window.IsActive(%s)" % window): if getCondVisibility("System.IdleTime(%s)" % seconds): if getCondVisibility("Window.IsActive(%s)" % window): xbmc.executebuiltin("Dialog.Close(%s)" % window) else: xbmc.sleep(500) def set_listitem_details(self, cur_listitem, content_type, prefix): '''set the window properties based on the current listitem''' try: if cur_listitem in self.listitem_details: # data already in memory all_props = self.listitem_details[cur_listitem] else: # skip if another lookup for the same listitem is already in progress... if self.lookup_busy.get(cur_listitem) or self.exit: return self.lookup_busy[cur_listitem] = True # clear all window props, do this delayed to prevent flickering of the screen thread.start_new_thread(self.delayed_flush, (cur_listitem, )) # wait if we already have more than 5 items in the queue while len(self.lookup_busy) > 5: xbmc.sleep(100) if self.exit or cur_listitem != self.last_listitem: self.lookup_busy.pop(cur_listitem, None) return # prefer listitem's contenttype over container's contenttype dbtype = xbmc.getInfoLabel("%sListItem.DBTYPE" % prefix) if not dbtype: dbtype = xbmc.getInfoLabel("%sListItem.Property(DBTYPE)" % prefix) if dbtype: content_type = dbtype + "s" # collect details from listitem details = self.get_listitem_details(content_type, prefix) if self.exit: return # music content if content_type in ["albums", "artists", "songs" ] and self.enable_musicart: details = self.metadatautils.extend_dict( details, self.metadatautils.get_music_artwork( details["artist"], details["album"], details["title"], details["discnumber"])) # moviesets elif details["path"].startswith( "videodb://movies/sets/") and details["dbid"]: details = self.metadatautils.extend_dict( details, self.metadatautils.get_moviesetdetails( details["title"], details["dbid"]), ["year"]) content_type = "sets" # video content elif content_type in [ "movies", "setmovies", "tvshows", "seasons", "episodes", "musicvideos" ]: # get imdb and tvdbid details[ "imdbnumber"], tvdbid = self.metadatautils.get_imdbtvdb_id( details["title"], content_type, details["year"], details["imdbnumber"], details["tvshowtitle"]) if self.exit: return # generic video properties (studio, streamdetails, omdb, top250) details = merge_dict( details, self.get_directors_writers(details["director"], details["writer"])) if self.enable_extrafanart: log_msg("skin.helper.service: extrafanart", xbmc.LOGNOTICE) if not details["filenameandpath"]: details["filenameandpath"] = details["path"] if "videodb://" not in details["filenameandpath"]: efa = self.metadatautils.get_extrafanart( details["filenameandpath"]) if efa: details["art"] = merge_dict( details["art"], efa["art"]) if self.enable_extraposter: if not details["filenameandpath"]: details["filenameandpath"] = details["path"] if "videodb://" not in details["filenameandpath"]: efa = self.metadatautils.get_extraposter( details["filenameandpath"]) if efa: details["art"] = merge_dict( details["art"], efa["art"]) if self.exit: return details = merge_dict( details, self.metadatautils.get_duration(details["duration"])) details = merge_dict(details, self.get_genres(details["genre"])) details = merge_dict( details, self.metadatautils.get_studio_logo(details["studio"])) details = merge_dict( details, self.metadatautils.get_omdb_info( details["imdbnumber"])) details = merge_dict( details, self.get_streamdetails(details["dbid"], details["path"], content_type)) details = merge_dict( details, self.metadatautils.get_top250_rating( details["imdbnumber"])) if self.exit: return # tvshows-only properties (tvdb) if content_type in ["tvshows", "seasons", "episodes"]: details = merge_dict( details, self.metadatautils.get_tvdb_details( details["imdbnumber"], tvdbid)) # movies-only properties (tmdb, animated art) if content_type in ["movies", "setmovies"]: details = merge_dict( details, self.metadatautils.get_tmdb_details( details["imdbnumber"])) if details["imdbnumber"] and self.enable_animatedart: details = self.metadatautils.extend_dict( details, self.metadatautils.get_animated_artwork( details["imdbnumber"])) if self.exit: return # extended art if self.enable_extendedart: tmdbid = details.get("tmdb_id", "") details = self.metadatautils.extend_dict( details, self.metadatautils.get_extended_artwork( details["imdbnumber"], tvdbid, tmdbid, content_type), [ "posters", "clearlogos", "banners", "discarts", "cleararts", "characterarts" ]) # monitor listitem props when PVR is active elif content_type in [ "tvchannels", "tvrecordings", "channels", "recordings", "timers", "tvtimers" ]: details = self.get_pvr_artwork(details, prefix) # process all properties all_props = prepare_win_props(details) if "sets" not in content_type: self.listitem_details[cur_listitem] = all_props self.lookup_busy.pop(cur_listitem, None) if cur_listitem == self.last_listitem: self.set_win_props(all_props) except Exception as exc: log_exception(__name__, exc) self.lookup_busy.pop(cur_listitem, None) def delayed_flush(self, cur_listitem): '''flushes existing properties when it takes too long to grab the new ones''' xbmc.sleep(500) if cur_listitem == self.last_listitem and cur_listitem in self.lookup_busy: self.reset_win_props() def do_background_work(self): '''stuff that's processed in the background''' try: if self.exit: return log_msg("Started Background worker...") self.set_generic_props() self.listitem_details = {} if self.exit: return self.cache.check_cleanup() log_msg("Ended Background worker...") except Exception as exc: log_exception(__name__, exc) def set_generic_props(self): '''set some generic window props with item counts''' # GET TOTAL ADDONS COUNT addons_count = len(kodi_json('Addons.GetAddons')) self.win.setProperty("SkinHelper.TotalAddons", "%s" % addons_count) addontypes = [] addontypes.append(("executable", "SkinHelper.TotalProgramAddons")) addontypes.append(("video", "SkinHelper.TotalVideoAddons")) addontypes.append(("audio", "SkinHelper.TotalAudioAddons")) addontypes.append(("image", "SkinHelper.TotalPicturesAddons")) for addontype in addontypes: media_array = kodi_json('Addons.GetAddons', {"content": addontype[0]}) self.win.setProperty(addontype[1], str(len(media_array))) # GET FAVOURITES COUNT favs = kodi_json('Favourites.GetFavourites') if favs: self.win.setProperty("SkinHelper.TotalFavourites", "%s" % len(favs)) if self.exit: return # GET TV CHANNELS COUNT if getCondVisibility("Pvr.HasTVChannels"): tv_channels = kodi_json('PVR.GetChannels', {"channelgroupid": "alltv"}) self.win.setProperty("SkinHelper.TotalTVChannels", "%s" % len(tv_channels)) if self.exit: return # GET MOVIE SETS COUNT movieset_movies_count = 0 moviesets = kodi_json('VideoLibrary.GetMovieSets') for item in moviesets: for item in kodi_json('VideoLibrary.GetMovieSetDetails', {"setid": item["setid"]}): movieset_movies_count += 1 self.win.setProperty("SkinHelper.TotalMovieSets", "%s" % len(moviesets)) self.win.setProperty("SkinHelper.TotalMoviesInSets", "%s" % movieset_movies_count) if self.exit: return # GET RADIO CHANNELS COUNT if getCondVisibility("Pvr.HasRadioChannels"): radio_channels = kodi_json('PVR.GetChannels', {"channelgroupid": "allradio"}) self.win.setProperty("SkinHelper.TotalRadioChannels", "%s" % len(radio_channels)) def reset_win_props(self): '''reset all window props set by the script...''' self.metadatautils.process_method_on_list( self.win.clearProperty, iter(list(self.all_window_props.keys()))) self.all_window_props = {} def set_win_prop(self, prop_tuple): '''sets a window property based on the given key-value''' key = prop_tuple[0] value = prop_tuple[1] if (key not in self.all_window_props) or ( key in self.all_window_props and self.all_window_props[key] != value): self.all_window_props[key] = value self.win.setProperty(key, value) def set_win_props(self, prop_tuples): '''set multiple window properties from list of tuples''' self.metadatautils.process_method_on_list(self.set_win_prop, prop_tuples) # cleanup remaining properties new_keys = [item[0] for item in prop_tuples] for key, value in list(self.all_window_props.items()): if value and key not in new_keys: self.all_window_props[key] = "" self.win.clearProperty(key) def set_content_header(self, content_type): '''sets a window propery which can be used as headertitle''' self.win.clearProperty("SkinHelper.ContentHeader") itemscount = xbmc.getInfoLabel("Container.NumItems") if itemscount: if xbmc.getInfoLabel( "Container.ListItemNoWrap(0).Label" ).startswith("*") or xbmc.getInfoLabel( "Container.ListItemNoWrap(1).Label").startswith("*"): itemscount = int(itemscount) - 1 headerprefix = "" if content_type == "movies": headerprefix = xbmc.getLocalizedString(36901) elif content_type == "tvshows": headerprefix = xbmc.getLocalizedString(36903) elif content_type == "seasons": headerprefix = xbmc.getLocalizedString(36905) elif content_type == "episodes": headerprefix = xbmc.getLocalizedString(36907) elif content_type == "sets": headerprefix = xbmc.getLocalizedString(36911) elif content_type == "albums": headerprefix = xbmc.getLocalizedString(36919) elif content_type == "songs": headerprefix = xbmc.getLocalizedString(36921) elif content_type == "artists": headerprefix = xbmc.getLocalizedString(36917) if headerprefix: self.win.setProperty("SkinHelper.ContentHeader", "%s %s" % (itemscount, headerprefix)) @staticmethod def get_genres(genres): '''get formatted genre string from actual genre''' details = {} if not isinstance(genres, list): genres = genres.split(" / ") details['genres'] = "[CR]".join(genres) for count, genre in enumerate(genres): details["genre.%s" % count] = genre return details @staticmethod def get_directors_writers(director, writer): '''get a formatted string with directors/writers from the actual string''' directors = director.split(" / ") writers = writer.split(" / ") return { 'Directors': "[CR]".join(directors), 'Writers': "[CR]".join(writers) } def get_listitem_details(self, content_type, prefix): '''collect all listitem properties/values we need''' listitem_details = {"art": {}} # basic properties for prop in ["dbtype", "dbid", "imdbnumber"]: propvalue = try_decode( xbmc.getInfoLabel('$INFO[%sListItem.%s]' % (prefix, prop))) if not propvalue or propvalue == "-1": propvalue = try_decode( xbmc.getInfoLabel('$INFO[%sListItem.Property(%s)]' % (prefix, prop))) listitem_details[prop] = propvalue # generic properties props = [ "label", "title", "filenameandpath", "year", "genre", "path", "folderpath", "duration", "plot", "plotoutline", "label2", "icon", "thumb" ] # properties for media items if content_type in [ "movies", "tvshows", "seasons", "episodes", "musicvideos", "setmovies" ]: props += [ "studio", "tvshowtitle", "premiered", "director", "writer", "firstaired", "tagline", "rating", "season", "episode" ] # properties for music items elif content_type in ["musicvideos", "artists", "albums", "songs"]: props += ["artist", "album", "rating", "albumartist", "discnumber"] # properties for pvr items elif content_type in [ "tvchannels", "tvrecordings", "channels", "recordings", "timers", "tvtimers" ]: props += ["channel", "channelname"] for prop in props: if self.exit: break propvalue = try_decode( xbmc.getInfoLabel('$INFO[%sListItem.%s]' % (prefix, prop))) listitem_details[prop] = propvalue # artwork properties artprops = [ "fanart", "poster", "clearlogo", "clearart", "landscape", "thumb", "banner", "discart", "characterart" ] for prop in artprops: if self.exit: break propvalue = try_decode( xbmc.getInfoLabel('$INFO[%sListItem.Art(%s)]' % (prefix, prop))) if not propvalue: propvalue = try_decode( xbmc.getInfoLabel('$INFO[%sListItem.Art(tvshow.%s)]' % (prefix, prop))) if propvalue: listitem_details["art"][prop] = propvalue # fix for folderpath if not listitem_details.get( "path") and "folderpath" in listitem_details: listitem_details["path"] = listitem_details["folderpath"] # fix for thumb if "thumb" not in listitem_details[ "art"] and "thumb" in listitem_details: listitem_details["art"]["thumb"] = listitem_details["thumb"] if "fanart" not in listitem_details[ "art"] and "fanart" in listitem_details: listitem_details["art"]["fanart"] = listitem_details["fanart"] return listitem_details def get_streamdetails(self, li_dbid, li_path, content_type): '''get the streamdetails for the current video''' details = {} if li_dbid and content_type in [ "movies", "episodes", "musicvideos" ] and not li_path.startswith("videodb://movies/sets/"): details = self.metadatautils.get_streamdetails( li_dbid, content_type) return details def set_forcedview(self, content_type): '''helper to force the view in certain conditions''' if self.enable_forcedviews: cur_forced_view = xbmc.getInfoLabel( "Skin.String(SkinHelper.ForcedViews.%s)" % content_type) if getCondVisibility( "Control.IsVisible(%s) | IsEmpty(Container.Viewmode) | System.HasModalDialog | System.HasVisibleModalDialog" % cur_forced_view): # skip if the view is already visible or if we're not in an actual media window return if (content_type and cur_forced_view and cur_forced_view != "None" and not getCondVisibility("Window.IsActive(MyPvrGuide.xml)")): self.win.setProperty("SkinHelper.ForcedView", cur_forced_view) count = 0 while not getCondVisibility( "Control.HasFocus(%s)" % cur_forced_view): xbmc.sleep(100) xbmc.executebuiltin("Container.SetViewMode(%s)" % cur_forced_view) xbmc.executebuiltin("SetFocus(%s)" % cur_forced_view) count += 1 if count == 50 or self.exit: break else: self.win.clearProperty("SkinHelper.ForcedView") else: self.win.clearProperty("SkinHelper.ForcedView") def get_pvr_artwork(self, listitem, prefix): '''get pvr artwork from artwork module''' if self.enable_pvrart: if getCondVisibility( "%sListItem.IsFolder" % prefix ) and not listitem["channelname"] and not listitem["title"]: listitem["title"] = listitem["label"] listitem = self.metadatautils.extend_dict( listitem, self.metadatautils.get_pvr_artwork(listitem["title"], listitem["channelname"], listitem["genre"]), ["title", "genre", "genres", "thumb"]) # pvr channellogo if listitem["channelname"]: listitem["art"][ "ChannelLogo"] = self.metadatautils.get_channellogo( listitem["channelname"]) elif listitem.get("pvrchannel"): listitem["art"][ "ChannelLogo"] = self.metadatautils.get_channellogo( listitem["pvrchannel"]) return listitem
class AnimatedArt(object): '''get animated artwork''' ignore_cache = False def __init__(self, simplecache=None, kodidb=None): '''Initialize - optionaly provide SimpleCache and KodiDb object''' if not kodidb: from kodidb import KodiDb self.kodidb = KodiDb() else: self.kodidb = kodidb if not simplecache: from simplecache import SimpleCache self.cache = SimpleCache() else: self.cache = simplecache @use_cache(14) def get_animated_artwork(self, imdb_id, manual_select=False, ignore_cache=False): '''returns all available animated art for the given imdbid/tmdbid''' # no cache so grab the results result = { "animatedposter": self.poster(imdb_id, manual_select), "animatedfanart": self.fanart(imdb_id, manual_select), "imdb_id": imdb_id } self.write_kodidb(result) log_msg("get_animated_artwork for imdbid: %s - result: %s" % (imdb_id, result)) return result def poster(self, imdb_id, manual_select=False): '''return preferred animated poster, optionally show selectdialog for manual selection''' img = self.select_art(self.posters(imdb_id), manual_select, "poster") return self.process_image(img, "poster", imdb_id) def fanart(self, imdb_id, manual_select=False): '''return preferred animated fanart, optionally show selectdialog for manual selection''' img = self.select_art(self.fanarts(imdb_id), manual_select, "fanart") return self.process_image(img, "fanart", imdb_id) def posters(self, imdb_id): '''return all animated posters for the given imdb_id (imdbid can also be tmdbid)''' return self.get_art(imdb_id, "posters") def fanarts(self, imdb_id): '''return animated fanarts for the given imdb_id (imdbid can also be tmdbid)''' return self.get_art(imdb_id, "fanarts") def get_art(self, imdb_id, art_type): '''get the artwork''' art_db = self.get_animatedart_db() if art_db.get(imdb_id): return art_db[imdb_id][art_type] return [] def get_animatedart_db(self): '''get the full animated art database as dict with imdbid and tmdbid as key uses 7 day cache to prevent overloading the server''' # get all animated posters from the online json file cache = self.cache.get("animatedartdb") if cache: return cache art_db = {} data = get_json('http://www.consiliumb.com/animatedgifs/movies.json', None) base_url = data.get("baseURL", "") if data and data.get('movies'): for item in data['movies']: for db_id in ["imdbid", "tmdbid"]: key = item[db_id] art_db[key] = {"posters": [], "fanarts": []} for entry in item['entries']: entry_new = { "contributedby": entry["contributedBy"], "dateadded": entry["dateAdded"], "language": entry["language"], "source": entry["source"], "image": "%s/%s" % (base_url, entry["image"].replace( ".gif", "_original.gif")), "thumb": "%s/%s" % (base_url, entry["image"]) } if entry['type'] == 'poster': art_db[key]["posters"].append(entry_new) elif entry['type'] == 'background': art_db[key]["fanarts"].append(entry_new) self.cache.set("animatedartdb", art_db, expiration=timedelta(days=7)) return art_db @staticmethod def select_art(items, manual_select=False, art_type=""): '''select the preferred image from the list''' image = None if manual_select: # show selectdialog to manually select the item results_list = [] # add none and browse entries listitem = xbmcgui.ListItem(label=xbmc.getLocalizedString(231), iconImage="DefaultAddonNone.png") results_list.append(listitem) listitem = xbmcgui.ListItem(label=xbmc.getLocalizedString(1030), iconImage="DefaultFolder.png") results_list.append(listitem) for item in items: labels = [ item["contributedby"], item["dateadded"], item["language"], item["source"] ] label = " / ".join(labels) listitem = xbmcgui.ListItem(label=label, iconImage=item["thumb"]) results_list.append(listitem) if manual_select and results_list: dialog = DialogSelect("DialogSelect.xml", "", listing=results_list, window_title=art_type) dialog.doModal() selected_item = dialog.result del dialog if selected_item == 1: # browse for image dialog = xbmcgui.Dialog() image = dialog.browse(2, xbmc.getLocalizedString(1030), 'files', mask='.gif').decode("utf-8") del dialog elif selected_item > 1: # user has selected an image from online results image = items[selected_item - 2]["image"] elif items: # just grab the first item as best match image = items[0]["image"] return image @staticmethod def process_image(image_url, art_type, imdb_id): '''animated gifs need to be stored locally, otherwise they won't work''' # make sure that our local path for the gif images exists addon = xbmcaddon.Addon(ADDON_ID) gifs_path = "%s/animatedgifs/" % addon.getAddonInfo('profile') del addon if not xbmcvfs.exists(gifs_path): xbmcvfs.mkdirs(gifs_path) # only process existing images if not image_url or not xbmcvfs.exists(image_url): return None # copy the image to our local path and return the new path as value local_filename = "%s%s_%s.gif" % (gifs_path, imdb_id, art_type) if xbmcvfs.exists(local_filename): xbmcvfs.delete(local_filename) # we don't use xbmcvfs.copy because we want to wait for the action to complete img = xbmcvfs.File(image_url) img_data = img.readBytes() img.close() img = xbmcvfs.File(local_filename, 'w') img.write(img_data) img.close() return local_filename def write_kodidb(self, artwork): '''store the animated artwork in kodi database to access it with ListItem.Art(animatedartX)''' kodi_movie = self.kodidb.movie_by_imdbid(artwork["imdb_id"]) if kodi_movie: params = { "movieid": kodi_movie["movieid"], "art": { "animatedfanart": artwork["animatedfanart"], "animatedposter": artwork["animatedposter"] } } self.kodidb.set_json('VideoLibrary.SetMovieDetails', params)
class MetadataUtils(object): ''' Provides all kind of mediainfo for kodi media, returned as dict with details ''' close_called = False def __init__(self): '''Initialize and load all our helpers''' self._studiologos_path = "" self.cache = SimpleCache() self.addon = xbmcaddon.Addon(ADDON_ID) self.kodidb = KodiDb() self.omdb = Omdb(self.cache) self.tmdb = Tmdb(self.cache) self.channellogos = ChannelLogos(self.kodidb) self.fanarttv = FanartTv(self.cache) self.imdb = Imdb(self.cache) self.google = GoogleImages(self.cache) self.studiologos = StudioLogos(self.cache) self.animatedart = AnimatedArt(self.cache, self.kodidb) self.thetvdb = TheTvDb() self.musicart = MusicArtwork(self) self.pvrart = PvrArtwork(self) log_msg("Initialized") def close(self): '''Cleanup instances''' self.close_called = True self.cache.close() self.addon = None del self.addon del self.kodidb del self.omdb del self.tmdb del self.channellogos del self.fanarttv del self.imdb del self.google del self.studiologos del self.animatedart del self.thetvdb del self.musicart del self.pvrart log_msg("Exited") def __del__(self): '''make sure close is called''' if not self.close_called: self.close() @use_cache(14) def get_extrafanart(self, file_path): '''helper to retrieve the extrafanart path for a kodi media item''' from helpers.extrafanart import get_extrafanart return get_extrafanart(file_path) def get_music_artwork(self, artist, album="", track="", disc="", ignore_cache=False, flush_cache=False): '''method to get music artwork for the goven artist/album/song''' return self.musicart.get_music_artwork(artist, album, track, disc, ignore_cache=ignore_cache, flush_cache=flush_cache) def music_artwork_options(self, artist, album="", track="", disc=""): '''options for music metadata for specific item''' return self.musicart.music_artwork_options(artist, album, track, disc) @use_cache(14) def get_extended_artwork(self, imdb_id="", tvdb_id="", tmdb_id="", media_type=""): '''get extended artwork for the given imdbid or tvdbid''' from urllib import quote_plus result = {"art": {}} if "movie" in media_type and tmdb_id: result["art"] = self.fanarttv.movie(tmdb_id) elif "movie" in media_type and imdb_id: result["art"] = self.fanarttv.movie(imdb_id) elif media_type in ["tvshow", "tvshows", "seasons", "episodes"]: if not tvdb_id: if imdb_id and not imdb_id.startswith("tt"): tvdb_id = imdb_id elif imdb_id: tvdb_id = self.thetvdb.get_series_by_imdb_id(imdb_id).get( "tvdb_id") if tvdb_id: result["art"] = self.fanarttv.tvshow(tvdb_id) # add additional art with special path for arttype in ["fanarts", "posters", "clearlogos", "banners"]: if result["art"].get(arttype): result["art"][arttype] = "plugin://script.skin.helper.service/"\ "?action=extrafanart&fanarts=%s" % quote_plus(repr(result["art"][arttype])) return result @use_cache(14) def get_tmdb_details(self, imdb_id="", tvdb_id="", title="", year="", media_type="", preftype="", manual_select=False, ignore_cache=False): '''returns details from tmdb''' result = {} if imdb_id: result = self.tmdb.get_videodetails_by_externalid( imdb_id, "imdb_id") elif tvdb_id: result = self.tmdb.get_videodetails_by_externalid( tvdb_id, "tvdb_id") elif title and media_type in ["movies", "setmovies", "movie"]: result = self.tmdb.search_movie(title, year, manual_select=manual_select) elif title and media_type in ["tvshows", "tvshow"]: result = self.tmdb.search_tvshow(title, year, manual_select=manual_select) elif title: result = self.tmdb.search_video(title, year, preftype=preftype, manual_select=manual_select) if result.get("status"): result["status"] = self.translate_string(result["status"]) if result.get("runtime"): result["runtime"] = result["runtime"] / 60 result.update(self.get_duration(result["runtime"])) return result def get_moviesetdetails(self, title, set_id): '''get a nicely formatted dict of the movieset details which we can for example set as window props''' # get details from tmdb from helpers.moviesetdetails import get_moviesetdetails return get_moviesetdetails(self, title, set_id) @use_cache(14) def get_streamdetails(self, db_id, media_type, ignore_cache=False): '''get a nicely formatted dict of the streamdetails ''' from helpers.streamdetails import get_streamdetails return get_streamdetails(self.kodidb, db_id, media_type) def get_pvr_artwork(self, title, channel="", genre="", manual_select=False, ignore_cache=False): '''get artwork and mediadetails for PVR entries''' return self.pvrart.get_pvr_artwork(title, channel, genre, manual_select=manual_select, ignore_cache=ignore_cache) def pvr_artwork_options(self, title, channel="", genre=""): '''options for pvr metadata for specific item''' return self.pvrart.pvr_artwork_options(title, channel, genre) @use_cache(14) def get_channellogo(self, channelname): '''get channellogo for the given channel name''' return self.channellogos.get_channellogo(channelname) def get_studio_logo(self, studio): '''get studio logo for the given studio''' # dont use cache at this level because of changing logospath return self.studiologos.get_studio_logo(studio, self.studiologos_path) @property def studiologos_path(self): '''path to use to lookup studio logos, must be set by the calling addon''' return self._studiologos_path @studiologos_path.setter def studiologos_path(self, value): '''path to use to lookup studio logos, must be set by the calling addon''' self._studiologos_path = value def get_animated_artwork(self, imdb_id, manual_select=False, ignore_cache=False): '''get animated artwork, perform extra check if local version still exists''' artwork = self.animatedart.get_animated_artwork( imdb_id, manual_select=manual_select, ignore_cache=ignore_cache) if not (manual_select or ignore_cache): refresh_needed = False if artwork.get("animatedposter") and not xbmcvfs.exists( artwork["animatedposter"]): refresh_needed = True if artwork.get("animatedfanart") and not xbmcvfs.exists( artwork["animatedfanart"]): refresh_needed = True return {"art": artwork} @use_cache(14) def get_omdb_info(self, imdb_id="", title="", year="", content_type=""): '''Get (kodi compatible formatted) metadata from OMDB, including Rotten tomatoes details''' title = title.split(" (")[0] # strip year appended to title result = {} if imdb_id: result = self.omdb.get_details_by_imdbid(imdb_id) elif title and content_type in [ "seasons", "season", "episodes", "episode", "tvshows", "tvshow" ]: result = self.omdb.get_details_by_title(title, "", "tvshows") elif title and year: result = self.omdb.get_details_by_title(title, year, content_type) if result.get("status"): result["status"] = self.translate_string(result["status"]) if result.get("runtime"): result["runtime"] = result["runtime"] / 60 result.update(self.get_duration(result["runtime"])) return result @use_cache(7) def get_top250_rating(self, imdb_id): '''get the position in the IMDB top250 for the given IMDB ID''' return self.imdb.get_top250_rating(imdb_id) @use_cache(14) def get_duration(self, duration): '''helper to get a formatted duration''' if isinstance(duration, (str, unicode)) and ":" in duration: dur_lst = duration.split(":") return { "Duration": "%s:%s" % (dur_lst[0], dur_lst[1]), "Duration.Hours": dur_lst[0], "Duration.Minutes": dur_lst[1], "Runtime": str((int(dur_lst[0]) * 60) + int(dur_lst[1])), } else: return _get_duration(duration) @use_cache(2) def get_tvdb_details(self, imdbid="", tvdbid=""): '''get metadata from tvdb by providing a tvdbid or tmdbid''' result = {} self.thetvdb.days_ahead = 365 if not tvdbid and imdbid and not imdbid.startswith("tt"): # assume imdbid is actually a tvdbid... tvdbid = imdbid if tvdbid: result = self.thetvdb.get_series(tvdbid) elif imdbid: result = self.thetvdb.get_series_by_imdb_id(imdbid) if result: if result["status"] == "Continuing": # include next episode info result["nextepisode"] = self.thetvdb.get_nextaired_episode( result["tvdb_id"]) # include last episode info result["lastepisode"] = self.thetvdb.get_last_episode_for_series( result["tvdb_id"]) result["status"] = self.translate_string(result["status"]) if result.get("runtime"): result["runtime"] = result["runtime"] / 60 result.update(_get_duration(result["runtime"])) return result @use_cache(14) def get_imdbtvdb_id(self, title, content_type, year="", imdbid="", tvshowtitle=""): '''try to figure out the imdbnumber and/or tvdbid''' tvdbid = "" if content_type in ["seasons", "episodes"] or tvshowtitle: title = tvshowtitle content_type = "tvshows" if imdbid and not imdbid.startswith("tt"): if content_type in ["tvshows", "seasons", "episodes"]: tvdbid = imdbid imdbid = "" if not imdbid and year: imdbid = self.get_omdb_info("", title, year, content_type).get("imdbnumber", "") if not imdbid: # repeat without year imdbid = self.get_omdb_info("", title, "", content_type).get("imdbnumber", "") # return results return (imdbid, tvdbid) def translate_string(self, _str): '''translate the received english string from the various sources like tvdb, tmbd etc''' translation = _str _str = _str.lower() if "continuing" in _str: translation = self.addon.getLocalizedString(32037) elif "ended" in _str: translation = self.addon.getLocalizedString(32038) elif "released" in _str: translation = self.addon.getLocalizedString(32040) return translation
def __init__(self, sysARG): log('__init__, sysARG = ' + str(sysARG)) self.sysARG = sysARG if self.chkUWP(): return self.cache = SimpleCache()
class NBC(object): def __init__(self, sysARG): log('__init__, sysARG = ' + str(sysARG)) self.sysARG = sysARG if self.chkUWP(): return self.cache = SimpleCache() def chkUWP(self): isUWP = (xbmc.getCondVisibility("system.platform.uwp") or sys.platform == "win10" or re.search(r"[/\\]WindowsApps[/\\]XBMCFoundation\.Kodi_", xbmc.translatePath("special://xbmc/"))) if isUWP: return xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30006), ICON, 4000) return isUWP def openURL(self, url): try: log('openURL, url = ' + str(url)) if DEBUG: cacheresponse = None else: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) if not cacheresponse: cacheresponse = urllib2.urlopen(urllib2.Request(url), timeout=TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s' % url, cacheresponse, expiration=datetime.timedelta(minutes=15)) return cacheresponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self, items): for item in items: self.addDir(*item) self.addYoutube(LANGUAGE(30004), 'plugin://plugin.video.youtube/user/NBC/') def browseEpisodes(self, url=None): log('browseEpisodes') if url is None: url = VIDEO_URL + FILTER % ('type', 'Full%20Episode') items = json.loads(self.openURL(url)) if items and 'data' in items: for item in items['data']: path = item['attributes']['fullUrl'] aired = str(item['attributes']['airdate']).split('T')[0] duration = int(item['attributes']['runTime']) plot = item['attributes']['description'] title = item['attributes']['title'] showTitle = '' for show in item['attributes']['categories']: if show.startswith('Series'): showTitle = show.split('Series/')[1] break try: episodeNumber = int(item['attributes']['episodeNumber']) except: episodeNumber = 0 try: seasonNumber = int(item['attributes']['seasonNumber']) except: seasonNumber = 0 try: thumb = ICON for image in items['included']: if image['id'] == item['relationships']['image'][ 'data']['id']: thumb = BASE_URL + image['attributes']['path'] break except: thumb = ICON seinfo = ('S' + ('0' if seasonNumber < 10 else '') + str(seasonNumber) + 'E' + ('0' if episodeNumber < 10 else '') + str(episodeNumber)) label = '%s - %s' % ( showTitle, title ) if seasonNumber + episodeNumber == 0 else '%s - %s - %s' % ( showTitle, seinfo, title) infoLabels = { "mediatype": "episodes", "label": label, "title": label, "TVShowTitle": showTitle, "plot": plot, "aired": aired, "duration": duration, "season": seasonNumber, "episode": episodeNumber } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addLink(label, path, 9, infoLabels, infoArt, len(items['data'])) try: next_page = items['links']['next'] except: next_page = None if next_page: self.addDir('>> Next', next_page, 1) def browseShows(self, url=None): log('browseShows') if url is None: url = SHOWS_URL items = json.loads(self.openURL(url)) if items and 'data' in items: for item in items['data']: showTitle = item['attributes']['shortTitle'] plot = (item['attributes']['shortDescription'] or showTitle).replace('<p>', '').replace('</p>', '') path = VIDEO_URL + FILTER % ('show', item['id']) vidID = item['relationships']['aggregates']['data']['id'] try: thumb = ICON for image in items['included']: if image['id'] == item['relationships']['image'][ 'data']['id']: thumb = BASE_URL + image['attributes']['path'] break except: thumb = ICON myURL = json.dumps({"url": path, "vidID": vidID}) infoLabels = { "mediatype": "tvshows", "label": showTitle, "title": showTitle, "TVShowTitle": showTitle, "plot": plot } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addDir(showTitle, myURL, 0, infoLabels, infoArt) try: next_page = items['links']['next'] except: next_page = None if next_page: self.addDir('>> Next', next_page, 2) def buildShow(self, url): log('buildShow') myURL = json.loads(url) items = json.loads(self.openURL(SHOW_URL % myURL['vidID'])) if items and 'data' in items: for item in items['data']['attributes']['videoTypes']: self.browseEpisodes(myURL['url'] + FILTER % ('type', urllib2.quote(item))) @use_cache(28) def resolveURL(self, url): log('resolveURL') return getVideoInfo(url, QUALITY, True).streams() def playVideo(self, name, url): log('playVideo') info = self.resolveURL(url) if info is None: return url = info[0]['xbmc_url'] liz = xbmcgui.ListItem(name, path=url) if 'm3u8' in url.lower() and inputstreamhelper.Helper( 'hls').check_inputstream() and not DEBUG: liz.setProperty('inputstreamaddon', 'inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type', 'hls') if 'subtitles' in info[0]['ytdl_format']: liz.setSubtitles([ x['url'] for x in info[0]['ytdl_format']['subtitles'].get('en', '') if 'url' in x ]) xbmcplugin.setResolvedUrl(int(self.sysARG[1]), True, liz) def addYoutube(self, name, url): liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label": name, "title": name}) liz.setArt({'thumb': ICON, 'fanart': FANART}) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=url, listitem=liz, isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name, "genre": "News" }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = self.sysARG[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=u, listitem=liz, totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name, "genre": "News" }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = self.sysARG[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=u, listitem=liz, isFolder=True) def getParams(self): return dict(urlparse.parse_qsl(self.sysARG[2][1:])) def run(self): params = self.getParams() try: url = urllib.unquote_plus(params["url"]) except: url = None try: name = urllib.unquote_plus(params["name"]) except: name = None try: mode = int(params["mode"]) except: mode = None log("Mode: " + str(mode)) log("URL : " + str(url)) log("Name: " + str(name)) if mode == None: self.buildMenu(MAIN_MENU) elif mode == 0: self.buildShow(url) elif mode == 1: self.browseEpisodes(url) elif mode == 2: self.browseShows(url) elif mode == 9: self.playVideo(name, url) xbmcplugin.setContent(int(self.sysARG[1]), CONTENT_TYPE) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_UNSORTED) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_NONE) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_TITLE) xbmcplugin.endOfDirectory(int(self.sysARG[1]), cacheToDisc=True)
class Cheddar(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): try: cacheResponce = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheResponce: request = urllib2.Request(url) request.add_header('User-Agent','Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)') response = urllib2.urlopen(request, timeout=TIMEOUT) cacheResponce = response.read() response.close() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, cacheResponce, expiration=datetime.timedelta(hours=1)) return cacheResponce except urllib2.URLError as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) except socket.timeout as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self, items): log('buildMenu') if items == Cheddar_LIVE: for item in items: self.addLink(*item) else: for item in items: self.addDir(*item) if items == Cheddar_MENU: self.addYoutube(LANGUAGE(30033), 'plugin://plugin.video.youtube/channel/UC04KsGq3npibMCE9Td3mVDg/') def browse(self, link): log('browse') soup = BeautifulSoup(self.openURL(BASEURL + link), "html.parser") latestLink = (soup('div', {'class': 'video_thumb'})) for item in latestLink: uriLink = item('a', {'class': 'cf'})[0] uri = BASEURL + uriLink['href'] thumb = uriLink('div', {'class': 'vid_img'})[0].find_all('img')[0].get('src') airdate, title = uriLink.text.strip().replace('\r','').replace('\t','').split('\n') label = title.strip() plot = '%s [CR]Aired: %s'%(label, airdate) try: airdate = datetime.datetime.strptime(airdate, "%B %d, %Y") except: airdate = datetime.datetime.now() airdate = airdate.strftime('%Y-%m-%d') infoList = {"mediatype":"episode","label":label,"title":label,"plot":plot,'genre':'News',"studio":"cheddar","aired":airdate} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART} self.addLink(label, uri, 9, infoList, infoArt) def playVideo(self, name, url): log('playVideo, name = ' + name) if url.endswith('m3u8'): liz = xbmcgui.ListItem(name, path=url) liz.setProperty('inputstreamaddon','inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type','hls') else: info = getVideoInfo(url,QUALITY,True) if info is None: return info = info.streams() url = info[0]['xbmc_url'] liz = xbmcgui.ListItem(name, path=url) if 'subtitles' in info[0]['ytdl_format']: liz.setSubtitles([x['url'] for x in info[0]['ytdl_format']['subtitles'].get('en','') if 'url' in x]) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label":name,"title":name} ) liz.setArt({'thumb':ICON,'fanart':FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name} ) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
class PlutoTV(object): def __init__(self, sysARG): log('__init__, sysARG = ' + str(sysARG)) self.sysARG = sysARG self.net = net.Net() self.cache = SimpleCache() self.region = self.getRegion() self.filter = False if self.region == 'US' else True self.categoryMenu = self.getCategories() self.mediaType = self.getMediaTypes() log('__init__, region = ' + self.region) def getRegion(self): return (self.openURL(REGION_URL, life=datetime.timedelta(hours=12)).get('countryCode','') or 'US') def login(self): log('login') #ignore guest login if USER_EMAIL == LANGUAGE(30009): return if len(USER_EMAIL) > 0: header_dict = {} header_dict['Accept'] = 'application/json, text/javascript, */*; q=0.01' header_dict['Host'] = 'api.pluto.tv' header_dict['Connection'] = 'keep-alive' header_dict['Referer'] = 'http://pluto.tv/' header_dict['Origin'] = 'http://pluto.tv' header_dict['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.2; rv:24.0) Gecko/20100101 Firefox/24.0' try: xbmcvfs.rmdir(COOKIE_JAR) except: pass if xbmcvfs.exists(COOKIE_JAR) == False: try: xbmcvfs.mkdirs(SETTINGS_LOC) f = xbmcvfs.File(COOKIE_JAR, 'w') f.close() except: log('login, Unable to create the storage directory', xbmc.LOGERROR) form_data = ({'optIn': 'true', 'password': PASSWORD,'synced': 'false', 'userIdentity': USER_EMAIL}) self.net.set_cookies(COOKIE_JAR) try: loginlink = json.loads(self.net.http_POST(LOGIN_URL, form_data=form_data, headers=header_dict).content.encode("utf-8").rstrip()) if loginlink and loginlink['email'].lower() == USER_EMAIL.lower(): xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30006) + loginlink['displayName'], ICON, 4000) self.net.save_cookies(COOKIE_JAR) else: xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30007), ICON, 4000) except Exception as e: log('login, Unable to create the storage directory ' + str(e), xbmc.LOGERROR) else: #firstrun wizard if yesnoDialog(LANGUAGE(30008),no=LANGUAGE(30009), yes=LANGUAGE(30010)): REAL_SETTINGS.setSetting('User_Email',inputDialog(LANGUAGE(30001))) REAL_SETTINGS.setSetting('User_Password',inputDialog(LANGUAGE(30002))) else: REAL_SETTINGS.setSetting('User_Email',LANGUAGE(30009)) xbmc.executebuiltin('RunScript("' + ADDON_PATH + '/country.py' + '")') def openURL(self, url, life=datetime.timedelta(minutes=15)): log('openURL, url = ' + url) try: header_dict = {} header_dict['Accept'] = 'application/json, text/javascript, */*; q=0.01' header_dict['Host'] = 'api.pluto.tv' header_dict['Connection'] = 'keep-alive' header_dict['Referer'] = 'http://pluto.tv/' header_dict['Origin'] = 'http://pluto.tv' header_dict['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.2; rv:24.0) Gecko/20100101 Firefox/24.0' self.net.set_cookies(COOKIE_JAR) trans_table = ''.join( [chr(i) for i in range(128)] + [' '] * 128 ) cacheResponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheResponse: try: cacheResponse = self.net.http_GET(url, headers=header_dict).content.encode("utf-8", 'ignore') except: cacheResponse = (self.net.http_GET(url, headers=header_dict).content.translate(trans_table)).encode("utf-8") self.net.save_cookies(COOKIE_JAR) self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, cacheResponse, expiration=life) if isinstance(cacheResponse, basestring): cacheResponse = json.loads(cacheResponse) return cacheResponse except Exception as e: log('openURL, Unable to open url ' + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, 'Unable to Connect, Check User Credentials', ICON, 4000) return {} def mainMenu(self): log('mainMenu') self.login() for item in PLUTO_MENU: self.addDir(*item) def browseMenu(self): log('browseMenu') for item in self.categoryMenu: self.addDir(*item) def getCategories(self): log('getCategories') collect= [] lineup = [] data = self.openURL(BASE_LINEUP) for channel in data: collect.append(channel['category']) counter = collections.Counter(collect) for key, value in sorted(counter.iteritems()): lineup.append(("%s"%(key) , BASE_LINEUP, 2)) lineup.insert(0,(LANGUAGE(30016), BASE_LINEUP, 2)) lineup.insert(2,(LANGUAGE(30014), BASE_LINEUP, 2)) return lineup def getMediaTypes(self): mediaType = {} for type in self.categoryMenu: type = type[0] if type == 'Movies': mediaType[type] = 'movie' elif type == 'TV': mediaType[type] = 'episodes' elif type == 'Music + Radio': mediaType[type] = 'musicvideo' else: mediaType[type] = 'video' return mediaType def browse(self, chname, url): log('browse, chname = ' + chname) geowarn = False data = (self.openURL(url)) for channel in data: id = channel['_id'] cat = channel['category'] number = channel['number'] region = channel['regionFilter']['include'] exclude = channel['regionFilter']['exclude'] name = channel['name'] plot = channel['description'] feat = (channel.get('featured','') or 0) == -1 if self.filter == True and (self.region in exclude or self.region not in region): if geowarn == False: geowarn = True xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30004), ICON, 4000) continue thumb = ICON if 'thumbnail' in channel: thumb = (channel['thumbnail'].get('path',ICON) or ICON) land = FANART if 'featuredImage' in channel: land = (channel['featuredImage'].get('path',FANART) or FANART) logo = ICON if 'logo' in channel: logo = (channel['logo']['path'] or ICON) if chname == "All Channels": title = "%s - %s: %s" % (cat, number, name) infoLabels ={"mediatype":self.mediaType[cat],"label":title ,"title":title ,"plot":plot, "code":number, "genre":cat, "imdbnumber":id} infoArt ={"thumb":thumb,"poster":thumb,"fanart":land,"icon":logo,"logo":logo} self.addDir(title, id, 8, infoLabels, infoArt) elif chname == "Featured" and feat == True: title = "%s - %s: %s" % (cat, number, name) infoLabels ={"mediatype":self.mediaType[cat],"label":title ,"title":title ,"plot":plot, "code":number, "genre":cat, "imdbnumber":id} infoArt ={"thumb":thumb,"poster":thumb,"fanart":land,"icon":logo,"logo":logo} self.addDir(title, id, 8, infoLabels, infoArt) elif chname.lower() == cat.lower(): title = "%s: %s" % (number, name) infoLabels ={"mediatype":self.mediaType[cat],"label":title ,"title":title ,"plot":plot, "code":number, "genre":cat, "imdbnumber":id} infoArt ={"thumb":thumb,"poster":thumb,"fanart":land,"icon":logo,"logo":logo} self.addDir(title, id, 8, infoLabels, infoArt) def pagination(self, seq, rowlen): for start in xrange(0, len(seq), rowlen): yield seq[start:start+rowlen] def browseGuide(self, start=0, end=14): log('browseGuide') geowarn = False start = 0 if start == BASE_LINEUP else int(start) data = list(self.pagination((self.openURL(BASE_LINEUP)), end)) start = 0 if start >= len(data) else start link = self.getGuidedata() if start == 0 and end == 14: self.addDir(LANGUAGE(30014), '', 10) for channel in data[start]: chid = channel['_id'] chcat = channel['category'] chnum = channel['number'] region = channel['regionFilter']['include'] exclude = channel['regionFilter']['exclude'] chname = channel['name'] chplot = channel['description'] chthumb = ICON if 'thumbnail' in channel: chthumb = ((channel['thumbnail'].get('path','')).replace(' ','%20') or ICON) print chnum, chthumb feat = (channel.get('featured','') or 0) == -1 if self.filter == True and (self.region in exclude or self.region not in region): if geowarn == False: geowarn = True xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30004), ICON, 4000) continue item = link[chid] if len(item) == 0: continue item = item[0] epid = (item['episode']['_id']) epname = item['episode']['name'] epplot = (item['episode'].get('description',epname) or epname) epgenre = (item['episode'].get('genre',chcat) or chcat) epdur = int(item['episode'].get('duration','0') or '0') // 1000 live = item['episode']['liveBroadcast'] thumb = chthumb #(item['episode']['thumbnail']['path'] or chthumb) #site doesn't update missing episode thumbs title = "%s: %s - %s" % (chnum, chname, epname) if any(k.lower().startswith(title.lower()) for k in IGNORE_KEYS): continue infoLabels ={"mediatype":self.mediaType[chcat],"label":title ,"title":title ,"plot":epplot, "code":epid, "genre":epgenre, "imdbnumber":chid, "duration":epdur} infoArt ={"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addLink(title, chid, 9, infoLabels, infoArt, end) start += 1 if end == 14: self.addDir(LANGUAGE(30015), '%s'%(start), 0) def playChannel(self, name, url): log('playChannel') origurl = url if PTVL_RUN: self.playContent(name, url) link = self.getGuidedata() item = link[origurl][0] id = item['episode']['_id'] ch_start = datetime.datetime.fromtimestamp(time.mktime(time.strptime((item["start"].split('.')[0]), "%Y-%m-%dT%H:%M:%S"))) ch_timediff = (datetime.datetime.now() - ch_start).seconds dur_sum = 0 playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist.clear() xbmc.sleep(100) for idx, field in enumerate(self.openURL(BASE_CLIPS %(id))): url = (field['url'] or field['code']) name = field['name'] thumb = (field['thumbnail'] or ICON) provider = field['provider'] url = self.resolveURL(provider, url) dur = int(field['duration'] or '0') // 1000 dur_start = dur_sum dur_sum += dur liz=xbmcgui.ListItem(name, path=url) infoList = {"mediatype":"video","label":name,"title":name,"duration":dur} infoArt = {"thumb":thumb,"poster":thumb,"icon":ICON,"fanart":FANART} liz.setInfo(type="Video", infoLabels=infoList) liz.setArt(infoArt) liz.setProperty("IsPlayable","true") liz.setProperty("IsInternetStream",str(field['liveBroadcast']).lower()) if 'm3u8' in url.lower() and inputstreamhelper.Helper('hls').check_inputstream(): liz.setProperty('inputstreamaddon','inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type','hls') if dur_start < ch_timediff and dur_sum > ch_timediff: vid_offset = ch_timediff - dur_start liz.setProperty('ResumeTime', str(vid_offset)) playlist.add(url, liz, idx) if idx == 0: xbmcplugin.setResolvedUrl(int(self.sysARG[1]), True, liz) def playContent(self, name, url): log('playContent') origurl = url link = self.getGuidedata() try: item = link[origurl][0] except Exception as e: return log('playContent, failed! ' + str(e) + ', origurl = ' + str(link.get(origurl,[EMPTY])), xbmc.LOGERROR) id = item['episode']['_id'] ch_start = datetime.datetime.fromtimestamp(time.mktime(time.strptime((item["start"].split('.')[0]), "%Y-%m-%dT%H:%M:%S"))) ch_timediff = (datetime.datetime.now() - ch_start).seconds data = (self.openURL(BASE_CLIPS %(id))) dur_sum = 0 for idx, field in enumerate(data): url = (field['url'] or field['code']) name = field['name'] thumb = (field['thumbnail'] or ICON) provider = (field['provider'] or None) url = urllib.quote(json.dumps({"provider":provider,"url":url})) dur = int(field['duration'] or '0') // 1000 dur_start = dur_sum dur_sum += dur if any(k.lower().startswith(name.lower()) for k in IGNORE_KEYS): continue infoList = {"mediatype":"video","label":name,"title":name,"duration":dur} infoArt = {"thumb":thumb,"poster":thumb,"icon":ICON,"fanart":FANART} if PTVL_RUN: self.playVideo(name, url) else: self.addLink(name, url, 7, infoList, infoArt, len(data)) @use_cache(1) def resolveURL(self, provider, url): log('resolveURL, provider = ' + str(provider) + ', url = ' + url) if provider == 'jwplatform' or 'm3u8' in url.lower() or url is None: return url elif provider == 'youtube': url = url.replace('feature=player_embedded&','') if len(re.findall('http[s]?://www.youtube.com/watch', url)) > 0: return YTURL + url.split('/watch?v=')[1] elif len(re.findall('http[s]?://youtu.be/', url)) > 0: return YTURL + url.split('/youtu.be/')[1] elif provider == 'vimeo': if len(re.findall('http[s]?://vimeo.com/', url)) > 0: return VMURL + url.split('/vimeo.com/')[1] else: info = None if isUWP() == False: from YDStreamExtractor import getVideoInfo info = getVideoInfo(url,3,True) if info is None: return YTURL + 'W6FjQgmtt0k' info = info.streams() return info[0]['xbmc_url'] def playVideo(self, name, url, liz=None): log('playVideo') url = json.loads(urllib.unquote(url)) provider = url['provider'] url = url['url'] if liz is None: liz = xbmcgui.ListItem(name, path=self.resolveURL(provider, url)) if 'm3u8' in url.lower() and inputstreamhelper.Helper('hls').check_inputstream(): liz.setProperty('inputstreamaddon','inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type','hls') xbmcplugin.setResolvedUrl(int(self.sysARG[1]), True, liz) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=self.sysARG[0]+"?url="+urllib.quote(u)+"&mode="+str(mode)+"&name="+urllib.quote(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name} ) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=self.sysARG[0]+"?url="+urllib.quote(u)+"&mode="+str(mode)+"&name="+urllib.quote(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]),url=u,listitem=liz,isFolder=True) def poolList(self, method, items): results = [] if ENABLE_POOL: pool = ThreadPool(cpu_count()) results = pool.imap_unordered(method, items) pool.close() pool.join() else: results = [method(item) for item in items] results = filter(None, results) return results def getGuidedata(self): return (self.openURL(BASE_GUIDE % (datetime.datetime.now().strftime('%Y-%m-%dT%H:00:00'),(datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%dT%H:00:00')), life=datetime.timedelta(hours=6))) @buildChannels({'refresh_path':urllib.quote(json.dumps("plugin://%s?mode=20"%ADDON_ID)),'refresh_interval':"7200"}) def uEPG(self): log('uEPG') #support for uEPG universal epg framework module available from the Kodi repository. https://github.com/Lunatixz/KODI_Addons/tree/master/script.module.uepg data = (self.openURL(BASE_LINEUP)) self.link = self.getGuidedata() return [self.buildGuide(channel) for channel in data] def buildGuide(self, channel): chthumb = '' chlogo = '' chid = channel['_id'] chcat = channel['category'] chnum = channel['number'] region = channel['regionFilter']['include'] exclude = channel['regionFilter']['exclude'] chname = channel['name'] chplot = channel['description'] isFavorite = False #(channel.get('featured','') or 0) == -1 if self.filter == True and (self.region in exclude or self.region not in region): return if 'thumbnail' in channel: chthumb = (channel['thumbnail'].get('path','') or '') if 'logo' in channel: chlogo = (channel['logo'].get('path','') or '') log('buildGuide, channel = ' + str(chnum)) newChannel = {} guidedata = [] newChannel['channelname'] = chname newChannel['channelnumber'] = chnum newChannel['channellogo'] = chlogo newChannel['isfavorite'] = isFavorite for i in range(len(self.link.get(chid,[]))): item = self.link[chid][i] epname = item['episode']['name'] epid = (item['episode']['_id']) epplot = (item['episode'].get('description',epname) or epname) epgenre = (item['episode'].get('genre',chcat) or chcat) epsubgenre= (item['episode'].get('subGenre','') or '') genre = '%s + %s'%(epgenre, epsubgenre) if len(epsubgenre) > 0 else epgenre epdur = int(item['episode'].get('duration','0') or '0') // 1000 live = item['episode']['liveBroadcast'] == "true" thumb = chthumb poster = (item['episode'].get('thumbnail','').get('path',chthumb) or chthumb) clips = self.link[chid] if len(clips) == 0: return tmpdata = {} clips = clips[0] id = clips['episode']['_id'] data = (self.openURL(BASE_CLIPS %(id))) for field in data: url = (field['url'] or field['code']) name = field['name'] thumb = (field['thumbnail'] or ICON) provider = (field['provider'] or None) url = urllib.quote(json.dumps({"provider":provider,"url":url})) dur = int(field['duration'] or '0') // 1000 title = "%s: %s" %(chname, epname) if any(k.lower().startswith(title.lower()) for k in IGNORE_KEYS): return tmpdata = {"mediatype":self.mediaType[chcat],"label":title,"title":chname,"originaltitle":epname,"plot":epplot, "code":epid, "genre":chcat, "imdbnumber":chid, "duration":dur} tmpdata['starttime'] = int(time.mktime(time.strptime((item["start"].split('.')[0]), "%Y-%m-%dT%H:%M:%S"))) tmpdata['url'] = self.sysARG[0]+'?mode=7&name=%s&url=%s'%(title,url) tmpdata['art'] = {"thumb":thumb,"clearart":poster,"fanart":FANART,"icon":chthumb,"clearlogo":chlogo} guidedata.append(tmpdata) newChannel['guidedata'] = guidedata return newChannel def getParams(self): return dict(urlparse.parse_qsl(self.sysARG[2][1:])) def run(self): params=self.getParams() try: url=urllib.unquote_plus(params["url"]) except: url=None try: name=urllib.unquote_plus(params["name"]) except: name=None try: mode=int(params["mode"]) except: mode=None log("Mode: "+str(mode)) log("URL : "+str(url)) log("Name: "+str(name)) if mode==None: self.mainMenu() elif mode == 0: self.browseGuide(url) elif mode == 1: self.browseMenu() elif mode == 2: self.browse(name, url) elif mode == 7: self.playVideo(name, url) elif mode == 8: self.playContent(name, url) elif mode == 9: self.playChannel(name, url) elif mode == 10: self.browseGuide(end=5000) elif mode == 20: self.uEPG() # elif mode == 20: xbmc.executebuiltin("RunScript(script.module.uepg,json=%s&skin_path=%s&refresh_path=%s&refresh_interval=%s&row_count=%s)"%(urllib.quote(json.dumps(list(self.uEPG()))),urllib.quote(json.dumps(ADDON_PATH)),urllib.quote(json.dumps(self.sysARG[0]+"?mode=20")),"7200","5")) xbmcplugin.setContent(int(self.sysARG[1]) , CONTENT_TYPE) xbmcplugin.addSortMethod(int(self.sysARG[1]) , xbmcplugin.SORT_METHOD_UNSORTED) xbmcplugin.addSortMethod(int(self.sysARG[1]) , xbmcplugin.SORT_METHOD_NONE) xbmcplugin.addSortMethod(int(self.sysARG[1]) , xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.addSortMethod(int(self.sysARG[1]) , xbmcplugin.SORT_METHOD_TITLE) xbmcplugin.endOfDirectory(int(self.sysARG[1]), cacheToDisc=False)
class Tmdb(object): '''get metadata from tmdb''' api_key = None def __init__(self, simplecache=None): '''Initialize - optionaly provide simplecache object''' if not simplecache: from simplecache import SimpleCache self.cache = SimpleCache() else: self.cache = simplecache addon = xbmcaddon.Addon(id=ADDON_ID) self.api_key = addon.getSetting("tmdb_apikey") del addon def search_movie(self, title, year="", manual_select=False): ''' Search tmdb for a specific movie, returns full details of best match parameters: title: (required) the title of the movie to search for year: (optional) the year of the movie to search for (enhances search result if supplied) manual_select: (optional) if True will show select dialog with all results ''' details = self.select_best_match(self.search_movies(title, year), manual_select=manual_select) if details: details = self.get_movie_details(details["id"]) return details @use_cache(30) def search_movieset(self, title): '''search for movieset details providing the title of the set''' details = {} params = {"query": title, "language": KODI_LANGUAGE} result = self.get_data("search/collection", params) if result: set_id = result[0]["id"] details = self.get_movieset_details(set_id) return details @use_cache(4) def search_tvshow(self, title, year="", manual_select=False): ''' Search tmdb for a specific movie, returns full details of best match parameters: title: (required) the title of the movie to search for year: (optional) the year of the movie to search for (enhances search result if supplied) manual_select: (optional) if True will show select dialog with all results ''' details = self.select_best_match(self.search_tvshows(title, year), manual_select=manual_select) if details: details = self.get_tvshow_details(details["id"]) return details @use_cache(4) def search_video(self, title, prefyear="", preftype="", manual_select=False): ''' Search tmdb for a specific entry (can be movie or tvshow), returns full details of best match parameters: title: (required) the title of the movie/tvshow to search for prefyear: (optional) prefer result if year matches preftype: (optional) prefer result if type matches manual_select: (optional) if True will show select dialog with all results ''' results = self.search_videos(title) details = self.select_best_match(results, prefyear=prefyear, preftype=preftype, preftitle=title, manual_select=manual_select) if details and details["media_type"] == "movie": details = self.get_movie_details(details["id"]) elif details and "tv" in details["media_type"]: details = self.get_tvshow_details(details["id"]) return details @use_cache(4) def search_videos(self, title): ''' Search tmdb for a specific entry (can be movie or tvshow), parameters: title: (required) the title of the movie/tvshow to search for ''' results = [] page = 1 maxpages = 5 while page < maxpages: params = {"query": title, "language": KODI_LANGUAGE, "page": page} subresults = self.get_data("search/multi", params) page += 1 if subresults: for item in subresults: if item["media_type"] in ["movie", "tv"]: results.append(item) else: break return results @use_cache(4) def search_movies(self, title, year=""): ''' Search tmdb for a specific movie, returns a list of all closest matches parameters: title: (required) the title of the movie to search for year: (optional) the year of the movie to search for (enhances search result if supplied) ''' params = {"query": title, "language": KODI_LANGUAGE} if year: params["year"] = try_parse_int(year) return self.get_data("search/movie", params) @use_cache(4) def search_tvshows(self, title, year=""): ''' Search tmdb for a specific tvshow, returns a list of all closest matches parameters: title: (required) the title of the tvshow to search for year: (optional) the first air date year of the tvshow to search for (enhances search result if supplied) ''' params = {"query": title, "language": KODI_LANGUAGE} if year: params["first_air_date_year"] = try_parse_int(year) return self.get_data("search/tv", params) def get_actor(self, name): ''' Search tmdb for a specific actor/person, returns the best match as kodi compatible dict required parameter: name --> the name of the person ''' params = {"query": name, "language": KODI_LANGUAGE} result = self.get_data("search/person", params) if result: result = result[0] cast_thumb = "http://image.tmdb.org/t/p/original%s" % result[ "profile_path"] if result["profile_path"] else "" item = {"name": result["name"], "thumb": cast_thumb, "roles": [item["title"] if item.get("title") else item["name"] for item in result["known_for"]]} return item else: return {} def get_movie_details(self, movie_id): '''get all moviedetails''' params = { "append_to_response": "keywords,videos,credits,images", "include_image_language": "%s,en" % KODI_LANGUAGE, "language": KODI_LANGUAGE } return self.map_details(self.get_data("movie/%s" % movie_id, params), "movie") def get_movieset_details(self, movieset_id): '''get all moviesetdetails''' details = {"art": {}} params = {"language": KODI_LANGUAGE} result = self.get_data("collection/%s" % movieset_id, params) if result: details["title"] = result["name"] details["plot"] = result["overview"] details["tmdb_id"] = result["id"] details["art"]["poster"] = "http://image.tmdb.org/t/p/original%s" % result["poster_path"] details["art"]["fanart"] = "http://image.tmdb.org/t/p/original%s" % result["backdrop_path"] details["totalmovies"] = len(result["parts"]) return details def get_tvshow_details(self, tvshow_id): '''get all tvshowdetails''' params = { "append_to_response": "keywords,videos,external_ids,credits,images", "include_image_language": "%s,en" % KODI_LANGUAGE, "language": KODI_LANGUAGE } return self.map_details(self.get_data("tv/%s" % tvshow_id, params), "tvshow") def get_videodetails_by_externalid(self, extid, extid_type): '''get metadata by external ID (like imdbid)''' params = {"external_source": extid_type, "language": KODI_LANGUAGE} results = self.get_data("find/%s" % extid, params) if results and results["movie_results"]: return self.get_movie_details(results["movie_results"][0]["id"]) elif results and results["tv_results"]: return self.get_tvshow_details(results["tv_results"][0]["id"]) return {} def get_data(self, endpoint, params): '''helper method to get data from tmdb json API''' if self.api_key: params["api_key"] = self.api_key rate_limit = None expiration = datetime.timedelta(days=7) else: params["api_key"] = "ae06df54334aa653354e9a010f4b81cb" # without personal api key = rate limiting and older info from cache rate_limit = ("themoviedb.org",10) expiration = datetime.timedelta(days=60) cachestr = "tmdb.%s" % params.itervalues() cache = self.cache.get(cachestr) if cache: # data obtained from cache result = cache else: # no cache, grab data from API url = u'http://api.themoviedb.org/3/%s' % endpoint result = get_json(url, params) # make sure that we have a plot value (if localized value fails, fallback to english) if result and "language" in params and "overview" in result: if not result["overview"] and params["language"] != "en": params["language"] = "en" result2 = get_json(url, params) if result2 and result2.get("overview"): result = result2 self.cache.set(url, result, expiration=expiration) return result def map_details(self, data, media_type): '''helper method to map the details received from tmdb to kodi compatible formatting''' if not data: return {} details = {} details["tmdb_id"] = data["id"] details["rating"] = data["vote_average"] details["votes"] = data["vote_count"] details["rating.tmdb"] = data["vote_average"] details["votes.tmdb"] = data["vote_count"] details["popularity"] = data["popularity"] details["popularity.tmdb"] = data["popularity"] details["plot"] = data["overview"] details["genre"] = [item["name"] for item in data["genres"]] details["homepage"] = data["homepage"] details["status"] = data["status"] details["cast"] = [] details["castandrole"] = [] details["writer"] = [] details["director"] = [] details["media_type"] = media_type # cast if "credits" in data: if "cast" in data["credits"]: for cast_member in data["credits"]["cast"]: cast_thumb = "" if cast_member["profile_path"]: cast_thumb = "http://image.tmdb.org/t/p/original%s" % cast_member["profile_path"] details["cast"].append({"name": cast_member["name"], "role": cast_member["character"], "thumbnail": cast_thumb}) details["castandrole"].append((cast_member["name"], cast_member["character"])) # crew (including writers and directors) if "crew" in data["credits"]: for crew_member in data["credits"]["crew"]: cast_thumb = "" if crew_member["profile_path"]: cast_thumb = "http://image.tmdb.org/t/p/original%s" % crew_member["profile_path"] if crew_member["job"] in ["Author", "Writer"]: details["writer"].append(crew_member["name"]) if crew_member["job"] in ["Producer", "Executive Producer"]: details["director"].append(crew_member["name"]) if crew_member["job"] in ["Producer", "Executive Producer", "Author", "Writer"]: details["cast"].append({"name": crew_member["name"], "role": crew_member["job"], "thumbnail": cast_thumb}) # artwork details["art"] = {} if data.get("images"): if data["images"].get("backdrops"): fanarts = self.get_best_images(data["images"]["backdrops"]) details["art"]["fanarts"] = fanarts details["art"]["fanart"] = fanarts[0] if fanarts else "" if data["images"].get("posters"): posters = self.get_best_images(data["images"]["posters"]) details["art"]["posters"] = posters details["art"]["poster"] = posters[0] if posters else "" if not details["art"].get("poster") and data.get("poster_path"): details["art"]["poster"] = "http://image.tmdb.org/t/p/original%s" % data["poster_path"] if not details["art"].get("fanart") and data.get("backdrop_path"): details["art"]["fanart"] = "http://image.tmdb.org/t/p/original%s" % data["backdrop_path"] # movies only if media_type == "movie": details["title"] = data["title"] details["originaltitle"] = data["original_title"] if data["belongs_to_collection"]: details["set"] = data["belongs_to_collection"].get("name", "") if data.get("release_date"): details["premiered"] = data["release_date"] details["year"] = try_parse_int(data["release_date"].split("-")[0]) details["tagline"] = data["tagline"] if data["runtime"]: details["runtime"] = data["runtime"] * 60 details["imdbnumber"] = data["imdb_id"] details["budget"] = data["budget"] details["budget.formatted"] = int_with_commas(data["budget"]) details["revenue"] = data["revenue"] details["revenue.formatted"] = int_with_commas(data["revenue"]) if data.get("production_companies"): details["studio"] = [item["name"] for item in data["production_companies"]] if data.get("production_countries"): details["country"] = [item["name"] for item in data["production_countries"]] if data.get("keywords"): details["tag"] = [item["name"] for item in data["keywords"]["keywords"]] # tvshows only if media_type == "tvshow": details["title"] = data["name"] details["originaltitle"] = data["original_name"] if data.get("created_by"): details["director"] += [item["name"] for item in data["created_by"]] if data.get("episode_run_time"): details["runtime"] = data["episode_run_time"][0] * 60 if data.get("first_air_date"): details["premiered"] = data["first_air_date"] details["year"] = try_parse_int(data["first_air_date"].split("-")[0]) if "last_air_date" in data: details["lastaired"] = data["last_air_date"] if data.get("networks"): details["studio"] = [item["name"] for item in data["networks"]] if "origin_country" in data: details["country"] = data["origin_country"] if data.get("external_ids"): details["imdbnumber"] = data["external_ids"].get("imdb_id", "") details["tvdb_id"] = data["external_ids"].get("tvdb_id", "") if "results" in data["keywords"]: details["tag"] = [item["name"] for item in data["keywords"]["results"]] # trailer for video in data["videos"]["results"]: if video["site"] == "YouTube" and video["type"] == "Trailer": details["trailer"] = 'plugin://plugin.video.youtube/?action=play_video&videoid=%s' % video["key"] break return details @staticmethod def get_best_images(images): '''get the best 5 images based on number of likes and the language''' for image in images: score = 0 score += image["vote_count"] score += image["vote_average"] * 10 score += image["height"] if "iso_639_1" in image: if image["iso_639_1"] == KODI_LANGUAGE: score += 1000 image["score"] = score if not image["file_path"].startswith("http"): image["file_path"] = "http://image.tmdb.org/t/p/original%s" % image["file_path"] images = sorted(images, key=itemgetter("score"), reverse=True) return [image["file_path"] for image in images] @staticmethod def select_best_match(results, prefyear="", preftype="", preftitle="", manual_select=False): '''helper to select best match or let the user manually select the best result from the search''' details = {} # score results if one or more preferences are given if results and (prefyear or preftype or preftitle): newdata = [] preftitle = preftitle.lower() for item in results: item["score"] = 0 itemtitle = item["title"] if item.get("title") else item["name"] itemtitle = itemtitle.lower() itemorgtitle = item["original_title"] if item.get("original_title") else item["original_name"] itemorgtitle = itemorgtitle.lower() # high score if year matches if prefyear: if item.get("first_air_date") and prefyear in item["first_air_date"]: item["score"] += 800 # matches preferred year if item.get("release_date") and prefyear in item["release_date"]: item["score"] += 800 # matches preferred year # find exact match on title if preftitle and preftitle == itemtitle: item["score"] += 1000 # exact match! if preftitle and preftitle == itemorgtitle: item["score"] += 1000 # exact match! # match title by replacing some characters if preftitle and get_compare_string(preftitle) == get_compare_string(itemtitle): item["score"] += 750 if preftitle and get_compare_string(preftitle) == get_compare_string(itemorgtitle): item["score"] += 750 # add SequenceMatcher score to the results if preftitle: stringmatchscore = SM(None, preftitle, itemtitle).ratio( ) + SM(None, preftitle, itemorgtitle).ratio() if stringmatchscore > 1.6: item["score"] += stringmatchscore * 250 # higher score if result ALSO matches our preferred type or native language # (only when we already have a score) if item["score"]: if preftype and (item["media_type"] in preftype) or (preftype in item["media_type"]): item["score"] += 250 # matches preferred type if item["original_language"] == KODI_LANGUAGE: item["score"] += 500 # native language! if KODI_LANGUAGE.upper() in item.get("origin_country", []): item["score"] += 500 # native language! if KODI_LANGUAGE in item.get("languages", []): item["score"] += 500 # native language! if item["score"] > 500 or manual_select: newdata.append(item) results = sorted(newdata, key=itemgetter("score"), reverse=True) if results and manual_select: # show selectdialog to manually select the item results_list = [] for item in results: title = item["name"] if "name" in item else item["title"] if item.get("premiered"): year = item["premiered"].split("-")[0] else: year = item.get("first_air_date", "").split("-")[0] if item["poster_path"]: thumb = "http://image.tmdb.org/t/p/original%s" % item["poster_path"] else: thumb = "" label = "%s (%s) - %s" % (title, year, item["media_type"]) listitem = xbmcgui.ListItem(label=label, iconImage=thumb, label2=item["overview"]) results_list.append(listitem) if manual_select and results_list: dialog = DialogSelect("DialogSelect.xml", "", listing=results_list, window_title="%s - TMDB" % xbmc.getLocalizedString(283)) dialog.doModal() selected_item = dialog.result del dialog if selected_item != -1: details = results[selected_item] else: results = [] if not details and results: # just grab the first item as best match details = results[0] return details
def __init__(self): log('__init__') self.cache = SimpleCache()
def __init__(self): log('__init__') self.cache = SimpleCache() self.stateMenu = self.getStates()
class PluginContent: '''Hidden plugin entry point providing some helper features''' params = {} win = None def __init__(self): self.cache = SimpleCache() self.mutils = MetadataUtils() self.win = xbmcgui.Window(10000) try: self.params = dict( urlparse.parse_qsl(sys.argv[2].replace( '?', '').lower().decode("utf-8"))) log_msg("plugin called with parameters: %s" % self.params) self.main() except Exception as exc: log_exception(__name__, exc) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) # cleanup when done processing self.close() def close(self): '''Cleanup Kodi Cpython instances''' self.cache.close() self.mutils.close() del self.mutils del self.win def main(self): '''main action, load correct function''' action = self.params.get("action", "") if self.win.getProperty("SkinHelperShutdownRequested"): # do not proceed if kodi wants to exit log_msg( "%s --> Not forfilling request: Kodi is exiting" % __name__, xbmc.LOGWARNING) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) else: try: if hasattr(self.__class__, action): # launch module for action provided by this plugin getattr(self, action)() else: # legacy (widget) path called !!! self.load_widget() except Exception as exc: log_exception(__name__, exc) def load_widget(self): '''legacy entrypoint called (widgets are moved to seperate addon), start redirect...''' action = self.params.get("action", "") newaddon = "script.skin.helper.widgets" log_msg( "Deprecated method: %s. Please reassign your widgets to get rid of this message. -" "This automatic redirect will be removed in the future" % (action), xbmc.LOGWARNING) paramstring = "" for key, value in self.params.iteritems(): paramstring += ",%s=%s" % (key, value) if getCondVisibility("System.HasAddon(%s)" % newaddon): # TEMP !!! for backwards compatability reasons only - to be removed in the near future!! import imp addon = xbmcaddon.Addon(newaddon) addon_path = addon.getAddonInfo('path').decode("utf-8") imp.load_source('plugin', os.path.join(addon_path, "plugin.py")) from plugin import main main.Main() del addon else: # trigger install of the addon if KODI_VERSION > 16: xbmc.executebuiltin("InstallAddon(%s)" % newaddon) else: xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon) def playchannel(self): '''play channel from widget helper''' params = {"item": {"channelid": int(self.params["channelid"])}} self.mutils.kodidb.set_json("Player.Open", params) def playrecording(self): '''retrieve the recording and play to get resume working''' recording = self.mutils.kodidb.recording(self.params["recordingid"]) params = {"item": {"recordingid": recording["recordingid"]}} self.mutils.kodidb.set_json("Player.Open", params) # manually seek because passing resume to the player json cmd doesn't seem to work if recording["resume"].get("position"): for i in range(50): if getCondVisibility("Player.HasVideo"): break xbmc.sleep(50) xbmc.Player().seekTime(recording["resume"].get("position")) def launch(self): '''launch any builtin action using a plugin listitem''' if "runscript" in self.params["path"]: self.params["path"] = self.params["path"].replace("?", ",") xbmc.executebuiltin(self.params["path"]) def playalbum(self): '''helper to play an entire album''' xbmc.executeJSONRPC( '{ "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "albumid": %d } }, "id": 1 }' % int(self.params["albumid"])) def smartshortcuts(self): '''called from skinshortcuts to retrieve listing of all smart shortcuts''' import skinshortcuts skinshortcuts.get_smartshortcuts(self.params.get("path", "")) @staticmethod def backgrounds(): '''called from skinshortcuts to retrieve listing of all backgrounds''' import skinshortcuts skinshortcuts.get_backgrounds() def widgets(self): '''called from skinshortcuts to retrieve listing of all widgetss''' import skinshortcuts skinshortcuts.get_widgets(self.params.get("path", ""), self.params.get("sublevel", "")) def resourceimages(self): '''retrieve listing of specific resource addon images''' from resourceaddons import get_resourceimages addontype = self.params.get("addontype", "") for item in get_resourceimages(addontype, True): listitem = xbmcgui.ListItem(item[0], label2=item[2], path=item[1], iconImage=item[3]) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item[1], listitem=listitem, isFolder=False) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def extrafanart(self): '''helper to display extrafanart in multiimage control in the skin''' fanarts = eval(self.params["fanarts"]) # process extrafanarts for count, item in enumerate(fanarts): listitem = xbmcgui.ListItem("fanart%s" % count, path=item) listitem.setProperty('mimetype', 'image/jpeg') xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item, listitem=listitem) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def extraposter(self): '''helper to display extraposter in multiimage control in the skin''' posters = eval(self.params["posters"]) # process extraposters for count, item in enumerate(posters): listitem = xbmcgui.ListItem("poster%s" % count, path=item) listitem.setProperty('mimetype', 'image/jpeg') xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item, listitem=listitem) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def genrebackground(self): '''helper to display images for a specific genre in multiimage control in the skin''' genre = self.params.get("genre").split(".")[0] arttype = self.params.get("arttype", "fanart") randomize = self.params.get("random", "false") == "true" mediatype = self.params.get("mediatype", "movies") if genre and genre != "..": filters = [{"operator": "is", "field": "genre", "value": genre}] if randomize: sort = {"method": "random", "order": "descending"} else: sort = {"method": "sorttitle", "order": "ascending"} items = getattr(self.mutils.kodidb, mediatype)(sort=sort, filters=filters, limits=(0, 50)) for item in items: image = self.mutils.get_clean_image(item["art"].get( arttype, "")) if image: image = self.mutils.get_clean_image(item["art"][arttype]) listitem = xbmcgui.ListItem(image, path=image) listitem.setProperty('mimetype', 'image/jpeg') xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=image, listitem=listitem) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def getcastmedia(self): '''helper to display get all media for a specific actor''' name = self.params.get("name") if name: all_items = self.mutils.kodidb.castmedia(name) all_items = self.mutils.process_method_on_list( self.mutils.kodidb.prepare_listitem, all_items) all_items = self.mutils.process_method_on_list( self.mutils.kodidb.create_listitem, all_items) xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items, len(all_items)) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def getcast(self): '''helper to get all cast for a given media item''' db_id = None all_cast = [] all_cast_names = list() cache_str = "" download_thumbs = self.params.get("downloadthumbs", "") == "true" extended_cast_action = self.params.get("castaction", "") == "extendedinfo" movie = self.params.get("movie") tvshow = self.params.get("tvshow") episode = self.params.get("episode") movieset = self.params.get("movieset") try: # try to parse db_id if movieset: cache_str = "movieset.castcache-%s-%s" % ( self.params["movieset"], download_thumbs) db_id = int(movieset) elif tvshow: cache_str = "tvshow.castcache-%s-%s" % (self.params["tvshow"], download_thumbs) db_id = int(tvshow) elif movie: cache_str = "movie.castcache-%s-%s" % (self.params["movie"], download_thumbs) db_id = int(movie) elif episode: cache_str = "episode.castcache-%s-%s" % ( self.params["episode"], download_thumbs) db_id = int(episode) except Exception: pass cachedata = self.cache.get(cache_str) if cachedata: # get data from cache all_cast = cachedata else: # retrieve data from json api... if movie and db_id: all_cast = self.mutils.kodidb.movie(db_id)["cast"] elif movie and not db_id: filters = [{ "operator": "is", "field": "title", "value": movie }] result = self.mutils.kodidb.movies(filters=filters) all_cast = result[0]["cast"] if result else [] elif tvshow and db_id: all_cast = self.mutils.kodidb.tvshow(db_id)["cast"] elif tvshow and not db_id: filters = [{ "operator": "is", "field": "title", "value": tvshow }] result = self.mutils.kodidb.tvshows(filters=filters) all_cast = result[0]["cast"] if result else [] elif episode and db_id: all_cast = self.mutils.kodidb.episode(db_id)["cast"] elif episode and not db_id: filters = [{ "operator": "is", "field": "title", "value": episode }] result = self.mutils.kodidb.episodes(filters=filters) all_cast = result[0]["cast"] if result else [] elif movieset: if not db_id: for item in self.mutils.kodidb.moviesets(): if item["title"].lower() == movieset.lower(): db_id = item["setid"] if db_id: json_result = self.mutils.kodidb.movieset( db_id, include_set_movies_fields=["cast"]) if "movies" in json_result: for movie in json_result['movies']: all_cast += movie['cast'] # optional: download missing actor thumbs if all_cast and download_thumbs: for cast in all_cast: if cast.get("thumbnail"): cast["thumbnail"] = self.mutils.get_clean_image( cast.get("thumbnail")) if not cast.get("thumbnail"): artwork = self.mutils.tmdb.get_actor(cast["name"]) cast["thumbnail"] = artwork.get("thumb", "") # lookup tmdb if item is requested that is not in local db if not all_cast: tmdbdetails = {} if movie and not db_id: tmdbdetails = self.mutils.tmdb.search_movie(movie) elif tvshow and not db_id: tmdbdetails = self.mutils.tmdb.search_tvshow(tvshow) if tmdbdetails.get("cast"): all_cast = tmdbdetails["cast"] # save to cache self.cache.set(cache_str, all_cast) # process listing with the results... for cast in all_cast: if cast.get("name") not in all_cast_names: liz = xbmcgui.ListItem(label=cast.get("name"), label2=cast.get("role"), iconImage=cast.get("thumbnail")) if extended_cast_action: url = "RunScript(script.extendedinfo,info=extendedactorinfo,name=%s)" % cast.get( "name") url = "plugin://script.skin.helper.service/?action=launch&path=%s" % url is_folder = False else: url = "RunScript(script.skin.helper.service,action=getcastmedia,name=%s)" % cast.get( "name") url = "plugin://script.skin.helper.service/?action=launch&path=%s" % urlencode( url) is_folder = False all_cast_names.append(cast.get("name")) liz.setThumbnailImage(cast.get("thumbnail")) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=liz, isFolder=is_folder) xbmcplugin.endOfDirectory(int(sys.argv[1])) @staticmethod def alphabet(): '''display an alphabet scrollbar in listings''' all_letters = [] if xbmc.getInfoLabel("Container.NumItems"): for i in range(int(xbmc.getInfoLabel("Container.NumItems"))): all_letters.append( xbmc.getInfoLabel("Listitem(%s).SortLetter" % i).upper()) start_number = "" for number in ["2", "3", "4", "5", "6", "7", "8", "9"]: if number in all_letters: start_number = number break for letter in [ start_number, "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" ]: if letter == start_number: label = "#" else: label = letter listitem = xbmcgui.ListItem(label=label) if letter not in all_letters: lipath = "noop" listitem.setProperty("NotAvailable", "true") else: lipath = "plugin://script.skin.helper.service/?action=alphabetletter&letter=%s" % letter xbmcplugin.addDirectoryItem(int(sys.argv[1]), lipath, listitem, isFolder=False) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def alphabetletter(self): '''used with the alphabet scrollbar to jump to a letter''' if KODI_VERSION > 16: xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=False, listitem=xbmcgui.ListItem()) letter = self.params.get("letter", "").upper() jumpcmd = "" if letter in ["A", "B", "C", "2"]: jumpcmd = "2" elif letter in ["D", "E", "F", "3"]: jumpcmd = "3" elif letter in ["G", "H", "I", "4"]: jumpcmd = "4" elif letter in ["J", "K", "L", "5"]: jumpcmd = "5" elif letter in ["M", "N", "O", "6"]: jumpcmd = "6" elif letter in ["P", "Q", "R", "S", "7"]: jumpcmd = "7" elif letter in ["T", "U", "V", "8"]: jumpcmd = "8" elif letter in ["W", "X", "Y", "Z", "9"]: jumpcmd = "9" if jumpcmd: xbmc.executebuiltin("SetFocus(50)") for i in range(40): xbmc.executeJSONRPC( '{ "jsonrpc": "2.0", "method": "Input.ExecuteAction",\ "params": { "action": "jumpsms%s" }, "id": 1 }' % (jumpcmd)) xbmc.sleep(50) if xbmc.getInfoLabel("ListItem.Sortletter").upper() == letter: break
class KodiDb(object): '''various methods and helpers to get data from kodi json api''' def __init__(self, simplecache=None): '''Initialize - optionaly provide simplecache object''' if not simplecache: from simplecache import SimpleCache self.cache = SimpleCache() else: self.cache = simplecache def movie(self, db_id): '''get moviedetails from kodi db''' return self.get_json("VideoLibrary.GetMovieDetails", returntype="moviedetails", fields=FIELDS_MOVIES, optparam=("movieid", try_parse_int(db_id))) def movies(self, sort=None, filters=None, limits=None, filtertype=None): '''get moviedetails from kodi db''' return self.get_json("VideoLibrary.GetMovies", sort=sort, filters=filters, fields=FIELDS_MOVIES, limits=limits, returntype="movies", filtertype=filtertype) def movie_by_imdbid(self, imdb_id): '''gets a movie from kodidb by imdbid.''' # apparently you can't filter on imdb so we have to do this the complicated way if KODI_VERSION > 16: # from Kodi 17 we have a uniqueid field instead of imdbnumber all_items = self.cache.get("kodidb.all_movies_uniqueids") if not all_items: all_items = self.get_json('VideoLibrary.GetMovies', fields=["uniqueid"], returntype="movies") self.cache.set("kodidb.all_movies_uniqueids", all_items, expiration=datetime.timedelta(minutes=15)) for item in all_items: for item2 in item["uniqueid"].values(): if item2 == imdb_id: return self.movie(item["movieid"]) else: all_items = self.get_json('VideoLibrary.GetMovies', fields=["imdbnumber"], returntype="movies") for item in all_items: if item["imdbnumber"] == imdb_id: return self.movie(item["movieid"]) return {} def tvshow(self, db_id): '''get tvshow from kodi db''' tvshow = self.get_json("VideoLibrary.GetTvShowDetails", returntype="tvshowdetails", fields=FIELDS_TVSHOWS, optparam=("tvshowid", try_parse_int(db_id))) return self.tvshow_watchedcounts(tvshow) def tvshows(self, sort=None, filters=None, limits=None, filtertype=None): '''get tvshows from kodi db''' tvshows = self.get_json("VideoLibrary.GetTvShows", sort=sort, filters=filters, fields=FIELDS_TVSHOWS, limits=limits, returntype="tvshows", filtertype=filtertype) # append watched counters for tvshow in tvshows: tvshow = self.tvshow_watchedcounts(tvshow) return tvshows def tvshow_by_imdbid(self, imdb_id): '''gets a tvshow from kodidb by imdbid (or tvdbid).''' # apparently you can't filter on imdb so we have to do this the complicated way if KODI_VERSION > 16: # from Kodi 17 we have a uniqueid field instead of imdbnumber all_items = self.get_json('VideoLibrary.GetTvShows', fields=["uniqueid"], returntype="tvshows") for item in all_items: for item2 in item["uniqueid"].values(): if item2 == imdb_id: return self.tvshow(item["tvshowid"]) else: # pre-kodi 17 approach all_items = self.get_json('VideoLibrary.GetTvShows', fields=["imdbnumber"], returntype="tvshows") for item in all_items: if item["imdbnumber"] == imdb_id: return self.tvshow(item["tvshowid"]) return {} def episode(self, db_id): '''get episode from kodi db''' return self.get_json("VideoLibrary.GetEpisodeDetails", returntype="episodedetails", fields=FIELDS_EPISODES, optparam=("episodeid", try_parse_int(db_id))) def episodes(self, sort=None, filters=None, limits=None, filtertype=None, tvshowid=None, fields=FIELDS_EPISODES): '''get episodes from kodi db''' if tvshowid: params = ("tvshowid", try_parse_int(tvshowid)) else: params = None return self.get_json("VideoLibrary.GetEpisodes", sort=sort, filters=filters, fields=fields, limits=limits, returntype="episodes", filtertype=filtertype, optparam=params) def musicvideo(self, db_id): '''get musicvideo from kodi db''' return self.get_json("VideoLibrary.GetMusicVideoDetails", returntype="musicvideodetails", fields=FIELDS_MUSICVIDEOS, optparam=("musicvideoid", try_parse_int(db_id))) def musicvideos(self, sort=None, filters=None, limits=None, filtertype=None): '''get musicvideos from kodi db''' return self.get_json("VideoLibrary.GetMusicVideos", sort=sort, filters=filters, fields=FIELDS_MUSICVIDEOS, limits=limits, returntype="musicvideos", filtertype=filtertype) def movieset(self, db_id, include_set_movies_fields=""): '''get movieset from kodi db''' if include_set_movies_fields: optparams = [("setid", try_parse_int(db_id)), ("movies", { "properties": include_set_movies_fields })] else: optparams = ("setid", try_parse_int(db_id)) return self.get_json("VideoLibrary.GetMovieSetDetails", returntype="", fields=["title", "art", "playcount"], optparam=optparams) def moviesets(self, sort=None, limits=None, include_set_movies=False): '''get moviesetdetails from kodi db''' if include_set_movies: optparam = ("movies", {"properties": FIELDS_MOVIES}) else: optparam = None return self.get_json("VideoLibrary.GetMovieSets", sort=sort, fields=["title", "art", "playcount"], limits=limits, returntype="", optparam=optparam) def files(self, vfspath, sort=None, limits=None): '''gets all items in a kodi vfs path''' return self.get_json("Files.GetDirectory", returntype="", optparam=("directory", vfspath), fields=FIELDS_FILES, sort=sort, limits=limits) def genres(self, media_type): '''return all genres for the given media type (movie/tvshow/musicvideo)''' return self.get_json("VideoLibrary.GetGenres", fields=["thumbnail", "title"], returntype="genres", optparam=("type", media_type)) def song(self, db_id): '''get songdetails from kodi db''' return self.get_json("AudioLibrary.GetSongDetails", returntype="songdetails", fields=FIELDS_SONGS, optparam=("songid", try_parse_int(db_id))) def songs(self, sort=None, filters=None, limits=None, filtertype=None): '''get songs from kodi db''' return self.get_json("AudioLibrary.GetSongs", sort=sort, filters=filters, fields=FIELDS_SONGS, limits=limits, returntype="songs", filtertype=filtertype) def album(self, db_id): '''get albumdetails from kodi db''' album = self.get_json("AudioLibrary.GetAlbumDetails", returntype="albumdetails", fields=FIELDS_ALBUMS, optparam=("albumid", try_parse_int(db_id))) # override type as the kodi json api is returning the album type instead of mediatype album["type"] = "album" return album def albums(self, sort=None, filters=None, limits=None, filtertype=None): '''get albums from kodi db''' albums = self.get_json("AudioLibrary.GetAlbums", sort=sort, filters=filters, fields=FIELDS_ALBUMS, limits=limits, returntype="albums", filtertype=filtertype) # override type as the kodi json api is returning the album type instead of mediatype for album in albums: album["type"] = "album" return albums def artist(self, db_id): '''get artistdetails from kodi db''' return self.get_json("AudioLibrary.GetArtistDetails", returntype="artistdetails", fields=FIELDS_ARTISTS, optparam=("artistid", try_parse_int(db_id))) def artists(self, sort=None, filters=None, limits=None, filtertype=None): '''get artists from kodi db''' return self.get_json("AudioLibrary.GetArtists", sort=sort, filters=filters, fields=FIELDS_ARTISTS, limits=limits, returntype="artists", filtertype=filtertype) def recording(self, db_id): '''get pvr recording from kodi db''' return self.get_json("PVR.GetRecordingDetails", returntype="recordingdetails", fields=FIELDS_RECORDINGS, optparam=("recordingid", try_parse_int(db_id))) def recordings(self, limits=None): '''get pvr recordings from kodi db''' return self.get_json("PVR.GetRecordings", fields=FIELDS_RECORDINGS, limits=limits, returntype="recordings") def channel(self, db_id): '''get pvr channel from kodi db''' return self.get_json("PVR.GetChannelDetails", returntype="channeldetails", fields=FIELDS_CHANNELS, optparam=("channelid", try_parse_int(db_id))) def channels(self, limits=None, channelgroupid="alltv"): '''get pvr channels from kodi db''' return self.get_json("PVR.GetChannels", fields=FIELDS_CHANNELS, limits=limits, returntype="channels", optparam=("channelgroupid", channelgroupid)) def channelgroups(self, limits=None, channeltype="tv"): '''get pvr channelgroups from kodi db''' return self.get_json("PVR.GetChannelGroups", fields=[], limits=limits, returntype="channelgroups", optparam=("channeltype", channeltype)) def timers(self, limits=None): '''get pvr recordings from kodi db''' fields = [ "title", "endtime", "starttime", "channelid", "summary", "file" ] return self.get_json("PVR.GetTimers", fields=fields, limits=limits, returntype="timers") def favourites(self): '''get kodi favourites''' items = self.get_favourites_from_file() if not items: fields = ["path", "thumbnail", "window", "windowparameter"] optparams = ("type", None) items = self.get_json("Favourites.GetFavourites", fields=fields, optparam=optparams) return items def castmedia(self, actorname): '''helper to display all media (movies/shows) for a specific actor''' # use db counts as simple checksum all_items = [] filters = [{ "operator": "contains", "field": "actor", "value": actorname }] all_items = self.movies(filters=filters) for item in self.tvshows(filters=filters): item["file"] = "videodb://tvshows/titles/%s" % item["tvshowid"] item["isFolder"] = True all_items.append(item) return all_items def actors(self): '''return all actors''' all_items = [] all_actors = [] result = self.files("videodb://movies/actors") result += self.files("videodb://tvshows/actors") for item in result: if not item["label"] in all_actors: all_actors.append(item["label"]) item["type"] = "actor" item["isFolder"] = True if not item["art"].get("thumb"): item["art"]["thumb"] = "DefaultActor.png" all_items.append(item) return sorted(all_items, key=itemgetter("label")) @staticmethod def set_json(jsonmethod, params): '''method to set info in the kodi json api''' kodi_json = {} kodi_json["jsonrpc"] = "2.0" kodi_json["method"] = jsonmethod kodi_json["params"] = params kodi_json["id"] = 1 json_response = xbmc.executeJSONRPC(try_encode(json.dumps(kodi_json))) return json.loads(json_response.decode('utf-8', 'replace')) @staticmethod def get_json(jsonmethod, sort=None, filters=None, fields=None, limits=None, returntype=None, optparam=None, filtertype=None): '''method to get details from the kodi json api''' kodi_json = {} kodi_json["jsonrpc"] = "2.0" kodi_json["method"] = jsonmethod kodi_json["params"] = {} if optparam: if isinstance(optparam, list): for param in optparam: kodi_json["params"][param[0]] = param[1] else: kodi_json["params"][optparam[0]] = optparam[1] kodi_json["id"] = 1 if sort: kodi_json["params"]["sort"] = sort if filters: if not filtertype: filtertype = "and" if len(filters) > 1: kodi_json["params"]["filter"] = {filtertype: filters} else: kodi_json["params"]["filter"] = filters[0] if fields: kodi_json["params"]["properties"] = fields if limits: kodi_json["params"]["limits"] = { "start": limits[0], "end": limits[1] } json_response = xbmc.executeJSONRPC(try_encode(json.dumps(kodi_json))) json_object = json.loads(json_response.decode('utf-8', 'replace')) # set the default returntype to prevent errors if "details" in jsonmethod.lower(): result = {} else: result = [] if 'result' in json_object: if returntype and returntype in json_object['result']: # returntype specified, return immediately result = json_object['result'][returntype] else: # no returntype specified, we'll have to look for it for key, value in json_object['result'].iteritems(): if not key == "limits" and (isinstance(value, list) or isinstance(value, dict)): result = value else: log_msg(json_response) log_msg(kodi_json) return result @staticmethod def get_favourites_from_file(): '''json method for favourites doesn't return all items (such as android apps) so retrieve them from file''' allfavourites = [] try: from xml.dom.minidom import parse favourites_path = xbmc.translatePath( 'special://profile/favourites.xml').decode("utf-8") if xbmcvfs.exists(favourites_path): doc = parse(favourites_path) result = doc.documentElement.getElementsByTagName('favourite') for fav in result: action = fav.childNodes[0].nodeValue action = action.replace('"', '') label = fav.attributes['name'].nodeValue try: thumb = fav.attributes['thumb'].nodeValue except Exception: thumb = "" window = "" windowparameter = "" action_type = "unknown" if action.startswith("StartAndroidActivity"): action_type = "androidapp" elif action.startswith("ActivateWindow"): action_type = "window" actionparts = action.replace("ActivateWindow(", "").replace( ",return)", "").split(",") window = actionparts[0] if len(actionparts) > 1: windowparameter = actionparts[1] elif action.startswith("PlayMedia"): action_type = "media" action = action.replace("PlayMedia(", "")[:-1] allfavourites.append({ "label": label, "path": action, "thumbnail": thumb, "window": window, "windowparameter": windowparameter, "type": action_type }) except Exception as exc: log_exception(__name__, exc) return allfavourites @staticmethod def create_listitem(item, as_tuple=True, offscreen=True): '''helper to create a kodi listitem from kodi compatible dict with mediainfo''' try: if KODI_VERSION > 17: liz = xbmcgui.ListItem(label=item.get("label", ""), label2=item.get("label2", ""), path=item['file'], offscreen=offscreen) else: liz = xbmcgui.ListItem(label=item.get("label", ""), label2=item.get("label2", ""), path=item['file']) # only set isPlayable prop if really needed if item.get("isFolder", False): liz.setProperty('IsPlayable', 'false') elif "plugin://script.skin.helper" not in item['file']: liz.setProperty('IsPlayable', 'true') nodetype = "Video" if item["type"] in ["song", "album", "artist"]: nodetype = "Music" # extra properties for key, value in item["extraproperties"].iteritems(): liz.setProperty(key, value) # video infolabels if nodetype == "Video": infolabels = { "title": item.get("title"), "size": item.get("size"), "genre": item.get("genre"), "year": item.get("year"), "top250": item.get("top250"), "tracknumber": item.get("tracknumber"), "rating": item.get("rating"), "playcount": item.get("playcount"), "overlay": item.get("overlay"), "cast": item.get("cast"), "castandrole": item.get("castandrole"), "director": item.get("director"), "mpaa": item.get("mpaa"), "plot": item.get("plot"), "plotoutline": item.get("plotoutline"), "originaltitle": item.get("originaltitle"), "sorttitle": item.get("sorttitle"), "duration": item.get("duration"), "studio": item.get("studio"), "tagline": item.get("tagline"), "writer": item.get("writer"), "tvshowtitle": item.get("tvshowtitle"), "premiered": item.get("premiered"), "status": item.get("status"), "code": item.get("imdbnumber"), "imdbnumber": item.get("imdbnumber"), "aired": item.get("aired"), "credits": item.get("credits"), "album": item.get("album"), "artist": item.get("artist"), "votes": item.get("votes"), "trailer": item.get("trailer") } if item["type"] == "episode": infolabels["season"] = item["season"] infolabels["episode"] = item["episode"] # streamdetails if item.get("streamdetails"): liz.addStreamInfo("video", item["streamdetails"].get("video", {})) liz.addStreamInfo("audio", item["streamdetails"].get("audio", {})) liz.addStreamInfo( "subtitle", item["streamdetails"].get("subtitle", {})) if "dateadded" in item: infolabels["dateadded"] = item["dateadded"] if "date" in item: infolabels["date"] = item["date"] # music infolabels else: infolabels = { "title": item.get("title"), "size": item.get("size"), "genre": item.get("genre"), "year": item.get("year"), "tracknumber": item.get("track"), "album": item.get("album"), "artist": " / ".join(item.get('artist')), "rating": str(item.get("rating", 0)), "lyrics": item.get("lyrics"), "playcount": item.get("playcount") } if "date" in item: infolabels["date"] = item["date"] if "duration" in item: infolabels["duration"] = item["duration"] if "lastplayed" in item: infolabels["lastplayed"] = item["lastplayed"] # setting the dbtype and dbid is supported from kodi krypton and up if KODI_VERSION > 16 and item["type"] not in [ "recording", "channel", "favourite" ]: infolabels["mediatype"] = item["type"] # setting the dbid on music items is not supported ? if nodetype == "Video" and "DBID" in item["extraproperties"]: infolabels["dbid"] = item["extraproperties"]["DBID"] if "lastplayed" in item: infolabels["lastplayed"] = item["lastplayed"] # assign the infolabels liz.setInfo(type=nodetype, infoLabels=infolabels) # artwork liz.setArt(item.get("art", {})) if "icon" in item: liz.setIconImage(item['icon']) if "thumbnail" in item: liz.setThumbnailImage(item['thumbnail']) # contextmenu if item["type"] in ["episode", "season" ] and "season" in item and "tvshowid" in item: # add series and season level to widgets if "contextmenu" not in item: item["contextmenu"] = [] item["contextmenu"] += [ (xbmc.getLocalizedString(20364), "ActivateWindow(Video,videodb://tvshows/titles/%s/,return)" % (item["tvshowid"])), (xbmc.getLocalizedString(20373), "ActivateWindow(Video,videodb://tvshows/titles/%s/%s/,return)" % (item["tvshowid"], item["season"])) ] if "contextmenu" in item: liz.addContextMenuItems(item["contextmenu"]) if as_tuple: return (item["file"], liz, item.get("isFolder", False)) else: return liz except Exception as exc: log_exception(__name__, exc) log_msg(item) return None @staticmethod def prepare_listitem(item): '''helper to convert kodi output from json api to compatible format for listitems''' try: # fix values returned from json to be used as listitem values properties = item.get("extraproperties", {}) # set type for idvar in [('episode', 'DefaultTVShows.png'), ('tvshow', 'DefaultTVShows.png'), ('movie', 'DefaultMovies.png'), ('song', 'DefaultAudio.png'), ('album', 'DefaultAudio.png'), ('artist', 'DefaultArtist.png'), ('musicvideo', 'DefaultMusicVideos.png'), ('recording', 'DefaultTVShows.png'), ('channel', 'DefaultAddonPVRClient.png')]: dbid = item.get(idvar[0] + "id") if dbid: properties["DBID"] = str(dbid) if not item.get("type"): item["type"] = idvar[0] if not item.get("icon"): item["icon"] = idvar[1] break # general properties if "genre" in item and isinstance(item['genre'], list): item["genre"] = " / ".join(item['genre']) if "studio" in item and isinstance(item['studio'], list): item["studio"] = " / ".join(item['studio']) if "writer" in item and isinstance(item['writer'], list): item["writer"] = " / ".join(item['writer']) if 'director' in item and isinstance(item['director'], list): item["director"] = " / ".join(item['director']) if 'artist' in item and not isinstance(item['artist'], list): item["artist"] = [item['artist']] if 'artist' not in item: item["artist"] = [] if item['type'] == "album" and 'album' not in item and 'label' in item: item['album'] = item['label'] if "duration" not in item and "runtime" in item: if (item["runtime"] / 60) > 300: item["duration"] = item["runtime"] / 60 else: item["duration"] = item["runtime"] if "plot" not in item and "comment" in item: item["plot"] = item["comment"] if "tvshowtitle" not in item and "showtitle" in item: item["tvshowtitle"] = item["showtitle"] if "premiered" not in item and "firstaired" in item: item["premiered"] = item["firstaired"] if "firstaired" in item and "aired" not in item: item["aired"] = item["firstaired"] if "imdbnumber" not in properties and "imdbnumber" in item: properties["imdbnumber"] = item["imdbnumber"] if "imdbnumber" not in properties and "uniqueid" in item: for value in item["uniqueid"].values(): if value.startswith("tt"): properties["imdbnumber"] = value properties["dbtype"] = item["type"] properties["DBTYPE"] = item["type"] properties["type"] = item["type"] properties["path"] = item.get("file") # cast list_cast = [] list_castandrole = [] item["cast_org"] = item.get("cast", []) if "cast" in item and isinstance(item["cast"], list): for castmember in item["cast"]: if isinstance(castmember, dict): list_cast.append(castmember.get("name", "")) list_castandrole.append( (castmember["name"], castmember["role"])) else: list_cast.append(castmember) list_castandrole.append((castmember, "")) item["cast"] = list_cast item["castandrole"] = list_castandrole if "season" in item and "episode" in item: properties["episodeno"] = "s%se%s" % (item.get("season"), item.get("episode")) if "resume" in item: properties["resumetime"] = str(item['resume']['position']) properties["totaltime"] = str(item['resume']['total']) properties['StartOffset'] = str(item['resume']['position']) # streamdetails if "streamdetails" in item: streamdetails = item["streamdetails"] audiostreams = streamdetails.get('audio', []) videostreams = streamdetails.get('video', []) subtitles = streamdetails.get('subtitle', []) if len(videostreams) > 0: stream = videostreams[0] height = stream.get("height", "") width = stream.get("width", "") if height and width: resolution = "" if width <= 720 and height <= 480: resolution = "480" elif width <= 768 and height <= 576: resolution = "576" elif width <= 960 and height <= 544: resolution = "540" elif width <= 1280 and height <= 720: resolution = "720" elif width <= 1920 and height <= 1080: resolution = "1080" elif width * height >= 6000000: resolution = "4K" properties["VideoResolution"] = resolution if stream.get("codec", ""): properties["VideoCodec"] = str(stream["codec"]) if stream.get("aspect", ""): properties["VideoAspect"] = str( round(stream["aspect"], 2)) item["streamdetails"]["video"] = stream # grab details of first audio stream if len(audiostreams) > 0: stream = audiostreams[0] properties["AudioCodec"] = stream.get('codec', '') properties["AudioChannels"] = str( stream.get('channels', '')) properties["AudioLanguage"] = stream.get('language', '') item["streamdetails"]["audio"] = stream # grab details of first subtitle if len(subtitles) > 0: properties["SubtitleLanguage"] = subtitles[0].get( 'language', '') item["streamdetails"]["subtitle"] = subtitles[0] else: item["streamdetails"] = {} item["streamdetails"]["video"] = { 'duration': item.get('duration', 0) } # additional music properties if 'album_description' in item: properties["Album_Description"] = item.get('album_description') # pvr properties if "starttime" in item: # convert utc time to local time item["starttime"] = localdate_from_utc_string( item["starttime"]) item["endtime"] = localdate_from_utc_string(item["endtime"]) # set some localized versions of the time and date as additional properties startdate, starttime = localized_date_time(item['starttime']) enddate, endtime = localized_date_time(item['endtime']) properties["StartTime"] = starttime properties["StartDate"] = startdate properties["EndTime"] = endtime properties["EndDate"] = enddate properties["Date"] = "%s %s-%s" % (startdate, starttime, endtime) properties["StartDateTime"] = "%s %s" % (startdate, starttime) properties["EndDateTime"] = "%s %s" % (enddate, endtime) # set date to startdate item["date"] = arrow.get( item["starttime"]).format("DD.MM.YYYY") if "channellogo" in item: properties["channellogo"] = item["channellogo"] properties["channelicon"] = item["channellogo"] if "episodename" in item: properties["episodename"] = item["episodename"] if "channel" in item: properties["channel"] = item["channel"] properties["channelname"] = item["channel"] item["label2"] = item["title"] # artwork art = item.get("art", {}) if item["type"] in ["episode", "season"]: if not art.get("fanart") and art.get("season.fanart"): art["fanart"] = art["season.fanart"] if not art.get("poster") and art.get("season.poster"): art["poster"] = art["season.poster"] if not art.get("landscape") and art.get("season.landscape"): art["poster"] = art["season.landscape"] if not art.get("fanart") and art.get("tvshow.fanart"): art["fanart"] = art.get("tvshow.fanart") if not art.get("poster") and art.get("tvshow.poster"): art["poster"] = art.get("tvshow.poster") if not art.get("clearlogo") and art.get("tvshow.clearlogo"): art["clearlogo"] = art.get("tvshow.clearlogo") if not art.get("banner") and art.get("tvshow.banner"): art["banner"] = art.get("tvshow.banner") if not art.get("landscape") and art.get("tvshow.landscape"): art["landscape"] = art.get("tvshow.landscape") if not art.get("fanart") and item.get('fanart'): art["fanart"] = item.get('fanart') if not art.get("thumb") and item.get('thumbnail'): art["thumb"] = get_clean_image(item.get('thumbnail')) if not art.get("thumb") and art.get('poster'): art["thumb"] = get_clean_image(art.get('poster')) if not art.get("thumb") and item.get('icon'): art["thumb"] = get_clean_image(item.get('icon')) if not item.get("thumbnail") and art.get('thumb'): item["thumbnail"] = art["thumb"] # clean art for key, value in art.iteritems(): if not isinstance(value, (str, unicode)): art[key] = "" elif value: art[key] = get_clean_image(value) item["art"] = art item["extraproperties"] = properties if "file" not in item: log_msg("Item is missing file path ! --> %s" % item["label"], xbmc.LOGWARNING) item["file"] = "" # return the result return item except Exception as exc: log_exception(__name__, exc) log_msg(item) return None @staticmethod def tvshow_watchedcounts(tvshow): '''append watched counts to tvshow details''' tvshow["extraproperties"] = { "totalseasons": str(tvshow["season"]), "totalepisodes": str(tvshow["episode"]), "watchedepisodes": str(tvshow["watchedepisodes"]), "unwatchedepisodes": str(tvshow["episode"] - tvshow["watchedepisodes"]) } return tvshow
def __init__(self): self.cache = SimpleCache() self.sources = self.openURL(SOURCES_URL).get('sources','')
IMAGE_PATH = os.path.join(ADDONPATH, 'resources', 'skins', 'Default', 'media') # Onli load master menu BASE_URL = 'menu' EPG_MENU = 'tablet=1' LIVETV_PATH = 'menu/livetv_alternative' SITE_LOGIN_PAGE = SITE_PATH + 'user/signin' SITE_LOGOUT_PAGE = SITE_PATH + 'logout' # VIEW # WIDTH = 1280 HEIGHT = 720 TIMEBAR_HEIGHT = int(float(WIDTH / 32)) TV_LOGO_WIDTH = (HEIGHT - TIMEBAR_HEIGHT) / CHANNELS_PER_PAGE CACHE = SimpleCache() # dialog = xbmcgui.Dialog() ########################################################################################## ## INFO OBJECT ## ########################################################################################## class ControlAndInfo(object): def __init__(self, control, info): self.control = control if 'title' in info: self.title = info['title'].encode('utf-8') if 'is_visible' in info: self.is_visible = info['is_visible'] if 'url' in info: self.url = info['url'] if 'pos' in info: self.pos = info['pos'] if 'sec_controls' in info: self.sec_controls = info['sec_controls']
class CBS(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheResponse = self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) if not cacheResponse: request = urllib2.Request(url) cacheResponse = urllib2.urlopen(request, timeout=TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s' % url, cacheResponse, expiration=datetime.timedelta(hours=1)) return cacheResponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self, items): for item in items: self.addDir(*item) self.addYoutube("Youtube", 'plugin://plugin.video.youtube/user/CBS/') def browseLatest(self, page=0): items = json.loads(self.openURL(LATEST_JSON % page)) if 'result' not in items: return for item in items['result']['data']: if item['status'] != "AVAILABLE": continue seasonNumber = 0 episodeNumber = 0 label = uni(item.get('title') or item['label']) plot = uni((item.get('description', '') or label)) try: aired = item['airdate_iso'].split('T')[0] except: aired = datetime.datetime.now().strftime('%Y-%m-%d') thumb = item['thumbUrl'] url = item['url'] if not url.startswith('http://'): url = (BASE_URL + '%s' % url).lstrip('/') seasonNumber = (int( filter(str.isdigit, str(item.get('season_number', seasonNumber))))) episodeNumber = (int( filter(str.isdigit, str(item.get('episode_number', episodeNumber))))) seinfo = ('S' + ('0' if seasonNumber < 10 else '') + str(seasonNumber) + 'E' + ('0' if episodeNumber < 10 else '') + str(episodeNumber)) label = '%s' % ( label) if seasonNumber + episodeNumber == 0 else '%s - %s' % ( label, seinfo) infoLabels = { "mediatype": "episode", "label": label, "title": label, "TVShowTitle": label, "plot": plot, "aired": aired, "duration": item.get('duration_raw', 0) } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addLink(label, url, 9, infoLabels, infoArt, len(items)) self.addDir('>> Next', str(page + 1), 6) def browseEpisodes(self, url): log('browseEpisodes') items = json.loads(self.openURL(url))['result']['data'] for item in items: if 'status' in item and item['status'].lower() != 'available': continue title = uni( item.get('title', '') or item.get('label', '') or item.get('episode_title', '')) vidType = item['type'] thumb = (item['thumb'].get('large', '') or item['thumb'].get('small', '') or ICON) aired = str( item['airdate_iso']).split('T')[0] #str(item['airdate']) showTitle = uni(item['series_title']) runtime = item['duration'].split(':') if len(runtime) == 3: h, m, s = runtime duration = int(h) * 3600 + int(m) * 60 + int(s) else: m, s = runtime duration = int(m) * 60 + int(s) seasonNumber = int(item.get('season_number', '0') or '0') episodeNumber = int(item.get('episode_number', '0') or '0') url = item['url'] if not url.startswith('http://'): url = (BASE_URL + '%s' % url).lstrip('/') seinfo = ('S' + ('0' if seasonNumber < 10 else '') + str(seasonNumber) + 'E' + ('0' if episodeNumber < 10 else '') + str(episodeNumber)) label = '%s - %s' % ( showTitle, title ) if seasonNumber + episodeNumber == 0 else '%s - %s - %s' % ( showTitle, seinfo, title) plot = uni(item.get('description', label)) infoLabels = { "mediatype": "episode", "label": label, "title": label, "TVShowTitle": showTitle, "plot": plot, "aired": aired, "duration": duration, "season": seasonNumber, "episode": episodeNumber } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addLink(label, url, 9, infoLabels, infoArt, len(items)) def browseCategory(self, url): log('browseCategory') items = json.loads(url) url = items['url'] thumb = items['thumb'] response = self.openURL(url).replace('\n', '').replace('\r', '').replace('\t', '') seasons = re.search( '<select class="dropdown__filter__fake">(.*?)</select>', response) if seasons: items = re.findall(r'<option value="(.*?)".+?>(.*?)</option>', seasons.group(1), re.DOTALL) if items: CONTENT_TYPE = 'tvshows' for item in items: try: seasonURL = SHOW_URL % (url, item[0]) title = item[1].strip() infoLabels = { "mediatype": "tvshow", "label": title, "title": title, "TVShowTitle": title } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addDir(title, seasonURL, 5, infoLabels, infoArt) except: continue else: seasonURL = SHOW_URL % (url, '') title = "Full Episodes" infoLabels = { "mediatype": "tvshow", "label": title, "title": title, "TVShowTitle": title } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addDir(title, seasonURL, 5, infoLabels, infoArt) def browseShows(self, url=None): log('browseShows') soup = BeautifulSoup(self.openURL(SHOWS_URL), "html.parser") shows = soup('article', {'class': 'show-browse-item'}) for idx, show in enumerate(shows): title = uni(show.get_text()).replace("\n", "") if 'previews' in title.lower() or 'premieres' in title.lower(): continue url = shows[idx].a['href'] if not url.startswith('http://'): url = (BASE_URL + url).lstrip('/') if not url.endswith('/video/'): url = '%s/video/' % url.rstrip('/') thumb = shows[idx].img['data-src'] url = json.dumps({'url': url, 'thumb': thumb}) infoLabels = { "mediatype": "episode", "label": title, "title": title, "TVShowTitle": title } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addDir(title, url, 3, infoLabels, infoArt) def playVideo(self, name, url, liz=None): log('playVideo') info = getVideoInfo(url, QUALITY, True) if info is None: return info = info.streams() url = info[0]['xbmc_url'] liz = xbmcgui.ListItem(name, path=url) if 'subtitles' in info[0]['ytdl_format']: liz.setSubtitles([ x['url'] for x in info[0]['ytdl_format']['subtitles'].get('en', '') if 'url' in x ]) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label": name, "title": name}) liz.setArt({'thumb': ICON, 'fanart': FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=liz, isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = sys.argv[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = sys.argv[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, isFolder=True)
class Installer(object): def __init__(self): self.cache = SimpleCache() if self.chkUWP(): return self.killKodi = threading.Timer(2.0, self.killME) self.lastURL = (REAL_SETTINGS.getSetting("LastURL") or self.buildMain()) self.lastPath = REAL_SETTINGS.getSetting("LastPath") self.selectDialog(self.lastURL) def chkUWP(self): isUWP = (xbmc.getCondVisibility("system.platform.uwp") or sys.platform == "win10" or re.search(r"[/\\]WindowsApps[/\\]XBMCFoundation\.Kodi_", xbmc.translatePath("special://xbmc/"))) if isUWP: return self.disable() return isUWP def disable(self): xbmcgui.Dialog().notification(ADDON_NAME, VERSION, ICON, 8000) if not xbmcgui.Dialog().yesno(ADDON_NAME, LANGUAGE(30009), LANGUAGE(30012)): return True xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "method":"Addons.SetAddonEnabled","params":{"addonid":"%s","enabled":false}, "id": 1}' % (ADDON_ID)) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30011), ICON, 4000) return True def openURL(self, url): if url is None: return log('openURL, url = ' + str(url)) try: cacheResponce = self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) if not cacheResponce: request = urllib2.Request(url) cacheResponce = urllib2.urlopen(request, timeout=TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s' % url, cacheResponce, expiration=datetime.timedelta(minutes=5)) return BeautifulSoup(cacheResponce, "html.parser") except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return None def getItems(self, soup): try: #folders items = (soup.find_all('tr')) del items[0] except: #files items = (soup.find_all('a')) return [x.get_text() for x in items if x.get_text() is not None] def buildMain(self): tmpLST = [] for idx, item in enumerate(BUILD_OPT): tmpLST.append(xbmcgui.ListItem(item.title(), BUILD_DEC[idx], ICON)) select = xbmcgui.Dialog().select(ADDON_NAME, tmpLST, preselect=-1, useDetails=True) if select < 0: return #return on cancel. return WIND_URL % (BUILD_OPT[select].lower().replace('//', '/'), PLATFORM) def buildItems(self, url): soup = self.openURL(url) if soup is None: return for item in self.getItems(soup): try: #folders if 'uwp' in item.lower(): continue #ignore UWP builds label, label2 = re.compile("(.*?)/-(.*)").match(item).groups() if label == PLATFORM: label2 = LANGUAGE(30014) % PLATFORM else: label2 = '' #Don't use time-stamp for folders yield (xbmcgui.ListItem(label.strip(), label2.strip(), ICON)) except: #files label, label2 = re.compile("(.*?)\s(.*)").match(item).groups() if label.endswith('.exe'): yield (xbmcgui.ListItem(label.strip(), label2.strip(), ICON)) def setLastPath(self, url, path): REAL_SETTINGS.setSetting("LastURL", url) REAL_SETTINGS.setSetting("LastPath", path) def okDialog(self, str1, str2='', str3='', header=ADDON_NAME): xbmcgui.Dialog().ok(header, str1, str2, str3) def selectDialog(self, url, bypass=False): log('selectDialog, url = ' + str(url)) newURL = url while not xbmc.Monitor().abortRequested(): items = list(self.buildItems(url)) if len(items) == 0: break elif len(items) == 2 and not bypass and items[0].getLabel( ).startswith('Parent directory' ) and not items[1].getLabel().startswith('.exe'): select = 1 #If one folder bypass selection. else: label = url.replace(BASE_URL, './').replace('//', '/') select = xbmcgui.Dialog().select(label, items, preselect=-1, useDetails=True) if select < 0: return #return on cancel. label = items[select].getLabel() newURL = url + items[select].getLabel() preURL = url.rsplit('/', 2)[0] + '/' if newURL.endswith('.exe'): dest = xbmc.translatePath(os.path.join(SETTINGS_LOC, label)) self.setLastPath(url, dest) return self.downloadEXE(newURL, dest) elif label.startswith('Parent directory') and "windows" in preURL: return self.selectDialog(preURL, True) elif label.startswith( 'Parent directory') and "windows" not in preURL: return self.selectDialog(self.buildMain(), False) url = newURL + '/' def fileExists(self, dest): if xbmcvfs.exists(dest): if not xbmcgui.Dialog().yesno(ADDON_NAME, LANGUAGE(30004), dest.rsplit('/', 1)[-1], nolabel=LANGUAGE(30005), yeslabel=LANGUAGE(30006)): return False elif CLEAN and xbmcvfs.exists(self.lastPath): self.deleteEXE(self.lastPath) return False def deleteEXE(self, path): count = 0 #some file systems don't release the file lock instantly. while not xbmc.Monitor().abortRequested() and count < 3: count += 1 if xbmc.Monitor().waitForAbort(1): return try: if xbmcvfs.delete(path): return except: pass def downloadEXE(self, url, dest): if self.fileExists(dest): return self.installEXE(dest) start_time = time.time() dia = xbmcgui.DialogProgress() dia.create(ADDON_NAME, LANGUAGE(30002)) try: urllib.urlretrieve( url.rstrip('/'), dest, lambda nb, bs, fs: self.pbhook(nb, bs, fs, dia, start_time)) except Exception as e: dia.close() xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) log("downloadAPK, Failed! (%s) %s" % (url, str(e)), xbmc.LOGERROR) return self.deleteEXE(dest) return self.installEXE(dest) def pbhook(self, numblocks, blocksize, filesize, dia, start_time): try: percent = min(numblocks * blocksize * 100 / filesize, 100) currently_downloaded = float(numblocks) * blocksize / (1024 * 1024) kbps_speed = numblocks * blocksize / (time.time() - start_time) if kbps_speed > 0: eta = (filesize - numblocks * blocksize) / kbps_speed else: eta = 0 kbps_speed = kbps_speed / 1024 total = float(filesize) / (1024 * 1024) mbs = '%.02f MB of %.02f MB' % (currently_downloaded, total) e = 'Speed: %.02f Kb/s ' % kbps_speed if eta < 0: eta = divmod(0, 60) else: eta = divmod(eta, 60) e += 'ETA: %02d:%02d' % eta dia.update(percent, LANGUAGE(30002), mbs, e) except Exception('Download Failed'): dia.update(100) if dia.iscanceled(): raise Exception('Download Canceled') def installEXE(self, exefile): if not xbmcvfs.exists(exefile): return xbmc.executebuiltin( 'XBMC.AlarmClock(shutdowntimer,XBMC.Quit(),0.5,true)') self.killKodi.start() subprocess.call(exefile, shell=True) def killME(self): subprocess.call('taskkill /f /im kodi.exe')
class AIRYTV(object): def __init__(self, sysARG=sys.argv): log('__init__, sysARG = %s' % (sysARG)) self.sysARG = sysARG self.cache = SimpleCache() self.channels = self.getChanneldata() def getURL( self, url, param={}, header={ 'User-agent': 'Mozilla/5.0 (Windows NT 6.2; rv:24.0) Gecko/20100101 Firefox/24.0' }, life=datetime.timedelta(minutes=15)): log('getURL, url = %s, header = %s' % (url, header)) cacheresponse = self.cache.get('%s.getURL, url = %s.%s.%s' % (ADDON_ID, url, param, header)) if not cacheresponse: try: req = requests.get(url, param, headers=header) cacheresponse = req.json() req.close() except Exception as e: log("getURL, Failed! %s" % (e), xbmc.LOGERROR) notificationDialog(LANGUAGE(30001)) return {} self.cache.set('%s.getURL, url = %s.%s.%s' % (ADDON_ID, url, param, header), json.dumps(cacheresponse), expiration=life) return cacheresponse return json.loads(cacheresponse) def buildMenu(self): log('buildMenu') AIRY_MENU = [(LANGUAGE(30011), (getLive, )), (LANGUAGE(30040), (getLiveFavs, )), (LANGUAGE(30018), (getLineups, )), (LANGUAGE(30017), (getCats, ))] for item in AIRY_MENU: self.addDir(*item) def getChanneldata(self): log('getChanneldata') collect = {} echannels = [] categories = self.getURL(BASE_API.format(tz=getTZ()), life=datetime.timedelta(hours=1)).get( 'response', {}).get('categories', []) for category in categories: genre = self.cleanString(category.get('name')) channels = (category.get('banners', []) + category.get('stream_channels', []) + category.get('channels', [])) for channel in channels: channel['category'] = genre channel['name'] = self.cleanString(channel.get('name')) if channel['name'] not in echannels: #catch dups; lazy fix echannels.append(channel['name']) collect.setdefault(genre, []).append(channel) return collect def getCategories(self): log('getCategories') return self.channels.keys() def getChannels(self): log('getChannels') categories = self.getCategories() for category in categories: channels = self.channels.get(category, []) for channel in channels: yield channel def getChannel(self, id): log('getChannel, id = %s' % (id)) channels = self.getChannels() for channel in channels: if channel.get('id') == int(id): return channel return {} def getBroadcast(self, id, epid): log('getBroadcast, id = %s, epid = %s' % (id, epid)) channel = self.getChannel(id) broadcasts = channel.get('broadcasts', []) for broadcast in broadcasts: if broadcast.get('id') == int(epid): return channel, broadcast return {}, {} def buildCategories(self, cat=None): log('buildCategories, cat = %s' % (cat)) for category in self.getCategories(): if cat and cat != category: continue self.addDir(category, uri=(getCat, category), infoArt={ 'thumb': LOGO_URL % (category), 'fanart': FANART }) def buildLineups(self, id=None, cat=None): log('buildLineups, id = %s, cat = %s' % (id, cat)) for channel in self.getChannels(): if id and channel.get('id') != int(id): continue if cat and channel.get('category') != cat: continue self.addDir('%s| %s' % (channel.get('number'), channel.get('name')), uri=(getLineup, channel.get('id')), infoArt={ 'thumb': LOGO_URL % (channel.get('name')), 'fanart': FANART }) def buildChannels(self, opt='live'): log('buildChannels, opt=%s' % (opt)) return list(self.poolList(self.buildChannel, self.getChannels(), opt)) def buildChannel(self, data): channel, opt = data log('buildChannel, channel = %s, opt=%s' % (channel.get('id'), opt)) stname = channel.get('name') stnum = channel.get('number') broadcasts = channel.get('broadcasts', []) favorite = isFavorite(stnum) if opt == 'iptv_channel': channel = { "name": stname, "stream": "plugin://%s/play/pvr/%s" % (ADDON_ID, channel.get('id')), "id": "%s.%s@%s" % (stnum, slugify(stname), slugify(ADDON_NAME)), "logo": LOGO, "preset": stnum, "group": [channel.get('category'), ADDON_NAME], "radio": False } if favorite: channel['group'].append(LANGUAGE(49012)) channel['group'] = ';'.join(channel['group']) if REAL_SETTINGS.getSettingBool( 'Build_Favorites') and not favorite: return None return channel else: return self.buildBroadcasts(channel, broadcasts, opt) def buildBroadcasts(self, channel, broadcasts, opt=''): log('buildBroadcasts, channel = %s, opt = %s' % (channel.get('id'), opt)) id = channel.get('id') hls = channel.get('hls', False) name = channel.get('name') number = channel.get('number') category = channel.get('category') favorite = isFavorite(number) programmes = {id: []} now = datetime.datetime.now().astimezone().replace(microsecond=0) for idx, broadcast in enumerate(broadcasts): """{ 'id': 3092301, 'title': 'Mankind From Space', 'parts': [{ 'duration': 2617, 'source_url': 'https://ia801605.us.archive.org/12/items/National.GeographicAlien.Earths2009.720p.AC3_201702/National.Geographic%20-%20Mankind.From.Space%20-%202015.720p.AAC.mp4', 'start_at_iso': '2021-03-18T14:35:24-04:00' }], 'description': "Trace humankind's long journey from hunter-gatherer to dominant global species. From the perspective of space, this two-hour special uses mind-boggling data and CGI to disclose the breathtaking extent of humanity's influence, revealing how we've transformed our planet and produced an interconnected world of extraordinary complexity. A trip through 12,000 years of development, the documentary shows how seemingly small flashes of innovation - innovations that touch all of us in ways unimaginable to our ancestors - have changed the course of civilization. As our global population soars, the program considers the challenges humanity will face in order to survive.", 'view_duration': 1141, 'stream_duration': 2617, 'view_start_at_iso': '2021-03-18T15:00:00-04:00', 'stream_start_at_iso': '2021-03-18T14:35:24-04:00' }""" """{ 'id': 3121442, 'title': 'Fishing Offshore', 'description': 'Fishing Offshore', 'view_duration': 1391, 'stream_duration': 2700, 'view_start_at_iso': '2021-03-24T18:00:00-04:00', 'stream_start_at_iso': '2021-03-24T17:38:11-04:00' }""" try: starttime = strpTime(broadcast['stream_start_at_iso']) except: continue offsettime = strpTime(broadcast.get('view_start_at_iso')) remaining = (broadcast.get('view_duration', 0)) duration = (broadcast.get('stream_duration', '') or remaining) stoptime = (starttime + datetime.timedelta(seconds=duration)) epid = (broadcast.get('id')) title = (broadcast.get('title') or name) plot = (broadcast.get('description') or xbmc.getLocalizedString(161)) parts = (broadcast.get('parts', [])) for part in parts: runtime = (part.get('duration', 0)) stream = (part.get('source_url')) start = strpTime(part.get('start_at_iso')) if hls: uri = (playLive, id) else: uri = (playVOD, id, epid) if opt == 'iptv_broadcasts': program = { "start": starttime.strftime(DTFORMAT), "stop": stoptime.strftime(DTFORMAT), "title": title, "description": plot, "subtitle": "", "episode": "", "genre": category, "image": FANART, "date": starttime.strftime('%Y-%m-%d'), "credits": "", "stream": "plugin://%s/play/vod/%s/%s" % (ADDON_ID, id, epid) } programmes[id].append(program) elif opt in ['live', 'favorites', 'broadcast']: chname = '%s| %s' % (number, name) label = '%s : [B] %s[/B]' % (chname, title) if opt == 'favorites' and not favorite: return None if now >= starttime and now < stoptime: if opt == 'broadcast': return broadcast return self.addLink(label, uri, infoList={ "favorite": favorite, "chnum": number, "chname": name, "mediatype": "video", "label": label, "title": label }, infoArt={ 'thumb': LOGO_URL % (name), 'fanart': FANART }) elif opt == 'lineup': if stoptime < now: continue elif now >= starttime and now < stoptime: label = '%s - [B]%s[/B]' % ( starttime.strftime('%I:%M %p').lstrip('0'), title) else: label = '%s - %s' % ( starttime.strftime('%I:%M %p').lstrip('0'), title) uri = list(uri) if hls: uri[1] = 'NEXT_SHOW' uri = tuple(uri) self.addLink(label, uri, infoList={ "favorite": favorite, "chnum": number, "chname": name, "mediatype": "video", "label": label, "title": label }, infoArt={ 'thumb': LOGO_URL % (name), 'fanart': FANART }) return programmes def cleanString(self, text): return text.replace('_', ' ') def poolList(self, method, items=None, args=None, chunk=25): log("poolList") results = [] if ENABLE_POOL: pool = ThreadPool(CORES) if args is not None: results = pool.map(method, zip(items, repeat(args))) elif items: results = pool.map(method, items) #, chunksize=chunk) pool.close() pool.join() else: if args is not None: results = [method((item, args)) for item in items] elif items: results = [method(item) for item in items] return filter(None, results) def resolveYoutube(self, url, seek=0): log('resolveYoutube, url = %s, seek = %s' % (url, seek)) """Returns Video_ID extracting from the given url of Youtube Examples of URLs: Valid: 'http://youtu.be/_lOT2p_FCvA', 'www.youtube.com/watch?v=_lOT2p_FCvA&feature=feedu', 'http://www.youtube.com/embed/_lOT2p_FCvA', 'http://www.youtube.com/v/_lOT2p_FCvA?version=3&hl=en_US', 'https://www.youtube.com/watch?v=rTHlyTphWP0&index=6&list=PLjeDyYvG6-40qawYNR4juzvSOg-ezZ2a6', 'youtube.com/watch?v=_lOT2p_FCvA', Invalid: 'youtu.be/watch?v=_lOT2p_FCvA', """ if url.startswith(('youtu', 'www')): url = 'http://%s' % url query = urllib.parse.urlparse(url) if 'youtube' in query.hostname: if query.path == '/watch': match = urllib.parse.parse_qs(query.query)['v'][0] elif query.path.startswith(('/embed/', '/v/')): match = query.path.split('/')[2] elif 'youtu.be' in query.hostname: match = query.path[1:] else: match = None if match: return 'plugin://plugin.video.tubed/?mode=play&video_id={vid}&start_offset={offset}'.format( vid=match, offset=float(seek)) return url def resolveURL(self, id, epid=None, opt='live'): log('resolveURL, id = %s, epid = %s, opt = %s' % (id, epid, opt)) lizs = [] urls = [] if opt == 'live': channel = self.getChannel(id) urls = [channel.get('source_url')] broadcast = self.buildChannel((channel, 'broadcast')) runtime = (broadcast.get('stream_duration', 0)) elif opt == 'vod': channel, broadcast = self.getBroadcast(id, epid) parts = (broadcast.get('parts', [])) runtime = (broadcast.get('stream_duration', 0)) for part in parts: urls.append(part.get('source_url')) if not urls: urls = [channel.get('source_url')] print('channel', channel, 'broadcast', broadcast) for url in urls: name = (broadcast.get('title')) liz = xbmcgui.ListItem(name) if xbmcgui.Window(10000).getProperty('PseudoTVRunning') == "True": liz.setPath(self.resolveYoutube(url)) else: #apply channel offset when not using PseudoTV liz.setPath( self.resolveYoutube(url, broadcast.get('view_duration', 0))) liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name, "duration": runtime, "plot": (broadcast.get('description', '')) }) liz.setArt({ 'thumb': ICON, 'fanart': FANART, "icon": LOGO, "logo": LOGO, "clearart": LOGO }) liz.setProperty("IsPlayable", "true") if 'm3u8' in url.lower() and inputstreamhelper.Helper( 'hls').check_inputstream(): liz.setProperty('IsInternetStream', 'true') liz.setProperty('inputstream', 'inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type', 'hls') return liz #todo playlist? return xbmcgui.ListItem() # # # self.listitems = [] # self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) # self.playlist.clear() # channel = list(filter(lambda k:k.get('_id','') == id, self.getGuidedata()))[0] # urls = channel.get('stitched',{}).get('urls',[]) # if isinstance(urls, list): urls = [url['url'] for url in urls if url['type'].lower() == 'hls'][0] # liz = xbmcgui.ListItem(channel.get('name'),path=urls) # liz.setProperty('IsPlayable','true') # liz.setProperty('IsInternetStream','true') # if opt != 'pvr': # self.browseGuide(opt='play',data=[channel]) # [self.playlist.add(urls,lz,idx) for idx,lz in enumerate(self.listitems)] # liz = self.listitems.pop(0) # liz.setPath(path=urls) # return liz def playVOD(self, id, epid): log('playVOD, id = %s, epid = %s' % (id, epid)) xbmcplugin.setResolvedUrl(ROUTER.handle, True, self.resolveURL(id, epid, 'vod')) def playLive(self, id, epid=None, opt='live'): log('playLive, id = %s, epid = %s, opt = %s' % (id, epid, opt)) #if opt == 'pvr', find epid. if id == 'NEXT_SHOW': found = False liz = xbmcgui.ListItem() notificationDialog(LANGUAGE(30029), time=4000) else: found = True liz = self.resolveURL(id, epid, opt) log('playLive, url = %s' % (liz.getPath())) xbmcplugin.setResolvedUrl(ROUTER.handle, found, liz) def addPlaylist(self, name, path='', infoList={}, infoArt={}, infoVideo={}, infoAudio={}, infoType='video'): log('addPlaylist, name = %s' % name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') liz.setProperty('IsInternetStream', 'true') if infoList: liz.setInfo(type=infoType, infoLabels=infoList) else: liz.setInfo(type=infoType, infoLabels={ "mediatype": infoType, "label": name, "title": name }) if infoArt: liz.setArt(infoArt) else: liz.setArt({ 'thumb': ICON, 'fanart': FANART, "icon": LOGO, "logo": LOGO, "clearart": LOGO }) if infoVideo: liz.addStreamInfo('video', infoVideo) if infoAudio: liz.addStreamInfo('audio', infoAudio) self.listitems.append(liz) def addLink(self, name, uri=(''), infoList={}, infoArt={}, infoVideo={}, infoAudio={}, infoType='video', total=0): log('addLink, name = %s' % name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') liz.setProperty('IsInternetStream', 'true') if infoList: liz.setInfo(type=infoType, infoLabels=infoList) else: liz.setInfo(type=infoType, infoLabels={ "mediatype": infoType, "label": name, "title": name }) if infoArt: liz.setArt(infoArt) else: liz.setArt({ 'thumb': ICON, 'fanart': FANART, "icon": LOGO, "logo": LOGO, "clearart": LOGO }) if infoVideo: liz.addStreamInfo('video', infoVideo) if infoAudio: liz.addStreamInfo('audio', infoAudio) if infoList.get('favorite', None) is not None: liz = self.addContextMenu(liz, infoList) xbmcplugin.addDirectoryItem(ROUTER.handle, ROUTER.url_for(*uri), liz, isFolder=False, totalItems=total) def addDir(self, name, uri=(''), infoList={}, infoArt={}, infoType='video'): log('addDir, name = %s' % name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList: liz.setInfo(type=infoType, infoLabels=infoList) else: liz.setInfo(type=infoType, infoLabels={ "mediatype": infoType, "label": name, "title": name }) if infoArt: liz.setArt(infoArt) else: liz.setArt({ 'thumb': ICON, 'fanart': FANART, "icon": LOGO, "logo": LOGO, "clearart": LOGO }) if infoList.get('favorite', None) is not None: liz = self.addContextMenu(liz, infoList) xbmcplugin.addDirectoryItem(ROUTER.handle, ROUTER.url_for(*uri), liz, isFolder=True) def addContextMenu(self, liz, infoList={}): log('addContextMenu') if infoList['favorite']: liz.addContextMenuItems([ (LANGUAGE(49010), 'RunScript(special://home/addons/%s/favorites.py, %s)' % (ADDON_ID, urllib.parse.quote( json.dumps({ "chnum": infoList.pop('chnum'), "chname": infoList.pop('chname'), "mode": "del" })))) ]) else: liz.addContextMenuItems([ (LANGUAGE(49009), 'RunScript(special://home/addons/%s/favorites.py, %s)' % (ADDON_ID, urllib.parse.quote( json.dumps({ "chnum": infoList.pop('chnum'), "chname": infoList.pop('chname'), "mode": "add" })))) ]) return liz def run(self): ROUTER.run() xbmcplugin.setContent(ROUTER.handle, CONTENT_TYPE) xbmcplugin.addSortMethod(ROUTER.handle, xbmcplugin.SORT_METHOD_UNSORTED) xbmcplugin.addSortMethod(ROUTER.handle, xbmcplugin.SORT_METHOD_NONE) xbmcplugin.addSortMethod(ROUTER.handle, xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.addSortMethod(ROUTER.handle, xbmcplugin.SORT_METHOD_TITLE) xbmcplugin.endOfDirectory(ROUTER.handle, cacheToDisc=DISC_CACHE)
class SCAN(object): def __init__(self): self.cache = SimpleCache() self.silent = False self.pDialog = None self.pUpdate = 0 self.matchCNT = 0 self.errorCNT = 0 self.kodiModules = {} def sendJSON(self, command, life=datetime.timedelta(seconds=15)): log('sendJSON, command = ' + (command)) cacheresponse = self.cache.get(ADDON_NAME + '.sendJSON, command = %s' % command) if not cacheresponse: cacheresponse = loadJSON(xbmc.executeJSONRPC(command)) self.cache.set(ADDON_NAME + '.sendJSON, command = %s' % command, cacheresponse, expiration=life) return cacheresponse def openURL(self, url): try: log('openURL, url = ' + str(url)) cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) if DEBUG: cacheresponse = None if not cacheresponse: headers = {'User-Agent': 'Kodi-Auditor'} req = urllib2.Request(url, None, headers) page = urllib2.urlopen(req, timeout=TIMEOUT) if page.headers.get('Content-Type').find( 'gzip') >= 0 or page.headers.get('Content-Type').find( 'application/octet-stream') >= 0: d = zlib.decompressobj(16 + zlib.MAX_WBITS) cacheresponse = d.decompress(page.read()) else: cacheresponse = page.read() self.cache.set(ADDON_NAME + '.openURL, url = %s' % url, cacheresponse, expiration=datetime.timedelta(hours=12)) return cacheresponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) notificationDialog(LANGUAGE(30001)) return '' def preliminary(self): self.silent = True self.validate() plural = 's' if self.errorCNT > 0 else '' if self.errorCNT > 0: notificationDialog(LANGUAGE(32006) % (self.errorCNT, plural), time=8000) def validate(self): log('validate') if getProperty('Running') == 'True': return setProperty('Running', 'True') self.matchCNT = 0 self.errorCNT = 0 summary = self.scanModules() setProperty('Running', 'False') if self.silent: return if yesnoDialog(LANGUAGE(32009), yes=LANGUAGE(32008), no=LANGUAGE(32007)): self.buildDetails(filterErrors(summary)) else: textViewer('\n'.join([item['label'] for item in summary]), silent=self.silent) def buildDetails(self, items): log('buildDetails') select = -1 listItems = [] for item in items: addonID = (item['myModule'].get('id', '') or item['myModule'].get('addonid', None)) if addonID is None: continue author = (item['myModule'].get('provider-name', '') or item['myModule']['author']) label = '%s v.%s by %s' % (addonID, item['myModule']['version'], author) label2 = 'Enabled: [B]%s[/B] | %s' % (str(item['myModule'].get( 'enabled', True)), item['label2']) liz = buildListItem((label, label2, addonID)) # liz.setProperty('myModule' ,dumpJSON(item['myModule'])) # liz.setProperty('kodiModule',dumpJSON(item['kodiModule'])) listItems.append(liz) while select is not None: select = selectDialog(listItems, LANGUAGE(32012), preselect=select) if select is None: return sitem = listItems[select] label = sitem.getLabel() label2 = sitem.getLabel2() addonID = sitem.getPath() pselect = selectDialog( [buildListItem((mItem, '', '')) for mItem in MENU_ITEMS], LANGUAGE(32025) % (addonID)) if pselect == 0: state = not (cleanString( label2.split('Enabled: ')[1].split(' |')[0])) if okDisable(LANGUAGE(32011) % (label), label, addonID, state): listItems.pop(select) elif pselect == 1: setWhiteList(addonID) listItems.pop(select) def scanModules(self): log('scanModules') summary = [] progCNT = 0 repository = self.buildRepo() whiteList = getWhiteList()['modules'] myModules = self.sendJSON(MOD_QUERY)['result']['addons'] pTotal = len(myModules) for idx1, myModule in enumerate(myModules): found = False error = False if myModule['addonid'] in whiteList: continue self.label = '{name} v.{version}{filler}[B]{status}[/B]'.format( name=uni(myModule['name']), version=uni(myModule['version']), filler='{filler}', status='{status}') self.pUpdate = (idx1) * 100 // pTotal self.pDialog = progressDialog(self.pUpdate, control=self.pDialog, string1=LANGUAGE(32016), silent=self.silent) found, error, kodiModule = self.findModule( myModule, self.kodiModules[repository]) log('scanModules, myModule = %s, repository = %s, found = %s' % (myModule['addonid'], repository, found)) verifed = 'True' if found and not error else 'False' self.pDialog = progressDialog(self.pUpdate, control=self.pDialog, string2=LANGUAGE(32017) % (uni(myModule['addonid'])), silent=self.silent) if found and error: self.label = self.label.format(filler=generateFiller( self.label, LANGUAGE(32004)), status=LANGUAGE(32004)) self.label2 = LANGUAGE(32004) elif found and not error: self.label = self.label.format(filler=generateFiller( self.label, LANGUAGE(32002)), status=LANGUAGE(32002)) self.label2 = LANGUAGE(32002) if not found and not error: self.label = self.label.format(filler=generateFiller( self.label, LANGUAGE(32003)), status=LANGUAGE(32003)) self.label2 = LANGUAGE(32003) summary.append({ 'found': found, 'error': error, 'label': self.label, 'label2': self.label2, 'kodiModule': (kodiModule), 'myModule': (myModule) }) summary = sortItems(summary) filler = generateFiller(LANGUAGE(32013), '') filler = filler[:(len(filler) / 2) - 1] summary.insert( 0, { 'found': False, 'error': False, 'label': '%s%s%s' % (filler, LANGUAGE(32013), filler), 'label2': '', 'kodiModule': {}, 'myModule': {} }) summary.insert( 1, { 'found': False, 'error': False, 'label': '\n', 'label2': '', 'kodiModule': {}, 'myModule': {} }) self.pDialog = progressDialog(100, control=self.pDialog, string3=LANGUAGE(32018), silent=self.silent) return summary def findModule(self, myModule, kodiModules): found = False error = False for kodiModule in kodiModules: self.pDialog = progressDialog(self.pUpdate, control=self.pDialog, string3='Checking %s ...' % (uni(kodiModule['id'])), silent=self.silent) try: if myModule['addonid'].lower() == kodiModule['id'].lower(): found = True self.matchCNT += 1 if myModule['version'] != kodiModule['version']: error = True self.errorCNT += 1 break except Exception as e: log( 'findModule, failed parse %s - %s' % (str(myModule), str(e)), xbmc.LOGERROR) if found: return found, error, kodiModule return found, error, myModule def buildRepo(self): self.pDialog = progressDialog(0, string1=LANGUAGE(32014), silent=self.silent) repository = BUILDS[self.sendJSON(VER_QUERY)['result']['version'] ['major']] log('buildRepo, repository = %s' % (repository)) self.pDialog = progressDialog(0, string2=LANGUAGE(32015) % (repository.title()), silent=self.silent) self.kodiModules[repository] = list(self.buildModules(repository)) return repository def buildModules(self, branch): log('buildModules, branch = ' + (branch)) tree = ET.fromstring(self.openURL(BASE_URL % (branch))) for elem in tree.iter(): if elem.tag == 'addon': addon = elem.attrib.copy() if elem.tag == 'extension' and elem.attrib.copy( )['point'] == 'xbmc.python.module': yield (addon) def getParams(self): try: return dict(urlparse.parse_qsl(sys.argv[2][1:])) except: return None
def __init__(self, sysARG=sys.argv): log('__init__, sysARG = %s' % (sysARG)) self.sysARG = sysARG self.cache = SimpleCache() self.channels = self.getChanneldata()
class MainModule: '''mainmodule provides the script methods for the skinhelper addon''' def __init__(self): '''Initialization and main code run''' self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.kodidb = KodiDb() self.cache = SimpleCache() self.params = self.get_params() log_msg("MainModule called with parameters: %s" % self.params) action = self.params.get("action", "") # launch module for action provided by this script try: getattr(self, action)() except AttributeError: log_exception(__name__, "No such action: %s" % action) except Exception as exc: log_exception(__name__, exc) finally: xbmc.executebuiltin("dialog.Close(busydialog)") # do cleanup self.close() def close(self): '''Cleanup Kodi Cpython instances on exit''' self.cache.close() del self.win del self.addon del self.kodidb log_msg("MainModule exited") @classmethod def get_params(self): '''extract the params from the called script path''' params = {} for arg in sys.argv[1:]: paramname = arg.split('=')[0] paramvalue = arg.replace(paramname + "=", "") paramname = paramname.lower() if paramname == "action": paramvalue = paramvalue.lower() params[paramname] = paramvalue return params def deprecated_method(self, newaddon): ''' used when one of the deprecated methods is called print warning in log and call the external script with the same parameters ''' action = self.params.get("action") log_msg("Deprecated method: %s. Please call %s directly" % (action, newaddon), xbmc.LOGWARNING) paramstring = "" for key, value in self.params.iteritems(): paramstring += ",%s=%s" % (key, value) if xbmc.getCondVisibility("System.HasAddon(%s)" % newaddon): xbmc.executebuiltin("RunAddon(%s%s)" % (newaddon, paramstring)) else: # trigger install of the addon if KODI_VERSION > 16: xbmc.executebuiltin("InstallAddon(%s)" % newaddon) else: xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon) @staticmethod def musicsearch(): '''helper to go directly to music search dialog''' xbmc.executebuiltin("ActivateWindow(Music)") xbmc.executebuiltin("SendClick(8)") def setview(self): '''sets the selected viewmode for the container''' xbmc.executebuiltin("ActivateWindow(busydialog)") content_type = get_current_content_type() if not content_type: content_type = "files" current_view = xbmc.getInfoLabel("Container.Viewmode").decode("utf-8") view_id, view_label = self.selectview(content_type, current_view) current_forced_view = xbmc.getInfoLabel("Skin.String(SkinHelper.ForcedViews.%s)" % content_type) if view_id is not None: # also store forced view if (content_type and current_forced_view and current_forced_view != "None" and xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.ForcedViews.Enabled)")): xbmc.executebuiltin("Skin.SetString(SkinHelper.ForcedViews.%s,%s)" % (content_type, view_id)) xbmc.executebuiltin("Skin.SetString(SkinHelper.ForcedViews.%s.label,%s)" % (content_type, view_label)) self.win.setProperty("SkinHelper.ForcedView", view_id) if not xbmc.getCondVisibility("Control.HasFocus(%s)" % current_forced_view): xbmc.sleep(100) xbmc.executebuiltin("Container.SetViewMode(%s)" % view_id) xbmc.executebuiltin("SetFocus(%s)" % view_id) else: self.win.clearProperty("SkinHelper.ForcedView") # set view xbmc.executebuiltin("Container.SetViewMode(%s)" % view_id) def selectview(self, content_type="other", current_view=None, display_none=False): '''reads skinfile with all views to present a dialog to choose from''' cur_view_select_id = None label = "" all_views = [] if display_none: listitem = xbmcgui.ListItem(label="None") listitem.setProperty("id", "None") all_views.append(listitem) # read the special skin views file views_file = xbmc.translatePath('special://skin/extras/views.xml').decode("utf-8") if xbmcvfs.exists(views_file): doc = parse(views_file) listing = doc.documentElement.getElementsByTagName('view') itemcount = 0 for view in listing: label = xbmc.getLocalizedString(int(view.attributes['languageid'].nodeValue)) viewid = view.attributes['value'].nodeValue mediatypes = view.attributes['type'].nodeValue.lower().split(",") if label.lower() == current_view.lower() or viewid == current_view: cur_view_select_id = itemcount if display_none: cur_view_select_id += 1 if (("all" in mediatypes or content_type.lower() in mediatypes) and (not "!" + content_type.lower() in mediatypes) and not xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.view.Disabled.%s)" % viewid)): image = "special://skin/extras/viewthumbs/%s.jpg" % viewid listitem = xbmcgui.ListItem(label=label, iconImage=image) listitem.setProperty("viewid", viewid) listitem.setProperty("icon", image) all_views.append(listitem) itemcount += 1 dialog = DialogSelect("DialogSelect.xml", "", listing=all_views, windowtitle=self.addon.getLocalizedString(32012), richlayout=True) dialog.autofocus_id = cur_view_select_id dialog.doModal() result = dialog.result del dialog if result: viewid = result.getProperty("viewid") label = result.getLabel().decode("utf-8") return (viewid, label) else: return (None, None) # pylint: disable-msg=too-many-local-variables def enableviews(self): '''show select dialog to enable/disable views''' all_views = [] views_file = xbmc.translatePath('special://skin/extras/views.xml').decode("utf-8") richlayout = self.params.get("richlayout", "") == "true" if xbmcvfs.exists(views_file): doc = parse(views_file) listing = doc.documentElement.getElementsByTagName('view') for view in listing: view_id = view.attributes['value'].nodeValue label = xbmc.getLocalizedString(int(view.attributes['languageid'].nodeValue)) desc = label + " (" + str(view_id) + ")" image = "special://skin/extras/viewthumbs/%s.jpg" % view_id listitem = xbmcgui.ListItem(label=label, label2=desc, iconImage=image) listitem.setProperty("viewid", view_id) if not xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.view.Disabled.%s)" % view_id): listitem.select(selected=True) excludefromdisable = False try: excludefromdisable = view.attributes['excludefromdisable'].nodeValue == "true" except Exception: pass if not excludefromdisable: all_views.append(listitem) dialog = DialogSelect( "DialogSelect.xml", "", listing=all_views, windowtitle=self.addon.getLocalizedString(32013), multiselect=True, richlayout=richlayout) dialog.doModal() result = dialog.result del dialog if result: for item in result: view_id = item.getProperty("viewid") if item.isSelected(): # view is enabled xbmc.executebuiltin("Skin.Reset(SkinHelper.view.Disabled.%s)" % view_id) else: # view is disabled xbmc.executebuiltin("Skin.SetBool(SkinHelper.view.Disabled.%s)" % view_id) # pylint: enable-msg=too-many-local-variables def setforcedview(self): '''helper that sets a forced view for a specific content type''' content_type = self.params.get("contenttype") if content_type: current_view = xbmc.getInfoLabel("Skin.String(SkinHelper.ForcedViews.%s)" % content_type) if not current_view: current_view = "0" view_id, view_label = self.selectview(content_type, current_view, True) if view_id or view_label: xbmc.executebuiltin("Skin.SetString(SkinHelper.ForcedViews.%s,%s)" % (content_type, view_id)) xbmc.executebuiltin("Skin.SetString(SkinHelper.ForcedViews.%s.label,%s)" % (content_type, view_label)) @staticmethod def get_youtube_listing(searchquery): '''get items from youtube plugin by query''' lib_path = u"plugin://plugin.video.youtube/kodion/search/query/?q=%s" % searchquery return KodiDb().files(lib_path) def searchyoutube(self): '''helper to search youtube for the given title''' xbmc.executebuiltin("ActivateWindow(busydialog)") title = self.params.get("title", "") window_header = self.params.get("header", "") results = [] for media in self.get_youtube_listing(title): if not media["filetype"] == "directory": label = media["label"] label2 = media["plot"] image = "" if media.get('art'): if media['art'].get('thumb'): image = (media['art']['thumb']) listitem = xbmcgui.ListItem(label=label, label2=label2, iconImage=image) listitem.setProperty("path", media["file"]) results.append(listitem) # finished lookup - display listing with results xbmc.executebuiltin("dialog.Close(busydialog)") dialog = DialogSelect("DialogSelect.xml", "", listing=results, windowtitle=window_header, multiselect=False, richlayout=True) dialog.doModal() result = dialog.result del dialog if result: if xbmc.getCondVisibility( "Window.IsActive(script-skin_helper_service-CustomInfo.xml) | " "Window.IsActive(movieinformation)"): xbmc.executebuiltin("Dialog.Close(movieinformation)") xbmc.executebuiltin("Dialog.Close(script-skin_helper_service-CustomInfo.xml)") xbmc.sleep(1000) xbmc.executebuiltin('PlayMedia("%s")' % result.getProperty("path")) del result def getcastmedia(self): '''helper to show a dialog with all media for a specific actor''' xbmc.executebuiltin("ActivateWindow(busydialog)") name = self.params.get("name", "") window_header = self.params.get("name", "") results = [] items = self.kodidb.castmedia(name) items = process_method_on_list(self.kodidb.prepare_listitem, items) for item in items: if item["file"].startswith("videodb://"): item["file"] = "ActivateWindow(Videos,%s,return)" % item["file"] else: item["file"] = 'PlayMedia("%s")' % item["file"] results.append(self.kodidb.create_listitem(item, False)) # finished lookup - display listing with results xbmc.executebuiltin("dialog.Close(busydialog)") dialog = DialogSelect("DialogSelect.xml", "", listing=results, windowtitle=window_header, richlayout=True) dialog.doModal() result = dialog.result del dialog if result: while xbmc.getCondVisibility("System.HasModalDialog"): xbmc.executebuiltin("Action(Back)") xbmc.sleep(300) xbmc.executebuiltin(result.getfilename()) del result def setfocus(self): '''helper to set focus on a list or control''' control = self.params.get("control") fallback = self.params.get("fallback") position = self.params.get("position", "0") relativeposition = self.params.get("relativeposition") if relativeposition: position = int(relativeposition) - 1 count = 0 if control: while not xbmc.getCondVisibility("Control.HasFocus(%s)" % control): if xbmc.getCondVisibility("Window.IsActive(busydialog)"): xbmc.sleep(150) continue elif count == 20 or (xbmc.getCondVisibility( "!Control.IsVisible(%s) | " "!IntegerGreaterThan(Container(%s).NumItems,0)" % (control, control))): if fallback: xbmc.executebuiltin("Control.SetFocus(%s)" % fallback) break else: xbmc.executebuiltin("Control.SetFocus(%s,%s)" % (control, position)) xbmc.sleep(50) count += 1 def setwidgetcontainer(self): '''helper that reports the current selected widget container/control''' controls = self.params.get("controls", "").split("-") if controls: xbmc.sleep(50) for i in range(10): for control in controls: if xbmc.getCondVisibility("Control.IsVisible(%s) + IntegerGreaterThan(Container(%s).NumItems,0)" % (control, control)): self.win.setProperty("SkinHelper.WidgetContainer", control) return xbmc.sleep(50) self.win.clearProperty("SkinHelper.WidgetContainer") def saveskinimage(self): '''let the user select an image and save it to addon_data for easy backup''' skinstring = self.params.get("skinstring", "") allow_multi = self.params.get("multi", "") == "true" header = self.params.get("header", "") value = SkinSettings().save_skin_image(skinstring, allow_multi, header) if value: xbmc.executebuiltin("Skin.SetString(%s,%s)" % (skinstring.encode("utf-8"), value.encode("utf-8"))) @staticmethod def checkskinsettings(): '''performs check of all default skin settings and labels''' SkinSettings().correct_skin_settings() def setskinsetting(self): '''allows the user to set a skin setting with a select dialog''' setting = self.params.get("setting", "") org_id = self.params.get("id", "") if "$" in org_id: org_id = xbmc.getInfoLabel(org_id).decode("utf-8") header = self.params.get("header", "") SkinSettings().set_skin_setting(setting=setting, window_header=header, original_id=org_id) def setskinconstant(self): '''allows the user to set a skin constant with a select dialog''' setting = self.params.get("setting", "") value = self.params.get("value", "") header = self.params.get("header", "") SkinSettings().set_skin_constant(setting, header, value) def setskinconstants(self): '''allows the skinner to set multiple skin constants''' settings = self.params.get("settings", "").split("|") values = self.params.get("values", "").split("|") SkinSettings().set_skin_constants(settings, values) def setskinshortcutsproperty(self): '''allows the user to make a setting for skinshortcuts using the special skinsettings dialogs''' setting = self.params.get("setting", "") prop = self.params.get("property", "") header = self.params.get("header", "") SkinSettings().set_skinshortcuts_property(setting, header, prop) def togglekodisetting(self): '''toggle kodi setting''' settingname = self.params.get("setting", "") cur_value = xbmc.getCondVisibility("system.getbool(%s)" % settingname) if cur_value: new_value = "false" else: new_value = "true" xbmc.executeJSONRPC( '{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"%s","value":%s}}' % (settingname, new_value)) def setkodisetting(self): '''set kodi setting''' settingname = self.params.get("setting", "") value = self.params.get("value", "") is_int = False try: valueint = int(value) is_int = True del valueint except Exception: pass if value.lower() == "true": value = 'true' elif value.lower() == "false": value = 'false' elif is_int: value = '"%s"' % value xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue",\ "params":{"setting":"%s","value":%s}}' % (settingname, value)) def playtrailer(self): '''auto play windowed trailer inside video listing''' if not xbmc.getCondVisibility("Player.HasMedia | Container.Scrolling | Container.OnNext | " "Container.OnPrevious | !IsEmpty(Window(Home).Property(traileractionbusy))"): self.win.setProperty("traileractionbusy", "traileractionbusy") widget_container = self.params.get("widgetcontainer", "") trailer_mode = self.params.get("mode", "").replace("auto_", "") allow_youtube = self.params.get("youtube", "") == "true" if not trailer_mode: trailer_mode = "windowed" if widget_container: widget_container_prefix = "Container(%s)." % widget_container else: widget_container_prefix = "" li_title = xbmc.getInfoLabel("%sListItem.Title" % widget_container_prefix).decode('utf-8') li_trailer = xbmc.getInfoLabel("%sListItem.Trailer" % widget_container_prefix).decode('utf-8') if not li_trailer and allow_youtube: youtube_result = self.get_youtube_listing("%s Trailer" % li_title) if youtube_result: li_trailer = youtube_result[0].get("file") # always wait a bit to prevent trailer start playing when we're scrolling the list xbmc.Monitor().waitForAbort(3) if li_trailer and (li_title == xbmc.getInfoLabel("%sListItem.Title" % widget_container_prefix).decode('utf-8')): if trailer_mode == "fullscreen" and li_trailer: xbmc.executebuiltin('PlayMedia("%s")' % li_trailer) else: xbmc.executebuiltin('PlayMedia("%s",1)' % li_trailer) self.win.setProperty("TrailerPlaying", trailer_mode) self.win.clearProperty("traileractionbusy") def colorpicker(self): '''legacy''' self.deprecated_method("script.skin.helper.colorpicker") def backup(self): '''legacy''' self.deprecated_method("script.skin.helper.skinbackup") def restore(self): '''legacy''' self.deprecated_method("script.skin.helper.skinbackup") def reset(self): '''legacy''' self.deprecated_method("script.skin.helper.skinbackup") def colorthemes(self): '''legacy''' self.deprecated_method("script.skin.helper.skinbackup") def createcolortheme(self): '''legacy''' self.deprecated_method("script.skin.helper.skinbackup") def restorecolortheme(self): '''legacy''' self.deprecated_method("script.skin.helper.skinbackup") def conditionalbackgrounds(self): '''legacy''' self.deprecated_method("script.skin.helper.backgrounds") def splashscreen(self): '''helper to show a user defined splashscreen in the skin''' import time splashfile = self.params.get("file", "") duration = int(self.params.get("duration", 5)) if (splashfile.lower().endswith("jpg") or splashfile.lower().endswith("gif") or splashfile.lower().endswith("png") or splashfile.lower().endswith("tiff")): # this is an image file self.win.setProperty("SkinHelper.SplashScreen", splashfile) # for images we just wait for X seconds to close the splash again start_time = time.time() while (time.time() - start_time) <= duration: xbmc.sleep(500) else: # for video or audio we have to wait for the player to finish... xbmc.Player().play(splashfile, windowed=True) xbmc.sleep(500) while xbmc.getCondVisibility("Player.HasMedia"): xbmc.sleep(150) # replace startup window with home startupwindow = xbmc.getInfoLabel("System.StartupWindow") xbmc.executebuiltin("ReplaceWindow(%s)" % startupwindow) autostart_playlist = xbmc.getInfoLabel("$ESCINFO[Skin.String(autostart_playlist)]") if autostart_playlist: xbmc.executebuiltin("PlayMedia(%s)" % autostart_playlist) def videosearch(self): '''show the special search dialog''' xbmc.executebuiltin("ActivateWindow(busydialog)") from resources.lib.searchdialog import SearchDialog search_dialog = SearchDialog("script-skin_helper_service-CustomSearch.xml", self.addon.getAddonInfo('path').decode("utf-8"), "Default", "1080i") search_dialog.doModal() del search_dialog def showinfo(self): '''shows our special videoinfo dialog''' dbid = self.params.get("dbid", "") dbtype = self.params.get("dbtype", "") from infodialog import show_infodialog show_infodialog(dbid, dbtype) def deletedir(self): '''helper to delete a directory, input can be normal filesystem path or vfs''' del_path = self.params.get("path") if del_path: ret = xbmcgui.Dialog().yesno(heading=xbmc.getLocalizedString(122), line1=u"%s[CR]%s" % (xbmc.getLocalizedString(125), del_path)) if ret: success = recursive_delete_dir(del_path) if success: xbmcgui.Dialog().ok(heading=xbmc.getLocalizedString(19179), line1=self.addon.getLocalizedString(32014)) else: xbmcgui.Dialog().ok(heading=xbmc.getLocalizedString(16205), line1=xbmc.getLocalizedString(32015)) def overlaytexture(self): '''legacy: helper to let the user choose a background overlay from a skin defined folder''' skinstring = self.params.get("skinstring", "BackgroundOverlayTexture") self.params["skinstring"] = skinstring self.params["resourceaddon"] = "resource.images.backgroundoverlays" self.params["customfolder"] = "special://skin/extras/bgoverlays/" self.params["allowmulti"] = "false" self.params["header"] = self.addon.getLocalizedString(32002) self.selectimage() def busytexture(self): '''legacy: helper which lets the user select a busy spinner from predefined spinners in the skin''' skinstring = self.params.get("skinstring", "SkinHelper.SpinnerTexture") self.params["skinstring"] = skinstring self.params["resourceaddon"] = "resource.images.busyspinners" self.params["customfolder"] = "special://skin/extras/busy_spinners/" self.params["allowmulti"] = "true" self.params["header"] = self.addon.getLocalizedString(32006) self.selectimage() def selectimage(self): '''helper which lets the user select an image or imagepath from resourceaddons or custom path''' skinsettings = SkinSettings() skinstring = self.params.get("skinstring", "") skinshortcutsprop = self.params.get("skinshortcutsproperty", "") current_value = self.params.get("currentvalue", "") resource_addon = self.params.get("resourceaddon", "") allow_multi = self.params.get("allowmulti", "false") == "true" windowheader = self.params.get("header", "") skinhelper_backgrounds = self.params.get("skinhelperbackgrounds", "false") == "true" label, value = skinsettings.select_image( skinstring, allow_multi=allow_multi, windowheader=windowheader, resource_addon=resource_addon, skinhelper_backgrounds=skinhelper_backgrounds, current_value=current_value) if label: if skinshortcutsprop: # write value to skinshortcuts prop from skinshortcuts import set_skinshortcuts_property set_skinshortcuts_property(skinshortcutsprop, value, label) else: # write the values to skin strings if value.startswith("$INFO"): # we got an dynamic image from window property skinsettings.set_skin_variable(skinstring, value) value = "$VAR[%s]" % skinstring skinstring = skinstring.encode("utf-8") label = label.encode("utf-8") xbmc.executebuiltin("Skin.SetString(%s.label,%s)" % (skinstring, label)) xbmc.executebuiltin("Skin.SetString(%s.name,%s)" % (skinstring, label)) xbmc.executebuiltin("Skin.SetString(%s,%s)" % (skinstring, value)) xbmc.executebuiltin("Skin.SetString(%s.path,%s)" % (skinstring, value)) del skinsettings def dialogok(self): '''helper to show an OK dialog with a message''' headertxt = self.params.get("header") bodytxt = self.params.get("message") if bodytxt.startswith(" "): bodytxt = bodytxt[1:] if headertxt.startswith(" "): headertxt = headertxt[1:] dialog = xbmcgui.Dialog() dialog.ok(heading=headertxt, line1=bodytxt) del dialog def dialogyesno(self): '''helper to show a YES/NO dialog with a message''' headertxt = self.params.get("header") bodytxt = self.params.get("message") yesactions = self.params.get("yesaction", "").split("|") noactions = self.params.get("noaction", "").split("|") if bodytxt.startswith(" "): bodytxt = bodytxt[1:] if headertxt.startswith(" "): headertxt = headertxt[1:] if xbmcgui.Dialog().yesno(heading=headertxt, line1=bodytxt): for action in yesactions: xbmc.executebuiltin(action.encode("utf-8")) else: for action in noactions: xbmc.executebuiltin(action.encode("utf-8")) def textviewer(self): '''helper to show a textviewer dialog with a message''' headertxt = self.params.get("header", "") bodytxt = self.params.get("message", "") if bodytxt.startswith(" "): bodytxt = bodytxt[1:] if headertxt.startswith(" "): headertxt = headertxt[1:] xbmcgui.Dialog().textviewer(headertxt, bodytxt) def fileexists(self): '''helper to let the skinner check if a file exists and write the outcome to a window prop or skinstring''' filename = self.params.get("file") skinstring = self.params.get("skinstring") windowprop = self.params.get("winprop") if xbmcvfs.exists(filename): if windowprop: self.win.setProperty(windowprop, "exists") if skinstring: xbmc.executebuiltin("Skin.SetString(%s,exists)" % skinstring) else: if windowprop: self.win.clearProperty(windowprop) if skinstring: xbmc.executebuiltin("Skin.Reset(%s)" % skinstring) def stripstring(self): '''helper to allow the skinner to strip a string and write results to a skin string''' splitchar = self.params.get("splitchar") if splitchar.upper() == "[SPACE]": splitchar = " " skinstring = self.params.get("string") if not skinstring: skinstring = self.params.get("skinstring") output = self.params.get("output") index = self.params.get("index", 0) skinstring = skinstring.split(splitchar)[int(index)] self.win.setProperty(output, skinstring) def getfilename(self, filename=""): '''helper to display a sanitized filename in the vidoeinfo dialog''' output = self.params.get("output") if not filename: filename = xbmc.getInfoLabel("ListItem.FileNameAndPath") if not filename: filename = xbmc.getInfoLabel("ListItem.FileName") if "filename=" in filename: url_params = dict(urlparse.parse_qsl(filename)) filename = url_params.get("filename") self.win.setProperty(output, filename) def getplayerfilename(self): '''helper to parse the filename from a plugin (e.g. emby) filename''' filename = xbmc.getInfoLabel("Player.FileNameAndPath") if not filename: filename = xbmc.getInfoLabel("Player.FileName") self.getfilename(filename) def getpercentage(self): '''helper to calculate the percentage of 2 numbers and write results to a skinstring''' total = int(params.get("total")) count = int(params.get("count")) roundsteps = self.params.get("roundsteps") skinstring = self.params.get("skinstring") percentage = int(round((1.0 * count / total) * 100)) if roundsteps: roundsteps = int(roundsteps) percentage = percentage + (roundsteps - percentage) % roundsteps xbmc.executebuiltin("Skin.SetString(%s,%s)" % (skinstring, percentage)) def setresourceaddon(self): '''helper to let the user choose a resource addon and set that as skin string''' from resourceaddons import setresourceaddon addontype = self.params.get("addontype", "") skinstring = self.params.get("skinstring", "") setresourceaddon(addontype, skinstring) def checkresourceaddons(self): '''allow the skinner to perform a basic check if some required resource addons are available''' from resourceaddons import checkresourceaddons addonslist = self.params.get("addonslist", []) if addonslist: addonslist = addonslist.split("|") checkresourceaddons(addonslist)
class ScreenRant(object): def __init__(self, sysARG): log('__init__, sysARG = ' + str(sysARG)) self.sysARG = sysARG self.cache = SimpleCache() def openURL(self, url): try: log('openURL, url = ' + str(url)) cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) if not cacheresponse: request = urllib2.Request(url) request.add_header( 'User-Agent', 'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)' ) cacheresponse = urllib2.urlopen(request, timeout=TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s' % url, cacheresponse, expiration=datetime.timedelta(minutes=15)) return cacheresponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self, items): for item in items: self.addDir(*item) self.addYoutube(LANGUAGE(30006), 'plugin://plugin.video.youtube/user/ScreenRant/') def browse(self, name, url): log('browse, name = ' + name) soup = BeautifulSoup(self.openURL(BASE_URL + url), "html.parser") videos = soup('div', {'class': 'w-browse-clip'}) if name == LANGUAGE(30004): idx = 1 else: idx = 0 videos = videos[idx]('article', {'class': 'browse-clip'}) for video in videos: link = BASE_URL + video.find('a').attrs['href'] try: thumb = video('div', {'class': 'responsive-img' })[0].find('source').attrs['srcset'] except: thumb = (video( 'div', {'class': 'responsive-img' })[0].find('source').attrs['data-srcset']).split('?')[0] try: label = video( 'h3', {'class': 'bc-title'})[0].find('a').attrs['title'] except: label = (video( 'div', {'class': 'info-wrapper'})[0].find('a').get_text()) try: airdate = datetime.datetime.strptime( video('div', {'class': 'bc-details'})[0].find('time').get_text(), '%b %d, %Y') except: airdate = datetime.datetime.now() airdate = airdate.strftime('%Y-%m-%d') plot = '%s - %s' % (label, airdate) try: dur = (video('a', {'class': 'bc-img-link' })[0].find('span').get_text()).split(':') if len(dur) == 3: h, m, s = dur duration = int(h) * 3600 + int(m) * 60 + int(s) else: m, s = dur duration = int(m) * 60 + int(s) except: duration = '0' infoLabels = { "mediatype": "episode", "label": label, "title": label, "duration": duration, "plot": plot, "aired": airdate } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": thumb, "logo": thumb } vidID = thumb.split('/')[9].split('-') link = VIDEO_URL % (vidID[5].split('_')[2], vidID[2]) self.addLink(label, link, 9, infoLabels, infoArt, len(videos)) next = soup('div', {'class': 'wp-pagenavi'}) if name == LANGUAGE(30004) or len(next) == 0: return current_pg = int(next[0]('span', {'class': 'current'})[0].get_text()) next_url = '/video/page/%s' % (str(current_pg + 1)) next_label = next[0]('span', {'class': 'pages'})[0].get_text() self.addDir(next_label, next_url, 1) def playVideo(self, name, url): log('playVideo') liz = xbmcgui.ListItem(name, path=url) liz.setProperty('inputstreamaddon', 'inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type', 'hls') xbmcplugin.setResolvedUrl(int(self.sysARG[1]), True, liz) def addYoutube(self, name, url): liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label": name, "title": name}) liz.setArt({'thumb': ICON, 'fanart': FANART}) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=url, listitem=liz, isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = self.sysARG[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=u, listitem=liz, totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = self.sysARG[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=u, listitem=liz, isFolder=True) def getParams(self): return dict(urlparse.parse_qsl(self.sysARG[2][1:])) def run(self): params = self.getParams() try: url = urllib.unquote_plus(params["url"]) except: url = None try: name = urllib.unquote_plus(params["name"]) except: name = None try: mode = int(params["mode"]) except: mode = None log("Mode: " + str(mode)) log("URL : " + str(url)) log("Name: " + str(name)) if mode == None: self.buildMenu(MAIN_MENU) elif mode == 1: self.browse(name, url) elif mode == 9: self.playVideo(name, url) xbmcplugin.setContent(int(self.sysARG[1]), CONTENT_TYPE) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_UNSORTED) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_NONE) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_TITLE) xbmcplugin.endOfDirectory(int(self.sysARG[1]), cacheToDisc=True)
class NBC(object): def __init__(self): log('__init__') self.cache = SimpleCache() self.ydl = YoutubeDL() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheResponce = self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) if not cacheResponce: request = urllib2.Request(url) responce = urllib2.urlopen(request, timeout=TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s' % url, responce, expiration=datetime.timedelta(hours=1)) return self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) except urllib2.URLError as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) except socket.timeout as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self, items): for item in items: self.addDir(*item) self.addYoutube("Browse Youtube", 'plugin://plugin.video.youtube/user/NBC/') def browseEpisodes(self, url=None): log('browseEpisodes') if url is None: url = VIDEO_URL + FILTER % ('type', 'Full%20Episode') items = json.loads(self.openURL(url)) if items and 'data' in items: for item in items['data']: path = item['attributes']['fullUrl'] aired = str(item['attributes']['airdate']).split('T')[0] duration = int(item['attributes']['runTime']) plot = item['attributes']['description'] title = item['attributes']['title'] showTitle = '' for show in item['attributes']['categories']: if show.startswith('Series'): showTitle = show.split('Series/')[1] break try: episodeNumber = int(item['attributes']['episodeNumber']) except: episodeNumber = 0 try: seasonNumber = int(item['attributes']['seasonNumber']) except: seasonNumber = 0 try: thumb = ICON for image in items['included']: if image['id'] == item['relationships']['image'][ 'data']['id']: thumb = BASE_URL + image['attributes']['path'] break except: thumb = ICON seinfo = ('S' + ('0' if seasonNumber < 10 else '') + str(seasonNumber) + 'E' + ('0' if episodeNumber < 10 else '') + str(episodeNumber)) label = '%s - %s' % ( showTitle, title ) if seasonNumber + episodeNumber == 0 else '%s - %s - %s' % ( showTitle, seinfo, title) infoLabels = { "mediatype": "episodes", "label": label, "title": label, "TVShowTitle": showTitle, "plot": plot, "aired": aired, "duration": duration, "season": seasonNumber, "episode": episodeNumber } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addLink(label, path, 9, infoLabels, infoArt, len(items['data'])) try: next_page = items['links']['next'] except: next_page = None if next_page: self.addDir('>> Next', next_page, 1) def browseShows(self, url=None): log('browseShows') if url is None: url = SHOWS_URL items = json.loads(self.openURL(url)) if items and 'data' in items: for item in items['data']: showTitle = item['attributes']['shortTitle'] plot = (item['attributes']['shortDescription'] or showTitle).replace('<p>', '').replace('</p>', '') path = VIDEO_URL + FILTER % ('show', item['id']) vidID = item['relationships']['aggregates']['data']['id'] try: thumb = ICON for image in items['included']: if image['id'] == item['relationships']['image'][ 'data']['id']: thumb = BASE_URL + image['attributes']['path'] break except: thumb = ICON myURL = json.dumps({"url": path, "vidID": vidID}) infoLabels = { "mediatype": "tvshows", "label": showTitle, "title": showTitle, "TVShowTitle": showTitle, "plot": plot } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addDir(showTitle, myURL, 0, infoLabels, infoArt) try: next_page = items['links']['next'] except: next_page = None if next_page: self.addDir('>> Next', next_page, 2) def resolveURL(self, url): log('resolveURL') myURL = json.loads(url) items = json.loads(self.openURL(SHOW_URL % myURL['vidID'])) if items and 'data' in items: for item in items['data']['attributes']['videoTypes']: self.browseEpisodes(myURL['url'] + FILTER % ('type', urllib2.quote(item))) def playVideo(self, name, url, liz=None): log('playVideo') self.ydl.add_default_info_extractors() with self.ydl: result = self.ydl.extract_info(url, download=False) url = result['manifest_url'] liz = xbmcgui.ListItem(name, path=url) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label": name, "title": name}) liz.setArt({'thumb': ICON, 'fanart': FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=liz, isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name, "genre": "News" }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = sys.argv[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name, "genre": "News" }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = sys.argv[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, isFolder=True)
class BackgroundWindow(xbmcgui.WindowXMLDialog): def __init__(self, *args, **kwargs): xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) if DISABLE_TRAKT: xbmcgui.Window(10000).setProperty('script.trakt.paused','true') self.playList = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) self.fileCount = 0 self.cache = SimpleCache() self.myPlayer = Player() self.myPlayer.myBackground = self def onInit(self): self.winid = xbmcgui.Window(xbmcgui.getCurrentWindowDialogId()) self.winid.setProperty('ss_time', 'okay' if REAL_SETTINGS.getSetting("Time") == 'true' else 'nope') self.myPlayer.play(self.buildPlaylist()) if saveVolume(): setVolume(int(REAL_SETTINGS.getSetting('SetVolume'))) setRepeat('all') def onAction(self, act): log('onAction') if KEYLOCK and act.getId() != ACTION_STOP: return self.onClose() def onClose(self): log('onClose') setRepeat(REAL_SETTINGS.getSetting('RepeatState').lower()) xbmcgui.Window(10000).clearProperty('script.trakt.paused') xbmcgui.Window(10000).clearProperty('%s.Running'%(ADDON_ID)) setVolume(int(xbmcgui.Window(10000).getProperty('%s.RESTORE'%ADDON_ID))) self.myPlayer.stop() self.playList.clear() self.close() def getDirectory(self, path, media='video', ignore='false', method='episode', order='ascending', end=0, start=0, filter={}): log('getDirectory, path = %s'%(path)) cacheresponse = self.cache.get('%s.getDirectory, path = %s'%(ADDON_NAME,path)) if not cacheresponse: if RANDOM_PLAY: method = 'random' json_query = ('{"jsonrpc":"2.0","method":"Files.GetDirectory","params":{"directory":"%s","media":"%s","sort":{"ignorearticle":%s,"method":"%s","order":"%s"},"limits":{"end":%s,"start":%s}},"id":1}' % (path, media, ignore, method, order, end, start)) cacheresponse = (sendJSON(json_query)) if 'result' in cacheresponse: self.cache.set('%s.getDirectory, path = %s'%(ADDON_NAME,path), json.dumps(cacheresponse), expiration=datetime.timedelta(minutes=15)) return cacheresponse else: return json.loads(cacheresponse) def buildDirectory(self, path, limit): log('buildDirectory, path = %s'%(path)) itemLST = [] dirLST = [] with busy_dialog(): response = self.getDirectory(path, end=limit).get('result',{}).get('files',[]) for idx, item in enumerate(response): if self.fileCount > limit: break file = item.get('file','') fileType = item.get('filetype','') if fileType == 'file': self.fileCount += 1 itemLST.append(file) elif fileType == 'directory': dirLST.append(file) if self.fileCount < limit: for dir in dirLST: if self.fileCount > limit: break itemLST.extend(self.buildDirectory(dir, limit)) return itemLST def buildItem(self, responce): log('buildItem') if 'result' in responce and 'filedetails' in responce['result']: key = 'filedetails' elif 'result' in responce and 'files' in responce['result']: key = 'files' else: xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) for item in responce['result'][key]: if key == 'files' and item.get('filetype','') == 'directory': continue yield responce['result'][key]['file'] def buildPlaylist(self): log('buildPlaylist') self.playList.clear() xbmc.sleep(100) if not SINGLE_FLE: playListItem = self.buildDirectory(VIDEO_PATH, VIDEO_LIMIT) elif PLAYLIST_FLE: playListItem = self.buildDirectory(VIDEO_FILE, VIDEO_LIMIT) elif not VIDEO_FILE.startswith(('plugin://','upnp://','pvr://')): playListItem = list(self.buildItem(getFileDetails(VIDEO_FILE))) else: return VIDEO_FILE for idx, playItem in enumerate(playListItem): self.playList.add(playItem, index=idx) if RANDOM_PLAY: self.playList.shuffle() else: self.playList.unshuffle() return self.playList
class FunnyOrDie(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheresponse: request = urllib2.Request(url) response = urllib2.urlopen(request, timeout = TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, response, expiration=datetime.timedelta(days=1)) return self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' @use_cache(1) def getData(self): try: soup = BeautifulSoup(self.openURL(CAT_URL), "html.parser") return json.loads(re.compile("data-react-props='(.*?)'>", re.DOTALL ).findall(str(soup('div' , {'class': 'action-bar'})))[0]) except Exception as e: log("getData Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return None def getSorts(self): log('getSorts') getData = self.getData() if getData is None: return for sort in getData['sortOptions']: yield (sort['title'], '%s/%s'%(sort['key'], sort['date_filter']), 0) def getCategories(self, url): log('getCategories') getData = self.getData() if getData is None: return for cat in getData['categoryOptions']: yield (cat['title'], json.dumps({"url":'%s/%s/%s'%(cat['category'], cat['grade'], url), "page":1}), 1) def buildMenu(self, items, yt=False): for item in items: self.addDir(*item) if yt: self.addYoutube("Browse Youtube" , 'plugin://plugin.video.youtube/user/funnyordie/') def browseVideos(self, name, myurl): log('browse, ' + name) myurl = json.loads(myurl) url = myurl['url'] page = myurl['page'] soup = BeautifulSoup(self.openURL(VID_URL%(url,page)), "html.parser") videos = soup('div' , {'class': 'media-preview-crop'}) if len(videos) == 0: return for video in videos: vidurl= BASE_URL + video.find_all('a')[0].attrs['href'] title = video.find_all('a')[0].attrs['title'] thumb = (video.find_all('a')[0]('img' , {'class': 'media-preview-thumbnail'})[0].attrs['data-src'] or ICON) duration = video.find_all('a')[0]('span' , {'class': 'media-video-duration'})[0].get_text() try: runtime = duration.split(':') if len(runtime) == 3: h, m, s = runtime duration = int(h) * 3600 + int(m) * 60 + int(s) else: m, s = runtime duration = int(m) * 60 + int(s) except: duration = duration infoLabels = {"mediatype":"episode","label":title ,"title":title,"duration":duration,"plot":title} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} CONTENT_TYPE = 'episodes' self.addLink(title, vidurl, 9, infoLabels, infoArt, len(videos)) myurl = json.dumps({"url":url,"page":page + 1}) self.addDir('>> Next', myurl, 1) def playVideo(self, name, url, liz=None): log('playVideo') info = getVideoInfo(url,QUALITY,True) if info is None: return info = info.streams() url = info[0]['xbmc_url'] liz = xbmcgui.ListItem(name, path=url) if 'subtitles' in info[0]['ytdl_format']: liz.setSubtitles([x['url'] for x in info[0]['ytdl_format']['subtitles'].get('en','') if 'url' in x]) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label":name,"title":name} ) liz.setArt({'thumb':ICON,'fanart':FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
class StudioLogos(): """Helper class for studio logo images""" def __init__(self, simplecache=None): """Initialize - optionaly provide simplecache object""" if not simplecache: from simplecache import SimpleCache self.cache = SimpleCache() else: self.cache = simplecache @use_cache(14) def get_studio_logo(self, studios, lookup_path): """get the studio logo for the given studio string(s)""" if not studios: return {} result = {} if not isinstance(studios, list): studios = studios.split(" / ") result["Studio"] = studios[0] result['Studios'] = "[CR]".join(studios) result['StudioLogo'] = self.match_studio_logo( studios, self.get_studio_logos(lookup_path)) return result def get_studio_logos(self, lookup_path): """get all studio logos""" cache_str = u"SkinHelper.StudioLogos" cache = self.cache.get(cache_str, checksum=lookup_path) if cache: return cache # no cache - start lookup all_logos = {} if lookup_path.startswith("resource://"): all_logos = self.get_resource_addon_files(lookup_path) else: if not (lookup_path.endswith("/") or lookup_path.endswith("\\")): lookup_path = lookup_path + os.sep all_logos = self.list_files_in_path(lookup_path) # save in cache and return self.cache.set(cache_str, all_logos, expiration=timedelta(days=14), checksum=lookup_path) return all_logos @staticmethod def match_studio_logo(studios, studiologos): """try to find a matching studio logo""" studiologo = "" for studio in studios: if studiologo: break studio = studio.lower() # find logo normal if studio in studiologos: studiologo = studiologos[studio] if not studiologo: # find logo by substituting characters if " (" in studio: studio = studio.split(" (")[0] if studio in studiologos: studiologo = studiologos[studio] if not studiologo: # find logo by substituting characters for pvr channels if " HD" in studio: studio = studio.replace(" HD", "") elif " " in studio: studio = studio.replace(" ", "") if studio in studiologos: studiologo = studiologos[studio] return studiologo @use_cache(90) def get_resource_addon_files(self, resourcepath): """get listing of all files (eg studio logos) inside a resource image addonName read data from our permanent cache file to prevent that we have to query the resource addon""" return self.list_files_in_path(resourcepath) @staticmethod def list_files_in_path(filespath): """used for easy matching of studio logos""" all_files = {} dirs, files = xbmcvfs.listdir(filespath) if "/" in filespath: sep = "/" else: sep = "\\" for file in files: file = try_decode(file) name = file.split(".png")[0].lower() all_files[name] = filespath + file for directory in dirs: directory = try_decode(directory) files = xbmcvfs.listdir(os.path.join(filespath, directory) + sep)[1] for file in files: file = try_decode(file) name = directory + "/" + file.split(".png")[0].lower() all_files[name] = filespath + directory + sep + file # return the list return all_files
class EarthCam(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url, force=False): log('openURL, url = ' + str(url)) try: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheresponse or force: request = urllib2.Request(url) request.add_header('User-Agent','Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)') response = urllib2.urlopen(request, timeout = TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, response, expiration=datetime.timedelta(days=1)) return self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self, items): for item in items: self.addDir(*item) self.addYoutube(LANGUAGE(30005), 'plugin://plugin.video.youtube/user/earthcam/') def browse(self, name, url): log('browse, ' + name) soup = BeautifulSoup(self.openURL(url), "html.parser") if len(soup) == 0: return networks = soup('a', {'class': 'locationLink'}) for region in networks: title = region.get_text() url = NET_URL + region.attrs['href'] thumb = LOGO_URL%(urllib.quote(title)) infoLabels = {"mediatype":"files","label":title ,"title":title} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addDir(title,url,2,infoLabels,infoArt) def browseVideos(self, name, url): log('browseVideos, ' + name) soup = BeautifulSoup(self.openURL(url), "html.parser") if len(soup) == 0: return featured = soup('div', {'class': 'col-lg-3 col-md-4 col-sm-5 col-xs-12'}) for cam in featured: feat = cam('a', {'class': 'listImg'}) url = cam('a', {'class': 'featuredTitleLink'})[0].attrs['href'] if url.endswith('php'): continue thumb = feat[0].find('img').attrs['src'] title = feat[0].find('img').attrs['title'] infoLabels = {"mediatype":"files","label":title ,"title":title} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addDir(title,url,8,infoLabels,infoArt) def resolveURL(self, name, url): log('resolveURL, url = ' + str(url)) try: response = self.openURL(url) results = json.loads(re.compile("var json_base = (.*?);").findall(response)[0], strict=False) pageids = (json.loads(re.compile("js_cam_list = (.*?);").findall(response)[0], strict=False) or [url.split('?cam=')[1]]) except: return for id in pageids: try: results = results["cam"][id] except: return thumb = results["thumbnail_512"] ofset = results.get("timezone_offset","0") plot = (results["description"] or results["title"]) infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} if results["liveon"] == "true": label = '%s - %s, %s Live (HLS)'%(results["camtext"], name,results["country"]) infoLabels = {"mediatype":"episode","label":label,"title":label,"plot":plot} liveurl = ('http:%s%s'%(results["html5_streamingdomain"],results["html5_streampath"])) self.addLink(label, liveurl, 9, infoLabels, infoArt, len(pageids)) # label = '%s,%s - Live (FLV)'%(results["long_title"],results["country"]) # infoLabels = {"mediatype":"episode","label":label ,"title":label,"plot":plot} # liveurl = ('%s%s'%(results["streamingdomain"],results["livestreamingpath"])) # self.addLink(label, liveurl, 9, infoLabels, infoArt, len(pageids)) elif results["timelapseon"] == "true": label = '%s - %s, %s Timelapse'%(results["camtext"], name,results["country"]) infoLabels = {"mediatype":"episode","label":label,"title":label,"plot":plot} liveurl = ('http:%s%s'%(results["timelapsedomain"],results["timelapsepath"])) self.addLink(label, liveurl, 9, infoLabels, infoArt, len(pageids)) elif results["archiveon"] == "true": label = '%s - %s, %s Archive'%(results["camtext"], name,results["country"]) infoLabels = {"mediatype":"episode","label":label,"title":label,"plot":plot} liveurl = ('http:%s%s'%(results["archivedomain"],results["archivepath"])) self.addLink(label, liveurl, 9, infoLabels, infoArt, len(pageids)) def prepareLink(self, url): log('prepareLink, url = ' + str(url)) try: if len(re.findall('http[s]?://www.youtube.com/watch', url)) > 0: return 'plugin://plugin.video.youtube/play/?video_id=%s'%(url.split('/watch?v=')[1]) elif url.lower().endswith(".m3u8"): return url.replace('playlist', re.search(r'^([^#].+)\.m3u8$', self.openURL(url, True), re.MULTILINE).group(1)) except: return None return url def playVideo(self, name, url): log('playVideo') url = self.prepareLink(url) if url is None: return liz = xbmcgui.ListItem(name, path=url) # if url.endswith(".m3u8"): # liz.setProperty('inputstreamaddon','inputstream.adaptive') # liz.setProperty('inputstream.adaptive.manifest_type','hls') xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label":name,"title":name} ) liz.setArt({'thumb':ICON,'fanart':FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
class CBR(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): try: log('openURL, url = ' + str(url)) cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheresponse: request = urllib2.Request(url) request.add_header('User-Agent','Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)') cacheresponse = urllib2.urlopen(request, timeout=TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, cacheresponse, expiration=datetime.timedelta(minutes=15)) return cacheresponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self, items): for item in items: self.addDir(*item) self.addYoutube(LANGUAGE(30006), 'plugin://plugin.video.youtube/channel/UCuCk_7b2_4uSr6y5hFmjuMQ/') def browse(self, url): log('browse, url = ' + str(url)) soup = BeautifulSoup(self.openURL(url), "html.parser") videos = soup('div', {'class': 'thumb-wrap'}) videos.extend(soup('article', {'class': 'thumb-wrap'})) for video in videos: link = video('div', {'class': 'img-wrapper'})[0].find('a').attrs['href'] thumb = video('div', {'class': 'responsiveImg'})[0].find('source').attrs['srcset'] try: label = video('strong', {'class': 'title'})[0].find('a').attrs['title'] except: label = (video('div', {'class': 'info-wrapper'})[0].find('a').get_text()) try: airdate = datetime.datetime.strptime(video('div', {'class': 'details'})[0].find('time').get_text(), '%m.%d.%y') except: airdate = datetime.datetime.now() airdate = airdate.strftime('%Y-%m-%d') plot = '%s - %s'%(label,airdate) try: dur = (video('div', {'class': 'img-wrapper'})[0].find('span').get_text()).split(':') if len(dur) == 3: h, m, s = dur duration = int(h) * 3600 + int(m) * 60 + int(s) else: m, s = dur duration = int(m) * 60 + int(s) except: duration = '0' infoLabels = {"mediatype":"episode","label":label ,"title":label,"duration":duration,"plot":plot,"aired":airdate} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":thumb,"logo":thumb} vidID = ((thumb.split('/')[8]).split('-')[5]).split('_') link = VIDEO_URL%(vidID[2],vidID[0]) self.addLink(label, link, 9, infoLabels, infoArt, len(videos)) next = soup('a', {'class': 'nextpostslink'}) if len(next) == 0: return next_url = next[0].attrs['href'] next_label = soup('span', {'class': 'pages'})[0].get_text() self.addDir(next_label, next_url, 1) def playVideo(self, name, url): log('playVideo') liz = xbmcgui.ListItem(name, path=url) liz.setProperty('inputstreamaddon','inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type','hls') xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label":name,"title":name} ) liz.setArt({'thumb':ICON,'fanart':FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
class CC(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheresponse: request = urllib2.Request(url) response = urllib2.urlopen(request, timeout = TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, response, expiration=datetime.timedelta(days=1)) return self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) if str(e).startswith('HTTP Error 500'): return '' xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self, items): for item in items: self.addDir(*item) self.addYoutube(LANGUAGE(30006), 'plugin://plugin.video.youtube/user/comedycentral/') def browse(self, name, url): log('browse, ' + name) response = self.openURL(url) if len(response) == 0: return try: items = json.loads(re.search('var triforceManifestFeed = (.+?);\n',response).group(1)) except: items = json.loads(re.search('var triforceManifestURL = "(.+?)";',response).group(1)) try: thumb = (response.split('//meta[@property="og:image"]/@content')[0].strip() or ICON) except: thumb = ICON if not thumb.endswith(('.png','.jpg')): thumb = ICON elif thumb.startswith('//'): thumb = 'http:%s'%thumb if items and 'manifest' not in items: return for item in items['manifest']['zones']: if item in ('header', 'footer', 'ads-reporting', 'ENT_M171'): continue try: result = items['manifest']['zones'][item]['feed'] except: result = None if result is None: continue try: ent_code = result.split('/feeds/')[1].split('/')[0] except: try: ent_code = result.split('/modules/')[1].split('/')[0] except: ent_code = '' ent_code = ent_code.split('_cc')[0].split('_tosh')[0] try: jsonResponse = json.loads(self.openURL(result))['result'] except: log('browse, jsonResponse failed! ' + str(jsonResponse)) if ent_code == 'ent_m081': return self.buildEpisodes(name, url, jsonResponse, jsonResponse['episodes']) elif ent_code == 'ent_m013': return self.buildEpisodes(name, url, jsonResponse, jsonResponse['episodes']) elif ent_code in ['ent_m100','ent_m150']: for item in jsonResponse['data']['items']: if ent_code == 'ent_m100' and name == LANGUAGE(30008): self.buildShow(item) elif ent_code == 'ent_m150' and name == LANGUAGE(30004): for show in item['sortedItems']: self.buildShow(show) def buildShow(self, show): vid_url = (show.get('canonicalURL','') or show.get('url',None)) title = (show.get('title','') or show.get('shortTitle',None)) plot = (show.get('description','') or show.get('shortDescription','') or title) if vid_url is None or title is None or not vid_url.startswith(BASE_URL): return try: thumb = show['image']['url'] except: thumb = LOGO_URL%(urllib.quote(title)) infoLabels = {"mediatype":"tvshows","label":title ,"title":title,"TVShowTitle":title,"plot":plot} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addDir(title,vid_url,1,infoLabels,infoArt) def buildEpisodes(self, name, url, jsonResponse=None, videos=[], jsonKey='episodes'): log('buildEpisodes, ' + name) if jsonResponse is None: jsonResponse = json.loads(self.openURL(url))['result'] videos = jsonResponse[jsonKey] for video in videos: vid_url = (video.get('canonicalURL','') or video.get('url',None)) title = (video.get('title','') or video.get('shortTitle',None)) plot = (video.get('description','') or video.get('shortDescription','') or title) if vid_url is None or title is None: continue elif not vid_url.startswith(BASE_URL): continue try: show = video['show'].get('title',None) except: show = name try: thumb = video['images'][0]['url'] except: thumb = video['image'][0]['url'] try: season = int(video['season']['seasonNumber']) except: season = 0 try: episode = int(video['season']['episodeAiringOrder']) except: episode = 0 label = '%s - %s'%(show,title) seinfo = ('S' + ('0' if season < 10 else '') + str(season) + 'E' + ('0' if episode < 10 else '') + str(episode)) if season + episode > 0: label = '%s - %s - %s'%(show, seinfo, title) try: aired = datetime.datetime.fromtimestamp(float(video['airDate'])) except: aired = datetime.datetime.now() try: duration = video['duration'] except: duration = 0 infoLabels = {"mediatype":"episode","label":label ,"title":label,"TVShowTitle":show,"plot":plot,"aired":aired.strftime('%Y-%m-%d'),"duration":duration,"season":season,"episode":episode} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} CONTENT_TYPE = 'episodes' self.addLink(label, vid_url, 9, infoLabels, infoArt, len(videos)) try: next_page = jsonResponse['nextPageURL'] except: next_page = None if next_page: self.addDir('>> Next',next_page, 2) def playVideo(self, name, url, liz=None): log('playVideo') info = getVideoInfo(url,QUALITY,True) if info is None: return info = info.streams() if len(info) > 1: if PTVL_RUNNING: return xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30007), ICON, 4000) info = sorted(info, key=lambda x: x['idx']) plst = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) plst.clear() xbmc.sleep(200) for videos in info: vidIDX = videos['idx'] url = videos['xbmc_url'] liz = xbmcgui.ListItem(videos['title'], path=url) try: if 'subtitles' in videos['ytdl_format']: liz.setSubtitles([x['url'] for x in videos['ytdl_format']['subtitles'].get('en','') if 'url' in x]) except: pass plst.add(url, liz, vidIDX) if vidIDX == 0: xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) plst.unshuffle() else: liz = xbmcgui.ListItem(info[0]['title'], path=info[0]['xbmc_url']) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label":name,"title":name} ) liz.setArt({'thumb':ICON,'fanart':FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
class MultiChannel(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheresponse: request = urllib2.Request(url) request.add_header('User-Agent','Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)') cacheresponse = urllib2.urlopen(request, timeout = TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, cacheresponse, expiration=datetime.timedelta(hours=1)) return cacheresponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self): self.addDir(LANGUAGE(30003), BASE_VID, 1) self.addYoutube(LANGUAGE(30004), 'plugin://plugin.video.youtube/channel/UC0VOh1nD6Tlq_5gjkpSecWA/') def browse(self, url): log('browse') soup = BeautifulSoup(self.openURL(url), "html.parser") videos = soup('div', {'class': 'l-grid--item'}) for video in videos: link = video('div', {'class': 'm-card--media'})[0].find('a').attrs['href'] if not link.startswith('/video/'): continue link = BASE_URL+link label = video('div', {'class': 'm-card--media'})[0].find('a').attrs['title'] thumb = video('div', {'class': 'm-card--media'})[0].find('source').attrs['data-srcset'] try: plot = video('div', {'class': 'm-card--content'})[0].find('p').get_text() except: plot = label infoLabels = {"mediatype":"episode","label":label,"title":label,"plot":plot,"genre":'News'} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addLink(label, link, 9, infoLabels, infoArt, len(videos)) next = soup('li', {'class': 'pager-next'}) if len(next) == 0: return next_url = BASE_URL + next[0].find('a').attrs['href'] next_label = (next[0].find('a').attrs['title'] or next[0].get_text()) self.addDir(next_label, next_url, 1) def playVideo(self, name, url, liz=None): log('playVideo') info = getVideoInfo(url,QUALITY,True) if info is None: return info = info.streams() url = info[0]['xbmc_url'] liz = xbmcgui.ListItem(name, path=url) if 'subtitles' in info[0]['ytdl_format']: liz.setSubtitles([x['url'] for x in info[0]['ytdl_format']['subtitles'].get('en','') if 'url' in x]) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label":name,"title":name} ) liz.setArt({'thumb':ICON,'fanart':FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
class PluginContent: """Hidden plugin entry point providing some helper features""" params = {} win = None def __init__(self): self.cache = SimpleCache() self.kodi_db = KodiDb() self.win = xbmcgui.Window(10000) try: self.params = dict(urlparse.parse_qsl(sys.argv[2].replace("?", "").lower().decode("utf-8"))) log_msg("plugin called with parameters: %s" % self.params) self.main() except Exception as exc: log_exception(__name__, exc) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) # cleanup when done processing self.close() def close(self): """Cleanup Kodi Cpython instances""" self.cache.close() del self.win def main(self): """main action, load correct function""" action = self.params.get("action", "") if self.win.getProperty("SkinHelperShutdownRequested"): # do not proceed if kodi wants to exit log_msg("%s --> Not forfilling request: Kodi is exiting" % __name__, xbmc.LOGWARNING) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) else: try: if hasattr(self.__class__, action): # launch module for action provided by this plugin getattr(self, action)() else: # legacy (widget) path called !!! self.load_widget() except Exception as exc: log_exception(__name__, exc) def load_widget(self): """legacy entrypoint called (widgets are moved to seperate addon), start redirect...""" action = self.params.get("action", "") newaddon = "script.skin.helper.widgets" log_msg( "Deprecated method: %s. Please reassign your widgets to get rid of this message. -" "This automatic redirect will be removed in the future" % (action), xbmc.LOGWARNING, ) paramstring = "" for key, value in self.params.iteritems(): paramstring += ",%s=%s" % (key, value) if xbmc.getCondVisibility("System.HasAddon(%s)" % newaddon): # TEMP !!! for backwards compatability reasons only - to be removed in the near future!! import imp addon = xbmcaddon.Addon(newaddon) addon_path = addon.getAddonInfo("path").decode("utf-8") imp.load_source("plugin", os.path.join(addon_path, "plugin.py")) from plugin import main main.Main() del addon else: # trigger install of the addon if KODI_VERSION >= 17: xbmc.executebuiltin("InstallAddon(%s)" % newaddon) else: xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon) def playchannel(self): """play channel from widget helper""" params = {"item": {"channelid": int(self.params["channelid"])}} self.kodi_db.set_json("Player.Open", params) def playrecording(self): """retrieve the recording and play to get resume working""" recording = self.kodi_db.recording(self.params["recordingid"]) params = {"item": {"recordingid": recording["recordingid"]}} self.kodi_db.set_json("Player.Open", params) # manually seek because passing resume to the player json cmd doesn't seem to work if recording["resume"].get("position"): for i in range(50): if xbmc.getCondVisibility("Player.HasVideo"): break xbmc.sleep(50) xbmc.Player().seekTime(recording["resume"].get("position")) def launch(self): """launch any builtin action using a plugin listitem""" if "runscript" in self.params["path"]: self.params["path"] = self.params["path"].replace("?", ",") xbmc.executebuiltin(self.params["path"]) def playalbum(self): """helper to play an entire album""" xbmc.executeJSONRPC( '{ "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "albumid": %d } }, "id": 1 }' % int(self.params["albumid"]) ) def smartshortcuts(self): """called from skinshortcuts to retrieve listing of all smart shortcuts""" import skinshortcuts skinshortcuts.get_smartshortcuts(self.params.get("path", "")) @staticmethod def backgrounds(): """called from skinshortcuts to retrieve listing of all backgrounds""" import skinshortcuts skinshortcuts.get_backgrounds() def widgets(self): """called from skinshortcuts to retrieve listing of all widgetss""" import skinshortcuts skinshortcuts.get_widgets(self.params.get("path", ""), self.params.get("sublevel", "")) def resourceimages(self): """retrieve listing of specific resource addon images""" from resourceaddons import get_resourceimages addontype = self.params.get("addontype", "") for item in get_resourceimages(addontype, True): listitem = xbmcgui.ListItem(item[0], label2=item[2], path=item[1], iconImage=item[3]) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item[1], listitem=listitem, isFolder=False) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def extrafanart(self): """helper to display extrafanart in multiimage control in the skin""" fanarts = eval(self.params["fanarts"]) # process extrafanarts for item in fanarts: listitem = xbmcgui.ListItem(item, path=item) listitem.setProperty("mimetype", "image/jpeg") xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item, listitem=listitem) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def genrebackground(self): """helper to display images for a specific genre in multiimage control in the skin""" genre = self.params.get("genre").split(".")[0] arttype = self.params.get("arttype", "fanart") randomize = self.params.get("random", "false") == "true" mediatype = self.params.get("mediatype", "movies") if genre and genre != "..": filters = [{"operator": "is", "field": "genre", "value": genre}] if randomize: sort = {"method": "random", "order": "descending"} else: sort = None items = getattr(self.kodi_db, mediatype)(sort=sort, filters=filters, limits=(0, 50)) for item in items: image = get_clean_image(item["art"].get(arttype, "")) if image: image = get_clean_image(item["art"][arttype]) listitem = xbmcgui.ListItem(image, path=image) listitem.setProperty("mimetype", "image/jpeg") xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=image, listitem=listitem) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def getcastmedia(self): """helper to display get all media for a specific actor""" name = self.params.get("name") if name: all_items = self.kodi_db.castmedia(name) all_items = process_method_on_list(self.kodi_db.prepare_listitem, all_items) all_items = process_method_on_list(self.kodi_db.create_listitem, all_items) xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items, len(all_items)) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def getcast(self): """helper to get all cast for a given media item""" db_id = None all_cast = [] all_cast_names = list() cache_str = "" download_thumbs = self.params.get("downloadthumbs", "") == "true" extended_cast_action = self.params.get("castaction", "") == "extendedinfo" tmdb = Tmdb() movie = self.params.get("movie") tvshow = self.params.get("tvshow") episode = self.params.get("episode") movieset = self.params.get("movieset") try: # try to parse db_id if movieset: cache_str = "movieset.castcache-%s-%s" % (self.params["movieset"], download_thumbs) db_id = int(movieset) elif tvshow: cache_str = "tvshow.castcache-%s-%s" % (self.params["tvshow"], download_thumbs) db_id = int(tvshow) elif movie: cache_str = "movie.castcache-%s-%s" % (self.params["movie"], download_thumbs) db_id = int(movie) elif episode: cache_str = "episode.castcache-%s-%s" % (self.params["episode"], download_thumbs) db_id = int(episode) except Exception: pass cachedata = self.cache.get(cache_str) if cachedata: # get data from cache all_cast = cachedata else: # retrieve data from json api... if movie and db_id: all_cast = self.kodi_db.movie(db_id)["cast"] elif movie and not db_id: filters = [{"operator": "is", "field": "title", "value": movie}] result = self.kodi_db.movies(filters=filters) all_cast = result[0]["cast"] if result else [] elif tvshow and db_id: all_cast = self.kodi_db.tvshow(db_id)["cast"] elif tvshow and not db_id: filters = [{"operator": "is", "field": "title", "value": tvshow}] result = self.kodi_db.tvshows(filters=filters) all_cast = result[0]["cast"] if result else [] elif episode and db_id: all_cast = self.kodi_db.episode(db_id)["cast"] elif episode and not db_id: filters = [{"operator": "is", "field": "title", "value": episode}] result = self.kodi_db.episodes(filters=filters) all_cast = result[0]["cast"] if result else [] elif movieset: if not db_id: for item in self.kodi_db.moviesets(): if item["title"].lower() == movieset.lower(): db_id = item["setid"] if db_id: json_result = self.kodi_db.movieset(db_id, include_set_movies_fields=["cast"]) if "movies" in json_result: for movie in json_result["movies"]: all_cast += movie["cast"] # optional: download missing actor thumbs if all_cast and download_thumbs: for cast in all_cast: if cast.get("thumbnail"): cast["thumbnail"] = get_clean_image(cast.get("thumbnail")) if not cast.get("thumbnail"): artwork = tmdb.get_actor(cast["name"]) cast["thumbnail"] = artwork.get("thumb", "") # lookup tmdb if item is requested that is not in local db if not all_cast: tmdbdetails = {} if movie and not db_id: tmdbdetails = tmdb.search_movie(movie) elif tvshow and not db_id: tmdbdetails = tmdb.search_tvshow(tvshow) if tmdbdetails.get("cast"): all_cast = tmdbdetails["cast"] # save to cache self.cache.set(cache_str, all_cast) # process listing with the results... for cast in all_cast: if cast.get("name") not in all_cast_names: liz = xbmcgui.ListItem(label=cast.get("name"), label2=cast.get("role"), iconImage=cast.get("thumbnail")) if extended_cast_action: url = "RunScript(script.extendedinfo,info=extendedactorinfo,name=%s)" % cast.get("name") url = "plugin://script.skin.helper.service/?action=launch&path=%s" % url is_folder = False else: url = "RunScript(script.skin.helper.service,action=getcastmedia,name=%s)" % cast.get("name") url = "plugin://script.skin.helper.service/?action=launch&path=%s" % urlencode(url) is_folder = False all_cast_names.append(cast.get("name")) liz.setThumbnailImage(cast.get("thumbnail")) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=liz, isFolder=is_folder) xbmcplugin.endOfDirectory(int(sys.argv[1])) @staticmethod def alphabet(): """display an alphabet scrollbar in listings""" all_letters = [] if xbmc.getInfoLabel("Container.NumItems"): for i in range(int(xbmc.getInfoLabel("Container.NumItems"))): all_letters.append(xbmc.getInfoLabel("Listitem(%s).SortLetter" % i).upper()) start_number = "" for number in ["2", "3", "4", "5", "6", "7", "8", "9"]: if number in all_letters: start_number = number break for letter in [ start_number, "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", ]: if letter == start_number: label = "#" else: label = letter listitem = xbmcgui.ListItem(label=label) if letter not in all_letters: lipath = "noop" listitem.setProperty("NotAvailable", "true") else: lipath = "plugin://script.skin.helper.service/?action=alphabetletter&letter=%s" % letter xbmcplugin.addDirectoryItem(int(sys.argv[1]), lipath, listitem, isFolder=False) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def alphabetletter(self): """used with the alphabet scrollbar to jump to a letter""" if KODI_VERSION > 16: xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=False, listitem=xbmcgui.ListItem()) letter = self.params.get("letter", "").upper() jumpcmd = "" if letter in ["A", "B", "C", "2"]: jumpcmd = "2" elif letter in ["D", "E", "F", "3"]: jumpcmd = "3" elif letter in ["G", "H", "I", "4"]: jumpcmd = "4" elif letter in ["J", "K", "L", "5"]: jumpcmd = "5" elif letter in ["M", "N", "O", "6"]: jumpcmd = "6" elif letter in ["P", "Q", "R", "S", "7"]: jumpcmd = "7" elif letter in ["T", "U", "V", "8"]: jumpcmd = "8" elif letter in ["W", "X", "Y", "Z", "9"]: jumpcmd = "9" if jumpcmd: xbmc.executebuiltin("SetFocus(50)") for i in range(40): xbmc.executeJSONRPC( '{ "jsonrpc": "2.0", "method": "Input.ExecuteAction",\ "params": { "action": "jumpsms%s" }, "id": 1 }' % (jumpcmd) ) xbmc.sleep(50) if xbmc.getInfoLabel("ListItem.Sortletter").upper() == letter: break
class Disclose(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) if not cacheresponse: request = urllib2.Request(url) request.add_header( 'User-Agent', 'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)' ) cacheresponse = urllib2.urlopen(request, timeout=TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s' % url, cacheresponse, expiration=datetime.timedelta(hours=1)) return cacheresponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self): for item in MAIN_MENU: self.addDir(*item) self.addYoutube( LANGUAGE(30008), 'plugin://plugin.video.youtube/channel/UCA-Ls4dkRBXHMjRjeTDTdjg/') def browse(self, url): log('browse') soup = BeautifulSoup(self.openURL(url), "html.parser") videos = soup('div', {'class': 'teaser teaser--third'}) for video in videos: try: thumb = 'http:%s' % (video( 'div', {'class': 'ratio-container ratio16_9' })[0].find('img').attrs['data-src']) except: thumb = FANART items = video('div', {'class': 'teaser__caption'}) vid_url = BASE_URL + (items[0]('a', { 'class': 'article-link' })[0].attrs['href']) label = items[0]('a', {'class': 'article-link'})[0].get_text() timeago = items[0]('span', {'class': 'meta-timeago'})[0].get_text() plot = '%s - %s' % (timeago, label) try: genre = video('span', {'class': 'teaser-figure__cat'})[0].get_text() except: genre = 'Unknown' runtime = (video( 'span', {'class': 'teaser-figure__len'})[0].get_text()).split(':') if len(runtime) == 3: h, m, s = runtime duration = int(h) * 3600 + int(m) * 60 + int(s) else: m, s = runtime duration = (int(m) * 60) + int(s) infoLabels = { "mediatype": "episode", "label": label, "title": label, "duration": duration, "plot": plot } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addLink(label, vid_url, 9, infoLabels, infoArt, len(videos)) next = soup('li', {'class': 'more-container__button m-auto'}) if len(next) == 0: return next_url = BASE_URL + next[0].find('a').attrs['href'] next_label = (next[0].find('a').attrs['title'] or next[0].get_text()) self.addDir(next_label, next_url, 1) def resolveURL(self, name, url): try: data = json.loads( re.findall('"drupal-settings-json">(.+?)</script>', self.openURL(url), flags=re.DOTALL)[0])['dtv_video'] provider = data['provider'] log('resolveURL, provider = ' + provider) url = re.findall('src="(.+?)"', (data['player_code']), flags=re.DOTALL)[0].split('?')[0] if provider == 'youtube': if len(re.findall('http[s]?://www.youtube', url)) > 0: url = YTURL % (url.rsplit('/', 1)[1]) elif len(re.findall('http[s]?://youtu.be/', url)) > 0: url = YTURL % (url.split('/youtu.be/')[1]) elif provider == 'vimeo': if len(re.findall('http[s]?://vimeo.com/', url)) > 0: url = VMURL % (url.split('/vimeo.com/')[1]) else: raise Exception('resolveURL, unknown provider; data =' + json.dumps(data)) log('resolveURL, url = ' + url) return xbmcgui.ListItem(name, path=url) except Exception as e: log("resolveURL Failed! " + str(e), xbmc.LOGERROR) if isUWP(): return '' from YDStreamExtractor import getVideoInfo info = getVideoInfo(url, QUALITY, True) if info is None: return info = info.streams() url = info[0]['xbmc_url'] liz = xbmcgui.ListItem(name, path=url) try: if 'subtitles' in info[0]['ytdl_format']: liz.setSubtitles([ x['url'] for x in info[0]['ytdl_format']['subtitles'].get('en', '') if 'url' in x ]) except: pass return liz def playVideo(self, name, url): log('playVideo') xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, self.resolveURL(name, url)) def addYoutube(self, name, url): liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label": name, "title": name}) liz.setArt({'thumb': ICON, 'fanart': FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=liz, isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = sys.argv[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = sys.argv[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, isFolder=True)
class NewsOn(object): def __init__(self): log('__init__') self.cache = SimpleCache() self.stateMenu = self.getStates() def openURL(self, url): try: cacheResponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheResponse: request = urllib2.Request(url) request.add_header('Accept-encoding', 'gzip') request.add_header('User-Agent','Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)') response = urllib2.urlopen(request, timeout = TIMEOUT) log(response.headers['content-type']) log(response.headers['content-encoding']) if response.info().get('content-encoding') == 'gzip': buf = StringIO(response.read()) f = gzip.GzipFile(fileobj=buf) cacheResponse = f.read() else: cacheResponse = response response.close() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, cacheResponse, expiration=datetime.timedelta(hours=1)) if isinstance(cacheResponse, basestring): cacheResponse = json.loads(cacheResponse) return cacheResponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def mainMenu(self): log('mainMenu') for item in MENU: self.addDir(*item) def browseMenu(self, id=1): log('browseMenu, id = ' + str(id)) self.stateMenu = [tuple(s.format(id) for s in tup) for tup in self.stateMenu] for item in self.stateMenu: self.addDir(*item) def getStates(self): log('getStates') state = [] stateLST = [] data = self.openURL(BASE_API) if len(data) == 0: return [] for channel in data: try: state.append(channel['config']['state']) except: pass states = collections.Counter(state) for key, value in sorted(states.iteritems()): stateLST.append(("%s"%(key), key , '{}')) return stateLST def newsCasts(self, state): log('newsCasts, state = ' + state) urls = [] data = self.openURL(BASE_API) if len(data) == 0: return for channel in data: try: states = channel['config']['state'] except: continue if state in states: chid = channel['identifier'] title = channel['title'] icon = (channel['icon'] or ICON) for idx, stream in enumerate(channel['streams']): streamType = stream['StreamType'] if streamType == 'website': continue#random.choice(['website','roku']): #multiple urls, only add unique. url = stream['Url'] offset = stream['OffsetFromNow'] delay = url+'&delay=%d' #todo do something with delay option? if url not in urls: urls.append(url) chid = chid+'.%d'%idx if idx > 0 else chid label = "%s - %s" % (chid, title) infoLabels ={"mediatype":"episodes","label":label ,"title":label} infoArt ={"thumb":icon,"poster":icon,"fanart":FANART,"icon":icon,"logo":icon} self.addLink(title, url, 9, infoLabels, infoArt) def videoclips(self, state): log('videoclips, state = ' + state) data = self.openURL(BASE_API) if len(data) == 0: return for channel in data: try: states = channel['config']['state'] except: continue if state in states: chid = channel['identifier'] title = channel['title'] icon = (channel['icon'] or ICON) vidURL = channel['config']['localvodfeed'] if vidURL: label = "%s - %s" % (chid, title) infoLabels ={"mediatype":"video","label":label,"title":label} infoArt ={"thumb":icon,"poster":icon,"fanart":FANART,"icon":ICON,"logo":ICON} self.addDir(label, vidURL, 4, infoLabels, infoArt) def parseclips(self, url): log('parseclips, url = ' + url) feed = feedparser.parse(url) for item in feed['entries']: if item and 'summary_detail' in item: for vids in item['media_content']: title = item['title'] url = vids['url'] plot = item['summary'] aired = item.get('published','').replace(' EST','').replace(' UTC','').replace(' GMT','') try: aired = (datetime.datetime.strptime(aired, '%a, %d %b %Y %H:%M:%S')) except: aired = datetime.datetime.now() aired = aired.strftime("%Y-%m-%d") thumb = item['media_thumbnail'][0]['url'] tagLST = [] if 'tags' in item: for tag in item['tags']: tagLST.append(((tag['term']).split('/')[0]).title()) if len(tagLST) > 0: genre = (tagLST[0] or '') infoLabels ={"mediatype":"episode","label":title,"title":title,"plot":plot,"aired":aired,'genre':genre,'tags':tagLST} infoArt ={"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addLink(title, url, 8, infoLabels, infoArt) def playVideo(self, name, url, live=False): log('playVideo') liz = xbmcgui.ListItem(name, path=url) if live: liz.setProperty('inputstreamaddon','inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type','hls') xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo( type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
def __init__(self): self.cache = SimpleCache()
class CNN(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheresponse: request = urllib2.Request(url) response = urllib2.urlopen(request, timeout = TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, response, expiration=datetime.timedelta(hours=1)) return self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self): for item in MENU_ITEMS: self.addDir(item, JSON_URL%item.lower(), 1) self.addDir('Digital Shorts', SHORTS_URL, 2) self.addYoutube("Browse Youtube" , 'plugin://plugin.video.youtube/user/CNN/') def browse(self, name, url): log('browse, ' + name) response = json.loads(self.openURL(url)) items = response['videos'] for item in items: try: runtime = item['duration'].split(':') if len(runtime) == 3: h, m, s = runtime duration = int(h) * 3600 + int(m) * 60 + int(s) else: m, s = runtime duration = int(m) * 60 + int(s) except: duration = item['duration'] label = item['headline'] thumb = 'http:%s'%item['fullsize_url'] infoLabels = {"mediatype":"episode","label":label ,"title":label,"duration":duration,"plot":item['description'],"genre":"News"} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addLink(label, BASE_URL + item['clickback_url'], 9, infoLabels, infoArt, len(items)) def browseShorts(self, name, url): log('browseShorts, ' + name) soup = BeautifulSoup(self.openURL(url), "html.parser") videos = soup('article', {'class': 'cd'}) for video in videos: vid_url = video('h3', {'class': 'cd__headline'})[0].find('a')['href'] if not vid_url.startswith('http'): vid_url = BASE_URL + vid_url if vid_url.startswith('http://cnn.it'): continue if not '/video' in vid_url: continue thumb = (video('div', {'class': 'cd__wrapper'})[0]) try: thumb = 'http:' + (json.loads((re.search('"large":(.*?)"},', str(thumb)).group(1) + '"}'))['uri']) except: thumb = ICON results = video('div', {'class': 'cd__content'})[0] try: title = results('div', {'class': 'cd__kicker'})[0].get_text() except: title = None subtitle = results('span', {'class': 'cd__headline-text'})[0].get_text() label = '%s - %s'%(title, subtitle) if title is not None else subtitle try: plot = results('div', {'class': 'cd__description'})[0].get_text() except: plot = subtitle duration = 0 infoLabels = {"mediatype":"episode","label":label ,"title":label,"duration":duration,"plot":plot} infoArt = {"thumb":thumb,"poster":thumb,"fanart":FANART,"icon":ICON,"logo":ICON} self.addLink(label, vid_url, 9, infoLabels, infoArt, len(videos)) def playVideo(self, name, url, liz=None): log('playVideo') info = getVideoInfo(url,QUALITY,True) if info is None: return info = info.streams() url = info[0]['xbmc_url'] liz = xbmcgui.ListItem(name, path=url) if 'subtitles' in info[0]['ytdl_format']: liz.setSubtitles([x['url'] for x in info[0]['ytdl_format']['subtitles'].get('en','') if 'url' in x]) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label":name,"title":name} ) liz.setArt({'thumb':ICON,'fanart':FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
class TUFFTV(object): def __init__(self): log('__init__') self.cache = SimpleCache() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url) if not cacheresponse: request = urllib2.Request(url) cacheresponse = urllib2.urlopen(request, timeout = TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, cacheresponse, expiration=datetime.timedelta(minutes=5)) return cacheresponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self): self.addLink(LANGUAGE(30003)%self.buildGuide(live=True),'',0) self.addDir( LANGUAGE(30004)%REGION,'',1) self.addYoutube(LANGUAGE(30005), 'plugin://plugin.video.youtube/channel/UCXEIxLGYxu5pB61GHnaQ_hA/') def buildLive(self): log('buildLive') return 'http:'+re.findall('"file": "(.+?)"',self.openURL(FEED_URL),re.DOTALL)[0] def buildGuide(self, live=False): log('buildGuide, live = ' + str(live)) idx = 0 AMPM = 'AM' url = self.buildLive() now = datetime.datetime.now() tnow = datetime.datetime.strptime((datetime.datetime.time(now)).strftime('%I:%M %p'), '%I:%M %p') dayofweek = {'Monday':1,'Tuesday':2,'Wednesday':3,'Thursday':4,'Friday':5,'Saturday':6,'Sunday':7}[now.strftime('%A')] soup = BeautifulSoup(self.openURL(REGION_URL), "html.parser") items = soup('table' , {'class': 'schedule-table'})[0].find_all('tr') for item in items: item = item.find_all('td') try: stime = int((item[0].get_text()).split(':')[0]) if idx > 6 and stime == 12: AMPM = 'PM' starttime = '%s %s'%(item[0].get_text(),AMPM) title = item[dayofweek].get_text().replace('[block]2','').replace('[block]4','') if title == '[/block]': continue label = '%s %s'%(starttime,title) startDate = (datetime.datetime.strptime(starttime, '%I:%M %p')) endDate1 = (startDate + datetime.timedelta(minutes=30)) endDate2 = (startDate + datetime.timedelta(minutes=60)) idx += 1 if live and (tnow >= startDate and (tnow <= endDate1 or tnow <= endDate2)): return label elif live: continue airdate = now.strftime('%Y-%m-%d') plot = title infoLabels = {"mediatype":"episode","label":label,"title":label,"plot":plot,"aired":airdate,"studio":"Tuff.TV"} infoArt = {"thumb":ICON,"poster":ICON,"fanart":FANART,"icon":ICON,"logo":ICON} self.addLink(label,url,9,infoLabels,infoArt) except: pass def playLive(self, name): log('playLive') self.playVideo(name, self.buildLive()) def playVideo(self, name, url): log('playVideo') liz = xbmcgui.ListItem(name, path=url) if 'm3u8' in url: liz.setProperty('inputstreamaddon','inputstream.adaptive') liz.setProperty('inputstream.adaptive.manifest_type','hls') xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addYoutube(self, name, url): liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label":name,"title":name} ) liz.setArt({'thumb':ICON,'fanart':FANART}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
class Disclose(object): def __init__(self, sysARG): log('__init__, sysARG = ' + str(sysARG)) self.sysARG = sysARG self.cache = SimpleCache() def openURL(self, url): log('openURL, url = ' + str(url)) try: cacheresponse = self.cache.get(ADDON_NAME + '.openURL, url = %s' % url) if not cacheresponse: request = urllib2.Request(url) request.add_header( 'User-Agent', 'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)' ) cacheresponse = urllib2.urlopen(request, timeout=TIMEOUT).read() self.cache.set(ADDON_NAME + '.openURL, url = %s' % url, cacheresponse, expiration=datetime.timedelta(hours=12)) return cacheresponse except Exception as e: log("openURL Failed! " + str(e), xbmc.LOGERROR) xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000) return '' def buildMenu(self): for item in MAIN_MENU: self.addDir(*item) self.addYoutube( LANGUAGE(30008), 'plugin://plugin.video.youtube/channel/UCA-Ls4dkRBXHMjRjeTDTdjg/') def browse(self, url): soup = BeautifulSoup(self.openURL(url), "html.parser") videos = soup('div', {'class': 'grid-item'}) for video in videos: items = video('div', {'class': 'teaser--masonry'}) try: thumb = 'http:%s' % (video( 'div', {'class': 'ratio-container ratio16_9' })[0].find('img').attrs['data-src']) except: thumb = FANART info = items[0]('a', {'class': 'article-link'}) vid_url = BASE_URL + (info[0].attrs['href']) label = items[0]('h3', {'class': 'teaser__title'})[0].get_text() timeago = items[0]('span', {'class': 'meta-timeago'})[0].get_text() plot = label #'%s - %s'%(timeago, label) try: genre = video('span', {'class': 'teaser-figure__cat'})[0].get_text() except: genre = '' try: aired = (datetime.datetime.strptime(timeago, '%b %d %Y')) except: aired = datetime.datetime.now() aired = aired.strftime("%Y-%m-%d") try: runtime = (info[0].get_text()).split(':') if len(runtime) == 3: h, m, s = runtime duration = int(h) * 3600 + int(m) * 60 + int(s) else: m, s = runtime duration = (int(m) * 60) + int(s) except: duration = 0 infoLabels = { "mediatype": "episode", "label": label, "title": label, "duration": duration, "plot": plot, "genre": genre, "aired": aired } infoArt = { "thumb": thumb, "poster": thumb, "fanart": FANART, "icon": ICON, "logo": ICON } self.addLink(label, vid_url, 9, infoLabels, infoArt, len(videos)) next = soup('li', {'class': 'more-container__button m-auto'}) if len(next) == 0: return next_url = BASE_URL.rstrip('/') + next[0].find('a').attrs['href'] next_label = (next[0].find('a').attrs['title'] or next[0].get_text()) self.addDir(next_label, next_url, 1) def resolveURL(self, name, url): log('resolveURL, url = %s' % url) soup = BeautifulSoup(self.openURL(url), "html.parser") vid_url = url for element in soup('iframe'): video = element.get('data-src', '') if video: vid_url = video break if vid_url == url: for element in soup('embed'): video = element.get('data-src', '') if video: vid_url = video break print vid_url if 'youtube' in vid_url: yt_id = re.search('embed\/([-\w]+)', vid_url).group(1) if not yt_id: yt_id = re.search('youtube.com\/watch\?v=([-\w]+)', vid_url).group(1) elif not yt_id: yt_id = re.search('youtube.be\/watch\?v=([-\w]+)', vid_url).group(1) if yt_id: vid_url = YTURL % (yt_id) if vid_url == url and not isUWP(): from YDStreamExtractor import getVideoInfo info = getVideoInfo(url, QUALITY, True) if info is None: return info = info.streams() url = info[0]['xbmc_url'] else: url = vid_url liz = xbmcgui.ListItem(name, path=url) try: if 'subtitles' in info[0]['ytdl_format']: liz.setSubtitles([ x['url'] for x in info[0]['ytdl_format']['subtitles'].get('en', '') if 'url' in x ]) except: pass return liz def playVideo(self, name, url): log('playVideo') xbmcplugin.setResolvedUrl(int(self.sysARG[1]), True, self.resolveURL(name, url)) def addYoutube(self, name, url): liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') liz.setInfo(type="Video", infoLabels={"label": name, "title": name}) liz.setArt({'thumb': ICON, 'fanart': FANART}) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=url, listitem=liz, isFolder=True) def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0): name = name.encode("utf-8") log('addLink, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = self.sysARG[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=u, listitem=liz, totalItems=total) def addDir(self, name, u, mode, infoList=False, infoArt=False): name = name.encode("utf-8") log('addDir, name = ' + name) liz = xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={ "mediatype": "video", "label": name, "title": name }) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb': ICON, 'fanart': FANART}) else: liz.setArt(infoArt) u = self.sysARG[0] + "?url=" + urllib.quote_plus(u) + "&mode=" + str( mode) + "&name=" + urllib.quote_plus(name) xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]), url=u, listitem=liz, isFolder=True) def getParams(self): return dict(urlparse.parse_qsl(self.sysARG[2][1:])) def run(self): params = self.getParams() try: url = urllib.unquote_plus(params["url"]) except: url = None try: name = urllib.unquote_plus(params["name"]) except: name = None try: mode = int(params["mode"]) except: mode = None log("Mode: " + str(mode)) log("URL : " + str(url)) log("Name: " + str(name)) if mode == None: self.buildMenu() elif mode == 1: self.browse(url) elif mode == 9: self.playVideo(name, url) xbmcplugin.setContent(int(self.sysARG[1]), CONTENT_TYPE) xbmcplugin.addSortMethod(int(self.sysARG[1]), xbmcplugin.SORT_METHOD_UNSORTED) xbmcplugin.endOfDirectory(int(self.sysARG[1]), cacheToDisc=True)
class MetadataUtils(object): ''' Provides all kind of mediainfo for kodi media, returned as dict with details ''' close_called = False def __init__(self): '''Initialize and load all our helpers''' self._studiologos_path = "" self.cache = SimpleCache() self.addon = xbmcaddon.Addon(ADDON_ID) self.kodidb = KodiDb() self.omdb = Omdb(self.cache) self.tmdb = Tmdb(self.cache) self.channellogos = ChannelLogos(self.kodidb) self.fanarttv = FanartTv(self.cache) self.imdb = Imdb(self.cache) self.google = GoogleImages(self.cache) self.studiologos = StudioLogos(self.cache) self.animatedart = AnimatedArt(self.cache, self.kodidb) self.thetvdb = TheTvDb() self.musicart = MusicArtwork(self) self.pvrart = PvrArtwork(self) log_msg("Initialized") def close(self): '''Cleanup instances''' self.close_called = True self.cache.close() self.addon = None del self.addon del self.kodidb del self.omdb del self.tmdb del self.channellogos del self.fanarttv del self.imdb del self.google del self.studiologos del self.animatedart del self.thetvdb del self.musicart del self.pvrart log_msg("Exited") def __del__(self): '''make sure close is called''' if not self.close_called: self.close() @use_cache(14) def get_extrafanart(self, file_path): '''helper to retrieve the extrafanart path for a kodi media item''' from helpers.extrafanart import get_extrafanart return get_extrafanart(file_path) def get_music_artwork(self, artist, album="", track="", disc="", ignore_cache=False, flush_cache=False): '''method to get music artwork for the goven artist/album/song''' return self.musicart.get_music_artwork( artist, album, track, disc, ignore_cache=ignore_cache, flush_cache=flush_cache) def music_artwork_options(self, artist, album="", track="", disc=""): '''options for music metadata for specific item''' return self.musicart.music_artwork_options(artist, album, track, disc) @use_cache(7) def get_extended_artwork(self, imdb_id="", tvdb_id="", tmdb_id="", media_type=""): '''get extended artwork for the given imdbid or tvdbid''' result = None if "movie" in media_type and tmdb_id: result = self.fanarttv.movie(tmdb_id) elif "movie" in media_type and imdb_id: result = self.fanarttv.movie(imdb_id) elif media_type in ["tvshow", "tvshows", "seasons", "episodes"]: if not tvdb_id: if imdb_id and not imdb_id.startswith("tt"): tvdb_id = imdb_id elif imdb_id: tvdb_id = self.thetvdb.get_series_by_imdb_id(imdb_id).get("tvdb_id") if tvdb_id: result = self.fanarttv.tvshow(tvdb_id) # add additional art with special path if result: result = {"art": result} for arttype in ["fanarts", "posters", "clearlogos", "banners"]: if result["art"].get(arttype): result["art"][arttype] = "plugin://script.skin.helper.service/"\ "?action=extrafanart&fanarts=%s" % quote_plus(repr(result["art"][arttype])) return result def get_tmdb_details(self, imdb_id="", tvdb_id="", title="", year="", media_type="", preftype="", manual_select=False, ignore_cache=False): '''returns details from tmdb''' result = {} if imdb_id: result = self.tmdb.get_videodetails_by_externalid( imdb_id, "imdb_id") elif tvdb_id: result = self.tmdb.get_videodetails_by_externalid( tvdb_id, "tvdb_id") elif title and media_type in ["movies", "setmovies", "movie"]: result = self.tmdb.search_movie( title, year, manual_select=manual_select) elif title and media_type in ["tvshows", "tvshow"]: result = self.tmdb.search_tvshow( title, year, manual_select=manual_select) elif title: result = self.tmdb.search_video( title, year, preftype=preftype, manual_select=manual_select) if result and result.get("status"): result["status"] = self.translate_string(result["status"]) if result and result.get("runtime"): result["runtime"] = result["runtime"] / 60 result.update(self.get_duration(result["runtime"])) return result def get_moviesetdetails(self, title, set_id): '''get a nicely formatted dict of the movieset details which we can for example set as window props''' # get details from tmdb from helpers.moviesetdetails import get_moviesetdetails return get_moviesetdetails(self, title, set_id) @use_cache(14) def get_streamdetails(self, db_id, media_type, ignore_cache=False): '''get a nicely formatted dict of the streamdetails ''' from helpers.streamdetails import get_streamdetails return get_streamdetails(self.kodidb, db_id, media_type) def get_pvr_artwork(self, title, channel="", genre="", manual_select=False, ignore_cache=False): '''get artwork and mediadetails for PVR entries''' return self.pvrart.get_pvr_artwork( title, channel, genre, manual_select=manual_select, ignore_cache=ignore_cache) def pvr_artwork_options(self, title, channel="", genre=""): '''options for pvr metadata for specific item''' return self.pvrart.pvr_artwork_options(title, channel, genre) def get_channellogo(self, channelname): '''get channellogo for the given channel name''' return self.channellogos.get_channellogo(channelname) def get_studio_logo(self, studio): '''get studio logo for the given studio''' # dont use cache at this level because of changing logospath return self.studiologos.get_studio_logo(studio, self.studiologos_path) @property def studiologos_path(self): '''path to use to lookup studio logos, must be set by the calling addon''' return self._studiologos_path @studiologos_path.setter def studiologos_path(self, value): '''path to use to lookup studio logos, must be set by the calling addon''' self._studiologos_path = value def get_animated_artwork(self, imdb_id, manual_select=False, ignore_cache=False): '''get animated artwork, perform extra check if local version still exists''' artwork = self.animatedart.get_animated_artwork( imdb_id, manual_select=manual_select, ignore_cache=ignore_cache) if not (manual_select or ignore_cache): refresh_needed = False if artwork.get("animatedposter") and not xbmcvfs.exists( artwork["animatedposter"]): refresh_needed = True if artwork.get("animatedfanart") and not xbmcvfs.exists( artwork["animatedfanart"]): refresh_needed = True return {"art": artwork} def get_omdb_info(self, imdb_id="", title="", year="", content_type=""): '''Get (kodi compatible formatted) metadata from OMDB, including Rotten tomatoes details''' title = title.split(" (")[0] # strip year appended to title result = {} if imdb_id: result = self.omdb.get_details_by_imdbid(imdb_id) elif title and content_type in ["seasons", "season", "episodes", "episode", "tvshows", "tvshow"]: result = self.omdb.get_details_by_title(title, "", "tvshows") elif title and year: result = self.omdb.get_details_by_title(title, year, content_type) if result and result.get("status"): result["status"] = self.translate_string(result["status"]) if result and result.get("runtime"): result["runtime"] = result["runtime"] / 60 result.update(self.get_duration(result["runtime"])) return result def get_top250_rating(self, imdb_id): '''get the position in the IMDB top250 for the given IMDB ID''' return self.imdb.get_top250_rating(imdb_id) @use_cache(14) def get_duration(self, duration): '''helper to get a formatted duration''' if isinstance(duration, (str, unicode)) and ":" in duration: dur_lst = duration.split(":") return { "Duration": "%s:%s" % (dur_lst[0], dur_lst[1]), "Duration.Hours": dur_lst[0], "Duration.Minutes": dur_lst[1], "Runtime": str((int(dur_lst[0]) * 60) + int(dur_lst[1])), } else: return _get_duration(duration) @use_cache(2) def get_tvdb_details(self, imdbid="", tvdbid=""): '''get metadata from tvdb by providing a tvdbid or tmdbid''' result = {} self.thetvdb.days_ahead = 365 if not tvdbid and imdbid and not imdbid.startswith("tt"): # assume imdbid is actually a tvdbid... tvdbid = imdbid if tvdbid: result = self.thetvdb.get_series(tvdbid) elif imdbid: result = self.thetvdb.get_series_by_imdb_id(imdbid) if result: if result["status"] == "Continuing": # include next episode info result["nextepisode"] = self.thetvdb.get_nextaired_episode(result["tvdb_id"]) # include last episode info result["lastepisode"] = self.thetvdb.get_last_episode_for_series(result["tvdb_id"]) result["status"] = self.translate_string(result["status"]) if result.get("runtime"): result["runtime"] = result["runtime"] / 60 result.update(_get_duration(result["runtime"])) return result @use_cache(90) def get_imdbtvdb_id(self, title, content_type, year="", imdbid="", tvshowtitle=""): '''try to figure out the imdbnumber and/or tvdbid''' tvdbid = "" if content_type in ["seasons", "episodes"] or tvshowtitle: title = tvshowtitle content_type = "tvshows" if imdbid and not imdbid.startswith("tt"): if content_type in ["tvshows", "seasons", "episodes"]: tvdbid = imdbid imdbid = "" if not imdbid and year: imdbid = self.get_omdb_info( "", title, year, content_type).get("imdbnumber", "") if not imdbid: # repeat without year imdbid = self.get_omdb_info("", title, "", content_type).get("imdbnumber", "") # return results return (imdbid, tvdbid) def translate_string(self, _str): '''translate the received english string from the various sources like tvdb, tmbd etc''' translation = _str _str = _str.lower() if "continuing" in _str: translation = self.addon.getLocalizedString(32037) elif "ended" in _str: translation = self.addon.getLocalizedString(32038) elif "released" in _str: translation = self.addon.getLocalizedString(32040) return translation
def __init__(self, sysARG): log('__init__, sysARG = ' + str(sysARG)) self.sysARG = sysARG self.cache = SimpleCache()