# coding=utf-8 import os import logging from sqlalchemy.exc import IntegrityError from app.database import database, TableEpisodes, delete, update, insert, select from app.config import settings from utilities.path_mappings import path_mappings from subtitles.indexer.series import store_subtitles, series_full_scan_subtitles from subtitles.mass_download import episode_download_subtitles from app.event_handler import event_stream from sonarr.info import get_sonarr_info, url_sonarr from .parser import episodeParser from .utils import get_episodes_from_sonarr_api, get_episodesFiles_from_sonarr_api def update_all_episodes(): series_full_scan_subtitles() logging.info('BAZARR All existing episode subtitles indexed from disk.') def sync_episodes(series_id, send_event=True): logging.debug('BAZARR Starting episodes sync from Sonarr.') apikey_sonarr = settings.sonarr.apikey # Get current episodes id in DB if series_id: current_episodes_id_db_list = [row.sonarrEpisodeId for row in database.execute( select(TableEpisodes.sonarrEpisodeId, TableEpisodes.path, TableEpisodes.sonarrSeriesId) .where(TableEpisodes.sonarrSeriesId == series_id)).all()] current_episodes_db_kv = [x.items() for x in [y._asdict()['TableEpisodes'].__dict__ for y in database.execute( select(TableEpisodes) .where(TableEpisodes.sonarrSeriesId == series_id)) .all()]] else: return current_episodes_sonarr = [] episodes_to_update = [] episodes_to_add = [] # Get episodes data for a series from Sonarr episodes = get_episodes_from_sonarr_api(apikey_sonarr=apikey_sonarr, series_id=series_id) if episodes: # 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(apikey_sonarr=apikey_sonarr, series_id=series_id) for episode in episodes: if episodeFiles and 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: try: bazarr_file_size = \ os.path.getsize(path_mappings.path_replace(episode['episodeFile']['path'])) except OSError: bazarr_file_size = 0 if episode['episodeFile']['size'] > 20480 or bazarr_file_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_id_db_list: parsed_episode = episodeParser(episode) if not any([parsed_episode.items() <= x for x in current_episodes_db_kv]): episodes_to_update.append(parsed_episode) else: episodes_to_add.append(episodeParser(episode)) else: return # Remove old episodes from DB episodes_to_delete = list(set(current_episodes_id_db_list) - set(current_episodes_sonarr)) if len(episodes_to_delete): try: database.execute(delete(TableEpisodes).where(TableEpisodes.sonarrEpisodeId.in_(episodes_to_delete))) except IntegrityError as e: logging.error(f"BAZARR cannot delete episodes because of {e}") else: for removed_episode in episodes_to_delete: if send_event: event_stream(type='episode', action='delete', payload=removed_episode) # Update existing episodes in DB if len(episodes_to_update): for updated_episode in episodes_to_update: try: database.execute(update(TableEpisodes) .values(updated_episode) .where(TableEpisodes.sonarrEpisodeId == updated_episode['sonarrEpisodeId'])) except IntegrityError as e: logging.error(f"BAZARR cannot update episodes because of {e}") else: store_subtitles(updated_episode['path'], path_mappings.path_replace(updated_episode['path'])) if send_event: event_stream(type='episode', action='update', payload=updated_episode['sonarrEpisodeId']) # Insert new episodes in DB if len(episodes_to_add): for added_episode in episodes_to_add: try: database.execute(insert(TableEpisodes).values(added_episode)) except IntegrityError as e: logging.error(f"BAZARR cannot insert episodes because of {e}") else: store_subtitles(added_episode['path'], path_mappings.path_replace(added_episode['path'])) if send_event: event_stream(type='episode', payload=added_episode['sonarrEpisodeId']) logging.debug(f'BAZARR All episodes from series ID {series_id} synced from Sonarr into database.') def sync_one_episode(episode_id, defer_search=False): logging.debug(f'BAZARR syncing this specific episode from Sonarr: {episode_id}') url = url_sonarr() apikey_sonarr = settings.sonarr.apikey # Check if there's a row in database for this episode ID existing_episode = database.execute( select(TableEpisodes.path, TableEpisodes.episode_file_id) .where(TableEpisodes.sonarrEpisodeId == episode_id)) \ .first() try: # Get episode data from sonarr api episode = None episode_data = get_episodes_from_sonarr_api(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(apikey_sonarr=apikey_sonarr, episode_file_id=episode_data['episodeFileId']) episode = episodeParser(episode_data) except Exception: logging.exception('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: try: database.execute( delete(TableEpisodes) .where(TableEpisodes.sonarrEpisodeId == episode_id)) except IntegrityError as e: logging.error(f"BAZARR cannot delete episode {existing_episode.path} because of {e}") else: event_stream(type='episode', action='delete', payload=int(episode_id)) logging.debug( f'BAZARR deleted this episode from the database:{path_mappings.path_replace(existing_episode["path"])}') return # Update existing episodes in DB elif episode and existing_episode: try: database.execute( update(TableEpisodes) .values(episode) .where(TableEpisodes.sonarrEpisodeId == episode_id)) except IntegrityError as e: logging.error(f"BAZARR cannot update episode {episode['path']} because of {e}") else: store_subtitles(episode['path'], path_mappings.path_replace(episode['path'])) event_stream(type='episode', action='update', payload=int(episode_id)) logging.debug( f'BAZARR updated this episode into the database:{path_mappings.path_replace(episode["path"])}') # Insert new episodes in DB elif episode and not existing_episode: try: database.execute( insert(TableEpisodes) .values(episode)) except IntegrityError as e: logging.error(f"BAZARR cannot insert episode {episode['path']} because of {e}") else: store_subtitles(episode['path'], path_mappings.path_replace(episode['path'])) event_stream(type='episode', action='update', payload=int(episode_id)) logging.debug( f'BAZARR inserted this episode into the database:{path_mappings.path_replace(episode["path"])}') # Storing existing subtitles logging.debug(f'BAZARR storing subtitles for this episode: {path_mappings.path_replace(episode["path"])}') store_subtitles(episode['path'], path_mappings.path_replace(episode['path'])) # Downloading missing subtitles if defer_search: logging.debug( f'BAZARR searching for missing subtitles is deferred until scheduled task execution for this episode: ' f'{path_mappings.path_replace(episode["path"])}') else: logging.debug( f'BAZARR downloading missing subtitles for this episode: {path_mappings.path_replace(episode["path"])}') episode_download_subtitles(episode_id)