2 # -*- coding: utf-8 -*-
4 # Created on: 13.01.2017
14 from UniversalAnalytics import Tracker
16 import cPickle as pickle
21 """Consumes all the configuration data from Kodi as well as turns data into lists of folders and videos"""
23 def __init__ (self, plugin_handle, base_url):
24 """Fetches all needed info from Kodi & configures the baseline of the plugin
28 plugin_handle : :obj:`int`
34 self.plugin_handle = plugin_handle
35 self.base_url = base_url
36 self.addon = xbmcaddon.Addon()
37 self.plugin = self.addon.getAddonInfo('name')
38 self.base_data_path = xbmc.translatePath(self.addon.getAddonInfo('profile'))
39 self.home_path = xbmc.translatePath('special://home')
40 self.plugin_path = self.addon.getAddonInfo('path')
41 self.cookie_path = self.base_data_path + 'COOKIE'
42 self.data_path = self.base_data_path + 'DATA'
43 self.config_path = os.path.join(self.base_data_path, 'config')
44 self.msl_data_path = xbmc.translatePath('special://profile/addon_data/service.msl').decode('utf-8') + '/'
45 self.verb_log = self.addon.getSetting('logging') == 'true'
46 self.default_fanart = self.addon.getAddonInfo('fanart')
47 self.win = xbmcgui.Window(xbmcgui.getCurrentWindowId())
52 """Refrsh the current list"""
53 return xbmc.executebuiltin('Container.Refresh')
55 def show_rating_dialog (self):
56 """Asks the user for a movie rating
61 Movie rating between 0 & 10
63 dlg = xbmcgui.Dialog()
64 return dlg.numeric(heading=self.get_local_string(string_id=30019) + ' ' + self.get_local_string(string_id=30022), type=0)
66 def show_search_term_dialog (self):
67 """Asks the user for a term to query the netflix search for
74 dlg = xbmcgui.Dialog()
75 term = dlg.input(self.get_local_string(string_id=30003), type=xbmcgui.INPUT_ALPHANUM)
80 def show_add_to_library_title_dialog (self, original_title):
81 """Asks the user for an alternative title for the show/movie that gets exported to the local library
85 original_title : :obj:`str`
86 Original title of the show (as suggested by the addon)
93 dlg = xbmcgui.Dialog()
94 return dlg.input(heading=self.get_local_string(string_id=30031), defaultt=original_title, type=xbmcgui.INPUT_ALPHANUM)
96 def show_password_dialog (self):
97 """Asks the user for its Netflix password
104 dlg = xbmcgui.Dialog()
105 return dlg.input(self.get_local_string(string_id=30004), type=xbmcgui.INPUT_ALPHANUM, option=xbmcgui.ALPHANUM_HIDE_INPUT)
107 def show_email_dialog (self):
108 """Asks the user for its Netflix account email
113 Netflix account email
115 dlg = xbmcgui.Dialog()
116 return dlg.input(self.get_local_string(string_id=30005), type=xbmcgui.INPUT_ALPHANUM)
118 def show_login_failed_notification (self):
119 """Shows notification that the login failed
126 dialog = xbmcgui.Dialog()
127 dialog.notification(self.get_local_string(string_id=30008), self.get_local_string(string_id=30009), xbmcgui.NOTIFICATION_ERROR, 5000)
130 def show_missing_inputstream_addon_notification (self):
131 """Shows notification that the inputstream addon couldn't be found
138 dialog = xbmcgui.Dialog()
139 dialog.notification(self.get_local_string(string_id=30028), self.get_local_string(string_id=30029), xbmcgui.NOTIFICATION_ERROR, 5000)
142 def show_no_search_results_notification (self):
143 """Shows notification that no search results could be found
150 dialog = xbmcgui.Dialog()
151 dialog.notification(self.get_local_string(string_id=30011), self.get_local_string(string_id=30013))
154 def show_no_seasons_notification (self):
155 """Shows notification that no seasons be found
162 dialog = xbmcgui.Dialog()
163 dialog.notification(self.get_local_string(string_id=30010), self.get_local_string(string_id=30012))
166 def set_setting (self, key, value):
167 """Public interface for the addons setSetting method
172 Setting could be set or not
174 return self.addon.setSetting(key, value)
176 def get_credentials (self):
177 """Returns the users stored credentials
181 :obj:`dict` of :obj:`str`
182 The users stored account data
185 'email': self.addon.getSetting('email'),
186 'password': self.addon.getSetting('password')
189 def get_dolby_setting(self):
191 Returns if the dolby sound is enabled
194 return self.addon.getSetting('enable_dolby_sound') == 'true'
196 def get_custom_library_settings (self):
197 """Returns the settings in regards to the custom library folder(s)
201 :obj:`dict` of :obj:`str`
202 The users library settings
205 'enablelibraryfolder': self.addon.getSetting('enablelibraryfolder'),
206 'customlibraryfolder': self.addon.getSetting('customlibraryfolder')
209 def get_ssl_verification_setting (self):
210 """Returns the setting that describes if we should verify the ssl transport when loading data
217 return self.addon.getSetting('ssl_verification') == 'true'
219 def set_main_menu_selection (self, type):
220 """Persist the chosen main menu entry in memory
227 self.win.setProperty('main_menu_selection', type)
229 def get_main_menu_selection (self):
230 """Gets the persisted chosen main menu entry from memory
235 The last chosen main menu entry
237 return self.win.getProperty('main_menu_selection')
239 def setup_memcache (self):
240 """Sets up the memory cache if not existant"""
241 cached_items = self.win.getProperty('memcache')
242 # no cache setup yet, create one
243 if len(cached_items) < 1:
244 self.win.setProperty('memcache', pickle.dumps({}))
246 def invalidate_memcache (self):
247 """Invalidates the memory cache"""
248 self.win.setProperty('memcache', pickle.dumps({}))
250 def has_cached_item (self, cache_id):
251 """Checks if the requested item is in memory cache
255 cache_id : :obj:`str`
256 ID of the cache entry
263 cached_items = pickle.loads(self.win.getProperty('memcache'))
264 return cache_id in cached_items.keys()
266 def get_cached_item (self, cache_id):
267 """Returns an item from the in memory cache
271 cache_id : :obj:`str`
272 ID of the cache entry
277 Contents of the requested cache item or none
279 cached_items = pickle.loads(self.win.getProperty('memcache'))
280 if self.has_cached_item(cache_id) != True:
282 return cached_items[cache_id]
284 def add_cached_item (self, cache_id, contents):
285 """Adds an item to the in memory cache
289 cache_id : :obj:`str`
290 ID of the cache entry
295 cached_items = pickle.loads(self.win.getProperty('memcache'))
296 cached_items.update({cache_id: contents})
297 self.win.setProperty('memcache', pickle.dumps(cached_items))
299 def build_profiles_listing (self, profiles, action, build_url):
300 """Builds the profiles list Kodi screen
304 profiles : :obj:`dict` of :obj:`str`
305 List of user profiles
308 Action paramter to build the subsequent routes
310 build_url : :obj:`fn`
311 Function to build the subsequent routes
318 for profile_id in profiles:
319 profile = profiles[profile_id]
320 url = build_url({'action': action, 'profile_id': profile_id})
321 li = xbmcgui.ListItem(label=profile['profileName'], iconImage=profile['avatar'])
322 li.setProperty('fanart_image', self.default_fanart)
323 xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)
324 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
325 xbmcplugin.endOfDirectory(self.plugin_handle)
328 def build_main_menu_listing (self, video_list_ids, user_list_order, actions, build_url):
329 """Builds the video lists (my list, continue watching, etc.) Kodi screen
333 video_list_ids : :obj:`dict` of :obj:`str`
336 user_list_order : :obj:`list` of :obj:`str`
337 Ordered user lists, to determine what should be displayed in the main menue
339 actions : :obj:`dict` of :obj:`str`
340 Dictionary of actions to build subsequent routes
342 build_url : :obj:`fn`
343 Function to build the subsequent routes
351 for category in user_list_order:
352 for video_list_id in video_list_ids['user']:
353 if video_list_ids['user'][video_list_id]['name'] == category:
354 label = video_list_ids['user'][video_list_id]['displayName']
355 if category == 'netflixOriginals':
356 label = label.capitalize()
357 li = xbmcgui.ListItem(label=label)
358 li.setProperty('fanart_image', self.default_fanart)
359 # determine action route
360 action = actions['default']
361 if category in actions.keys():
362 action = actions[category]
363 # determine if the item should be selected
364 preselect_items.append((False, True)[category == self.get_main_menu_selection()])
365 url = build_url({'action': action, 'video_list_id': video_list_id, 'type': category})
366 xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)
368 # add recommendations/genres as subfolders (save us some space on the home page)
370 'recommendations': self.get_local_string(30001),
371 'genres': self.get_local_string(30010)
373 for type in i18n_ids.keys():
374 # determine if the lists have contents
375 if len(video_list_ids[type]) > 0:
376 # determine action route
377 action = actions['default']
378 if type in actions.keys():
379 action = actions[type]
380 # determine if the item should be selected
381 preselect_items.append((False, True)[type == self.get_main_menu_selection()])
382 li_rec = xbmcgui.ListItem(label=i18n_ids[type])
383 li_rec.setProperty('fanart_image', self.default_fanart)
384 url_rec = build_url({'action': action, 'type': type})
385 xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url_rec, listitem=li_rec, isFolder=True)
387 # add search as subfolder
388 action = actions['default']
389 if 'search' in actions.keys():
390 action = actions[type]
391 li_rec = xbmcgui.ListItem(label=self.get_local_string(30011))
392 li_rec.setProperty('fanart_image', self.default_fanart)
393 url_rec = build_url({'action': action, 'type': 'search'})
394 xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url_rec, listitem=li_rec, isFolder=True)
397 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_UNSORTED)
398 xbmcplugin.endOfDirectory(self.plugin_handle)
400 # (re)select the previously selected main menu entry
402 for item in preselect_items:
404 preselected_list_item = idx if item else None
405 preselected_list_item = idx + 1 if self.get_main_menu_selection() == 'search' else preselected_list_item
406 if preselected_list_item != None:
407 xbmc.executebuiltin('ActivateWindowAndFocus(%s, %s)' % (str(self.win.getFocusId()), str(preselected_list_item)))
410 def build_video_listing (self, video_list, actions, type, build_url):
411 """Builds the video lists (my list, continue watching, etc.) contents Kodi screen
415 video_list_ids : :obj:`dict` of :obj:`str`
418 actions : :obj:`dict` of :obj:`str`
419 Dictionary of actions to build subsequent routes
422 None or 'queue' f.e. when it´s a special video lists
424 build_url : :obj:`fn`
425 Function to build the subsequent routes
432 for video_list_id in video_list:
433 video = video_list[video_list_id]
434 if type != 'queue' or (type == 'queue' and video['in_my_list'] == True):
435 li = xbmcgui.ListItem(label=video['title'])
436 # add some art to the item
437 li = self._generate_art_info(entry=video, li=li)
438 # it´s a show, so we need a subfolder & route (for seasons)
440 url = build_url({'action': actions[video['type']], 'show_id': video_list_id})
441 # lists can be mixed with shows & movies, therefor we need to check if its a movie, so play it right away
442 if video_list[video_list_id]['type'] == 'movie':
443 # it´s a movie, so we need no subfolder & a route to play it
445 url = build_url({'action': 'play_video', 'video_id': video_list_id})
447 li = self._generate_entry_info(entry=video, li=li)
448 li = self._generate_context_menu_items(entry=video, li=li)
449 xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=isFolder)
451 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
452 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
453 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR)
454 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_GENRE)
455 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LASTPLAYED)
456 xbmcplugin.endOfDirectory(self.plugin_handle)
459 def build_search_result_listing (self, video_list, actions, build_url):
460 """Builds the search results list Kodi screen
464 video_list : :obj:`dict` of :obj:`str`
465 List of videos or shows
467 actions : :obj:`dict` of :obj:`str`
468 Dictionary of actions to build subsequent routes
470 build_url : :obj:`fn`
471 Function to build the subsequent routes
478 return self.build_video_listing(video_list=video_list, actions=actions, type='search', build_url=build_url)
480 def build_no_seasons_available (self):
481 """Builds the season list screen if no seasons could be found
488 self.show_no_seasons_notification()
489 xbmcplugin.endOfDirectory(self.plugin_handle)
492 def build_no_search_results_available (self, build_url, action):
493 """Builds the search results screen if no matches could be found
498 Action paramter to build the subsequent routes
500 build_url : :obj:`fn`
501 Function to build the subsequent routes
508 self.show_no_search_results_notification()
509 return xbmcplugin.endOfDirectory(self.plugin_handle)
511 def build_user_sub_listing (self, video_list_ids, type, action, build_url):
512 """Builds the video lists screen for user subfolders (genres & recommendations)
516 video_list_ids : :obj:`dict` of :obj:`str`
520 List type (genre or recommendation)
523 Action paramter to build the subsequent routes
525 build_url : :obj:`fn`
526 Function to build the subsequent routes
533 for video_list_id in video_list_ids:
534 li = xbmcgui.ListItem(video_list_ids[video_list_id]['displayName'])
535 li.setProperty('fanart_image', self.default_fanart)
536 url = build_url({'action': action, 'video_list_id': video_list_id})
537 xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)
539 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
540 xbmcplugin.endOfDirectory(self.plugin_handle)
543 def build_season_listing (self, seasons_sorted, season_list, build_url):
544 """Builds the season list screen for a show
548 seasons_sorted : :obj:`list` of :obj:`str`
549 Sorted season indexes
551 season_list : :obj:`dict` of :obj:`str`
552 List of season entries
554 build_url : :obj:`fn`
555 Function to build the subsequent routes
562 for index in seasons_sorted:
563 for season_id in season_list:
564 season = season_list[season_id]
565 if int(season['idx']) == index:
566 li = xbmcgui.ListItem(label=season['text'])
567 # add some art to the item
568 li = self._generate_art_info(entry=season, li=li)
570 li = self._generate_entry_info(entry=season, li=li, base_info={'mediatype': 'season'})
571 li = self._generate_context_menu_items(entry=season, li=li)
572 url = build_url({'action': 'episode_list', 'season_id': season_id})
573 xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)
575 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE)
576 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR)
577 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
578 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LASTPLAYED)
579 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
580 xbmcplugin.endOfDirectory(self.plugin_handle)
583 def build_episode_listing (self, episodes_sorted, episode_list, build_url):
584 """Builds the episode list screen for a season of a show
588 episodes_sorted : :obj:`list` of :obj:`str`
589 Sorted episode indexes
591 episode_list : :obj:`dict` of :obj:`str`
592 List of episode entries
594 build_url : :obj:`fn`
595 Function to build the subsequent routes
602 for index in episodes_sorted:
603 for episode_id in episode_list:
604 episode = episode_list[episode_id]
605 if int(episode['episode']) == index:
606 li = xbmcgui.ListItem(label=episode['title'])
607 # add some art to the item
608 li = self._generate_art_info(entry=episode, li=li)
610 li = self._generate_entry_info(entry=episode, li=li, base_info={'mediatype': 'episode'})
611 li = self._generate_context_menu_items(entry=episode, li=li)
612 url = build_url({'action': 'play_video', 'video_id': episode_id, 'start_offset': episode['bookmark']})
613 xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=False)
615 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_EPISODE)
616 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE)
617 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR)
618 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
619 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LASTPLAYED)
620 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
621 xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_DURATION)
622 xbmcplugin.endOfDirectory(self.plugin_handle)
625 def play_item (self, esn, video_id, start_offset=-1):
631 ESN needed for Widevine/Inputstream
633 video_id : :obj:`str`
634 ID of the video that should be played
636 start_offset : :obj:`str`
637 Offset to resume playback from (in seconds)
644 inputstream_addon = self.get_inputstream_addon()
645 if inputstream_addon == None:
646 self.show_missing_inputstream_addon_notification()
647 self.log(msg='Inputstream addon not found')
651 self.track_event('playVideo')
653 # inputstream addon properties
654 msl_service_url = 'http://localhost:' + str(self.addon.getSetting('msl_service_port'))
655 play_item = xbmcgui.ListItem(path=msl_service_url + '/manifest?id=' + video_id)
656 play_item.setProperty(inputstream_addon + '.license_type', 'com.widevine.alpha')
657 play_item.setProperty(inputstream_addon + '.manifest_type', 'mpd')
658 play_item.setProperty(inputstream_addon + '.license_key', msl_service_url + '/license?id=' + video_id + '||b{SSM}!b{SID}|')
659 play_item.setProperty(inputstream_addon + '.server_certificate', 'Cr0CCAMSEOVEukALwQ8307Y2+LVP+0MYh/HPkwUijgIwggEKAoIBAQDm875btoWUbGqQD8eAGuBlGY+Pxo8YF1LQR+Ex0pDONMet8EHslcZRBKNQ/09RZFTP0vrYimyYiBmk9GG+S0wB3CRITgweNE15cD33MQYyS3zpBd4z+sCJam2+jj1ZA4uijE2dxGC+gRBRnw9WoPyw7D8RuhGSJ95OEtzg3Ho+mEsxuE5xg9LM4+Zuro/9msz2bFgJUjQUVHo5j+k4qLWu4ObugFmc9DLIAohL58UR5k0XnvizulOHbMMxdzna9lwTw/4SALadEV/CZXBmswUtBgATDKNqjXwokohncpdsWSauH6vfS6FXwizQoZJ9TdjSGC60rUB2t+aYDm74cIuxAgMBAAE6EHRlc3QubmV0ZmxpeC5jb20SgAOE0y8yWw2Win6M2/bw7+aqVuQPwzS/YG5ySYvwCGQd0Dltr3hpik98WijUODUr6PxMn1ZYXOLo3eED6xYGM7Riza8XskRdCfF8xjj7L7/THPbixyn4mULsttSmWFhexzXnSeKqQHuoKmerqu0nu39iW3pcxDV/K7E6aaSr5ID0SCi7KRcL9BCUCz1g9c43sNj46BhMCWJSm0mx1XFDcoKZWhpj5FAgU4Q4e6f+S8eX39nf6D6SJRb4ap7Znzn7preIvmS93xWjm75I6UBVQGo6pn4qWNCgLYlGGCQCUm5tg566j+/g5jvYZkTJvbiZFwtjMW5njbSRwB3W4CrKoyxw4qsJNSaZRTKAvSjTKdqVDXV/U5HK7SaBA6iJ981/aforXbd2vZlRXO/2S+Maa2mHULzsD+S5l4/YGpSt7PnkCe25F+nAovtl/ogZgjMeEdFyd/9YMYjOS4krYmwp3yJ7m9ZzYCQ6I8RQN4x/yLlHG5RH/+WNLNUs6JAZ0fFdCmw=')
660 # TODO: Change when Kodi can handle/trnsfer defaults in hidden values in settings
661 #play_item.setProperty(inputstream_addon + '.server_certificate', self.addon.getSetting('msl_service_certificate'))
662 play_item.setProperty('inputstreamaddon', inputstream_addon)
664 # check if we have a bookmark e.g. start offset position
665 if int(start_offset) > 0:
666 play_item.setProperty('StartOffset', str(start_offset) + '.0')
667 return xbmcplugin.setResolvedUrl(self.plugin_handle, True, listitem=play_item)
669 def _generate_art_info (self, entry, li):
670 """Adds the art info from an entry to a Kodi list item
674 entry : :obj:`dict` of :obj:`str`
675 Entry that should be turned into a list item
677 li : :obj:`XMBC.ListItem`
678 Kodi list item instance
683 Kodi list item instance
685 art = {'fanart': self.default_fanart}
686 if 'boxarts' in dict(entry).keys():
688 'poster': entry['boxarts']['big'],
689 'landscape': entry['boxarts']['big'],
690 'thumb': entry['boxarts']['small'],
691 'fanart': entry['boxarts']['big']
693 if 'interesting_moment' in dict(entry).keys():
695 'poster': entry['interesting_moment'],
696 'fanart': entry['interesting_moment']
698 if 'thumb' in dict(entry).keys():
699 art.update({'thumb': entry['thumb']})
700 if 'fanart' in dict(entry).keys():
701 art.update({'fanart': entry['fanart']})
702 if 'poster' in dict(entry).keys():
703 art.update({'poster': entry['poster']})
707 def _generate_entry_info (self, entry, li, base_info={}):
708 """Adds the item info from an entry to a Kodi list item
712 entry : :obj:`dict` of :obj:`str`
713 Entry that should be turned into a list item
715 li : :obj:`XMBC.ListItem`
716 Kodi list item instance
718 base_info : :obj:`dict` of :obj:`str`
719 Additional info that overrules the entry info
724 Kodi list item instance
727 entry_keys = entry.keys()
728 if 'cast' in entry_keys and len(entry['cast']) > 0:
729 infos.update({'cast': entry['cast']})
730 if 'creators' in entry_keys and len(entry['creators']) > 0:
731 infos.update({'writer': entry['creators'][0]})
732 if 'directors' in entry_keys and len(entry['directors']) > 0:
733 infos.update({'director': entry['directors'][0]})
734 if 'genres' in entry_keys and len(entry['genres']) > 0:
735 infos.update({'genre': entry['genres'][0]})
736 if 'maturity' in entry_keys:
737 if 'mpaa' in entry_keys:
738 infos.update({'mpaa': entry['mpaa']})
740 infos.update({'mpaa': str(entry['maturity']['board']) + '-' + str(entry['maturity']['value'])})
741 if 'rating' in entry_keys:
742 infos.update({'rating': int(entry['rating']) * 2})
743 if 'synopsis' in entry_keys:
744 infos.update({'plot': entry['synopsis']})
745 if 'plot' in entry_keys:
746 infos.update({'plot': entry['plot']})
747 if 'runtime' in entry_keys:
748 infos.update({'duration': entry['runtime']})
749 if 'duration' in entry_keys:
750 infos.update({'duration': entry['duration']})
751 if 'seasons_label' in entry_keys:
752 infos.update({'season': entry['seasons_label']})
753 if 'season' in entry_keys:
754 infos.update({'season': entry['season']})
755 if 'title' in entry_keys:
756 infos.update({'title': entry['title']})
757 if 'type' in entry_keys:
758 if entry['type'] == 'movie' or entry['type'] == 'episode':
759 li.setProperty('IsPlayable', 'true')
760 if 'mediatype' in entry_keys:
761 if entry['mediatype'] == 'movie' or entry['mediatype'] == 'episode':
762 li.setProperty('IsPlayable', 'true')
763 infos.update({'mediatype': entry['mediatype']})
764 if 'watched' in entry_keys:
765 infos.update({'playcount': (1, 0)[entry['watched']]})
766 if 'index' in entry_keys:
767 infos.update({'episode': entry['index']})
768 if 'episode' in entry_keys:
769 infos.update({'episode': entry['episode']})
770 if 'year' in entry_keys:
771 infos.update({'year': entry['year']})
772 if 'quality' in entry_keys:
773 quality = {'width': '960', 'height': '540'}
774 if entry['quality'] == '720':
775 quality = {'width': '1280', 'height': '720'}
776 if entry['quality'] == '1080':
777 quality = {'width': '1920', 'height': '1080'}
778 li.addStreamInfo('video', quality)
779 li.setInfo('video', infos)
782 def _generate_context_menu_items (self, entry, li):
783 """Adds context menue items to a Kodi list item
787 entry : :obj:`dict` of :obj:`str`
788 Entry that should be turned into a list item
790 li : :obj:`XMBC.ListItem`
791 Kodi list item instance
795 Kodi list item instance
799 entry_keys = entry.keys()
801 # action item templates
802 encoded_title = urllib.urlencode({'title': entry['title'].encode('utf-8')}) if 'title' in entry else ''
803 url_tmpl = 'XBMC.RunPlugin(' + self.base_url + '?action=%action%&id=' + str(entry['id']) + '&' + encoded_title + ')'
805 ['export_to_library', self.get_local_string(30018), 'export'],
806 ['remove_from_library', self.get_local_string(30030), 'remove'],
807 ['rate_on_netflix', self.get_local_string(30019), 'rating'],
808 ['remove_from_my_list', self.get_local_string(30020), 'remove_from_list'],
809 ['add_to_my_list', self.get_local_string(30021), 'add_to_list']
812 # build concrete action items
813 for action_item in actions:
814 action.update({action_item[0]: [action_item[1], url_tmpl.replace('%action%', action_item[2])]})
816 # add or remove the movie/show/season/episode from & to the users "My List"
817 if 'in_my_list' in entry_keys:
818 items.append(action['remove_from_my_list']) if entry['in_my_list'] else items.append(action['add_to_my_list'])
819 elif 'queue' in entry_keys:
820 items.append(action['remove_from_my_list']) if entry['queue'] else items.append(action['add_to_my_list'])
821 elif 'my_list' in entry_keys:
822 items.append(action['remove_from_my_list']) if entry['my_list'] else items.append(action['add_to_my_list'])
823 # rate the movie/show/season/episode on Netflix
824 items.append(action['rate_on_netflix'])
826 # add possibility to export this movie/show/season/episode to a static/local library (and to remove it)
827 if 'type' in entry_keys:
829 if entry['type'] == 'movie':
830 action_type = 'remove_from_library' if self.library.movie_exists(title=entry['title'], year=entry['year']) else 'export_to_library'
831 items.append(action[action_type])
833 if entry['type'] == 'show' and 'title' in entry_keys:
834 action_type = 'remove_from_library' if self.library.show_exists(title=entry['title']) else 'export_to_library'
835 items.append(action[action_type])
838 li.addContextMenuItems(items)
841 def log (self, msg, level=xbmc.LOGDEBUG):
842 """Adds a log entry to the Kodi log
847 Entry that should be turned into a list item
852 if isinstance(msg, unicode):
853 msg = msg.encode('utf-8')
854 xbmc.log('[%s] %s' % (self.plugin, msg.__str__()), level)
856 def get_local_string (self, string_id):
857 """Returns the localized version of a string
861 string_id : :obj:`int`
862 ID of the string that shoudl be fetched
867 Requested string or empty string
869 src = xbmc if string_id < 30000 else self.addon
870 locString = src.getLocalizedString(string_id)
871 if isinstance(locString, unicode):
872 locString = locString.encode('utf-8')
875 def get_inputstream_addon (self):
876 """Checks if the inputstream addon is installed & enabled.
877 Returns the type of the inputstream addon used or None if not found
882 Inputstream addon or None
884 type = 'inputstream.adaptive'
888 'method': 'Addons.GetAddonDetails',
891 'properties': ['enabled']
894 response = xbmc.executeJSONRPC(json.dumps(payload))
895 data = json.loads(response)
896 if not 'error' in data.keys():
897 if data['result']['addon']['enabled'] == True:
901 def set_library (self, library):
902 """Adds an instance of the Library class
906 library : :obj:`Library`
907 instance of the Library class
909 self.library = library
911 def track_event(self, event):
913 Send a tracking event if tracking is enabled
914 :param event: the string idetifier of the event
917 # Check if tracking is enabled
918 enable_tracking = (self.addon.getSetting('enable_tracking') == 'true')
920 #Get or Create Tracking id
921 tracking_id = self.addon.getSetting('tracking_id')
922 if tracking_id is '':
923 tracking_id = str(uuid.uuid4())
924 self.addon.setSetting('tracking_id', tracking_id)
925 # Send the tracking event
926 tracker = Tracker.create('UA-46081640-5', client_id=tracking_id)
927 tracker.send('event', event)