def sync_episodes(series_id=None, send_event=True): logging.debug('BAZARR Starting episodes sync from Sonarr.') apikey_sonarr = settings.sonarr.apikey # Get current episodes id in DB current_episodes_db = TableEpisodes.select(TableEpisodes.sonarrEpisodeId, TableEpisodes.path, TableEpisodes.sonarrSeriesId)\ .where((TableEpisodes.sonarrSeriesId == series_id) if series_id else None)\ .dicts() current_episodes_db_list = [ x['sonarrEpisodeId'] for x in current_episodes_db ] current_episodes_sonarr = [] episodes_to_update = [] episodes_to_add = [] altered_episodes = [] # Get sonarrId for each series from database seriesIdList = get_series_from_sonarr_api( series_id=series_id, url=url_sonarr(), apikey_sonarr=apikey_sonarr, ) series_count = len(seriesIdList) for i, seriesId in enumerate(seriesIdList): if send_event: show_progress(id='episodes_progress', header='Syncing episodes...', name=seriesId['title'], value=i, count=series_count) # Get episodes data for a series from Sonarr episodes = get_episodes_from_sonarr_api( url=url_sonarr(), apikey_sonarr=apikey_sonarr, series_id=seriesId['sonarrSeriesId']) if not episodes: continue else: # For Sonarr v3, we need to update episodes to integrate the episodeFile API endpoint results if not get_sonarr_info.is_legacy(): episodeFiles = get_episodesFiles_from_sonarr_api( url=url_sonarr(), apikey_sonarr=apikey_sonarr, series_id=seriesId['sonarrSeriesId']) for episode in episodes: if episode['hasFile']: item = [ x for x in episodeFiles if x['id'] == episode['episodeFileId'] ] if item: episode['episodeFile'] = item[0] for episode in episodes: if 'hasFile' in episode: if episode['hasFile'] is True: if 'episodeFile' in episode: if episode['episodeFile']['size'] > 20480: # Add episodes in sonarr to current episode list current_episodes_sonarr.append(episode['id']) # Parse episode data if episode['id'] in current_episodes_db_list: episodes_to_update.append( episodeParser(episode)) else: episodes_to_add.append( episodeParser(episode)) if send_event: hide_progress(id='episodes_progress') # Remove old episodes from DB removed_episodes = list( set(current_episodes_db_list) - set(current_episodes_sonarr)) for removed_episode in removed_episodes: episode_to_delete = TableEpisodes.select(TableEpisodes.sonarrSeriesId, TableEpisodes.sonarrEpisodeId)\ .where(TableEpisodes.sonarrEpisodeId == removed_episode)\ .dicts()\ .get() TableEpisodes.delete().where( TableEpisodes.sonarrEpisodeId == removed_episode).execute() if send_event: event_stream(type='episode', action='delete', payload=episode_to_delete['sonarrEpisodeId']) # Update existing episodes in DB episode_in_db_list = [] episodes_in_db = TableEpisodes.select( TableEpisodes.sonarrSeriesId, TableEpisodes.sonarrEpisodeId, TableEpisodes.title, TableEpisodes.path, TableEpisodes.season, TableEpisodes.episode, TableEpisodes.scene_name, TableEpisodes.monitored, TableEpisodes.format, TableEpisodes.resolution, TableEpisodes.video_codec, TableEpisodes.audio_codec, TableEpisodes.episode_file_id, TableEpisodes.audio_language, TableEpisodes.file_size).dicts() for item in episodes_in_db: episode_in_db_list.append(item) episodes_to_update_list = [ i for i in episodes_to_update if i not in episode_in_db_list ] for updated_episode in episodes_to_update_list: TableEpisodes.update(updated_episode).where( TableEpisodes.sonarrEpisodeId == updated_episode['sonarrEpisodeId']).execute() altered_episodes.append([ updated_episode['sonarrEpisodeId'], updated_episode['path'], updated_episode['sonarrSeriesId'] ]) # Insert new episodes in DB for added_episode in episodes_to_add: result = TableEpisodes.insert(added_episode).on_conflict( action='IGNORE').execute() if result > 0: altered_episodes.append([ added_episode['sonarrEpisodeId'], added_episode['path'], added_episode['monitored'] ]) if send_event: event_stream(type='episode', payload=added_episode['sonarrEpisodeId']) else: logging.debug( 'BAZARR unable to insert this episode into the database:{}'. format(path_mappings.path_replace(added_episode['path']))) # Store subtitles for added or modified episodes for i, altered_episode in enumerate(altered_episodes, 1): store_subtitles(altered_episode[1], path_mappings.path_replace(altered_episode[1])) logging.debug('BAZARR All episodes synced from Sonarr into database.')
def parse_video_metadata(file, file_size, episode_file_id=None, movie_file_id=None, use_cache=True): # Define default data keys value data = { "ffprobe": {}, "enzyme": {}, "file_id": episode_file_id or movie_file_id, "file_size": file_size, } if use_cache: # Get the actual cache value form database if episode_file_id: cache_key = TableEpisodes.select(TableEpisodes.ffprobe_cache)\ .where(TableEpisodes.path == path_mappings.path_replace_reverse(file))\ .dicts()\ .get() elif movie_file_id: cache_key = TableMovies.select(TableMovies.ffprobe_cache)\ .where(TableMovies.path == path_mappings.path_replace_reverse_movie(file))\ .dicts()\ .get() else: cache_key = None # check if we have a value for that cache key try: # Unpickle ffprobe cache cached_value = pickle.loads(cache_key['ffprobe_cache']) except: pass else: # Check if file size and file id matches and if so, we return the cached value if cached_value['file_size'] == file_size and cached_value[ 'file_id'] in [episode_file_id, movie_file_id]: return cached_value # if not, we retrieve the metadata from the file from utils import get_binary ffprobe_path = get_binary("ffprobe") # if we have ffprobe available if ffprobe_path: api.initialize({"provider": "ffmpeg", "ffmpeg": ffprobe_path}) data["ffprobe"] = api.know(file) # if not, we use enzyme for mkv files else: if os.path.splitext(file)[1] == ".mkv": with open(file, "rb") as f: try: mkv = enzyme.MKV(f) except MalformedMKVError: logger.error( "BAZARR cannot analyze this MKV with our built-in MKV parser, you should install " "ffmpeg/ffprobe: " + file) else: data["enzyme"] = mkv # we write to db the result and return the newly cached ffprobe dict if episode_file_id: TableEpisodes.update({TableEpisodes.ffprobe_cache: pickle.dumps(data, pickle.HIGHEST_PROTOCOL)})\ .where(TableEpisodes.path == path_mappings.path_replace_reverse(file))\ .execute() elif movie_file_id: TableMovies.update({TableEpisodes.ffprobe_cache: pickle.dumps(data, pickle.HIGHEST_PROTOCOL)})\ .where(TableMovies.path == path_mappings.path_replace_reverse_movie(file))\ .execute() return data
def store_subtitles(file): logging.debug('BAZARR started subtitles indexing for this file: ' + file) actual_subtitles = [] if os.path.exists(file): if settings.general.getboolean('use_embedded_subs'): logging.debug("BAZARR is trying to index embedded subtitles.") try: subtitle_languages = embedded_subs_reader.list_languages(file) for subtitle_language, subtitle_forced, subtitle_codec in subtitle_languages: try: if settings.general.getboolean( "ignore_pgs_subs" ) and subtitle_codec == "hdmv_pgs_subtitle": logging.debug( "BAZARR skipping pgs sub for language: " + str(alpha2_from_alpha3(subtitle_language))) continue if alpha2_from_alpha3(subtitle_language) is not None: lang = str(alpha2_from_alpha3(subtitle_language)) if subtitle_forced: lang = lang + ":forced" logging.debug( "BAZARR embedded subtitles detected: " + lang) actual_subtitles.append([lang, None]) except: logging.debug( "BAZARR unable to index this unrecognized language: " + subtitle_language) pass except Exception as e: logging.exception( "BAZARR error when trying to analyze this %s file: %s" % (os.path.splitext(file)[1], file)) pass brazilian_portuguese = [".pt-br", ".pob", "pb"] brazilian_portuguese_forced = [ ".pt-br.forced", ".pob.forced", "pb.forced" ] try: dest_folder = get_subtitle_destination_folder() subliminal_patch.core.CUSTOM_PATHS = [dest_folder ] if dest_folder else [] subtitles = search_external_subtitles( file, languages=get_language_set(), only_one=settings.general.getboolean('single_language')) except Exception as e: logging.exception("BAZARR unable to index external subtitles.") pass else: for subtitle, language in subtitles.iteritems(): subtitle_path = get_external_subtitles_path(file, subtitle) if str(os.path.splitext(subtitle)[0]).lower().endswith( tuple(brazilian_portuguese)): logging.debug("BAZARR external subtitles detected: " + "pb") actual_subtitles.append( [str("pb"), path_replace_reverse(subtitle_path)]) elif str(os.path.splitext(subtitle)[0]).lower().endswith( tuple(brazilian_portuguese_forced)): logging.debug("BAZARR external subtitles detected: " + "pb:forced") actual_subtitles.append([ str("pb:forced"), path_replace_reverse(subtitle_path) ]) elif str(language) != 'und': logging.debug("BAZARR external subtitles detected: " + str(language)) actual_subtitles.append( [str(language), path_replace_reverse(subtitle_path)]) else: if os.path.splitext(subtitle)[1] != ".sub": logging.debug( "BAZARR falling back to file content analysis to detect language." ) with open( os.path.join(os.path.dirname(file), subtitle), 'r') as f: text = list(islice(f, 100)) text = ' '.join(text) encoding = UnicodeDammit(text) try: text = text.decode(encoding.original_encoding) detected_language = langdetect.detect(text) except Exception as e: logging.exception( 'BAZARR Error trying to detect language for this subtitles file: ' + os.path.join(os.path.dirname(file), subtitle) + ' You should try to delete this subtitles file manually and ask Bazarr to download it again.' ) else: if len(detected_language) > 0: logging.debug( "BAZARR external subtitles detected and analysis guessed this language: " + str(detected_language)) actual_subtitles.append([ str(detected_language), path_replace_reverse( os.path.join( os.path.dirname(file), subtitle)) ]) update_count = TableEpisodes.update({ TableEpisodes.subtitles: str(actual_subtitles) }).where(TableEpisodes.path == path_replace_reverse(file)).execute() if update_count > 0: logging.debug("BAZARR storing those languages to DB: " + str(actual_subtitles)) else: logging.debug( "BAZARR haven't been able to update existing subtitles to DB : " + str(actual_subtitles)) else: logging.debug( "BAZARR this file doesn't seems to exist or isn't accessible.") logging.debug('BAZARR ended subtitles indexing for this file: ' + file) return actual_subtitles
def sync_one_episode(episode_id): logging.debug( 'BAZARR syncing this specific episode from Sonarr: {}'.format( episode_id)) url = url_sonarr() apikey_sonarr = settings.sonarr.apikey # Check if there's a row in database for this episode ID try: existing_episode = TableEpisodes.select(TableEpisodes.path, TableEpisodes.episode_file_id)\ .where(TableEpisodes.sonarrEpisodeId == episode_id)\ .dicts()\ .get() except DoesNotExist: existing_episode = None try: # Get episode data from sonarr api episode = None episode_data = get_episodes_from_sonarr_api( url=url, apikey_sonarr=apikey_sonarr, episode_id=episode_id) if not episode_data: return else: # For Sonarr v3, we need to update episodes to integrate the episodeFile API endpoint results if not get_sonarr_info.is_legacy( ) and existing_episode and episode_data['hasFile']: episode_data['episodeFile'] = \ get_episodesFiles_from_sonarr_api(url=url, apikey_sonarr=apikey_sonarr, episode_file_id=existing_episode['episode_file_id']) episode = episodeParser(episode_data) except Exception: logging.debug( 'BAZARR cannot get episode returned by SignalR feed from Sonarr API.' ) return # Drop useless events if not episode and not existing_episode: return # Remove episode from DB if not episode and existing_episode: TableEpisodes.delete().where( TableEpisodes.sonarrEpisodeId == episode_id).execute() event_stream(type='episode', action='delete', payload=int(episode_id)) logging.debug( 'BAZARR deleted this episode from the database:{}'.format( path_mappings.path_replace(existing_episode['path']))) return # Update existing episodes in DB elif episode and existing_episode: TableEpisodes.update(episode).where( TableEpisodes.sonarrEpisodeId == episode_id).execute() event_stream(type='episode', action='update', payload=int(episode_id)) logging.debug( 'BAZARR updated this episode into the database:{}'.format( path_mappings.path_replace(episode['path']))) # Insert new episodes in DB elif episode and not existing_episode: TableEpisodes.insert(episode).on_conflict(action='IGNORE').execute() event_stream(type='episode', action='update', payload=int(episode_id)) logging.debug( 'BAZARR inserted this episode into the database:{}'.format( path_mappings.path_replace(episode['path']))) # Storing existing subtitles logging.debug('BAZARR storing subtitles for this episode: {}'.format( path_mappings.path_replace(episode['path']))) store_subtitles(episode['path'], path_mappings.path_replace(episode['path'])) # Downloading missing subtitles logging.debug( 'BAZARR downloading missing subtitles for this episode: {}'.format( path_mappings.path_replace(episode['path']))) episode_download_subtitles(episode_id)
def list_missing_subtitles(no=None): episodes_subtitles_clause = {TableShows.sonarr_series_id.is_null(False)} if no is not None: episodes_subtitles_clause = {TableShows.sonarr_series_id**no} episodes_subtitles = TableEpisodes.select( TableEpisodes.sonarr_episode_id, TableEpisodes.subtitles, TableShows.languages, TableShows.forced).join_from( TableEpisodes, TableShows, JOIN.LEFT_OUTER).where(episodes_subtitles_clause).objects() missing_subtitles_global = [] use_embedded_subs = settings.general.getboolean('use_embedded_subs') for episode_subtitles in episodes_subtitles: actual_subtitles_temp = [] desired_subtitles_temp = [] actual_subtitles = [] desired_subtitles = [] missing_subtitles = [] if episode_subtitles.subtitles is not None: if use_embedded_subs: actual_subtitles = ast.literal_eval( episode_subtitles.subtitles) else: actual_subtitles_temp = ast.literal_eval( episode_subtitles.subtitles) for subtitle in actual_subtitles_temp: if subtitle[1] is not None: actual_subtitles.append(subtitle) if episode_subtitles.languages is not None: desired_subtitles = ast.literal_eval(episode_subtitles.languages) if episode_subtitles.forced == "True" and desired_subtitles is not None: for i, desired_subtitle in enumerate(desired_subtitles): desired_subtitles[i] = desired_subtitle + ":forced" elif episode_subtitles.forced == "Both" and desired_subtitles is not None: for desired_subtitle in desired_subtitles: desired_subtitles_temp.append(desired_subtitle) desired_subtitles_temp.append(desired_subtitle + ":forced") desired_subtitles = desired_subtitles_temp actual_subtitles_list = [] if desired_subtitles is None: missing_subtitles_global.append( tuple(['[]', episode_subtitles.sonarr_episode_id])) else: for item in actual_subtitles: if item[0] == "pt-BR": actual_subtitles_list.append("pb") elif item[0] == "pt-BR:forced": actual_subtitles_list.append("pb:forced") else: actual_subtitles_list.append(item[0]) missing_subtitles = list( set(desired_subtitles) - set(actual_subtitles_list)) missing_subtitles_global.append( tuple([ str(missing_subtitles), episode_subtitles.sonarr_episode_id ])) for missing_subtitles_item in missing_subtitles_global: TableEpisodes.update({ TableEpisodes.missing_subtitles: missing_subtitles_item[0] }).where(TableEpisodes.sonarr_episode_id == missing_subtitles_item[1]).execute()
def _wanted_episode(episode): audio_language_list = get_audio_profile_languages(episode_id=episode['sonarrEpisodeId']) if len(audio_language_list) > 0: audio_language = audio_language_list[0]['name'] else: audio_language = 'None' languages = [] for language in ast.literal_eval(episode['missing_subtitles']): # confirm if language is still missing or if cutoff have been reached confirmed_missing_subs = TableEpisodes.select(TableEpisodes.missing_subtitles) \ .where(TableEpisodes.sonarrEpisodeId == episode['sonarrEpisodeId']) \ .dicts() \ .get_or_none() if not confirmed_missing_subs: continue if language not in ast.literal_eval(confirmed_missing_subs['missing_subtitles']): continue if is_search_active(desired_language=language, attempt_string=episode['failedAttempts']): TableEpisodes.update({TableEpisodes.failedAttempts: updateFailedAttempts(desired_language=language, attempt_string=episode['failedAttempts'])}) \ .where(TableEpisodes.sonarrEpisodeId == episode['sonarrEpisodeId']) \ .execute() hi_ = "True" if language.endswith(':hi') else "False" forced_ = "True" if language.endswith(':forced') else "False" languages.append((language.split(":")[0], hi_, forced_)) else: logging.debug( f"BAZARR Search is throttled by adaptive search for this episode {episode['path']} and " f"language: {language}") for result in generate_subtitles(path_mappings.path_replace(episode['path']), languages, audio_language, str(episode['scene_name']), episode['title'], 'series'): if result: message = result[0] path = result[1] forced = result[5] if result[8]: language_code = result[2] + ":hi" elif forced: language_code = result[2] + ":forced" else: language_code = result[2] provider = result[3] score = result[4] subs_id = result[6] subs_path = result[7] store_subtitles(episode['path'], path_mappings.path_replace(episode['path'])) history_log(1, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path, language_code, provider, score, subs_id, subs_path) event_stream(type='series', action='update', payload=episode['sonarrSeriesId']) event_stream(type='episode-wanted', action='delete', payload=episode['sonarrEpisodeId']) send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message)
def sync_episodes(): notifications.write(msg='Episodes sync from Sonarr started...', queue='get_episodes') logging.debug('BAZARR Starting episodes sync from Sonarr.') apikey_sonarr = settings.sonarr.apikey # Get current episodes id in DB current_episodes_db = TableEpisodes.select(TableEpisodes.sonarr_episode_id, TableEpisodes.path, TableEpisodes.sonarr_series_id) current_episodes_db_list = [ x.sonarr_episode_id for x in current_episodes_db ] current_episodes_sonarr = [] episodes_to_update = [] episodes_to_add = [] altered_episodes = [] # Get sonarrId for each series from database seriesIdList = TableShows.select(TableShows.sonarr_series_id, TableShows.title) seriesIdListLength = seriesIdList.count() for i, seriesId in enumerate(seriesIdList, 1): notifications.write(msg='Getting episodes data from Sonarr...', queue='get_episodes', item=i, length=seriesIdListLength) # Get episodes data for a series from Sonarr url_sonarr_api_episode = url_sonarr + "/api/episode?seriesId=" + str( seriesId.sonarr_series_id) + "&apikey=" + apikey_sonarr try: r = requests.get(url_sonarr_api_episode, timeout=60, verify=False) r.raise_for_status() except requests.exceptions.HTTPError as errh: logging.exception( "BAZARR Error trying to get episodes from Sonarr. Http error.") return except requests.exceptions.ConnectionError as errc: logging.exception( "BAZARR Error trying to get episodes from Sonarr. Connection Error." ) return except requests.exceptions.Timeout as errt: logging.exception( "BAZARR Error trying to get episodes from Sonarr. Timeout Error." ) return except requests.exceptions.RequestException as err: logging.exception( "BAZARR Error trying to get episodes from Sonarr.") return else: for episode in r.json(): if 'hasFile' in episode: if episode['hasFile'] is True: if 'episodeFile' in episode: if episode['episodeFile']['size'] > 20480: # Add shows in Sonarr to current shows list if 'sceneName' in episode['episodeFile']: sceneName = episode['episodeFile'][ 'sceneName'] else: sceneName = None try: format, resolution = episode[ 'episodeFile']['quality']['quality'][ 'name'].split('-') except: format = episode['episodeFile']['quality'][ 'quality']['name'] try: resolution = str( episode['episodeFile']['quality'] ['quality']['resolution']) + 'p' except: resolution = None if 'mediaInfo' in episode['episodeFile']: if 'videoCodec' in episode['episodeFile'][ 'mediaInfo']: videoCodec = episode['episodeFile'][ 'mediaInfo']['videoCodec'] videoCodec = SonarrFormatVideoCodec( videoCodec) else: videoCodec = None if 'audioCodec' in episode['episodeFile'][ 'mediaInfo']: audioCodec = episode['episodeFile'][ 'mediaInfo']['audioCodec'] audioCodec = SonarrFormatAudioCodec( audioCodec) else: audioCodec = None else: videoCodec = None audioCodec = None # Add episodes in sonarr to current episode list current_episodes_sonarr.append(episode['id']) if episode['id'] in current_episodes_db_list: episodes_to_update.append({ 'sonarr_series_id': episode['seriesId'], 'sonarr_episode_id': episode['id'], 'title': episode['title'], 'path': episode['episodeFile']['path'], 'season': episode['seasonNumber'], 'episode': episode['episodeNumber'], 'scene_name': sceneName, 'monitored': str(bool(episode['monitored'])), 'format': format, 'resolution': resolution, 'video_codec': videoCodec, 'audio_codec': audioCodec, 'episode_file_id': episode['episodeFile']['id'] }) else: episodes_to_add.append({ 'sonarr_series_id': episode['seriesId'], 'sonarr_episode_id': episode['id'], 'title': episode['title'], 'path': episode['episodeFile']['path'], 'season': episode['seasonNumber'], 'episode': episode['episodeNumber'], 'scene_name': sceneName, 'monitored': str(bool(episode['monitored'])), 'format': format, 'resolution': resolution, 'video_codec': videoCodec, 'audio_codec': audioCodec, 'episode_file_id': episode['episodeFile']['id'] }) # Update existing episodes in DB episode_in_db_list = [] episodes_in_db = TableEpisodes.select( TableEpisodes.sonarr_series_id, TableEpisodes.sonarr_episode_id, TableEpisodes.title, TableEpisodes.path, TableEpisodes.season, TableEpisodes.episode, TableEpisodes.scene_name, TableEpisodes.monitored, TableEpisodes.format, TableEpisodes.resolution, TableEpisodes.video_codec, TableEpisodes.audio_codec, TableEpisodes.episode_file_id).dicts() for item in episodes_in_db: episode_in_db_list.append(item) episodes_to_update_list = [ i for i in episodes_to_update if i not in episode_in_db_list ] for updated_episode in episodes_to_update_list: TableEpisodes.update(updated_episode).where( TableEpisodes.sonarr_episode_id == updated_episode['sonarr_episode_id']).execute() altered_episodes.append([ updated_episode['sonarr_episode_id'], updated_episode['path'], updated_episode['sonarr_series_id'] ]) # Insert new episodes in DB for added_episode in episodes_to_add: TableEpisodes.insert(added_episode).on_conflict_ignore().execute() altered_episodes.append([ added_episode['sonarr_episode_id'], added_episode['path'], added_episode['sonarr_series_id'] ]) # Remove old episodes from DB removed_episodes = list( set(current_episodes_db_list) - set(current_episodes_sonarr)) for removed_episode in removed_episodes: TableEpisodes.delete().where( TableEpisodes.sonarr_episode_id == removed_episode).execute() # Store subtitles for added or modified episodes for i, altered_episode in enumerate(altered_episodes, 1): notifications.write(msg='Indexing episodes embedded subtitles...', queue='get_episodes', item=i, length=len(altered_episodes)) store_subtitles(path_replace(altered_episode[1])) list_missing_subtitles(altered_episode[2]) logging.debug('BAZARR All episodes synced from Sonarr into database.') # Search for desired subtitles if no more than 5 episodes have been added. if len(altered_episodes) <= 5: logging.debug( "BAZARR No more than 5 episodes were added during this sync then we'll search for subtitles." ) for altered_episode in altered_episodes: episode_download_subtitles(altered_episode[0]) else: logging.debug( "BAZARR More than 5 episodes were added during this sync then we wont search for subtitles right now." )
def store_subtitles(original_path, reversed_path, use_cache=True): logging.debug('BAZARR started subtitles indexing for this file: ' + reversed_path) actual_subtitles = [] if os.path.exists(reversed_path): if settings.general.getboolean('use_embedded_subs'): logging.debug("BAZARR is trying to index embedded subtitles.") item = TableEpisodes.select(TableEpisodes.episode_file_id, TableEpisodes.file_size)\ .where(TableEpisodes.path == original_path)\ .dicts()\ .get_or_none() if not item: logging.exception(f"BAZARR error when trying to select this episode from database: {reversed_path}") else: try: subtitle_languages = embedded_subs_reader(reversed_path, file_size=item['file_size'], episode_file_id=item['episode_file_id'], use_cache=use_cache) for subtitle_language, subtitle_forced, subtitle_hi, subtitle_codec in subtitle_languages: try: if (settings.general.getboolean("ignore_pgs_subs") and subtitle_codec.lower() == "pgs") or \ (settings.general.getboolean("ignore_vobsub_subs") and subtitle_codec.lower() == "vobsub") or \ (settings.general.getboolean("ignore_ass_subs") and subtitle_codec.lower() == "ass"): logging.debug("BAZARR skipping %s sub for language: %s" % (subtitle_codec, alpha2_from_alpha3(subtitle_language))) continue if alpha2_from_alpha3(subtitle_language) is not None: lang = str(alpha2_from_alpha3(subtitle_language)) if subtitle_forced: lang = lang + ":forced" if subtitle_hi: lang = lang + ":hi" logging.debug("BAZARR embedded subtitles detected: " + lang) actual_subtitles.append([lang, None]) except Exception as error: logging.debug("BAZARR unable to index this unrecognized language: %s (%s)", subtitle_language, error) except Exception: logging.exception( "BAZARR error when trying to analyze this %s file: %s" % (os.path.splitext(reversed_path)[1], reversed_path)) pass try: dest_folder = get_subtitle_destination_folder() core.CUSTOM_PATHS = [dest_folder] if dest_folder else [] subtitles = search_external_subtitles(reversed_path, languages=get_language_set(), only_one=settings.general.getboolean('single_language')) full_dest_folder_path = os.path.dirname(reversed_path) if dest_folder: if settings.general.subfolder == "absolute": full_dest_folder_path = dest_folder elif settings.general.subfolder == "relative": full_dest_folder_path = os.path.join(os.path.dirname(reversed_path), dest_folder) subtitles = guess_external_subtitles(full_dest_folder_path, subtitles) except Exception: logging.exception("BAZARR unable to index external subtitles.") else: for subtitle, language in subtitles.items(): valid_language = False if language: if hasattr(language, 'alpha3'): valid_language = alpha2_from_alpha3(language.alpha3) else: logging.debug(f"Skipping subtitles because we are unable to define language: {subtitle}") continue if not valid_language: logging.debug(f'{language.alpha3} is an unsupported language code.') continue subtitle_path = get_external_subtitles_path(reversed_path, subtitle) custom = CustomLanguage.found_external(subtitle, subtitle_path) if custom is not None: actual_subtitles.append([custom, path_mappings.path_replace_reverse(subtitle_path)]) elif str(language) != 'und': if language.forced: language_str = str(language) elif language.hi: language_str = str(language) + ':hi' else: language_str = str(language) logging.debug("BAZARR external subtitles detected: " + language_str) actual_subtitles.append([language_str, path_mappings.path_replace_reverse(subtitle_path)]) TableEpisodes.update({TableEpisodes.subtitles: str(actual_subtitles)})\ .where(TableEpisodes.path == original_path)\ .execute() matching_episodes = TableEpisodes.select(TableEpisodes.sonarrEpisodeId, TableEpisodes.sonarrSeriesId)\ .where(TableEpisodes.path == original_path)\ .dicts() for episode in matching_episodes: if episode: logging.debug("BAZARR storing those languages to DB: " + str(actual_subtitles)) list_missing_subtitles(epno=episode['sonarrEpisodeId']) else: logging.debug("BAZARR haven't been able to update existing subtitles to DB : " + str(actual_subtitles)) else: logging.debug("BAZARR this file doesn't seems to exist or isn't accessible.") logging.debug('BAZARR ended subtitles indexing for this file: ' + reversed_path) return actual_subtitles
def list_missing_subtitles(no=None, epno=None, send_event=True): if epno is not None: episodes_subtitles_clause = (TableEpisodes.sonarrEpisodeId == epno) elif no is not None: episodes_subtitles_clause = (TableEpisodes.sonarrSeriesId == no) else: episodes_subtitles_clause = None episodes_subtitles = TableEpisodes.select(TableShows.sonarrSeriesId, TableEpisodes.sonarrEpisodeId, TableEpisodes.subtitles, TableShows.profileId, TableEpisodes.audio_language)\ .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\ .where(episodes_subtitles_clause)\ .dicts() if isinstance(episodes_subtitles, str): logging.error("BAZARR list missing subtitles query to DB returned this instead of rows: " + episodes_subtitles) return use_embedded_subs = settings.general.getboolean('use_embedded_subs') for episode_subtitles in episodes_subtitles: missing_subtitles_text = '[]' if episode_subtitles['profileId']: # get desired subtitles desired_subtitles_temp = get_profiles_list(profile_id=episode_subtitles['profileId']) desired_subtitles_list = [] if desired_subtitles_temp: for language in desired_subtitles_temp['items']: if language['audio_exclude'] == "True": if language_from_alpha2(language['language']) in ast.literal_eval( episode_subtitles['audio_language']): continue desired_subtitles_list.append([language['language'], language['forced'], language['hi']]) # get existing subtitles actual_subtitles_list = [] if episode_subtitles['subtitles'] is not None: if use_embedded_subs: actual_subtitles_temp = ast.literal_eval(episode_subtitles['subtitles']) else: actual_subtitles_temp = [x for x in ast.literal_eval(episode_subtitles['subtitles']) if x[1]] for subtitles in actual_subtitles_temp: subtitles = subtitles[0].split(':') lang = subtitles[0] forced = False hi = False if len(subtitles) > 1: if subtitles[1] == 'forced': forced = True hi = False elif subtitles[1] == 'hi': forced = False hi = True actual_subtitles_list.append([lang, str(forced), str(hi)]) # check if cutoff is reached and skip any further check cutoff_met = False cutoff_temp_list = get_profile_cutoff(profile_id=episode_subtitles['profileId']) if cutoff_temp_list: for cutoff_temp in cutoff_temp_list: cutoff_language = [cutoff_temp['language'], cutoff_temp['forced'], cutoff_temp['hi']] if cutoff_temp['audio_exclude'] == 'True' and language_from_alpha2(cutoff_temp['language']) in \ ast.literal_eval(episode_subtitles['audio_language']): cutoff_met = True elif cutoff_language in actual_subtitles_list: cutoff_met = True elif cutoff_language and [cutoff_language[0], 'True', 'False'] in actual_subtitles_list: cutoff_met = True elif cutoff_language and [cutoff_language[0], 'False', 'True'] in actual_subtitles_list: cutoff_met = True if cutoff_met: missing_subtitles_text = str([]) else: # if cutoff isn't met or None, we continue # get difference between desired and existing subtitles missing_subtitles_list = [] for item in desired_subtitles_list: if item not in actual_subtitles_list: missing_subtitles_list.append(item) # remove missing that have hi subtitles for this language in existing for item in actual_subtitles_list: if item[2] == 'True': try: missing_subtitles_list.remove([item[0], 'False', 'False']) except ValueError: pass # make the missing languages list looks like expected missing_subtitles_output_list = [] for item in missing_subtitles_list: lang = item[0] if item[1] == 'True': lang += ':forced' elif item[2] == 'True': lang += ':hi' missing_subtitles_output_list.append(lang) missing_subtitles_text = str(missing_subtitles_output_list) TableEpisodes.update({TableEpisodes.missing_subtitles: missing_subtitles_text})\ .where(TableEpisodes.sonarrEpisodeId == episode_subtitles['sonarrEpisodeId'])\ .execute() if send_event: event_stream(type='episode', payload=episode_subtitles['sonarrEpisodeId']) event_stream(type='badges')