diff --git a/Contents/Code/__init__.py b/Contents/Code/__init__.py index 97036ba..84aaa90 100644 --- a/Contents/Code/__init__.py +++ b/Contents/Code/__init__.py @@ -74,15 +74,40 @@ def Search(self, results, media, lang, manual, movie): # http://127.0.0.1:8111/api/serie/search?query=Clannad&level=1&apikey=d422dfd2-bdc3-4219-b3bb-08b85aa65579 - prelimresults = HttpReq("api/serie/search?query=%s&level=%d&fuzzy=%d" % (urllib.quote(name.encode('utf8')), 1, Prefs['Fuzzy'])) + if movie: + if media.filename: + filename = os.path.basename(urllib.unquote(media.filename)) - for result in prelimresults: - #for result in group['series']: - score = 100 if result['name'] == name else 85 # TODO: Improve this to respect synonyms./ - meta = MetadataSearchResult('%s' % result['id'], result['name'], try_get(result, 'year', None), score, lang) - results.Append(meta) + episode_data = HttpReq("api/ep/getbyfilename?filename=%s" % (urllib.quote(filename.encode('utf8')))) + movie_data = HttpReq("api/serie/fromep?id=%s" % (episode_data['id'])) - # results.Sort('score', descending=True) + score = 100 if movie_data['name'] == name else 85 # TODO: Improve this to respect synonyms./ + title = movie_data['name'] + ' - ' + episode_data['name'] + meta = MetadataSearchResult('%s' % (episode_data['id']), title, try_get(episode_data, 'year', None), score, lang) + results.Append(meta) + + else: # For manual searches + prelimresults = HttpReq("api/serie/search?query=%s&level=%d&fuzzy=%d&ismovie=1" % (urllib.quote(name.encode('utf8')), 2, Prefs['Fuzzy'])) + + for result in prelimresults: + for episode in result['eps']: + title = result['name'] + ' - ' + episode['name'] + if title == name: score = 100 # Check if full name matches (Series name + episode name) + elif result['name'] == name: score = 90 # Check if series name matches + else: score = 80 + meta = MetadataSearchResult('%s' % (episode['id']), title, try_get(episode, 'year', None), score, lang) + results.Append(meta) + + else: + prelimresults = HttpReq("api/serie/search?query=%s&level=%d&fuzzy=%d" % (urllib.quote(name.encode('utf8')), 1, Prefs['Fuzzy'])) + + for result in prelimresults: + #for result in group['series']: + score = 100 if result['name'] == name else 85 # TODO: Improve this to respect synonyms./ + meta = MetadataSearchResult('%s' % result['id'], result['name'], try_get(result, 'year', None), score, lang) + results.Append(meta) + + # results.Sort('score', descending=True) def Update(self, metadata, media, lang, force, movie): Log("update(%s)" % metadata.id) @@ -100,18 +125,37 @@ def Update(self, metadata, media, lang, force, movie): flags = flags | Prefs['hideUsefulMiscTags'] << 3 #0b01000 : Hide Useful Miscellaneous Tags flags = flags | Prefs['hideSpoilerTags'] << 4 #0b10000 : Hide Plot Spoiler Tags + if movie: + series = HttpReq("api/serie/fromep?id=%s&level=3&allpics=1&tagfilter=%d" % (aid, flags)) + movie_episode_data = HttpReq("api/ep?id=%s" % (aid)) + aid = try_get(series, 'id', None) + if not aid: + Log('Error! Series not found.') + return + + if movie_episode_data['name'] == 'Complete Movie': + movie_name = series['name'] + movie_sort_name = series['name'] + else: + movie_name = series['name'] + ' - ' + movie_episode_data['name'] + movie_sort_name = series['name'] + ' - ' + str(movie_episode_data['epnumber']).zfill(3) + + metadata.summary = summary_sanitizer(try_get(series, 'summary')) + metadata.title = movie_name + metadata.title_sort = movie_sort_name + metadata.rating = float(movie_episode_data['rating']) + year = try_get(movie_episode_data, "year", try_get(series, "year", None)) + + if year: + metadata.year = int(year) - series = HttpReq("api/serie?id=%s&level=3&allpics=1&tagfilter=%d" % (aid, flags)) + else: + series = HttpReq("api/serie?id=%s&level=3&allpics=1&tagfilter=%d" % (aid, flags)) - # build metadata on the TV show. - #metadata.summary = re.sub(LINK_REGEX, r'\1', try_get(series, 'summary')) - metadata.summary = summary_sanitizer(try_get(series, 'summary')) - metadata.title = series['name'] - metadata.rating = float(series['rating']) - year = try_get(series, "year", None) + metadata.summary = summary_sanitizer(try_get(series, 'summary')) + metadata.title = series['name'] + metadata.rating = float(series['rating']) - #if year: - # metadata.year = int(year) tags = [] for tag in try_get(series, 'tags', []): @@ -184,13 +228,14 @@ def Update(self, metadata, media, lang, force, movie): if ep['eptype'] == "Episode": season = 1 elif ep['eptype'] == "Special": season = 0 elif ep['eptype'] == "Credits": season = -1 - elif ep['eptype'] == "Trailer": season = -2; - try: - season = int(ep['season'].split('x')[0]) - if season <= 0 and ep['eptype'] == 'Episode': season = 1 - elif season > 0 and ep['eptype'] == 'Special': season = 0 - except: - pass + elif ep['eptype'] == "Trailer": season = -2 + if not Prefs['SingleSeasonOrdering']: + try: + season = int(ep['season'].split('x')[0]) + if season <= 0 and ep['eptype'] == 'Episode': season = 1 + elif season > 0 and ep['eptype'] == 'Special': season = 0 + except: + pass episodeObj = metadata.seasons[season].episodes[ep['epnumber']] episodeObj.title = ep['name'] diff --git a/Contents/DefaultPrefs.json b/Contents/DefaultPrefs.json index 9b24868..7c312eb 100644 --- a/Contents/DefaultPrefs.json +++ b/Contents/DefaultPrefs.json @@ -29,6 +29,13 @@ "default": "8111" }, + { + "id": "SingleSeasonOrdering", + "label": "Use single season ordering (IMPORTANT: Make sure the setting in scanner matches with this!)", + "type": "bool", + "default": false + }, + { "id": "Fuzzy", "label": "Use fuzzy searching in Shoko for matching the titles.", diff --git a/Contents/Resources/Movies/Shoko Movie Scanner.py b/Contents/Resources/Movies/Shoko Movie Scanner.py index b6731a8..8d35baa 100644 --- a/Contents/Resources/Movies/Shoko Movie Scanner.py +++ b/Contents/Resources/Movies/Shoko Movie Scanner.py @@ -8,22 +8,46 @@ 'Port': 8111, 'Username': 'Default', 'Password': '', - 'IncludeOther': True } API_KEY = '' -def log(methodName, message, *args): - ''' - Create a log message given the message and arguments - ''' - logMsg = message - # Replace the arguments in the string - if args: - logMsg = message % args - - logMsg = methodName + ' :: ' + logMsg - print logMsg +### Log + LOG_PATH calculated once for all calls ### +import logging, logging.handlers # +RootLogger = logging.getLogger('main') +RootHandler = None +RootFormatting = logging.Formatter('%(message)s') #%(asctime)-15s %(levelname)s - +RootLogger.setLevel(logging.DEBUG) +Log = RootLogger + +FileListLogger = logging.getLogger('FileListLogger') +FileListHandler = None +FileListFormatting = logging.Formatter('%(message)s') +FileListLogger.setLevel(logging.DEBUG) +LogFileList = FileListLogger.info + +def set_logging(instance, filename): + global RootLogger, RootHandler, RootFormatting, FileListLogger, FileListHandler, FileListFormatting + logger, handler, formatting, backup_count = [RootLogger, RootHandler, RootFormatting, 9] if instance=="Root" else [FileListLogger, FileListHandler, FileListFormatting, 1] + if handler: logger.removeHandler(handler) + handler = logging.handlers.RotatingFileHandler(os.path.join(LOG_PATH, filename), maxBytes=10*1024*1024, backupCount=backup_count) #handler = logging.FileHandler(os.path.join(LOG_PATH, filename), mode) + #handler.setFormatter(formatting) + handler.setLevel(logging.DEBUG) + logger.addHandler(handler) + if instance=="Root": RootHandler = handler + else: FileListHandler = handler + +### Check config files on boot up then create library variables ### #platform = xxx if callable(getattr(sys,'platform')) else "" +import inspect +LOG_PATH = os.path.abspath(os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), "..", "..", "Logs")) +if not os.path.isdir(LOG_PATH): + path_location = { 'Windows': '%LOCALAPPDATA%\\Plex Media Server', + 'MacOSX': '$HOME/Library/Application Support/Plex Media Server', + 'Linux': '$PLEX_HOME/Library/Application Support/Plex Media Server' } + try: path = os.path.expandvars(path_location[Platform.OS.lower()] if Platform.OS.lower() in path_location else '~') # Platform.OS: Windows, MacOSX, or Linux + except: pass #os.makedirs(LOG_PATH) # User folder on MacOS-X +LOG_FILE_LIBRARY = LOG_FILE = 'Shoko Metadata Movie Scanner.log' # Log filename library will include the library name, LOG_FILE not and serve as reference +set_logging("Root", LOG_FILE_LIBRARY) def HttpPost(url, postdata): @@ -33,16 +57,24 @@ def HttpPost(url, postdata): return json.load(urllib2.urlopen(req, postdata)) -def HttpReq(url, authenticate=True): - log('HttpReq' ,"Requesting: %s", url) +def HttpReq(url, authenticate=True, retry=True): + global API_KEY + Log.info("Requesting: %s", url) api_string = '' if authenticate: api_string = '&apikey=%s' % GetApiKey() myheaders = {'Accept': 'application/json'} - req = urllib2.Request('http://%s:%s/%s%s' % (Prefs['Hostname'], Prefs['Port'], url, api_string), headers=myheaders) - return json.load(urllib2.urlopen(req)) + try: + req = urllib2.Request('http://%s:%s/%s%s' % (Prefs['Hostname'], Prefs['Port'], url, api_string), headers=myheaders) + return json.load(urllib2.urlopen(req)) + except Exception, e: + if not retry: + raise e + + API_KEY = '' + return HttpReq(url, authenticate, False) def GetApiKey(): @@ -52,7 +84,7 @@ def GetApiKey(): data = '{"user":"%s", "pass":"%s", "device":"%s"}' % ( Prefs['Username'], Prefs['Password'] if Prefs['Password'] != None else '', 'Shoko Series Scanner For Plex') resp = HttpPost('api/auth', data)['apikey'] - log('GetApiKey', "Got API KEY: %s", resp) + Log.info( "Got API KEY: %s", resp) API_KEY = resp return resp @@ -60,53 +92,42 @@ def GetApiKey(): def Scan(path, files, mediaList, subdirs, language=None, root=None): - log('Scan', 'path: %s', path) - log('Scan', 'files: %s', files) - log('Scan', 'mediaList: %s', mediaList) - log('Scan', 'subdirs: %s', subdirs) - log('Scan', 'language: %s', language) - log('Scan', 'root: %s', root) - + + Log.debug('path: %s', path) + Log.debug('files: %s', files) + Log.debug('subdirs: %s', subdirs) + # Scan for video files. VideoFiles.Scan(path, files, mediaList, subdirs, root) for idx, file in enumerate(files): - log('Scan', 'file: %s', file) - # http://127.0.0.1:8111/api/ep/getbyfilename?apikey=d422dfd2-bdc3-4219-b3bb-08b85aa65579&filename=%5Bjoseole99%5D%20Clannad%20-%2001%20(1280x720%20Blu-ray%20H264)%20%5B8E128DF5%5D.mkv - - episode_data = HttpReq("api/ep/getbyfilename?filename=%s" % (urllib.quote(os.path.basename(file)))) - if len(episode_data) == 0: break - if (try_get(episode_data, "code", 200) == 404): break - - series_data = HttpReq("api/serie/fromep?id=%d&nocast=1¬ag=1" % episode_data['id']) - showTitle = series_data['name'].encode("utf-8") #no idea why I need to do this. - log('Scan', 'show title: %s', showTitle) - - seasonYear = episode_data['year'] - log('Scan', 'season year: %s', seasonYear) - seasonNumber = 0 - seasonStr = try_get(episode_data, 'season', None) - if episode_data['eptype'] == 'Credits': seasonNumber = -1 #season -1 for OP/ED - elif episode_data['eptype'] == 'Trailer': seasonNumber = -2 #season -2 for Trailer - elif seasonStr == None: - if episode_data['eptype'] == 'Episode': seasonNumber = 1 - elif episode_data['eptype'] == 'Special': seasonNumber = 0 - else: - seasonNumber = int(seasonStr.split('x')[0]) - if seasonNumber <= 0 and episode_data['eptype'] == 'Episode': seasonNumber = 1 - - if seasonNumber <= 0 and Prefs['IncludeOther'] == False: continue #Ignore this by choice. - - if (try_get(series_data, "ismovie", 0) == 0 or seasonNumber <= 0): - continue - vid = Media.Movie(showTitle, int(seasonYear)) - log('Scan', 'vid: %s', vid) - vid.parts.append(file) - mediaList.append(vid) - - log('Scan', 'stack media') + try: + Log.info('file: %s', file) + # http://127.0.0.1:8111/api/ep/getbyfilename?apikey=d422dfd2-bdc3-4219-b3bb-08b85aa65579&filename=%5Bjoseole99%5D%20Clannad%20-%2001%20(1280x720%20Blu-ray%20H264)%20%5B8E128DF5%5D.mkv + + episode_data = HttpReq("api/ep/getbyfilename?filename=%s" % (urllib.quote(os.path.basename(file)))) + if len(episode_data) == 0: continue + if (try_get(episode_data, "code", 200) == 404): continue + + series_data = HttpReq("api/serie/fromep?id=%d&nocast=1¬ag=1" % episode_data['id']) + if not (try_get(series_data, "ismovie", 0)) or (episode_data['eptype'] != 'Episode'): continue # Skip series and extras + showTitle = series_data['name'].encode("utf-8") #no idea why I need to do this. + Log.info('show title: %s', showTitle) + + seasonYear = episode_data['year'] + Log.info('season year: %s', seasonYear) + + vid = Media.Movie(showTitle, int(seasonYear)) + Log.info('vid: %s', vid) + vid.parts.append(file) + mediaList.append(vid) + except Exception as e: + Log.error("Error in Scan: '%s'" % e) + continue + + Log.info('Scan', 'stack media') Stack.Scan(path, files, mediaList, subdirs) - log('Scan', 'media list %s', mediaList) + Log.info('Scan', 'media list %s', mediaList) def try_get(arr, idx, default=""): diff --git a/Contents/Resources/Series/Shoko Series Scanner.py b/Contents/Resources/Series/Shoko Series Scanner.py index 55d3635..3757671 100644 --- a/Contents/Resources/Series/Shoko Series Scanner.py +++ b/Contents/Resources/Series/Shoko Series Scanner.py @@ -8,7 +8,9 @@ 'Port': 8111, 'Username': 'Default', 'Password': '', - 'IncludeOther': True + 'IncludeSpecials': True, + 'IncludeOther': True, + 'SingleSeasonOrdering': False } API_KEY = '' @@ -93,58 +95,101 @@ def GetApiKey(): def Scan(path, files, mediaList, subdirs, language=None, root=None): - try: - Log.debug('path: %s', path) - Log.debug('files: %s', files) - Log.debug('mediaList: %s', mediaList) - Log.debug('subdirs: %s', subdirs) - Log.debug('language: %s', language) - Log.info('root: %s', root) + Log.debug('path: %s', path) + Log.debug('files: %s', files) + + for subdir in subdirs: + Log.info("[folder] " + os.path.relpath(subdir, root)) + + if files: # Scan for video files. VideoFiles.Scan(path, files, mediaList, subdirs, root) for idx, file in enumerate(files): - Log.info('file: %s', file) - # http://127.0.0.1:8111/api/ep/getbyfilename?apikey=d422dfd2-bdc3-4219-b3bb-08b85aa65579&filename=%5Bjoseole99%5D%20Clannad%20-%2001%20(1280x720%20Blu-ray%20H264)%20%5B8E128DF5%5D.mkv - - episode_data = HttpReq("api/ep/getbyfilename?filename=%s" % (urllib.quote(os.path.basename(file)))) - if len(episode_data) == 0: continue - if (try_get(episode_data, "code", 200) == 404): continue - - series_data = HttpReq("api/serie/fromep?id=%d&nocast=1¬ag=1" % episode_data['id']) - showTitle = series_data['name'].encode("utf-8") #no idea why I need to do this. - Log.info('show title: %s', showTitle) - - seasonNumber = 0 - seasonStr = try_get(episode_data, 'season', None) - if episode_data['eptype'] == 'Credits': seasonNumber = -1 #season -1 for OP/ED - elif episode_data['eptype'] == 'Trailer': seasonNumber = -2 #season -2 for Trailer - elif seasonStr == None: - if episode_data['eptype'] == 'Episode': seasonNumber = 1 - elif episode_data['eptype'] == 'Special': seasonNumber = 0 - else: - seasonNumber = int(seasonStr.split('x')[0]) - if seasonNumber <= 0 and episode_data['eptype'] == 'Episode': seasonNumber = 1 - elif seasonNumber > 0 and episode_data['eptype'] == 'Special': seasonNumber = 0 - - if seasonNumber <= 0 and Prefs['IncludeOther'] == False: continue #Ignore this by choice. + try: + Log.info('file: %s', file) + # http://127.0.0.1:8111/api/ep/getbyfilename?apikey=d422dfd2-bdc3-4219-b3bb-08b85aa65579&filename=%5Bjoseole99%5D%20Clannad%20-%2001%20(1280x720%20Blu-ray%20H264)%20%5B8E128DF5%5D.mkv + + episode_data = HttpReq("api/ep/getbyfilename?filename=%s" % (urllib.quote(os.path.basename(file)))) + if len(episode_data) == 0: continue + if (try_get(episode_data, "code", 200) == 404): continue + + series_data = HttpReq("api/serie/fromep?id=%d&nocast=1¬ag=1" % episode_data['id']) + showTitle = series_data['name'].encode("utf-8") #no idea why I need to do this. + Log.info('show title: %s', showTitle) + + seasonNumber = 0 + seasonStr = try_get(episode_data, 'season', None) + if episode_data['eptype'] == 'Credits': seasonNumber = -1 #season -1 for OP/ED + elif episode_data['eptype'] == 'Trailer': seasonNumber = -2 #season -2 for Trailer + elif Prefs['SingleSeasonOrdering'] or seasonStr == None: + if episode_data['eptype'] == 'Episode': seasonNumber = 1 + elif episode_data['eptype'] == 'Special': seasonNumber = 0 + else: + seasonNumber = int(seasonStr.split('x')[0]) + if seasonNumber <= 0 and episode_data['eptype'] == 'Episode': seasonNumber = 1 + elif seasonNumber > 0 and episode_data['eptype'] == 'Special': seasonNumber = 0 + + if seasonNumber == 0 and Prefs['IncludeSpecials'] == False: continue + if seasonNumber < 0 and Prefs['IncludeOther'] == False: continue #Ignore this by choice. + + if (try_get(series_data, "ismovie", 0) == 1 and seasonNumber >= 1): continue # Ignore movies in preference for Shoko Movie Scanner, but keep specials as Plex sees specials as duplicate + Log.info('season number: %s', seasonNumber) + episodeNumber = int(episode_data['epnumber']) + Log.info('episode number: %s', episodeNumber) + + vid = Media.Episode(showTitle, seasonNumber, episodeNumber) + Log.info('vid: %s', vid) + vid.parts.append(file) + mediaList.append(vid) + except Exception as e: + Log.error("Error in Scan: '%s'" % e) + continue + + Stack.Scan(path, files, mediaList, subdirs) + + if not path: # If current folder is root folder + Log.info("Manual call to group folders") + subfolders = subdirs[:] + + while subfolders: # subfolder scanning queue + full_path = subfolders.pop(0) + path = os.path.relpath(full_path, root) + + reverse_path = list(reversed(path.split(os.sep))) - if (try_get(series_data, "ismovie", 0) == 1 and seasonNumber >= 1): continue # Ignore movies in preference for Shoko Movie Scanner, but keep specials as Plex sees specials as duplicate - Log.info('season number: %s', seasonNumber) - episodeNumber = int(episode_data['epnumber']) - Log.info('episode number: %s', episodeNumber) + Log.info('=' * 100) + Log.info('Started subfolder scan: %s', full_path) + Log.info('=' * 100) + + subdir_dirs, subdir_files = [], [] + + for file in os.listdir(full_path): + path_item = os.path.join(full_path, file) + if os.path.isdir(path_item): + subdir_dirs.append(path_item) + else: + subdir_files.append(path_item) + + Log.info("Sub-directories: %s", subdir_dirs) + Log.info("Files: %s", subdir_files) + + for dir in subdir_dirs: + Log.info("[Added for scanning] " + dir) # Add the subfolder to subfolder scanning queue) + subfolders.append(dir) + + grouping_dir = full_path.rsplit(os.sep, full_path.count(os.sep)-1-root.count(os.sep))[0] + if subdir_files and (len(reverse_path)>1 or subdir_dirs): + if grouping_dir in subdirs: + subdirs.remove(grouping_dir) #Prevent group folders from being called by Plex normal call to Scan() + Log.info("CALLING SCAN FOR FILES IN CURRENT FOLDER") + Scan(path, sorted(subdir_files), mediaList, [], language, root) + # relative path for dir or it will group multiple series into one as before and no empty subdirs array because they will be scanned afterwards. - vid = Media.Episode(showTitle, seasonNumber, episodeNumber) - Log.info('vid: %s', vid) - vid.parts.append(file) - mediaList.append(vid) - - Log.info('stack media') - Stack.Scan(path, files, mediaList, subdirs) - Log.debug('media list %s', mediaList) - except Exception as e: - Log.error("Error in Scan: '%s'" % e) + Log.info('=' * 100) + Log.info('Completed subfolder scan: %s', full_path) + Log.info('=' * 100) def try_get(arr, idx, default=""):