# -*- coding: utf-8 -*- # ------------------------------------------------------------ # XBMC Library Tools # ------------------------------------------------------------ import sys, os, threading, time, re, math, xbmc, xbmcgui PY3 = False if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int if PY3: import urllib.request as urllib2 # Es muy lento en PY2. En PY3 es nativo else: import urllib2 # Usamos el nativo de PY2 que es más rápido from core import filetools, jsontools, support, videolibrarydb from platformcode import config, logger, platformtools from core import scrapertools from xml.dom import minidom def mark_auto_as_watched(): def mark_as_watched_subThread(item): logger.debug() # logger.dbg() actual_time = 0 total_time = 0 # time_limit = time.time() + 10 # while not platformtools.isPlaying() and time.time() < time_limit: # time.sleep(1) marked = False sync = False next_episode = None show_server = True mark_time = 100 percentage = float(config.getSetting("watched_setting")) / 100 time_from_end = config.getSetting('next_ep_seconds') if item.contentType != 'movie' and 0 < config.getSetting('next_ep') < 3: next_dialogs = ['NextDialog.xml', 'NextDialogExtended.xml', 'NextDialogCompact.xml'] next_ep_type = config.getSetting('next_ep_type') ND = next_dialogs[next_ep_type] try: next_episode = next_ep(item) except: next_episode = False logger.debug(next_episode) while not xbmc.Monitor().abortRequested(): if not platformtools.isPlaying(): break try: actual_time = xbmc.Player().getTime() except: actual_time = 0 try: total_time = xbmc.Player().getTotalTime() except: total_time = 0 if actual_time and item.played_time and xbmcgui.getCurrentWindowId() == 12005: logger.debug('VAI A', item.played_time) xbmc.Player().seekTime(item.played_time) item.played_time = 0 mark_time = total_time * percentage difference = total_time - actual_time # Mark as Watched if actual_time > mark_time and mark_time > 0 and not marked: logger.info("Marked as Watched") item.playcount = 1 marked = True item.played_time = 0 platformtools.setPlayedTime(item) if item.options['strm'] : sync = True show_server = False if not next_episode: break # check for next Episode if next_episode and marked and time_from_end >= difference: nextdialog = NextDialog(ND, config.getRuntimePath(), item=next_episode) while platformtools.isPlaying() and not nextdialog.is_exit(): xbmc.sleep(100) if nextdialog.continuewatching: next_episode.next_ep = True xbmc.Player().stop() nextdialog.close() break if actual_time < mark_time: item.played_time = actual_time else: item.played_time = 0 if mark_time: logger.debug('Set Played Time', item.played_time) platformtools.setPlayedTime(item) # Silent sync with Trakt if sync and config.getSetting("trakt_sync"): sync_trakt_kodi() while platformtools.isPlaying(): xbmc.sleep(10) if not show_server and not item.no_return: if item.window: # xbmc.executebuiltin('ActivateWindow({})'.format(filetools.join(config.getRuntimePath(), 'resources', 'skins', 'Default', '720p', 'Servers.xml'))) xbmc.executebuiltin('Action(BackSpace)') else: xbmc.sleep(700) xbmc.executebuiltin('Action(ParentDir)') if marked: from specials import videolibrary videolibrary.mark_content_as_watched(item) if next_episode and next_episode.next_ep and config.getSetting('next_ep') == 1: from platformcode.launcher import run run(next_episode) # db need to be closed when not used, it will cause freezes from core import db db.close() # If it is configured to mark as seen if config.getSetting("mark_as_watched", "videolibrary"): from core import db item = db['control'].get('playItem') db.close() threading.Thread(target=mark_as_watched_subThread, args=[item]).start() logger.debug('EXIT MONITOR') def sync_trakt_addon(path_folder): """ Updates the values ​​of episodes seen if """ logger.debug() # if the addon exists we do the search if xbmc.getCondVisibility('System.HasAddon("script.trakt")'): # we import dependencies paths = ["special://home/addons/script.module.dateutil/lib/", "special://home/addons/script.module.six/lib/", "special://home/addons/script.module.arrow/lib/", "special://home/addons/script.module.trakt/lib/", "special://home/addons/script.trakt/"] for path in paths: sys.path.append(xbmc.translatePath(path)) # the series seen is obtained try: from resources.lib.traktapi import traktAPI traktapi = traktAPI() except: return shows = traktapi.getShowsWatched({}) shows = list(shows.items()) # we get the series id to compare _id = re.findall(r"\[(.*?)\]", path_folder, flags=re.DOTALL)[0] logger.debug("the id is %s" % _id) if "tt" in _id: type_id = "imdb" elif "tvdb_" in _id: _id = _id.strip("tvdb_") type_id = "tvdb" elif "tmdb_" in _id: type_id = "tmdb" _id = _id.strip("tmdb_") else: logger.error("There is no _id of the series") return # we obtain the values ​​of the series from core import videolibrarytools tvshow_file = filetools.join(path_folder, "tvshow.nfo") head_nfo, serie = videolibrarytools.read_nfo(tvshow_file) # we look in the trakt series for show in shows: show_aux = show[1].to_dict() try: _id_trakt = show_aux['ids'].get(type_id, None) # logger.debug("ID ES %s" % _id_trakt) if _id_trakt: if _id == _id_trakt: logger.debug("FOUND! %s" % show_aux) # we create the trakt dictionary for the found series with the value that has "seen" dict_trakt_show = {} for idx_season, season in enumerate(show_aux['seasons']): for idx_episode, episode in enumerate(show_aux['seasons'][idx_season]['episodes']): sea_epi = "%sx%s" % (show_aux['seasons'][idx_season]['number'], str(show_aux['seasons'][idx_season]['episodes'][idx_episode]['number']).zfill(2)) dict_trakt_show[sea_epi] = show_aux['seasons'][idx_season]['episodes'][idx_episode]['watched'] logger.debug("dict_trakt_show %s " % dict_trakt_show) # we get the keys that are episodes regex_epi = re.compile(r'\d+x\d+') keys_episodes = [key for key in serie.library_playcounts if regex_epi.match(key)] # we get the keys that are seasons keys_seasons = [key for key in serie.library_playcounts if 'season ' in key] # we get the numbers of the seasons keys seasons = [key.strip('season ') for key in keys_seasons] # we mark the episodes watched for k in keys_episodes: serie.library_playcounts[k] = dict_trakt_show.get(k, 0) for season in seasons: episodes_temporada = 0 episodes_vistos_temporada = 0 # we obtain the keys of the episodes of a certain season keys_season_episodes = [key for key in keys_episodes if key.startswith("%sx" % season)] for k in keys_season_episodes: episodes_temporada += 1 if serie.library_playcounts[k] > 0: episodes_vistos_temporada += 1 # it is verified that if all the episodes are watched, the season is marked as watched if episodes_temporada == episodes_vistos_temporada: serie.library_playcounts.update({"season %s" % season: 1}) temporada = 0 temporada_vista = 0 for k in keys_seasons: temporada += 1 if serie.library_playcounts[k] > 0: temporada_vista += 1 # sCheck that if all seasons are viewed, the series is marked as view if temporada == temporada_vista: serie.library_playcounts.update({serie.title: 1}) logger.debug("the new values %s " % serie.library_playcounts) filetools.write(tvshow_file, head_nfo + serie.tojson()) break else: continue else: logger.error("could not get id, trakt has: %s" % show_aux['ids']) except: import traceback logger.error(traceback.format_exc()) def sync_trakt_kodi(silent=True): # So that the synchronization is not silent it is worth with silent = False if xbmc.getCondVisibility('System.HasAddon("script.trakt")'): notificacion = True if platformtools.isPlaying(): notificacion = False xbmc.executebuiltin('RunScript(script.trakt,action=sync,silent=%s)' % silent) logger.debug("Synchronization with Trakt started") if notificacion: platformtools.dialogNotification(config.getLocalizedString(20000), config.getLocalizedString(60045), sound=False, time=2000) def mark_content_as_watched_on_kodi(item, value=1): """ mark the content as seen or not seen in the Kodi library @type item: item @param item: element to mark @type value: int @param value: > 0 for seen, 0 for not seen """ logger.debug() # logger.dbg() if item.contentType == 'movie': path = '%{}%'.format(item.strm_path.split('\\')[0].split('/')[0] if item.strm_path else item.base_name) sql = 'select idMovie from movie_view where strPath like "{}"'.format(path) n, r = execute_sql_kodi(sql) if r: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": {"movieid": r[0][0], "playcount": value}, "id": 1} data = get_data(payload) logger.debug(data) elif item.contentType == 'episode': path = '%{}'.format(item.strm_path.replace('\\','%').replace('/', '%')) sql = 'select idEpisode from episode_view where c18 like "{}"'.format(path) n, r = execute_sql_kodi(sql) if r: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.SetEpisodeDetails", "params": {"episodeid": r[0][0], "playcount": value}, "id": 1} data = get_data(payload) else: nun_records, records = execute_sql_kodi('SELECT idShow FROM tvshow_view WHERE uniqueid_value LIKE "{}"'.format(item.videolibrary_id)) # delete TV show if records: tvshowid = records[0][0] nun_records, records = execute_sql_kodi('SELECT idFile FROM episode WHERE idShow={}'.format(tvshowid)) sql = 'DELETE FROM files WHERE idFile IN (?)' params = [record[0] for record in records] sql = 'DELETE FROM files WHERE idFile IN {}'.format(tuple(params)) execute_sql_kodi(sql) payload = {"jsonrpc": "2.0", "method": "VideoLibrary.RemoveTVShow", "id": 1, "params": {"tvshowid": tvshowid}} data = get_data(payload) from platformcode.dbconverter import add_video;add_video(item) def set_watched_on_kod(data): from specials import videolibrary from core import videolibrarydb data = jsontools.load(data) Type = data.get('item', {}).get('type','') ID = data.get('item', {}).get('id','') if not Type or not ID: return playcount = data.get('playcount',0) if Type in ['episode']: sql = 'select c18 from {}_view where (id{} like "{}")'.format(Type, Type.capitalize(), ID) n, records = execute_sql_kodi(sql) if records: _id = scrapertools.find_single_match(records[0][0], r'\[([^\]]+)') episode = scrapertools.find_single_match(records[0][0], r'(\d+x\d+)') season = episode.split('x')[0] episodes = videolibrarydb['episode'].get(_id, {}) item = episodes.get(episode, {}).get('item', None) elif Type in ['season']: sql = 'select season, strPath from {}_view where (id{} like "{}")'.format(Type, Type.capitalize(), ID) n, records = execute_sql_kodi(sql) if records: logger.debug('RECORDS' , records) _id = scrapertools.find_single_match(records[0][1], r'\[([^\]]+)') season = records[0][0] seasons = videolibrarydb['season'].get(_id, {}) item = seasons.get(season, None) # item.all_ep else: # logger.dbg() sql = 'select strPath from {}_view where (id{} like "{}")'.format(Type, Type.replace('tv','').capitalize(), ID) n, records = execute_sql_kodi(sql) if records: logger.debug('RECORDS' , records) _id = scrapertools.find_single_match(records[0][0], r'\[([^\]]+)') contents = videolibrarydb[Type].get(_id, {}) item = contents.get('item', None) if item: item.playcount = playcount item.not_update = True videolibrary.mark_content_as_watched(item) videolibrarydb.close() # path = filetools.join(path, filename) # head_nfo, item = videolibrarytools.read_nfo(path) # item.library_playcounts.update({title: playcount}) # filetools.write(path, head_nfo + item.tojson()) # if item.infoLabels['mediatype'] == "tvshow": # for season in item.library_playcounts: # if "season" in season: # season_num = int(scrapertools.find_single_match(season, r'season (\d+)')) # item = videolibrary.check_season_playcount(item, season_num) # filetools.write(path, head_nfo + item.tojson()) def mark_content_as_watched_on_kod(path): from specials import videolibrary from core import videolibrarytools """ mark the entire series or movie as viewed or unseen in the Alpha Video Library based on their status in the Kodi Video Library @type str: path @param path: content folder to mark """ logger.debug() #logger.debug("path: " + path) FOLDER_MOVIES = config.getSetting("folder_movies") FOLDER_TVSHOWS = config.getSetting("folder_tvshows") VIDEOLIBRARY_PATH = config.getVideolibraryConfigPath() if not VIDEOLIBRARY_PATH: return # set_watched_on_kod # We can only mark the content as a view in the Kodi database if the database is local, in case of sharing database this functionality will not work # if config.getSetting("db_mode", "videolibrary"): # return path2 = '' if "special://" in VIDEOLIBRARY_PATH: if FOLDER_TVSHOWS in path: path2 = re. sub(r'.*?%s' % FOLDER_TVSHOWS, filetools.join(VIDEOLIBRARY_PATH, FOLDER_TVSHOWS), path).replace("\\", "/") if FOLDER_MOVIES in path: path2 = re. sub(r'.*?%s' % FOLDER_MOVIES, filetools.join(VIDEOLIBRARY_PATH, FOLDER_MOVIES), path).replace("\\", "/") if "\\" in path: path = path.replace("/", "\\") head_nfo, item = videolibrarytools.read_nfo(path) # I read the content .nfo old = item.clone() if not item: logger.error('.NFO not found: ' + path) return if FOLDER_TVSHOWS in path: # I check if it is CINEMA or SERIES contentType = "episode_view" # I mark the Kodi Video BBDD table nfo_name = "tvshow.nfo" # I build the name of the .nfo path1 = path.replace("\\\\", "\\").replace(nfo_name, '') # for SQL I just need the folder if not path2: path2 = path1.replace("\\", "/") # Format no Windows else: path2 = path2.replace(nfo_name, '') else: contentType = "movie_view" # I mark the Kodi Video BBDD table path1 = path.replace("\\\\", "\\") # Windows format if not path2: path2 = path1.replace("\\", "/") # Format no Windows nfo_name = scrapertools.find_single_match(path2, r'\]\/(.*?)$') # I build the name of the .nfo path1 = path1.replace(nfo_name, '') # for SQL I just need the folder path2 = path2.replace(nfo_name, '') # for SQL I just need the folder path2 = filetools.remove_smb_credential(path2) # If the file is on an SMB server, we remove the credentials # Let's execute the SQL statement sql = 'select strFileName, playCount from %s where (strPath like "%s" or strPath like "%s")' % (contentType, path1, path2) nun_records = 0 records = None nun_records, records = execute_sql_kodi(sql) # SQL execution if nun_records == 0: # is there an error? logger.error("SQL error: " + sql + ": 0 registros") return # we quit: either it is not cataloged in Kodi, or there is an error in the SQL for title, playCount in records: # Now we go through all the records obtained if contentType == "episode_view": title_plain = title.replace('.strm', '') # If it is Serial, we remove the suffix .strm else: title_plain = scrapertools.find_single_match(item.strm_path, r'.(.*?\s\[.*?\])') # if it's a movie, we remove the title if playCount is None or playCount == 0: # not yet seen, we set it to 0 playCount_final = 0 elif playCount >= 1: playCount_final = 1 elif not PY3 and isinstance(title_plain, (str, unicode)): title_plain = title_plain.decode("utf-8").encode("utf-8") # We do this because if it doesn't generate this: u'title_plain ' elif PY3 and isinstance(title_plain, bytes): title_plain = title_plain.decode('utf-8') item.library_playcounts.update({title_plain: playCount_final}) # update the .nfo playCount if item.infoLabels['mediatype'] == "tvshow": # We update the Season and Series playCounts for season in item.library_playcounts: if "season" in season: # we look for the tags "season" inside playCounts season_num = int(scrapertools.find_single_match(season, r'season (\d+)')) # we save the season number item = videolibrary.check_season_playcount(item, season_num) # We call the method that updates Temps. and series if item.library_playcounts != old.library_playcounts: logger.debug('scrivo') filetools.write(path, head_nfo + item.tojson()) #logger.debug(item) def get_data(payload): """ get the information of the JSON-RPC call with the information passed in payload @type payload: dict @param payload: data :return: """ try: import urllib.request as urllib except ImportError: import urllib logger.debug("payload: %s" % payload) # Required header for XBMC JSON-RPC calls, otherwise you'll get a 415 HTTP response code - Unsupported media type headers = {'content-type': 'application/json'} if config.getSetting("db_mode", "videolibrary"): try: try: xbmc_port = config.getSetting("xbmc_puerto", "videolibrary") except: xbmc_port = 0 xbmc_json_rpc_url = "http://" + config.getSetting("xbmc_host", "videolibrary") + ":" + str(xbmc_port) + "/jsonrpc" req = urllib2.Request(xbmc_json_rpc_url, data=jsontools.dump(payload), headers=headers) f = urllib.urlopen(req) response = f.read() f.close() logger.debug("get_data: response %s" % response) data = jsontools.load(response) except Exception as ex: template = "An exception of type %s occured. Arguments:\n%r" message = template % (type(ex).__name__, ex.args) logger.error("error en xbmc_json_rpc_url: %s" % message) data = ["error"] else: try: data = jsontools.load(xbmc.executeJSONRPC(jsontools.dump(payload))) except Exception as ex: template = "An exception of type %s occured. Arguments:\n%r" message = template % (type(ex).__name__, ex.args) logger.error("error en xbmc.executeJSONRPC: %s" % message) data = ["error"] logger.debug("data: %s" % data) return data def update(folder_content=config.getSetting("folder_tvshows"), folder=""): """ Update the library depending on the type of content and the path passed to it. @type folder_content: str @param folder_content: type of content to update, series or movies @type folder: str @param folder: name of the folder to scan. """ logger.debug(folder) payload = { "jsonrpc": "2.0", "method": "VideoLibrary.Scan", "id": 1 } if folder: folder = str(folder) videolibrarypath = config.getVideolibraryConfigPath() if folder.endswith('/') or folder.endswith('\\'): folder = folder[:-1] update_path = None if videolibrarypath.startswith("special:"): if videolibrarypath.endswith('/'): videolibrarypath = videolibrarypath[:-1] update_path = videolibrarypath + "/" + folder_content + "/" + folder + "/" else: # update_path = filetools.join(videolibrarypath, folder_content, folder) + "/" # Encoder problems in "folder" update_path = filetools.join(videolibrarypath, folder_content, ' ').rstrip() if videolibrarypath.startswith("special:") or not scrapertools.find_single_match(update_path, r'(^\w+:\/\/)'): payload["params"] = {"directory": update_path} while xbmc.getCondVisibility('Library.IsScanningVideo()'): xbmc.sleep(500) data = get_data(payload) def search_library_path(): sql = 'SELECT strPath FROM path WHERE strPath LIKE "special://%/plugin.video.kod/library/" AND idParentPath ISNULL' nun_records, records = execute_sql_kodi(sql) if nun_records >= 1: logger.debug(records[0][0]) return records[0][0] return None def search_local_path(item): ids = [item.infoLabels['imdb_id'], item.infoLabels['tmdb_id'], item.infoLabels['tvdb_id']] for Id in ids: nun_ids, ids = execute_sql_kodi('SELECT idShow FROM tvshow_view WHERE uniqueid_value LIKE "%s"' % Id) if nun_ids >= 1: nun_records, records = execute_sql_kodi('SELECT idPath FROM tvshowlinkpath WHERE idShow LIKE "%s"' % ids[0][0]) if nun_records >= 1: for record in records: num_path, path_records = execute_sql_kodi('SELECT strPath FROM path WHERE idPath LIKE "%s"' % record[0]) for path in path_records: if config.getSetting('videolibrarypath') not in path[0]: return path[0] return '' def set_content(silent=False): logger.debug() videolibrarypath = config.getSetting("videolibrarypath") sep = '/' if '/' in videolibrarypath else '\\' paths = {'movie': filetools.join(videolibrarypath, config.getSetting('folder_movies')) + sep, 'tvshow': filetools.join(videolibrarypath, config.getSetting('folder_tvshows')) + sep} for k, v in paths.items(): sql = 'SELECT idPath, strPath FROM path where strPath= "{}"'.format(v) n, records = execute_sql_kodi(sql) if records: sql = 'update path set strScraper="metadata.local" where idPath={}'.format(records[0][0]) n, records = execute_sql_kodi(sql) else: sql ='INSERT OR IGNORE INTO path (strPath, strContent, strScraper, scanRecursive, useFolderNames, strSettings, noUpdate) VALUES ("{}", "{}", "metadata.local", 0, 0, 0, 0)'.format(v, k) n, records = execute_sql_kodi(sql) from platformcode.dbconverter import save_all; save_all() def update_db(old_path, new_path, old_movies_folder, new_movies_folder, old_tvshows_folder, new_tvshows_folder, progress): def path_replace(path, old, new): logger.debug() logger.debug('path: ' + path + ', old: ' + old + ', new: ' + new) if new.startswith("special://") or '://' in new: sep = '/' else: sep = os.sep path = path.replace(old,new) if sep == '/': path = path.replace('\\','/') else: path = path.replace('/','\\') return path logger.debug() sql_old_path = old_path if sql_old_path.startswith("special://"): sql_old_path = sql_old_path.replace('/profile/', '/%/').replace('/home/userdata/', '/%/') sep = '/' elif '://' in sql_old_path: sep = '/' else: sep = os.sep if not sql_old_path.endswith(sep): sql_old_path += sep logger.debug('sql_old_path: ' + sql_old_path) # search MAIN path in the DB sql = 'SELECT idPath, strPath FROM path where strPath LIKE "%s"' % sql_old_path logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) # change main path if records: idPath = records[0][0] strPath = path_replace(records[0][1], old_path, new_path) sql = 'UPDATE path SET strPath="%s" WHERE idPath=%s' % (strPath, idPath) logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) else: progress.update(100) xbmc.sleep(1000) progress.close() return p = 80 progress.update(p, config.getLocalizedString(20000), config.getLocalizedString(80013)) for OldFolder, NewFolder in [[old_movies_folder, new_movies_folder], [old_tvshows_folder, new_tvshows_folder]]: sql_old_folder = sql_old_path + OldFolder if not sql_old_folder.endswith(sep): sql_old_folder += sep # Search Main Sub Folder sql = 'SELECT idPath, strPath FROM path where strPath LIKE "%s"' % sql_old_folder logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) # Change Main Sub Folder if records: for record in records: idPath = record[0] strPath = path_replace(record[1], filetools.join(old_path, OldFolder), filetools.join(new_path, NewFolder)) sql = 'UPDATE path SET strPath="%s" WHERE idPath=%s' % (strPath, idPath) logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) # Search if Sub Folder exixt in all paths sql_old_folder += '%' sql = 'SELECT idPath, strPath FROM path where strPath LIKE "%s"' % sql_old_folder logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) #Change Sub Folder in all paths if records: for record in records: idPath = record[0] strPath = path_replace(record[1], filetools.join(old_path, OldFolder), filetools.join(new_path, NewFolder)) sql = 'UPDATE path SET strPath="%s" WHERE idPath=%s' % (strPath, idPath) logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) if OldFolder == old_movies_folder: # if is Movie Folder # search and modify in "movie" sql = 'SELECT idMovie, c22 FROM movie where c22 LIKE "%s"' % sql_old_folder logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) if records: for record in records: idMovie = record[0] strPath = path_replace(record[1], filetools.join(old_path, OldFolder), filetools.join(new_path, NewFolder)) sql = 'UPDATE movie SET c22="%s" WHERE idMovie=%s' % (strPath, idMovie) logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) else: # if is TV Show Folder # search and modify in "episode" sql = 'SELECT idEpisode, c18 FROM episode where c18 LIKE "%s"' % sql_old_folder logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) if records: for record in records: idEpisode = record[0] strPath = path_replace(record[1], filetools.join(old_path, OldFolder), filetools.join(new_path, NewFolder)) sql = 'UPDATE episode SET c18="%s" WHERE idEpisode=%s' % (strPath, idEpisode) logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) p += 5 progress.update(p, config.getLocalizedString(20000), config.getLocalizedString(80013)) progress.update(100) xbmc.sleep(1000) progress.close() xbmc.executebuiltin('ReloadSkin()') def clean(path_list=[]): def sql_format(path): if path.startswith("special://"): path = path.replace('/profile/', '/%/').replace('/home/userdata/', '/%/') sep = '/' elif '://' in path or path.startswith('/') or path.startswith('%/'): sep = '/' else: sep = os.sep if sep == '/': path = path.replace('\\','/') else: path = path.replace('/','\\') return path, sep logger.debug() progress = platformtools.dialogProgressBg(config.getLocalizedString(20000), config.getLocalizedString(80025)) progress.update(0) # if the path list is empty, clean the entire video library if not path_list: logger.debug('the path list is empty, clean the entire video library') if not config.getSetting("videolibrary_kodi"): sql_path, sep = sql_format(config.getSetting("videolibrarypath")) if not sql_path.endswith(sep): sql_path += sep sql = 'SELECT idPath FROM path where strPath LIKE "%s"' % sql_path logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) if records: idPath = records[0][0] sql = 'DELETE from path WHERE idPath=%s' % idPath logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) sql = 'DELETE from path WHERE idParentPath=%s' % idPath logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) from core import videolibrarytools for path, folders, files in filetools.walk(videolibrarytools.MOVIES_PATH): for folder in folders: path_list.append(filetools.join(config.getSetting("videolibrarypath"), videolibrarytools.FOLDER_MOVIES, folder)) for path, folders, files in filetools.walk(videolibrarytools.TVSHOWS_PATH): for folder in folders: tvshow_nfo = filetools.join(path, folder, "tvshow.nfo") if filetools.exists(tvshow_nfo): path_list.append(filetools.join(config.getSetting("videolibrarypath"), videolibrarytools.FOLDER_TVSHOWS, folder)) logger.debug('path_list: ' + str(path_list)) if path_list: t = float(100) / len(path_list) for i, path in enumerate(path_list): progress.update(int(math.ceil((i + 1) * t))) if not path: continue sql_path, sep = sql_format(path) if filetools.isdir(path) and not sql_path.endswith(sep): sql_path += sep logger.debug('path: ' + path) logger.debug('sql_path: ' + sql_path) if filetools.isdir(path): # search movie in the DB sql = 'SELECT idMovie FROM movie where c22 LIKE "%s"' % (sql_path + '%') logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) # delete movie if records: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.RemoveMovie", "id": 1, "params": {"movieid": records[0][0]}} data = get_data(payload) continue # search TV show in the DB sql = 'SELECT idShow FROM tvshow_view where strPath LIKE "%s"' % sql_path logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) # delete TV show if records: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.RemoveTVShow", "id": 1, "params": {"tvshowid": records[0][0]}} data = get_data(payload) elif config.getSetting("folder_movies") in sql_path: # search movie in the DB sql = 'SELECT idMovie FROM movie where c22 LIKE "%s"' % sql_path logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) # delete movie if records: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.RemoveMovie", "id": 1, "params": {"movieid": records[0][0]}} data = get_data(payload) else: # search episode in the DB sql = 'SELECT idEpisode FROM episode where c18 LIKE "%s"' % sql_path logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) # delete episode if records: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.RemoveEpisode", "id": 1, "params": {"episodeid": records[0][0]}} data = get_data(payload) progress.update(100) xbmc.sleep(1000) progress.close() def clean_by_id(item): logger.debug() # imdb_id = item.infoLabels.get('imdb_id', '') tmdb_id = item.infoLabels.get('tmdb_id', '') season_id = item.infoLabels.get('temporada_id', '') episode_id = item.infoLabels.get('episodio_id', '') # logger.dbg() # search movie ID if item.contentType == 'movie': nun_records, records = execute_sql_kodi('SELECT idMovie FROM movie_view WHERE uniqueid_value LIKE "%s"' % tmdb_id) # delete movie if records: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.RemoveMovie", "id": 1, "params": {"movieid": records[0][0]}} data = get_data(payload) return # search tvshow ID elif item.contentType == 'tvshow': nun_records, records = execute_sql_kodi('SELECT idShow FROM tvshow_view WHERE uniqueid_value LIKE "%s"' % tmdb_id) # delete TV show if records: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.RemoveTVShow", "id": 1, "params": {"tvshowid": records[0][0]}} data = get_data(payload) elif item.contentType == 'episode': nun_records, records = execute_sql_kodi('SELECT idEpisode FROM episode_view WHERE uniqueid_value LIKE "%s"' % episode_id) # delete TV show if records: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.RemoveEpisode", "id": 1, "params": {"episodeid": records[0][0]}} data = get_data(payload) elif item.contentType == 'season': nun_records, records = execute_sql_kodi('SELECT idSeason FROM season_view WHERE uniqueid_value LIKE "%s"' % season_id) # delete TV show if records: payload = {"jsonrpc": "2.0", "method": "VideoLibrary.RemoveSeason", "id": 1, "params": {"seasonid": records[0][0]}} data = get_data(payload) def check_db(path): if '\\' in path: sep = '\\' else: sep = '/' if path.endswith(sep): path = path[:-len(sep)] ret = False sql_path = '%' + sep + path.split(sep)[-1] + sep + '%' sql = 'SELECT idShow FROM tvshow_view where strPath LIKE "%s"' % sql_path logger.debug('sql: ' + sql) nun_records, records = execute_sql_kodi(sql) if records: ret = True return ret def get_file_db(): """ Return the path of MyVideos kodi db """ file_db = '' # We look for the archive of the video database according to the version of kodi video_db = config.getXBMCPlatform(True)['video_db'] if video_db: file_db = filetools.join(xbmc.translatePath("special://userdata/Database"), video_db) # alternative method to locate the database if not file_db or not filetools.exists(file_db): file_db = "" for f in filetools.listdir(xbmc.translatePath("special://userdata/Database")): path_f = filetools.join(xbmc.translatePath("special://userdata/Database"), f) if filetools.isfile(path_f) and f.lower().startswith('myvideos') and f.lower().endswith('.db'): file_db = path_f break return file_db def execute_sql_kodi(sql, params=None, conn=None): """ Run sql query against kodi database @param sql: Valid sql query @type sql: str @param params: Parameters to insert instead of ? in sql @type params: list, tuple @param conn: sqlite3 connection to use, reusing same one increase performance on multiple calls @type conn: sqlite3.Connection @return: Number of records modified or returned by the query @rtype nun_records: int @return: list with the query result @rtype records: list of tuples """ logger.debug() file_db = get_file_db() nun_records = 0 records = None if file_db: logger.debug("DB file: %s" % file_db) conn_internal = None try: if not conn: import sqlite3 conn_internal = sqlite3.connect(file_db) else: conn_internal = conn cursor = conn_internal.cursor() logger.debug("Running sql: %s" % sql) if params: if type(params) == list: cursor.executemany(sql, params) else: cursor.execute(sql, params) else: cursor.execute(sql) if sql.lower().startswith("select"): records = cursor.fetchall() nun_records = len(records) if nun_records == 1 and records[0][0] is None: nun_records = 0 records = [] else: conn_internal.commit() nun_records = conn.total_changes if not conn: conn_internal.close() logger.debug("Query executed. Records: %s" % nun_records) except: logger.error("Error executing sql query") if not conn and conn_internal: conn_internal.close() else: logger.debug("Database not found") return nun_records, records def check_sources(new_movies_path='', new_tvshows_path=''): def format_path(path): if path.startswith("special://") or '://' in path: sep = '/' else: sep = os.sep if not path.endswith(sep): path += sep return path logger.debug() new_movies_path = format_path(new_movies_path) new_tvshows_path = format_path(new_tvshows_path) SOURCES_PATH = xbmc.translatePath("special://userdata/sources.xml") if filetools.isfile(SOURCES_PATH): xmldoc = minidom.parse(SOURCES_PATH) video_node = xmldoc.childNodes[0].getElementsByTagName("video")[0] paths_node = video_node.getElementsByTagName("path") list_path = [p.firstChild.data for p in paths_node] return new_movies_path in list_path, new_tvshows_path in list_path else: xmldoc = minidom.Document() source_nodes = xmldoc.createElement("sources") for type in ['programs', 'video', 'music', 'picture', 'files']: nodo_type = xmldoc.createElement(type) element_default = xmldoc.createElement("default") element_default.setAttribute("pathversion", "1") nodo_type.appendChild(element_default) source_nodes.appendChild(nodo_type) xmldoc.appendChild(source_nodes) return False, False def update_sources(new='', old=''): logger.debug() if new == old: return SOURCES_PATH = xbmc.translatePath("special://userdata/sources.xml") if filetools.isfile(SOURCES_PATH): xmldoc = minidom.parse(SOURCES_PATH) else: xmldoc = minidom.Document() source_nodes = xmldoc.createElement("sources") for type in ['programs', 'video', 'music', 'picture', 'files']: nodo_type = xmldoc.createElement(type) element_default = xmldoc.createElement("default") element_default.setAttribute("pathversion", "1") nodo_type.appendChild(element_default) source_nodes.appendChild(nodo_type) xmldoc.appendChild(source_nodes) # collect nodes # nodes = xmldoc.getElementsByTagName("video") video_node = xmldoc.childNodes[0].getElementsByTagName("video")[0] paths_node = video_node.getElementsByTagName("path") if old: # delete old path for node in paths_node: if node.firstChild.data == old: parent = node.parentNode remove = parent.parentNode remove.removeChild(parent) # write changes if sys.version_info[0] >= 3: #PY3 filetools.write(SOURCES_PATH, '\n'.join([x for x in xmldoc.toprettyxml().splitlines() if x.strip()])) else: filetools.write(SOURCES_PATH, '\n'.join([x for x in xmldoc.toprettyxml().splitlines() if x.strip()]), vfs=False) logger.debug("The path %s has been removed from sources.xml" % old) if new: # create new path list_path = [p.firstChild.data for p in paths_node] if new in list_path: logger.debug("The path %s already exists in sources.xml" % new) return logger.debug("The path %s does not exist in sources.xml" % new) # if the path does not exist we create one source_node = xmldoc.createElement("source") # Node name_node = xmldoc.createElement("name") sep = os.sep if new.startswith("special://") or scrapertools.find_single_match(new, r'(^\w+:\/\/)'): sep = "/" name = new if new.endswith(sep): name = new[:-1] name_node.appendChild(xmldoc.createTextNode(name.rsplit(sep)[-1])) source_node.appendChild(name_node) # Node path_node = xmldoc.createElement("path") path_node.setAttribute("pathversion", "1") path_node.appendChild(xmldoc.createTextNode(new)) source_node.appendChild(path_node) # Node allowsharing_node = xmldoc.createElement("allowsharing") allowsharing_node.appendChild(xmldoc.createTextNode('true')) source_node.appendChild(allowsharing_node) # Añadimos a