def rootDir(): url = common.build_url({'action': 'watchlist', 'list': 'Film'}) nav.addDir('Filme', url) url = common.build_url({'action': 'watchlist', 'list': 'Episode'}) nav.addDir('Episoden', url) url = common.build_url({'action': 'watchlist', 'list': 'Sport'}) nav.addDir('Sport', url) xbmcplugin.endOfDirectory(addon_handle, cacheToDisc=False)
def getAssets(data, key='asset_type'): asset_list = [] for asset in data: if asset[key].lower() in ['film', 'episode', 'sport']: url = common.build_url({'action': 'playVod', 'vod_id': asset['id']}) asset_list.append({'type': asset[key], 'label': '', 'url': url, 'data': asset}) elif asset[key].lower() == 'clip': url = common.build_url({'action': 'playClip', 'id': asset['id']}) asset_list.append({'type': asset[key], 'label': '', 'url': url, 'data': asset}) elif asset[key].lower() == 'series': url = common.build_url({'action': 'listSeries', 'id': asset['id']}) asset_list.append({'type': asset[key], 'label': asset['title'], 'url': url, 'data': asset}) return asset_list
def listEpisodesFromSeason(series_id, season_id): url = skygo.baseUrl + '/sg/multiplatform/web/json/details/series/' + str(series_id) + '_global.json' r = requests.get(url) data = r.json()['serieRecap']['serie'] xbmcplugin.setContent(addon_handle, 'episodes') for season in data['seasons']['season']: if str(season['id']) == str(season_id): for episode in season['episodes']['episode']: #Check Altersfreigabe / Jugendschutzeinstellungen if 'parental_rating' in episode: if js_showall == 'false': if not skygo.parentalCheck(episode['parental_rating']['value'], play=False): continue url = common.build_url({'action': 'playVod', 'vod_id': episode['id']}) li = xbmcgui.ListItem() li.setProperty('IsPlayable', 'true') li.addContextMenuItems(getWatchlistContextItem({'type': 'Episode', 'data': episode}), replaceItems=False) info = getInfoLabel('Episode', episode) #li.setInfo('video', info) li.setLabel('%02d. %s' % (info['episode'], info['title'])) li.setArt({'poster': skygo.baseUrl + season['path'], 'fanart': getHeroImage(data), 'thumb': skygo.baseUrl + episode['webplayer_config']['assetThumbnail']}) xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li, isFolder=False) xbmcplugin.endOfDirectory(addon_handle, cacheToDisc=True)
def overview_list(): vod.generateLPageDir('http://www.skygo.sky.de/sg/multiplatform/web/json/landingpage/5.json') url = common.build_url({'action': 'seriesList'}) li = xbmcgui.ListItem('A-Z') xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li, isFolder=True) xbmcplugin.endOfDirectory(addon_handle, cacheToDisc=True)
def listLiveTvChannels(channeldir_name): data = getlistLiveChannelData(channeldir_name) for tab in data: if tab['tabName'].lower() == channeldir_name.lower(): details = {} for event in tab['eventList']: if event['event']['detailPage'].startswith("http"): detail = event['event']['detailPage'] else: detail = str(event['event']['cmsid']) if 'assetid' not in event['event']: assetid_match = re.search('\/([0-9]*)\.html', event['event']['detailPage']) if assetid_match: assetid = int(assetid_match.group(1)) try: if assetid > 0: mediainfo = getAssetDetailsFromCache(assetid) event['mediainfo'] = mediainfo manifest_url = mediainfo['media_url'] if not manifest_url.startswith('http://'): continue except: continue url = common.build_url({'action': 'playLive', 'manifest_url': manifest_url, 'package_code': event['channel']['mobilepc']}) elif event['channel']['msMediaUrl'].startswith('http://'): manifest_url = event['channel']['msMediaUrl'] url = common.build_url({'action': 'playLive', 'manifest_url': manifest_url, 'package_code': event['channel']['mobilepc']}) else: url = common.build_url({'action': 'playVod', 'vod_id': event['event']['assetid']}) try: if event['event']['assetid'] > 0: mediainfo = getAssetDetailsFromCache(event['event']['assetid']) event['mediainfo'] = mediainfo except: pass #zeige keine doppelten sender mit gleichem stream - nutze hd falls verfügbar if detail != '': if not detail in details.keys(): details[detail] = {'type': 'live', 'label': event['channel']['name'], 'url': url, 'data': event} elif details[detail]['data']['channel']['hd'] == 0 and event['channel']['hd'] == 1 and event['channel']['name'].find('+') == -1: details[detail] = {'type': 'live', 'label': event['channel']['name'], 'url': url, 'data': event} listAssets(sorted(details.values(), key=lambda k:k['data']['channel']['name']))
def listPage(page_id): nav = getNav() items = getPageItems(nav, page_id) if len(items) == 1: if 'path' in items[0].attrib: listPath(items[0].attrib['path']) return for item in items: url = '' if item.tag == 'item': url = common.build_url({'action': 'listPage', 'path': item.attrib['path']}) elif item.tag == 'section': url = common.build_url({'action': 'listPage', 'id': item.attrib['id']}) addDir(item.attrib['label'], url) if len(items) > 0: xbmcplugin.endOfDirectory(addon_handle, cacheToDisc=True)
def listLiveTvChannelDirs(): data = getlistLiveChannelData() for tab in data: url = common.build_url({'action': 'listLiveTvChannels', 'channeldir_name': tab['tabName']}) li = xbmcgui.ListItem(label=tab['tabName'].title(), iconImage=icon_file) xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li, isFolder=True) xbmcplugin.addSortMethod(handle=addon_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE) xbmcplugin.addSortMethod(handle=addon_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.endOfDirectory(addon_handle, cacheToDisc=True)
def parseListing(page, path): listitems = [] curr_page = 1 page_count = 1 if 'letters' in page: for item in page['letters']['letter']: if item['linkable'] is True: url = common.build_url({'action': 'listPage', 'path': path.replace('header', str(item['content']) + '_p1')}) listitems.append({'type': 'path', 'label': str(item['content']), 'url': url}) elif 'listing' in page: if 'isPaginated' in page['listing']: curr_page = page['listing']['currPage'] page_count = page['listing']['pages'] if 'asset_listing' in page['listing']: listitems = getAssets(page['listing']['asset_listing']['asset']) elif 'listing' in page['listing']: listing_type = page['listing'].get('type', '') #SportClips if listing_type == 'ClipsListing': listitems = getAssets(page['listing']['listing']['item'], key='type') #SportReplays elif 'asset' in page['listing']['listing']: listitems = getAssets(page['listing']['listing']['asset']) elif 'item' in page['listing']['listing']: if isinstance(page['listing']['listing']['item'], list): #Zeige nur A-Z Sortierung if checkForLexic(page['listing']['listing']['item']): path = page['listing']['listing']['item'][0]['path'].replace('header.json', 'sort_by_lexic_p1.json') listPath(path) return [] for item in page['listing']['listing']['item']: if not 'asset_type' in item and 'path' in item: url = common.build_url({'action': 'listPage', 'path': item['path']}) listitems.append({'type': 'listPage', 'label': item['title'], 'url': url}) else: listPath(page['listing']['listing']['item']['path']) if curr_page < page_count: url = common.build_url({'action': 'listPage', 'path': path.replace('_p' + str(curr_page), '_p' + str(curr_page+1))}) listitems.append({'type': 'path', 'label': 'Mehr...', 'url': url}) return listitems
def all_by_lexic(): r = requests.get('http://www.skygo.sky.de/sg/multiplatform/web/json/automatic_listing/series/all/221/header.json') header = r.json() for letter in header['letters']['letter']: if letter['linkable'] is True: url = common.build_url({'action': 'seriesList', 'letter': str(letter['content'])}) li = xbmcgui.ListItem(str(letter['content'])) xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li, isFolder=True) xbmcplugin.endOfDirectory(addon_handle, cacheToDisc=True)
def getWatchlistContextItem(item, delete=False): label = 'Zur Merkliste hinzufügen' action = 'watchlistAdd' asset_type = item['type'] if delete: label = 'Von Merkliste entfernen' action = 'watchlistDel' if asset_type == 'searchresult': asset_type = item['data']['contentType'] url = common.build_url({'action': action, 'id': item['data']['id'], 'assetType': asset_type}) return [(label, 'RunPlugin(' + url + ')')]
def rootDir(): print sys.argv nav = getNav() #Livesender liveChannelsDir() #Navigation der Ipad App for item in nav: if item.attrib['hide'] == 'true' or item.tag == 'item': continue url = common.build_url({'action': 'listPage', 'id': item.attrib['id']}) addDir(item.attrib['label'], url) li = xbmcgui.ListItem(item.attrib['label']) #Merkliste watchlistDir() #Suchfunktion url = common.build_url({'action': 'search'}) addDir('Suche', url) xbmcplugin.endOfDirectory(addon_handle, cacheToDisc=True)
def listPath(path): page = {} path = path.replace('ipad', 'web') r = requests.get(skygo.baseUrl + path) if r.status_code != 404: page = r.json() else: return False if 'sort_by_lexic_p' in path: url = common.build_url({'action': 'listPage', 'path': path[0:path.index('sort_by_lexic_p')] + 'header.json'}) addDir('[A-Z]', url) listitems = parseListing(page, path) listAssets(listitems)
def listLiveChannels(): listitems = [] channelid_list = [] url = 'http://www.skygo.sky.de/epgd/sg/ipad/excerpt/' r = requests.get(url) data = r.json() for tab in data: for event in tab['eventList']: if event['channel']['msMediaUrl'].startswith('http://'): url = common.build_url({'action': 'playLive', 'channel_id': event['channel']['id']}) if not event['channel']['id'] in channelid_list: listitems.append({'type': 'live', 'label': event['channel']['name'], 'url': url, 'data': event}) channelid_list.append(event['channel']['id']) listAssets(listitems)
def listSeasonsFromSeries(series_id): url = skygo.baseUrl + '/sg/multiplatform/web/json/details/series/' + str(series_id) + '_global.json' r = requests.get(url) data = r.json()['serieRecap']['serie'] xbmcplugin.setContent(addon_handle, 'seasons') for season in data['seasons']['season']: url = common.build_url({'action': 'listSeason', 'id': season['id'], 'series_id': data['id']}) label = '%s - Staffel %02d' % (data['title'], season['nr']) li = xbmcgui.ListItem(label=label) li.setProperty('IsPlayable', 'false') li.setArt({'poster': skygo.baseUrl + season['path'], 'fanart': getHeroImage(data)}) li.setInfo('video', {'plot': data['synopsis'].replace('\n', '').strip()}) xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li, isFolder=True) xbmcplugin.endOfDirectory(addon_handle, cacheToDisc=True)
def listWatchlist(asset_type, page=0): skygo.login() url = base_url + 'get?type=' + asset_type + '&page=' + str(page) + '&pageSize=8' r = skygo.session.get(url) data = json.loads(r.text[3:len(r.text)-1]) if not 'watchlist' in data: return listitems = [] for item in data['watchlist']: asset = skygo.getAssetDetails(item['assetId']) for asset_details in nav.getAssets([asset]): listitems.append(asset_details) if data['hasNext']: url = common.build_url({'action': 'watchlist', 'list': asset_type, 'page': page+1}) listitems.append({'type': 'path', 'label': 'Mehr...', 'url': url}) nav.listAssets(listitems, isWatchlist=True)
def listLiveChannels(): mediaurls = {} url = 'http://www.skygo.sky.de/epgd/sg/ipad/excerpt/' r = requests.get(url) data = r.json() for tab in data: for event in tab['eventList']: media_url = event['channel']['msMediaUrl'] if media_url.startswith('http://'): url = common.build_url({'action': 'playLive', 'channel_id': event['channel']['id']}) #zeige keine doppelten sender mit gleichem stream - nutze hd falls verfügbar if not media_url in mediaurls.keys(): mediaurls[media_url] = {'type': 'live', 'label': event['channel']['name'], 'url': url, 'data': event} else: if mediaurls[media_url]['data']['channel']['hd'] == 0 and event['channel']['hd'] == 1: mediaurls[media_url] = {'type': 'live', 'label': event['channel']['name'], 'url': url, 'data': event} listAssets(sorted(mediaurls.values(), key=lambda k:k['data']['channel']['id']))
def search(): dlg = xbmcgui.Dialog() term = dlg.input('Suchbegriff', type=xbmcgui.INPUT_ALPHANUM) if term == '': return term = term.replace(' ', '+') url = 'https://www.skygo.sky.de/SILK/services/public/search/web?searchKey=' + term + '&version=12354&platform=web&product=SG' r = skygo.session.get(url) data = json.loads(r.text[3:len(r.text)-1]) listitems = [] for item in data['assetListResult']: url = common.build_url({'action': 'playVod', 'vod_id': item['id']}) listitems.append({'type': 'searchresult', 'label': item['title'], 'url': url, 'data': item}) # if data['assetListResult']['hasNext']: # url = common.build_url({'action': 'listPage', 'path': ''}) # listitems.append({'type': 'path', 'label': 'Mehr...', 'url': url}) listAssets(listitems)
def build_video_listing(video_list, menu_data, pathitems=None, genre_id=None): """Build a video listing""" directory_items = [ _create_video_item(videoid_value, video, video_list) for videoid_value, video in video_list.videos.iteritems() ] # If genre_id exists add possibility to browse lolomos subgenres if genre_id and genre_id != 'None': menu_id = 'subgenre_' + genre_id sub_menu_data = menu_data.copy() sub_menu_data['path'] = [menu_data['path'][0], menu_id, genre_id] sub_menu_data['lolomo_known'] = False sub_menu_data['lolomo_contexts'] = None sub_menu_data['content_type'] = g.CONTENT_SHOW sub_menu_data['main_menu'] = menu_data['main_menu']\ if menu_data.get('main_menu') else menu_data.copy() sub_menu_data.update({'title': common.get_local_string(30089)}) g.LOCAL_DB.set_value(menu_id, sub_menu_data, TABLE_MENU_DATA) directory_items.insert( 0, (common.build_url(['genres', menu_id, genre_id], mode=g.MODE_DIRECTORY), list_item_skeleton( common.get_local_string(30089), icon='DefaultVideoPlaylists.png', description=common.get_local_string(30088)), True)) add_items_previous_next_page(directory_items, pathitems, video_list.perpetual_range_selector, genre_id) # At the moment it is not possible to make a query with results sorted for the 'mylist', # so we adding the sort order of kodi sort_type = 'sort_nothing' if menu_data['path'][1] == 'myList': sort_type = 'sort_label_ignore_folders' parent_menu_data = g.LOCAL_DB.get_value(menu_data['path'][1], table=TABLE_MENU_DATA, data_type=dict) finalize_directory(directory_items, menu_data.get('content_type', g.CONTENT_SHOW), title=parent_menu_data['title'], sort_type=sort_type) return menu_data.get('view')
def _create_videolist_item(video_list_id, video_list, menu_data, static_lists=False): """Create a tuple that can be added to a Kodi directory that represents a videolist as listed in a LoLoMo""" if static_lists and g.is_known_menu_context(video_list['context']): pathitems = menu_data['path'] pathitems.append(video_list['context']) else: # Has a dynamic video list-menu context if menu_data.get('force_videolistbyid', False): path = 'video_list_byid' else: path = 'video_list' pathitems = [path, menu_data['path'][1], video_list_id] list_item = list_item_skeleton(video_list['displayName']) add_info(video_list.id, list_item, video_list, video_list.data) if video_list.artitem: add_art(video_list.id, list_item, video_list.artitem) url = common.build_url(pathitems, mode=g.MODE_DIRECTORY) return (url, list_item, True)
def build_video_listing(video_list): """Build a video listing""" directory_items = [ _create_video_item(videoid_value, video, video_list) for videoid_value, video in video_list.videos.iteritems() ] if video_list.get('genreId'): directory_items.append( (common.build_url( ['genres', unicode(video_list['genreId'])], mode=g.MODE_DIRECTORY), list_item_skeleton(common.get_local_string(30088), icon='DefaultAddSource.png', description=common.get_local_string(30090)), True)) # TODO: Implement browsing of subgenres # directory_items.append( # (common.build_url(pathitems=['genres', genre_id, 'subgenres'], # mode=g.MODE_DIRECTORY), # list_item_skeleton('Browse subgenres...'), # True)) finalize_directory(directory_items, CONTENT_SHOW, title=video_list.title)
def listSeasonsFromSeries(series_id): url = skyticket.baseUrl + '/st/multiplatform/web/json/details/series/' + str(series_id) + '_global.json' r = requests.get(url) data = r.json()['serieRecap']['serie'] xbmcplugin.setContent(skyticket.addon_handle, 'tvshows') for season in data['seasons']['season']: url = common.build_url({'action': 'listSeason', 'id': season['id'], 'series_id': data['id']}) label = '%s - Staffel %02d' % (data['title'], season['nr']) li = xbmcgui.ListItem(label=label) li.setProperty('IsPlayable', 'false') li.setArt({'poster': skyticket.baseUrl + season['path'], 'fanart': getHeroImage(data)}) li.setInfo('video', {'plot': data['synopsis'].replace('\n', '').strip()}) li.addContextMenuItems(getWatchlistContextItem({'type': 'Episode', 'data': season}, False), replaceItems=False) xbmcplugin.addDirectoryItem(handle=skyticket.addon_handle, url=url, listitem=li, isFolder=True) xbmcplugin.addSortMethod(handle=skyticket.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE) xbmcplugin.addSortMethod(handle=skyticket.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE) xbmcplugin.addSortMethod(handle=skyticket.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.addSortMethod(handle=skyticket.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR) xbmcplugin.endOfDirectory(skyticket.addon_handle, cacheToDisc=True)
def _create_videolist_item(list_id, video_list, menu_data, common_data, static_lists=False): if static_lists and G.is_known_menu_context(video_list['context']): pathitems = list(menu_data['path']) # Make a copy pathitems.append(video_list['context']) else: # It is a dynamic video list / menu context if menu_data.get('force_use_videolist_id', False): path = 'video_list' else: path = 'video_list_sorted' pathitems = [path, menu_data['path'][1], list_id] dict_item = {'label': video_list['displayName'], 'is_folder': True} add_info_dict_item(dict_item, video_list.videoid, video_list, video_list.data, False, common_data, art_item=video_list.artitem) # Add possibility to browse the sub-genres (see build_video_listing) sub_genre_id = video_list.get('genreId') params = {'sub_genre_id': str(sub_genre_id)} if sub_genre_id else None dict_item['url'] = common.build_url(pathitems, params=params, mode=G.MODE_DIRECTORY) return dict_item
def on_playback_stopped(self, player_state): # It could happen that Kodi does not assign as watched a video, # this because the credits can take too much time, then the point where playback is stopped # falls in the part that kodi recognizes as unwatched (playcountminimumpercent 90% + no-mans land 2%) # https://kodi.wiki/view/HOW-TO:Modify_automatic_watch_and_resume_points#Settings_explained # In these cases we try change/fix manually the watched status of the video by using netflix offset data if int(player_state['percentage']) > 92: return if not self.watched_threshold or not player_state[ 'elapsed_seconds'] > self.watched_threshold: return if G.ADDON.getSettingBool( 'sync_watched_status') and not self.is_played_from_strm: # This have not to be applied with our custom watched status of Netflix sync, within the addon return if self.is_played_from_strm: # The current video played is a STRM, then generate the path of a STRM file file_path = G.SHARED_DB.get_episode_filepath( self.videoid.tvshowid, self.videoid.seasonid, self.videoid.episodeid) url = xbmcvfs.translatePath(file_path) common.json_rpc('Files.SetFileDetails', { "file": url, "media": "video", "resume": None, "playcount": 1 }) else: url = common.build_url( videoid=self.videoid, mode=G.MODE_PLAY, params={'profile_guid': G.LOCAL_DB.get_active_profile_guid()}) common.json_rpc('Files.SetFileDetails', { "file": url, "media": "video", "resume": None, "playcount": 1 }) LOG.info('Has been fixed the watched status of the video: {}', url)
def build_main_menu_listing(lolomo): """ Builds the video lists (my list, continue watching, etc.) Kodi screen """ directory_items = [] mylist_menu_exists = False for menu_id, data in g.MAIN_MENU_ITEMS.iteritems(): show_in_menu = g.ADDON.getSettingBool('_'.join(('show_menu', menu_id))) if show_in_menu: if data['lolomo_known']: for list_id, user_list in lolomo.lists_by_context( data['lolomo_contexts'], break_on_first=True): directory_items.append( _create_videolist_item(list_id, user_list, data, static_lists=True)) g.PERSISTENT_STORAGE['menu_titles'][menu_id] = user_list[ 'displayName'] if "queue" in data['lolomo_contexts']: mylist_menu_exists = True else: menu_title = common.get_local_string(data['label_id']) \ if data['label_id'] is not None else 'Missing menu title' g.PERSISTENT_STORAGE['menu_titles'][menu_id] = menu_title menu_description = common.get_local_string(data['description_id']) \ if data['description_id'] is not None else '' directory_items.append( (common.build_url(data['path'], mode=g.MODE_DIRECTORY), list_item_skeleton(menu_title, icon=data['icon'], description=menu_description), True)) #g.PERSISTENT_STORAGE.commit() performed with the next call to PERSISTENT_STORAGE setitem g.PERSISTENT_STORAGE['profile_have_mylist_menu'] = mylist_menu_exists finalize_directory(directory_items, g.CONTENT_FOLDER, title=common.get_local_string(30097))
def _create_episode_item(seasonid, episodeid_value, episode, episodes_list, common_data): episodeid = seasonid.derive_episode(episodeid_value) dict_item = { 'video_id': episodeid_value, 'media_type': episodeid.mediatype, 'label': episode['title'], 'is_folder': False, 'properties': { 'nf_videoid': episodeid.to_string() } } add_info_dict_item(dict_item, episodeid, episode, episodes_list.data, False, common_data) set_watched_status(dict_item, episode, common_data) dict_item['art'] = get_art(episodeid, episode, common_data['profile_language_code']) dict_item['url'] = common.build_url(videoid=episodeid, mode=G.MODE_PLAY, params=common_data['params']) dict_item['menu_items'] = generate_context_menu_items( episodeid, False, None) return dict_item
def get_upnext_info(videoid, current_episode, metadata): """Determine next episode and send an AddonSignal to UpNext addon""" try: next_episode_id = _find_next_episode(videoid, metadata) except (TypeError, KeyError): import traceback common.debug(traceback.format_exc()) common.debug('There is no next episode, not setting up Up Next') return {} common.debug('Next episode is {}'.format(next_episode_id)) next_episode = infolabels.add_info_for_playback(next_episode_id, xbmcgui.ListItem()) next_info = { 'current_episode': upnext_info(videoid, *current_episode), 'next_episode': upnext_info(next_episode_id, *next_episode), 'play_info': {'play_path': common.build_url(videoid=next_episode_id, mode=g.MODE_PLAY)}, } if 'creditsOffset' in metadata[0]: next_info['notification_time'] = (metadata[0]['runtime'] - metadata[0]['creditsOffset']) return next_info
def _get_upnext_info(self, videoid_next_ep, info_next_ep, metadata, is_played_from_strm): """Get the data to send to Up Next add-on""" upnext_info = { 'current_episode': _upnext_curr_ep_info(self.videoid), 'next_episode': _upnext_next_ep_info(videoid_next_ep, *info_next_ep) } if is_played_from_strm: # The current video played is a STRM, then generate the path of next STRM file file_path = G.SHARED_DB.get_episode_filepath( videoid_next_ep.tvshowid, videoid_next_ep.seasonid, videoid_next_ep.episodeid) url = xbmcvfs.translatePath(file_path) else: url = common.build_url( videoid=videoid_next_ep, mode=G.MODE_PLAY, params={'profile_guid': G.LOCAL_DB.get_active_profile_guid()}) upnext_info['play_url'] = url if 'creditsOffset' in metadata[0]: upnext_info['notification_offset'] = metadata[0]['creditsOffset'] return upnext_info
def getWatchlistContextItem(item, delete=False): label = 'Zur Merkliste hinzufügen' action = 'watchlistAdd' asset_type = item['type'] ids = [] if delete: label = 'Von Merkliste entfernen' action = 'watchlistDel' if asset_type == 'searchresult': asset_type = item['data']['contentType'] if delete == False and asset_type == 'Episode' and len( item.get('data').get('episodes', {})) > 0: for episode in item.get('data').get('episodes').get('episode'): ids.append(str(episode.get('id'))) else: ids.append(str(item['data']['id'])) url = common.build_url({ 'action': action, 'id': ','.join(ids), 'assetType': asset_type }) return [(label, 'RunPlugin(' + url + ')')]
def _create_video_item(videoid_value, video, video_list, perpetual_range_start, common_data): videoid = common.VideoId.from_videolist_item(video) is_folder = videoid.mediatype == common.VideoId.SHOW is_in_mylist = videoid in common_data['mylist_items'] dict_item = { 'video_id': videoid_value, 'media_type': videoid.mediatype, 'label': video['title'], 'is_folder': is_folder } add_info_dict_item(dict_item, videoid, video, video_list.data, is_in_mylist, common_data) set_watched_status(dict_item, video, common_data) dict_item['art'] = get_art(videoid, video, common_data['profile_language_code']) dict_item['url'] = common.build_url( videoid=videoid, mode=g.MODE_DIRECTORY if is_folder else g.MODE_PLAY, params=common_data['params']) dict_item['menu_items'] = generate_context_menu_items( videoid, is_in_mylist, perpetual_range_start) return dict_item
def listWatchlist(asset_type, page=0): skygo.login() url = base_url + 'get?type=' + asset_type + '&page=' + str( page) + '&pageSize=8' r = skygo.session.get(url) data = json.loads(r.text[3:len(r.text) - 1]) if not 'watchlist' in data: return listitems = [] for item in data['watchlist']: asset = skygo.getAssetDetails(item['assetId']) for asset_details in nav.getAssets([asset]): listitems.append(asset_details) if data['hasNext']: url = common.build_url({ 'action': 'watchlist', 'list': asset_type, 'page': page + 1 }) listitems.append({'type': 'path', 'label': 'Mehr...', 'url': url}) nav.listAssets(listitems, isWatchlist=True)
def listSeasonsFromSeries(series_id): url = skygo.baseUrl + skygo.baseServicePath + '/multiplatform/web/json/details/series/' + str( series_id) + '_global.json' r = skygo.session.get(url) data = r.json()['serieRecap']['serie'] xbmcplugin.setContent(skygo.addon_handle, 'tvshows') for season in data['seasons']['season']: url = common.build_url({ 'action': 'listSeason', 'id': season['id'], 'series_id': data['id'] }) label = '%s - Staffel %02d' % (data['title'], season['nr']) li = xbmcgui.ListItem(label=label) li.setProperty('IsPlayable', 'false') li.setArt({ 'poster': skygo.baseUrl + season['path'] + '|User-Agent=' + skygo.user_agent, 'fanart': getHeroImage(data), 'thumb': icon_file }) li.setInfo('video', {'plot': data['synopsis'].replace('\n', '').strip()}) li.addContextMenuItems(getWatchlistContextItem( { 'type': 'Episode', 'data': season }, False), replaceItems=False) xbmcplugin.addDirectoryItem(handle=skygo.addon_handle, url=url, listitem=li, isFolder=True) xbmcplugin.endOfDirectory(skygo.addon_handle, cacheToDisc=True)
def _create_videolist_item(video_list_id, video_list, menu_data, static_lists=False): """Create a tuple that can be added to a Kodi directory that represents a videolist as listed in a LoLoMo""" if static_lists and g.is_known_menu_context(video_list['context']): pathitems = menu_data['path'] pathitems.append(video_list['context']) else: # Has a dynamic video list-menu context if menu_data.get('force_videolistbyid', False): path = 'video_list' else: path = 'video_list_sorted' pathitems = [path, menu_data['path'][1], video_list_id] list_item = list_item_skeleton(video_list['displayName']) infos = add_info(video_list.id, list_item, video_list, video_list.data) if not static_lists: add_highlighted_title(list_item, video_list.id, infos) list_item.setInfo('video', infos) if video_list.artitem: add_art(video_list.id, list_item, video_list.artitem) url = common.build_url(pathitems, params={'genre_id': unicode(video_list.get('genreId'))}, mode=g.MODE_DIRECTORY) return (url, list_item, True)
def build_main_menu_listing(lolomo): """ Builds the video lists (my list, continue watching, etc.) Kodi screen """ directory_items = [] for menu_id, data in iteritems(g.MAIN_MENU_ITEMS): if not g.ADDON.getSettingBool('_'.join(('show_menu', menu_id))): continue if data['lolomo_known']: context_data = lolomo.find_by_context(data['lolomo_contexts'][0]) if not context_data: continue list_id, video_list = context_data menu_title = video_list['displayName'] videolist_item = _create_videolist_item(list_id, video_list, data, static_lists=True) else: menu_title = common.get_local_string(data['label_id']) if data.get( 'label_id') else 'Missing menu title' menu_description = common.get_local_string(data['description_id']) \ if data['description_id'] is not None else '' videolist_item = (common.build_url(data['path'], mode=g.MODE_DIRECTORY), list_item_skeleton(menu_title, icon=data['icon'], description=menu_description), True) videolist_item[1].addContextMenuItems( generate_context_menu_mainmenu(menu_id)) directory_items.append(videolist_item) g.LOCAL_DB.set_value(menu_id, {'title': menu_title}, TABLE_MENU_DATA) finalize_directory(directory_items, g.CONTENT_FOLDER, title=common.get_local_string(30097))
def build_main_menu_listing(lolomo): """ Builds the video lists (my list, continue watching, etc.) Kodi screen """ directory_items = [] mylist_menu_exists = False for menu_id, data in g.MAIN_MENU_ITEMS.iteritems(): if data['show_in_menu']: if data['lolomo_known']: for list_id, user_list in lolomo.lists_by_context( data['contexts'], break_on_first=True): directory_items.append( _create_videolist_item(list_id, user_list, data, static_lists=True)) data['menu_title'] = user_list['displayName'] if "queue" in data['contexts']: mylist_menu_exists = True else: menu_title = common.get_local_string(data['label_id']) \ if data['label_id'] is not None else 'Missing menu title' data['menu_title'] = menu_title menu_description = common.get_local_string(data['description_id']) \ if data['description_id'] is not None else '' directory_items.append( (common.build_url(data['path'], mode=g.MODE_DIRECTORY), list_item_skeleton(menu_title, icon=data['icon'], description=menu_description), True)) g.MAIN_MENU_HAVE_MYLIST = mylist_menu_exists finalize_directory(directory_items, g.CONTENT_FOLDER, title=common.get_local_string(30097))
def listLiveTvChannels(channeldir_name): if addon.getSetting('show_refresh') == 'true': url = common.build_url({'action': 'refresh'}) li = xbmcgui.ListItem(label='Aktualisieren', iconImage=icon_file, thumbnailImage=icon_file) xbmcplugin.addDirectoryItem(handle=skygo.addon_handle, url=url, listitem=li, isFolder=False) data = getlistLiveChannelData(channeldir_name) for tab in data: if tab['tabName'].lower() == channeldir_name.lower(): details = getLiveChannelDetails(tab.get('eventList'), None) listAssets( sorted(details.values(), key=lambda k: k['data']['channel']['name'])) xbmcplugin.addSortMethod(handle=skygo.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE) xbmcplugin.addSortMethod(handle=skygo.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.endOfDirectory(skygo.addon_handle, cacheToDisc=True)
def ctx_url_builder(videoid): """Build a context menu item URL""" return common.build_url(paths, videoid, mode=mode)
def _on_change(self): common.reset_log_level_global_var() common.debug( 'SettingsMonitor: settings have been changed, started checks') reboot_addon = False clean_cache = False use_mysql = g.ADDON.getSettingBool('use_mysql') use_mysql_old = g.LOCAL_DB.get_value('use_mysql', False, TABLE_SETTINGS_MONITOR) use_mysql_turned_on = use_mysql and not use_mysql_old common.debug( 'SettingsMonitor: Reinitialization of service global settings') g.init_globals(sys.argv, use_mysql != use_mysql_old) # Check the MySQL connection status after reinitialization of service global settings use_mysql_after = g.ADDON.getSettingBool('use_mysql') if use_mysql_turned_on and use_mysql_after: g.LOCAL_DB.set_value('use_mysql', True, TABLE_SETTINGS_MONITOR) ui.show_notification(g.ADDON.getLocalizedString(30202)) if not use_mysql_after and use_mysql_old: g.LOCAL_DB.set_value('use_mysql', False, TABLE_SETTINGS_MONITOR) _esn_checks() # Check menu settings changes for menu_id, menu_data in iteritems(g.MAIN_MENU_ITEMS): # Check settings changes in show/hide menu if menu_data.get('has_show_setting', True): show_menu_new_setting = bool( g.ADDON.getSettingBool('_'.join(('show_menu', menu_id)))) show_menu_old_setting = g.LOCAL_DB.get_value( 'menu_{}_show'.format(menu_id), True, TABLE_SETTINGS_MONITOR) if show_menu_new_setting != show_menu_old_setting: g.LOCAL_DB.set_value('menu_{}_show'.format(menu_id), show_menu_new_setting, TABLE_SETTINGS_MONITOR) reboot_addon = True # Check settings changes in sort order of menu if menu_data.get('has_sort_setting'): menu_sortorder_new_setting = int( g.ADDON.getSettingInt('menu_sortorder_' + menu_data['path'][1])) menu_sortorder_old_setting = g.LOCAL_DB.get_value( 'menu_{}_sortorder'.format(menu_id), 0, TABLE_SETTINGS_MONITOR) if menu_sortorder_new_setting != menu_sortorder_old_setting: g.LOCAL_DB.set_value('menu_{}_sortorder'.format(menu_id), menu_sortorder_new_setting, TABLE_SETTINGS_MONITOR) # We remove the cache to allow get the new results in the chosen order g.CACHE.clear([CACHE_COMMON, CACHE_MYLIST, CACHE_SEARCH]) # Check changes on content profiles # This is necessary because it is possible that some manifests # could be cached using the previous settings (see msl_handler - load_manifest) menu_keys = [ 'enable_dolby_sound', 'enable_vp9_profiles', 'enable_hevc_profiles', 'enable_hdr_profiles', 'enable_dolbyvision_profiles', 'enable_force_hdcp', 'disable_webvtt_subtitle' ] collect_int = '' for menu_key in menu_keys: collect_int += unicode(int(g.ADDON.getSettingBool(menu_key))) collect_int_old = g.LOCAL_DB.get_value('content_profiles_int', '', TABLE_SETTINGS_MONITOR) if collect_int != collect_int_old: g.LOCAL_DB.set_value('content_profiles_int', collect_int, TABLE_SETTINGS_MONITOR) g.CACHE.clear([CACHE_MANIFESTS]) # Check if Progress Manager settings is changed progress_manager_enabled = g.ADDON.getSettingBool( 'ProgressManager_enabled') progress_manager_enabled_old = g.LOCAL_DB.get_value( 'progress_manager_enabled', False, TABLE_SETTINGS_MONITOR) if progress_manager_enabled != progress_manager_enabled_old: g.LOCAL_DB.set_value('progress_manager_enabled', progress_manager_enabled, TABLE_SETTINGS_MONITOR) common.send_signal(signal=common.Signals.SWITCH_EVENTS_HANDLER, data=progress_manager_enabled) # Avoid perform these operations when the add-on is installed from scratch and there are no credentials if (clean_cache or reboot_addon) and not common.check_credentials(): reboot_addon = False if reboot_addon: common.debug('SettingsMonitor: addon will be rebooted') # Open root page common.container_update( common.build_url(['root'], mode=g.MODE_DIRECTORY))
def getLiveChannelDetails(eventlist, s_manifest_url=None): details = {} for event in eventlist: url = '' manifest_url = '' if event['channel']['msMediaUrl'].startswith('http'): manifest_url = event['channel']['msMediaUrl'] url = common.build_url({'action': 'playLive', 'manifest_url': manifest_url, 'package_code': event['channel']['mobilepc']}) elif s_manifest_url is None and 'assetid' in event['event']: try: if event['event']['assetid'] > 0 and extMediaInfos and extMediaInfos == 'true': mediainfo = getAssetDetailsFromCache(event['event']['assetid']) event['mediainfo'] = mediainfo except: pass url = common.build_url({'action': 'playVod', 'vod_id': event['event']['assetid']}) if 'mediainfo' not in event and extMediaInfos and extMediaInfos == 'true': assetid_match = re.search('\/([0-9]*)\.html', event['event']['detailPage']) if assetid_match: assetid = 0 try: assetid = int(assetid_match.group(1)) except: pass try: if assetid > 0: mediainfo = getAssetDetailsFromCache(assetid) event['mediainfo'] = mediainfo if not manifest_url.startswith('http'): manifest_url = mediainfo['media_url'] if not manifest_url.startswith('http'): continue except: if not manifest_url.startswith('http'): continue if event['event']['detailPage'].startswith("http"): detail = event['event']['detailPage'] else: detail = str(event['event']['cmsid']) # zeige keine doppelten sender mit gleichem stream - nutze hd falls verfügbar if detail != '': parental_rating = 0 fskInfo = re.search('(\d+)', event['event']['fskInfo']) if fskInfo: try: parental_rating = int(fskInfo.group(1)) except: pass event['parental_rating'] = {'value': parental_rating} if not detail in details.keys(): details[detail] = {'type': 'live', 'label': event['channel']['name'], 'url': url, 'data': event} elif details[detail]['url'] == '': newlabel = details[detail]['data']['channel']['name'] event['channel']['name'] = newlabel details[detail] = {'type': 'live', 'label': newlabel, 'url': url, 'data': event} elif details[detail]['data']['channel']['hd'] == 0 and event['channel']['hd'] == 1 and event['channel']['name'].find('+') == -1: details[detail] = {'type': 'live', 'label': event['channel']['name'], 'url': url, 'data': event} if s_manifest_url: if s_manifest_url == manifest_url: return {detail: details[detail]} return {} if s_manifest_url else details
def watchlistDir(): url = common.build_url({'action': 'watchlist'}) addDir('Merkliste', url)
def parseListing(page, path): listitems = [] curr_page = 1 page_count = 1 if 'letters' in page: for item in page['letters']['letter']: if item['linkable'] is True: url = common.build_url({ 'action': 'listPage', 'path': path.replace('header', str(item['content']) + '_p1') }) listitems.append({ 'type': 'path', 'label': str(item['content']), 'url': url }) elif 'listing' in page: if 'isPaginated' in page['listing']: curr_page = page['listing']['currPage'] page_count = page['listing']['pages'] if 'asset_listing' in page['listing']: listitems = getAssets(page['listing']['asset_listing']['asset']) elif 'listing' in page['listing']: listing_type = page['listing'].get('type', '') # SportClips if listing_type == 'ClipsListing': listitems = getAssets(page['listing']['listing']['item'], key='type') # SportReplays elif 'asset' in page['listing']['listing']: listitems = getAssets(page['listing']['listing']['asset']) elif 'item' in page['listing']['listing']: if isinstance(page['listing']['listing']['item'], list): # Zeige nur A-Z Sortierung if checkForLexic(page['listing']['listing']['item']): path = page['listing']['listing']['item'][0][ 'path'].replace('header.json', 'sort_by_lexic_p1.json') listPath(path) return [] for item in page['listing']['listing']['item']: if not 'asset_type' in item and 'path' in item: url = common.build_url({ 'action': 'listPage', 'path': item['path'] }) listitems.append({ 'type': 'listPage', 'label': item['title'], 'url': url }) else: listPath(page['listing']['listing']['item']['path']) if curr_page < page_count: url = common.build_url({ 'action': 'listPage', 'path': path.replace('_p' + str(curr_page), '_p' + str(curr_page + 1)) }) listitems.append({'type': 'path', 'label': 'Mehr...', 'url': url}) return listitems
def liveChannelsDir(): url = common.build_url({'action': 'listLiveTvChannels'}) addDir('Livesender', url)
def listLiveTvChannels(channeldir_name): data = getlistLiveChannelData(channeldir_name) for tab in data: if tab['tabName'].lower() == channeldir_name.lower(): details = {} for event in tab['eventList']: if event['event']['detailPage'].startswith("http"): detail = event['event']['detailPage'] else: detail = str(event['event']['cmsid']) if 'assetid' not in event['event']: manifest_url = '' if 'mediaurl' in event['channel'] and event['channel'][ 'mediaurl'].startswith('http'): manifest_url = event['channel']['mediaurl'] if not manifest_url.startswith('http') or ( extMediaInfos and extMediaInfos == 'true'): assetid_match = re.search('\/([0-9]*)\.html', event['event']['detailPage']) if assetid_match: assetid = 0 try: assetid = int(assetid_match.group(1)) except: pass try: if assetid > 0: mediainfo = getAssetDetailsFromCache( assetid) event['mediainfo'] = mediainfo if not manifest_url.startswith('http'): manifest_url = mediainfo['media_url'] if not manifest_url.startswith('http'): continue except: if not manifest_url.startswith('http'): continue url = common.build_url({ 'action': 'playLive', 'manifest_url': manifest_url, 'package_code': event['channel']['mobilepc'] }) else: if event['channel']['msMediaUrl'].startswith('http'): manifest_url = event['channel']['msMediaUrl'] url = common.build_url({ 'action': 'playLive', 'manifest_url': manifest_url, 'package_code': event['channel']['mobilepc'] }) else: url = common.build_url({ 'action': 'playVod', 'vod_id': event['event']['assetid'] }) try: if event['event'][ 'assetid'] > 0 and extMediaInfos and extMediaInfos == 'true': mediainfo = getAssetDetailsFromCache( event['event']['assetid']) event['mediainfo'] = mediainfo except: pass # zeige keine doppelten sender mit gleichem stream - nutze hd falls verfügbar if detail != '': parental_rating = 0 fskInfo = re.search('(\d+)', event['event']['fskInfo']) if fskInfo: try: parental_rating = int(fskInfo.group(1)) except: pass event['parental_rating'] = {'value': parental_rating} if not detail in details.keys(): details[detail] = { 'type': 'live', 'label': event['channel']['name'], 'url': url, 'data': event } elif details[detail]['data']['channel'][ 'hd'] == 0 and event['channel'][ 'hd'] == 1 and event['channel']['name'].find( '+') == -1: details[detail] = { 'type': 'live', 'label': event['channel']['name'], 'url': url, 'data': event } addDir('Aktualisieren', 'xbmc.executebuiltin("container.refresh")') listAssets( sorted(details.values(), key=lambda k: k['data']['channel']['name']))
def listEpisodesFromSeason(series_id, season_id): url = skygo.baseUrl + '/sg/multiplatform/web/json/details/series/' + str( series_id) + '_global.json' r = requests.get(url) data = r.json()['serieRecap']['serie'] xbmcplugin.setContent(skygo.addon_handle, 'episodes') for season in data['seasons']['season']: if str(season['id']) == str(season_id): for episode in season['episodes']['episode']: # Check Altersfreigabe / Jugendschutzeinstellungen parental_rating = 0 if 'parental_rating' in episode: parental_rating = episode['parental_rating']['value'] if js_showall == 'false': if not skygo.parentalCheck(parental_rating, play=False): continue li = xbmcgui.ListItem() li.setProperty('IsPlayable', 'true') li.addContextMenuItems(getWatchlistContextItem({ 'type': 'Episode', 'data': episode }), replaceItems=False) info, episode = getInfoLabel('Episode', episode) li.setInfo('video', info) li.setLabel(info['title']) li = addStreamInfo(li, episode) li.setArt({ 'poster': skygo.baseUrl + season['path'], 'fanart': getHeroImage(data), 'thumb': skygo.baseUrl + episode['webplayer_config']['assetThumbnail'] }) url = common.build_url({ 'action': 'playVod', 'vod_id': episode['id'], 'infolabels': info, 'parental_rating': parental_rating }) xbmcplugin.addDirectoryItem(handle=skygo.addon_handle, url=url, listitem=li, isFolder=False) xbmcplugin.addSortMethod(skygo.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_EPISODE) xbmcplugin.addSortMethod(skygo.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.addSortMethod(skygo.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE) xbmcplugin.addSortMethod(skygo.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR) xbmcplugin.addSortMethod(skygo.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_DURATION) xbmcplugin.addSortMethod(skygo.addon_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE) xbmcplugin.endOfDirectory(skygo.addon_handle, cacheToDisc=True)
def build_video_listing(video_list, menu_data, sub_genre_id=None, pathitems=None, perpetual_range_start=None, mylist_items=None): """Build a video listing""" common_data = { 'params': get_param_watched_status_by_profile(), 'mylist_items': mylist_items, 'set_watched_status': G.ADDON.getSettingBool('ProgressManager_enabled'), 'supplemental_info_color': get_color_name(G.ADDON.getSettingInt('supplemental_info_color')), 'mylist_titles_color': (get_color_name(G.ADDON.getSettingInt('mylist_titles_color')) if menu_data['path'][1] != 'myList' else None), 'profile_language_code': G.LOCAL_DB.get_profile_config('language', ''), 'ctxmenu_remove_watched_status': menu_data['path'][1] == 'continueWatching', 'active_profile_guid': G.LOCAL_DB.get_active_profile_guid() } directory_items = [ _create_video_item(videoid_value, video, video_list, perpetual_range_start, common_data) for videoid_value, video in video_list.videos.items() ] # If genre_id exists add possibility to browse LoCo sub-genres if sub_genre_id and sub_genre_id != 'None': # Create dynamic sub-menu info in MAIN_MENU_ITEMS menu_id = 'subgenre_' + sub_genre_id sub_menu_data = menu_data.copy() sub_menu_data['path'] = [menu_data['path'][0], menu_id, sub_genre_id] sub_menu_data['loco_known'] = False sub_menu_data['loco_contexts'] = None sub_menu_data['content_type'] = menu_data.get('content_type', G.CONTENT_SHOW) sub_menu_data.update({'title': common.get_local_string(30089)}) sub_menu_data['initial_menu_id'] = menu_data.get( 'initial_menu_id', menu_data['path'][1]) G.LOCAL_DB.set_value(menu_id, sub_menu_data, TABLE_MENU_DATA) # Create the folder for the access to sub-genre folder_list_item = ListItemW(label=common.get_local_string(30089)) folder_list_item.setArt({'icon': 'DefaultVideoPlaylists.png'}) folder_list_item.setInfo( 'video', {'plot': common.get_local_string(30088)}) # The description directory_items.insert( 0, (common.build_url(['genres', menu_id, sub_genre_id], mode=G.MODE_DIRECTORY), folder_list_item, True)) # add_items_previous_next_page use the new value of perpetual_range_selector add_items_previous_next_page(directory_items, pathitems, video_list.perpetual_range_selector, sub_genre_id) G.CACHE_MANAGEMENT.execute_pending_db_ops() return directory_items, {}
def liveChannelsDir(): url = common.build_url({'action': 'listLiveTvChannelDirs'}) addDir('Livesender', url)
def _create_subgenre_item(video_list_id, subgenre_data, menu_data): pathitems = ['video_list_sorted', menu_data['path'][1], video_list_id] list_item = ListItemW(label=subgenre_data['name']) return common.build_url(pathitems, mode=G.MODE_DIRECTORY), list_item, True
def logout(): """Logout of the current account""" url = common.build_url(['root'], mode=g.MODE_DIRECTORY) common.make_call('logout', url) g.CACHE.invalidate()
# ('DateAdded', ['availability', 'availabilityStartTime']) ] # pylint: disable=unnecessary-lambda INFO_TRANSFORMATIONS = { 'Season': lambda s_value: _convert_season(s_value), 'Episode': lambda ep: str(ep), 'Rating': lambda r: r / 10, 'PlayCount': lambda w: int(w), 'Trailer': lambda video_id: common.build_url( pathitems=[common.VideoId.SUPPLEMENTAL, str(video_id)], mode=g.MODE_PLAY), 'DateAdded': lambda ats: common.strf_timestamp(int(ats / 1000), '%Y-%m-%d %H:%M:%S') } REFERENCE_MAPPINGS = { 'cast': 'cast', 'director': 'directors', 'writer': 'creators', 'genre': 'genres' } def _convert_season(value): if isinstance(value, int):