Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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()
Ejemplo n.º 3
0
 def start(self):
     if get_sonarr_info.is_legacy():
         logging.warning(
             'BAZARR can only sync from Sonarr v3 SignalR feed to get real-time update. You should '
             'consider upgrading your version({}).'.format(
                 get_sonarr_info.version()))
     else:
         logging.info('BAZARR trying to connect to Sonarr SignalR feed...')
         self.configure()
         while not self.connection.started:
             try:
                 self.connection.start()
             except ConnectionError:
                 time.sleep(5)
             except json.decoder.JSONDecodeError:
                 logging.error(
                     "BAZARR cannot parse JSON returned by SignalR feed. This is caused by a permissions "
                     "issue when Sonarr try to access its /config/.config directory."
                     "Typically permissions are too permissive - only the user and group Sonarr runs as should have Read/Write permissions (e.g. files 664 / folders 775)"
                     "You should fix permissions on that directory and restart Sonarr. Also, if you're a Docker image "
                     "user, you should make sure you properly defined PUID/PGID environment variables. "
                     "Otherwise, please contact Sonarr support.")
             else:
                 logging.info(
                     'BAZARR SignalR client for Sonarr is connected and waiting for events.'
                 )
             finally:
                 if not args.dev:
                     scheduler.add_job(update_series,
                                       kwargs={'send_event': True},
                                       max_instances=1)
                     scheduler.add_job(sync_episodes,
                                       kwargs={'send_event': True},
                                       max_instances=1)
Ejemplo n.º 4
0
def get_series_from_sonarr_api(url, apikey_sonarr, sonarr_series_id=None):
    url_sonarr_api_series = url + "/api/{0}series/{1}?apikey={2}".format(
        '' if get_sonarr_info.is_legacy() else 'v3/',
        sonarr_series_id if sonarr_series_id else "", apikey_sonarr)
    try:
        r = requests.get(url_sonarr_api_series,
                         timeout=60,
                         verify=False,
                         headers=headers)
        r.raise_for_status()
    except requests.exceptions.HTTPError as e:
        if e.response.status_code:
            raise 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
    else:
        return r.json()
Ejemplo n.º 5
0
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()
Ejemplo n.º 6
0
def seriesParser(show, action, tags_dict, serie_default_profile, audio_profiles):
    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 get_sonarr_info.is_legacy():
        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 tags_dict if d['id'] in show['tags']]

    imdbId = show['imdbId'] if 'imdbId' in show else None

    if action == 'update':
        return {'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:
        return {'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}
Ejemplo n.º 7
0
def get_series_from_sonarr_api(series_id, url, apikey_sonarr):
    if series_id:
        url_sonarr_api_series = url + "/api/{0}series/{1}?apikey={2}".format(
            '' if get_sonarr_info.is_legacy() else 'v3/', series_id,
            apikey_sonarr)
    else:
        url_sonarr_api_series = url + "/api/{0}series?apikey={1}".format(
            '' if get_sonarr_info.is_legacy() else 'v3/', apikey_sonarr)
    try:
        r = requests.get(url_sonarr_api_series,
                         timeout=60,
                         verify=False,
                         headers=headers)
        r.raise_for_status()
    except requests.exceptions.HTTPError as e:
        if e.response.status_code:
            raise 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
    else:
        series_json = []
        if series_id:
            series_json.append(r.json())
        else:
            series_json = r.json()
        series_list = []
        for series in series_json:
            series_list.append({
                'sonarrSeriesId': series['id'],
                'title': series['title']
            })
        return series_list
Ejemplo n.º 8
0
def get_episodes_from_sonarr_api(url,
                                 apikey_sonarr,
                                 series_id=None,
                                 episode_id=None):
    if series_id:
        url_sonarr_api_episode = url + "/api/{0}episode?seriesId={1}&apikey={2}".format(
            '' if get_sonarr_info.is_legacy() else 'v3/', series_id,
            apikey_sonarr)
    elif episode_id:
        url_sonarr_api_episode = url + "/api/{0}episode/{1}?apikey={2}".format(
            '' if get_sonarr_info.is_legacy() else 'v3/', episode_id,
            apikey_sonarr)
    else:
        return

    try:
        r = requests.get(url_sonarr_api_episode,
                         timeout=60,
                         verify=False,
                         headers=headers)
        r.raise_for_status()
    except requests.exceptions.HTTPError:
        logging.exception(
            "BAZARR Error trying to get episodes from Sonarr. Http error.")
        return
    except requests.exceptions.ConnectionError:
        logging.exception(
            "BAZARR Error trying to get episodes from Sonarr. Connection Error."
        )
        return
    except requests.exceptions.Timeout:
        logging.exception(
            "BAZARR Error trying to get episodes from Sonarr. Timeout Error.")
        return
    except requests.exceptions.RequestException:
        logging.exception("BAZARR Error trying to get episodes from Sonarr.")
        return
    else:
        return r.json()
Ejemplo n.º 9
0
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'])
Ejemplo n.º 10
0
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()
Ejemplo n.º 11
0
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.')
Ejemplo n.º 12
0
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)