# coding=utf-8 import os import logging from peewee import IntegrityError from app.database import TableEpisodes 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, show_progress, hide_progress from sonarr.info import get_sonarr_info, url_sonarr from .parser import episodeParser from .utils import get_series_from_sonarr_api, 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=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(url=url_sonarr(), apikey_sonarr=apikey_sonarr, sonarr_series_id=series_id) 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['id']) 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['id']) 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: 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_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.path, TableEpisodes.sonarrSeriesId, TableEpisodes.sonarrEpisodeId)\ .where(TableEpisodes.sonarrEpisodeId == removed_episode)\ .dicts()\ .get_or_none() if not episode_to_delete: continue try: TableEpisodes.delete().where(TableEpisodes.sonarrEpisodeId == removed_episode).execute() except Exception as e: logging.error(f"BAZARR cannot delete episode {episode_to_delete['path']} because of {e}") continue else: 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: try: TableEpisodes.update(updated_episode).where(TableEpisodes.sonarrEpisodeId == updated_episode['sonarrEpisodeId']).execute() except IntegrityError as e: logging.error(f"BAZARR cannot update episode {updated_episode['path']} because of {e}") continue else: altered_episodes.append([updated_episode['sonarrEpisodeId'], updated_episode['path'], updated_episode['sonarrSeriesId']]) # Insert new episodes in DB for added_episode in episodes_to_add: try: result = TableEpisodes.insert(added_episode).on_conflict(action='IGNORE').execute() except IntegrityError as e: logging.error(f"BAZARR cannot insert episode {added_episode['path']} because of {e}") continue else: 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 sync_one_episode(episode_id, defer_search=False): 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 existing_episode = TableEpisodes.select(TableEpisodes.path, TableEpisodes.episode_file_id)\ .where(TableEpisodes.sonarrEpisodeId == episode_id)\ .dicts()\ .get_or_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=episode_data['episodeFileId']) 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: try: TableEpisodes.delete().where(TableEpisodes.sonarrEpisodeId == episode_id).execute() except Exception 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('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: try: TableEpisodes.update(episode).where(TableEpisodes.sonarrEpisodeId == episode_id).execute() except IntegrityError as e: logging.error(f"BAZARR cannot update episode {episode['path']} because of {e}") else: 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: try: TableEpisodes.insert(episode).on_conflict(action='IGNORE').execute() except IntegrityError as e: logging.error(f"BAZARR cannot insert episode {episode['path']} because of {e}") else: 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 if defer_search: logging.debug('BAZARR searching for missing subtitles is deferred until scheduled task execution for this ' 'episode: {}'.format(path_mappings.path_replace(episode['path']))) else: logging.debug('BAZARR downloading missing subtitles for this episode: {}'.format(path_mappings.path_replace( episode['path']))) episode_download_subtitles(episode_id)