# -*- coding: utf-8 -*- # ------------------------------------------------------------ # Common Library Tools # ------------------------------------------------------------ #from builtins import str import sys PY3 = False if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int import errno, math, traceback, re, os from core import filetools, scraper, scrapertools from core.item import Item from lib import generictools from platformcode import config, logger, platformtools FOLDER_MOVIES = config.get_setting("folder_movies") FOLDER_TVSHOWS = config.get_setting("folder_tvshows") VIDEOLIBRARY_PATH = config.get_videolibrary_path() MOVIES_PATH = filetools.join(VIDEOLIBRARY_PATH, FOLDER_MOVIES) TVSHOWS_PATH = filetools.join(VIDEOLIBRARY_PATH, FOLDER_TVSHOWS) if not FOLDER_MOVIES or not FOLDER_TVSHOWS or not VIDEOLIBRARY_PATH \ or not filetools.exists(MOVIES_PATH) or not filetools.exists(TVSHOWS_PATH): config.verify_directories_created() addon_name = "plugin://plugin.video.%s/" % config.PLUGIN_NAME def read_nfo(path_nfo, item=None): """ Method to read nfo files. Nfo files have the following structure: url_scraper | xml + item_json [url_scraper] and [xml] are optional, but only one of them must always exist. @param path_nfo: absolute path to nfo file @type path_nfo: str @param item: If this parameter is passed the returned item will be a copy of it with the values ​​of 'infoLabels', 'library_playcounts' and 'path' read from the nfo @type: Item @return: A tuple consisting of the header (head_nfo = 'url_scraper' | 'xml') and the object 'item_json' @rtype: tuple (str, Item) """ head_nfo = "" it = None data = filetools.read(path_nfo) if data: head_nfo = data.splitlines()[0] + "\n" data = "\n".join(data.splitlines()[1:]) it_nfo = Item().fromjson(data) if item: it = item.clone() it.infoLabels = it_nfo.infoLabels if 'library_playcounts' in it_nfo: it.library_playcounts = it_nfo.library_playcounts if it_nfo.path: it.path = it_nfo.path else: it = it_nfo if 'fanart' in it.infoLabels: it.fanart = it.infoLabels['fanart'] return head_nfo, it def save_movie(item, silent=False): """ saves the item element in the movie library, with the values ​​it contains. @type item: item @param item: item to be saved. @rtype insertados: int @return: the number of elements inserted @rtype sobreescritos: int @return: the number of overwritten elements @rtype fallidos: int @return: the number of failed items or -1 if all failed """ logger.info() # logger.debug(item.tostring('\n')) insertados = 0 sobreescritos = 0 fallidos = 0 path = "" # We try to obtain the correct title: # 1. contentTitle: This should be the correct site, since the title usually contains "Add to the video library..." # 2. fulltitle # 3. title # if item.contentTitle: item.title = item.contentTitle # elif item.fulltitle: item.title = item.fulltitle if not item.contentTitle: # We put the correct title on your site so that scraper can locate it if item.fulltitle: item.contentTitle = item.fulltitle else: item.contentTitle = item.title # If at this point we do not have a title, we leave if not item.contentTitle or not item.channel: logger.debug("contentTitle NOT FOUND") return 0, 0, -1, path # Salimos sin guardar scraper_return = scraper.find_and_set_infoLabels(item) # At this point we can have: # scraper_return = True: An item with infoLabels with the updated information of the movie # scraper_return = False: An item without movie information (it has been canceled in the window) # item.infoLabels['code'] == "" : The required IMDB identifier was not found to continue, we quit if not scraper_return or not item.infoLabels['code']: logger.debug("NOT FOUND IN SCRAPER OR DO NOT HAVE code") return 0, 0, -1, path _id = item.infoLabels['code'][0] # progress dialog if not silent: p_dialog = platformtools.dialog_progress(config.get_localized_string(20000), config.get_localized_string(60062)) if config.get_setting("original_title_folder", "videolibrary") and item.infoLabels['originaltitle']: base_name = item.infoLabels['originaltitle'] else: base_name = item.contentTitle if not PY3: base_name = unicode(filetools.validate_path(base_name.replace('/', '-')), "utf8").encode("utf8") else: base_name = filetools.validate_path(base_name.replace('/', '-')) if config.get_setting("lowerize_title", "videolibrary"): base_name = base_name.lower() for raiz, subcarpetas, ficheros in filetools.walk(MOVIES_PATH): for c in subcarpetas: code = scrapertools.find_single_match(c, r'\[(.*?)\]') if code and code in item.infoLabels['code']: path = filetools.join(raiz, c) _id = code break if not path: # Create folder path = filetools.join(MOVIES_PATH, ("%s [%s]" % (base_name, _id)).strip()) logger.info("Creating movie directory:" + path) if not filetools.mkdir(path): logger.debug("Could not create directory") return 0, 0, -1, path nfo_path = filetools.join(path, "%s [%s].nfo" % (base_name, _id)) strm_path = filetools.join(path, "%s.strm" % base_name) json_path = filetools.join(path, ("%s [%s].json" % (base_name, item.channel.lower()))) nfo_exists = filetools.exists(nfo_path) strm_exists = filetools.exists(strm_path) json_exists = filetools.exists(json_path) if not nfo_exists: # We create .nfo if it doesn't exist logger.info("Creating .nfo: " + nfo_path) head_nfo = scraper.get_nfo(item) item_nfo = Item(title=item.contentTitle, channel="videolibrary", action='findvideos', library_playcounts={"%s [%s]" % (base_name, _id): 0}, infoLabels=item.infoLabels, library_urls={}) else: # If .nfo exists, but we are adding a new channel we open it head_nfo, item_nfo = read_nfo(nfo_path) if not strm_exists: # Create base_name.strm if you do not exist item_strm = Item(channel='videolibrary', action='play_from_library', strm_path=strm_path.replace(MOVIES_PATH, ""), contentType='movie', contentTitle=item.contentTitle) strm_exists = filetools.write(strm_path, '%s?%s' % (addon_name, item_strm.tourl())) item_nfo.strm_path = strm_path.replace(MOVIES_PATH, "") # Only if item_nfo and .strm exist we continue if item_nfo and strm_exists: if json_exists: logger.info("The file exists. Is overwritten") sobreescritos += 1 else: insertados += 1 # If the emergency url option has been checked, it is added to the movie after running Findvideos from the channel try: headers = {} if item.headers: headers = item.headers channel = item.channel if config.get_setting("emergency_urls", channel) in [1, 3]: item = emergency_urls(item, None, json_path, headers=headers) if item_nfo.emergency_urls and not isinstance(item_nfo.emergency_urls, dict): del item_nfo.emergency_urls if not item_nfo.emergency_urls: item_nfo.emergency_urls = dict() item_nfo.emergency_urls.update({item.channel: True}) except: logger.error("Unable to save %s emergency urls in the video library" % item.contentTitle) logger.error(traceback.format_exc()) if filetools.write(json_path, item.tojson()): if not silent: p_dialog.update(100, item.contentTitle) item_nfo.library_urls[item.channel] = item.url if filetools.write(nfo_path, head_nfo + item_nfo.tojson()): #logger.info("FOLDER_MOVIES : %s" % FOLDER_MOVIES) # We update the Kodi video library with the movie if config.is_xbmc() and config.get_setting("videolibrary_kodi") and not silent: from platformcode import xbmc_videolibrary xbmc_videolibrary.update() if not silent: p_dialog.close() return insertados, sobreescritos, fallidos, path # If we get to this point it is because something has gone wrong logger.error("Could not save %s in the video library" % item.contentTitle) if not silent: p_dialog.update(100, item.contentTitle) p_dialog.close() return 0, 0, -1, path def update_renumber_options(item, head_nfo, path): from core import jsontools # from core.support import dbg;dbg() tvshow_path = filetools.join(path, 'tvshow.nfo') if filetools.isfile(tvshow_path) and item.channel_prefs: for channel in item.channel_prefs: filename = filetools.join(config.get_data_path(), "settings_channels", channel + '_data.json') json_file = jsontools.load(filetools.read(filename)) if 'TVSHOW_AUTORENUMBER' in json_file: json = json_file['TVSHOW_AUTORENUMBER'] if item.fulltitle in json: item.channel_prefs[channel]['TVSHOW_AUTORENUMBER'] = json[item.fulltitle] logger.info('UPDATED=\n' + str(item.channel_prefs)) filetools.write(tvshow_path, head_nfo + item.tojson()) def add_renumber_options(item, head_nfo, path): from core import jsontools # from core.support import dbg;dbg() ret = None filename = filetools.join(config.get_data_path(), "settings_channels", item.channel + '_data.json') json_file = jsontools.load(filetools.read(filename)) if 'TVSHOW_AUTORENUMBER' in json_file: json = json_file['TVSHOW_AUTORENUMBER'] if item.fulltitle in json: ret = json[item.fulltitle] return ret def check_renumber_options(item): from platformcode.autorenumber import load, write for key in item.channel_prefs: if 'TVSHOW_AUTORENUMBER' in item.channel_prefs[key]: item.channel = key json = load(item) if not json or item.fulltitle not in json: json[item.fulltitle] = item.channel_prefs[key]['TVSHOW_AUTORENUMBER'] write(item, json) # head_nfo, tvshow_item = read_nfo(filetools.join(item.context[0]['nfo'])) # if tvshow_item['channel_prefs'][item.fullti] def filter_list(episodelist, action=None, path=None): # if path: path = path.decode('utf8') # import xbmc # if xbmc.getCondVisibility('system.platform.windows') > 0: path = path.replace('smb:','').replace('/','\\') channel_prefs = {} lang_sel = quality_sel = show_title = channel ='' # from core.support import dbg;dbg() if action: tvshow_path = filetools.join(path, "tvshow.nfo") head_nfo, tvshow_item = read_nfo(tvshow_path) channel = episodelist[0].channel show_title = tvshow_item.infoLabels['tvshowtitle'] if not tvshow_item.channel_prefs: tvshow_item.channel_prefs={channel:{}} list_item = filetools.listdir(path) for File in list_item: if (File.endswith('.strm') or File.endswith('.json') or File.endswith('.nfo')): filetools.remove(filetools.join(path, File)) if channel not in tvshow_item.channel_prefs: tvshow_item.channel_prefs[channel] = {} channel_prefs = tvshow_item.channel_prefs[channel] renumber = add_renumber_options(episodelist[0], head_nfo, tvshow_path) if renumber: channel_prefs['TVSHOW_AUTORENUMBER'] = renumber if action == 'get_seasons': if 'favourite_language' not in channel_prefs: channel_prefs['favourite_language'] = '' if 'favourite_quality' not in channel_prefs: channel_prefs['favourite_quality'] = '' if channel_prefs['favourite_language']: lang_sel = channel_prefs['favourite_language'] if channel_prefs['favourite_quality']: quality_sel = channel_prefs['favourite_quality'] # if Download if not show_title: show_title = episodelist[0].fulltitle if not channel: channel= episodelist[0].channel # SELECT EISODE BY LANG AND QUALITY quality_dict = {'N/A': ['n/a'], 'BLURAY': ['br', 'bluray'], 'FULLHD': ['fullhd', 'fullhd 1080', 'fullhd 1080p', 'full hd', 'full hd 1080', 'full hd 1080p', 'hd1080', 'hd1080p', 'hd 1080', 'hd 1080p', '1080', '1080p'], 'HD': ['hd', 'hd720', 'hd720p', 'hd 720', 'hd 720p', '720', '720p', 'hdtv'], '480P': ['sd', '480p', '480'], '360P': ['360p', '360'], '240P': ['240p', '240'], 'MAX':['MAX']} quality_order = ['N/A', '240P', '360P','480P', 'HD', 'FULLHD', 'BLURAY', 'MAX'] lang_list = [] sub_list = [] quality_list = ['MAX'] # Make Language List for episode in episodelist: if type(episode.contentLanguage) == list and episode.contentLanguage not in lang_list: #lang_list = episode.contentLanguage pass else: if episode.contentLanguage and episode.contentLanguage not in lang_list: # Make list of subtitled languages if 'sub' in episode.contentLanguage.lower(): sub = re.sub('Sub-','', episode.contentLanguage) if sub not in sub_list: sub_list.append(sub) else: lang_list.append(episode.contentLanguage) # add to Language List subtitled languages if sub_list: for sub in sub_list: if sub in lang_list: lang_list.insert(lang_list.index(sub) + 1, 'Sub-' + sub) lang_list.insert(lang_list.index(sub) + 2, sub + ' + Sub-' + sub) else: lang_list.append('Sub-' + sub) # Make Quality List for episode in episodelist: for name, var in quality_dict.items(): if not episode.quality and 'N/A' not in quality_list: quality_list.append('N/A') if episode.quality.lower() in var and name not in quality_list: quality_list.append(name) quality_list = sorted(quality_list, key=lambda x:quality_order.index(x)) # if more than one language if len(lang_list) > 1: selection = lang_list.index(lang_sel) if lang_sel else platformtools.dialog_select(config.get_localized_string(70725) % (show_title, channel),lang_list) if action: lang_sel = channel_prefs['favourite_language'] = lang_list[selection] langs = lang_list[selection].split(' + ') ep_list = [] count = 0 stop = False while not stop: for episode in episodelist: title = scrapertools.find_single_match(episode.title, r'(\d+x\d+)') if not any(title in word for word in ep_list) and episode.contentLanguage == langs[count]: ep_list.append(episode.title) if count < len(langs)-1: count += 1 else: stop = True it = [] for episode in episodelist: if episode.title in ep_list: it.append(episode) episodelist = it else: channel_prefs['favourite_language'] = '' # if more than one quality if len(quality_list) > 2: if config.get_setting('videolibrary_max_quality'): selection = favourite_quality_selection = len(quality_list)-1 else: selection = favourite_quality_selection = quality_list.index(quality_sel) if quality_sel else platformtools.dialog_select(config.get_localized_string(70726) % (show_title, channel) ,quality_list) ep_list = [] stop = False while not stop: for episode in episodelist: title = scrapertools.find_single_match(episode.title, r'(\d+x\d+)') if not any(title in word for word in ep_list) and episode.quality.lower() in quality_dict[quality_list[selection]]: ep_list.append(episode.title) if selection != 0: selection = selection - 1 else: stop = True if quality_list[selection] == 'N/A': for episode in episodelist: title = scrapertools.find_single_match(episode.title, r'(\d+x\d+)') if not any(title in word for word in ep_list): ep_list.append(episode.title) it = [] for episode in episodelist: if episode.title in ep_list: if action: channel_prefs['favourite_quality'] = quality_list[favourite_quality_selection] it.append(episode) episodelist = it else:channel_prefs['favourite_quality'] = '' if action: filetools.write(tvshow_path, head_nfo + tvshow_item.tojson()) return episodelist def save_tvshow(item, episodelist, silent=False): """ stores in the series library the series with all the chapters included in the episodelist @type item: item @param item: item that represents the series to save @type episodelist: list @param episodelist: list of items that represent the episodes to be saved. @rtype insertados: int @return: the number of episodes inserted @rtype sobreescritos: int @return: the number of overwritten episodes @rtype fallidos: int @return: the number of failed episodes or -1 if the entire series has failed @rtype path: str @return: serial directory """ logger.info() # logger.debug(item.tostring('\n')) path = "" # If at this point we do not have a title or code, we leave if not (item.contentSerieName or item.infoLabels['code']) or not item.channel: logger.debug("NOT FOUND contentSerieName or code") return 0, 0, -1, path # Salimos sin guardar contentTypeBackup = item.contentType # Fix errors in some channels scraper_return = scraper.find_and_set_infoLabels(item) item.contentType = contentTypeBackup # Fix errors in some channels # At this point we can have: # scraper_return = True: An item with infoLabels with the updated information of the series # scraper_return = False: An item without movie information (it has been canceled in the window) # item.infoLabels['code'] == "" :T he required IMDB identifier was not found to continue, we quit if not scraper_return or not item.infoLabels['code']: logger.debug("NOT FOUND IN SCRAPER OR DO NOT HAVE code") return 0, 0, -1, path _id = item.infoLabels['code'][0] if not item.infoLabels['code'][0] or item.infoLabels['code'][0] == 'None': if item.infoLabels['code'][1] and item.infoLabels['code'][1] != 'None': _id = item.infoLabels['code'][1] elif item.infoLabels['code'][2] and item.infoLabels['code'][2] != 'None': _id = item.infoLabels['code'][2] else: logger.error("NOT FOUND IN SCRAPER OR HAS NO CODE: " + item.url + ' / ' + item.infoLabels['code']) return 0, 0, -1, path if config.get_setting("original_title_folder", "videolibrary") and item.infoLabels['originaltitle']: base_name = item.infoLabels['originaltitle'] elif item.infoLabels['tvshowtitle']: base_name = item.infoLabels['tvshowtitle'] elif item.infoLabels['title']: base_name = item.infoLabels['title'] else: base_name = item.contentSerieName if not PY3: base_name = unicode(filetools.validate_path(base_name.replace('/', '-')), "utf8").encode("utf8") else: base_name = filetools.validate_path(base_name.replace('/', '-')) if config.get_setting("lowerize_title", "videolibrary"): base_name = base_name.lower() for raiz, subcarpetas, ficheros in filetools.walk(TVSHOWS_PATH): for c in subcarpetas: code = scrapertools.find_single_match(c, r'\[(.*?)\]') if code and code != 'None' and code in item.infoLabels['code']: path = filetools.join(raiz, c) _id = code break if not path: path = filetools.join(TVSHOWS_PATH, ("%s [%s]" % (base_name, _id)).strip()) logger.info("Creating series directory: " + path) try: filetools.mkdir(path) except OSError as exception: if exception.errno != errno.EEXIST: raise tvshow_path = filetools.join(path, "tvshow.nfo") if not filetools.exists(tvshow_path): # We create tvshow.nfo, if it does not exist, with the head_nfo, series info and watched episode marks logger.info("Creating tvshow.nfo: " + tvshow_path) head_nfo = scraper.get_nfo(item) item.infoLabels['mediatype'] = "tvshow" item.infoLabels['title'] = item.contentSerieName item_tvshow = Item(title=item.contentSerieName, channel="videolibrary", action="get_seasons", fanart=item.infoLabels['fanart'], thumbnail=item.infoLabels['thumbnail'], infoLabels=item.infoLabels, path=path.replace(TVSHOWS_PATH, ""), fulltitle=item.fulltitle) item_tvshow.library_playcounts = {} item_tvshow.library_urls = {item.channel: item.url} else: # If tvshow.nfo exists, but we are adding a new channel we update the list of urls head_nfo, item_tvshow = read_nfo(tvshow_path) item_tvshow.fulltitle = item.fulltitle item_tvshow.channel = "videolibrary" item_tvshow.action = "get_seasons" item_tvshow.library_urls[item.channel] = item.url # FILTERTOOLS # if the channel has a language filter, we add the channel and the show if episodelist and "list_language" in episodelist[0]: # if we have already added a previously filtered channel, we add or update the channel and show if "library_filter_show" in item_tvshow: if item.title_from_channel: item_tvshow.library_filter_show[item.channel] = item.title_from_channel else: item_tvshow.library_filter_show[item.channel] = item.show # there was no filter channel and we generated it for the first time else: if item.title_from_channel: item_tvshow.library_filter_show = {item.channel: item.title_from_channel} else: item_tvshow.library_filter_show = {item.channel: item.show} if item.channel != "downloads": item_tvshow.active = 1 # to be updated daily when service is called filetools.write(tvshow_path, head_nfo + item_tvshow.tojson()) if not episodelist: # The episode list is empty return 0, 0, 0, path # Save the episodes '''import time start_time = time.time()''' insertados, sobreescritos, fallidos = save_episodes(path, episodelist, item, silent=silent) '''msg = "Insertados: %d | Sobreescritos: %d | Fallidos: %d | Tiempo: %2.2f segundos" % \ (insertados, sobreescritos, fallidos, time.time() - start_time) logger.debug(msg)''' return insertados, sobreescritos, fallidos, path def save_episodes(path, episodelist, serie, silent=False, overwrite=True): """ saves in the indicated path all the chapters included in the episodelist @type path: str @param path: path to save the episodes @type episodelist: list @param episodelist: list of items that represent the episodes to be saved. @type serie: item @param serie: series from which to save the episodes @type silent: bool @param silent: sets whether notification is displayed @param overwrite: allows to overwrite existing files @type overwrite: bool @rtype insertados: int @return: the number of episodes inserted @rtype sobreescritos: int @return: the number of overwritten episodes @rtype fallidos: int @return: the number of failed episodes """ logger.info() episodelist = filter_list(episodelist, serie.action, path) # No episode list, nothing to save if not len(episodelist): logger.info("There is no episode list, we go out without creating strm") return 0, 0, 0 # process local episodes local_episodes_path = '' local_episodelist = [] update = False nfo_path = filetools.join(path, "tvshow.nfo") head_nfo, item_nfo = read_nfo(nfo_path) if item_nfo.update_last: local_episodes_path = item_nfo.local_episodes_path elif config.get_setting("local_episodes", "videolibrary"): done, local_episodes_path = config_local_episodes_path(path, serie) if done < 0: logger.info("An issue has occurred while configuring local episodes, going out without creating strm") return 0, 0, done item_nfo.local_episodes_path = local_episodes_path filetools.write(nfo_path, head_nfo + item_nfo.tojson()) if local_episodes_path: from platformcode.xbmc_videolibrary import check_db, clean # check if the local episodes are in the Kodi video library if check_db(local_episodes_path): local_episodelist += get_local_content(local_episodes_path) clean_list = [] for f in filetools.listdir(path): match = scrapertools.find_single_match(f, r'[S]?(\d+)(?:x|_|\.)?[E]?(\d+)') if match: ep = '%dx%02d' % (int(match[0]), int(match[1])) if ep in local_episodelist: del_file = filetools.join(path, f) filetools.remove(del_file) if f.endswith('strm'): sep = '\\' if '\\' in path else '/' clean_path = path[:-len(sep)] if path.endswith(sep) else path clean_path = '%/' + clean_path.split(sep)[-1] + '/' + f clean_list.append(clean_path) clean_list.append(clean_path.replace('/','\\')) if clean_list: clean(clean_list) update = True if item_nfo.local_episodes_list: difference = [x for x in item_nfo.local_episodes_list if (x not in local_episodelist)] if len(difference) > 0: clean_list = [] for f in difference: sep = '\\' if '\\' in local_episodes_path else '/' clean_path = local_episodes_path[:-len(sep)] if local_episodes_path.endswith(sep) else local_episodes_path clean_path = '%/' + clean_path.split(sep)[-1] + '/%' + f.replace('x','%') + '%' clean_list.append(clean_path) clean_list.append(clean_path.replace('/','\\')) clean(clean_list) update = True item_nfo.local_episodes_list = sorted(local_episodelist) filetools.write(nfo_path, head_nfo + item_nfo.tojson()) # the local episodes are not in the Kodi video library else: process_local_episodes(local_episodes_path, path) insertados = 0 sobreescritos = 0 fallidos = 0 news_in_playcounts = {} # We list all the files in the series, so we avoid having to check if they exist one by one raiz, carpetas_series, ficheros = next(filetools.walk(path)) ficheros = [filetools.join(path, f) for f in ficheros] # Silent is to show no progress (for service) if not silent: # progress dialog p_dialog = platformtools.dialog_progress(config.get_localized_string(60064) ,'') # p_dialog.update(0, config.get_localized_string(60065)) channel_alt = serie.channel # We prepare to add the emergency urls emergency_urls_stat = config.get_setting("emergency_urls", channel_alt) # Does the channel want emergency urls? emergency_urls_succ = False try: channel = __import__('specials.%s' % channel_alt, fromlist=["specials.%s" % channel_alt]) except: channel = __import__('channels.%s' % channel_alt, fromlist=["channels.%s" % channel_alt]) if serie.torrent_caching_fail: # If the conversion process has failed, they are not cached emergency_urls_stat = 0 del serie.torrent_caching_fail new_episodelist = [] # We obtain the season and episode number and discard those that are not for e in episodelist: headers = {} if e.headers: headers = e.headers try: season_episode = scrapertools.get_season_and_episode(e.title) # If the emergency url option has been checked, it is added to each episode after running Findvideos from the channel if e.emergency_urls and isinstance(e.emergency_urls, dict): del e.emergency_urls # We erase previous traces json_path = filetools.join(path, ("%s [%s].json" % (season_episode, e.channel)).lower()) # Path of the episode .json if emergency_urls_stat == 1 and not e.emergency_urls and e.contentType == 'episode': # Do we keep emergency urls? if not silent: p_dialog.update(0, 'Caching links and .torren filest...\n' + e.title) # progress dialog if json_path in ficheros: # If there is the .json we get the urls from there if overwrite: # but only if .json are overwritten json_epi = Item().fromjson(filetools.read(json_path)) #We read the .json if json_epi.emergency_urls: # if there are emergency urls ... e.emergency_urls = json_epi.emergency_urls # ... we copy them else: # if not... e = emergency_urls(e, channel, json_path, headers=headers) # ... we generate them else: e = emergency_urls(e, channel, json_path, headers=headers) # If the episode does not exist, we generate the urls if e.emergency_urls: #If we already have urls... emergency_urls_succ = True # ... is a success and we are going to mark the .nfo elif emergency_urls_stat == 2 and e.contentType == 'episode': # Do we delete emergency urls? if e.emergency_urls: del e.emergency_urls emergency_urls_succ = True # ... is a success and we are going to mark the .nfo elif emergency_urls_stat == 3 and e.contentType == 'episode': # Do we update emergency urls? if not silent: p_dialog.update(0, 'Caching links and .torrent files...\n' + e.title) # progress dialog e = emergency_urls(e, channel, json_path, headers=headers) # we generate the urls if e.emergency_urls: # If we already have urls... emergency_urls_succ = True # ... is a success and we are going to mark the .nfo if not e.infoLabels["tmdb_id"] or (serie.infoLabels["tmdb_id"] and e.infoLabels["tmdb_id"] != serie.infoLabels["tmdb_id"]): #en series multicanal, prevalece el infolabels... e.infoLabels = serie.infoLabels # ... dthe current channel and not the original one e.contentSeason, e.contentEpisodeNumber = season_episode.split("x") if e.videolibray_emergency_urls: del e.videolibray_emergency_urls if e.channel_redir: del e.channel_redir # ... and redirect marks are erased new_episodelist.append(e) except: if e.contentType == 'episode': logger.error("Unable to save %s emergency urls in the video library" % e.contentTitle) continue # No episode list, nothing to save if not len(new_episodelist): logger.info("There is no episode list, we go out without creating strm") return 0, 0, 0 local_episodelist += get_local_content(path) # fix float because division is done poorly in python 2.x try: t = float(100) / len(new_episodelist) except: t = 0 for i, e in enumerate(scraper.sort_episode_list(new_episodelist)): if not silent: p_dialog.update(int(math.ceil((i + 1) * t)), e.title) high_sea = e.contentSeason high_epi = e.contentEpisodeNumber if scrapertools.find_single_match(e.title, r'[a|A][l|L]\s*(\d+)'): high_epi = int(scrapertools.find_single_match(e.title, r'al\s*(\d+)')) max_sea = e.infoLabels["number_of_seasons"] max_epi = 0 if e.infoLabels["number_of_seasons"] and (e.infoLabels["temporada_num_episodios"] or e.infoLabels["number_of_seasons"] == 1): if e.infoLabels["number_of_seasons"] == 1 and e.infoLabels["number_of_episodes"]: max_epi = e.infoLabels["number_of_episodes"] else: max_epi = e.infoLabels["temporada_num_episodios"] season_episode = "%sx%s" % (e.contentSeason, str(e.contentEpisodeNumber).zfill(2)) strm_path = filetools.join(path, "%s.strm" % season_episode) nfo_path = filetools.join(path, "%s.nfo" % season_episode) json_path = filetools.join(path, ("%s [%s].json" % (season_episode, e.channel)).lower()) if season_episode in local_episodelist: logger.info('Skipped: Serie ' + serie.contentSerieName + ' ' + season_episode + ' available as local content') continue # check if the episode has been downloaded if filetools.join(path, "%s [downloads].json" % season_episode) in ficheros: logger.info('INFO: "%s" episode %s has been downloaded, skipping it' % (serie.contentSerieName, season_episode)) continue strm_exists = strm_path in ficheros nfo_exists = nfo_path in ficheros json_exists = json_path in ficheros if not strm_exists: # If there is no season_episode.strm add it item_strm = Item(action='play_from_library', channel='videolibrary', strm_path=strm_path.replace(TVSHOWS_PATH, ""), infoLabels={}) item_strm.contentSeason = e.contentSeason item_strm.contentEpisodeNumber = e.contentEpisodeNumber item_strm.contentType = e.contentType item_strm.contentTitle = season_episode # FILTERTOOLS if item_strm.list_language: # if tvshow.nfo has a filter it is passed to the item_strm to be generated if "library_filter_show" in serie: item_strm.library_filter_show = serie.library_filter_show if item_strm.library_filter_show == "": logger.error("There was an error getting the name of the series to filter") # logger.debug("item_strm" + item_strm.tostring('\n')) # logger.debug("serie " + serie.tostring('\n')) strm_exists = filetools.write(strm_path, '%s?%s' % (addon_name, item_strm.tourl())) item_nfo = None if not nfo_exists and e.infoLabels["code"]: # If there is no season_episode.nfo add it scraper.find_and_set_infoLabels(e) head_nfo = scraper.get_nfo(e) item_nfo = e.clone(channel="videolibrary", url="", action='findvideos', strm_path=strm_path.replace(TVSHOWS_PATH, "")) if item_nfo.emergency_urls: del item_nfo.emergency_urls # It only stays in the episode's .json nfo_exists = filetools.write(nfo_path, head_nfo + item_nfo.tojson()) # Only if there are season_episode.nfo and season_episode.strm we continue if nfo_exists and strm_exists: if not json_exists or overwrite: # We get infoLabel from the episode if not item_nfo: head_nfo, item_nfo = read_nfo(nfo_path) # In multichannel series, the infolabels of the current channel prevail and not that of the original if not e.infoLabels["tmdb_id"] or (item_nfo.infoLabels["tmdb_id"] and e.infoLabels["tmdb_id"] != item_nfo.infoLabels["tmdb_id"]): e.infoLabels = item_nfo.infoLabels if filetools.write(json_path, e.tojson()): if not json_exists: logger.info("Inserted: %s" % json_path) insertados += 1 # We mark episode as unseen news_in_playcounts[season_episode] = 0 # We mark the season as unseen news_in_playcounts["season %s" % e.contentSeason] = 0 # We mark the series as unseen # logger.debug("serie " + serie.tostring('\n')) news_in_playcounts[serie.contentSerieName] = 0 else: logger.info("Overwritten: %s" % json_path) sobreescritos += 1 else: logger.info("Failed: %s" % json_path) fallidos += 1 else: logger.info("Failed: %s" % json_path) fallidos += 1 if not silent and p_dialog.iscanceled(): break #logger.debug('high_sea x high_epi: %sx%s' % (str(high_sea), str(high_epi))) #logger.debug('max_sea x max_epi: %sx%s' % (str(max_sea), str(max_epi))) if not silent: p_dialog.close() if news_in_playcounts or emergency_urls_succ or serie.infoLabels["status"] == "Ended" or serie.infoLabels["status"] == "Canceled": # If there are new episodes we mark them as unseen on tvshow.nfo ... tvshow_path = filetools.join(path, "tvshow.nfo") try: import datetime head_nfo, tvshow_item = read_nfo(tvshow_path) tvshow_item.library_playcounts.update(news_in_playcounts) # If the emergency url insert / delete operation in the .jsons of the episodes was successful, the .nfo is checked if emergency_urls_succ: if tvshow_item.emergency_urls and not isinstance(tvshow_item.emergency_urls, dict): del tvshow_item.emergency_urls if emergency_urls_stat in [1, 3]: # Save / update links operation if not tvshow_item.emergency_urls: tvshow_item.emergency_urls = dict() if tvshow_item.library_urls.get(serie.channel, False): tvshow_item.emergency_urls.update({serie.channel: True}) elif emergency_urls_stat == 2: # Delete links operation if tvshow_item.emergency_urls and tvshow_item.emergency_urls.get(serie.channel, False): tvshow_item.emergency_urls.pop(serie.channel, None) # delete the entry of the .nfo if tvshow_item.active == 30: tvshow_item.active = 1 if tvshow_item.infoLabels["tmdb_id"] == serie.infoLabels["tmdb_id"]: tvshow_item.infoLabels = serie.infoLabels tvshow_item.infoLabels["title"] = tvshow_item.infoLabels["tvshowtitle"] if max_sea == high_sea and max_epi == high_epi and (tvshow_item.infoLabels["status"] == "Ended" or tvshow_item.infoLabels["status"] == "Canceled") and insertados == 0 and fallidos == 0 and not tvshow_item.local_episodes_path: tvshow_item.active = 0 # ... nor we will update it more logger.debug("%s [%s]: 'Finished' or 'Canceled' series. Periodic update is disabled" % (serie.contentSerieName, serie.channel)) update_last = datetime.date.today() tvshow_item.update_last = update_last.strftime('%Y-%m-%d') update_next = datetime.date.today() + datetime.timedelta(days=int(tvshow_item.active)) tvshow_item.update_next = update_next.strftime('%Y-%m-%d') filetools.write(tvshow_path, head_nfo + tvshow_item.tojson()) except: logger.error("Error updating tvshow.nfo") logger.error("Unable to save %s emergency urls in the video library" % serie.contentSerieName) logger.error(traceback.format_exc()) fallidos = -1 else: # ... if it was correct we update the Kodi video library if config.is_xbmc() and config.get_setting("videolibrary_kodi") and not silent: update = True if update: from platformcode import xbmc_videolibrary xbmc_videolibrary.update() if fallidos == len(episodelist): fallidos = -1 logger.debug("%s [%s]: inserted= %s, overwritten= %s, failed= %s" % (serie.contentSerieName, serie.channel, insertados, sobreescritos, fallidos)) return insertados, sobreescritos, fallidos def config_local_episodes_path(path, item, silent=False): logger.info(item) from platformcode.xbmc_videolibrary import search_local_path local_episodes_path=search_local_path(item) if not local_episodes_path: title = item.contentSerieName if item.contentSerieName else item.show if not silent: silent = platformtools.dialog_yesno(config.get_localized_string(30131), config.get_localized_string(80044) % title) if silent: if config.is_xbmc() and not config.get_setting("videolibrary_kodi"): platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(80043)) local_episodes_path = platformtools.dialog_browse(0, config.get_localized_string(80046)) if local_episodes_path == '': logger.info("User has canceled the dialog") return -2, local_episodes_path elif path in local_episodes_path: platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(80045)) logger.info("Selected folder is the same of the TV show one") return -2, local_episodes_path if local_episodes_path: # import artwork artwork_extensions = ['.jpg', '.jpeg', '.png'] files = filetools.listdir(local_episodes_path) for file in files: if os.path.splitext(file)[1] in artwork_extensions: filetools.copy(filetools.join(local_episodes_path, file), filetools.join(path, file)) return 0, local_episodes_path def process_local_episodes(local_episodes_path, path): logger.info() sub_extensions = ['.srt', '.sub', '.sbv', '.ass', '.idx', '.ssa', '.smi'] artwork_extensions = ['.jpg', '.jpeg', '.png'] extensions = sub_extensions + artwork_extensions local_episodes_list = [] files_list = [] for root, folders, files in filetools.walk(local_episodes_path): for file in files: if os.path.splitext(file)[1] in extensions: continue season_episode = scrapertools.get_season_and_episode(file) if season_episode == "": continue local_episodes_list.append(season_episode) files_list.append(file) nfo_path = filetools.join(path, "tvshow.nfo") head_nfo, item_nfo = read_nfo(nfo_path) # if a local episode has been added, overwrites the strm for season_episode, file in zip(local_episodes_list, files_list): if not season_episode in item_nfo.local_episodes_list: filetools.write(filetools.join(path, season_episode + '.strm'), filetools.join(root, file)) # if a local episode has been removed, deletes the strm for season_episode in set(item_nfo.local_episodes_list).difference(local_episodes_list): filetools.remove(filetools.join(path, season_episode + '.strm')) # updates the local episodes path and list in the nfo if not local_episodes_list: item_nfo.local_episodes_path = '' item_nfo.local_episodes_list = sorted(set(local_episodes_list)) filetools.write(nfo_path, head_nfo + item_nfo.tojson()) def get_local_content(path): logger.info() local_episodelist = [] for root, folders, files in filetools.walk(path): for file in files: season_episode = scrapertools.get_season_and_episode(file) if season_episode == "" or filetools.exists(filetools.join(path, "%s.strm" % season_episode)): continue local_episodelist.append(season_episode) local_episodelist = sorted(set(local_episodelist)) return local_episodelist def add_movie(item): """ Keep a movie at the movie library. The movie can be a link within a channel or a previously downloaded video. To add locally downloaded episodes, the item must have exclusively: - contentTitle: title of the movie - title: title to show next to the list of links -findvideos- ("Play local HD video") - infoLabels ["tmdb_id"] o infoLabels ["imdb_id"] - contentType == "movie" - channel = "downloads" - url: local path to the video @type item: item @param item: item to be saved. """ logger.info() from platformcode.launcher import set_search_temp; set_search_temp(item) # To disambiguate titles, TMDB is caused to ask for the really desired title # The user can select the title among those offered on the first screen # or you can cancel and enter a new title on the second screen # If you do it in "Enter another name", TMDB will automatically search for the new title # If you do it in "Complete Information", it partially changes to the new title, but does not search TMDB. We have to do it # If the second screen is canceled, the variable "scraper_return" will be False. The user does not want to continue item = generictools.update_title(item) # We call the method that updates the title with tmdb.find_and_set_infoLabels #if item.tmdb_stat: # del item.tmdb_stat # We clean the status so that it is not recorded in the Video Library if item: new_item = item.clone(action="findvideos") insertados, sobreescritos, fallidos, path = save_movie(new_item) if fallidos == 0: platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(30135) % new_item.contentTitle) # 'has been added to the video library' else: filetools.rmdirtree(path) platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60066) % new_item.contentTitle) # "ERROR, the movie has NOT been added to the video library") def add_tvshow(item, channel=None): """ Save content in the series library. This content can be one of these two: - The series with all the chapters included in the episodelist. - A single chapter previously downloaded locally. To add locally downloaded episodes, the item must have exclusively: - contentSerieName (or show): Title of the series - contentTitle: title of the episode to extract season_and_episode ("1x01 Pilot") - title: title to show next to the list of links -findvideos- ("Play local video") - infoLabels ["tmdb_id"] o infoLabels ["imdb_id"] - contentType != "movie" - channel = "downloads" - url: local path to the video @type item: item @param item: item that represents the series to save @type channel: modulo @param channel: channel from which the series will be saved. By default, item.from_channel or item.channel will be imported. """ logger.info("show=#" + item.show + "#") from platformcode.launcher import set_search_temp; set_search_temp(item) if item.channel == "downloads": itemlist = [item.clone()] else: # This mark is because the item has something else apart in the "extra" attribute # item.action = item.extra if item.extra else item.action if isinstance(item.extra, str) and "###" in item.extra: item.action = item.extra.split("###")[0] item.extra = item.extra.split("###")[1] if item.from_action: item.__dict__["action"] = item.__dict__.pop("from_action") if item.from_channel: item.__dict__["channel"] = item.__dict__.pop("from_channel") if not channel: try: channel = __import__('channels.%s' % item.channel, fromlist=["channels.%s" % item.channel]) # channel = __import__('specials.%s' % item.channel, fromlist=["specials.%s" % item.channel]) except ImportError: exec("import channels." + item.channel + " as channel") # To disambiguate titles, TMDB is caused to ask for the really desired title # The user can select the title among those offered on the first screen # or you can cancel and enter a new title on the second screen # If you do it in "Enter another name", TMDB will automatically search for the new title # If you do it in "Complete Information", it partially changes to the new title, but does not search TMDB. We have to do it # If the second screen is canceled, the variable "scraper_return" will be False. The user does not want to continue item = generictools.update_title(item) # We call the method that updates the title with tmdb.find_and_set_infoLabels #if item.tmdb_stat: # del item.tmdb_stat # We clean the status so that it is not recorded in the Video Library # Get the episode list # from core.support import dbg;dbg() itemlist = getattr(channel, item.action)(item) if itemlist and not scrapertools.find_single_match(itemlist[0].title, r'(\d+x\d+)'): from platformcode.autorenumber import start, check if not check(item): action = item.action item.renumber = True start(item) item.renumber = False item.action = action return add_tvshow(item, channel) else: itemlist = getattr(channel, item.action)(item) global magnet_caching magnet_caching = False insertados, sobreescritos, fallidos, path = save_tvshow(item, itemlist) if not insertados and not sobreescritos and not fallidos: filetools.rmdirtree(path) platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60067) % item.show) logger.error("The string %s could not be added to the video library. Could not get any episode" % item.show) elif fallidos == -1: filetools.rmdirtree(path) platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60068) % item.show) logger.error("The string %s could not be added to the video library" % item.show) elif fallidos == -2: filetools.rmdirtree(path) elif fallidos > 0: platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60069) % item.show) logger.error("Could not add %s episodes of series %s to the video library" % (fallidos, item.show)) else: platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60070) % item.show) logger.info("%s episodes of series %s have been added to the video library" % (insertados, item.show)) if config.is_xbmc(): if config.get_setting("sync_trakt_new_tvshow", "videolibrary"): import xbmc from platformcode import xbmc_videolibrary if config.get_setting("sync_trakt_new_tvshow_wait", "videolibrary"): # Check that you are not looking for content in the Kodi video library while xbmc.getCondVisibility('Library.IsScanningVideo()'): xbmc.sleep(1000) # Synchronization for Kodi video library launched xbmc_videolibrary.sync_trakt_kodi() # Synchronization for the addon video library is launched xbmc_videolibrary.sync_trakt_addon(path) def emergency_urls(item, channel=None, path=None, headers={}): logger.info() import re from servers import torrent try: magnet_caching_e = magnet_caching except: magnet_caching_e = True """ We call Findvideos of the channel with the variable "item.videolibray_emergency_urls = True" to get the variable "item.emergency_urls" with the list of tuple lists of torrent links and direct servers for that episode or movie Torrents should always go in list [0], if any. If you want to cache the .torrents, the search goes against that list. List two will include direct server links, but also magnet links (which are not cacheable). """ # we launched a "lookup" in the "findvideos" of the channel to obtain the emergency links try: if channel == None: # If the caller has not provided the channel structure, it is created channel = item.channel # It is verified if it is a clone, which returns "newpct1" #channel = __import__('channels.%s' % channel, fromlist=["channels.%s" % channel]) channel = __import__('specials.%s' % channel_alt, fromlist=["specials.%s" % channel_alt]) if hasattr(channel, 'findvideos'): # If the channel has "findvideos" ... item.videolibray_emergency_urls = True # ... marks itself as "lookup" channel_save = item.channel # ... save the original channel in case of fail-over in Newpct1 category_save = item.category # ... save the original category in case of fail-over or redirection in Newpct1 if item.channel_redir: # ... if there is a redir, the alternate channel is temporarily restored item.channel = scrapertools.find_single_match(item.url, r'http.?\:\/\/(?:www.)?(\w+)\.\w+\/').lower() item.category = scrapertools.find_single_match(item.url, r'http.?\:\/\/(?:www.)?(\w+)\.\w+\/').capitalize() item_res = getattr(channel, 'findvideos')(item) # ... the process of Findvideos item_res.channel = channel_save # ... restore the original channel in case there is a fail-over in Newpct1 item_res.category = category_save # ... restore the original category in case there is a fail-over or redirection in Newpct1 item.category = category_save # ... restore the original category in case there is a fail-over or redirection in Newpct1 del item_res.videolibray_emergency_urls # ... and the lookup mark is erased if item.videolibray_emergency_urls: del item.videolibray_emergency_urls # ... and the original lookup mark is erased except: logger.error('ERROR when processing the title in Findvideos del Canal: ' + item.channel + ' / ' + item.title) logger.error(traceback.format_exc()) item.channel = channel_save # ... restore the original channel in case of fail-over or redirection in Newpct1 item.category = category_save # ... restore the original category in case there is a fail-over or redirection in Newpct1 item_res = item.clone() # If there has been an error, the original Item is returned if item_res.videolibray_emergency_urls: del item_res.videolibray_emergency_urls # ... and the lookup mark is erased if item.videolibray_emergency_urls: del item.videolibray_emergency_urls # ... and the original lookup mark is erased # If the user has activated the option "emergency_urls_torrents", the .torrent files of each title will be downloaded else: # If the links have been successfully cached ... try: referer = None post = None channel_bis =item.channel if config.get_setting("emergency_urls_torrents", channel_bis) and item_res.emergency_urls and path != None: videolibrary_path = config.get_videolibrary_path() # we detect the absolute path of the title movies = config.get_setting("folder_movies") series = config.get_setting("folder_tvshows") if movies in path: folder = movies else: folder = series videolibrary_path = filetools.join(videolibrary_path, folder) i = 1 if item_res.referer: referer = item_res.referer if item_res.post: post = item_res.post for url in item_res.emergency_urls[0]: # We go through the emergency urls ... torrents_path = re.sub(r'(?:\.\w+$)', '_%s.torrent' % str(i).zfill(2), path) path_real = '' if magnet_caching_e or not url.startswith('magnet'): path_real = torrent.caching_torrents(url, referer, post, torrents_path=torrents_path, headers=headers) # ... to download the .torrents if path_real: # If you have been successful ... item_res.emergency_urls[0][i-1] = path_real.replace(videolibrary_path, '') # if it looks at the relative "path" i += 1 # We restore original variables if item.referer: item_res.referer = item.referer elif item_res.referer: del item_res.referer if item.referer: item_res.referer = item.referer elif item_res.referer: del item_res.referer item_res.url = item.url except: logger.error('ERROR when caching the .torrent of: ' + item.channel + ' / ' + item.title) logger.error(traceback.format_exc()) item_res = item.clone() # If there has been an error, the original Item is returned #logger.debug(item_res.emergency_urls) return item_res # We return the updated Item with the emergency links