def setEpisodeToWanted(show, s, e): """ Sets an episode to wanted, only if it is currently skipped """ epObj = show.getEpisode(s, e) if epObj: with epObj.lock: if epObj.status != SKIPPED or epObj.airdate == datetime.date.fromordinal( 1): return logger.log("Setting episode {show} {ep} to wanted".format( show=show.name, ep=episode_num(s, e))) # figure out what segment the episode is in and remember it so we can backlog it epObj.status = WANTED epObj.saveToDB() cur_backlog_queue_item = search_queue.BacklogQueueItem(show, [epObj]) sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item) logger.log( "Starting backlog search for {show} {ep} because some episodes were set to wanted" .format(show=show.name, ep=episode_num(s, e)))
def refresh_subtitles(episode): video = get_video(episode.location) if not video: logger.log("Exception caught in subliminal.scan_video, subtitles couldn't be refreshed", logger.DEBUG) return episode.subtitles, None current_subtitles = get_subtitles(video) if episode.subtitles == current_subtitles: logger.log('No changed subtitles for {0} {1}'.format (episode.show.name, episode_num(episode.season, episode.episode) or episode_num(episode.season, episode.episode, numbering='absolute')), logger.DEBUG) return episode.subtitles, None else: return current_subtitles, True
def addEpisodeToTraktWatchList(self): if sickbeard.TRAKT_SYNC_WATCHLIST and sickbeard.USE_TRAKT: logger.log("WATCHLIST::ADD::START - Look for Episodes to Add to Trakt Watchlist", logger.DEBUG) main_db_con = db.DBConnection() sql_selection = 'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode from tv_episodes,tv_shows where tv_shows.indexer_id = tv_episodes.showid and tv_episodes.status in (' + ','.join([str(x) for x in Quality.SNATCHED + Quality.SNATCHED_PROPER + [WANTED]]) + ')' episodes = main_db_con.select(sql_selection) if episodes is not None: trakt_data = [] for cur_episode in episodes: if not self._checkInList(sickchill.indexer.slug(cur_episode[b"indexer"]), str(cur_episode[b"showid"]), str(cur_episode[b"season"]), str(cur_episode[b"episode"])): logger.log("Adding Episode {show} {ep} to watchlist".format (show=cur_episode[b"show_name"], ep=episode_num(cur_episode[b"season"], cur_episode[b"episode"])), logger.DEBUG) trakt_data.append((cur_episode[b"showid"], cur_episode[b"indexer"], cur_episode[b"show_name"], cur_episode[b"startyear"], cur_episode[b"season"], cur_episode[b"episode"])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.traktRequest("sync/watchlist", data, method='POST') self._getEpisodeWatchlist() except traktException as e: logger.log("Could not connect to Trakt service. Error {0}".format(ex(e)), logger.WARNING) logger.log("WATCHLIST::ADD::FINISH - Look for Episodes to Add to Trakt Watchlist", logger.DEBUG)
def _get_episode_search_strings(self, ep_obj, add_string=''): if not ep_obj: return [{}] to_return = [] search_params = {'category': 'Episode'} # episode if ep_obj.show.air_by_date or ep_obj.show.sports: date_str = str(ep_obj.airdate) # BTN uses dots in dates, we just search for the date since that # combined with the series identifier should result in just one episode search_params['name'] = date_str.replace('-', '.') else: # BTN uses the same format for both Anime and TV # Do a general name search for the episode, formatted like SXXEYY search_params['name'] = "{ep}".format(ep=episode_num(ep_obj.scene_season, ep_obj.scene_episode)) # search if ep_obj.show.indexer == 1: search_params['tvdb'] = ep_obj.show.indexerid to_return.append(search_params) else: # add new query string for every exception name_exceptions = list( set(scene_exceptions.get_scene_exceptions(ep_obj.show.indexerid) + [ep_obj.show.name])) for cur_exception in name_exceptions: search_params['series'] = sanitizeSceneName(cur_exception) to_return.append(search_params) return to_return
def addEpisodeToTraktWatchList(self): if sickbeard.TRAKT_SYNC_WATCHLIST and sickbeard.USE_TRAKT: logger.log("WATCHLIST::ADD::START - Look for Episodes to Add to Trakt Watchlist", logger.DEBUG) main_db_con = db.DBConnection() sql_selection = 'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode from tv_episodes,tv_shows where tv_shows.indexer_id = tv_episodes.showid and tv_episodes.status in (' + ','.join([str(x) for x in Quality.SNATCHED + Quality.SNATCHED_PROPER + [WANTED]]) + ')' episodes = main_db_con.select(sql_selection) if episodes is not None: trakt_data = [] for cur_episode in episodes: trakt_id = sickbeard.indexerApi(cur_episode[b"indexer"]).config['trakt_id'] if not self._checkInList(trakt_id, str(cur_episode[b"showid"]), str(cur_episode[b"season"]), str(cur_episode[b"episode"])): logger.log("Adding Episode {show} {ep} to watchlist".format (show=cur_episode[b"show_name"], ep=episode_num(cur_episode[b"season"], cur_episode[b"episode"])), logger.DEBUG) trakt_data.append((cur_episode[b"showid"], cur_episode[b"indexer"], cur_episode[b"show_name"], cur_episode[b"startyear"], cur_episode[b"season"], cur_episode[b"episode"])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.traktRequest("sync/watchlist", data, method='POST') self._getEpisodeWatchlist() except traktException as e: logger.log("Could not connect to Trakt service. Error {0}".format(ex(e)), logger.WARNING) logger.log("WATCHLIST::ADD::FINISH - Look for Episodes to Add to Trakt Watchlist", logger.DEBUG)
def removeEpisodeFromTraktCollection(self): if sickbeard.TRAKT_SYNC_REMOVE and sickbeard.TRAKT_SYNC and sickbeard.USE_TRAKT: logger.log("COLLECTION::REMOVE::START - Look for Episodes to Remove From Trakt Collection", logger.DEBUG) main_db_con = db.DBConnection() sql_selection = 'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode, tv_episodes.status, tv_episodes.location from tv_episodes,tv_shows where tv_shows.indexer_id = tv_episodes.showid' episodes = main_db_con.select(sql_selection) if episodes is not None: trakt_data = [] for cur_episode in episodes: trakt_id = sickbeard.indexerApi(cur_episode[b"indexer"]).config[b'trakt_id'] if self._checkInList(trakt_id, str(cur_episode[b"showid"]), str(cur_episode[b"season"]), str(cur_episode[b"episode"]), List='Collection'): if cur_episode[b"location"] == '': logger.log("Removing Episode {show} {ep} from collection".format( show=cur_episode[b"show_name"], ep=episode_num(cur_episode[b"season"], cur_episode[b"episode"])), logger.DEBUG ) trakt_data.append((cur_episode[b"showid"], cur_episode[b"indexer"], cur_episode[b"show_name"], cur_episode[b"startyear"], cur_episode[b"season"], cur_episode[b"episode"])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.traktRequest("sync/collection/remove", data, method='POST') self._getShowCollection() except traktException as e: logger.log("Could not connect to Trakt service. Error: {0}".format(ex(e)), logger.WARNING) logger.log("COLLECTION::REMOVE::FINISH - Look for Episodes to Remove From Trakt Collection", logger.DEBUG)
def convert_archived_to_compound(self): logger.debug(_('Checking for archived episodes not qualified')) sql_results = self.connection.select( "SELECT episode_id, showid, status, location, season, episode FROM tv_episodes WHERE status = ?", [common.ARCHIVED] ) if sql_results: logger.warning(_(f"Found {len(sql_results)} shows with bare archived status, attempting automatic conversion...")) for archivedEp in sql_results: fixed_status = common.Quality.compositeStatus(common.ARCHIVED, common.Quality.UNKNOWN) existing = archivedEp['location'] and os.path.exists(archivedEp['location']) if existing: quality = common.Quality.nameQuality(archivedEp['location']) fixed_status = common.Quality.compositeStatus(common.ARCHIVED, quality) old_status = common.statusStrings[common.ARCHIVED] new_status = common.statusStrings[fixed_status] archived_episode = archivedEp['showid'] ep = episode_num(archivedEp['season']) episode_id = archivedEp['episode_id'] location = archivedEp['location'] or 'unknown location' result = ('NOT FOUND', 'EXISTS')[bool(existing)] logger.info(_(f'Changing status from {old_status} to {new_status} for {archived_episode}: {ep} at {location} (File {result})')) self.connection.action("UPDATE tv_episodes SET status = ? WHERE episode_id = ?", [fixed_status, episode_id])
def convert_archived_to_compound(self): logger.debug(_("Checking for archived episodes not qualified")) sql_results = self.connection.select( "SELECT episode_id, showid, status, location, season, episode FROM tv_episodes WHERE status = ?", [common.ARCHIVED] ) if sql_results: logger.warning(_("Found {count} shows with bare archived status, attempting automatic conversion...".format(count=len(sql_results)))) for archivedEp in sql_results: fixed_status = common.Quality.compositeStatus(common.ARCHIVED, common.Quality.UNKNOWN) existing = archivedEp["location"] and os.path.exists(archivedEp["location"]) if existing: quality = common.Quality.nameQuality(archivedEp["location"]) fixed_status = common.Quality.compositeStatus(common.ARCHIVED, quality) old_status = common.statusStrings[common.ARCHIVED] new_status = common.statusStrings[fixed_status] archived_episode = archivedEp["showid"] ep = episode_num(archivedEp["season"]) episode_id = archivedEp["episode_id"] location = archivedEp["location"] or "unknown location" result = ("NOT FOUND", "EXISTS")[bool(existing)] logger.info( _( "Changing status from {old_status} to {new_status} for {archived_episode}: {ep} at {location} (File {result})".format( old_status=old_status, new_status=new_status, archived_episode=archived_episode, ep=ep, location=location, result=result ) ) ) self.connection.action("UPDATE tv_episodes SET status = ? WHERE episode_id = ?", [fixed_status, episode_id])
def removeEpisodeFromTraktCollection(self): if sickbeard.TRAKT_SYNC_REMOVE and sickbeard.TRAKT_SYNC and sickbeard.USE_TRAKT: logger.log("COLLECTION::REMOVE::START - Look for Episodes to Remove From Trakt Collection", logger.DEBUG) main_db_con = db.DBConnection() sql_selection = 'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode, tv_episodes.status, tv_episodes.location from tv_episodes,tv_shows where tv_shows.indexer_id = tv_episodes.showid' episodes = main_db_con.select(sql_selection) if episodes is not None: trakt_data = [] for cur_episode in episodes: if self._checkInList(sickchill.indexer.slug(cur_episode[b"indexer"]), str(cur_episode[b"showid"]), str(cur_episode[b"season"]), str(cur_episode[b"episode"]), List='Collection'): if cur_episode[b"location"] == '': logger.log("Removing Episode {show} {ep} from collection".format( show=cur_episode[b"show_name"], ep=episode_num(cur_episode[b"season"], cur_episode[b"episode"])), logger.DEBUG ) trakt_data.append((cur_episode[b"showid"], cur_episode[b"indexer"], cur_episode[b"show_name"], cur_episode[b"startyear"], cur_episode[b"season"], cur_episode[b"episode"])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.traktRequest("sync/collection/remove", data, method='POST') self._getShowCollection() except traktException as e: logger.log("Could not connect to Trakt service. Error: {0}".format(ex(e)), logger.WARNING) logger.log("COLLECTION::REMOVE::FINISH - Look for Episodes to Remove From Trakt Collection", logger.DEBUG)
def addEpisodeToTraktCollection(self): if settings.TRAKT_SYNC and settings.USE_TRAKT: logger.debug( "COLLECTION::ADD::START - Look for Episodes to Add to Trakt Collection" ) main_db_con = db.DBConnection() sql_selection = ( "select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode from tv_episodes,tv_shows where tv_shows.indexer_id = tv_episodes.showid and tv_episodes.status in (" + ",".join( [str(x) for x in Quality.DOWNLOADED + Quality.ARCHIVED]) + ")") episodes = main_db_con.select(sql_selection) if episodes is not None: trakt_data = [] for cur_episode in episodes: if not self._checkInList( sickchill.indexer.slug(cur_episode["indexer"]), str(cur_episode["showid"]), str(cur_episode["season"]), str(cur_episode["episode"]), List="Collection", ): logger.debug( "Adding Episode {show} {ep} to collection".format( show=cur_episode["show_name"], ep=episode_num(cur_episode["season"], cur_episode["episode"]))) trakt_data.append(( cur_episode["showid"], cur_episode["indexer"], cur_episode["show_name"], cur_episode["startyear"], cur_episode["season"], cur_episode["episode"], )) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.traktRequest("sync/collection", data, method="POST") self._getShowCollection() except traktException as e: logger.warning( "Could not connect to Trakt service. Error: {0}". format(str(e))) logger.debug( "COLLECTION::ADD::FINISH - Look for Episodes to Add to Trakt Collection" )
def removeEpisodeFromTraktWatchList(self): if sickbeard.TRAKT_SYNC_WATCHLIST and sickbeard.USE_TRAKT: logger.log( "WATCHLIST::REMOVE::START - Look for Episodes to Remove from Trakt Watchlist", logger.DEBUG) main_db_con = db.DBConnection() sql_selection = 'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode, tv_episodes.status from tv_episodes,tv_shows where tv_shows.indexer_id = tv_episodes.showid' episodes = main_db_con.select(sql_selection) if episodes is not None: trakt_data = [] for cur_episode in episodes: trakt_id = sickbeard.indexerApi( cur_episode[b"indexer"]).config['trakt_id'] if self._checkInList(trakt_id, str(cur_episode[b"showid"]), str(cur_episode[b"season"]), str(cur_episode[b"episode"])): if cur_episode[ b"status"] not in Quality.SNATCHED + Quality.SNATCHED_PROPER + [ UNKNOWN ] + [WANTED]: logger.log( "Removing Episode {show} {ep} from watchlist". format( show=cur_episode[b"show_name"], ep=episode_num(cur_episode[b"season"], cur_episode[b"episode"])), logger.DEBUG) trakt_data.append((cur_episode[b"showid"], cur_episode[b"indexer"], cur_episode[b"show_name"], cur_episode[b"startyear"], cur_episode[b"season"], cur_episode[b"episode"])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.traktRequest("sync/watchlist/remove", data, method='POST') self._getEpisodeWatchlist() except traktException as e: logger.log( "Could not connect to Trakt service. Error: {0}". format(ex(e)), logger.WARNING) logger.log( "WATCHLIST::REMOVE::FINISH - Look for Episodes to Remove from Trakt Watchlist", logger.DEBUG)
def setEpisodeToWanted(show, s, e): """ Sets an episode to wanted, only if it is currently skipped """ epObj = show.getEpisode(s, e) if epObj: with epObj.lock: if epObj.status != SKIPPED or epObj.airdate == datetime.date.fromordinal(1): return logger.log("Setting episode {show} {ep} to wanted".format (show=show.name, ep=episode_num(s, e))) # figure out what segment the episode is in and remember it so we can backlog it epObj.status = WANTED epObj.saveToDB() cur_backlog_queue_item = search_queue.BacklogQueueItem(show, [epObj]) sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item) logger.log("Starting backlog search for {show} {ep} because some episodes were set to wanted".format (show=show.name, ep=episode_num(s, e)))
def addEpisodeToTraktCollection(self): if sickbeard.TRAKT_SYNC and sickbeard.USE_TRAKT: logger.log( "COLLECTION::ADD::START - Look for Episodes to Add to Trakt Collection", logger.DEBUG) main_db_con = db.DBConnection() sql_selection = 'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode from tv_episodes,tv_shows where tv_shows.indexer_id = tv_episodes.showid and tv_episodes.status in (' + ','.join( [str(x) for x in Quality.DOWNLOADED + Quality.ARCHIVED]) + ')' episodes = main_db_con.select(sql_selection) if episodes is not None: trakt_data = [] for cur_episode in episodes: trakt_id = sickbeard.indexerApi( cur_episode[b"indexer"]).config['trakt_id'] if not self._checkInList(trakt_id, str(cur_episode[b"showid"]), str(cur_episode[b"season"]), str(cur_episode[b"episode"]), List='Collection'): logger.log( "Adding Episode {show} {ep} to collection".format( show=cur_episode[b"show_name"], ep=episode_num(cur_episode[b"season"], cur_episode[b"episode"])), logger.DEBUG) trakt_data.append( (cur_episode[b"showid"], cur_episode[b"indexer"], cur_episode[b"show_name"], cur_episode[b"startyear"], cur_episode[b"season"], cur_episode[b"episode"])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.traktRequest("sync/collection", data, method='POST') self._getShowCollection() except traktException as e: logger.log( "Could not connect to Trakt service. Error: {0}". format(ex(e)), logger.WARNING) logger.log( "COLLECTION::ADD::FINISH - Look for Episodes to Add to Trakt Collection", logger.DEBUG)
def run(self, force=False): if not settings.USE_SUBTITLES: return if not enabled_service_list(): logger.warning( "Not enough services selected. At least 1 service is required to " "search subtitles in the background") return self.amActive = True def dhm(td): days = td.days hours = td.seconds // 60**2 minutes = (td.seconds // 60) % 60 ret = ("", "{0} days, ".format(days))[days > 0] + ( "", "{0} hours, ".format(hours))[hours > 0] + ( "", "{0} minutes".format(minutes))[minutes > 0] if days == 1: ret = ret.replace("days", "day") if hours == 1: ret = ret.replace("hours", "hour") if minutes == 1: ret = ret.replace("minutes", "minute") return ret.rstrip(", ") logger.info("Checking for missed subtitles") database = db.DBConnection() sql_results = database.select( "SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.subtitles, e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch, e.location, (? - e.airdate) as age FROM tv_episodes AS e INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id) " "WHERE s.subtitles = 1 AND e.subtitles NOT LIKE ? " + ("AND e.season != 0 ", "")[settings.SUBTITLES_INCLUDE_SPECIALS] + "AND e.location != '' AND e.status IN ({}) ORDER BY age ASC". format(",".join(["?"] * len(Quality.DOWNLOADED))), [datetime.datetime.now().toordinal(), wanted_languages(True)] + Quality.DOWNLOADED, ) if not sql_results: logger.info("No subtitles to download") self.amActive = False return for ep_to_sub in sql_results: if not os.path.isfile(ep_to_sub["location"]): logger.debug( "Episode file does not exist, cannot download subtitles for {0} {1}" .format( ep_to_sub["show_name"], episode_num(ep_to_sub["season"], ep_to_sub["episode"]) or episode_num(ep_to_sub["season"], ep_to_sub["episode"], numbering="absolute"), )) continue if not needs_subtitles(ep_to_sub["subtitles"]): logger.debug( "Episode already has all needed subtitles, skipping {0} {1}" .format( ep_to_sub["show_name"], episode_num(ep_to_sub["season"], ep_to_sub["episode"]) or episode_num(ep_to_sub["season"], ep_to_sub["episode"], numbering="absolute"), )) continue try: lastsearched = datetime.datetime.strptime( ep_to_sub["lastsearch"], dateTimeFormat) except ValueError: lastsearched = datetime.datetime.min try: if not force: now = datetime.datetime.now() days = int(ep_to_sub["age"]) delay_time = datetime.timedelta( hours=8 if days < 10 else 7 * 24 if days < 30 else 30 * 24) # Search every hour for the first 24 hours since aired, then every 8 hours until 10 days passes # After 10 days, search every 7 days, after 30 days search once a month # Will always try an episode regardless of age at least 2 times if lastsearched + delay_time > now and int( ep_to_sub["searchcount"]) > 2 and days: logger.debug( "Subtitle search for {0} {1} delayed for {2}". format( ep_to_sub["show_name"], episode_num(ep_to_sub["season"], ep_to_sub["episode"]) or episode_num(ep_to_sub["season"], ep_to_sub["episode"], numbering="absolute"), dhm(lastsearched + delay_time - now), )) continue logger.info( "Searching for missing subtitles of {0} {1}".format( ep_to_sub["show_name"], episode_num(ep_to_sub["season"], ep_to_sub["episode"]) or episode_num(ep_to_sub["season"], ep_to_sub["episode"], numbering="absolute"), )) show_object = Show.find(settings.showList, int(ep_to_sub["showid"])) if not show_object: logger.debug( "Show with ID {0} not found in the database".format( ep_to_sub["showid"])) continue episode_object = show_object.getEpisode( ep_to_sub["season"], ep_to_sub["episode"]) if isinstance(episode_object, str): logger.debug("{0} {1} not found in the database".format( ep_to_sub["show_name"], episode_num(ep_to_sub["season"], ep_to_sub["episode"]) or episode_num(ep_to_sub["season"], ep_to_sub["episode"], numbering="absolute"), )) continue try: new_subtitles = episode_object.download_subtitles() except Exception as error: logger.error( "Unable to find subtitles for {0} {1}. Error: {2}". format( ep_to_sub["show_name"], episode_num(ep_to_sub["season"], ep_to_sub["episode"]) or episode_num(ep_to_sub["season"], ep_to_sub["episode"], numbering="absolute"), str(error), )) continue if new_subtitles: logger.info("Downloaded {0} subtitles for {1} {2}".format( ", ".join(new_subtitles), ep_to_sub["show_name"], episode_num(ep_to_sub["season"], ep_to_sub["episode"]) or episode_num(ep_to_sub["season"], ep_to_sub["episode"], numbering="absolute"), )) except Exception as error: logger.error( "Error while searching subtitles for {0} {1}. Error: {2}". format( ep_to_sub["show_name"], episode_num(ep_to_sub["season"], ep_to_sub["episode"]) or episode_num(ep_to_sub["season"], ep_to_sub["episode"], numbering="absolute"), str(error), )) continue logger.info("Finished checking for missed subtitles") self.amActive = False
def download_subtitles(episode, force_lang=None): existing_subtitles = episode.subtitles if not needs_subtitles(existing_subtitles, force_lang): logger.debug( "Episode already has all needed subtitles, skipping {0} {1}". format( episode.show.name, episode_num(episode.season, episode.episode) or episode_num( episode.season, episode.episode, numbering="absolute"))) return existing_subtitles, None if not force_lang: languages = get_needed_languages(existing_subtitles) else: languages = {from_code(force_lang)} if not languages: logger.debug("No subtitles needed for {0} {1}".format( episode.show.name, episode_num(episode.season, episode.episode) or episode_num( episode.season, episode.episode, numbering="absolute"))) return existing_subtitles, None subtitles_path = get_subtitles_path(episode.location) video_path = episode.location # Perfect match = hash score - hearing impaired score - resolution score # (subtitle for 720p is the same as for 1080p) # Perfect match = 215 - 1 - 1 = 213 # Non-perfect match = series + year + season + episode # Non-perfect match = 108 + 54 + 18 + 18 = 198 # From latest subliminal code: # episode_scores = {'hash': 215, 'series': 108, 'year': 54, 'season': 18, 'episode': 18, 'release_group': 9, # 'source': 4, 'audio_codec': 2, 'resolution': 1, 'hearing_impaired': 1, 'video_codec': 1} user_score = 213 if settings.SUBTITLES_PERFECT_MATCH else 198 video = get_video(video_path, subtitles_path=subtitles_path, episode=episode) if not video: logger.debug( "Exception caught in subliminal.scan_video for {0} {1}".format( episode.show.name, episode_num(episode.season, episode.episode) or episode_num( episode.season, episode.episode, numbering="absolute"))) return existing_subtitles, None providers = enabled_service_list() pool = SubtitleProviderPool() try: subtitles_list = pool.list_subtitles(video, languages) for provider in providers: if provider in pool.discarded_providers: logger.debug( "Could not search in {0} provider. Discarding for now". format(provider)) if not subtitles_list: logger.debug("No subtitles found for {0} {1}".format( episode.show.name, episode_num(episode.season, episode.episode) or episode_num( episode.season, episode.episode, numbering="absolute"))) return existing_subtitles, None for subtitle in subtitles_list: score = subliminal.score.compute_score( subtitle, video, hearing_impaired=settings.SUBTITLES_HEARING_IMPAIRED) logger.debug( "[{0}] Subtitle score for {1} is: {2} (min={3})".format( subtitle.provider_name, subtitle.id, score, user_score)) found_subtitles = pool.download_best_subtitles( subtitles_list, video, languages=languages, hearing_impaired=settings.SUBTITLES_HEARING_IMPAIRED, min_score=user_score, only_one=not settings.SUBTITLES_MULTI, ) subliminal.save_subtitles(video, found_subtitles, directory=subtitles_path, single=not settings.SUBTITLES_MULTI, encoding="utf8") except IOError as error: if "No space left on device" in str(error): logger.warning("Not enough space on the drive to save subtitles") else: logger.warning(traceback.format_exc()) return existing_subtitles, None except Exception: logger.info( "Error occurred when downloading subtitles for: {0}".format( video_path)) logger.exception(traceback.format_exc()) return existing_subtitles, None for subtitle in found_subtitles: subtitle_path = subliminal.subtitle.get_subtitle_path( video.name, None if not settings.SUBTITLES_MULTI else subtitle.language) if subtitles_path is not None: subtitle_path = os.path.join(subtitles_path, os.path.split(subtitle_path)[1]) sickchill.oldbeard.helpers.chmodAsParent(subtitle_path) sickchill.oldbeard.helpers.fixSetGroupID(subtitle_path) if settings.SUBTITLES_HISTORY: logger.debug("history.logSubtitle {0}, {1}".format( subtitle.provider_name, subtitle.language.opensubtitles)) history.logSubtitle(episode.show.indexerid, episode.season, episode.episode, episode.status, subtitle) if settings.SUBTITLES_EXTRA_SCRIPTS and is_media_file( video_path) and not settings.EMBEDDED_SUBTITLES_ALL: run_subs_extra_scripts(episode, subtitle, video, single=not settings.SUBTITLES_MULTI) new_subtitles = sorted( {subtitle.language.opensubtitles for subtitle in found_subtitles}) current_subtitles = sorted( {subtitle for subtitle in new_subtitles + existing_subtitles}) if existing_subtitles else new_subtitles if not settings.SUBTITLES_MULTI and len(found_subtitles) == 1: new_code = found_subtitles[0].language.opensubtitles if new_code not in existing_subtitles: current_subtitles.remove(new_code) current_subtitles.append("und") return current_subtitles, new_subtitles
def test_episode_num(self): # Standard numbering self.assertEqual(episode_num(0, 1), 'S00E01') # Seasons start at 0 for specials self.assertEqual(episode_num(1, 1), 'S01E01') # Absolute numbering self.assertEqual(episode_num(1, numbering='absolute'), '001') self.assertEqual(episode_num(0, 1, numbering='absolute'), '001') self.assertEqual(episode_num(1, 0, numbering='absolute'), '001') # Must have both season and episode for standard numbering self.assertEqual(episode_num(0), None) self.assertEqual(episode_num(1), None) # Episode numbering starts at 1 self.assertEqual(episode_num(0, 0), None) self.assertEqual(episode_num(1, 0), None) # Absolute numbering starts at 1 self.assertEqual(episode_num(0, 0, numbering='absolute'), None) # Absolute numbering can't have both season and episode self.assertEqual(episode_num(1, 1, numbering='absolute'), None)
def test_episode_num(self): # Standard numbering assert episode_num(0, 1) == "S00E01" # Seasons start at 0 for specials assert episode_num(1, 1) == "S01E01" # Absolute numbering assert episode_num(1, numbering="absolute") == "001" assert episode_num(0, 1, numbering="absolute") == "001" assert episode_num(1, 0, numbering="absolute") == "001" # Must have both season and episode for standard numbering assert episode_num(0) is None assert episode_num(1) is None # Episode numbering starts at 1 assert episode_num(0, 0) is None assert episode_num(1, 0) is None # Absolute numbering starts at 1 assert episode_num(0, 0, numbering="absolute") is None # Absolute numbering can't have both season and episode assert episode_num(1, 1, numbering="absolute") is None
def run(self, force=False): if not sickbeard.USE_SUBTITLES: return if not sickbeard.subtitles.enabled_service_list(): logger.log( 'Not enough services selected. At least 1 service is required to ' 'search subtitles in the background', logger.WARNING) return self.amActive = True def dhm(td): days = td.days hours = td.seconds // 60**2 minutes = (td.seconds // 60) % 60 ret = ('', '{0} days, '.format(days))[days > 0] + \ ('', '{0} hours, '.format(hours))[hours > 0] + \ ('', '{0} minutes'.format(minutes))[minutes > 0] if days == 1: ret = ret.replace('days', 'day') if hours == 1: ret = ret.replace('hours', 'hour') if minutes == 1: ret = ret.replace('minutes', 'minute') return ret.rstrip(', ') logger.log('Checking for missed subtitles', logger.INFO) database = db.DBConnection() sql_results = database.select( "SELECT s.show_name, e.showid, e.season, e.episode, " "e.status, e.subtitles, e.subtitles_searchcount AS searchcount, " "e.subtitles_lastsearch AS lastsearch, e.location, (? - e.airdate) as age " "FROM tv_episodes AS e INNER JOIN tv_shows AS s " "ON (e.showid = s.indexer_id) " "WHERE s.subtitles = 1 AND e.subtitles NOT LIKE ? " + ("AND e.season != 0 ", "")[sickbeard.SUBTITLES_INCLUDE_SPECIALS] + "AND e.location != '' AND e.status IN ({}) ORDER BY age ASC". format(','.join(['?'] * len(Quality.DOWNLOADED))), [datetime.datetime.now().toordinal(), wanted_languages(True)] + Quality.DOWNLOADED) if not sql_results: logger.log('No subtitles to download', logger.INFO) self.amActive = False return for ep_to_sub in sql_results: try: # Encode path to system encoding. subtitle_path = ep_to_sub[b'location'].encode( sickbeard.SYS_ENCODING) except UnicodeEncodeError: # Fallback to UTF-8. subtitle_path = ep_to_sub[b'location'].encode('utf-8') if not os.path.isfile(subtitle_path): logger.log( 'Episode file does not exist, cannot download subtitles for {0} {1}' .format( ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute')), logger.DEBUG) continue if not needs_subtitles(ep_to_sub[b'subtitles']): logger.log( 'Episode already has all needed subtitles, skipping {0} {1}' .format( ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute')), logger.DEBUG) continue try: lastsearched = datetime.datetime.strptime( ep_to_sub[b'lastsearch'], dateTimeFormat) except ValueError: lastsearched = datetime.datetime.min try: if not force: now = datetime.datetime.now() days = int(ep_to_sub[b'age']) delay_time = datetime.timedelta( hours=8 if days < 10 else 7 * 24 if days < 30 else 30 * 24) # Search every hour for the first 24 hours since aired, then every 8 hours until 10 days passes # After 10 days, search every 7 days, after 30 days search once a month # Will always try an episode regardless of age at least 2 times if lastsearched + delay_time > now and int( ep_to_sub[b'searchcount']) > 2 and days: logger.log( 'Subtitle search for {0} {1} delayed for {2}'. format( ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute'), dhm(lastsearched + delay_time - now)), logger.DEBUG) continue logger.log( 'Searching for missing subtitles of {0} {1}'.format( ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute')), logger.INFO) show_object = Show.find(sickbeard.showList, int(ep_to_sub[b'showid'])) if not show_object: logger.log( 'Show with ID {0} not found in the database'.format( ep_to_sub[b'showid']), logger.DEBUG) continue episode_object = show_object.getEpisode( ep_to_sub[b'season'], ep_to_sub[b'episode']) if isinstance(episode_object, str): logger.log( '{0} {1} not found in the database'.format( ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute')), logger.DEBUG) continue try: new_subtitles = episode_object.download_subtitles() except Exception as error: logger.log( 'Unable to find subtitles for {0} {1}. Error: {2}'. format( ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute'), ex(error)), logger.ERROR) continue if new_subtitles: logger.log('Downloaded {0} subtitles for {1} {2}'.format( ', '.join(new_subtitles), ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute'))) except Exception as error: logger.log( 'Error while searching subtitles for {0} {1}. Error: {2}'. format( ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute'), ex(error)), logger.ERROR) continue logger.log('Finished checking for missed subtitles', logger.INFO) self.amActive = False
def run(self, force=False): # pylint: disable=too-many-branches, too-many-statements, too-many-locals if not sickbeard.USE_SUBTITLES: return if not sickbeard.subtitles.enabled_service_list(): logger.log('Not enough services selected. At least 1 service is required to ' 'search subtitles in the background', logger.WARNING) return self.amActive = True def dhm(td): days = td.days hours = td.seconds // 60 ** 2 minutes = (td.seconds // 60) % 60 ret = ('', '{0} days, '.format(days))[days > 0] + \ ('', '{0} hours, '.format(hours))[hours > 0] + \ ('', '{0} minutes'.format(minutes))[minutes > 0] if days == 1: ret = ret.replace('days', 'day') if hours == 1: ret = ret.replace('hours', 'hour') if minutes == 1: ret = ret.replace('minutes', 'minute') return ret.rstrip(', ') logger.log('Checking for missed subtitles', logger.INFO) database = db.DBConnection() sql_results = database.select( "SELECT s.show_name, e.showid, e.season, e.episode, " "e.status, e.subtitles, e.subtitles_searchcount AS searchcount, " "e.subtitles_lastsearch AS lastsearch, e.location, (? - e.airdate) as age " "FROM tv_episodes AS e INNER JOIN tv_shows AS s " "ON (e.showid = s.indexer_id) " "WHERE s.subtitles = 1 AND e.subtitles NOT LIKE ? " + ("AND e.season != 0 ", "")[sickbeard.SUBTITLES_INCLUDE_SPECIALS] + "AND e.location != '' AND e.status IN ({}) ORDER BY age ASC".format(','.join(['?'] * len(Quality.DOWNLOADED))), [datetime.datetime.now().toordinal(), wanted_languages(True)] + Quality.DOWNLOADED ) if not sql_results: logger.log('No subtitles to download', logger.INFO) self.amActive = False return for ep_to_sub in sql_results: try: # Encode path to system encoding. subtitle_path = ep_to_sub[b'location'].encode(sickbeard.SYS_ENCODING) except UnicodeEncodeError: # Fallback to UTF-8. subtitle_path = ep_to_sub[b'location'].encode('utf-8') if not os.path.isfile(subtitle_path): logger.log('Episode file does not exist, cannot download subtitles for {0} {1}'.format (ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute')), logger.DEBUG) continue if not needs_subtitles(ep_to_sub[b'subtitles']): logger.log('Episode already has all needed subtitles, skipping {0} {1}'.format (ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute')), logger.DEBUG) continue try: lastsearched = datetime.datetime.strptime(ep_to_sub[b'lastsearch'], dateTimeFormat) except ValueError: lastsearched = datetime.datetime.min try: if not force: now = datetime.datetime.now() days = int(ep_to_sub[b'age']) delay_time = datetime.timedelta(hours=8 if days < 10 else 7 * 24 if days < 30 else 30 * 24) # Search every hour for the first 24 hours since aired, then every 8 hours until 10 days passes # After 10 days, search every 7 days, after 30 days search once a month # Will always try an episode regardless of age at least 2 times if lastsearched + delay_time > now and int(ep_to_sub[b'searchcount']) > 2 and days: logger.log('Subtitle search for {0} {1} delayed for {2}'.format (ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute'), dhm(lastsearched + delay_time - now)), logger.DEBUG) continue logger.log('Searching for missing subtitles of {0} {1}'.format (ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute')), logger.INFO) show_object = Show.find(sickbeard.showList, int(ep_to_sub[b'showid'])) if not show_object: logger.log('Show with ID {0} not found in the database'.format(ep_to_sub[b'showid']), logger.DEBUG) continue episode_object = show_object.getEpisode(ep_to_sub[b'season'], ep_to_sub[b'episode']) if isinstance(episode_object, str): logger.log('{0} {1} not found in the database'.format (ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute')), logger.DEBUG) continue try: new_subtitles = episode_object.download_subtitles() except Exception as error: logger.log('Unable to find subtitles for {0} {1}. Error: {2}'.format (ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute'), ex(error)), logger.ERROR) continue if new_subtitles: logger.log('Downloaded {0} subtitles for {1} {2}'.format (', '.join(new_subtitles), ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute'))) except Exception as error: logger.log('Error while searching subtitles for {0} {1}. Error: {2}'.format (ep_to_sub[b'show_name'], episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode']) or episode_num(ep_to_sub[b'season'], ep_to_sub[b'episode'], numbering='absolute'), ex(error)), logger.ERROR) continue logger.log('Finished checking for missed subtitles', logger.INFO) self.amActive = False
def download_subtitles(episode, force_lang=None): # pylint: disable=too-many-locals, too-many-branches, too-many-statements existing_subtitles = episode.subtitles if not needs_subtitles(existing_subtitles, force_lang): logger.log('Episode already has all needed subtitles, skipping {0} {1}'.format (episode.show.name, episode_num(episode.season, episode.episode) or episode_num(episode.season, episode.episode, numbering='absolute')), logger.DEBUG) return existing_subtitles, None if not force_lang: languages = get_needed_languages(existing_subtitles) else: languages = {from_code(force_lang)} if not languages: logger.log('No subtitles needed for {0} {1}'.format (episode.show.name, episode_num(episode.season, episode.episode) or episode_num(episode.season, episode.episode, numbering='absolute')), logger.DEBUG) return existing_subtitles, None subtitles_path = get_subtitles_path(episode.location) video_path = episode.location # Perfect match = hash score - hearing impaired score - resolution score # (subtitle for 720p is the same as for 1080p) # Perfect match = 215 - 1 - 1 = 213 # Non-perfect match = series + year + season + episode # Non-perfect match = 108 + 54 + 18 + 18 = 198 # From latest subliminal code: # episode_scores = {'hash': 215, 'series': 108, 'year': 54, 'season': 18, 'episode': 18, 'release_group': 9, # 'format': 4, 'audio_codec': 2, 'resolution': 1, 'hearing_impaired': 1, 'video_codec': 1} user_score = 213 if sickbeard.SUBTITLES_PERFECT_MATCH else 198 video = get_video(video_path, subtitles_path=subtitles_path, episode=episode) if not video: logger.log('Exception caught in subliminal.scan_video for {0} {1}'.format (episode.show.name, episode_num(episode.season, episode.episode) or episode_num(episode.season, episode.episode, numbering='absolute')), logger.DEBUG) return existing_subtitles, None providers = enabled_service_list() pool = SubtitleProviderPool() try: subtitles_list = pool.list_subtitles(video, languages) for provider in providers: if provider in pool.discarded_providers: logger.log('Could not search in {0} provider. Discarding for now'.format(provider), logger.DEBUG) if not subtitles_list: logger.log('No subtitles found for {0} {1}'.format (episode.show.name, episode_num(episode.season, episode.episode) or episode_num(episode.season, episode.episode, numbering='absolute')), logger.DEBUG) return existing_subtitles, None for subtitle in subtitles_list: score = subliminal.score.compute_score(subtitle, video, hearing_impaired=sickbeard.SUBTITLES_HEARING_IMPAIRED) logger.log('[{0}] Subtitle score for {1} is: {2} (min={3})'.format (subtitle.provider_name, subtitle.id, score, user_score), logger.DEBUG) found_subtitles = pool.download_best_subtitles(subtitles_list, video, languages=languages, hearing_impaired=sickbeard.SUBTITLES_HEARING_IMPAIRED, min_score=user_score, only_one=not sickbeard.SUBTITLES_MULTI) subliminal.save_subtitles(video, found_subtitles, directory=subtitles_path, single=not sickbeard.SUBTITLES_MULTI) except IOError as error: if 'No space left on device' in ex(error): logger.log('Not enough space on the drive to save subtitles', logger.WARNING) else: logger.log(traceback.format_exc(), logger.WARNING) except Exception: logger.log('Error occurred when downloading subtitles for: {0}'.format(video_path)) logger.log(traceback.format_exc(), logger.ERROR) return existing_subtitles, None for subtitle in found_subtitles: subtitle_path = subliminal.subtitle.get_subtitle_path(video.name, None if not sickbeard.SUBTITLES_MULTI else subtitle.language) if subtitles_path is not None: subtitle_path = os.path.join(subtitles_path, os.path.split(subtitle_path)[1]) sickbeard.helpers.chmodAsParent(subtitle_path) sickbeard.helpers.fixSetGroupID(subtitle_path) if sickbeard.SUBTITLES_HISTORY: logger.log('history.logSubtitle {0}, {1}'.format (subtitle.provider_name, subtitle.language.opensubtitles), logger.DEBUG) history.logSubtitle(episode.show.indexerid, episode.season, episode.episode, episode.status, subtitle) if sickbeard.SUBTITLES_EXTRA_SCRIPTS and is_media_file(video_path) and not sickbeard.EMBEDDED_SUBTITLES_ALL: run_subs_extra_scripts(episode, subtitle, video, single=not sickbeard.SUBTITLES_MULTI) new_subtitles = sorted({subtitle.language.opensubtitles for subtitle in found_subtitles}) current_subtitles = sorted({subtitle for subtitle in new_subtitles + existing_subtitles}) if existing_subtitles else new_subtitles if not sickbeard.SUBTITLES_MULTI and len(found_subtitles) == 1: new_code = found_subtitles[0].language.opensubtitles if new_code not in existing_subtitles: current_subtitles.remove(new_code) current_subtitles.append('und') return current_subtitles, new_subtitles