def set_tags(self, showid, newtags): """ Updates the tags of the specified **showid** to **newtags** and queues the list update for the next sync. """ # Check if operation is supported by the API if 'can_tag' not in self.mediainfo or not self.mediainfo.get( 'can_tag'): raise utils.EngineError('Operation not supported by API.') # Get the show and update it show = self.get_show_info(showid) # More checks if show['my_tags'] == newtags: raise utils.EngineError("Tags already %s" % newtags) # Change score self.msg.info( self.name, "Updating show %s to tags '%s'..." % (show['title'], newtags)) self.data_handler.queue_update(show, 'my_tags', newtags) # Emit signal self._emit_signal('tags_changed', show) return show
def get_show_info(self, showid=None, title=None, filename=None): """ Returns the show dictionary for the specified **showid**. """ showdict = self.data_handler.get() if showid: # Get show by ID try: return showdict[showid] except KeyError: raise utils.EngineError("Show not found.") elif title: showdict = self.data_handler.get() # Get show by title, slower for k, show in showdict.items(): if show['title'] == title: return show raise utils.EngineError("Show not found.") elif filename: # Guess show by filename self.msg.debug(self.name, "Guessing by filename.") aie = AnimeInfoExtractor(filename) (show_title, ep) = aie.getName(), aie.getEpisode() self.msg.debug(self.name, "Guessed {}".format(show_title)) if show_title: show = utils.guess_show(show_title, self._get_tracker_list()) if show: return (show, ep) else: raise utils.EngineError("Show not found.") else: raise utils.EngineError("File name not recognized.")
def scan_library(self, my_status=None, rescan=False): # Check if operation is supported by the API if not self.mediainfo.get('can_play'): raise utils.EngineError( 'Operation not supported by current site or mediatype.') if not self.config['searchdir']: raise utils.EngineError('Media directory is not set.') if not utils.dir_exists(self.config['searchdir']): raise utils.EngineError('The set media directory doesn\'t exist.') t = time.time() library = {} library_cache = self.data_handler.library_cache_get() if not my_status: my_status = self.mediainfo['status_start'] self.msg.info(self.name, "Scanning local library...") self.msg.debug(self.name, "Directory: %s" % self.config['searchdir']) tracker_list = self._get_tracker_list(my_status) # Do a full listing of the media directory for fullpath, filename in utils.regex_find_videos( 'mkv|mp4|avi', self.config['searchdir']): (library, library_cache) = self._add_show_to_library( library, library_cache, rescan, fullpath, filename, tracker_list) self.msg.debug(self.name, "Time: %s" % (time.time() - t)) self.data_handler.library_save(library) self.data_handler.library_cache_save(library_cache) return library
def set_status(self, showid, newstatus): """ Updates the score of the specified **showid** to **newstatus** (number) and queues the list update for the next sync. """ # Check if operation is supported by the API if not self.mediainfo.get('can_status'): raise utils.EngineError('Operation not supported by API.') try: newstatus = int(newstatus) except ValueError: pass # It's not necessary for it to be an int # Check if the status is valid _statuses = self.mediainfo['statuses_dict'] if newstatus not in _statuses: raise utils.EngineError('Invalid status.') # Get the show and update it show = self.get_show_info(showid) # More checks if show['my_status'] == newstatus: raise utils.EngineError("Show already in %s." % _statuses[newstatus]) # Change status old_status = show['my_status'] self.msg.info(self.name, "Updating show %s status to %s..." % (show['title'], _statuses[newstatus])) self.data_handler.queue_update(show, 'my_status', newstatus) # Emit signal self._emit_signal('status_changed', show, old_status) return show
def play_episode(self, show, playep=0): """ Does a local search in the hard disk (in the folder specified by the config file) for the specified episode (**playep**) for the specified **show**. If no **playep** is specified, the next episode of the show will be played. """ # Check if operation is supported by the API if not self.mediainfo.get('can_play'): raise utils.EngineError( 'Operation not supported by current site or mediatype.') try: playep = int(playep) except ValueError: raise utils.EngineError('Episode must be numeric.') if show: playing_next = False if not playep: playep = show['my_progress'] + 1 playing_next = True if show['total'] and playep > show['total']: raise utils.EngineError('Episode beyond limits.') if self.config.get('debug_oldsearch'): # Deprecated self.msg.info( self.name, "Searching for %s %s..." % (show['title'], playep)) titles = self.data_handler.get_show_titles(show) filename, endep = self._search_video(titles, playep) else: self.msg.info( self.name, "Getting %s %s from library..." % (show['title'], playep)) filename = self.get_episode_path(show, playep) endep = playep if filename: self.msg.info(self.name, 'Found. Starting player...') arg_list = shlex.split(self.config['player']) arg_list.append(filename) try: with open(os.devnull, 'wb') as DEVNULL: subprocess.Popen(arg_list, stdout=DEVNULL, stderr=DEVNULL) except OSError: raise utils.EngineError( 'Player not found, check your config.json') return endep else: raise utils.EngineError('Episode file not found.')
def set_score(self, showid, newscore): """ Updates the score of the specified **showid** to **newscore** and queues the list update for the next sync. """ # Check if operation is supported by the API if not self.mediainfo.get('can_score'): raise utils.EngineError('Operation not supported by API.') # Check for the correctness of the score if (Decimal(str(newscore)) % Decimal(str(self.mediainfo['score_step']))) != 0: raise utils.EngineError('Invalid score.') # Convert to proper type if isinstance(self.mediainfo['score_step'], int): newscore = int(newscore) else: newscore = float(newscore) # Get the show and update it show = self.get_show_info(showid) # More checks if newscore > self.mediainfo['score_max']: raise utils.EngineError('Score out of limits.') if show['my_score'] == newscore: raise utils.EngineError("Score already at %s" % newscore) # Change score self.msg.info(self.name, "Updating show %s to score %s..." % (show['title'], newscore)) self.data_handler.queue_update(show, 'my_score', newscore) # Emit signal self._emit_signal('score_changed', show) # Change status if required if ( show['total'] and show['my_progress'] == show['total'] and show['my_score'] and self.mediainfo.get('can_status') and self.config['auto_status_change'] and self.config['auto_status_change_if_scored'] and self.mediainfo.get('statuses_finish') ): try: self.set_status(show['id'], self._guess_new_finish(show)) except utils.EngineError as e: # Only warn about engine errors since status change here is not crtical self.msg.warn( self.name, 'Updated episode but status wasn\'t changed: %s' % e) return show
def get_episode_path(self, show, episode): """ This function returns the full path of the requested episode from the requested show. """ library = self.library() showid = show['id'] if showid not in library: raise utils.EngineError('Show not in library.') if episode not in library[showid]: raise utils.EngineError('Episode not in library.') return library[showid][episode]
def set_status(self, showid, newstatus): """ Updates the score of the specified **showid** to **newstatus** (number) and queues the list update for the next sync. """ # Check if operation is supported by the API if not self.mediainfo.get('can_status'): raise utils.EngineError('Operation not supported by API.') try: newstatus = int(newstatus) except ValueError: pass # It's not necessary for it to be an int # Check if the status is valid _statuses = self.mediainfo['statuses_dict'] if newstatus not in _statuses: raise utils.EngineError('Invalid status.') # Get the show and update it show = self.get_show_info(showid) # More checks if show['my_status'] == newstatus: raise utils.EngineError("Show already in %s." % _statuses[newstatus]) # Change status old_status = show['my_status'] self.msg.info( self.name, "Updating show %s status to %s..." % (show['title'], _statuses[newstatus])) self.data_handler.queue_update(show, 'my_status', newstatus) # Change repeat if required if self.mediainfo.get('can_repeat'): if (old_status == self.mediainfo['statuses_start'][-1] or newstatus == self.mediainfo['statuses_start'][-1]): newrepeat = show['my_repeat'] + 1 try: self.set_repeat(show['id'], newrepeat) except utils.EngineError as e: # Only warn about engine errors since repeat change here is not critical self.msg.warn( self.name, 'Updated status but repeat wasn\'t changed: %s' % e) # Emit signal self._emit_signal('status_changed', show, old_status) return show
def get_show_info_title(self, pattern): showdict = self.data_handler.get() # Do title lookup, slower for k, show in showdict.items(): if show['title'] == pattern: return show raise utils.EngineError("Show not found.")
def _open_folder(self, show_id): show = self._engine.get_show_info(show_id) try: filename = self._engine.get_episode_path(show, 1) with open(os.devnull, 'wb') as DEVNULL: if sys.platform == 'darwin': subprocess.Popen( ["open", os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL) elif sys.platform == 'win32': subprocess.Popen( ["explorer", os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL) else: subprocess.Popen( ["/usr/bin/xdg-open", os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL) except OSError: # xdg-open failed. raise utils.EngineError("Could not open folder.") except utils.EngineError: # Show not in library. self._error_dialog_idle("No folder found.")
def play_episode(self, show, playep=0): """ Does a local search in the hard disk (in the folder specified by the config file) for the specified episode (**playep**) for the specified **show**. If no **playep** is specified, the next episode of the show will be returned. """ # Check if operation is supported by the API if not self.mediainfo.get('can_play'): raise utils.EngineError( 'Operation not supported by current site or mediatype.') try: playep = int(playep) except ValueError: raise utils.EngineError('Episode must be numeric.') if show: playing_next = False if not playep: playep = show['my_progress'] + 1 playing_next = True if show['total'] and playep > show['total']: raise utils.EngineError('Episode beyond limits.') self.msg.info( self.name, "Getting %s %s from library..." % (show['title'], playep)) filename = self.get_episode_path(show, playep) endep = playep if filename: self.msg.info(self.name, 'Found. Starting player...') args = shlex.split(self.config['player']) if len(args) > 0 and shutil.which(args[0]) == None: raise utils.EngineError( 'Player not found, check your config.json') args.append(filename) return args else: raise utils.EngineError('Episode file not found.')
def scan_library(self, my_status=None, rescan=False): # Check if operation is supported by the API if not self.mediainfo.get('can_play'): raise utils.EngineError( 'Operation not supported by current site or mediatype.') if not self.config['searchdir']: raise utils.EngineError('Media directories not set.') t = time.time() library = {} library_cache = self.data_handler.library_cache_get() if not my_status: if self.config['scan_whole_list']: my_status = self.mediainfo['statuses'] else: my_status = self.mediainfo.get( 'statuses_library', self.mediainfo['statuses_start']) if rescan: self.msg.info(self.name, "Scanning local library (overriding cache)...") else: self.msg.info(self.name, "Scanning local library...") tracker_list = self._get_tracker_list(my_status) for searchdir in self.searchdirs: self.msg.debug(self.name, "Directory: %s" % searchdir) # Do a full listing of the media directory for fullpath, filename in utils.regex_find_videos(searchdir): if self.config['library_full_path']: filename = self._get_show_name_from_full_path( searchdir, fullpath).strip() (library, library_cache) = self._add_show_to_library( library, library_cache, rescan, fullpath, filename, tracker_list) self.msg.debug(self.name, "Time: %s" % (time.time() - t)) self.data_handler.library_save(library) self.data_handler.library_cache_save(library_cache) return library
def get_show_info(self, showid): """ Returns the show dictionary for the specified **showid**. """ showdict = self.data_handler.get() try: return showdict[showid] except KeyError: raise utils.EngineError("Show not found.")
def set_dates(self, showid, start_date=None, finish_date=None): """ Updates the start date and finish date of a show. If any of the two are None, it won't be changed. """ if not self.mediainfo.get('can_date'): raise utils.EngineError('Operation not supported by API.') show = self.get_show_info(showid) # Change the start date if required if start_date: if not isinstance(start_date, datetime.date): raise utils.EngineError('start_date must be a Date object.') self.data_handler.queue_update(show, 'my_start_date', start_date) if finish_date: if not isinstance(finish_date, datetime.date): raise utils.EngineError('finish_date must be a Date object.') self.data_handler.queue_update(show, 'my_finish_date', finish_date)
def search(self, criteria, method=utils.SEARCH_METHOD_KW): """ Request a remote list of shows matching the criteria and returns it as a list of show dictionaries. This is useful to add a show. """ if method not in self.mediainfo.get('search_methods', [utils.SEARCH_METHOD_KW]): raise utils.EngineError( 'Search method not supported by API or mediatype.') return self.data_handler.search(criteria, method)
def set_repeat(self, showid, newrepeat): """ Updates the rewatch counter of the specified **showid** to **newrepeat** and queues the list update for the next sync. """ # Check if operation is supported by the API if not self.mediainfo.get('can_repeat'): raise utils.EngineError('Operation not supported by API.') # Get the show and update it show = self.get_show_info(showid) # More checks if show['my_repeat'] == newrepeat: raise utils.EngineError("Show already at %s repeats" % newrepeat) # Change repeat self.msg.info( self.name, "Updating show %s to rewatched $s times" % (show['title'], newrepeat)) self.data_handler.queue_update(show, 'my_repeat', newrepeat) # Emit signal self._emit_signal('repeat_changed', newrepeat)
def delete_show(self, show): """ Deletes **show** completely from the list and queues the list update for the next sync. """ if not self.mediainfo.get('can_delete'): raise utils.EngineError('Operation not supported by API.') # Add in data handler self.data_handler.queue_delete(show) # Update the tracker with the new information self._update_tracker() # Emit signal self._emit_signal('show_deleted', show)
def add_show(self, show, status=None): """ Adds **show** to the list and queues the list update for the next sync. """ # Check if operation is supported by the API if not self.mediainfo.get('can_add'): raise utils.EngineError('Operation not supported by API.') # Set to the requested status if status: if status not in self.mediainfo['statuses']: raise utils.EngineError('Invalid status.') show['my_status'] = status # Add in data handler self.data_handler.queue_add(show) # Update the tracker with the new information self._update_tracker() # Emit signal self._emit_signal('show_added', show)
def do_openfolder(self): item = self._get_selected_item() try: show = self.engine.get_show_info(item.showid) filename = self.engine.get_episode_path(show, 1) with open(os.devnull, 'wb') as DEVNULL: subprocess.Popen(["/usr/bin/xdg-open", os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL) except OSError: # xdg-open failed. raise utils.EngineError("Could not open folder.") except utils.EngineError: # Show not in library. self.error("No folder found.")
def play_random(self): """ This function will pick a random show that has a new episode to watch and return the arguments to play it. """ library = self.library() newep = [] self.msg.info(self.name, 'Looking for random episode.') for showid, eps in library.items(): show = self.get_show_info(showid) if show['my_progress'] + 1 in eps: newep.append(show) if not newep: raise utils.EngineError('No new episodes found to pick from.') show = random.choice(newep) return self.play_episode(show)
def play_episode(self, show, playep=0): """ Does a local search in the hard disk (in the folder specified by the config file) for the specified episode (**playep**) for the specified **show**. If no **playep** is specified, the next episode of the show will be played. """ # Check if operation is supported by the API if not self.mediainfo.get('can_play'): raise utils.EngineError( 'Operation not supported by current site or mediatype.') try: playep = int(playep) except ValueError: raise utils.EngineError('Episode must be numeric.') if show: playing_next = False if not playep: playep = show['my_progress'] + 1 playing_next = True if show['total'] and playep > show['total']: raise utils.EngineError('Episode beyond limits.') self.msg.info(self.name, "Getting %s %s from library..." % (show['title'], playep)) filename = self.get_episode_path(show, playep) endep = playep if filename: self.msg.info(self.name, 'Found. Starting player...') arg_list = shlex.split(self.config['player']) if len(arg_list) > 0 and shutil.which(arg_list[0]) == None: raise utils.EngineError( 'Player not found, check your config.json') arg_list.append(filename) # Do a double fork on *nix to prevent zombie processes if not sys.platform.startswith('win32'): try: pid = os.fork() if pid > 0: os.waitpid(pid, 0) return endep except OSError: sys.exit(1) os.setsid() fd = os.open("/dev/null", os.O_RDWR) os.dup2(fd, 0) os.dup2(fd, 1) os.dup2(fd, 2) try: pid = os.fork() if pid > 0: sys.exit(0) except OSError: sys.exit(1) os.execv(arg_list[0], arg_list) else: subprocess.Popen( arg_list, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return endep else: raise utils.EngineError('Episode file not found.')
def play_episode(self, show, playep=0, playto=0): """ Does a local search in the hard disk (in the folder specified by the config file) for the specified episode (**playep**) for the specified **show**. If no **playep** is specified, the next episode of the show will be played. """ # Check if operation is supported by the API if not self.mediainfo.get('can_play'): raise utils.EngineError( 'Operation not supported by current site or mediatype.') eps = re.split('-', str(playep)) if len(eps) > 1: if eps[0] != '': playep = eps[0] else: playep = 0 if eps[1] != '': playto = eps[1] else: playto = show['total'] try: playep = int(playep) playto = int(playto) except ValueError: raise utils.EngineError( 'Episode[s] must be numeric or with optional range (eg: 1-3, -3, or - to play all unseen episodes)' ) if show: playing_next = False if not playep: playep = show['my_progress'] + 1 playing_next = True if not playto or playto < playep: playto = playep if show['total']: if playep > show['total']: raise utils.EngineError('Episode beyond limits.') if playto > show['total']: self.msg.info( self.name, "Play to %i is beyond limits of show %s. Defaulting to total episodes of %s" % (playto, show['title'], show['total'])) playto = show['total'] self.msg.info( self.name, "Getting %s %s from library..." % (show['title'], playep)) endep = playep if self.get_episode_path(show, playep): self.msg.info(self.name, 'Found. Starting player...') arg_list = shlex.split(self.config['player']) for episode in range(playep, playto + 1): ep = self.get_episode_path(show, episode, error_on_fail=False) if ep: arg_list.append(ep) try: with open(os.devnull, 'wb') as DEVNULL: subprocess.Popen(arg_list, stdout=DEVNULL, stderr=DEVNULL) except OSError: raise utils.EngineError( 'Player not found, check your config.json') return endep else: raise utils.EngineError('Episode file not found.')
def set_episode(self, showid, newep): """ Updates the progress of the specified **showid** to **newep** and queues the list update for the next sync. """ # Check if operation is supported by the API if not self.mediainfo.get('can_update'): raise utils.EngineError('Operation not supported by API.') # Check for the episode number try: newep = int(newep) except ValueError: raise utils.EngineError('Episode must be numeric.') # Get the show info show = self.get_show_info(showid) # More checks if (show['total'] and newep > show['total']) or newep < 0: raise utils.EngineError('Episode out of limits.') if show['my_progress'] == newep: raise utils.EngineError("Show already at episode %d" % newep) # Change episode self.msg.info(self.name, "Updating show %s to episode %d..." % (show['title'], newep)) self.data_handler.queue_update(show, 'my_progress', newep) # Emit signal self._emit_signal('episode_changed', show) # Change status if required if self.config['auto_status_change'] and self.mediainfo.get('can_status'): try: if newep == show['total'] and self.mediainfo.get('statuses_finish'): if ( not self.config['auto_status_change_if_scored'] or not self.mediainfo.get('can_score') or show['my_score'] ): # Change to finished status self.set_status( show['id'], self._guess_new_finish(show)) else: self.msg.warn( self.name, "Updated episode but status won't be changed until a score is set.") elif newep == 1 and self.mediainfo.get('statuses_start'): # Change to start status self.set_status(show['id'], self._guess_new_start(show)) except utils.EngineError as e: # Only warn about engine errors since status change here is not crtical self.msg.warn( self.name, 'Updated episode but status wasn\'t changed: %s' % e) # Change dates if required if self.config['auto_date_change'] and self.mediainfo.get('can_date'): start_date = finish_date = None try: if newep == 1: start_date = datetime.date.today() if newep == show['total']: finish_date = datetime.date.today() self.set_dates(show['id'], start_date, finish_date) except utils.EngineError as e: # Only warn about engine errors since date change here is not crtical self.msg.warn( self.name, 'Updated episode but dates weren\'t changed: %s' % e) # Update the tracker with the new information self._update_tracker() return show