├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── addon.py ├── addon.xml ├── context.py ├── context2.py ├── resources ├── __init__.py ├── fanart.jpg ├── icon.png ├── lib │ ├── TheMovieDB.py │ ├── TheTVDB.py │ ├── Trakt.py │ ├── __init__.py │ ├── executor.py │ ├── fanarttv.py │ ├── lib_movies.py │ ├── lib_tvshows.py │ ├── listers.py │ ├── lists.py │ ├── menu_items.py │ ├── meta_info.py │ ├── meta_players.py │ ├── nav_base.py │ ├── nav_movies.py │ ├── nav_tvshows.py │ ├── play_base.py │ ├── play_movies.py │ ├── play_tvshows.py │ ├── playrandom.py │ ├── rpc.py │ ├── text.py │ ├── tools.py │ ├── updater.py │ ├── video_player.py │ └── xswift2.py ├── media │ ├── aired.png │ ├── airing.png │ ├── checked.png │ ├── clear.png │ ├── context.png │ ├── faq8.png │ ├── favourites.png │ ├── genre_action.png │ ├── genre_adventure.png │ ├── genre_animation.png │ ├── genre_comedy.png │ ├── genre_crime.png │ ├── genre_documentary.png │ ├── genre_drama.png │ ├── genre_education.png │ ├── genre_family.png │ ├── genre_fantasy.png │ ├── genre_foreign.png │ ├── genre_history.png │ ├── genre_horror.png │ ├── genre_kids.png │ ├── genre_music.png │ ├── genre_mystery.png │ ├── genre_news.png │ ├── genre_reality.png │ ├── genre_romance.png │ ├── genre_scifi.png │ ├── genre_soap.png │ ├── genre_talk.png │ ├── genre_thriller.png │ ├── genre_tv.png │ ├── genre_war.png │ ├── genre_western.png │ ├── genres.png │ ├── imdb.png │ ├── intheatres.png │ ├── item_next.png │ ├── library.png │ ├── lists.png │ ├── magnet.png │ ├── most_voted.png │ ├── movies.png │ ├── ontheair.png │ ├── openmeta.png │ ├── player.png │ ├── popular.png │ ├── search.png │ ├── settings.png │ ├── top_rated.png │ ├── trakt.png │ ├── traktcalendar.png │ ├── traktcollection.png │ ├── traktlikedlists.png │ ├── traktmylists.png │ ├── traktnextepisodes.png │ ├── traktrecommendations.png │ ├── traktwatchlist.png │ ├── trending.png │ ├── tv.png │ └── unchecked.png ├── screenshot000.jpg ├── screenshot001.jpg ├── screenshot002.jpg └── settings.xml └── service.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Platform** 27 | - Kodi version (17.6 Krypton, 18.0 Leia, 18.1 Leia, etc...) 28 | - Device (PC, Nvidia Shield, Mibox, Raspberry Pi, etc...) 29 | - Operating System (Android, WIndows, etc...) 30 | 31 | **Log File** 32 | Please attach a log file here. For information on how to retrieve your log file, check the [Kodi wiki](https://kodi.wiki/view/Log_file/Easy). 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | MANIFEST 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *.cover 46 | .hypothesis/ 47 | .pytest_cache/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | db.sqlite3 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # Environments 84 | .env 85 | .venv 86 | env/ 87 | venv/ 88 | ENV/ 89 | env.bak/ 90 | venv.bak/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | .spyproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | 99 | # mkdocs documentation 100 | /site 101 | 102 | # mypy 103 | .mypy_cache/ 104 | 105 | \.idea/ 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plugin.video.openmeta 2 | OpenMeta Kodi Add-on 3 | -------------------------------------------------------------------------------- /addon.py: -------------------------------------------------------------------------------- 1 | import os, sys, time, shutil 2 | import xbmcplugin 3 | from resources.lib import updater 4 | from resources.lib import menu_items 5 | from resources.lib import lib_movies 6 | from resources.lib import lib_tvshows 7 | from resources.lib.xswift2 import plugin 8 | from resources.lib import meta_players 9 | from resources.lib import play_base 10 | import xbmc 11 | 12 | 13 | @plugin.route('/defaultplayer') 14 | def setdefaultplayers(): 15 | media = ['movies', 'tvshows'] 16 | none = {'name':'None'} 17 | for i in media: 18 | play_plugin = meta_players.ADDON_SELECTOR.id 19 | players = meta_players.get_players(i) 20 | players = [p for p in players if p.id == play_plugin] or players 21 | players.append(meta_players.AddonPlayer('', '', none)) 22 | if not players or len(players) == 0: 23 | xbmc.executebuiltin('Action(Info)') 24 | play_base.action_cancel() 25 | return 26 | index = plugin.select('Play %s with...' %i, [player.title for player in players]) 27 | if index >= 0: 28 | plugin.set_setting(i + 'default', players[index].title) 29 | 30 | @plugin.route('/clear_cache') 31 | def clear_cache(): 32 | for filename in os.listdir(plugin.storage_path): 33 | file_path = os.path.join(plugin.storage_path, filename) 34 | if os.path.isfile(file_path): 35 | os.unlink(file_path) 36 | elif os.path.isdir(file_path): 37 | shutil.rmtree(file_path) 38 | plugin.notify('Cache', 'Cleared', plugin.get_addon_icon(), 3000) 39 | 40 | @plugin.route('/settings') 41 | def open_settings(): 42 | plugin.open_settings() 43 | 44 | @plugin.route('/update_library') 45 | def update_library(): 46 | now = time.time() 47 | is_syncing = plugin.getProperty('openmeta_syncing_library') 48 | is_updating = plugin.getProperty('openmeta_updating_library') 49 | if is_syncing and now - int(is_syncing) < 120: 50 | plugin.log.debug('Skipping library sync') 51 | else: 52 | if plugin.get_setting('library_sync_collection', bool) == True: 53 | try: 54 | plugin.setProperty('openmeta_syncing_library', int(now)) 55 | plugin.notify('OpenMeta', 'Syncing library', plugin.get_addon_icon(), 1000) 56 | lib_tvshows.sync_trakt_collection() 57 | lib_movies.sync_trakt_collection() 58 | finally: 59 | plugin.clearProperty('openmeta_syncing_library') 60 | if is_updating and now - int(is_updating) < 120: 61 | plugin.log.debug('Skipping library update') 62 | return 63 | else: 64 | if plugin.get_setting('library_updates', bool) == True: 65 | try: 66 | plugin.setProperty('openmeta_updating_library', int(now)) 67 | plugin.notify('OpenMeta', 'Updating library', plugin.get_addon_icon(), 1000) 68 | lib_tvshows.update_library() 69 | finally: 70 | plugin.clearProperty('openmeta_updating_library') 71 | 72 | @plugin.route('/update_library_from_settings') 73 | def update_library_from_settings(): 74 | now = time.time() 75 | if plugin.yesno('OpenMeta', 'Would you like to update the library now?'): 76 | try: 77 | plugin.setProperty('openmeta_updating_library', int(now)) 78 | plugin.notify('OpenMeta', 'Updating library', plugin.get_addon_icon(), 500) 79 | lib_tvshows.update_library() 80 | finally: 81 | plugin.clearProperty('openmeta_updating_library') 82 | 83 | @plugin.route('/update_players') 84 | @plugin.route('/update_players/', name='players_update_url') 85 | def update_players(url=None): 86 | url = plugin.get_setting('players_update_url', unicode) 87 | if updater.update_players(url): 88 | plugin.notify('OpenMeta players update', 'Done', plugin.get_addon_icon(), 3000) 89 | else: 90 | plugin.notify('OpenMeta players update', 'Failed', plugin.get_addon_icon(), 3000) 91 | 92 | @plugin.route('/setup/total') 93 | def total_setup(): 94 | plugin.notify('Total Setup', 'Started', plugin.get_addon_icon(), 2000) 95 | if sources_setup() == True: 96 | pass 97 | if players_setup() == True: 98 | pass 99 | plugin.notify('Total Setup', 'Done', plugin.get_addon_icon(), 3000) 100 | 101 | @plugin.route('/setup/silent') 102 | def silent_setup(): 103 | plugin.setProperty('totalopenmeta', 'true') 104 | movielibraryfolder = plugin.get_setting('movies_library_folder', unicode) 105 | tvlibraryfolder = plugin.get_setting('tv_library_folder', unicode) 106 | try: 107 | lib_movies.auto_movie_setup(movielibraryfolder) 108 | lib_tvshows.auto_tvshows_setup(tvlibraryfolder) 109 | except: 110 | pass 111 | plugin.clearProperty('totalopenmeta') 112 | 113 | 114 | @plugin.route('/setup/players') 115 | def players_setup(): 116 | plugin.setProperty('openmeta_players_setup', 'true') 117 | url = plugin.get_setting('players_update_url', unicode) 118 | if url == '': 119 | if plugin.yesno('OpenMeta players setup', 'Would you like to set a URL for players now?'): 120 | plugin.open_settings() 121 | else: 122 | plugin.notify('OpenMeta players setup', 'Failed', plugin.get_addon_icon(), 3000) 123 | elif updater.update_players(url): 124 | plugin.notify('OpenMeta players setup', 'Done', plugin.get_addon_icon(), 3000) 125 | else: 126 | plugin.notify('OpenMeta players setup', 'Failed', plugin.get_addon_icon(), 3000) 127 | plugin.clearProperty('openmeta_players_setup') 128 | return True 129 | 130 | @plugin.route('/setup/sources') 131 | def sources_setup(): 132 | movielibraryfolder = plugin.get_setting('movies_library_folder', unicode) 133 | tvlibraryfolder = plugin.get_setting('tv_library_folder', unicode) 134 | try: 135 | lib_movies.auto_movie_setup(movielibraryfolder) 136 | lib_tvshows.auto_tvshows_setup(tvlibraryfolder) 137 | plugin.notify('OpenMeta sources setup', 'Done', plugin.get_addon_icon(), 3000) 138 | except: 139 | plugin.notify('OpenMeta sources setup', 'Failed', plugin.get_addon_icon(), 3000) 140 | return True 141 | 142 | if __name__ == '__main__': 143 | arg = sys.argv[0] 144 | handle = int(sys.argv[1]) 145 | if '/movies/' in arg: 146 | xbmcplugin.setContent(handle, 'movies') 147 | elif '/tv/' in arg: 148 | xbmcplugin.setContent(handle, 'tvshows') 149 | elif '/lists/show' in arg: 150 | xbmcplugin.setContent(handle, 'videos') 151 | elif '/tv_episodes_' in arg: 152 | xbmcplugin.setContent(handle, 'episodes') 153 | elif '/movies_genres' in arg or '/tv_genres' in arg: 154 | xbmcplugin.setContent(handle, 'genres') 155 | else: 156 | xbmcplugin.setContent(handle, 'addons') 157 | plugin.run() -------------------------------------------------------------------------------- /addon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | video 12 | 13 | 14 | 15 | 16 | 17 | 18 | !String.StartsWith(ListItem.path,plugin://plugin.video.openmeta/)+[String.IsEqual(ListItem.dbtype,episode)|String.IsEqual(ListItem.dbtype,movie)] 19 | 20 | 21 | 22 | String.IsEqual(ListItem.dbtype,movie) | String.IsEqual(ListItem.dbtype,episode) 23 | 24 | 25 | 26 | 27 | all 28 | TMDb, TVDb and Trakt browser 29 | Browse The MovieDb, The TVDb, Trakt and pass info along to library or an addon. Needs player-files and the appropriate addon to play the files. 30 | 31 | resources/icon.png 32 | resources/fanart.jpg 33 | resources/screenshot000.jpg 34 | resources/screenshot001.jpg 35 | resources/screenshot002.jpg 36 | 37 | 38 | 39 | 40 | 41 | 42 | 1.4.14 - Fix bug with missing ID in TV menus 43 | 1.4.13 - Added realtitle (movies) and realname (tvshows) parameters for plexkodiconnect compatibility 44 | 1.4.12 - Handle non-existent 'filename' key in TVDB data. 45 | 1.4.11 - TVDB API fix, thanks to @ruinernin and @bg-22 (among others)! 46 | BUG: Some artwork may not load for certain items, due to ongoing API instability. 47 | 1.4.10 - Added language option for player-files (TV) 48 | 1.4.9 - Fix art in TV menus 49 | 1.4.8 - Fix crash from empty "first_aired" string" 50 | 1.4.7 - Fix crash when we can't contact TVDB 51 | 1.4.6 - Fix crashes from non-existent keys 52 | 1.4.5 - Added default play action, options for movies are; Play/OpenInfo and options for episodes are; Play/OpenInfo(TV Show info)/OpenInfo(Episode info). 53 | 1.4.4 - Fix clearlogo for movies 54 | 1.4.3 - Fix typo in season fanart request. 55 | 1.4.2 - Add fanart caching 56 | 1.4.1 - Fix season artwork 57 | 1.4.0 - Add unwatched counts for shows/seasons. 58 | 1.3.2 - Fix "Upcoming Episodes" and "Next Episodes". 59 | 1.3.1 - Fixed "Next Page" bug with widgets. 60 | 61 | 62 | -------------------------------------------------------------------------------- /context.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import xbmc 3 | 4 | if __name__ == '__main__': 5 | base = 'plugin://plugin.video.openmeta' 6 | info = sys.listitem.getVideoInfoTag() 7 | type = info.getMediaType() 8 | imdb = info.getIMDBNumber() 9 | if type == 'episode': 10 | url = '%s/tv/play_by_name/%s/%s/%s/en' % (base, info.getTVShowTitle(), info.getSeason(), info.getEpisode()) 11 | elif type == 'movie': 12 | if imdb.startswith('tt'): 13 | url = '%s/movies/play/imdb/%s' % (base, imdb) 14 | else: 15 | url = '%s/movies/play_by_name/%s/en' % (base, info.getTitle()) 16 | xbmc.executebuiltin('RunPlugin(%s)' % url) -------------------------------------------------------------------------------- /context2.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import xbmc 3 | 4 | if __name__ == '__main__': 5 | base = 'plugin://plugin.video.openmeta' 6 | info = sys.listitem.getVideoInfoTag() 7 | type = info.getMediaType() 8 | imdb = info.getIMDBNumber() 9 | if type == 'episode': 10 | url = '%s/tv/play_by_name_choose_player/%s/%s/%s/en/False' % (base, info.getTVShowTitle(), info.getSeason(), info.getEpisode()) 11 | elif type == 'movie': 12 | if imdb.startswith('tt'): 13 | url = '%s/movies/play_choose_player/imdb/%s/False' % (base, imdb) 14 | else: 15 | url = '%s/movies/play_by_name_choose_player/%s/en/False' % (base, info.getTitle()) 16 | xbmc.executebuiltin('RunPlugin(%s)' % url) -------------------------------------------------------------------------------- /resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a4k-openproject/plugin.video.openmeta/5b145d4b2fafeac8636bcc065c2fcdd5de45b34d/resources/__init__.py -------------------------------------------------------------------------------- /resources/fanart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a4k-openproject/plugin.video.openmeta/5b145d4b2fafeac8636bcc065c2fcdd5de45b34d/resources/fanart.jpg -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a4k-openproject/plugin.video.openmeta/5b145d4b2fafeac8636bcc065c2fcdd5de45b34d/resources/icon.png -------------------------------------------------------------------------------- /resources/lib/TheTVDB.py: -------------------------------------------------------------------------------- 1 | # vi: set noet ff=dos : 2 | import os, io, time, urllib, zipfile, requests_cache 3 | from xml.etree import ElementTree 4 | from resources.lib import text 5 | from resources.lib.xswift2 import xbmc, plugin 6 | 7 | ext_key = plugin.get_setting('tvdb_api', str) 8 | 9 | def create_tvdb(language=xbmc.getLanguage(xbmc.ISO_639_1,)): 10 | return Tvdb(language=language) 11 | 12 | if len(ext_key) == 16: 13 | API_key = ext_key 14 | else: 15 | API_key = 'AB2ED64C9BE51811' 16 | 17 | def to_bytes(text): 18 | if isinstance(text, unicode): 19 | text = text.encode('utf-8') 20 | return text 21 | 22 | class Show(dict): 23 | def __init__(self): 24 | dict.__init__(self) 25 | self.data = {} 26 | 27 | def __repr__(self): 28 | return '' % (self.data.get(u'seriesname', 'instance'), len(self)) 29 | 30 | def get(self, key, default=None): 31 | try: 32 | return self[key] 33 | except: 34 | pass 35 | return default 36 | 37 | def __getitem__(self, key): 38 | if key in self: 39 | return dict.__getitem__(self, key) 40 | if key in self.data: 41 | return dict.__getitem__(self.data, key) 42 | message = '%s not found' % key 43 | raise KeyError('%s not found' % key) 44 | 45 | def get_poster(self, language=None, default=None): 46 | try: 47 | posters = self['_banners']['poster'].values() 48 | posters = [poster for poster in posters if poster['season'] == str(self.num)] 49 | posters.sort(key=lambda k: float(k.get('rating',0)), reverse=True) 50 | if language: 51 | posters.sort(key=lambda k: k['language']!=language) 52 | return posters[0]['_bannerpath'] 53 | except: 54 | return default 55 | 56 | class Season(dict): 57 | def __init__(self, show=None, num=0): 58 | self.show = show 59 | self.num = num 60 | 61 | def __repr__(self): 62 | return '' % (self.num, len(self.keys())) 63 | 64 | def __getitem__(self, episode_number): 65 | return dict.__getitem__(self, episode_number) 66 | 67 | def has_aired(self, flexible=False): 68 | if len(self.keys()) > 0 and self.values()[0].has_aired(flexible): 69 | return True 70 | return False 71 | 72 | def get_poster(self, language=None, default=None): 73 | try: 74 | posters = self.show['_banners']['season']['season'].values() 75 | posters = [poster for poster in posters if poster['season'] == str(self.num)] 76 | posters.sort(key=lambda k: float(k.get('rating',0)), reverse=True) 77 | if language: 78 | posters.sort(key=lambda k: k['language']!=language) 79 | return posters[0]['_bannerpath'] 80 | except: 81 | return default 82 | 83 | class Episode(dict): 84 | def __init__(self, season=None): 85 | self.season = season 86 | 87 | def __repr__(self): 88 | seasno = int(self.get(u'seasonnumber', 0)) 89 | epno = int(self.get(u'episodenumber', 0)) 90 | epname = self.get(u'episodename') 91 | if epname is not None: 92 | return '' % (seasno, epno, epname) 93 | else: 94 | return '' % (seasno, epno) 95 | 96 | def __getitem__(self, key): 97 | return dict.__getitem__(self, key) 98 | 99 | def get_air_time(self): 100 | firstaired = self.get('firstaired', None) 101 | if firstaired and '-' in firstaired: 102 | try: 103 | return text.date_to_timestamp(firstaired) 104 | except: 105 | pass 106 | return -1 107 | 108 | def get_imdb(self): 109 | firstaired = self.get('imdb_id') 110 | if firstaired and firstaired not in ('', None): 111 | try: 112 | return firstaired 113 | except: 114 | pass 115 | return '0' 116 | 117 | def has_aired(self, flexible=False): 118 | if not self.get('firstaired', None): 119 | return flexible 120 | return self.get_air_time() <= time.time() 121 | 122 | class Tvdb: 123 | def __init__(self, language='en'): 124 | languages = ['da', 'fi', 'nl', 'de', 'it', 'es', 'fr', 'pl', 'hu', 125 | 'el', 'tr', 'ru', 'he', 'ja', 'pt', 'zh', 'cs', 'sl', 126 | 'hr', 'ko', 'sv', 'no'] 127 | config = {} 128 | config['apikey'] = API_key 129 | if language in languages: 130 | config['language'] = language 131 | else: 132 | config['language'] = 'en' 133 | config['url_search'] = u'https://thetvdb.com/api/GetSeries.php?seriesname=%%s&language=%%s' % config 134 | config['url_search_by_imdb'] = u'https://thetvdb.com/api/GetSeriesByRemoteID.php?imdbid=%%s&language=%%s' % config 135 | config['url_sid_full'] = u'https://thetvdb.com/api/%(apikey)s/series/%%s/all/%%s.zip' % config 136 | config['url_sid_base'] = u'https://thetvdb.com/api/%(apikey)s/series/%%s/%%s.xml' % config 137 | config['url_artwork_prefix'] = u'https://thetvdb.com/banners/%%s' % config 138 | self.config = config 139 | self.session = requests_cache.CachedSession(expire_after=21600, backend='sqlite', cache_name=os.path.join(plugin.storage_path, 'TheTVDB')) 140 | self.shows = {} 141 | 142 | def clear_cache(self): 143 | try: 144 | self.session.cache.clear() 145 | except: 146 | pass 147 | 148 | def search(self, series, year=None, language='en'): 149 | series = urllib.quote(to_bytes(series)) 150 | result = self._loadUrl(self.config['url_search'] % (series, language)) 151 | seriesEt = self._parseXML(result) 152 | allSeries = [] 153 | for series in seriesEt: 154 | result = {elem.tag.lower(): elem.text 155 | for elem in series.getchildren() if elem.text} 156 | result['id'] = int(result['id']) 157 | if 'aliasnames' in result: 158 | result['aliasnames'] = result['aliasnames'].split('|') 159 | if year: 160 | try: 161 | year = int(year) 162 | aired_year = int(result['firstaired'].split('-')[0].strip()) 163 | if aired_year != year: 164 | continue 165 | except: 166 | continue 167 | allSeries.append(result) 168 | return allSeries 169 | 170 | def search_by_imdb(self, imdb_id, year=None): 171 | language = 'en' 172 | result = self._loadUrl(self.config['url_search_by_imdb'] % (imdb_id, language)) 173 | pre_tvdb = str(result).split('') 174 | if len(pre_tvdb) > 1: 175 | tvdb = str(pre_tvdb[1]).split('') 176 | return tvdb[0] 177 | else: 178 | return None 179 | 180 | def url_sid_full(self, sid, language): 181 | return self.config['url_sid_full'] % (sid, language) 182 | 183 | def get_show(self, sid, language=None, full=False): 184 | if language is None: 185 | language = self.config['language'] 186 | if full: 187 | url = self.url_sid_full(sid, language) 188 | response = self._loadZip(url) 189 | for fname in ('%s.xml', '%s.zip.xml'): 190 | xml_fname = fname % language 191 | if xml_fname in response: 192 | break 193 | else: 194 | raise KeyError("Show XML not found in zip from API") 195 | fullDataEt = self._parseXML(response[xml_fname]) 196 | self._parseSeriesData(sid, fullDataEt) 197 | self._parseEpisodesData(sid, fullDataEt) 198 | bannersEt = self._parseXML(response['banners.xml']) 199 | self._parseBanners(sid, bannersEt) 200 | else: 201 | url = self.config['url_sid_base'] % (sid, language) 202 | response = self._loadUrl(url) 203 | if '404 Not Found' in str(response): 204 | return None 205 | else: 206 | seriesInfoEt = self._parseXML(response) 207 | self._parseSeriesData(sid, seriesInfoEt) 208 | return self.shows[sid] 209 | 210 | def __getitem__(self, key): 211 | key = int(key) 212 | if key not in self.shows: 213 | self.shows[key] = self.get_show(key, full=True) 214 | return self.shows[key] 215 | 216 | def _loadZip(self, url): 217 | resp = self.session.get(url) 218 | if 'application/zip' in resp.headers.get('Content-Type', ''): 219 | myzipfile = zipfile.ZipFile(io.BytesIO(resp.content)) 220 | files = myzipfile.namelist() 221 | return dict([(file, myzipfile.read(file)) for file in files]) 222 | return None 223 | 224 | def _loadUrl(self, url): 225 | resp = None 226 | for i in xrange(3): 227 | try: 228 | resp = self.session.get(url) 229 | break 230 | except: 231 | time.sleep(0.5) 232 | if resp is None: 233 | raise 234 | return resp.content 235 | 236 | def _parseXML(self, content): 237 | content = content.rstrip('\r') 238 | try: 239 | return ElementTree.fromstring(content) 240 | except ParseError: 241 | xbmc.log("An error occured when parsing data from TVDB.", level=xbmc.LOGERROR) 242 | return 243 | 244 | def _cleanData(self, data): 245 | data = data.replace(u'&', u'&') 246 | data = data.strip() 247 | return data 248 | 249 | def _setShowData(self, sid, key, value): 250 | if sid not in self.shows: 251 | self.shows[sid] = Show() 252 | self.shows[sid].data[key] = value 253 | 254 | def _setItem(self, sid, seas, ep, attrib, value): 255 | if sid not in self.shows: 256 | self.shows[sid] = Show() 257 | if seas not in self.shows[sid]: 258 | self.shows[sid][seas] = Season(show = self.shows[sid], num=seas) 259 | if ep not in self.shows[sid][seas]: 260 | self.shows[sid][seas][ep] = Episode(season = self.shows[sid][seas]) 261 | self.shows[sid][seas][ep][attrib] = value 262 | 263 | def _parseEpisodesData(self, sid, et): 264 | for cur_ep in et.findall('Episode'): 265 | elem_seasnum, elem_epno = cur_ep.find('SeasonNumber'), cur_ep.find('EpisodeNumber') 266 | if elem_seasnum is None or elem_epno is None: 267 | continue 268 | seas_no = int(float(elem_seasnum.text)) 269 | ep_no = int(float(elem_epno.text)) 270 | for cur_item in cur_ep.getchildren(): 271 | tag = cur_item.tag.lower() 272 | value = cur_item.text 273 | if value is not None: 274 | if tag == 'filename': 275 | value = self.config['url_artwork_prefix'] % (value) 276 | else: 277 | value = self._cleanData(value) 278 | self._setItem(sid, seas_no, ep_no, tag, value) 279 | 280 | def _parseSeriesData(self, sid, et): 281 | for curInfo in et.findall('Series')[0]: 282 | tag = curInfo.tag.lower() 283 | value = curInfo.text 284 | if value is not None: 285 | if tag in ['banner', 'fanart', 'poster']: 286 | value = self.config['url_artwork_prefix'] % (value) 287 | else: 288 | value = self._cleanData(value) 289 | self._setShowData(sid, tag, value) 290 | if tag == 'firstaired': 291 | try: 292 | self._setShowData(sid, 'year', int(value.split('-')[0].strip())) 293 | except: 294 | pass 295 | 296 | def _parseBanners(self, sid, bannersEt): 297 | banners = {} 298 | for cur_banner in bannersEt.findall('Banner'): 299 | bid = cur_banner.find('id').text 300 | btype = cur_banner.find('BannerType') 301 | btype2 = cur_banner.find('BannerType2') 302 | if btype is None or btype2 is None: 303 | continue 304 | btype, btype2 = btype.text, btype2.text 305 | if not btype in banners: 306 | banners[btype] = {} 307 | if not btype2 in banners[btype]: 308 | banners[btype][btype2] = {} 309 | if not bid in banners[btype][btype2]: 310 | banners[btype][btype2][bid] = {} 311 | for cur_element in cur_banner.getchildren(): 312 | tag = cur_element.tag.lower() 313 | value = cur_element.text 314 | if tag is None or value is None: 315 | continue 316 | tag, value = tag.lower(), value.lower() 317 | banners[btype][btype2][bid][tag] = value 318 | for k, v in list(banners[btype][btype2][bid].items()): 319 | if k.endswith('path'): 320 | new_key = '_%s' % (k) 321 | new_url = self.config['url_artwork_prefix'] % (v) 322 | banners[btype][btype2][bid][new_key] = new_url 323 | self._setShowData(sid, '_banners', banners) 324 | 325 | TVDB = Tvdb() 326 | -------------------------------------------------------------------------------- /resources/lib/Trakt.py: -------------------------------------------------------------------------------- 1 | import time, requests 2 | import xbmc, xbmcgui 3 | from resources.lib import text 4 | from resources.lib.xswift2 import plugin 5 | 6 | title = 'Authenticate Trakt' 7 | msg1 = 'Do you want to authenticate with Trakt now?' 8 | msg2 = 'Please go to https://trakt.tv/activate and enter this code: ' 9 | limit = plugin.get_setting('trakt_items_per_page', int) 10 | 11 | TCI = plugin.get_setting('trakt_api_client_id', str) 12 | TCS = plugin.get_setting('trakt_api_client_secret', str) 13 | if len(TCI) == 64 and len(TCS) == 64: 14 | CLIENT_ID = TCI 15 | CLIENT_SECRET = TCS 16 | else: 17 | CLIENT_ID = 'd1feff7915af479f8d14cf9afcc2e5a2fb5534512021d58447985e2fd555b26d' 18 | CLIENT_SECRET = '68dd208db29a54c56753549a6dbc635e7e3a1e03104b15fc0dd00555f1a549cb' 19 | 20 | def call_trakt(path, params={}, data=None, is_delete=False, with_auth=True, pagination=False, page=1): 21 | params = dict([(k, text.to_utf8(v)) for k, v in params.items() if v]) 22 | headers = { 23 | 'Content-Type': 'application/json', 24 | 'trakt-api-key': CLIENT_ID, 25 | 'trakt-api-version': '2' 26 | } 27 | 28 | def send_query(): 29 | if with_auth: 30 | try: 31 | expires_at = plugin.get_setting('trakt_expires_at', int) 32 | if time.time() > expires_at: 33 | trakt_refresh_token() 34 | except: 35 | pass 36 | token = plugin.get_setting('trakt_access_token', unicode) 37 | if token: 38 | headers['Authorization'] = 'Bearer ' + token 39 | if data is not None: 40 | assert not params 41 | return requests.post('https://api.trakt.tv/' + path, json=data, headers=headers) 42 | elif is_delete: 43 | return requests.delete('https://api.trakt.tv/' + path, headers=headers) 44 | else: 45 | return requests.get('https://api.trakt.tv/' + path, params, headers=headers) 46 | 47 | def paginated_query(page): 48 | lists = [] 49 | params['page'] = page 50 | results = send_query() 51 | if with_auth and results.status_code == 401 and plugin.yesno(title, msg1) and trakt_authenticate(): 52 | response = paginated_query(1) 53 | return response 54 | results.raise_for_status() 55 | results.encoding = 'utf-8' 56 | lists.extend(results.json()) 57 | return lists, results.headers['X-Pagination-Page-Count'] 58 | 59 | if pagination == False: 60 | response = send_query() 61 | if with_auth and response.status_code == 401 and plugin.yesno(title, msg1) and trakt_authenticate(): 62 | response = send_query() 63 | response.raise_for_status() 64 | response.encoding = 'utf-8' 65 | return response.json() 66 | else: 67 | response, numpages = paginated_query(page) 68 | return response, numpages 69 | 70 | def search_trakt(id_type, id, type): 71 | return call_trakt('search/%s/%s?type=%s' % (id_type, id, type)) 72 | 73 | def find_trakt_ids(id_type, id, type): 74 | response = search_trakt(id_type, id, type) 75 | if response: 76 | content = response[0] 77 | return content[content['type']]['ids'] 78 | return {} 79 | 80 | def trakt_get_device_code(): 81 | data = {'client_id': CLIENT_ID} 82 | return call_trakt('oauth/device/code', data=data, with_auth=False) 83 | 84 | def trakt_get_device_token(device_codes): 85 | data = { 86 | 'code': device_codes['device_code'], 87 | 'client_id': CLIENT_ID, 88 | 'client_secret': CLIENT_SECRET 89 | } 90 | start = time.time() 91 | expires_in = device_codes['expires_in'] 92 | pDialog = xbmcgui.DialogProgress() 93 | pDialog.create(title, msg2 + str(device_codes['user_code'])) 94 | try: 95 | time_passed = 0 96 | while not xbmc.Monitor().abortRequested() and not pDialog.iscanceled() and time_passed < expires_in: 97 | try: 98 | response = call_trakt('oauth/device/token', data=data, with_auth=False) 99 | except requests.HTTPError as e: 100 | if e.response.status_code != 400: 101 | raise e 102 | progress = int(100 * time_passed / expires_in) 103 | pDialog.update(progress) 104 | xbmc.sleep(max(device_codes['interval'], 1)*1000) 105 | else: 106 | return response 107 | time_passed = time.time() - start 108 | finally: 109 | pDialog.close() 110 | del pDialog 111 | return None 112 | 113 | def trakt_refresh_token(): 114 | data = { 115 | 'client_id': CLIENT_ID, 116 | 'client_secret': CLIENT_SECRET, 117 | 'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob', 118 | 'grant_type': 'refresh_token', 119 | 'refresh_token': plugin.get_setting('trakt_refresh_token', unicode) 120 | } 121 | response = call_trakt('oauth/token', data=data, with_auth=False) 122 | if response: 123 | plugin.set_setting('trakt_access_token', response['access_token']) 124 | plugin.set_setting('trakt_refresh_token', response['refresh_token']) 125 | 126 | @plugin.route('/authenticate_trakt') 127 | def trakt_authenticate(): 128 | code = trakt_get_device_code() 129 | token = trakt_get_device_token(code) 130 | if token: 131 | expires_at = time.time() + 60*60*24*30 132 | plugin.set_setting('trakt_expires_at', str(expires_at)) 133 | plugin.set_setting('trakt_access_token', token['access_token']) 134 | plugin.set_setting('trakt_refresh_token', token['refresh_token']) 135 | return True 136 | return False 137 | 138 | @plugin.route('/cleartrakt') 139 | def clear_trakt(): 140 | title = 'OpenMeta: Clear Trakt account settings' 141 | msg = 'Reauthorizing Trakt will be required to access My Trakt.[CR][CR]Are you sure?' 142 | if plugin.yesno(title, msg): 143 | plugin.set_setting('trakt_access_token', '') 144 | plugin.set_setting('trakt_refresh_token', '') 145 | plugin.set_setting('trakt_expires_at', '') 146 | 147 | def add_list(name, privacy_id=None, description=None): 148 | data = { 149 | 'name': name, 150 | 'description': description or '', 151 | 'privacy': privacy_id or ('private', 'friends', 'public')[0] 152 | } 153 | return call_trakt('users/me/lists', data=data) 154 | 155 | def del_list(list_slug): 156 | return call_trakt('users/me/lists/%s' % list_slug, is_delete=True) 157 | 158 | @plugin.cached(TTL=60, cache='Trakt') 159 | def get_hidden_items(type): 160 | return call_trakt('users/hidden/%s' % type) 161 | 162 | def get_collection(type): 163 | return call_trakt('sync/collection/%s?extended=full' % type) 164 | 165 | def get_movie_history(id): 166 | return call_trakt('users/me/history/movies/%s' % id) 167 | 168 | def get_lists(): 169 | return call_trakt('users/me/lists') 170 | 171 | @plugin.cached(TTL=60, cache='Trakt') 172 | def get_list(user, slug): 173 | return call_trakt('users/%s/lists/%s/items?extended=full' % (user, slug)) 174 | 175 | def get_liked_lists(page): 176 | result, pages = call_trakt('users/likes/lists?limit=%s' % limit, pagination=True, page=page) 177 | return result, pages 178 | 179 | def get_watchlist(type): 180 | return call_trakt('sync/watchlist/%s?extended=full' % type) 181 | 182 | def get_recommendations(type): 183 | return call_trakt('recommendations/%s?extended=full&ignore_collected=true&limit=%s' % (type, limit)) 184 | 185 | @plugin.cached(TTL=60, cache='Trakt') 186 | def get_calendar(): 187 | return call_trakt('calendars/my/shows?extended=full') 188 | 189 | @plugin.cached(TTL=60*24, cache='Trakt') 190 | def get_genres(type): 191 | return call_trakt('genres/%s' % type) 192 | 193 | @plugin.cached(TTL=60, cache='Trakt') 194 | def get_show(id): 195 | return call_trakt('shows/%s' % id) 196 | 197 | @plugin.cached(TTL=60, cache='Trakt') 198 | def get_show_play_count(id): 199 | return call_trakt('shows/%s/progress/watched' % id) 200 | 201 | @plugin.cached(TTL=60, cache='Trakt') 202 | def get_show_play_count_specials(id): 203 | return call_trakt('shows/%s/progress/watched?specials=true' % id) 204 | 205 | def get_latest_episode(id): 206 | return call_trakt('shows/%s/last_episode' % id) 207 | 208 | @plugin.cached(TTL=60, cache='Trakt') 209 | def get_season(id,season_number): 210 | seasons = call_trakt('shows/%s/seasons' % id) 211 | for season in seasons: 212 | if season['number'] == season_number: 213 | return season 214 | 215 | @plugin.cached(TTL=60, cache='Trakt') 216 | def get_seasons(id): 217 | seasons = call_trakt('shows/%s/seasons' % id) 218 | return seasons 219 | 220 | @plugin.cached(TTL=60, cache='Trakt') 221 | def get_episode(id, season, episode): 222 | return call_trakt('shows/%s/seasons/%s/episodes/%s?extended=full' % (id, season, episode)) 223 | 224 | @plugin.cached(TTL=60, cache='Trakt') 225 | def get_movie(id): 226 | return call_trakt('movies/%s' % id) 227 | 228 | @plugin.cached(TTL=60, cache='Trakt') 229 | def search_for_list(list_name, page): 230 | results, pages = call_trakt('search?type=list&query=%s&limit=%s' % (list_name, limit), pagination=True, page=page) 231 | return results, pages 232 | 233 | @plugin.cached(TTL=60, cache='Trakt') 234 | def search_for_movie(movie_title, page): 235 | results = call_trakt('search?type=movie&query=%s' % movie_title) 236 | return results 237 | 238 | @plugin.cached(TTL=60, cache='Trakt') 239 | def search_for_movie_paginated(movie_title, page): 240 | results, pages = call_trakt('search?type=movie&query=%s&limit=%s' % (movie_title, limit), pagination=True, page=page) 241 | return results, pages 242 | 243 | @plugin.cached(TTL=60, cache='Trakt') 244 | def search_for_tvshow_paginated(show_name, page): 245 | results, pages = call_trakt('search?type=show&query=%s&limit=%s' % (show_name, limit), pagination=True, page=page) 246 | return results, pages 247 | 248 | @plugin.cached(TTL=60, cache='Trakt') 249 | def get_next_episodes(): 250 | shows = call_trakt('sync/watched/shows?extended=noseasons&extended=full') 251 | hidden_shows = [item['show']['ids']['trakt'] for item in get_hidden_items('progress_watched') if item['type'] == 'show'] 252 | items = [] 253 | for item in shows: 254 | show = item['show'] 255 | id = show['ids']['trakt'] 256 | if id in hidden_shows: 257 | continue 258 | response = call_trakt('shows/%s/progress/watched?extended=full' % id) 259 | if response['next_episode']: 260 | next_episode = response['next_episode'] 261 | next_episode['show'] = show 262 | items.append(next_episode) 263 | return items 264 | 265 | @plugin.cached(TTL=60, cache='Trakt') 266 | def get_netflix_collected_shows(page): 267 | result, pages = call_trakt('shows/collected/weekly?networks=Netflix&extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 268 | return result, pages 269 | 270 | @plugin.cached(TTL=60, cache='Trakt') 271 | def get_latest_releases_movies(): 272 | return call_trakt('users/giladg/lists/latest-releases/items?extended=full', with_auth=False) 273 | 274 | @plugin.cached(TTL=60, cache='Trakt') 275 | def get_imdb_top_rated_movies(page): 276 | result, pages = call_trakt('users/justin/lists/imdb-top-rated-movies/items?extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 277 | return result, pages 278 | 279 | @plugin.cached(TTL=60, cache='Trakt') 280 | def get_trending_shows_paginated(page): 281 | result, pages = call_trakt('shows/trending?extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 282 | return result, pages 283 | 284 | @plugin.cached(TTL=60, cache='Trakt') 285 | def get_popular_shows_paginated(page): 286 | result, pages = call_trakt('shows/popular?extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 287 | return result, pages 288 | 289 | @plugin.cached(TTL=60, cache='Trakt') 290 | def get_watched_shows_paginated(page): 291 | result, pages = call_trakt('shows/watched/weekly?extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 292 | return result, pages 293 | 294 | @plugin.cached(TTL=60, cache='Trakt') 295 | def get_collected_shows_paginated(page): 296 | result, pages = call_trakt('shows/collected/weekly?extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 297 | return result, pages 298 | 299 | @plugin.cached(TTL=60, cache='Trakt') 300 | def get_trending_movies_paginated(page): 301 | result, pages = call_trakt('movies/trending?extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 302 | return result, pages 303 | 304 | @plugin.cached(TTL=60, cache='Trakt') 305 | def get_popular_movies_paginated(page): 306 | result, pages = call_trakt('movies/popular?extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 307 | return result, pages 308 | 309 | @plugin.cached(TTL=60, cache='Trakt') 310 | def get_watched_movies_paginated(page): 311 | result, pages = call_trakt('movies/watched/weekly?extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 312 | return result, pages 313 | 314 | @plugin.cached(TTL=60, cache='Trakt') 315 | def get_collected_movies_paginated(page): 316 | result, pages = call_trakt('movies/collected/weekly?extended=full&limit=%s' % limit, pagination=True, page=page, with_auth=False) 317 | return result, pages 318 | 319 | @plugin.cached(TTL=60, cache='Trakt') 320 | def get_related_movies_paginated(imdb_id, page): 321 | return call_trakt('movies/%s/related?extended=full&limit=%s' % (imdb_id, limit), pagination=True, page=page, with_auth=False) -------------------------------------------------------------------------------- /resources/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a4k-openproject/plugin.video.openmeta/5b145d4b2fafeac8636bcc065c2fcdd5de45b34d/resources/lib/__init__.py -------------------------------------------------------------------------------- /resources/lib/executor.py: -------------------------------------------------------------------------------- 1 | from itertools import islice 2 | from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED 3 | import xbmc 4 | 5 | def execute(f, iterable, stop_flag=None, workers=10): 6 | Executor = ThreadPoolExecutor 7 | with Executor(max_workers=workers) as executor: 8 | for future in _batched_pool_runner(executor, workers, f, iterable): 9 | if xbmc.Monitor().abortRequested(): 10 | break 11 | if stop_flag and stop_flag.isSet(): 12 | break 13 | yield future.result() 14 | 15 | def _batched_pool_runner(pool, batch_size, f, iterable): 16 | it = iter(iterable) 17 | futures = set(pool.submit(f, x) for x in islice(it, batch_size)) 18 | while futures: 19 | done, futures = wait(futures, return_when=FIRST_COMPLETED) 20 | futures.update(pool.submit(f, x) for x in islice(it, len(done))) 21 | for d in done: 22 | yield d -------------------------------------------------------------------------------- /resources/lib/fanarttv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from resources.lib.xswift2 import plugin 4 | import requests, xbmc 5 | 6 | client_key = plugin.get_setting('fanart.apikey', str) 7 | base_url = "http://webservice.fanart.tv/v3/%s/%s" 8 | api_key = "ac3aa6a86ba7518c9e0e198af71a3017" 9 | language = xbmc.getLanguage(xbmc.ISO_639_1) 10 | 11 | def get_query_lang(art, lang, season_num=False): 12 | if art is None: 13 | return '' 14 | if not any(i['lang'] == lang for i in art): 15 | lang = 'en' 16 | try: 17 | temp = [] 18 | 19 | if season_num: 20 | for i in art: 21 | if 'season' in i: 22 | if i['season'] == 'all': 23 | temp.append(i) 24 | elif int(i['season']) == int(season_num): 25 | temp.append(i) 26 | else: 27 | temp.append(i) 28 | else: 29 | for i in art: 30 | if 'season' in i: 31 | if i['season'] == 'all': 32 | temp.append(i) 33 | else: 34 | temp.append(i) 35 | 36 | art = temp 37 | 38 | result = [(x['url'], x['likes']) for x in art if x.get('lang') == lang] 39 | result = [(x[0], x[1]) for x in result] 40 | result = sorted(result, key=lambda x: int(x[1]), reverse=True) 41 | result = [x[0] for x in result][0] 42 | result = result 43 | except: 44 | result = '' 45 | if not 'http' in result: 46 | result = '' 47 | 48 | return result 49 | 50 | def get_query(art): 51 | if art is None: return '' 52 | try: 53 | result = [(x['url'], x['likes']) for x in art] 54 | result = [(x[0], x[1]) for x in result] 55 | result = sorted(result, key=lambda x: int(x[1]), reverse=True) 56 | result = [x[0] for x in result][0] 57 | result = result.encode('utf-8') 58 | 59 | except: 60 | result = '' 61 | if not 'http' in result: result = '' 62 | 63 | return result 64 | 65 | @plugin.cached(TTL=60*24*7, cache='Fanart') 66 | def get(remote_id, query, season): 67 | 68 | type = query 69 | 70 | if type in ['tv', 'show', 'season', 'episode']: 71 | query = 'tv' 72 | elif type == 'movies': 73 | query = 'movies' 74 | 75 | art = base_url % (query, remote_id) 76 | headers = {'client-key': client_key, 'api-key': api_key} 77 | 78 | art = requests.get(art, headers=headers).json() 79 | 80 | if type == 'movies': 81 | meta = {'poster': get_query_lang(art.get('movieposter'), language), 82 | 'fanart': get_query_lang(art.get('moviebackground'), language), 83 | 'banner': get_query_lang(art.get('moviebanner'), language), 84 | 'clearlogo': get_query_lang(art.get('movielogo', []) + art.get('hdmovielogo', []), language), 85 | 'landscape': get_query_lang(art.get('moviethumb'), language)} 86 | elif type in ['season', 'episode']: 87 | meta = {'poster': get_query_lang(art.get('seasonposter'), language, season_num=season), 88 | 'fanart': get_query_lang(art.get('showbackground'), language, season_num=season), 89 | 'banner': get_query_lang(art.get('seasonbanner'), language, season_num=season), 90 | 'clearart': get_query_lang(art.get('clearart', []) + art.get('hdclearart', []), language), 91 | 'clearlogo': get_query_lang(art.get('hdtvlogo', []) + art.get('clearlogo', []), language), 92 | 'landscape': get_query_lang(art.get('seasonthumb'), language, season_num=season)} 93 | elif type in ['tv', 'show']: 94 | meta = {'poster': get_query_lang(art.get('tvposter'), language), 95 | 'fanart': get_query_lang(art.get('showbackground'), language), 96 | 'banner': get_query_lang(art.get('tvbanner'), language), 97 | 'clearart': get_query_lang(art.get('clearart', []) + art.get('hdclearart', []), language), 98 | 'clearlogo': get_query_lang(art.get('hdtvlogo', []) + art.get('clearlogo', []), language), 99 | 'landscape': get_query_lang(art.get('tvthumb'), language)} 100 | 101 | return meta 102 | -------------------------------------------------------------------------------- /resources/lib/lib_movies.py: -------------------------------------------------------------------------------- 1 | import os, json, urllib 2 | import xbmc, xbmcvfs 3 | from resources.lib import tools 4 | from resources.lib.xswift2 import plugin 5 | 6 | 7 | @plugin.cached(TTL=60*2) 8 | def query_movies_server(url): 9 | response = urllib.urlopen(url) 10 | return json.loads(response.read()) 11 | 12 | def update_library(): 13 | library_folder = plugin.get_setting('movies_library_folder', unicode) 14 | if not xbmcvfs.exists(library_folder): 15 | return 16 | tools.scan_library(path=plugin.get_setting('movies_library_folder', unicode)) 17 | 18 | def sync_trakt_collection(): 19 | from resources.lib import nav_movies 20 | nav_movies.lists_trakt_movies_collection_to_library() 21 | 22 | def add_movie_to_library(library_folder, src, id): 23 | changed = False 24 | movie_folder = os.path.join(library_folder, str(id) + '/') 25 | if not xbmcvfs.exists(movie_folder): 26 | try: 27 | xbmcvfs.mkdir(movie_folder) 28 | except: 29 | pass 30 | nfo_filepath = os.path.join(movie_folder, str(id) + '.nfo') 31 | if not xbmcvfs.exists(nfo_filepath): 32 | changed = True 33 | nfo_file = xbmcvfs.File(nfo_filepath, 'w') 34 | if src == 'imdb': 35 | content = 'https://www.imdb.com/title/%s/' % str(id) 36 | else: 37 | content = 'https://www.themoviedb.org/movie/%s' % str(id) 38 | nfo_file.write(content) 39 | nfo_file.close() 40 | strm_filepath = os.path.join(movie_folder, str(id) + '.strm') 41 | if not xbmcvfs.exists(strm_filepath): 42 | changed = True 43 | strm_file = xbmcvfs.File(strm_filepath, 'w') 44 | content = plugin.url_for('movies_play', src=src, id=id) 45 | strm_file.write(content) 46 | strm_file.close() 47 | return changed 48 | 49 | def batch_add_movies_to_library(library_folder, id): 50 | if id == None: 51 | return 52 | changed = False 53 | movie_folder = os.path.join(library_folder, str(id) + '/') 54 | if not xbmcvfs.exists(movie_folder): 55 | try: 56 | xbmcvfs.mkdir(movie_folder) 57 | except: 58 | pass 59 | nfo_filepath = os.path.join(movie_folder, str(id) + '.nfo') 60 | if not xbmcvfs.exists(nfo_filepath): 61 | changed = True 62 | nfo_file = xbmcvfs.File(nfo_filepath, 'w') 63 | content = 'https://www.imdb.com/title/%s/' % str(id) 64 | nfo_file.write(content) 65 | nfo_file.close() 66 | strm_filepath = os.path.join(movie_folder, str(id) + '.strm') 67 | src = 'imdb' 68 | if not xbmcvfs.exists(strm_filepath): 69 | changed = True 70 | strm_file = xbmcvfs.File(strm_filepath, 'w') 71 | try: 72 | content = plugin.url_for('movies_play', src=src, id=id) 73 | strm_file.write(content) 74 | strm_file.close() 75 | except: 76 | pass 77 | return changed 78 | 79 | def setup_library(library_folder): 80 | if library_folder[-1] != '/': 81 | library_folder += '/' 82 | if not xbmcvfs.exists(library_folder): 83 | xbmcvfs.mkdir(library_folder) 84 | msg = 'Would you like to automatically set OpenMeta as a movies video source?' 85 | if plugin.yesno('Library setup', msg): 86 | source_thumbnail = plugin.get_media_icon('movies') 87 | source_name = 'OpenMeta Movies' 88 | source_content = "('%s','movies','metadata.themoviedb.org','',2147483647,1,'Rated truetruefalseenTMDbustrue',0,0,NULL,NULL)" % library_folder 89 | tools.add_source(source_name, library_folder, source_content, source_thumbnail) 90 | return xbmc.translatePath(library_folder) 91 | 92 | def auto_movie_setup(library_folder): 93 | if library_folder[-1] != '/': 94 | library_folder += '/' 95 | try: 96 | xbmcvfs.mkdir(library_folder) 97 | source_thumbnail = plugin.get_media_icon('movies') 98 | source_name = 'OpenMeta Movies' 99 | source_content = "('%s','movies','metadata.themoviedb.org','',2147483647,1,'Rated truetruefalseenTMDbustrue',0,0,NULL,NULL)" % library_folder 100 | tools.add_source(source_name, library_folder, source_content, source_thumbnail) 101 | return True 102 | except: 103 | False -------------------------------------------------------------------------------- /resources/lib/lib_tvshows.py: -------------------------------------------------------------------------------- 1 | import os, shutil 2 | import xbmc, xbmcvfs 3 | from resources.lib import text 4 | from resources.lib import tools 5 | from resources.lib.rpc import RPC 6 | from resources.lib.TheTVDB import TVDB 7 | from resources.lib.xswift2 import plugin 8 | 9 | 10 | def update_library(): 11 | folder_path = plugin.get_setting('tv_library_folder', unicode) 12 | if not xbmcvfs.exists(folder_path): 13 | return 14 | library_folder = setup_library(folder_path) 15 | try: 16 | shows = xbmcvfs.listdir(library_folder)[0] 17 | except: 18 | shows = [] 19 | clean_needed = False 20 | updated = 0 21 | for id in shows: 22 | try: 23 | id = int(id) 24 | with TVDB.session.cache_disabled(): 25 | if add_tvshow_to_library(library_folder, TVDB[id]): 26 | clean_needed = True 27 | except: 28 | continue 29 | updated += 1 30 | if clean_needed: 31 | plugin.setProperty('plugin.video.openmeta.clean_library', 'true') 32 | if updated > 0: 33 | tools.scan_library(path=plugin.get_setting('tv_library_folder', unicode)) 34 | 35 | def sync_trakt_collection(): 36 | from resources.lib import nav_tvshows 37 | nav_tvshows.lists_trakt_tv_collection_to_library() 38 | 39 | def add_tvshow_to_library(library_folder, show): 40 | clean_needed = False 41 | id = show['id'] 42 | showname = text.to_utf8(show['seriesname']) 43 | if showname == 'None' or showname == None: 44 | show_folder = os.path.join(library_folder, str(id) + '/') 45 | if os.path.isdir(show_folder): 46 | return shutil.rmtree(show_folder) 47 | enc_show = showname.translate(None, '\/:*?"<>|').strip('.') 48 | show_folder = os.path.join(library_folder, str(id) + '/') 49 | if not xbmcvfs.exists(show_folder): 50 | try: 51 | xbmcvfs.mkdir(show_folder) 52 | except: 53 | pass 54 | nfo_filepath = os.path.join(show_folder, 'tvshow.nfo') 55 | if not xbmcvfs.exists(nfo_filepath): 56 | nfo_file = xbmcvfs.File(nfo_filepath, 'w') 57 | content = 'http://thetvdb.com/?tab=series&id=%s' % str(id) 58 | nfo_file.write(content) 59 | nfo_file.close() 60 | ids = [id, show.get('imdb_id', None)] 61 | ids = [x for x in ids if x] 62 | try: 63 | libshows = RPC.VideoLibrary.GetTVShows(properties=['imdbnumber', 'title', 'year'])['tvshows'] 64 | libshows = [i for i in libshows if str(i['imdbnumber']) in ids or (str(i['year']) == str(show.get('year', 0)) and text.equals(show['seriesname'], i['title']))] 65 | libshow = libshows[0] 66 | libepisodes = RPC.VideoLibrary.GetEpisodes(filter={'and': [ {'field': 'tvshow', 'operator': 'is', 'value': text.to_utf8(libshow['title'])}]}, properties=['season', 'episode', 'file'])['episodes'] 67 | libepisodes = [(int(i['season']), int(i['episode'])) for i in libepisodes if not i['file'].endswith('.strm')] 68 | except: 69 | libepisodes = [] 70 | for (season_num,season) in show.items(): 71 | if season_num == 0: 72 | continue 73 | for (episode_num, episode) in season.items(): 74 | if episode_num == 0: 75 | continue 76 | delete = False 77 | if not episode.has_aired(flexible=False): 78 | delete = True 79 | if delete or (season_num, episode_num) in libepisodes: 80 | if library_tv_remove_strm(show, show_folder, id, season_num, episode_num): 81 | clean_needed = True 82 | else: 83 | library_tv_strm(show, show_folder, id, season_num, episode_num) 84 | files, dirs = xbmcvfs.listdir(show_folder) 85 | if not dirs: 86 | shutil.rmtree(show_folder) 87 | clean_needed = True 88 | return clean_needed 89 | 90 | def batch_add_tvshows_to_library(library_folder, show): 91 | id = show['id'] 92 | showname = text.to_utf8(show['seriesname']) 93 | show_folder = os.path.join(library_folder, str(id) + '/') 94 | if not xbmcvfs.exists(show_folder): 95 | try: 96 | xbmcvfs.mkdir(show_folder) 97 | except: 98 | pass 99 | nfo_filepath = os.path.join(show_folder, 'tvshow.nfo') 100 | if not xbmcvfs.exists(nfo_filepath): 101 | nfo_file = xbmcvfs.File(nfo_filepath, 'w') 102 | content = 'https://thetvdb.com/?tab=series&id=%s' % str(id) 103 | nfo_file.write(content) 104 | nfo_file.close() 105 | clean_needed = True 106 | return clean_needed 107 | 108 | def library_tv_remove_strm(show, folder, id, season, episode): 109 | enc_season = ('Season %s' % season).translate(None, '\/:*?"<>|').strip('.') 110 | enc_name = '%s - S%02dE%02d.strm' % (text.clean_title(show['seriesname']), season, episode) 111 | season_folder = os.path.join(folder, enc_season) 112 | stream_file = os.path.join(season_folder, enc_name) 113 | if xbmcvfs.exists(stream_file): 114 | xbmcvfs.delete(stream_file) 115 | while not xbmc.Monitor().abortRequested() and xbmcvfs.exists(stream_file): 116 | xbmc.sleep(1000) 117 | a,b = xbmcvfs.listdir(season_folder) 118 | if not a and not b: 119 | xbmcvfs.rmdir(season_folder) 120 | return True 121 | return False 122 | 123 | def library_tv_strm(show, folder, id, season, episode): 124 | enc_season = ('Season %s' % season).translate(None, '\/:*?"<>|').strip('.') 125 | folder = os.path.join(folder, enc_season) 126 | try: 127 | xbmcvfs.mkdir(folder) 128 | except: 129 | pass 130 | enc_name = '%s - S%02dE%02d.strm' % (text.clean_title(show['seriesname']), season, episode) 131 | stream = os.path.join(folder, enc_name) 132 | if not xbmcvfs.exists(stream): 133 | file = xbmcvfs.File(stream, 'w') 134 | content = plugin.url_for('tv_play', id=id, season=season, episode=episode) 135 | file.write(str(content)) 136 | file.close() 137 | 138 | def setup_library(library_folder): 139 | if library_folder[-1] != '/': 140 | library_folder += '/' 141 | if not xbmcvfs.exists(library_folder): 142 | xbmcvfs.mkdir(library_folder) 143 | msg = 'Would you like to automatically set OpenMeta as a tv shows source?' 144 | if plugin.yesno('Library setup', msg): 145 | try: 146 | source_thumbnail = plugin.get_media_icon('tv') 147 | source_name = 'OpenMeta TV shows' 148 | source_content = "('%s','tvshows','metadata.tvdb.com','',0,0,'falsetruefalsetrueestrueenTheTVDBtrue',0,0,NULL,NULL)" % library_folder 149 | tools.add_source(source_name, library_folder, source_content, source_thumbnail) 150 | except: 151 | pass 152 | return xbmc.translatePath(library_folder) 153 | 154 | def auto_tvshows_setup(library_folder): 155 | if library_folder[-1] != '/': library_folder += '/' 156 | try: 157 | xbmcvfs.mkdir(library_folder) 158 | source_thumbnail = plugin.get_media_icon('tv') 159 | source_name = 'OpenMeta TV shows' 160 | source_content = "('%s','tvshows','metadata.tvdb.com','',0,0,'falsetruefalsetrueestrueenTheTVDBtrue',0,0,NULL,NULL)" % library_folder 161 | tools.add_source(source_name, library_folder, source_content, source_thumbnail) 162 | return True 163 | except: 164 | False -------------------------------------------------------------------------------- /resources/lib/listers.py: -------------------------------------------------------------------------------- 1 | import re, copy, time, threading 2 | import xbmc, xbmcgui, xbmcaddon 3 | from resources.lib import text 4 | from resources.lib.rpc import RPC 5 | from resources.lib.xswift2 import plugin 6 | 7 | IGNORE_CHARS = ('.', '%20') 8 | 9 | def wait_for_dialog(dialog_id, timeout=None, interval=500): 10 | start = time.time() 11 | while not xbmc.getCondVisibility('Window.IsActive(%s)' % dialog_id): 12 | if xbmc.Monitor().abortRequested() or (timeout and time.time() - start >= timeout): 13 | return False 14 | xbmc.sleep(interval) 15 | return True 16 | 17 | def list_dir(path): 18 | path = text.urlencode_path(path) 19 | try: 20 | response = RPC.Files.GetDirectory(media='files', directory=path, properties=['season','episode']) 21 | except: 22 | plugin.log.error(path) 23 | raise 24 | dirs = [] 25 | files = [] 26 | for item in response.get('files', []): 27 | if item.has_key('file') and item.has_key('filetype') and item.has_key('label'): 28 | if item['filetype'] == 'directory': 29 | for ext in ('.xsp', '.xml'): 30 | if item['file'].endswith(ext) or item['file'].endswith(ext+'/'): 31 | continue 32 | dirs.append({'path': item['file'], 'label': item['label'], 'season': item.get('season')}) 33 | else: 34 | files.append({'path': item['file'], 'label': item['label'], 'season': item.get('season'), 'episode': item.get('episode')}) 35 | return [path,dirs,files] 36 | 37 | def regex_escape(string): 38 | for c in '\\.$^{[(|)*+?': 39 | string = string.replace(c, '\\' + c) 40 | return string 41 | 42 | @plugin.cached(TTL=5, cache='browser') 43 | def cached_list_dir(path, keyboard_hint=None): 44 | return list_dir(path) 45 | 46 | class KeyboardMonitor(threading.Thread): 47 | def __init__(self): 48 | threading.Thread.__init__(self) 49 | self.active = True 50 | self.search_term = None 51 | self.owner_thread = None 52 | self.lock = threading.Lock() 53 | self.access_lock = threading.RLock() 54 | 55 | def stop(self): 56 | self.active = False 57 | 58 | def set_term(self, search_term): 59 | self.lock.acquire() 60 | self.owner_thread = threading.current_thread() 61 | self.search_term = search_term 62 | 63 | def release(self): 64 | with self.access_lock: 65 | if self.owner_thread is not None: 66 | self.search_term = None 67 | self.owner_thread = None 68 | self.lock.release() 69 | 70 | def release_if_owner(self): 71 | with self.access_lock: 72 | if self.owner_thread is threading.current_thread(): 73 | self.release() 74 | 75 | def prep_search_str(self, string): 76 | t_text = text.to_unicode(string) 77 | for chr in t_text: 78 | if ord(chr) >= 1488 and ord(chr) <= 1514: 79 | return text.to_utf8(string[::-1]) 80 | return text.to_utf8(string) 81 | 82 | def run(self): 83 | while self.active and not xbmc.Monitor().abortRequested(): 84 | if wait_for_dialog('virtualkeyboard', timeout=5, interval=100): 85 | if self.search_term is not None: 86 | xbmc.executebuiltin('Dialog.Close(virtualkeyboard, true)') 87 | text = self.prep_search_str(self.search_term) 88 | RPC.Input.SendText(text=text, done=True) 89 | self.release() 90 | 91 | class Lister: 92 | def __init__(self, preserve_viewid=None, stop_flag=None): 93 | if stop_flag is None: 94 | stop_flag = threading.Event() 95 | self.stop_flag = stop_flag 96 | if preserve_viewid is None: 97 | window = xbmcgui.Window(xbmcgui.getCurrentWindowId()) 98 | preserve_viewid = window.getFocusId() 99 | self.preserve_viewid = preserve_viewid 100 | self.keyboardMonitor = KeyboardMonitor() 101 | self.keyboardMonitor.start() 102 | 103 | def get(self, path, guidance, parameters): 104 | unescaped_parameters = parameters 105 | parameters = copy.deepcopy(parameters) 106 | for key, value in parameters.items(): 107 | if isinstance(value, basestring): 108 | for c in IGNORE_CHARS: 109 | value = value.replace(c, ' ') 110 | parameters[key] = regex_escape(value) 111 | try: 112 | return self._browse_external(path, guidance, parameters, unescaped_parameters) 113 | finally: 114 | self._restore_viewid() 115 | 116 | def is_active(self): 117 | return not self.stop_flag.is_set() 118 | 119 | def stop(self): 120 | if not self.stop_flag.is_set(): 121 | self.stop_flag.set() 122 | self.keyboardMonitor.stop() 123 | 124 | @staticmethod 125 | def _has_match(item, pattern, parameters): 126 | season_infolabel_match = False 127 | if item.get('season'): 128 | item_season = str(item.get('season')) 129 | param_season = str(parameters.get('season', '')) 130 | if item_season == param_season: 131 | season_infolabel_match = True 132 | if pattern == '{season}' and season_infolabel_match: 133 | return True 134 | episode_infolabel_match = False 135 | if item.get('episode'): 136 | item_episode = str(item.get('episode')) 137 | param_episode = str(parameters.get('episode', '')) 138 | if item_episode == param_episode: 139 | episode_infolabel_match = True 140 | if pattern == '{episode}' and episode_infolabel_match: 141 | return True 142 | if pattern == '{season}x{episode}' and season_infolabel_match and episode_infolabel_match: 143 | return True 144 | label = item['label'] 145 | pattern = text.to_unicode(pattern) 146 | pattern = pattern.replace('$$', r'($|^|\s|\]|\[)') 147 | first_season = False 148 | if '{season}' in pattern and '1' == str(parameters.get('season')): 149 | pattern = pattern.replace('{season}', '(?P\d*)') 150 | first_season = True 151 | pattern = text.apply_parameters(pattern, parameters) 152 | for c in IGNORE_CHARS: 153 | label = label.replace(c, ' ') 154 | pattern = text.to_unicode(text.to_utf8(pattern)) 155 | label = text.to_unicode(text.to_utf8(label)) 156 | if '$INFO[' in pattern: 157 | m = re.search('\\[(.*?)\]', pattern) 158 | info = m.group(1) 159 | pattern = pattern.replace('$INFO[%s]' % info, xbmc.getInfoLabel(info)) 160 | if '$LOCALIZE[' in pattern: 161 | m = re.search('\[(\d+)\]', pattern) 162 | id = int(m.group(1)) 163 | pattern = pattern.replace('$LOCALIZE[%s]' % id, xbmc.getLocalizedString(id).encode('utf-8')) 164 | if '$ADDON[' in pattern: 165 | m = re.search('\[.*\ (\d+)\]', pattern) 166 | aid = m.group(0).strip('[]').split(' ')[0] 167 | id = int(m.group(1)) 168 | pattern = pattern.replace('$ADDON[%s %s]' % (aid, id), xbmcaddon.Addon(aid).getLocalizedString(id).encode('utf-8')) 169 | if pattern.startswith('><'): 170 | label = re.sub(r'\[[^)].*?\]', '', label) 171 | pattern = pattern.strip('><') 172 | plugin.log.debug('matching pattern %s to label %s' % (text.to_utf8(pattern), text.to_utf8(label))) 173 | r = re.compile(pattern, re.I|re.UNICODE) 174 | match = r.match(label) 175 | if ', The' in label and match is None: 176 | label = u'The ' + label.replace(', The', '') 177 | match = r.match(label) 178 | if match is not None and match.end() == len(label): 179 | if first_season and not match.group('season') in ('1', '', '01', None): 180 | return False 181 | plugin.log.debug('match: ' + text.to_utf8(label)) 182 | return True 183 | return False 184 | 185 | def _restore_viewid(self): 186 | xbmc.executebuiltin('Container.SetViewMode(%d)' % self.preserve_viewid) 187 | 188 | def _browse_external(self, path, guidance, parameters, unescaped_parameters, depth=0): 189 | result_dirs = [] 190 | result_files = [] 191 | keyboard_hint = None 192 | for i, hint in enumerate(guidance): 193 | if self.stop_flag.isSet() or xbmc.Monitor().abortRequested(): 194 | return [],[] 195 | if not path: 196 | break 197 | if hint.startswith('keyboard:'): 198 | hint = u'@' + hint 199 | if hint.startswith('@keyboard:') and i != len(guidance) - 1: 200 | term = hint[len('@keyboard:'):].lstrip() 201 | term = term.format(**unescaped_parameters) 202 | self.keyboardMonitor.set_term(term) 203 | keyboard_hint = term 204 | continue 205 | try: 206 | _, dirs, files = cached_list_dir(path, keyboard_hint) 207 | except: 208 | break 209 | finally: 210 | if keyboard_hint is not None: 211 | self.keyboardMonitor.release_if_owner() 212 | keyboard_hint = None 213 | self._restore_viewid() 214 | path = None 215 | if hint.startswith('><'): 216 | hint = hint.strip('><') 217 | if hint == '@any': 218 | for dir in dirs: 219 | rec_files, rec_dirs = self._browse_external(dir['path'], guidance[i+1:], parameters, unescaped_parameters, depth) 220 | result_files += rec_files 221 | result_dirs += rec_dirs 222 | if result_files: 223 | break 224 | elif hint.startswith('@anyexcept:') and len(hint) >= 14: 225 | exceptions = [] 226 | exclusion = hint[len('@anyexcept:'):].lstrip() 227 | if '|' in exclusion: 228 | exceptions = exclusion.split('|', ) 229 | else: 230 | exceptions.append(exclusion) 231 | for dir in dirs: 232 | if dir['label'] not in exceptions: 233 | rec_files, rec_dirs = self._browse_external(dir['path'], guidance[i+1:], parameters, unescaped_parameters, depth) 234 | result_files += rec_files 235 | result_dirs += rec_dirs 236 | if result_files: 237 | break 238 | elif hint.startswith('@anynotcontaining:') and len(hint) >= 21: 239 | exceptions = [] 240 | exclusion = hint[len('@anynotcontaining:'):].lstrip() 241 | if '|' in exclusion: 242 | exceptions = exclusion.split('|', ) 243 | else: 244 | exceptions.append(exclusion) 245 | for dir in dirs: 246 | for exception in exceptions: 247 | if not exception in dir['label']: 248 | rec_files, rec_dirs = self._browse_external(dir['path'], guidance[i+1:], parameters, unescaped_parameters, depth) 249 | result_files += rec_files 250 | result_dirs += rec_dirs 251 | if result_files: 252 | break 253 | elif hint.startswith('@anycontaining:') and len(hint) >= 18: 254 | rules = [] 255 | inclusion = hint[len('@anycontaining:'):].lstrip() 256 | if '|' in inclusion: 257 | rules = inclusion.split('|', ) 258 | else: 259 | rules.append(inclusion) 260 | for dir in dirs: 261 | for rule in rules: 262 | if rule in dir['label']: 263 | rec_files, rec_dirs = self._browse_external(dir['path'], guidance[i+1:], parameters, unescaped_parameters, depth) 264 | result_files += rec_files 265 | result_dirs += rec_dirs 266 | if result_files: 267 | break 268 | else: 269 | next_page_hint = None 270 | maxdepth = 10 271 | if '@page:' in hint: 272 | if '$INFO[' in hint: 273 | m = re.search('\\[(.*?)\]', hint) 274 | info = m.group(1) 275 | hint = hint.replace('$INFO[%s]' % info, xbmc.getInfoLabel(info)) 276 | if '$LOCALIZE[' in hint: 277 | m = re.search('\[(\d+)\]', hint) 278 | id = int(m.group(1)) 279 | hint = hint.replace('$LOCALIZE[%s]' % id, xbmc.getLocalizedString(id).encode('utf-8')) 280 | if '$ADDON[' in hint: 281 | m = re.search('\[.*\ (\d+)\]', hint) 282 | aid = m.group(0).strip('[]').split(' ')[0] 283 | id = int(m.group(1)) 284 | hint = hint.replace('$ADDON[%s %s]' % (aid, id), xbmcaddon.Addon(aid).getLocalizedString(id).encode('utf-8')) 285 | hint, next_page_hint = hint.split('@page:') 286 | if '@depth:' in next_page_hint: 287 | next_page_hint, maxdepth = next_page_hint.split('@depth:') 288 | maxdepth = int(maxdepth) 289 | matched_dirs = [x for x in dirs if Lister._has_match(x, hint, parameters)] 290 | if matched_dirs: 291 | path = matched_dirs[0]['path'] 292 | if i == len(guidance) - 1: 293 | result_files = [x for x in files if Lister._has_match(x, hint, parameters)] 294 | result_dirs = matched_dirs 295 | if next_page_hint and depth < maxdepth and path is None and not result_files: 296 | next_page_dirs = [x for x in dirs if Lister._has_match(x, next_page_hint, parameters)] 297 | if next_page_dirs: 298 | rec_files, rec_dirs = self._browse_external(next_page_dirs[0]['path'], guidance[i:], parameters, unescaped_parameters, depth+1) 299 | result_files += rec_files 300 | result_dirs += rec_dirs 301 | if result_files: 302 | break 303 | result_files = result_files or [] 304 | result_dirs = result_dirs or [] 305 | return result_files, result_dirs -------------------------------------------------------------------------------- /resources/lib/lists.py: -------------------------------------------------------------------------------- 1 | import xbmcplugin 2 | from resources.lib import text 3 | from resources.lib import Trakt 4 | from resources.lib import meta_info 5 | from resources.lib import playrandom 6 | from resources.lib import nav_movies 7 | from resources.lib import nav_tvshows 8 | from resources.lib.xswift2 import plugin 9 | 10 | 11 | SORT = [ 12 | xbmcplugin.SORT_METHOD_UNSORTED, 13 | xbmcplugin.SORT_METHOD_LABEL, 14 | xbmcplugin.SORT_METHOD_VIDEO_YEAR] 15 | 16 | 17 | @plugin.route('/my_trakt/lists/trakt_my_lists') 18 | def lists_trakt_my_lists(): 19 | lists = Trakt.get_lists() 20 | items = [] 21 | for list in lists: 22 | name = list['name'] 23 | user = list['user']['username'] 24 | slug = list['ids']['slug'] 25 | items.append( 26 | { 27 | 'label': name, 28 | 'path': plugin.url_for('lists_trakt_show_list', user=user, slug=slug), 29 | 'thumbnail': plugin.get_media_icon('traktmylists') 30 | }) 31 | for item in items: 32 | item['properties'] = {'fanart_image': plugin.get_addon_fanart()} 33 | return plugin.finish(items=items, sort_methods=SORT) 34 | 35 | @plugin.route('/my_trakt/lists/trakt_liked_lists/') 36 | def lists_trakt_liked_lists(page): 37 | lists, pages = Trakt.get_liked_lists(page) 38 | items = [] 39 | for list in lists: 40 | info = list['list'] 41 | name = info['name'] 42 | user = info['user']['username'] 43 | slug = info['ids']['slug'] 44 | items.append( 45 | { 46 | 'label': name, 47 | 'path': plugin.url_for('lists_trakt_show_list', user=user, slug=slug), 48 | 'thumbnail': plugin.get_media_icon('traktlikedlists') 49 | }) 50 | for item in items: 51 | item['properties'] = {'fanart_image': plugin.get_addon_fanart()} 52 | nextpage = int(page) + 1 53 | if pages > page: 54 | items.append( 55 | { 56 | 'label': '%s/%s [I]Next page[/I] >>' % (nextpage, pages), 57 | 'path': plugin.url_for('lists_trakt_liked_lists', page=int(page) + 1), 58 | 'thumbnail': plugin.get_media_icon('item_next') 59 | }) 60 | return plugin.finish(items=items, sort_methods=SORT) 61 | 62 | @plugin.route('/lists/play_random//') 63 | def lists_trakt_play_random(user, slug): 64 | items = lists_trakt_show_list(user, slug, raw=True) 65 | playrandom.trakt_play_random(items) 66 | 67 | @plugin.route('/my_trakt/lists/show_list//') 68 | def lists_trakt_show_list(user, slug, raw=False): 69 | list_items = Trakt.get_list(user, slug) 70 | if raw: 71 | return list_items 72 | return _lists_trakt_show_list(list_items) 73 | 74 | @plugin.route('/my_trakt/lists/trakt_search_for_lists') 75 | def lists_trakt_search_for_lists(): 76 | term = plugin.keyboard(heading='Enter search string') 77 | if term != None and term != '': 78 | return lists_search_for_lists_term(term, 1) 79 | else: 80 | return 81 | 82 | @plugin.route('/my_trakt/lists/search_for_lists_term//') 83 | def lists_search_for_lists_term(term, page): 84 | lists, pages = Trakt.search_for_list(term, page) 85 | items = [] 86 | for list in lists: 87 | if 'list' in list: 88 | list_info = list['list'] 89 | else: 90 | continue 91 | name = list_info['name'] 92 | user = list_info['username'] 93 | slug = list_info['ids']['slug'] 94 | total = list_info['item_count'] 95 | info = {} 96 | info['title'] = name 97 | if 'description' in list_info: 98 | info['plot'] = list_info['description'] 99 | else: 100 | info['plot'] = 'No description available' 101 | if user != None and total != None and total != 0: 102 | items.append( 103 | { 104 | 'label': '%s - %s (%s)' % (text.to_utf8(name), text.to_utf8(user), total), 105 | 'path': plugin.url_for('lists_trakt_show_list', user=user, slug=slug), 106 | 'thumbnail': plugin.get_media_icon('traktlikedlists'), 107 | 'context_menu': [ 108 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('lists_trakt_play_random', user=user, slug=slug))], 109 | 'info': info 110 | }) 111 | for item in items: 112 | item['properties'] = {'fanart_image': plugin.get_addon_fanart()} 113 | nextpage = int(page) + 1 114 | if pages > page: 115 | items.append( 116 | { 117 | 'label': '%s/%s [I]Next page[/I] >>' % (nextpage, pages), 118 | 'path': plugin.url_for('lists_search_for_lists_term', term = term, page=int(page) + 1), 119 | 'thumbnail': plugin.get_media_icon('item_next'), 120 | 'properties': {'fanart_image': plugin.get_addon_fanart()} 121 | }) 122 | return plugin.finish(items=items, sort_methods=SORT) 123 | 124 | @plugin.route('/my_trakt/lists/_show_list/') 125 | def _lists_trakt_show_list(list_items): 126 | from resources.lib.TheMovieDB import People 127 | genres_dict = Trakt.get_genres('tv') 128 | items = [] 129 | for list_item in list_items: 130 | item = None 131 | item_type = list_item['type'] 132 | if item_type == 'show': 133 | tvdb_id = list_item['show']['ids']['tvdb'] 134 | if tvdb_id != '' and tvdb_id != None: 135 | show = list_item['show'] 136 | info = meta_info.get_tvshow_metadata_trakt(show, genres_dict) 137 | item = nav_tvshows.make_tvshow_item(info) 138 | else: 139 | item = None 140 | elif item_type == 'season': 141 | tvdb_id = list_item['show']['ids']['tvdb'] 142 | season = list_item['season'] 143 | show = list_item['show'] 144 | show_info = meta_info.get_tvshow_metadata_trakt(show, genres_dict) 145 | season_info = meta_info.get_season_metadata_trakt(show_info,season, genres_dict) 146 | label = '%s - Season %s' % (show['title'], season['number']) 147 | item = ( 148 | { 149 | 'label': label, 150 | 'path': plugin.url_for('tv_season', id=tvdb_id, season_num=list_item['season']['number']), 151 | 'info': season_info, 152 | 'thumbnail': season_info['poster'], 153 | 'poster': season_info['poster'], 154 | 'properties': {'fanart_image': season_info['fanart']} 155 | }) 156 | elif item_type == 'episode': 157 | tvdb_id = list_item['show']['ids']['tvdb'] 158 | episode = list_item['episode'] 159 | show = list_item['show'] 160 | season_number = episode['season'] 161 | episode_number = episode['number'] 162 | show_info = meta_info.get_tvshow_metadata_trakt(show, genres_dict) 163 | episode_info = meta_info.get_episode_metadata_trakt(show_info, episode) 164 | label = '%s - S%sE%s - %s' % (show_info['title'], season_number, episode_number, episode_info['title']) 165 | item = ( 166 | { 167 | 'label': label, 168 | 'path': plugin.url_for('tv_play', id=tvdb_id, season=season_number, episode=episode_number), 169 | 'info': episode_info, 170 | 'is_playable': True, 171 | 'info_type': 'video', 172 | 'stream_info': {'video': {}}, 173 | 'thumbnail': episode_info['poster'], 174 | 'poster': episode_info['poster'], 175 | 'properties': {'fanart_image': episode_info['fanart']} 176 | }) 177 | elif item_type == 'movie': 178 | movie = list_item['movie'] 179 | movie_info = meta_info.get_trakt_movie_metadata(movie) 180 | try: 181 | tmdb_id = movie_info['tmdb'] 182 | except: 183 | tmdb_id = '' 184 | try: 185 | imdb_id = movie_info['imdb'] 186 | except: 187 | imdb_id = '' 188 | if tmdb_id != None and tmdb_id != '': 189 | src = 'tmdb' 190 | id = tmdb_id 191 | elif imdb_id != None and mdb_id != '': 192 | src = 'imdb' 193 | id = imdb_id 194 | else: 195 | src = '' 196 | id = '' 197 | if src == '': 198 | item = None 199 | item = nav_movies.make_movie_item(movie_info) 200 | elif item_type == 'person': 201 | person_id = list_item['person']['ids']['trakt'] 202 | person_tmdb_id = list_item['person']['ids']['tmdb'] 203 | try: 204 | person_images = People(person_tmdb_id).images()['profiles'] 205 | person_image = 'https://image.tmdb.org/t/p/w640' + person_images[0]['file_path'] 206 | except: 207 | person_image = '' 208 | person_name = text.to_utf8(list_item['person']['name']) 209 | item = ( 210 | { 211 | 'label': person_name, 212 | 'path': plugin.url_for('trakt_movies_person', person_id=person_id), 213 | 'thumbnail': person_image, 214 | 'poster': person_image, 215 | 'properties': {'fanart_image': person_image} 216 | }) 217 | if item is not None: 218 | items.append(item) 219 | for item in items: 220 | item['properties'] = {'fanart_image': plugin.get_addon_fanart()} 221 | return items -------------------------------------------------------------------------------- /resources/lib/menu_items.py: -------------------------------------------------------------------------------- 1 | from resources.lib import lists 2 | from resources.lib import nav_movies 3 | from resources.lib import nav_tvshows 4 | from resources.lib.xswift2 import plugin 5 | 6 | @plugin.route('/') 7 | def root(): 8 | items = [ 9 | { 10 | 'label': 'Movies', 11 | 'path': plugin.url_for('movies'), 12 | 'thumbnail': plugin.get_media_icon('movies'), 13 | 'fanart': plugin.get_addon_fanart() 14 | }, 15 | { 16 | 'label': 'TV Shows', 17 | 'path': plugin.url_for('tv'), 18 | 'thumbnail': plugin.get_media_icon('tv'), 19 | 'fanart': plugin.get_addon_fanart() 20 | }, 21 | { 22 | 'label': 'My Trakt', 23 | 'path': plugin.url_for('my_trakt'), 24 | 'thumbnail': plugin.get_media_icon('trakt'), 25 | 'fanart': plugin.get_addon_fanart() 26 | }, 27 | { 28 | 'label': 'Search...', 29 | 'path': plugin.url_for('search_term'), 30 | 'thumbnail': plugin.get_media_icon('search'), 31 | 'fanart': plugin.get_addon_fanart() 32 | }, 33 | { 34 | 'label': 'Settings', 35 | 'path': plugin.url_for('open_settings'), 36 | 'thumbnail': plugin.get_media_icon('settings'), 37 | 'fanart': plugin.get_addon_fanart() 38 | }] 39 | return items 40 | 41 | @plugin.route('/movies') 42 | def movies(): 43 | items = [ 44 | { 45 | 'label': 'Blockbusters (TMDB)', 46 | 'path': plugin.url_for('tmdb_movies_blockbusters', page=1), 47 | 'thumbnail': plugin.get_media_icon('most_voted'), 48 | 'fanart': plugin.get_addon_fanart(), 49 | 'context_menu': [ 50 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('tmdb_movies_play_random_blockbuster'))] 51 | }, 52 | { 53 | 'label': 'In theatres (TMDB)', 54 | 'path': plugin.url_for('tmdb_movies_now_playing', page=1), 55 | 'thumbnail': plugin.get_media_icon('intheatres'), 56 | 'fanart': plugin.get_addon_fanart(), 57 | 'context_menu': [ 58 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('tmdb_movies_play_random_now_playing'))] 59 | }, 60 | { 61 | 'label': 'Popular (TMDB)', 62 | 'path': plugin.url_for('tmdb_movies_popular', page=1), 63 | 'thumbnail': plugin.get_media_icon('popular'), 64 | 'fanart': plugin.get_addon_fanart(), 65 | 'context_menu': [ 66 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('tmdb_movies_play_random_popular'))] 67 | }, 68 | { 69 | 'label': 'Top rated (TMDB)', 70 | 'path': plugin.url_for('tmdb_movies_top_rated', page=1), 71 | 'thumbnail': plugin.get_media_icon('top_rated'), 72 | 'fanart': plugin.get_addon_fanart(), 73 | 'context_menu': [ 74 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('tmdb_movies_play_random_top_rated'))] 75 | }, 76 | { 77 | 'label': 'Most watched (Trakt)', 78 | 'path': plugin.url_for('trakt_movies_watched', page=1), 79 | 'thumbnail': plugin.get_media_icon('traktwatchlist'), 80 | 'fanart': plugin.get_addon_fanart(), 81 | 'context_menu': [ 82 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('trakt_movies_play_random_watched'))] 83 | }, 84 | { 85 | 'label': 'Most collected (Trakt)', 86 | 'path': plugin.url_for('trakt_movies_collected', page=1), 87 | 'thumbnail': plugin.get_media_icon('traktcollection'), 88 | 'fanart': plugin.get_addon_fanart(), 89 | 'context_menu': [ 90 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('trakt_movies_play_random_collected'))] 91 | }, 92 | { 93 | 'label': 'Popular (Trakt)', 94 | 'path': plugin.url_for('trakt_movies_popular', page=1), 95 | 'thumbnail': plugin.get_media_icon('traktrecommendations'), 96 | 'fanart': plugin.get_addon_fanart(), 97 | 'context_menu': [ 98 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('trakt_movies_play_random_popular'))] 99 | }, 100 | { 101 | 'label': 'Trending (Trakt)', 102 | 'path': plugin.url_for('trakt_movies_trending', page=1), 103 | 'thumbnail': plugin.get_media_icon('trending'), 104 | 'fanart': plugin.get_addon_fanart(), 105 | 'context_menu': [ 106 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('trakt_movies_play_random_trending'))] 107 | }, 108 | { 109 | 'label': 'Latest releases (Trakt)', 110 | 'path': plugin.url_for('trakt_movies_latest_releases'), 111 | 'thumbnail': plugin.get_media_icon('traktcalendar'), 112 | 'fanart': plugin.get_addon_fanart(), 113 | 'context_menu': [ 114 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('trakt_movies_play_random_latest_releases'))] 115 | }, 116 | { 117 | 'label': 'Top 250 (IMDB)', 118 | 'path': plugin.url_for('trakt_movies_imdb_top_rated', page=1), 119 | 'thumbnail': plugin.get_media_icon('imdb'), 120 | 'fanart': plugin.get_addon_fanart(), 121 | 'context_menu': [ 122 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('trakt_movies_play_random_imdb_top_rated'))] 123 | }, 124 | { 125 | 'label': 'Genres', 126 | 'path': plugin.url_for('tmdb_movies_genres'), 127 | 'thumbnail': plugin.get_media_icon('genres'), 128 | 'fanart': plugin.get_addon_fanart() 129 | }] 130 | return items 131 | 132 | @plugin.route('/tv') 133 | def tv(): 134 | items = [ 135 | { 136 | 'label': 'Currently Airing (TMDB)', 137 | 'path': plugin.url_for('tmdb_tv_on_the_air', page=1), 138 | 'thumbnail': plugin.get_media_icon('ontheair'), 139 | 'fanart': plugin.get_addon_fanart() 140 | }, 141 | { 142 | 'label': 'Popular (TMDB)', 143 | 'path': plugin.url_for('tmdb_tv_most_popular', page=1), 144 | 'thumbnail': plugin.get_media_icon('popular'), 145 | 'fanart': plugin.get_addon_fanart() 146 | }, 147 | { 148 | 'label': 'Most Watched (Trakt)', 149 | 'path': plugin.url_for('trakt_tv_watched', page=1), 150 | 'thumbnail': plugin.get_media_icon('traktwatchlist'), 151 | 'fanart': plugin.get_addon_fanart() 152 | }, 153 | { 154 | 'label': 'Most Collected (Trakt)', 155 | 'path': plugin.url_for('trakt_tv_collected', page=1), 156 | 'thumbnail': plugin.get_media_icon('traktcollection'), 157 | 'fanart': plugin.get_addon_fanart() 158 | }, 159 | { 160 | 'label': 'Most Collected Netflix (Trakt)', 161 | 'path': plugin.url_for('trakt_netflix_tv_collected', page=1), 162 | 'thumbnail': plugin.get_media_icon('traktcollection'), 163 | 'fanart': plugin.get_addon_fanart() 164 | }, 165 | { 166 | 'label': 'Most Popular (Trakt)', 167 | 'path': plugin.url_for('tv_trakt_popular', page=1), 168 | 'thumbnail': plugin.get_media_icon('traktrecommendations'), 169 | 'fanart': plugin.get_addon_fanart() 170 | }, 171 | { 172 | 'label': 'Trending (Trakt)', 173 | 'path': plugin.url_for('trakt_tv_trending', page=1), 174 | 'thumbnail': plugin.get_media_icon('trending'), 175 | 'fanart': plugin.get_addon_fanart() 176 | }, 177 | { 178 | 'label': 'Genres', 179 | 'path': plugin.url_for('tmdb_tv_genres'), 180 | 'thumbnail': plugin.get_media_icon('genres'), 181 | 'fanart': plugin.get_addon_fanart() 182 | }] 183 | return items 184 | 185 | @plugin.route('/my_trakt') 186 | def my_trakt(): 187 | items = [ 188 | { 189 | 'label': 'Movies', 190 | 'path': plugin.url_for('movie_lists'), 191 | 'thumbnail': plugin.get_media_icon('movies'), 192 | 'fanart': plugin.get_addon_fanart() 193 | }, 194 | { 195 | 'label': 'TV Shows', 196 | 'path': plugin.url_for('tv_lists'), 197 | 'thumbnail': plugin.get_media_icon('tv'), 198 | 'fanart': plugin.get_addon_fanart() 199 | }, 200 | { 201 | 'label': 'Lists (Movies & TV Shows)', 202 | 'path': plugin.url_for('lists'), 203 | 'thumbnail': plugin.get_media_icon('traktmylists'), 204 | 'fanart': plugin.get_addon_fanart() 205 | }] 206 | return items 207 | 208 | @plugin.route('/my_trakt/movie_lists') 209 | def movie_lists(): 210 | items = [ 211 | { 212 | 'label': 'Collection', 213 | 'path': plugin.url_for('lists_trakt_movies_collection'), 214 | 'thumbnail': plugin.get_media_icon('traktcollection'), 215 | 'fanart': plugin.get_addon_fanart(), 216 | 'context_menu': [ 217 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('lists_trakt_movies_play_random_collection')), 218 | ('Add to library', 'RunPlugin(%s)' % plugin.url_for('lists_trakt_movies_collection_to_library'))] 219 | }, 220 | { 221 | 'label': 'Recommendations', 222 | 'path': plugin.url_for('trakt_movies_recommendations'), 223 | 'thumbnail': plugin.get_media_icon('traktrecommendations'), 224 | 'fanart': plugin.get_addon_fanart() 225 | }, 226 | { 227 | 'label': 'Watchlist', 228 | 'path': plugin.url_for('trakt_movies_watchlist'), 229 | 'thumbnail': plugin.get_media_icon('traktwatchlist'), 230 | 'fanart': plugin.get_addon_fanart(), 231 | 'context_menu': [ 232 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('trakt_movies_play_random_watchlist'))] 233 | }, 234 | { 235 | 'label': 'My Lists', 236 | 'path': plugin.url_for('lists_trakt_my_movie_lists'), 237 | 'thumbnail': plugin.get_media_icon('traktmylists'), 238 | 'fanart': plugin.get_addon_fanart() 239 | }, 240 | { 241 | 'label': 'Liked Lists', 242 | 'path': plugin.url_for('lists_trakt_liked_movie_lists', page=1), 243 | 'thumbnail': plugin.get_media_icon('traktlikedlists'), 244 | 'fanart': plugin.get_addon_fanart() 245 | }] 246 | return items 247 | 248 | @plugin.route('/my_trakt/tv_lists') 249 | def tv_lists(): 250 | items = [ 251 | { 252 | 'label': 'Collection', 253 | 'path': plugin.url_for('lists_trakt_tv_collection'), 254 | 'thumbnail': plugin.get_media_icon('traktcollection'), 255 | 'fanart': plugin.get_addon_fanart(), 256 | 'context_menu': [ 257 | ('Add to library', 'RunPlugin(%s)' % plugin.url_for('lists_trakt_tv_collection_to_library'))] 258 | }, 259 | { 260 | 'label': 'Recommendations', 261 | 'path': plugin.url_for('trakt_tv_recommendations'), 262 | 'thumbnail': plugin.get_media_icon('traktrecommendations'), 263 | 'fanart': plugin.get_addon_fanart() 264 | }, 265 | { 266 | 'label': 'Watchlist', 267 | 'path': plugin.url_for('trakt_tv_watchlist', page = 1), 268 | 'thumbnail': plugin.get_media_icon('traktwatchlist'), 269 | 'fanart': plugin.get_addon_fanart() 270 | }, 271 | { 272 | 'label': 'My Lists', 273 | 'path': plugin.url_for('lists_trakt_my_tv_lists'), 274 | 'thumbnail': plugin.get_media_icon('traktmylists'), 275 | 'fanart': plugin.get_addon_fanart() 276 | }, 277 | { 278 | 'label': 'Liked Lists', 279 | 'path': plugin.url_for('lists_trakt_liked_tv_lists', page=1), 280 | 'thumbnail': plugin.get_media_icon('traktlikedlists'), 281 | 'fanart': plugin.get_addon_fanart() 282 | }, 283 | { 284 | 'label': 'Next Episodes', 285 | 'path': plugin.url_for('trakt_tv_next_episodes'), 286 | 'thumbnail': plugin.get_media_icon('traktnextepisodes'), 287 | 'fanart': plugin.get_addon_fanart() 288 | }, 289 | { 290 | 'label': 'Upcoming Episodes', 291 | 'path': plugin.url_for('trakt_tv_upcoming_episodes'), 292 | 'thumbnail': plugin.get_media_icon('traktcalendar'), 293 | 'fanart': plugin.get_addon_fanart() 294 | }] 295 | return items 296 | 297 | @plugin.route('/my_trakt/lists') 298 | def lists(): 299 | items = [ 300 | { 301 | 'label': 'My Lists', 302 | 'path': plugin.url_for('lists_trakt_my_lists'), 303 | 'thumbnail': plugin.get_media_icon('traktmylists'), 304 | 'fanart': plugin.get_addon_fanart() 305 | }, 306 | { 307 | 'label': 'Liked Lists', 308 | 'path': plugin.url_for('lists_trakt_liked_lists', page=1), 309 | 'thumbnail': plugin.get_media_icon('traktlikedlists'), 310 | 'fanart': plugin.get_addon_fanart() 311 | }] 312 | return items 313 | 314 | @plugin.route('/search') 315 | def search_term(): 316 | term = plugin.keyboard(heading='Enter search string') 317 | if term != None and term != '': 318 | return search(term) 319 | else: 320 | return 321 | 322 | @plugin.route('/search/edit/') 323 | def search_edit(term): 324 | if term == ' ' or term == None or term == '': 325 | term = plugin.keyboard(heading='Enter search string') 326 | else: 327 | term = plugin.keyboard(default=term, heading='Enter search string') 328 | if term != None and term != '': 329 | return search(term) 330 | else: 331 | return 332 | 333 | @plugin.route('/search/', options = {'term': 'None'}) 334 | def search(term): 335 | items = [ 336 | { 337 | 'label': 'Movies (TMDB) search - ' + term, 338 | 'path': plugin.url_for('tmdb_movies_search_term', term=term, page=1), 339 | 'thumbnail': plugin.get_media_icon('search'), 340 | 'fanart': plugin.get_addon_fanart() 341 | }, 342 | { 343 | 'label': 'Movies (Trakt) search - ' + term, 344 | 'path': plugin.url_for('trakt_movies_search_term', term=term, page=1), 345 | 'thumbnail': plugin.get_media_icon('search'), 346 | 'fanart': plugin.get_addon_fanart() 347 | }, 348 | { 349 | 'label': 'TV shows (TVDB) search - ' + term, 350 | 'path': plugin.url_for('tvdb_tv_search_term', term=term, page=1), 351 | 'thumbnail': plugin.get_media_icon('search'), 352 | 'fanart': plugin.get_addon_fanart() 353 | }, 354 | { 355 | 'label': 'TV shows (Trakt) search - ' + term, 356 | 'path': plugin.url_for('trakt_tv_search_term', term=term, page=1), 357 | 'thumbnail': plugin.get_media_icon('search'), 358 | 'fanart': plugin.get_addon_fanart() 359 | }, 360 | { 361 | 'label': 'Lists (Trakt) search - ' + term, 362 | 'path': plugin.url_for('lists_search_for_lists_term', term=term, page=1), 363 | 'thumbnail': plugin.get_media_icon('search'), 364 | 'fanart': plugin.get_addon_fanart(), 365 | }, 366 | { 367 | 'label': 'Edit search string', 368 | 'path': plugin.url_for('search_edit', term=term), 369 | 'thumbnail': plugin.get_media_icon('search'), 370 | 'fanart': plugin.get_addon_fanart() 371 | }] 372 | return items -------------------------------------------------------------------------------- /resources/lib/meta_info.py: -------------------------------------------------------------------------------- 1 | import re, copy 2 | from resources.lib import text 3 | 4 | def make_trailer(trailer_url): 5 | match = re.search('\?v=(.*)', trailer_url) 6 | if match: 7 | return 'plugin://plugin.video.youtube/play/?video_id=%s' % match.group(1) 8 | 9 | def get_movie_metadata(movie, genres_dict=None): 10 | info = {} 11 | info['title'] = movie['title'] 12 | info['year'] = text.parse_year(movie['release_date']) 13 | info['premiered'] = movie['release_date'] 14 | info['name'] = u'%s (%s)' % (info['title'], info['year']) 15 | info['rating'] = movie['vote_average'] 16 | info['votes'] = movie['vote_count'] 17 | info['plot'] = movie['overview'] 18 | info['originaltitle'] = movie['original_title'] 19 | info['tmdb'] = str(movie['id']) 20 | info['poster'] = u'https://image.tmdb.org/t/p/original%s' % movie['poster_path'] 21 | info['fanart'] = u'https://image.tmdb.org/t/p/original%s' % movie['backdrop_path'] 22 | info['mediatype'] = 'movie' 23 | try: 24 | info['genre'] = u' / '.join([x['name'] for x in movie['genres']]) 25 | except KeyError: 26 | if genres_dict: 27 | info['genre'] = u' / '.join([genres_dict[x] for x in movie['genre_ids']]) 28 | else: 29 | info['genre'] = '' 30 | return info 31 | 32 | def get_trakt_movie_metadata(movie, genres_dict=None): 33 | info = {} 34 | info['title'] = movie['title'] 35 | info['year'] = movie['year'] 36 | info['name'] = u'%s (%s)' % (info['title'], info['year']) 37 | info['premiered'] = movie.get('released') 38 | info['rating'] = movie.get('rating') 39 | info['votes'] = movie.get('votes') 40 | info['tagline'] = movie.get('tagline') 41 | info['plot'] = movie.get('overview') 42 | info['duration'] = 60 * (movie.get('runtime') or 0) 43 | info['mpaa'] = movie.get('certification') 44 | info['playcount'] = movie.get('plays') 45 | if not info['playcount'] and movie.get('watched'): 46 | info['playcount'] = 1 47 | info['tmdb'] = movie['ids'].get('tmdb') 48 | info['trakt_id'] = movie['ids'].get('trakt') 49 | info['imdbnumber'] = movie['ids'].get('imdb') 50 | info['imdb_id'] = movie['ids'].get('imdb') 51 | info['mediatype'] = 'movie' 52 | if info['tmdb'] == None: 53 | info['tmdb'] = '' 54 | if info['trakt_id'] == None: 55 | info['trakt_id'] = '' 56 | if info['imdb_id'] == None: 57 | info['imdb_id'] = '' 58 | images = item_images('movie', tmdb_id=info['tmdb'], imdb_id=info['imdb_id'], name=info['title']) 59 | info['poster'] = images[0] 60 | info['fanart'] = images[1] 61 | if 'genres' in movie: 62 | if genres_dict: 63 | info['genre'] = ' / '.join([genres_dict[x] for x in movie['genres']]) 64 | else: 65 | info['genre'] = '' 66 | if movie.get('trailer'): 67 | info['trailer'] = make_trailer(movie['trailer']) 68 | return info 69 | 70 | def get_tvshow_metadata_trakt(show, genres_dict=None): 71 | info = {} 72 | info['mediatype'] = 'tvshow' 73 | info['title'] = show['title'] 74 | info['year'] = show['year'] 75 | info['name'] = u'%s (%s)' % (info['title'], info['year']) 76 | info['tvshowtitle'] = info['title'] 77 | info['premiered'] = show.get('released') 78 | info['rating'] = show.get('rating') 79 | info['votes'] = show.get('votes') 80 | info['tagline'] = show.get('tagline') 81 | info['plot'] = show.get('overview') 82 | info['studio'] = show.get('network','') 83 | info['mpaa'] = show.get('certification') 84 | info['playcount'] = show.get('plays') 85 | if not info['playcount'] and show.get('watched'): 86 | info['playcount'] = 1 87 | info['tmdb'] = show['ids'].get('tmdb') 88 | info['trakt_id'] = show['ids'].get('trakt') 89 | info['imdb_id'] = show['ids'].get('imdb') 90 | info['tvdb_id'] = show['ids'].get('tvdb') 91 | if info['tmdb'] == None: 92 | info['tmdb'] = '' 93 | if info['trakt_id'] == None: 94 | info['trakt_id'] = '' 95 | if info['imdb_id'] == None: 96 | info['imdb_id'] = '' 97 | if info['tvdb_id'] == None: 98 | info['tvdb_id'] = '' 99 | images = item_images('tv', tmdb_id=info['tmdb'], imdb_id=info['imdb_id'], tvdb_id=info['tvdb_id'], name=info['title']) 100 | info['poster'] = images[0] 101 | info['fanart'] = images[1] 102 | if genres_dict: 103 | try: 104 | info['genre'] = u' / '.join([genres_dict[x] for x in show['genres']]) 105 | except: 106 | pass 107 | if show.get('trailer'): 108 | info['trailer'] = make_trailer(show['trailer']) 109 | return info 110 | 111 | def get_tvshow_metadata_tvdb(tvdb_show, banners=True): 112 | info = {} 113 | if tvdb_show is None: 114 | return info 115 | if tvdb_show['genre']: 116 | if '|' in tvdb_show['genre']: 117 | genres = tvdb_show['genre'].replace('|',' / ') 118 | info['genre'] = genres[3:-3] 119 | info['tvdb_id'] = str(tvdb_show['id']) 120 | info['name'] = tvdb_show['seriesname'] 121 | info['title'] = tvdb_show['seriesname'] 122 | info['tvshowtitle'] = tvdb_show['seriesname'] 123 | info['originaltitle'] = tvdb_show['seriesname'] 124 | info['plot'] = tvdb_show.get('overview', '') 125 | if banners: 126 | info['poster'] = tvdb_show.get_poster(language='en') 127 | info['fanart'] = tvdb_show.get('fanart', '') 128 | info['rating'] = tvdb_show.get('rating') 129 | info['votes'] = tvdb_show.get('ratingcount') 130 | info['year'] = tvdb_show.get('year', 0) 131 | info['studio'] = tvdb_show.get('network','') 132 | info['imdb_id'] = tvdb_show.get('imdb_id', '') 133 | info['duration'] = int(tvdb_show.get('runtime') or 0) * 60 134 | info['mediatype'] = 'tvshow' 135 | return info 136 | 137 | def get_tvshow_metadata_tmdb(show, genres_dict=None): 138 | info = {} 139 | if show is None: 140 | return info 141 | if 'id' in show: 142 | info['tmdb'] = str(show['id']) 143 | info['mediatype'] = 'tvshow' 144 | info['name'] = show['name'] 145 | info['title'] = show['name'] 146 | info['tvshowtitle'] = show['original_name'] 147 | info['originaltitle'] = show['original_name'] 148 | info['plot'] = show['overview'] 149 | info['rating'] = str(show['vote_average']) 150 | info['votes'] = str(show['vote_count']) 151 | try: 152 | info['genre'] = u' / '.join([x['name'] for x in show['genres']]) 153 | except KeyError: 154 | if genres_dict: 155 | try: 156 | info['genre'] = u' / '.join([genres_dict[x] for x in show['genre_ids']]) 157 | except: 158 | info['genre'] = '' 159 | info['poster'] = u'https://image.tmdb.org/t/p/original%s' % show['poster_path'] 160 | info['fanart'] = u'https://image.tmdb.org/t/p/original%s' % show['backdrop_path'] 161 | return info 162 | 163 | def get_tvshow_metadata_tvmaze(show): 164 | info = {} 165 | if show is None: 166 | return info 167 | if show['externals']['thetvdb'] is not None: 168 | info['id'] = show['externals']['thetvdb'] 169 | if show['externals']['imdb'] is not None: 170 | info['imdb'] = show['externals']['imdb'] 171 | info['mediatype'] = 'tvshow' 172 | info['name'] = show['name'] 173 | info['title'] = show['name'] 174 | info['tvshowtitle'] = show['name'] 175 | info['originaltitle'] = show['name'] 176 | info['plot'] = re.sub(r'\<[^)].*?\>', '', show['summary']).replace('&','&').replace('\t','') 177 | info['rating'] = str(show['rating']['average']) 178 | info['votes'] = str(show['weight']) 179 | info['genre'] = show['type'] 180 | if show['image']['original']: 181 | info['poster'] = show['image']['original'] 182 | return info 183 | 184 | def get_season_metadata_tvdb(show_metadata, season, banners=True): 185 | info = copy.deepcopy(show_metadata) 186 | del info['title'] 187 | info['season'] = season.num 188 | if banners: 189 | info['poster'] = season.get_poster(language='en') 190 | return info 191 | 192 | def get_season_metadata_tmdb(show_metadata, season): 193 | info = copy.deepcopy(show_metadata) 194 | del info['name'] 195 | info['season'] = season['season_number'] 196 | if season['images']['posters']: 197 | info['poster'] = season['images']['posters'][0] 198 | if show_metadata['fanart']: 199 | info['fanart'] = show_metadata['fanart'] 200 | else: 201 | info['fanart'] = '' 202 | return info 203 | 204 | def get_season_metadata_trakt(show_metadata, season, banners=True): 205 | info = copy.deepcopy(show_metadata) 206 | del info['title'] 207 | info['season'] = season['number'] 208 | if not info['playcount'] and season.get('watched'): 209 | info['playcount'] = 1 210 | return info 211 | 212 | def get_season_metadata_tvmaze(show_metadata, season): 213 | info = copy.deepcopy(show_metadata) 214 | del info['name'] 215 | info['season'] = season['number'] 216 | return info 217 | 218 | def get_episode_metadata_tvdb(season_metadata, episode, banners=True): 219 | info = copy.deepcopy(season_metadata) 220 | info['season'] = int(episode['seasonnumber']) 221 | info['episode'] = int(episode.get('episodenumber')) 222 | info['name'] = episode.get('episodename','') 223 | info['title'] = u'%02dx%02d. %s' % (info['season'], info['episode'], info['name']) 224 | info['aired'] = episode.get('firstaired','') 225 | info['premiered'] = episode.get('firstaired','') 226 | info['rating'] = episode.get('rating', '') 227 | info['plot'] = episode.get('overview','') 228 | info['plotoutline'] = episode.get('overview','') 229 | info['votes'] = episode.get('ratingcount','') 230 | info['mediatype'] = 'episode' 231 | if banners: 232 | info['poster'] = episode.get('filename', '') 233 | return info 234 | 235 | def get_episode_metadata_tmdb(season_metadata, episode): 236 | info = copy.deepcopy(season_metadata) 237 | if episode == None or episode == '' or 'status_code' in str(episode): 238 | return info 239 | info['season'] = episode['season_number'] 240 | info['episode'] = episode['episode_number'] 241 | info['title'] = episode['name'] 242 | info['aired'] = episode['air_date'] 243 | info['premiered'] = episode['air_date'] 244 | info['rating'] = episode['vote_average'] 245 | info['plot'] = episode['overview'] 246 | info['plotoutline'] = episode['overview'] 247 | info['votes'] = episode['vote_count'] 248 | info['mediatype'] = 'episode' 249 | if episode['still_path']: 250 | info['poster'] = u'https://image.tmdb.org/t/p/original%s' % episode['still_path'] 251 | elif season_metadata['poster']: 252 | info['poster'] = u'https://image.tmdb.org/t/p/original%s' % season_metadata['poster'] 253 | else: 254 | info['poster'] = '' 255 | if season_metadata['fanart']: 256 | info['fanart'] = season_metadata['fanart'] 257 | else: 258 | info['fanart'] = '' 259 | return info 260 | 261 | def get_episode_metadata_trakt(season_metadata, episode): 262 | info = copy.deepcopy(season_metadata) 263 | info['season'] = episode['season'] 264 | info['episode'] = episode.get('number') 265 | info['title'] = episode.get('title','') 266 | info['aired'] = episode.get('first_aired','') 267 | info['premiered'] = episode.get('first_aired','') 268 | info['rating'] = episode.get('rating', '') 269 | info['plot'] = episode.get('overview','') 270 | info['plotoutline'] = episode.get('overview','') 271 | info['votes'] = episode.get('votes','') 272 | info['mediatype'] = 'episode' 273 | if not info['playcount'] and episode.get('watched'): 274 | info['playcount'] = 1 275 | return info 276 | 277 | def get_episode_metadata_tvmaze(season_metadata, episode): 278 | info = copy.deepcopy(season_metadata) 279 | if episode == None or episode == '': 280 | return info 281 | info['season'] = episode['season'] 282 | info['episode'] = episode['number'] 283 | info['season'] = episode['season'] 284 | info['title'] = episode['name'] 285 | info['aired'] = episode['airdate'] 286 | info['premiered'] = episode['airdate'] 287 | info['mediatype'] = 'episode' 288 | info['plot'] = re.sub(r'\<[^)].*?\>', '', str(episode['summary'])).replace('&','&').replace('\t','') 289 | info['plotoutline'] = re.sub(r'\<[^)].*?\>', '', str(episode['summary'])).replace('&','&').replace('\t','') 290 | if episode['image']: 291 | info['poster'] = episode['image']['original'] 292 | return info 293 | 294 | def item_images(type, tmdb_id=None, imdb_id=None, tvdb_id=None, name=None): 295 | from resources.lib.TheMovieDB import Movies, TV, Find 296 | poster = '' 297 | fanart = '' 298 | response = '' 299 | if not tmdb_id and not imdb_id and not tvdb_id and not tvrage_id and not name: 300 | return None 301 | if type == 'movie' and tmdb_id != None and tmdb_id != '': 302 | response = Movies(tmdb_id).info() 303 | elif type == 'tv' and tmdb_id != None and tmdb_id != '': 304 | response = TV(tmdb_id).info() 305 | elif type == 'tv' and tvdb_id != None and tvdb_id != '': 306 | response = Find(tvdb_id).info(external_source='tvdb_id') 307 | elif imdb_id != None and imdb_id != '': 308 | response = Find(imdb_id).info(external_source='imdb_id') 309 | if response == '': 310 | return False 311 | if tmdb_id == None: 312 | if type == 'movie': 313 | response = response.get('movie_results') 314 | elif type == 'tv': 315 | response = response.get('tv_results') 316 | elif type == 'season': 317 | response = response.get('season_results') 318 | elif type == 'episode': 319 | response = response.get('episode_results') 320 | if isinstance(response, dict): 321 | fanart = 'https://image.tmdb.org/t/p/original/%s' % response.get('backdrop_path') 322 | poster = 'https://image.tmdb.org/t/p/original/%s' % response.get('poster_path') 323 | elif isinstance(response, list): 324 | fanart = 'https://image.tmdb.org/t/p/original/%s' % response['backdrop_path'] 325 | poster = 'https://image.tmdb.org/t/p/original/%s' % response['poster_path'] 326 | images = [poster, fanart] 327 | return images 328 | -------------------------------------------------------------------------------- /resources/lib/meta_players.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import xbmc 4 | import xbmcgui 5 | import xbmcvfs 6 | from resources.lib.text import to_unicode 7 | 8 | class AddonPlayer(object): 9 | def __init__(self, filename, media, meta): 10 | self.media = media 11 | self.title = meta['name'] 12 | self.clean_title = re.compile(r'\[/?(?:color|b|i|u).*?\]', re.I|re.UNICODE).sub('', self.title) 13 | self.pluginid = meta.get('plugin') 14 | self.id = meta.get('id', filename.replace('.json', '')) 15 | self.order = meta.get('priority') 16 | self.commands = meta.get(media, []) 17 | self.filters = meta.get('filters', {}) 18 | self._postprocess = meta.get('postprocess') 19 | 20 | def postprocess(self, link): 21 | code = self._postprocess 22 | if not code or not isinstance(code, basestring) or '__' in code: 23 | return link 24 | link = eval(code, {'__builtins__': {}, 'link': link}) 25 | return link 26 | 27 | def is_empty(self): 28 | if self.pluginid and ',' in self.pluginid: 29 | PLUGINS = [xbmc.getCondVisibility('System.HasAddon(%s)' % p) for p in self.pluginid.split(',')] 30 | if False in PLUGINS: 31 | return True 32 | elif self.pluginid and not xbmc.getCondVisibility('System.HasAddon(%s)' % self.pluginid): 33 | return True 34 | return not bool(self.commands) 35 | 36 | def get_players(media, filters={}): 37 | assert media in ('tvshows', 'movies') 38 | players = [] 39 | players_path = 'special://profile/addon_data/plugin.video.openmeta/Players/' 40 | files = [x for x in xbmcvfs.listdir(players_path)[1] if x.endswith('.json')] 41 | for file in files: 42 | path = players_path + file 43 | try: 44 | f = xbmcvfs.File(path) 45 | try: 46 | content = f.read() 47 | meta = json.loads(content) 48 | finally: 49 | f.close() 50 | player = AddonPlayer(file, media, meta) 51 | if not player.is_empty(): 52 | players.append(player) 53 | except: 54 | xbmcgui.Dialog().ok('Invalid player', 'player %s is invalid' % file) 55 | return sort_players(players, filters) 56 | 57 | def sort_players(players, filters={}): 58 | result = [] 59 | for player in players: 60 | filtered = False 61 | checked = False 62 | for filter_key, filter_value in filters.items(): 63 | value = player.filters.get(filter_key) 64 | if value: 65 | checked = True 66 | if to_unicode(value) != to_unicode(filter_value): 67 | filtered = True 68 | if not filtered: 69 | needs_browsing = False 70 | for command_group in player.commands: 71 | for command in command_group: 72 | if command.get('steps'): 73 | needs_browsing = True 74 | break 75 | result.append((not checked, needs_browsing, player.order, player.clean_title.lower(), player)) 76 | result.sort() 77 | return [x[-1] for x in result] 78 | 79 | def get_needed_langs(players): 80 | languages = set() 81 | for player in players: 82 | for command_group in player.commands: 83 | for command in command_group: 84 | command_lang = command.get('language', 'en') 85 | languages.add(command_lang) 86 | return languages 87 | 88 | ADDON_SELECTOR = AddonPlayer('selector', 'any', meta = {'name': 'Selector'}) -------------------------------------------------------------------------------- /resources/lib/nav_base.py: -------------------------------------------------------------------------------- 1 | import sys, inspect 2 | from resources.lib.xswift2 import plugin 3 | 4 | def get_genre_icon(genre_id): 5 | genre_id = int(genre_id) 6 | icons = { 7 | 12 : 'genre_adventure', 8 | 14 : 'genre_fantasy', 9 | 16 : 'genre_animation', 10 | 18 : 'genre_drama', 11 | 27 : 'genre_horror', 12 | 28 : 'genre_action', 13 | 35 : 'genre_comedy', 14 | 36 : 'genre_history', 15 | 37 : 'genre_western', 16 | 53 : 'genre_thriller', 17 | 80 : 'genre_crime', 18 | 99 : 'genre_documentary', 19 | 878 : 'genre_scifi', 20 | 9648 : 'genre_mystery', 21 | 10402: 'genre_music', 22 | 10749: 'genre_romance', 23 | 10751: 'genre_family', 24 | 10752: 'genre_war', 25 | 10759: 'genre_action', 26 | 10762: 'genre_kids', 27 | 10763: 'genre_news', 28 | 10764: 'genre_reality', 29 | 10765: 'genre_scifi', 30 | 10766: 'genre_soap', 31 | 10767: 'genre_talk', 32 | 10768: 'genre_war', 33 | 10769: 'genre_foreign', 34 | 10770: 'genre_tv' 35 | } 36 | if genre_id in icons: 37 | return plugin.get_media_icon(icons[genre_id]) 38 | return 'DefaultVideo.png' 39 | 40 | def tmdb_movie_genres_mock(): 41 | mock = [ 42 | { 43 | 'id': 28, 44 | 'name': 'Action' 45 | }, 46 | { 47 | 'id': 12, 48 | 'name': 'Adventure' 49 | }, 50 | { 51 | 'id': 16, 52 | 'name': 'Animation' 53 | }, 54 | { 55 | 'id': 35, 56 | 'name': 'Comedy' 57 | }, 58 | { 59 | 'id': 80, 60 | 'name': 'Crime' 61 | }, 62 | { 63 | 'id': 99, 64 | 'name': 'Documentary' 65 | }, 66 | { 67 | 'id': 18, 68 | 'name': 'Drama' 69 | }, 70 | { 71 | 'id': 10751, 72 | 'name': 'Family' 73 | }, 74 | { 75 | 'id': 14, 76 | 'name': 'Fantasy' 77 | }, 78 | { 79 | 'id': 36, 80 | 'name': 'History' 81 | }, 82 | { 83 | 'id': 27, 84 | 'name': 'Horror' 85 | }, 86 | { 87 | 'id': 10402, 88 | 'name': 'Music' 89 | }, 90 | { 91 | 'id': 9648, 92 | 'name': 'Mystery' 93 | }, 94 | { 95 | 'id': 10749, 96 | 'name': 'Romance' 97 | }, 98 | { 99 | 'id': 878, 100 | 'name': 'Science Fiction' 101 | }, 102 | { 103 | 'id': 10770, 104 | 'name': 'TV Movie' 105 | }, 106 | { 107 | 'id': 53, 108 | 'name': 'Thriller' 109 | }, 110 | { 111 | 'id': 10752, 112 | 'name': 'War' 113 | }, 114 | { 115 | 'id': 37, 116 | 'name': 'Western' 117 | }] 118 | return dict([(i['id'], i['name'], i['properties']) for i in mock]) 119 | 120 | def tmdb_tv_genres_mock(): 121 | mock = [ 122 | { 123 | 'id': 10759, 124 | 'name': 'Action & Adventure' 125 | }, 126 | { 127 | 'id': 16, 128 | 'name': 'Animation' 129 | }, 130 | { 131 | 'id': 35, 132 | 'name': 'Comedy' 133 | }, 134 | { 135 | 'id': 80, 136 | 'name': 'Crime' 137 | }, 138 | { 139 | 'id': 99, 140 | 'name': 'Documentary' 141 | }, 142 | { 143 | 'id': 18, 144 | 'name': 'Drama' 145 | }, 146 | { 147 | 'id': 10751, 148 | 'name': 'Family' 149 | }, 150 | { 151 | 'id': 10762, 152 | 'name': 'Kids' 153 | }, 154 | { 155 | 'id': 9648, 156 | 'name': 'Mystery' 157 | }, 158 | { 159 | 'id': 10763, 160 | 'name': 'News' 161 | }, 162 | { 163 | 'id': 10764, 164 | 'name': 'Reality' 165 | }, 166 | { 167 | 'id': 10765, 168 | 'name': 'Sci-Fi & Fantasy' 169 | }, 170 | { 171 | 'id': 10766, 172 | 'name': 'Soap' 173 | }, 174 | { 175 | 'id': 10767, 176 | 'name': 'Talk' 177 | }, 178 | { 179 | 'id': 10768, 180 | 'name': 'War & Politics' 181 | }, 182 | { 183 | 'id': 37, 184 | 'name': 'Western' 185 | }] 186 | return dict([(i['id'], i['name']) for i in mock]) 187 | 188 | def caller_name(): 189 | return sys._getframe(2).f_code.co_name 190 | 191 | def caller_args(): 192 | caller = inspect.stack()[2][0] 193 | args, _, _, values = inspect.getargvalues(caller) 194 | return dict([(i, values[i]) for i in args]) 195 | 196 | def get_genres(): 197 | result = get_base_genres() 198 | result.update(get_tv_genres()) 199 | return result 200 | 201 | @plugin.cached(TTL=60, cache='genres') 202 | def get_tv_genres(): 203 | result = tmdb_tv_genres() 204 | if not result: 205 | result = tmdb_tv_genres_mock() 206 | return result 207 | 208 | @plugin.cached(TTL=60, cache='genres') 209 | def get_base_genres(): 210 | result = tmdb_movie_genres() 211 | if not result: 212 | result = tmdb_movie_genres_mock() 213 | return result 214 | 215 | @plugin.cached(TTL=None, cache='genres') 216 | def tmdb_movie_genres(): 217 | from resources.lib.TheMovieDB import Genres 218 | result = Genres().movie_list(language='en') 219 | genres= dict([(i['id'], i['name']) for i in result['genres'] if i['name'] is not None]) 220 | if genres: 221 | return genres 222 | return None 223 | 224 | @plugin.cached(TTL=None, cache='genres') 225 | def tmdb_tv_genres(): 226 | from resources.lib.TheMovieDB import Genres 227 | result = Genres().tv_list(language='en') 228 | genres= dict([(i['id'], i['name']) for i in result['genres'] if i['name'] is not None]) 229 | if genres: 230 | return genres 231 | return None 232 | 233 | def get_play_count_info(playdata, season_num, episode_num=False): 234 | season_index = next((index for (index, d) in enumerate(playdata['seasons']) if d["number"] == season_num), None) 235 | if episode_num: 236 | episode_index = next((index for (index, d) in enumerate(playdata['seasons'][season_index]['episodes']) if d["number"] == episode_num), None) 237 | return 1 if playdata['seasons'][season_index]['episodes'][episode_index]['completed'] == True else 0 238 | return season_index -------------------------------------------------------------------------------- /resources/lib/nav_movies.py: -------------------------------------------------------------------------------- 1 | import os 2 | import xbmc, xbmcvfs, xbmcplugin 3 | from resources.lib import text 4 | from resources.lib import Trakt 5 | from resources.lib import tools 6 | from resources.lib import nav_base 7 | from resources.lib import meta_info 8 | from resources.lib import lib_movies 9 | from resources.lib import playrandom 10 | from resources.lib import play_movies 11 | from resources.lib.rpc import RPC 12 | from resources.lib import fanarttv 13 | from resources.lib.xswift2 import plugin 14 | 15 | 16 | enablefanart = plugin.get_setting('enablefanart', bool) 17 | countenabled = plugin.get_setting('countenabled', bool) 18 | traktenabled = True if plugin.get_setting('trakt_access_token', unicode) != '' else False 19 | SORT = [ 20 | xbmcplugin.SORT_METHOD_UNSORTED, 21 | xbmcplugin.SORT_METHOD_LABEL, 22 | xbmcplugin.SORT_METHOD_VIDEO_YEAR, 23 | xbmcplugin.SORT_METHOD_GENRE, 24 | xbmcplugin.SORT_METHOD_VIDEO_RATING, 25 | xbmcplugin.SORT_METHOD_PLAYCOUNT] 26 | SORTRAKT = [ 27 | xbmcplugin.SORT_METHOD_UNSORTED, 28 | xbmcplugin.SORT_METHOD_LABEL, 29 | xbmcplugin.SORT_METHOD_VIDEO_YEAR, 30 | xbmcplugin.SORT_METHOD_GENRE, 31 | xbmcplugin.SORT_METHOD_VIDEO_RATING, 32 | xbmcplugin.SORT_METHOD_PLAYCOUNT, 33 | xbmcplugin.SORT_METHOD_DURATION] 34 | 35 | @plugin.route('/movies/tmdb/blockbusters//') 36 | def tmdb_movies_blockbusters(page, raw=False): 37 | from resources.lib.TheMovieDB import Discover 38 | result = Discover().movie(language='en', append_to_response='external_ids,videos', **{'page': page, 'sort_by': 'revenue.desc'}) 39 | if raw: 40 | return result 41 | else: 42 | return list_tmdb_movies(result) 43 | 44 | @plugin.route('/movies/tmdb/random_blockbuster') 45 | def tmdb_movies_play_random_blockbuster(): 46 | result = {} 47 | pages = plugin.get_setting('random_pages', int) + 1 48 | for i in range(1, pages): 49 | result.update(tmdb_movies_blockbusters(i, raw=True)) 50 | tmdb_movies_play_random(result) 51 | 52 | @plugin.route('/movies/tmdb/now_playing//') 53 | def tmdb_movies_now_playing(page, raw=False): 54 | from resources.lib.TheMovieDB import Movies 55 | result = Movies().now_playing(language='en', page=page, append_to_response='external_ids,videos') 56 | if raw: 57 | return result 58 | else: 59 | return list_tmdb_movies(result) 60 | 61 | @plugin.route('/movies/tmdb/random_now_playing') 62 | def tmdb_movies_play_random_now_playing(): 63 | result = {} 64 | pages = plugin.get_setting('random_pages', int) + 1 65 | for i in range(1, pages): 66 | result.update(tmdb_movies_now_playing(i, raw=True)) 67 | tmdb_movies_play_random(result) 68 | 69 | 70 | @plugin.route('/movies/tmdb/popular//') 71 | def tmdb_movies_popular(page, raw=False): 72 | from resources.lib.TheMovieDB import Movies 73 | result = Movies().popular(language='en', page=page) 74 | if raw: 75 | return result 76 | else: 77 | return list_tmdb_movies(result) 78 | 79 | @plugin.route('/movies/tmdb/random_popular') 80 | def tmdb_movies_play_random_popular(): 81 | result = {} 82 | pages = plugin.get_setting('random_pages', int) + 1 83 | for i in range(1, pages): 84 | result.update(tmdb_movies_popular(i, raw=True)) 85 | tmdb_movies_play_random(result) 86 | 87 | 88 | @plugin.route('/movies/tmdb/top_rated//') 89 | def tmdb_movies_top_rated(page, raw=False): 90 | from resources.lib.TheMovieDB import Movies 91 | result = Movies().top_rated(language='en', page=page, append_to_response='external_ids,videos') 92 | if raw: 93 | return result 94 | else: 95 | return list_tmdb_movies(result) 96 | 97 | @plugin.route('/movies/tmdb/random_top_rated') 98 | def tmdb_movies_play_random_top_rated(): 99 | result = {} 100 | pages = plugin.get_setting('random_pages', int) + 1 101 | for i in range(1, pages): 102 | result.update(tmdb_movies_top_rated(i, raw=True)) 103 | tmdb_movies_play_random(result) 104 | 105 | @plugin.route('/movies/trakt/search///') 106 | def trakt_movies_search_term(term, page): 107 | results, pages = Trakt.search_for_movie_paginated(term, page) 108 | return list_trakt_search_items(results, pages, page) 109 | 110 | @plugin.route('/movies/trakt/latest_releases') 111 | def trakt_movies_latest_releases(raw=False): 112 | results = sorted(Trakt.get_latest_releases_movies(), key=lambda k: k['listed_at'], reverse=True) 113 | if raw: 114 | return results 115 | else: 116 | return list_trakt_movies(results) 117 | 118 | @plugin.route('/movies/trakt/random_latest_releases') 119 | def trakt_movies_play_random_latest_releases(): 120 | results = trakt_movies_latest_releases(raw=True) 121 | trakt_movies_play_random(results) 122 | 123 | @plugin.route('/movies/trakt/imdb_top_rated_movies//') 124 | def trakt_movies_imdb_top_rated(page, raw=False): 125 | results, pages = Trakt.get_imdb_top_rated_movies(page) 126 | if raw: 127 | return results 128 | else: 129 | return list_trakt_movies(results, pages, page) 130 | 131 | @plugin.route('/movies/trakt/random_imdb_top_rated') 132 | def trakt_movies_play_random_imdb_top_rated(): 133 | result = [] 134 | pages = plugin.get_setting('random_pages', int) + 1 135 | for i in range(1, pages): 136 | result.extend(trakt_movies_imdb_top_rated(i, raw=True)) 137 | trakt_movies_play_random(result) 138 | 139 | @plugin.route('/movies/trakt/watched//') 140 | def trakt_movies_watched(page, raw=False): 141 | results, pages = Trakt.get_watched_movies_paginated(page) 142 | if raw: 143 | return results 144 | else: 145 | return list_trakt_movies(results, pages, page) 146 | 147 | @plugin.route('/movies/trakt/random_watched') 148 | def trakt_movies_play_random_watched(): 149 | result = [] 150 | pages = plugin.get_setting('random_pages', int) + 1 151 | for i in range(1, pages): 152 | result.extend(trakt_movies_watched(i, raw=True)) 153 | trakt_movies_play_random(result) 154 | 155 | @plugin.route('/movies/trakt/collected//') 156 | def trakt_movies_collected(page, raw=False): 157 | results, pages = Trakt.get_collected_movies_paginated(page) 158 | if raw: 159 | return results 160 | else: 161 | return list_trakt_movies(results, pages, page) 162 | 163 | @plugin.route('/movies/trakt/random_collected') 164 | def trakt_movies_play_random_collected(): 165 | result = [] 166 | pages = plugin.get_setting('random_pages', int) + 1 167 | for i in range(1, pages): 168 | result.extend(trakt_movies_collected(i, raw=True)) 169 | trakt_movies_play_random(result) 170 | 171 | @plugin.route('/movies/trakt/popular//') 172 | def trakt_movies_popular(page, raw=False): 173 | results, pages = Trakt.get_popular_movies_paginated(page) 174 | if raw: 175 | return results 176 | else: 177 | return list_trakt_movies([{u'movie': m} for m in results], pages, page) 178 | 179 | @plugin.route('/movies/trakt/random_popular') 180 | def trakt_movies_play_random_popular(): 181 | result = [] 182 | pages = plugin.get_setting('random_pages', int) + 1 183 | for i in range(1, pages): 184 | result.extend(trakt_movies_popular(i, raw=True)) 185 | trakt_movies_play_random(result) 186 | 187 | 188 | @plugin.route('/movies/trakt/trending//') 189 | def trakt_movies_trending(page, raw=False): 190 | results, pages = Trakt.get_trending_movies_paginated(page) 191 | if raw: 192 | return results 193 | else: 194 | return list_trakt_movies(results, pages, page) 195 | 196 | @plugin.route('/movies/trakt/random_trending') 197 | def trakt_movies_play_random_trending(): 198 | result = [] 199 | pages = plugin.get_setting('random_pages', int) + 1 200 | for i in range(1, pages): 201 | result.extend(trakt_movies_trending(i, raw=True)) 202 | trakt_movies_play_random(result) 203 | 204 | 205 | @plugin.route('/movies/tmdb/search_term///') 206 | def tmdb_movies_search_term(term, page): 207 | from resources.lib.TheMovieDB import Search 208 | result = Search().movie(query=term, language='en', page=page, append_to_response='external_ids,videos') 209 | return list_tmdb_items(result) 210 | 211 | @plugin.route('/movies/trakt/person/') 212 | def trakt_movies_person(person_id, raw=False): 213 | result = Trakt.get_person_movies(person_id)['cast'] 214 | if raw: 215 | return result 216 | else: 217 | return list_trakt_persons(result) 218 | 219 | @plugin.route('/movies_genres') 220 | def tmdb_movies_genres(): 221 | genres = nav_base.get_base_genres() 222 | items = sorted([ 223 | { 224 | 'label': name, 225 | 'path': plugin.url_for('tmdb_movies_genre', id=id, page=1), 226 | 'thumbnail': nav_base.get_genre_icon(id), 227 | 'fanart': plugin.get_addon_fanart(), 228 | 'context_menu': [ 229 | ('Play (random)', 'RunPlugin(%s)' % plugin.url_for('tmdb_movies_play_random_genre', id = id))] 230 | } for id, name in genres.items()], key=lambda k: k['label']) 231 | return plugin.finish(items=items, sort_methods=SORT) 232 | 233 | @plugin.route('/movies/genre///') 234 | def tmdb_movies_genre(id, page, raw=False): 235 | from resources.lib.TheMovieDB import Genres 236 | result = Genres(id).movies(id=id, language='en', page=page) 237 | if raw: 238 | return result 239 | else: 240 | return list_tmdb_movies(result) 241 | 242 | @plugin.route('/movies/tmdb/random_genre/') 243 | def tmdb_movies_play_random_genre(id): 244 | result = {} 245 | pages = plugin.get_setting('random_pages', int) + 1 246 | for i in range(1, pages): 247 | result.update(tmdb_movies_genre(id, i, raw=True)) 248 | tmdb_movies_play_random(result) 249 | 250 | @plugin.route('/movies/add_to_library//') 251 | def movies_add_to_library(src, id): 252 | from resources.lib.TheMovieDB import Movies 253 | library_folder = lib_movies.setup_library(plugin.get_setting('movies_library_folder', unicode)) 254 | if library_folder == False: 255 | return 256 | date = None 257 | if src == 'tmdb': 258 | movie = Movies(id).info() 259 | date = text.date_to_timestamp(movie.get('release_date')) 260 | imdb_id = movie.get('imdb_id') 261 | if imdb_id: 262 | src = 'imdb' 263 | id = imdb_id 264 | ids = [str(movie.get('id')), str(movie.get('imdb_id', None))] 265 | try: 266 | libmovies = RPC.VideoLibrary.GetMovies(properties=['imdbnumber', 'title', 'year'])['movies'] 267 | libmovies = [i for i in libmovies if str(i['imdbnumber']) in ids or (str(i['year']) == str(movie.get('year', 0)) and equals(movie.get['title'], i['title']))] 268 | libmovie = libmovies[0] 269 | except: 270 | libmovie = [] 271 | else: 272 | ids = [str(id), 'None'] 273 | try: 274 | libmovies = RPC.VideoLibrary.GetMovies(properties=['imdbnumber', 'title', 'year'])['movies'] 275 | libmovies = [i for i in libmovies if str(i['imdbnumber']) in ids] 276 | libmovie = libmovies[0] 277 | except: 278 | libmovie = [] 279 | if libmovie != []: 280 | return 281 | lib_movies.add_movie_to_library(library_folder, src, id) 282 | tools.scan_library(path=plugin.get_setting('movies_library_folder', unicode)) 283 | 284 | @plugin.route('/movies/add_to_library_parsed///') 285 | def movies_add_to_library_parsed(src, id, player): 286 | from resources.lib.TheMovieDB import Movies 287 | library_folder = lib_movies.setup_library(plugin.get_setting('movies_library_folder', unicode)) 288 | date = None 289 | if src == 'tmdb': 290 | movie = Movies(id).info() 291 | date = text.date_to_timestamp(movie.get('release_date')) 292 | imdb_id = movie.get('imdb_id') 293 | if imdb_id: 294 | if imdb_id != None and imdb_id != '': 295 | src = 'imdb' 296 | id = imdb_id 297 | lib_movies.add_movie_to_library(library_folder, src, id, player) 298 | tools.scan_library(path=plugin.get_setting('movies_library_folder', unicode)) 299 | 300 | def movies_add_all_to_library(items, noscan=False): 301 | library_folder = lib_movies.setup_library(plugin.get_setting('movies_library_folder', unicode)) 302 | if 'results' in items: 303 | ids = '\n'.join([str(r['id']) for r in items['results']]) 304 | else: 305 | ids = '\n'.join([i['movie']['ids']['imdb'] if i['movie']['ids']['imdb'] != None and i['movie']['ids']['imdb'] != '' else str(i['movie']['ids']['tmdb']) for i in items]) 306 | movies_batch_add_file = plugin.get_setting('movies_batch_add_file_path', unicode) 307 | if xbmcvfs.exists(movies_batch_add_file): 308 | batch_add_file = xbmcvfs.File(movies_batch_add_file) 309 | pre_ids = batch_add_file.read() 310 | xids = pre_ids.split('\n') 311 | for id in xids: 312 | if id != '' and id != None and id not in ids: 313 | ids = ids + str(id) + '\n' 314 | batch_add_file.close() 315 | xbmcvfs.delete(movies_batch_add_file) 316 | batch_add_file = xbmcvfs.File(movies_batch_add_file, 'w') 317 | batch_add_file.write(str(ids)) 318 | batch_add_file.close() 319 | xbmc.executebuiltin('RunPlugin(plugin://plugin.video.openmeta/movies/batch_add_to_library)') 320 | 321 | @plugin.route('/movies/batch_add_to_library') 322 | def movies_batch_add_to_library(): 323 | from resources.lib.TheMovieDB import Movies 324 | movie_batch_file = plugin.get_setting('movies_batch_add_file_path', unicode) 325 | if xbmcvfs.exists(movie_batch_file): 326 | try: 327 | f = open(xbmc.translatePath(movie_batch_file), 'r') 328 | r = f.read() 329 | f.close() 330 | ids = r.split('\n') 331 | except: 332 | plugin.notify('Movies', 'not found', plugin.get_addon_icon(), 3000) 333 | library_folder = lib_movies.setup_library(plugin.get_setting('movies_library_folder', unicode)) 334 | for id in ids: 335 | if ',' in id: 336 | csvs = id.split(',') 337 | for csv in csvs: 338 | if not str(csv).startswith('tt') and csv != '': 339 | movie = Movies(csv).info() 340 | csv = movie.get('imdb_id') 341 | lib_movies.batch_add_movies_to_library(library_folder, csv) 342 | else: 343 | if not str(id).startswith('tt') and id != '': 344 | movie = Movies(id).info() 345 | id = movie.get('imdb_id') 346 | lib_movies.batch_add_movies_to_library(library_folder, id) 347 | os.remove(xbmc.translatePath(movie_batch_file)) 348 | lib_movies.update_library() 349 | return True 350 | 351 | def list_tmdb_movies(result): 352 | genres_dict = nav_base.get_base_genres() 353 | movies = [meta_info.get_movie_metadata(item, genres_dict) for item in result['results']] 354 | items = [make_movie_item(movie) for movie in movies] 355 | if 'page' in result: 356 | page = int(result['page']) 357 | pages = int(result['total_pages']) 358 | args = nav_base.caller_args() 359 | if pages > page: 360 | args['page'] = page + 1 361 | args['confirm'] = 'yes' 362 | items.append( 363 | { 364 | 'label': '%s/%s [I]Next page[/I] >>' % (page, pages + 1), 365 | 'path': plugin.url_for(nav_base.caller_name(), **args), 366 | 'thumbnail': plugin.get_media_icon('item_next'), 367 | 'fanart': plugin.get_addon_fanart() 368 | }) 369 | return plugin.finish(items=items, sort_methods=SORT) 370 | 371 | def list_tmdb_items(result): 372 | genres_dict = nav_base.get_base_genres() 373 | movies = [meta_info.get_movie_metadata(item, None) for item in result['results']] 374 | items = [make_movie_item(movie) for movie in movies] 375 | if 'page' in result: 376 | page = int(result['page']) 377 | pages = int(result['total_pages']) 378 | args = nav_base.caller_args() 379 | if pages > page: 380 | args['page'] = page + 1 381 | items.append( 382 | { 383 | 'label': '%s/%s [I]Next page[/I] >>' % (page, pages + 1), 384 | 'path': plugin.url_for(nav_base.caller_name(), **args), 385 | 'thumbnail': plugin.get_media_icon('item_next'), 386 | 'fanart': plugin.get_addon_fanart() 387 | }) 388 | return plugin.finish(items=items, sort_methods=SORT) 389 | 390 | def list_trakt_persons(results): 391 | genres_dict = dict([(x['slug'], x['name']) for x in Trakt.get_genres('movies')]) 392 | movies = [meta_info.get_trakt_movie_metadata(item['movie'], genres_dict) for item in results] 393 | items = [make_movie_item(movie) for movie in movies] 394 | return items 395 | 396 | def list_trakt_search_items(results, pages, page): 397 | movies = [meta_info.get_trakt_movie_metadata(item['movie'], None) for item in results] 398 | items = [make_movie_item(movie) for movie in movies] 399 | page = int(page) 400 | pages = int(pages) 401 | if pages > 1: 402 | args = nav_base.caller_args() 403 | args['page'] = page + 1 404 | items.append( 405 | { 406 | 'label': '%s/%s [I]Next page[/I] >>' % (page, pages + 1), 407 | 'path': plugin.url_for(nav_base.caller_name(), **args), 408 | 'thumbnail': plugin.get_media_icon('item_next'), 409 | 'fanart': plugin.get_addon_fanart() 410 | }) 411 | return items 412 | 413 | def list_trakt_movies(results, pages=1, page=1): 414 | genres_dict = dict([(x['slug'], x['name']) for x in Trakt.get_genres('movies')]) 415 | try: 416 | movies = [meta_info.get_trakt_movie_metadata(item['movie'], genres_dict) for item in results] 417 | except KeyError: 418 | movies = [meta_info.get_trakt_movie_metadata(item, genres_dict) for item in results] 419 | items = [make_movie_item(movie) for movie in movies] 420 | page = int(page) 421 | pages = int(pages) 422 | if pages > 1: 423 | args = nav_base.caller_args() 424 | args['page'] = page + 1 425 | args['confirm'] = 'yes' 426 | items.append( 427 | { 428 | 'label': '%s/%s [I]Next page[/I] >>' % (page, pages + 1), 429 | 'path': plugin.url_for(nav_base.caller_name(), **args), 430 | 'thumbnail': plugin.get_media_icon('item_next'), 431 | 'fanart': plugin.get_addon_fanart() 432 | }) 433 | return plugin.finish(items=items, sort_methods=SORTRAKT) 434 | 435 | @plugin.route('/movies/play_choose_player///') 436 | def movies_play_choose_player(src, id, usedefault): 437 | from resources.lib.TheMovieDB import Find 438 | tmdb_id = None 439 | if src == 'tmdb': 440 | tmdb_id = id 441 | elif src == 'imdb': 442 | info = Find(id).info(external_source='imdb_id') 443 | tmdb_id = info['movie_results'][0]['id'] 444 | if not tmdb_id: 445 | plugin.notify('tmdb id', 'not found', plugin.get_addon_icon(), 3000) 446 | play_movies.play_movie(tmdb_id, usedefault) 447 | 448 | @plugin.route('/movies/play//') 449 | def movies_play(src, id, usedefault='True'): 450 | playaction = plugin.get_setting('movies_default_action', int) 451 | if playaction == 1 and xbmc.getCondVisibility('system.hasaddon(script.extendedinfo)') and not plugin.getProperty('infodialogs.active'): 452 | from resources.lib.play_base import action_cancel 453 | action_cancel() 454 | src = 'id' if src == 'tmdb' else 'imdb_id' 455 | xbmc.executebuiltin('RunScript(script.extendedinfo,info=extendedinfo,%s=%s)' % (src, id)) 456 | else: 457 | movies_play_choose_player(src, id, usedefault) 458 | 459 | @plugin.route('/movies/play_by_name//') 460 | def movies_play_by_name(name, lang='en', usedefault='True'): 461 | tools.show_busy() 462 | from resources.lib.TheMovieDB import Search 463 | items = Search().movie(query=name, language=lang, page=1)['results'] 464 | if not items: 465 | tools.hide_busy() 466 | plugin.ok('Movie not found', 'No information found for ' + name) 467 | if len(items) > 1: 468 | selection = plugin.select('Movie', ['%s (%s)' % ((s['title']), text.parse_year(s['release_date'])) for s in items]) 469 | else: 470 | selection = 0 471 | tools.hide_busy() 472 | if selection != -1: 473 | id = items[selection]['id'] 474 | movies_play_choose_player('tmdb', id, usedefault) 475 | 476 | @plugin.route('/movies/play_by_name_choose_player///') 477 | def movies_play_by_name_choose_player(name, lang='en', usedefault='False'): 478 | movies_play_by_name(name, lang, usedefault) 479 | 480 | def trakt_movies_play_random(movies, convert_list=False): 481 | for movie in movies: 482 | movie['type'] = 'movie' 483 | if convert_list: 484 | movie['movie'] = movie 485 | playrandom.trakt_play_random(movies) 486 | 487 | def tmdb_movies_play_random(list): 488 | movies = list['results'] 489 | for movie in movies: 490 | movie['type'] = 'movie' 491 | playrandom.tmdb_play_random(movies) 492 | 493 | def make_movie_item(movie_info): 494 | try: 495 | tmdb_id = movie_info.get('tmdb') 496 | except: 497 | tmdb_id = '' 498 | if tmdb_id == '': 499 | try: 500 | tmdb_id = info['tmdb'] 501 | except: 502 | tmdb_id = False 503 | try: 504 | imdb_id = movie_info.get('imdbnumber') 505 | except: 506 | imdb_id = '' 507 | if imdb_id == '': 508 | try: 509 | imdb_id = info['imdb_id'] 510 | except: 511 | imdb_id = False 512 | if tmdb_id: 513 | id = tmdb_id 514 | src = 'tmdb' 515 | elif imdb_id: 516 | id = imdb_id 517 | src = 'imdb' 518 | else: 519 | plugin.notify('tmdb or imdb id', 'not found', plugin.get_addon_icon(), 3000) 520 | if xbmc.getCondVisibility('system.hasaddon(script.extendedinfo)'): 521 | context_menu = [ 522 | ('Movie trailer', 'RunScript(script.extendedinfo,info=playtrailer,id=%s)' % id), 523 | ('Add to library','RunPlugin(%s)' % plugin.url_for('movies_add_to_library', src=src, id=id))] 524 | else: 525 | context_menu = [ 526 | ('Add to library','RunPlugin(%s)' % plugin.url_for('movies_add_to_library', src=src, id=id))] 527 | try: 528 | if traktenabled and countenabled: 529 | if 'trakt_id' in movie_info.keys() and movie_info['trakt_id'] != '': 530 | movie_id = movie_info['trakt_id'] 531 | elif tmdb_id != '': 532 | movie_id = Trakt.find_trakt_ids('tmdb', tmdb_id, 'movie')['trakt'] 533 | else: 534 | movie_id = Trakt.find_trakt_ids('imdb', imdb_id, 'movie')['trakt'] 535 | playdata = Trakt.get_movie_history(movie_id) 536 | movie_info.update({'playcount': len([k for d in playdata for k in d.keys() if k == 'watched_at'])}) 537 | except: 538 | pass 539 | movieitem = { 540 | 'label': movie_info['title'], 541 | 'path': plugin.url_for('movies_play', src=src, id=id, usedefault=True), 542 | 'context_menu': context_menu, 543 | 'thumbnail': movie_info['poster'], 544 | 'banner': movie_info['fanart'], 545 | 'poster': movie_info['poster'], 546 | 'fanart': movie_info['fanart'], 547 | 'is_playable': True, 548 | 'info_type': 'video', 549 | 'stream_info': {'video': {}}, 550 | 'info': movie_info 551 | } 552 | if enablefanart: 553 | try: 554 | art = get_fanarttv_art(id) 555 | art = checkart(art) 556 | movieitem.update(art) 557 | except: 558 | pass 559 | return movieitem 560 | 561 | def checkart(item): 562 | art = {} 563 | for key, val in item.items(): 564 | if val != '': 565 | art.update({key: val}) 566 | return art 567 | 568 | def get_fanarttv_art(id, query='movies', season=False): 569 | return fanarttv.get(id, query, season) 570 | 571 | @plugin.route('/my_trakt/movie_lists/movies/recommendations') 572 | def trakt_movies_recommendations(raw=False): 573 | result = Trakt.get_recommendations('movies') 574 | if raw: 575 | return result 576 | else: 577 | return list_trakt_movies(result, '1', '1') 578 | 579 | @plugin.route('/my_trakt/movie_lists/movies/watchlist') 580 | def trakt_movies_watchlist(raw=False): 581 | result = Trakt.get_watchlist('movies') 582 | if raw: 583 | return result 584 | else: 585 | return list_trakt_movies(result, '1', '1') 586 | 587 | @plugin.route('/my_trakt/movie_lists/movies/watchlist/movies_play_random') 588 | def trakt_movies_play_random_watchlist(): 589 | trakt_movies_play_random(trakt_movies_watchlist(raw=True)) 590 | 591 | @plugin.route('/my_trakt/movie_list/trakt_my_movie_lists') 592 | def lists_trakt_my_movie_lists(): 593 | lists = Trakt.get_lists() 594 | items = [] 595 | for list in lists: 596 | name = list['name'] 597 | user = list['user']['username'] 598 | slug = list['ids']['slug'] 599 | items.append( 600 | { 601 | 'label': name, 602 | 'path': plugin.url_for('lists_trakt_show_movie_list', user=user, slug=slug), 603 | 'thumbnail': plugin.get_media_icon('traktmylists'), 604 | 'fanart': plugin.get_addon_fanart() 605 | }) 606 | return plugin.finish(items=items, sort_methods=SORT) 607 | 608 | @plugin.route('/my_trakt/movie_list/trakt_liked_movie_list//') 609 | def lists_trakt_liked_movie_lists(page): 610 | lists, pages = Trakt.get_liked_lists(page) 611 | items = [] 612 | for list in lists: 613 | info = list['list'] 614 | name = info['name'] 615 | user = info['user']['username'] 616 | slug = info['ids']['slug'] 617 | items.append( 618 | { 619 | 'label': name, 620 | 'path': plugin.url_for('lists_trakt_show_movie_list', user=user, slug=slug), 621 | 'thumbnail': plugin.get_media_icon('traktlikedlists'), 622 | 'fanart': plugin.get_addon_fanart() 623 | }) 624 | nextpage = int(page) + 1 625 | if pages > page: 626 | items.append( 627 | { 628 | 'label': '%s/%s [I]Next page[/I] >>' % (nextpage, pages), 629 | 'path': plugin.url_for('lists_trakt_liked_movie_lists', page=int(page) + 1), 630 | 'thumbnail': plugin.get_media_icon('item_next'), 631 | 'fanart': plugin.get_addon_fanart() 632 | }) 633 | return plugin.finish(items=items, sort_methods=SORT) 634 | 635 | @plugin.route('/my_trakt/movie_lists/movies/collection') 636 | def lists_trakt_movies_collection(raw=False): 637 | results = sorted(Trakt.get_collection('movies'), key=lambda k: k['collected_at'], reverse=True) 638 | if raw: 639 | return results 640 | movies = [meta_info.get_trakt_movie_metadata(item['movie']) for item in results] 641 | items = [make_movie_item(movie) for movie in movies] 642 | return items 643 | 644 | @plugin.route('/my_trakt/movie_lists/movies/collection/movies_to_library') 645 | def lists_trakt_movies_collection_to_library(): 646 | movies_add_all_to_library(Trakt.get_collection('movies')) 647 | 648 | @plugin.route('/my_trakt/movie_lists/movies/collection/movies_play_random') 649 | def lists_trakt_movies_play_random_collection(): 650 | movies = lists_trakt_movies_collection(raw=True) 651 | for movie in movies: 652 | movie['type'] = 'movie' 653 | playrandom.trakt_play_random(movies) 654 | 655 | @plugin.route('/my_trakt/movie_list/movies/show_list//') 656 | def lists_trakt_show_movie_list(user, slug, raw=False): 657 | list_items = Trakt.get_list(user, slug) 658 | if raw: 659 | return list_items 660 | return _lists_trakt_show_movie_list(list_items) 661 | 662 | @plugin.route('/my_trakt/movie_list/play_random//') 663 | def lists_trakt_movies_play_random(user, slug): 664 | items = lists_trakt_show_movie_list(user, slug, raw=True) 665 | playrandom.trakt_play_random(items) 666 | 667 | @plugin.route('/my_trakt/movie_list/movies/_show_list/') 668 | def _lists_trakt_show_movie_list(list_items): 669 | from resources.lib.TheMovieDB import People 670 | items = [] 671 | for list_item in list_items: 672 | item = None 673 | item_type = list_item['type'] 674 | if item_type == 'movie': 675 | movie = list_item['movie'] 676 | movie_info = meta_info.get_trakt_movie_metadata(movie) 677 | try: 678 | tmdb_id = movie_info['tmdb'] 679 | except: 680 | tmdb_id = '' 681 | try: 682 | imdb_id = movie_info['imdb'] 683 | except: 684 | imdb_id = '' 685 | if tmdb_id != None and tmdb_id != '': 686 | src = 'tmdb' 687 | id = tmdb_id 688 | elif imdb_id != None and mdb_id != '': 689 | src = 'imdb' 690 | id = imdb_id 691 | else: 692 | src = '' 693 | id = '' 694 | if src == '': 695 | item = None 696 | item = make_movie_item(movie_info) 697 | elif item_type == 'person': 698 | person_id = list_item['person']['ids']['trakt'] 699 | person_tmdb_id = list_item['person']['ids']['tmdb'] 700 | try: 701 | person_images = People(person_tmdb_id).images()['profiles'] 702 | person_image = 'https://image.tmdb.org/t/p/w640' + person_images[0]['file_path'] 703 | except: 704 | person_image = '' 705 | person_name = text.to_utf8(list_item['person']['name']) 706 | item = ( 707 | { 708 | 'label': person_name, 709 | 'path': plugin.url_for('trakt_movies_person', person_id=person_id), 710 | 'thumbnail': person_image, 711 | 'poster': person_image, 712 | 'fanart': plugin.get_addon_fanart() 713 | }) 714 | if item is not None: 715 | items.append(item) 716 | return items -------------------------------------------------------------------------------- /resources/lib/play_base.py: -------------------------------------------------------------------------------- 1 | import json 2 | import xbmc 3 | from resources.lib import text 4 | from resources.lib import tools 5 | from resources.lib import Trakt 6 | from resources.lib.listers import Lister 7 | from resources.lib.xswift2 import plugin 8 | 9 | 10 | @plugin.route('/play/