def browse_sonarr_filesystem(path='#'): if path == '#': path = '' if get_sonarr_info.is_legacy(): url_sonarr_api_filesystem = url_sonarr() + "/api/filesystem?path=" + path + \ "&allowFoldersWithoutTrailingSlashes=true&includeFiles=false&apikey=" + \ settings.sonarr.apikey else: url_sonarr_api_filesystem = url_sonarr() + "/api/v3/filesystem?path=" + path + \ "&allowFoldersWithoutTrailingSlashes=true&includeFiles=false&apikey=" + \ settings.sonarr.apikey try: r = requests.get(url_sonarr_api_filesystem, timeout=60, verify=False, headers=headers) r.raise_for_status() except requests.exceptions.HTTPError: logging.exception("BAZARR Error trying to get series from Sonarr. Http error.") return except requests.exceptions.ConnectionError: logging.exception("BAZARR Error trying to get series from Sonarr. Connection Error.") return except requests.exceptions.Timeout: logging.exception("BAZARR Error trying to get series from Sonarr. Timeout Error.") return except requests.exceptions.RequestException: logging.exception("BAZARR Error trying to get series from Sonarr.") return return r.json()
def get_profile_list(): apikey_sonarr = settings.sonarr.apikey sonarr_version = get_sonarr_version() profiles_list = [] # Get profiles data from Sonarr if sonarr_version.startswith('2'): url_sonarr_api_series = url_sonarr() + "/api/profile?apikey=" + apikey_sonarr elif sonarr_version.startswith('3'): url_sonarr_api_series = url_sonarr() + "/api/v3/languageprofile?apikey=" + apikey_sonarr try: profiles_json = requests.get(url_sonarr_api_series, timeout=60, verify=False) except requests.exceptions.ConnectionError as errc: logging.exception("BAZARR Error trying to get profiles from Sonarr. Connection Error.") except requests.exceptions.Timeout as errt: logging.exception("BAZARR Error trying to get profiles from Sonarr. Timeout Error.") except requests.exceptions.RequestException as err: logging.exception("BAZARR Error trying to get profiles from Sonarr.") else: # Parsing data returned from Sonarr if sonarr_version.startswith('2'): for profile in profiles_json.json(): profiles_list.append([profile['id'], profile['language'].capitalize()]) elif sonarr_version.startswith('3'): for profile in profiles_json.json(): profiles_list.append([profile['id'], profile['name'].capitalize()]) return profiles_list return None
def get_profile_list(): apikey_sonarr = settings.sonarr.apikey profiles_list = [] # Get profiles data from Sonarr if get_sonarr_info.is_legacy(): url_sonarr_api_series = url_sonarr() + "/api/profile?apikey=" + apikey_sonarr else: url_sonarr_api_series = url_sonarr() + "/api/v3/languageprofile?apikey=" + apikey_sonarr try: profiles_json = requests.get(url_sonarr_api_series, timeout=60, verify=False, headers=headers) except requests.exceptions.ConnectionError: logging.exception("BAZARR Error trying to get profiles from Sonarr. Connection Error.") return None except requests.exceptions.Timeout: logging.exception("BAZARR Error trying to get profiles from Sonarr. Timeout Error.") return None except requests.exceptions.RequestException: logging.exception("BAZARR Error trying to get profiles from Sonarr.") return None # Parsing data returned from Sonarr if get_sonarr_info.is_legacy(): for profile in profiles_json.json(): profiles_list.append([profile['id'], profile['language'].capitalize()]) else: for profile in profiles_json.json(): profiles_list.append([profile['id'], profile['name'].capitalize()]) return profiles_list
def get_tags(): apikey_sonarr = settings.sonarr.apikey tagsDict = [] # Get tags data from Sonarr if get_sonarr_info.is_legacy(): url_sonarr_api_series = url_sonarr( ) + "/api/tag?apikey=" + apikey_sonarr else: url_sonarr_api_series = url_sonarr( ) + "/api/v3/tag?apikey=" + apikey_sonarr try: tagsDict = requests.get(url_sonarr_api_series, timeout=60, verify=False, headers=headers) except requests.exceptions.ConnectionError: logging.exception( "BAZARR Error trying to get tags from Sonarr. Connection Error.") return [] except requests.exceptions.Timeout: logging.exception( "BAZARR Error trying to get tags from Sonarr. Timeout Error.") return [] except requests.exceptions.RequestException: logging.exception("BAZARR Error trying to get tags from Sonarr.") return [] else: return tagsDict.json()
def series_images(url): url = url.strip("/") apikey = settings.sonarr.apikey baseUrl = settings.sonarr.base_url if get_sonarr_info.is_legacy(): url_image = (url_sonarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' + apikey).replace('poster-250', 'poster-500') else: url_image = (url_sonarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' + apikey).replace('poster-250', 'poster-500') try: req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers) except: return '', 404 else: return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type'])
def notify_sonarr(sonarr_series_id): try: url = url_sonarr() + "/api/command?apikey=" + settings.sonarr.apikey data = {'name': 'RescanSeries', 'seriesId': int(sonarr_series_id)} requests.post(url, json=data, timeout=60, verify=False) except Exception as e: logging.debug('BAZARR notify Sonarr')
def notify_sonarr(sonarr_series_id): try: if get_sonarr_info.is_legacy(): url = url_sonarr( ) + "/api/command?apikey=" + settings.sonarr.apikey else: url = url_sonarr( ) + "/api/v3/command?apikey=" + settings.sonarr.apikey data = {'name': 'RescanSeries', 'seriesId': int(sonarr_series_id)} requests.post(url, json=data, timeout=60, verify=False, headers=headers) except Exception: logging.exception('BAZARR cannot notify Sonarr')
def get_sonarr_version(): sonarr_version = '' if settings.general.getboolean('use_sonarr'): try: sv = url_sonarr() + "/api/system/status?apikey=" + settings.sonarr.apikey sonarr_version = requests.get(sv, timeout=60, verify=False).json()['version'] except Exception: logging.debug('BAZARR cannot get Sonarr version') return sonarr_version
def image_proxy(url): apikey = settings.sonarr.apikey url_image = (url_sonarr() + '/api/' + url + '?apikey=' + apikey).replace('poster-250', 'poster-500') try: req = requests.get(url_image, stream=True, timeout=15, verify=False) except: return None else: return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type'])
def get_sonarr_rootfolder(): apikey_sonarr = settings.sonarr.apikey sonarr_rootfolder = [] # Get root folder data from Sonarr if get_sonarr_info.is_legacy(): url_sonarr_api_rootfolder = url_sonarr() + "/api/rootfolder?apikey=" + apikey_sonarr else: url_sonarr_api_rootfolder = url_sonarr() + "/api/v3/rootfolder?apikey=" + apikey_sonarr try: rootfolder = requests.get(url_sonarr_api_rootfolder, timeout=60, verify=False, headers=headers) except requests.exceptions.ConnectionError: logging.exception("BAZARR Error trying to get rootfolder from Sonarr. Connection Error.") return [] except requests.exceptions.Timeout: logging.exception("BAZARR Error trying to get rootfolder from Sonarr. Timeout Error.") return [] except requests.exceptions.RequestException: logging.exception("BAZARR Error trying to get rootfolder from Sonarr.") return [] else: sonarr_movies_paths = list(TableShows.select(TableShows.path).dicts()) for folder in rootfolder.json(): if any(item['path'].startswith(folder['path']) for item in sonarr_movies_paths): sonarr_rootfolder.append({'id': folder['id'], 'path': folder['path']}) db_rootfolder = TableShowsRootfolder.select(TableShowsRootfolder.id, TableShowsRootfolder.path).dicts() rootfolder_to_remove = [x for x in db_rootfolder if not next((item for item in sonarr_rootfolder if item['id'] == x['id']), False)] rootfolder_to_update = [x for x in sonarr_rootfolder if next((item for item in db_rootfolder if item['id'] == x['id']), False)] rootfolder_to_insert = [x for x in sonarr_rootfolder if not next((item for item in db_rootfolder if item['id'] == x['id']), False)] for item in rootfolder_to_remove: TableShowsRootfolder.delete().where(TableShowsRootfolder.id == item['id']).execute() for item in rootfolder_to_update: TableShowsRootfolder.update({TableShowsRootfolder.path: item['path']})\ .where(TableShowsRootfolder.id == item['id'])\ .execute() for item in rootfolder_to_insert: TableShowsRootfolder.insert({TableShowsRootfolder.id: item['id'], TableShowsRootfolder.path: item['path']})\ .execute()
def configure(self): self.apikey_sonarr = settings.sonarr.apikey self.connection = Connection(url_sonarr() + "/signalr", self.session) self.connection.qs = {'apikey': self.apikey_sonarr} sonarr_hub = self.connection.register_hub( '') # Sonarr doesn't use named hub sonarr_method = ['series', 'episode'] for item in sonarr_method: sonarr_hub.client.on(item, dispatcher) self.connection.exception += self.exception_handler
def get_sonarr_platform(): sonarr_platform = '' if settings.general.getboolean('use_sonarr'): try: sv = url_sonarr() + "/api/system/status?apikey=" + settings.sonarr.apikey response = requests.get(sv, timeout=60, verify=False).json() if response['isLinux'] or response['isOsx']: sonarr_platform = 'posix' elif response['isWindows']: sonarr_platform = 'nt' except Exception: logging.debug('BAZARR cannot get Sonarr platform') return sonarr_platform
def version(): """ Call system/status API endpoint and get the Sonarr version @return: str """ sonarr_version = region.get( "sonarr_version", expiration_time=datetime.timedelta(seconds=60).total_seconds()) if sonarr_version: region.set("sonarr_version", sonarr_version) return sonarr_version else: sonarr_version = '' if settings.general.getboolean('use_sonarr'): try: sv = url_sonarr( ) + "/api/system/status?apikey=" + settings.sonarr.apikey sonarr_json = requests.get(sv, timeout=60, verify=False, headers=headers).json() if 'version' in sonarr_json: sonarr_version = sonarr_json['version'] else: raise json.decoder.JSONDecodeError except json.decoder.JSONDecodeError: sv = url_sonarr( ) + "/api/v3/system/status?apikey=" + settings.sonarr.apikey sonarr_version = requests.get( sv, timeout=60, verify=False, headers=headers).json()['version'] except Exception: logging.debug('BAZARR cannot get Sonarr version') sonarr_version = 'unknown' logging.debug('BAZARR got this Sonarr version from its API: {}'.format( sonarr_version)) region.set("sonarr_version", sonarr_version) return sonarr_version
def get_sonarr_platform(): use_sonarr = settings.general.getboolean('use_sonarr') apikey_sonarr = settings.sonarr.apikey sv = url_sonarr() + "/api/system/status?apikey=" + apikey_sonarr sonarr_platform = '' if use_sonarr: try: if requests.get(sv, timeout=60, verify=False).json()['isLinux'] or requests.get( sv, timeout=60, verify=False).json()['isOsx']: sonarr_platform = 'posix' elif requests.get(sv, timeout=60, verify=False).json()['isWindows']: sonarr_platform = 'nt' except Exception as e: logging.DEBUG('BAZARR cannot get Sonarr platform') return sonarr_platform
def configure(self): self.apikey_sonarr = settings.sonarr.apikey self.connection = HubConnectionBuilder() \ .with_url(url_sonarr() + "/signalr/messages?access_token={}".format(self.apikey_sonarr), options={ "verify_ssl": False, "headers": headers }) \ .with_automatic_reconnect({ "type": "raw", "keep_alive_interval": 5, "reconnect_interval": 180, "max_attempts": None }).build() self.connection.on_open(self.on_connect_handler) self.connection.on_reconnect(lambda: logging.error( 'BAZARR SignalR client for Sonarr connection as been lost. ' 'Trying to reconnect...')) self.connection.on_close(lambda: logging.debug( 'BAZARR SignalR client for Sonarr is disconnected.')) self.connection.on_error(self.exception_handler) self.connection.on("receiveMessage", dispatcher)
def update_series(): apikey_sonarr = settings.sonarr.apikey if apikey_sonarr is None: return sonarr_version = get_sonarr_version() serie_default_enabled = settings.general.getboolean( 'serie_default_enabled') if serie_default_enabled is True: serie_default_profile = settings.general.serie_default_profile if serie_default_profile == '': serie_default_profile = None else: serie_default_profile = None audio_profiles = get_profile_list() tagsDict = get_tags() # Get shows data from Sonarr url_sonarr_api_series = url_sonarr( ) + "/api/series?apikey=" + apikey_sonarr try: r = requests.get(url_sonarr_api_series, timeout=60, verify=False) r.raise_for_status() except requests.exceptions.HTTPError: logging.exception( "BAZARR Error trying to get series from Sonarr. Http error.") return except requests.exceptions.ConnectionError: logging.exception( "BAZARR Error trying to get series from Sonarr. Connection Error.") return except requests.exceptions.Timeout: logging.exception( "BAZARR Error trying to get series from Sonarr. Timeout Error.") return except requests.exceptions.RequestException: logging.exception("BAZARR Error trying to get series from Sonarr.") return # Get current shows in DB current_shows_db = database.execute( "SELECT sonarrSeriesId FROM table_shows") current_shows_db_list = [x['sonarrSeriesId'] for x in current_shows_db] current_shows_sonarr = [] series_to_update = [] series_to_add = [] series_list_length = len(r.json()) for i, show in enumerate(r.json(), 1): overview = show['overview'] if 'overview' in show else '' poster = '' fanart = '' for image in show['images']: if image['coverType'] == 'poster': poster_big = image['url'].split('?')[0] poster = os.path.splitext( poster_big)[0] + '-250' + os.path.splitext(poster_big)[1] if image['coverType'] == 'fanart': fanart = image['url'].split('?')[0] alternate_titles = None if show['alternateTitles'] is not None: alternate_titles = str( [item['title'] for item in show['alternateTitles']]) audio_language = [] if sonarr_version.startswith('2'): audio_language = profile_id_to_language(show['qualityProfileId'], audio_profiles) else: audio_language = profile_id_to_language(show['languageProfileId'], audio_profiles) tags = [d['label'] for d in tagsDict if d['id'] in show['tags']] imdbId = show['imdbId'] if 'imdbId' in show else None # Add shows in Sonarr to current shows list current_shows_sonarr.append(show['id']) if show['id'] in current_shows_db_list: series_to_update.append({ 'title': show["title"], 'path': show["path"], 'tvdbId': int(show["tvdbId"]), 'sonarrSeriesId': int(show["id"]), 'overview': overview, 'poster': poster, 'fanart': fanart, 'audio_language': str(audio_language), 'sortTitle': show['sortTitle'], 'year': str(show['year']), 'alternateTitles': alternate_titles, 'tags': str(tags), 'seriesType': show['seriesType'], 'imdbId': imdbId }) else: series_to_add.append({ 'title': show["title"], 'path': show["path"], 'tvdbId': show["tvdbId"], 'sonarrSeriesId': show["id"], 'overview': overview, 'poster': poster, 'fanart': fanart, 'audio_language': str(audio_language), 'sortTitle': show['sortTitle'], 'year': str(show['year']), 'alternateTitles': alternate_titles, 'tags': str(tags), 'seriesType': show['seriesType'], 'imdbId': imdbId, 'profileId': serie_default_profile }) # Remove old series from DB removed_series = list( set(current_shows_db_list) - set(current_shows_sonarr)) for series in removed_series: database.execute("DELETE FROM table_shows WHERE sonarrSeriesId=?", (series, )) event_stream(type='series', action='delete', series=series) # Update existing series in DB series_in_db_list = [] series_in_db = database.execute( "SELECT title, path, tvdbId, sonarrSeriesId, overview, poster, fanart, " "audio_language, sortTitle, year, alternateTitles, tags, seriesType, imdbId FROM table_shows" ) for item in series_in_db: series_in_db_list.append(item) series_to_update_list = [ i for i in series_to_update if i not in series_in_db_list ] for updated_series in series_to_update_list: query = dict_converter.convert(updated_series) database.execute( '''UPDATE table_shows SET ''' + query.keys_update + ''' WHERE sonarrSeriesId = ?''', query.values + (updated_series['sonarrSeriesId'], )) event_stream(type='series', action='update', series=updated_series['sonarrSeriesId']) # Insert new series in DB for added_series in series_to_add: query = dict_converter.convert(added_series) result = database.execute( '''INSERT OR IGNORE INTO table_shows(''' + query.keys_insert + ''') VALUES(''' + query.question_marks + ''')''', query.values) if result: list_missing_subtitles(no=added_series['sonarrSeriesId']) else: logging.debug( 'BAZARR unable to insert this series into the database:', path_mappings.path_replace(added_series['path'])) event_stream(type='series', action='insert', series=added_series['sonarrSeriesId']) logging.debug( 'BAZARR All series synced from Sonarr into database.')
def update_series(send_event=True): check_sonarr_rootfolder() apikey_sonarr = settings.sonarr.apikey if apikey_sonarr is None: return serie_default_enabled = settings.general.getboolean('serie_default_enabled') if serie_default_enabled is True: serie_default_profile = settings.general.serie_default_profile if serie_default_profile == '': serie_default_profile = None else: serie_default_profile = None audio_profiles = get_profile_list() tagsDict = get_tags() # Get shows data from Sonarr series = get_series_from_sonarr_api(url=url_sonarr(), apikey_sonarr=apikey_sonarr) if not series: return else: # Get current shows in DB current_shows_db = TableShows.select(TableShows.sonarrSeriesId).dicts() current_shows_db_list = [x['sonarrSeriesId'] for x in current_shows_db] current_shows_sonarr = [] series_to_update = [] series_to_add = [] series_count = len(series) for i, show in enumerate(series): if send_event: show_progress(id='series_progress', header='Syncing series...', name=show['title'], value=i, count=series_count) # Add shows in Sonarr to current shows list current_shows_sonarr.append(show['id']) if show['id'] in current_shows_db_list: series_to_update.append(seriesParser(show, action='update', tags_dict=tagsDict, serie_default_profile=serie_default_profile, audio_profiles=audio_profiles)) else: series_to_add.append(seriesParser(show, action='insert', tags_dict=tagsDict, serie_default_profile=serie_default_profile, audio_profiles=audio_profiles)) if send_event: hide_progress(id='series_progress') # Remove old series from DB removed_series = list(set(current_shows_db_list) - set(current_shows_sonarr)) for series in removed_series: try: TableShows.delete().where(TableShows.sonarrSeriesId == series).execute() except Exception as e: logging.error(f"BAZARR cannot delete series with sonarrSeriesId {series} because of {e}") continue else: if send_event: event_stream(type='series', action='delete', payload=series) # Update existing series in DB series_in_db_list = [] series_in_db = TableShows.select(TableShows.title, TableShows.path, TableShows.tvdbId, TableShows.sonarrSeriesId, TableShows.overview, TableShows.poster, TableShows.fanart, TableShows.audio_language, TableShows.sortTitle, TableShows.year, TableShows.alternateTitles, TableShows.tags, TableShows.seriesType, TableShows.imdbId).dicts() for item in series_in_db: series_in_db_list.append(item) series_to_update_list = [i for i in series_to_update if i not in series_in_db_list] for updated_series in series_to_update_list: try: TableShows.update(updated_series).where(TableShows.sonarrSeriesId == updated_series['sonarrSeriesId']).execute() except IntegrityError as e: logging.error(f"BAZARR cannot update series {updated_series['path']} because of {e}") continue else: if send_event: event_stream(type='series', payload=updated_series['sonarrSeriesId']) # Insert new series in DB for added_series in series_to_add: try: result = TableShows.insert(added_series).on_conflict(action='IGNORE').execute() except IntegrityError as e: logging.error(f"BAZARR cannot insert series {added_series['path']} because of {e}") continue else: if result: list_missing_subtitles(no=added_series['sonarrSeriesId']) else: logging.debug('BAZARR unable to insert this series into the database:', path_mappings.path_replace(added_series['path'])) if send_event: event_stream(type='series', action='update', payload=added_series['sonarrSeriesId']) logging.debug('BAZARR All series synced from Sonarr into database.')
def update_one_series(series_id, action): logging.debug('BAZARR syncing this specific series from Sonarr: {}'.format(series_id)) # Check if there's a row in database for this series ID existing_series = TableShows.select(TableShows.path)\ .where(TableShows.sonarrSeriesId == series_id)\ .dicts()\ .get_or_none() # Delete series from DB if action == 'deleted' and existing_series: try: TableShows.delete().where(TableShows.sonarrSeriesId == int(series_id)).execute() except Exception as e: logging.error(f"BAZARR cannot delete series with sonarrSeriesId {series_id} because of {e}") else: TableEpisodes.delete().where(TableEpisodes.sonarrSeriesId == int(series_id)).execute() event_stream(type='series', action='delete', payload=int(series_id)) return serie_default_enabled = settings.general.getboolean('serie_default_enabled') if serie_default_enabled is True: serie_default_profile = settings.general.serie_default_profile if serie_default_profile == '': serie_default_profile = None else: serie_default_profile = None audio_profiles = get_profile_list() tagsDict = get_tags() try: # Get series data from sonarr api series = None series_data = get_series_from_sonarr_api(url=url_sonarr(), apikey_sonarr=settings.sonarr.apikey, sonarr_series_id=int(series_id)) if not series_data: return else: if action == 'updated' and existing_series: series = seriesParser(series_data, action='update', tags_dict=tagsDict, serie_default_profile=serie_default_profile, audio_profiles=audio_profiles) elif action == 'updated' and not existing_series: series = seriesParser(series_data, action='insert', tags_dict=tagsDict, serie_default_profile=serie_default_profile, audio_profiles=audio_profiles) except Exception: logging.debug('BAZARR cannot parse series returned by SignalR feed.') return # Update existing series in DB if action == 'updated' and existing_series: try: TableShows.update(series).where(TableShows.sonarrSeriesId == series['sonarrSeriesId']).execute() except IntegrityError as e: logging.error(f"BAZARR cannot update series {series['path']} because of {e}") else: sync_episodes(series_id=int(series_id), send_event=True) event_stream(type='series', action='update', payload=int(series_id)) logging.debug('BAZARR updated this series into the database:{}'.format(path_mappings.path_replace( series['path']))) # Insert new series in DB elif action == 'updated' and not existing_series: try: TableShows.insert(series).on_conflict(action='IGNORE').execute() except IntegrityError as e: logging.error(f"BAZARR cannot insert series {series['path']} because of {e}") else: event_stream(type='series', action='update', payload=int(series_id)) logging.debug('BAZARR inserted this series into the database:{}'.format(path_mappings.path_replace( series['path'])))
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 update_series(): notifications.write(msg="Update series list from Sonarr is running...", queue='get_series') apikey_sonarr = settings.sonarr.apikey sonarr_version = get_sonarr_version() serie_default_enabled = settings.general.getboolean( 'serie_default_enabled') serie_default_language = settings.general.serie_default_language serie_default_hi = settings.general.serie_default_hi serie_default_forced = settings.general.serie_default_forced if apikey_sonarr is None: pass else: audio_profiles = get_profile_list() # Get shows data from Sonarr url_sonarr_api_series = url_sonarr( ) + "/api/series?apikey=" + apikey_sonarr try: r = requests.get(url_sonarr_api_series, timeout=60, verify=False) r.raise_for_status() except requests.exceptions.HTTPError as errh: logging.exception( "BAZARR Error trying to get series from Sonarr. Http error.") return except requests.exceptions.ConnectionError as errc: logging.exception( "BAZARR Error trying to get series from Sonarr. Connection Error." ) return except requests.exceptions.Timeout as errt: logging.exception( "BAZARR Error trying to get series from Sonarr. Timeout Error." ) return except requests.exceptions.RequestException as err: logging.exception("BAZARR Error trying to get series from Sonarr.") return else: # Get current shows in DB current_shows_db = database.execute( "SELECT sonarrSeriesId FROM table_shows") current_shows_db_list = [ x['sonarrSeriesId'] for x in current_shows_db ] current_shows_sonarr = [] series_to_update = [] series_to_add = [] altered_series = [] seriesListLength = len(r.json()) for i, show in enumerate(r.json(), 1): notifications.write(msg="Getting series data from Sonarr...", queue='get_series', item=i, length=seriesListLength) try: overview = six.text_type(show['overview']) except: overview = "" try: poster_big = show['images'][2]['url'].split('?')[0] poster = os.path.splitext(poster_big)[ 0] + '-250' + os.path.splitext(poster_big)[1] except: poster = "" try: fanart = show['images'][0]['url'].split('?')[0] except: fanart = "" if show['alternateTitles'] != None: alternateTitles = str( [item['title'] for item in show['alternateTitles']]) else: alternateTitles = None # Add shows in Sonarr to current shows list current_shows_sonarr.append(show['id']) if show['tvdbId'] in current_shows_db_list: series_to_update.append({ 'title': six.text_type(show["title"]), 'path': six.text_type(show["path"]), 'tvdbId': int(show["tvdbId"]), 'sonarrSeriesId': int(show["id"]), 'overview': six.text_type(overview), 'poster': six.text_type(poster), 'fanart': six.text_type(fanart), 'audio_language': six.text_type( profile_id_to_language( (show['qualityProfileId'] if get_sonarr_version().startswith('2') else show['languageProfileId']), audio_profiles)), 'sortTitle': six.text_type(show['sortTitle']), 'year': six.text_type(show['year']), 'alternateTitles': six.text_type(alternateTitles) }) else: if serie_default_enabled is True: series_to_add.append({ 'title': show["title"], 'path': show["path"], 'tvdbId': show["tvdbId"], 'languages': serie_default_language, 'hearing_impaired': serie_default_hi, 'sonarrSeriesId': show["id"], 'overview': overview, 'poster': poster, 'fanart': fanart, 'audio_language': profile_id_to_language( (show['qualityProfileId'] if sonarr_version.startswith('2') else show['languageProfileId']), audio_profiles), 'sortTitle': show['sortTitle'], 'year': show['year'], 'alternateTitles': alternateTitles, 'forced': serie_default_forced }) else: series_to_add.append({ 'title': show["title"], 'path': show["path"], 'tvdbId': show["tvdbId"], 'sonarrSeriesId': show["id"], 'overview': overview, 'poster': poster, 'fanart': fanart, 'audio_language': profile_id_to_language( (show['qualityProfileId'] if sonarr_version.startswith('2') else show['languageProfileId']), audio_profiles), 'sortTitle': show['sortTitle'], 'year': show['year'], 'alternateTitles': alternateTitles }) # Remove old series from DB removed_series = list( set(current_shows_db_list) - set(current_shows_sonarr)) for series in removed_series: database.execute( "DELETE FROM table_shows WHERE sonarrSEriesId=?", (series, )) # Update existing series in DB series_in_db_list = [] series_in_db = database.execute( "SELECT title, path, tvdbId, sonarrSeriesId, overview, poster, fanart, " "audio_language, sortTitle, year, alternateTitles FROM table_shows" ) for item in series_in_db: series_in_db_list.append(item) series_to_update_list = [ i for i in series_to_update if i not in series_in_db_list ] for updated_series in series_to_update_list: query = dict_converter.convert(updated_series) database.execute( '''UPDATE table_shows SET ''' + query.keys_update + ''' WHERE sonarrSeriesId = ?''', query.values + (updated_series['sonarrSeriesId'], )) # Insert new series in DB for added_series in series_to_add: query = dict_converter.convert(added_series) result = database.execute( '''INSERT OR IGNORE INTO table_shows(''' + query.keys_insert + ''') VALUES(''' + query.question_marks + ''')''', query.values) if result: list_missing_subtitles(no=added_series['sonarrSeriesId']) else: logging.debug( 'BAZARR unable to insert this series into the database:', path_replace(added_series['path'])) logging.debug( 'BAZARR All series synced from Sonarr into database.')
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 = database.execute( "SELECT sonarrEpisodeId, path, sonarrSeriesId FROM table_episodes") 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 = database.execute( "SELECT sonarrSeriesId, title FROM table_shows") seriesIdListLength = len(seriesIdList) 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['sonarrSeriesId']) + "&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({ 'sonarrSeriesId': episode['seriesId'], 'sonarrEpisodeId': 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({ 'sonarrSeriesId': episode['seriesId'], 'sonarrEpisodeId': 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'] }) # Remove old episodes from DB removed_episodes = list( set(current_episodes_db_list) - set(current_episodes_sonarr)) for removed_episode in removed_episodes: database.execute("DELETE FROM table_episodes WHERE sonarrEpisodeId=?", (removed_episode, )) # Update existing episodes in DB episode_in_db_list = [] episodes_in_db = database.execute( "SELECT sonarrSeriesId, sonarrEpisodeId, title, path, season, episode, " "scene_name, monitored, format, resolution, video_codec, audio_codec, " "episode_file_id FROM table_episodes") 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: query = dict_converter.convert(updated_episode) database.execute( '''UPDATE table_episodes SET ''' + query.keys_update + ''' WHERE sonarrEpisodeId = ?''', query.values + (updated_episode['sonarrEpisodeId'], )) altered_episodes.append([ updated_episode['sonarrEpisodeId'], updated_episode['path'], updated_episode['sonarrSeriesId'] ]) # Insert new episodes in DB for added_episode in episodes_to_add: query = dict_converter.convert(added_episode) result = database.execute( '''INSERT OR IGNORE INTO table_episodes(''' + query.keys_insert + ''') VALUES(''' + query.question_marks + ''')''', query.values) if result > 0: altered_episodes.append( [added_episode['sonarrEpisodeId'], added_episode['path']]) else: logging.debug( 'BAZARR unable to insert this episode into the database:', path_replace(added_episode['path'])) # 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(altered_episode[1], path_replace(altered_episode[1])) 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 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)