import sys import xbmc import xbmcvfs import xbmcaddon import xbmcgui import datetime from resources.lib.plugin import Plugin from resources.lib.traktapi import TraktAPI from resources.lib.kodilibrary import KodiLibrary import resources.lib.utils as utils _addon = xbmcaddon.Addon('plugin.video.themoviedb.helper') _plugin = Plugin() def library_cleancontent_replacer(content, old, new): content = content.replace(old, new) return library_cleancontent_replacer(content, old, new) if old in content else content def library_cleancontent(content, details='info=play'): content = content.replace('info=flatseasons', details) content = content.replace('info=details', details) content = content.replace('fanarttv=True', '') content = content.replace('widget=True', '') content = content.replace('localdb=True', '') content = content.replace('nextpage=True', '') content = library_cleancontent_replacer(content, '&', '&') content = library_cleancontent_replacer(content, '&&', '&') content = library_cleancontent_replacer(content, '?&', '?') content = content + '&islocal=True' if '&islocal=True' not in content else content return content def library_createpath(path): if xbmcvfs.exists(path): return path if xbmcvfs.mkdirs(path): utils.kodi_log(u'ADD LIBRARY -- Created path:\n{}'.format(path), 2) return path if _addon.getSettingBool('ignore_folderchecking'): utils.kodi_log(u'ADD LIBRARY -- xbmcvfs reports folder does NOT exist:\n{}\nIGNORING ERROR: User set folder checking to ignore'.format(path), 2) return path def library_createfile(filename, content, *args, **kwargs): """ Create the file and folder structure: filename=.strm file, content= content of file. *args = folders to create. """ path = kwargs.get('basedir', '').replace('\\', '/') # Convert MS-DOS style paths to UNIX style if not path: utils.kodi_log(u'ADD LIBRARY -- No basedir specified!', 2) return for folder in args: folder = utils.validify_filename(folder) path = '{}{}/'.format(path, folder) content = library_cleancontent(content) if kwargs.get('clean_url', True) else content if not content: utils.kodi_log(u'ADD LIBRARY -- No content specified!', 2) return if not filename: utils.kodi_log(u'ADD LIBRARY -- No filename specified!', 2) return if not library_createpath(path): xbmcgui.Dialog().ok( xbmc.getLocalizedString(20444), _addon.getLocalizedString(32122) + ' [B]{}[/B]'.format(path), _addon.getLocalizedString(32123)) utils.kodi_log(u'ADD LIBRARY -- XBMCVFS unable to create path:\n{}'.format(path), 2) return filepath = '{}{}.{}'.format(path, utils.validify_filename(filename), kwargs.get('file_ext', 'strm')) f = xbmcvfs.File(filepath, 'w') f.write(utils.try_encode_string(content)) f.close() utils.kodi_log(u'ADD LIBRARY -- Successfully added:\n{}\n{}'.format(filepath, content), 2) return filepath def library_create_nfo(tmdbtype, tmdb_id, *args, **kwargs): filename = 'movie' if tmdbtype == 'movie' else 'tvshow' content = 'https://www.themoviedb.org/{}/{}'.format(tmdbtype, tmdb_id) library_createfile(filename, content, file_ext='nfo', *args, **kwargs) def library_getnfo_tmdbid(basedir=None, folder=None): """ Checks .nfo file and returns TMDB ID it contains """ tmdb_id = None folder_list = xbmcvfs.listdir(basedir)[0] if folder in folder_list: nfo_folder = basedir + folder + '/' nfo = None for x in xbmcvfs.listdir(nfo_folder)[1]: if x.endswith('.nfo'): nfo = x if nfo: vfs_file = xbmcvfs.File(nfo_folder + nfo) content = '' try: content = vfs_file.read() finally: vfs_file.close() tmdb_id = content.replace('https://www.themoviedb.org/tv/', '') tmdb_id = tmdb_id.replace('&islocal=True', '') return tmdb_id def library_addtvshow(basedir=None, folder=None, url=None, tmdb_id=None, tvdb_id=None, imdb_id=None, p_dialog=None): if not basedir or not folder or not url: return nfo_tmdbid = library_getnfo_tmdbid(basedir, folder) # Check the nfo file in the folder to make sure it matches the TMDB ID if nfo_tmdbid and utils.try_parse_int(nfo_tmdbid) != utils.try_parse_int(tmdb_id): folder += ' (TMDB {})'.format(tmdb_id) # If different tvshow with same name exists create new folder with TMDB ID added details_tvshow = _plugin.tmdb.get_request_sc('tv', tmdb_id) # Get base tv show if not details_tvshow: return library_create_nfo('tv', tmdb_id, folder, basedir=basedir) # Create .nfo for tvshow seasons = [i.get('season_number') for i in details_tvshow.get('seasons', []) if i.get('season_number', 0) != 0] # Don't get specials s_count, s_total = 0, len(seasons) # Used to update p_dialog progress for season in seasons: s_count += 1 season_name = u'Season {}'.format(season) # Originally made a bad choice here to localise season name but reverted that now if p_dialog: # Update our progress dialog p_dialog_val = (s_count * 100) // s_total p_dialog_msg = u'Adding {} - {} to library...'.format(details_tvshow.get('original_name'), season_name) p_dialog.update(p_dialog_val, message=p_dialog_msg) details_season = _plugin.tmdb.get_request_sc('tv', tmdb_id, 'season', season) # Get season if not details_season: return episodes = [i for i in details_season.get('episodes', []) if i.get('episode_number', 0) != 0] # Only get non-special seasons e_count, e_total = 0, len(episodes) # Used to update p_dialog progress for episode in episodes: e_count += 1 episode_name = 'S{:02d}E{:02d} - {}'.format( utils.try_parse_int(season), utils.try_parse_int(episode.get('episode_number')), utils.validify_filename(episode.get('name'))) # Skip future episodes if _addon.getSettingBool('hide_unaired_episodes'): if not episode.get('air_date') or utils.convert_timestamp(episode.get('air_date'), "%Y-%m-%d", 10) > datetime.datetime.now(): p_dialog.update((e_count * 100) // e_total, message=u'{} not aired yet. Skipping...'.format(episode_name)) if p_dialog else None continue # Check if item has already been added if _plugin.get_db_info(info='dbid', tmdbtype='episode', imdb_id=imdb_id, tmdb_id=tmdb_id, season=season, episode=episode.get('episode_number')): utils.kodi_log(u'Add to Library\nFound {} - {} in library. Skipping...'.format(episode.get('showtitle'), episode_name)) p_dialog.update((e_count * 100) // e_total, message=u'Found {} in library. Skipping...'.format(episode_name)) if p_dialog else None continue p_dialog.update((e_count * 100) // e_total, message=u'Adding {} to library...'.format(episode_name)) if p_dialog else None episode_path = 'plugin://plugin.video.themoviedb.helper/?info=play&type=episode&islocal=True' episode_path += '&tmdb_id={}&season={}&episode={}'.format(tmdb_id, season, episode.get('episode_number')) library_createfile(episode_name, episode_path, folder, season_name, basedir=basedir) def browse(): tmdb_id = sys.listitem.getProperty('tvshow.tmdb_id') path = 'plugin://plugin.video.themoviedb.helper/' path = path + '?info=seasons&type=tv&nextpage=True&tmdb_id={}'.format(tmdb_id) path = path + '&fanarttv=True' if _addon.getSettingBool('fanarttv_lookup') else path command = 'Container.Update({})' if xbmc.getCondVisibility("Window.IsMedia") else 'ActivateWindow(videos,{},return)' xbmc.executebuiltin(command.format(path)) def play(): with utils.busy_dialog(): suffix = 'force_dialog=True' tmdb_id, season, episode = None, None, None dbtype = sys.listitem.getVideoInfoTag().getMediaType() if dbtype == 'episode': tmdb_id = sys.listitem.getProperty('tvshow.tmdb_id') season = sys.listitem.getVideoInfoTag().getSeason() episode = sys.listitem.getVideoInfoTag().getEpisode() suffix += ',season={},episode={}'.format(season, episode) elif dbtype == 'movie': tmdb_id = sys.listitem.getProperty('tmdb_id') or sys.listitem.getUniqueID('tmdb') # Try to lookup ID if we don't have it if not tmdb_id and dbtype == 'episode': id_details = TraktAPI().get_item_idlookup( 'episode', parent=True, tvdb_id=sys.listitem.getUniqueID('tvdb'), tmdb_id=sys.listitem.getUniqueID('tmdb'), imdb_id=sys.listitem.getUniqueID('imdb')) tmdb_id = id_details.get('show', {}).get('ids', {}).get('tmdb') elif not tmdb_id and dbtype == 'movie': tmdb_id = Plugin().get_tmdb_id( itemtype='movie', imdb_id=sys.listitem.getUniqueID('imdb'), query=sys.listitem.getVideoInfoTag().getTitle(), year=sys.listitem.getVideoInfoTag().getYear()) if not tmdb_id or not dbtype: return xbmcgui.Dialog().ok('TheMovieDb Helper', _addon.getLocalizedString(32157)) xbmc.executebuiltin('RunScript(plugin.video.themoviedb.helper,play={},tmdb_id={},{})'.format(dbtype, tmdb_id, suffix)) def library_userlist(user_slug=None, list_slug=None, confirmation_dialog=True, allow_update=True, busy_dialog=True): user_slug = user_slug or sys.listitem.getProperty('Item.user_slug') list_slug = list_slug or sys.listitem.getProperty('Item.list_slug') if busy_dialog: with utils.busy_dialog(): request = TraktAPI().get_response_json('users', user_slug, 'lists', list_slug, 'items') else: request = TraktAPI().get_response_json('users', user_slug, 'lists', list_slug, 'items') if not request: return i_count = 0 i_total = len(request) if confirmation_dialog: d_head = _addon.getLocalizedString(32125) d_body = _addon.getLocalizedString(32126) d_body += '\n[B]{}[/B] {} [B]{}[/B]'.format(list_slug, _addon.getLocalizedString(32127), user_slug) d_body += '\n\n[B][COLOR=red]{}[/COLOR][/B] '.format(xbmc.getLocalizedString(14117)) if i_total > 20 else '\n\n' d_body += '{} [B]{}[/B] {}.'.format(_addon.getLocalizedString(32128), i_total, _addon.getLocalizedString(32129)) if not xbmcgui.Dialog().yesno(d_head, d_body): return p_dialog = xbmcgui.DialogProgressBG() if busy_dialog else None p_dialog.create('TMDbHelper', 'Adding items to library...') if p_dialog else None basedir_movie = _addon.getSettingString('movies_library') or 'special://profile/addon_data/plugin.video.themoviedb.helper/movies/' basedir_tv = _addon.getSettingString('tvshows_library') or 'special://profile/addon_data/plugin.video.themoviedb.helper/tvshows/' all_movies = [] all_tvshows = [] for i in request: i_count += 1 i_type = i.get('type') if i_type not in ['movie', 'show']: continue # Only get movies or tvshows item = i.get(i_type, {}) tmdb_id = item.get('ids', {}).get('tmdb') imdb_id = item.get('ids', {}).get('imdb') tvdb_id = item.get('ids', {}).get('tvdb') if not tmdb_id: continue # Don't bother if there isn't a tmdb_id as lookup is too expensive for long lists if i_type == 'movie': # Add any movies # all_movies.append(('title', item.get('title'))) content = 'plugin://plugin.video.themoviedb.helper/?info=play&tmdb_id={}&type=movie'.format(tmdb_id) folder = u'{} ({})'.format(item.get('title'), item.get('year')) movie_name = u'{} ({})'.format(item.get('title'), item.get('year')) db_file = _plugin.get_db_info(info='file', tmdbtype='movie', imdb_id=imdb_id, tmdb_id=tmdb_id) if db_file: all_movies.append(('filename', db_file.replace('\\', '/').split('/')[-1])) p_dialog.update((i_count * 100) // i_total, message=u'Found {} in library. Skipping...'.format(movie_name)) if p_dialog else None utils.kodi_log(u'Trakt List Add to Library\nFound {} in library. Skipping...'.format(movie_name), 0) continue p_dialog.update((i_count * 100) // i_total, message=u'Adding {} to library...'.format(movie_name)) if p_dialog else None utils.kodi_log(u'Adding {} to library...'.format(movie_name), 0) db_file = library_createfile(movie_name, content, folder, basedir=basedir_movie) library_create_nfo('movie', tmdb_id, folder, basedir=basedir_movie) all_movies.append(('filename', db_file.split('/')[-1])) if i_type == 'show': # Add whole tvshows all_tvshows.append(('title', item.get('title'))) content = 'plugin://plugin.video.themoviedb.helper/?info=seasons&nextpage=True&tmdb_id={}&type=tv'.format(tmdb_id) folder = u'{}'.format(item.get('title')) p_dialog.update((i_count * 100) // i_total, message=u'Adding {} to library...'.format(item.get('title'))) if p_dialog else None library_addtvshow(basedir=basedir_tv, folder=folder, url=content, tmdb_id=tmdb_id, imdb_id=imdb_id, tvdb_id=tvdb_id, p_dialog=p_dialog) p_dialog.close() if p_dialog else None create_playlist(all_movies, 'movies', user_slug, list_slug) if all_movies else None create_playlist(all_tvshows, 'tvshows', user_slug, list_slug) if all_tvshows else None if allow_update and _addon.getSettingBool('auto_update'): xbmc.executebuiltin('UpdateLibrary(video)') def create_playlist(items, dbtype, user_slug, list_slug): """ Creates a smart playlist from a list of titles """ filename = '{}-{}-{}'.format(user_slug, list_slug, dbtype) filepath = 'special://profile/playlists/video/' fcontent = u'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>' fcontent += u'\n<smartplaylist type="{}">'.format(dbtype) fcontent += u'\n <name>{} by {} ({})</name>'.format(list_slug, user_slug, dbtype) fcontent += u'\n <match>any</match>' for i in items: fcontent += u'\n <rule field="{}" operator="is"><value>{}</value></rule>'.format(i[0], i[1]) fcontent += u'\n</smartplaylist>' library_createfile(filename, fcontent, basedir=filepath, file_ext='xsp', clean_url=False) def library(): with utils.busy_dialog(): title = utils.validify_filename(sys.listitem.getVideoInfoTag().getTitle()) dbtype = sys.listitem.getVideoInfoTag().getMediaType() basedir_movie = _addon.getSettingString('movies_library') or 'special://profile/addon_data/plugin.video.themoviedb.helper/movies/' basedir_tv = _addon.getSettingString('tvshows_library') or 'special://profile/addon_data/plugin.video.themoviedb.helper/tvshows/' auto_update = _addon.getSettingBool('auto_update') # Setup our folders and file names if dbtype == 'movie': folder = '{} ({})'.format(title, sys.listitem.getVideoInfoTag().getYear()) movie_name = '{} ({})'.format(title, sys.listitem.getVideoInfoTag().getYear()) library_createfile(movie_name, sys.listitem.getPath(), folder, basedir=basedir_movie) library_create_nfo('movie', sys.listitem.getProperty('tmdb_id'), folder, basedir=basedir_movie) xbmc.executebuiltin('UpdateLibrary(video)') if auto_update else None elif dbtype == 'episode': folder = sys.listitem.getVideoInfoTag().getTVShowTitle() season_name = 'Season {}'.format(sys.listitem.getVideoInfoTag().getSeason()) episode_name = 'S{:02d}E{:02d} - {}'.format( utils.try_parse_int(sys.listitem.getVideoInfoTag().getSeason()), utils.try_parse_int(sys.listitem.getVideoInfoTag().getEpisode()), title) library_createfile(episode_name, sys.listitem.getPath(), folder, season_name, basedir=basedir_tv) library_create_nfo('tv', sys.listitem.getProperty('tvshow.tmdb_id'), folder, basedir=basedir_tv) xbmc.executebuiltin('UpdateLibrary(video)') if auto_update else None elif dbtype == 'tvshow': folder = sys.listitem.getVideoInfoTag().getTVShowTitle() or title library_addtvshow( basedir=basedir_tv, folder=folder, url=sys.listitem.getPath(), tmdb_id=sys.listitem.getProperty('tmdb_id')) xbmc.executebuiltin('UpdateLibrary(video)') if auto_update else None elif dbtype == 'season': folder = sys.listitem.getVideoInfoTag().getTVShowTitle() episodes = KodiLibrary().get_directory(sys.listitem.getPath()) season_name = 'Season {}'.format(sys.listitem.getVideoInfoTag().getSeason()) for episode in episodes: if not episode.get('episode'): continue # Skip special episodes E00 episode_path = library_cleancontent(episode.get('file')) episode_name = 'S{:02d}E{:02d} - {}'.format( utils.try_parse_int(episode.get('season')), utils.try_parse_int(episode.get('episode')), utils.validify_filename(episode.get('title'))) library_createfile(episode_name, episode_path, folder, season_name, basedir=basedir_tv) library_create_nfo('tv', sys.listitem.getProperty('tvshow.tmdb_id'), folder, basedir=basedir_tv) xbmc.executebuiltin('UpdateLibrary(video)') if auto_update else None else: return def sync_userlist(remove_item=False): dbtype = sys.listitem.getVideoInfoTag().getMediaType() user_list = sys.listitem.getProperty('container.list_slug') if remove_item else None tmdb_id = sys.listitem.getProperty('tvshow.tmdb_id') imdb_id = sys.listitem.getUniqueID('imdb') tvdb_id = None if not dbtype == 'episode': tmdb_id = sys.listitem.getProperty('tmdb_id') or sys.listitem.getUniqueID('tmdb') tvdb_id = sys.listitem.getUniqueID('tvdb') if dbtype == 'movie': item_type = 'movie' elif dbtype in ['tvshow', 'season', 'episode']: item_type = 'show' else: # Not the right type of item so lets exit return TraktAPI().sync_userlist(item_type, tmdb_id=tmdb_id, tvdb_id=tvdb_id, imdb_id=imdb_id, remove_item=remove_item, user_list=user_list) xbmc.executebuiltin('Container.Refresh') def refresh_item(): dbtype = sys.listitem.getVideoInfoTag().getMediaType() if dbtype == 'episode': d_args = ( 'tv', sys.listitem.getProperty('tvshow.tmdb_id'), sys.listitem.getVideoInfoTag().getSeason(), sys.listitem.getVideoInfoTag().getEpisode()) elif dbtype == 'tvshow': d_args = ('tv', sys.listitem.getProperty('tmdb_id')) elif dbtype == 'movie': d_args = ('movie', sys.listitem.getProperty('tmdb_id')) else: return details = _plugin.tmdb.get_detailed_item(*d_args, cache_refresh=True) if details: xbmcgui.Dialog().ok(_addon.getLocalizedString(32144), _addon.getLocalizedString(32143).format(details.get('label'))) xbmc.executebuiltin('Container.Refresh') def action(action, tmdb_id=None, tmdb_type=None, season=None, episode=None, label=None): _traktapi = TraktAPI() if action == 'history': func = _traktapi.sync_history elif action == 'collection': func = _traktapi.sync_collection elif action == 'watchlist': func = _traktapi.sync_watchlist elif action == 'add_to_userlist': return sync_userlist() elif action == 'remove_from_userlist': return sync_userlist(remove_item=True) elif action == 'library_userlist': return library_userlist() elif action == 'library': return library() elif action == 'refresh_item': return refresh_item() elif action == 'play': return play() elif action == 'open': return browse() else: return with utils.busy_dialog(): if tmdb_id and tmdb_type: # Passed details via script dbtype = utils.type_convert(tmdb_type, 'dbtype') label = label or 'this {}'.format(utils.type_convert(tmdb_type, 'trakt')) parent_tmdb_id = tmdb_id else: # Context menu so retrieve details from listitem label = sys.listitem.getLabel() dbtype = sys.listitem.getVideoInfoTag().getMediaType() tmdb_id = sys.listitem.getProperty('tmdb_id') parent_tmdb_id = sys.listitem.getProperty('tvshow.tmdb_id') if dbtype == 'episode' else tmdb_id season = sys.listitem.getVideoInfoTag().getSeason() if dbtype == 'episode' else None episode = sys.listitem.getVideoInfoTag().getEpisode() if dbtype == 'episode' else None if tmdb_type == 'episode': # Passed episode details via script if not season or not episode: # Need season and episode for episodes return # Need season and episode if run from script so leave # Retrieve episode details so that we can get tmdb_id for episode episode_details = _plugin.tmdb.get_detailed_item(tmdb_type, parent_tmdb_id, season=season, episode=episode) tmdb_id = episode_details.get('infoproperties', {}).get('imdb_id') if dbtype == 'movie': tmdb_type = 'movie' elif dbtype == 'tvshow': tmdb_type = 'tv' elif dbtype == 'episode': tmdb_type = 'episode' else: return # Check if we're adding or removing the item and confirm with the user that they want to do that trakt_ids = func(utils.type_convert(tmdb_type, 'trakt'), 'tmdb', cache_refresh=True) boolean = 'remove' if int(tmdb_id) in trakt_ids else 'add' dialog_header = 'Trakt {0}'.format(action.capitalize()) dialog_text = xbmcaddon.Addon().getLocalizedString(32065) if boolean == 'add' else xbmcaddon.Addon().getLocalizedString(32064) dialog_text = dialog_text.format(utils.try_decode_string(label), action.capitalize(), tmdb_type, tmdb_id) dialog_text = dialog_text + ' Season: {} Episode: {}'.format(season, episode) if dbtype == 'episode' else dialog_text if not xbmcgui.Dialog().yesno(dialog_header, dialog_text): return with utils.busy_dialog(): slug_type = 'show' if tmdb_type == 'episode' else utils.type_convert(tmdb_type, 'trakt') trakt_type = utils.type_convert(tmdb_type, 'trakt') slug = _traktapi.get_traktslug(slug_type, 'tmdb', parent_tmdb_id) item = _traktapi.get_details(slug_type, slug, season=season, episode=episode) items = {trakt_type + 's': [item]} func(slug_type, mode=boolean, items=items) dialog_header = 'Trakt {0}'.format(action.capitalize()) dialog_text = xbmcaddon.Addon().getLocalizedString(32062) if boolean == 'add' else xbmcaddon.Addon().getLocalizedString(32063) dialog_text = dialog_text.format(tmdb_id, action.capitalize()) xbmcgui.Dialog().ok(dialog_header, dialog_text) xbmc.executebuiltin('Container.Refresh')