def delete(self):
        sonarrSeriesId = request.args.get('seriesid')
        sonarrEpisodeId = request.args.get('episodeid')
        episodeInfo = TableEpisodes.select(TableEpisodes.title,
                                           TableEpisodes.path,
                                           TableEpisodes.scene_name,
                                           TableEpisodes.audio_language)\
            .where(TableEpisodes.sonarrEpisodeId == sonarrEpisodeId)\
            .dicts()\
            .get()

        episodePath = path_mappings.path_replace(episodeInfo['path'])

        language = request.form.get('language')
        forced = request.form.get('forced')
        hi = request.form.get('hi')
        subtitlesPath = request.form.get('path')

        subtitlesPath = path_mappings.path_replace_reverse(subtitlesPath)

        delete_subtitles(media_type='series',
                         language=language,
                         forced=forced,
                         hi=hi,
                         media_path=episodePath,
                         subtitles_path=subtitlesPath,
                         sonarr_series_id=sonarrSeriesId,
                         sonarr_episode_id=sonarrEpisodeId)

        return '', 204
Exemple #2
0
    def get(self):
        episodes_conditions = [
            (TableEpisodes.missing_subtitles.is_null(False)),
            (TableEpisodes.missing_subtitles != '[]')
        ]
        episodes_conditions += get_exclusion_clause('series')
        missing_episodes = TableEpisodes.select(TableShows.tags,
                                                TableShows.seriesType,
                                                TableEpisodes.monitored)\
            .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
            .where(reduce(operator.and_, episodes_conditions))\
            .count()

        movies_conditions = [(TableMovies.missing_subtitles.is_null(False)),
                             (TableMovies.missing_subtitles != '[]')]
        movies_conditions += get_exclusion_clause('movie')
        missing_movies = TableMovies.select(TableMovies.tags,
                                            TableMovies.monitored)\
            .where(reduce(operator.and_, movies_conditions))\
            .count()

        throttled_providers = len(get_throttled_providers())

        health_issues = len(get_health_issues())

        result = {
            "episodes": missing_episodes,
            "movies": missing_movies,
            "providers": throttled_providers,
            "status": health_issues
        }
        return jsonify(result)
Exemple #3
0
    def get(self):
        # Manual Search
        sonarrEpisodeId = request.args.get('episodeid')
        episodeInfo = TableEpisodes.select(TableEpisodes.title,
                                           TableEpisodes.path,
                                           TableEpisodes.scene_name,
                                           TableShows.profileId) \
            .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
            .where(TableEpisodes.sonarrEpisodeId == sonarrEpisodeId) \
            .dicts() \
            .get()

        title = episodeInfo['title']
        episodePath = path_mappings.path_replace(episodeInfo['path'])
        sceneName = episodeInfo['scene_name']
        profileId = episodeInfo['profileId']
        if sceneName is None: sceneName = "None"

        providers_list = get_providers()
        providers_auth = get_providers_auth()

        data = manual_search(episodePath, profileId, providers_list,
                             providers_auth, sceneName, title, 'series')
        if not data:
            data = []
        return jsonify(data=data)
Exemple #4
0
def get_episode_name(sonarr_episode_id):
    data = TableEpisodes.select(TableEpisodes.title, TableEpisodes.season, TableEpisodes.episode)\
        .where(TableEpisodes.sonarrEpisodeId == sonarr_episode_id)\
        .dicts()\
        .get()

    return data['title'], data['season'], data['episode']
Exemple #5
0
    def post(self):
        sonarr_series_id = int(request.args.get('seriesid'))
        sonarr_episode_id = int(request.args.get('episodeid'))
        provider = request.form.get('provider')
        subs_id = request.form.get('subs_id')
        language = request.form.get('language')

        episodeInfo = TableEpisodes.select(TableEpisodes.path)\
            .where(TableEpisodes.sonarrEpisodeId == sonarr_episode_id)\
            .dicts()\
            .get_or_none()

        if not episodeInfo:
            return 'Episode not found', 500

        media_path = episodeInfo['path']
        subtitles_path = request.form.get('subtitles_path')

        blacklist_log(sonarr_series_id=sonarr_series_id,
                      sonarr_episode_id=sonarr_episode_id,
                      provider=provider,
                      subs_id=subs_id,
                      language=language)
        delete_subtitles(media_type='series',
                         language=language,
                         forced=False,
                         hi=False,
                         media_path=path_mappings.path_replace(media_path),
                         subtitles_path=subtitles_path,
                         sonarr_series_id=sonarr_series_id,
                         sonarr_episode_id=sonarr_episode_id)
        episode_download_subtitles(sonarr_episode_id)
        event_stream(type='episode-history')
        return '', 200
Exemple #6
0
def get_episode_name(sonarrEpisodeId):
    data = TableEpisodes.select(
        TableEpisodes.title, TableEpisodes.season,
        TableEpisodes.episode).where(
            TableEpisodes.sonarr_episode_id == sonarrEpisodeId).first()

    return data.title, data.season, data.episode
Exemple #7
0
def series_scan_subtitles(no):
    episodes = TableEpisodes.select(
        TableEpisodes.path).where(TableEpisodes.sonarr_series_id == no)

    for episode in episodes:
        store_subtitles(path_replace(episode.path))

    list_missing_subtitles(no)
Exemple #8
0
def series_scan_subtitles(no):
    episodes = TableEpisodes.select(TableEpisodes.path)\
        .where(TableEpisodes.sonarrSeriesId == no)\
        .order_by(TableEpisodes.sonarrEpisodeId)\
        .dicts()

    for episode in episodes:
        store_subtitles(episode['path'], path_mappings.path_replace(episode['path']), use_cache=False)
Exemple #9
0
def refine_from_ffprobe(path, video):
    if isinstance(video, Movie):
        file_id = TableMovies.select(TableMovies.movie_file_id, TableMovies.file_size)\
            .where(TableMovies.path == path_mappings.path_replace_reverse_movie(path))\
            .dicts()\
            .get_or_none()
    else:
        file_id = TableEpisodes.select(TableEpisodes.episode_file_id, TableEpisodes.file_size)\
            .where(TableEpisodes.path == path_mappings.path_replace_reverse(path))\
            .dicts()\
            .get_or_none()

    if not file_id:
        return video

    if isinstance(video, Movie):
        data = parse_video_metadata(file=path, file_size=file_id['file_size'],
                                    movie_file_id=file_id['movie_file_id'])
    else:
        data = parse_video_metadata(file=path, file_size=file_id['file_size'],
                                    episode_file_id=file_id['episode_file_id'])

    if not data['ffprobe']:
        logging.debug("No FFprobe available in cache for this file: {}".format(path))
        return video

    logging.debug('FFprobe found: %s', data['ffprobe'])

    if 'video' not in data['ffprobe']:
        logging.debug('BAZARR FFprobe was unable to find video tracks in the file!')
    else:
        if 'resolution' in data['ffprobe']['video'][0]:
            if not video.resolution:
                video.resolution = data['ffprobe']['video'][0]['resolution']
        if 'codec' in data['ffprobe']['video'][0]:
            if not video.video_codec:
                video.video_codec = data['ffprobe']['video'][0]['codec']
        if 'frame_rate' in data['ffprobe']['video'][0]:
            if not video.fps:
                if isinstance(data['ffprobe']['video'][0]['frame_rate'], float):
                    video.fps = data['ffprobe']['video'][0]['frame_rate']
                else:
                    try:
                        video.fps = data['ffprobe']['video'][0]['frame_rate'].magnitude
                    except AttributeError:
                        video.fps = data['ffprobe']['video'][0]['frame_rate']

    if 'audio' not in data['ffprobe']:
        logging.debug('BAZARR FFprobe was unable to find audio tracks in the file!')
    else:
        if 'codec' in data['ffprobe']['audio'][0]:
            if not video.audio_codec:
                video.audio_codec = data['ffprobe']['audio'][0]['codec']
        for track in data['ffprobe']['audio']:
            if 'language' in track:
                video.audio_languages.add(track['language'].alpha3)

    return video
Exemple #10
0
    def patch(self):
        sonarrSeriesId = request.args.get('seriesid')
        sonarrEpisodeId = request.args.get('episodeid')
        episodeInfo = TableEpisodes.select(TableEpisodes.path,
                                           TableEpisodes.scene_name,
                                           TableEpisodes.audio_language,
                                           TableShows.title) \
            .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId)) \
            .where(TableEpisodes.sonarrEpisodeId == sonarrEpisodeId)\
            .dicts()\
            .get_or_none()

        if not episodeInfo:
            return 'Episode not found', 500

        title = episodeInfo['title']
        episodePath = path_mappings.path_replace(episodeInfo['path'])
        sceneName = episodeInfo['scene_name'] or "None"

        language = request.form.get('language')
        hi = request.form.get('hi').capitalize()
        forced = request.form.get('forced').capitalize()

        audio_language_list = get_audio_profile_languages(episode_id=sonarrEpisodeId)
        if len(audio_language_list) > 0:
            audio_language = audio_language_list[0]['name']
        else:
            audio_language = None

        try:
            result = list(generate_subtitles(episodePath, [(language, hi, forced)], audio_language, sceneName,
                                             title, 'series', profile_id=get_profile_id(episode_id=sonarrEpisodeId)))
            if result:
                result = result[0]
                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]
                history_log(1, sonarrSeriesId, sonarrEpisodeId, message, path, language_code, provider, score, subs_id,
                            subs_path)
                send_notifications(sonarrSeriesId, sonarrEpisodeId, message)
                store_subtitles(path, episodePath)
            else:
                event_stream(type='episode', payload=sonarrEpisodeId)

        except OSError:
            pass

        return '', 204
Exemple #11
0
    def get(self):
        start = request.args.get('start') or 0
        length = request.args.get('length') or -1
        seriesId = request.args.getlist('seriesid[]')

        count = TableShows.select().count()

        if len(seriesId) != 0:
            result = TableShows.select() \
                .where(TableShows.sonarrSeriesId.in_(seriesId)) \
                .order_by(TableShows.sortTitle).dicts()
        else:
            result = TableShows.select().order_by(TableShows.sortTitle).limit(length).offset(start).dicts()

        result = list(result)

        for item in result:
            postprocessSeries(item)

            # Add missing subtitles episode count
            episodes_missing_conditions = [(TableEpisodes.sonarrSeriesId == item['sonarrSeriesId']),
                                           (TableEpisodes.missing_subtitles != '[]')]
            episodes_missing_conditions += get_exclusion_clause('series')

            episodeMissingCount = TableEpisodes.select(TableShows.tags,
                                                       TableEpisodes.monitored,
                                                       TableShows.seriesType) \
                .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId)) \
                .where(reduce(operator.and_, episodes_missing_conditions)) \
                .count()
            item.update({"episodeMissingCount": episodeMissingCount})

            # Add episode count
            episodeFileCount = TableEpisodes.select(TableShows.tags,
                                                    TableEpisodes.monitored,
                                                    TableShows.seriesType) \
                .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId)) \
                .where(TableEpisodes.sonarrSeriesId == item['sonarrSeriesId']) \
                .count()
            item.update({"episodeFileCount": episodeFileCount})

        return jsonify(data=result, total=count)
Exemple #12
0
    def get(self):
        seriesId = request.args.getlist('seriesid[]')
        episodeId = request.args.getlist('episodeid[]')

        if len(episodeId) > 0:
            result = TableEpisodes.select().where(
                TableEpisodes.sonarrEpisodeId.in_(episodeId)).dicts()
        elif len(seriesId) > 0:
            result = TableEpisodes.select()\
                .where(TableEpisodes.sonarrSeriesId.in_(seriesId))\
                .order_by(TableEpisodes.season.desc(), TableEpisodes.episode.desc())\
                .dicts()
        else:
            return "Series or Episode ID not provided", 400

        result = list(result)
        for item in result:
            postprocessEpisode(item)

        return jsonify(data=result)
Exemple #13
0
def series_full_scan_subtitles():
    episodes = TableEpisodes.select(TableEpisodes.path)
    count_episodes = episodes.count()

    for i, episode in enumerate(episodes, 1):
        notifications.write(msg='Updating all episodes subtitles from disk...',
                            queue='list_subtitles_series',
                            item=i,
                            length=count_episodes)
        store_subtitles(path_replace(episode.path))

    gc.collect()
Exemple #14
0
def series_full_scan_subtitles():
    use_ffprobe_cache = settings.sonarr.getboolean('use_ffprobe_cache')

    episodes = TableEpisodes.select(TableEpisodes.path).dicts()

    count_episodes = len(episodes)
    for i, episode in enumerate(episodes):
        show_progress(id='episodes_disk_scan',
                      header='Full disk scan...',
                      name='Episodes subtitles',
                      value=i,
                      count=count_episodes)
        store_subtitles(episode['path'], path_mappings.path_replace(episode['path']), use_cache=use_ffprobe_cache)

    hide_progress(id='episodes_disk_scan')

    gc.collect()
Exemple #15
0
def wanted_download_subtitles(sonarr_episode_id):
    episodes_details = TableEpisodes.select(TableEpisodes.path,
                                            TableEpisodes.missing_subtitles,
                                            TableEpisodes.sonarrEpisodeId,
                                            TableEpisodes.sonarrSeriesId,
                                            TableEpisodes.audio_language,
                                            TableEpisodes.scene_name,
                                            TableEpisodes.failedAttempts,
                                            TableShows.title)\
        .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
        .where((TableEpisodes.sonarrEpisodeId == sonarr_episode_id))\
        .dicts()
    episodes_details = list(episodes_details)

    for episode in episodes_details:
        providers_list = get_providers()

        if providers_list:
            _wanted_episode(episode)
        else:
            logging.info("BAZARR All providers are throttled")
            break
Exemple #16
0
def wanted_search_missing_subtitles_series():
    conditions = [(TableEpisodes.missing_subtitles != '[]')]
    conditions += get_exclusion_clause('series')
    episodes = TableEpisodes.select(TableEpisodes.sonarrSeriesId,
                                    TableEpisodes.sonarrEpisodeId,
                                    TableShows.tags,
                                    TableEpisodes.monitored,
                                    TableShows.title,
                                    TableEpisodes.season,
                                    TableEpisodes.episode,
                                    TableEpisodes.title.alias('episodeTitle'),
                                    TableShows.seriesType)\
        .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
        .where(reduce(operator.and_, conditions))\
        .dicts()
    episodes = list(episodes)

    count_episodes = len(episodes)
    for i, episode in enumerate(episodes):
        show_progress(id='wanted_episodes_progress',
                      header='Searching subtitles...',
                      name='{0} - S{1:02d}E{2:02d} - {3}'.format(episode['title'],
                                                                 episode['season'],
                                                                 episode['episode'],
                                                                 episode['episodeTitle']),
                      value=i,
                      count=count_episodes)

        providers = get_providers()
        if providers:
            wanted_download_subtitles(episode['sonarrEpisodeId'])
        else:
            logging.info("BAZARR All providers are throttled")
            break

    hide_progress(id='wanted_episodes_progress')

    logging.info('BAZARR Finished searching for missing Series Subtitles. Check History for more information.')
Exemple #17
0
    def post(self):
        json_webhook = request.form.get('payload')
        parsed_json_webhook = json.loads(json_webhook)

        event = parsed_json_webhook['event']
        if event not in ['media.play']:
            return '', 204

        media_type = parsed_json_webhook['Metadata']['type']

        if media_type == 'episode':
            season = parsed_json_webhook['Metadata']['parentIndex']
            episode = parsed_json_webhook['Metadata']['index']
        else:
            season = episode = None

        ids = []
        for item in parsed_json_webhook['Metadata']['Guid']:
            splitted_id = item['id'].split('://')
            if len(splitted_id) == 2:
                ids.append({splitted_id[0]: splitted_id[1]})
        if not ids:
            return '', 404

        if media_type == 'episode':
            try:
                episode_imdb_id = [x['imdb'] for x in ids if 'imdb' in x][0]
                r = requests.get(
                    'https://imdb.com/title/{}'.format(episode_imdb_id),
                    headers={"User-Agent": os.environ["SZ_USER_AGENT"]})
                soup = bso(r.content, "html.parser")
                series_imdb_id = soup.find(
                    'a',
                    {'class': re.compile(r'SeriesParentLink__ParentTextLink')
                     })['href'].split('/')[2]
            except:
                return '', 404
            else:
                sonarrEpisodeId = TableEpisodes.select(TableEpisodes.sonarrEpisodeId) \
                    .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId)) \
                    .where(TableShows.imdbId == series_imdb_id,
                           TableEpisodes.season == season,
                           TableEpisodes.episode == episode) \
                    .dicts() \
                    .get()

                if sonarrEpisodeId:
                    episode_download_subtitles(
                        no=sonarrEpisodeId['sonarrEpisodeId'],
                        send_progress=True)
        else:
            try:
                movie_imdb_id = [x['imdb'] for x in ids if 'imdb' in x][0]
            except:
                return '', 404
            else:
                radarrId = TableMovies.select(TableMovies.radarrId)\
                    .where(TableMovies.imdbId == movie_imdb_id)\
                    .dicts()\
                    .get()
                if radarrId:
                    movies_download_subtitles(no=radarrId['radarrId'])

        return '', 200
Exemple #18
0
def process_subtitle(subtitle,
                     media_type,
                     audio_language,
                     path,
                     max_score,
                     is_upgrade=False,
                     is_manual=False):
    use_postprocessing = settings.general.getboolean('use_postprocessing')
    postprocessing_cmd = settings.general.postprocessing_cmd

    downloaded_provider = subtitle.provider_name
    downloaded_language_code3 = _get_download_code3(subtitle)

    downloaded_language = language_from_alpha3(downloaded_language_code3)
    downloaded_language_code2 = alpha2_from_alpha3(downloaded_language_code3)
    audio_language_code2 = alpha2_from_language(audio_language)
    audio_language_code3 = alpha3_from_language(audio_language)
    downloaded_path = subtitle.storage_path
    subtitle_id = subtitle.id
    if subtitle.language.hi:
        modifier_string = " HI"
    elif subtitle.language.forced:
        modifier_string = " forced"
    else:
        modifier_string = ""
    logging.debug('BAZARR Subtitles file saved to disk: ' + downloaded_path)
    if is_upgrade:
        action = "upgraded"
    elif is_manual:
        action = "manually downloaded"
    else:
        action = "downloaded"

    percent_score = round(subtitle.score * 100 / max_score, 2)
    message = downloaded_language + modifier_string + " subtitles " + action + " from " + \
        downloaded_provider + " with a score of " + str(percent_score) + "%."

    if media_type == 'series':
        episode_metadata = TableEpisodes.select(TableEpisodes.sonarrSeriesId,
                                                TableEpisodes.sonarrEpisodeId) \
            .where(TableEpisodes.path == path_mappings.path_replace_reverse(path)) \
            .dicts() \
            .get_or_none()
        if not episode_metadata:
            return
        series_id = episode_metadata['sonarrSeriesId']
        episode_id = episode_metadata['sonarrEpisodeId']
        sync_subtitles(video_path=path,
                       srt_path=downloaded_path,
                       forced=subtitle.language.forced,
                       srt_lang=downloaded_language_code2,
                       media_type=media_type,
                       percent_score=percent_score,
                       sonarr_series_id=episode_metadata['sonarrSeriesId'],
                       sonarr_episode_id=episode_metadata['sonarrEpisodeId'])
    else:
        movie_metadata = TableMovies.select(TableMovies.radarrId) \
            .where(TableMovies.path == path_mappings.path_replace_reverse_movie(path)) \
            .dicts() \
            .get_or_none()
        if not movie_metadata:
            return
        series_id = ""
        episode_id = movie_metadata['radarrId']
        sync_subtitles(video_path=path,
                       srt_path=downloaded_path,
                       forced=subtitle.language.forced,
                       srt_lang=downloaded_language_code2,
                       media_type=media_type,
                       percent_score=percent_score,
                       radarr_id=movie_metadata['radarrId'])

    if use_postprocessing is True:
        command = pp_replace(postprocessing_cmd, path, downloaded_path,
                             downloaded_language, downloaded_language_code2,
                             downloaded_language_code3, audio_language,
                             audio_language_code2, audio_language_code3,
                             subtitle.language.forced, percent_score,
                             subtitle_id, downloaded_provider, series_id,
                             episode_id, subtitle.language.hi)

        if media_type == 'series':
            use_pp_threshold = settings.general.getboolean(
                'use_postprocessing_threshold')
            pp_threshold = int(settings.general.postprocessing_threshold)
        else:
            use_pp_threshold = settings.general.getboolean(
                'use_postprocessing_threshold_movie')
            pp_threshold = int(settings.general.postprocessing_threshold_movie)

        if not use_pp_threshold or (use_pp_threshold
                                    and percent_score < pp_threshold):
            logging.debug(
                "BAZARR Using post-processing command: {}".format(command))
            postprocessing(command, path)
        else:
            logging.debug(
                "BAZARR post-processing skipped because subtitles score isn't below this "
                "threshold value: " + str(pp_threshold) + "%")

    if media_type == 'series':
        reversed_path = path_mappings.path_replace_reverse(path)
        reversed_subtitles_path = path_mappings.path_replace_reverse(
            downloaded_path)
        notify_sonarr(episode_metadata['sonarrSeriesId'])
        event_stream(type='series',
                     action='update',
                     payload=episode_metadata['sonarrSeriesId'])
        event_stream(type='episode-wanted',
                     action='delete',
                     payload=episode_metadata['sonarrEpisodeId'])

    else:
        reversed_path = path_mappings.path_replace_reverse_movie(path)
        reversed_subtitles_path = path_mappings.path_replace_reverse_movie(
            downloaded_path)
        notify_radarr(movie_metadata['radarrId'])
        event_stream(type='movie-wanted',
                     action='delete',
                     payload=movie_metadata['radarrId'])

    track_event(category=downloaded_provider,
                action=action,
                label=downloaded_language)

    return message, reversed_path, downloaded_language_code2, downloaded_provider, subtitle.score, \
        subtitle.language.forced, subtitle.id, reversed_subtitles_path, subtitle.language.hi
Exemple #19
0
def manual_upload_subtitle(path, language, forced, hi, title, scene_name, media_type, subtitle, audio_language):
    logging.debug('BAZARR Manually uploading subtitles for this file: ' + path)

    single = settings.general.getboolean('single_language')

    use_postprocessing = settings.general.getboolean('use_postprocessing')
    postprocessing_cmd = settings.general.postprocessing_cmd

    chmod = int(settings.general.chmod, 8) if not sys.platform.startswith(
        'win') and settings.general.getboolean('chmod_enabled') else None

    language = alpha3_from_alpha2(language)

    custom = CustomLanguage.from_value(language, "alpha3")
    if custom is None:
        lang_obj = Language(language)
    else:
        lang_obj = custom.subzero_language()

    if forced:
        lang_obj = Language.rebuild(lang_obj, forced=True)

    sub = Subtitle(
        lang_obj,
        mods=get_array_from(settings.general.subzero_mods)
    )

    sub.content = subtitle.read()
    if not sub.is_valid():
        logging.exception('BAZARR Invalid subtitle file: ' + subtitle.filename)
        sub.mods = None

    if settings.general.getboolean('utf8_encode'):
        sub.set_encoding("utf-8")

    saved_subtitles = []
    try:
        saved_subtitles = save_subtitles(path,
                                         [sub],
                                         single=single,
                                         tags=None,  # fixme
                                         directory=get_target_folder(path),
                                         chmod=chmod,
                                         # formats=("srt", "vtt")
                                         path_decoder=force_unicode)
    except Exception:
        logging.exception('BAZARR Error saving Subtitles file to disk for this file:' + path)
        return

    if len(saved_subtitles) < 1:
        logging.exception('BAZARR Error saving Subtitles file to disk for this file:' + path)
        return

    subtitle_path = saved_subtitles[0].storage_path

    if hi:
        modifier_string = " HI"
    elif forced:
        modifier_string = " forced"
    else:
        modifier_string = ""
    message = language_from_alpha3(language) + modifier_string + " Subtitles manually uploaded."

    if hi:
        modifier_code = ":hi"
    elif forced:
        modifier_code = ":forced"
    else:
        modifier_code = ""
    uploaded_language_code3 = language + modifier_code
    uploaded_language = language_from_alpha3(language) + modifier_string
    uploaded_language_code2 = alpha2_from_alpha3(language) + modifier_code
    audio_language_code2 = alpha2_from_language(audio_language)
    audio_language_code3 = alpha3_from_language(audio_language)

    if media_type == 'series':
        episode_metadata = TableEpisodes.select(TableEpisodes.sonarrSeriesId, TableEpisodes.sonarrEpisodeId) \
            .where(TableEpisodes.path == path_mappings.path_replace_reverse(path)) \
            .dicts() \
            .get_or_none()
        if not episode_metadata:
            return
        series_id = episode_metadata['sonarrSeriesId']
        episode_id = episode_metadata['sonarrEpisodeId']
        sync_subtitles(video_path=path, srt_path=subtitle_path, srt_lang=uploaded_language_code2, media_type=media_type,
                       percent_score=100, sonarr_series_id=episode_metadata['sonarrSeriesId'], forced=forced,
                       sonarr_episode_id=episode_metadata['sonarrEpisodeId'])
    else:
        movie_metadata = TableMovies.select(TableMovies.radarrId) \
            .where(TableMovies.path == path_mappings.path_replace_reverse_movie(path)) \
            .dicts() \
            .get_or_none()
        if not movie_metadata:
            return
        series_id = ""
        episode_id = movie_metadata['radarrId']
        sync_subtitles(video_path=path, srt_path=subtitle_path, srt_lang=uploaded_language_code2, media_type=media_type,
                       percent_score=100, radarr_id=movie_metadata['radarrId'], forced=forced)

    if use_postprocessing:
        command = pp_replace(postprocessing_cmd, path, subtitle_path, uploaded_language,
                             uploaded_language_code2, uploaded_language_code3, audio_language,
                             audio_language_code2, audio_language_code3, forced, 100, "1", "manual", series_id,
                             episode_id, hi=hi)
        postprocessing(command, path)

    if media_type == 'series':
        reversed_path = path_mappings.path_replace_reverse(path)
        reversed_subtitles_path = path_mappings.path_replace_reverse(subtitle_path)
        notify_sonarr(episode_metadata['sonarrSeriesId'])
        event_stream(type='series', action='update', payload=episode_metadata['sonarrSeriesId'])
        event_stream(type='episode-wanted', action='delete', payload=episode_metadata['sonarrEpisodeId'])
    else:
        reversed_path = path_mappings.path_replace_reverse_movie(path)
        reversed_subtitles_path = path_mappings.path_replace_reverse_movie(subtitle_path)
        notify_radarr(movie_metadata['radarrId'])
        event_stream(type='movie', action='update', payload=movie_metadata['radarrId'])
        event_stream(type='movie-wanted', action='delete', payload=movie_metadata['radarrId'])

    return message, reversed_path, reversed_subtitles_path
Exemple #20
0
def series_download_subtitles(no):
    conditions = [(TableEpisodes.sonarrSeriesId == no),
                  (TableEpisodes.missing_subtitles != '[]')]
    conditions += get_exclusion_clause('series')
    episodes_details = TableEpisodes.select(TableEpisodes.path,
                                            TableEpisodes.missing_subtitles,
                                            TableEpisodes.monitored,
                                            TableEpisodes.sonarrEpisodeId,
                                            TableEpisodes.scene_name,
                                            TableShows.tags,
                                            TableShows.seriesType,
                                            TableEpisodes.audio_language,
                                            TableShows.title,
                                            TableEpisodes.season,
                                            TableEpisodes.episode,
                                            TableEpisodes.title.alias('episodeTitle')) \
        .join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId)) \
        .where(reduce(operator.and_, conditions)) \
        .dicts()
    if not episodes_details:
        logging.debug(
            "BAZARR no episode for that sonarrSeriesId have been found in database or they have all been "
            "ignored because of monitored status, series type or series tags: {}"
            .format(no))
        return

    count_episodes_details = len(episodes_details)

    for i, episode in enumerate(episodes_details):
        providers_list = get_providers()

        if providers_list:
            show_progress(id='series_search_progress_{}'.format(no),
                          header='Searching missing subtitles...',
                          name='{0} - S{1:02d}E{2:02d} - {3}'.format(
                              episode['title'], episode['season'],
                              episode['episode'], episode['episodeTitle']),
                          value=i,
                          count=count_episodes_details)

            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 language is not None:
                    hi_ = "True" if language.endswith(':hi') else "False"
                    forced_ = "True" if language.endswith(
                        ':forced') else "False"
                    languages.append((language.split(":")[0], hi_, forced_))

            if not languages:
                continue

            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, no, episode['sonarrEpisodeId'], message,
                                path, language_code, provider, score, subs_id,
                                subs_path)
                    send_notifications(no, episode['sonarrEpisodeId'], message)
        else:
            logging.info("BAZARR All providers are throttled")
            break

    hide_progress(id='series_search_progress_{}'.format(no))
Exemple #21
0
    def post(self):
        # Manual Download
        sonarrSeriesId = request.args.get('seriesid')
        sonarrEpisodeId = request.args.get('episodeid')
        episodeInfo = TableEpisodes.select(TableEpisodes.title,
                                           TableEpisodes.path,
                                           TableEpisodes.scene_name) \
            .where(TableEpisodes.sonarrEpisodeId == sonarrEpisodeId) \
            .dicts() \
            .get()

        title = episodeInfo['title']
        episodePath = path_mappings.path_replace(episodeInfo['path'])
        sceneName = episodeInfo['scene_name']
        if sceneName is None: sceneName = "None"

        language = request.form.get('language')
        hi = request.form.get('hi').capitalize()
        forced = request.form.get('forced').capitalize()
        selected_provider = request.form.get('provider')
        subtitle = request.form.get('subtitle')
        providers_auth = get_providers_auth()

        audio_language_list = get_audio_profile_languages(
            episode_id=sonarrEpisodeId)
        if len(audio_language_list) > 0:
            audio_language = audio_language_list[0]['name']
        else:
            audio_language = 'None'

        try:
            result = manual_download_subtitle(
                episodePath,
                language,
                audio_language,
                hi,
                forced,
                subtitle,
                selected_provider,
                providers_auth,
                sceneName,
                title,
                'series',
                profile_id=get_profile_id(episode_id=sonarrEpisodeId))
            if result is not None:
                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]
                history_log(2, sonarrSeriesId, sonarrEpisodeId, message, path,
                            language_code, provider, score, subs_id, subs_path)
                if not settings.general.getboolean(
                        'dont_notify_manual_actions'):
                    send_notifications(sonarrSeriesId, sonarrEpisodeId,
                                       message)
                store_subtitles(path, episodePath)
            return result, 201
        except OSError:
            pass

        return '', 204
Exemple #22
0
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 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
Exemple #24
0
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'])))
Exemple #25
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.')
Exemple #26
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)
Exemple #27
0
    def post(self):
        sonarrSeriesId = request.args.get('seriesid')
        sonarrEpisodeId = request.args.get('episodeid')
        episodeInfo = TableEpisodes.select(TableEpisodes.title,
                                           TableEpisodes.path,
                                           TableEpisodes.scene_name,
                                           TableEpisodes.audio_language)\
            .where(TableEpisodes.sonarrEpisodeId == sonarrEpisodeId)\
            .dicts()\
            .get()

        title = episodeInfo['title']
        episodePath = path_mappings.path_replace(episodeInfo['path'])
        sceneName = episodeInfo['scene_name']
        audio_language = episodeInfo['audio_language']
        if sceneName is None: sceneName = "None"

        language = request.form.get('language')
        forced = True if request.form.get('forced') == 'true' else False
        hi = True if request.form.get('hi') == 'true' else False
        subFile = request.files.get('file')

        _, ext = os.path.splitext(subFile.filename)

        if ext not in SUBTITLE_EXTENSIONS:
            raise ValueError('A subtitle of an invalid format was uploaded.')

        try:
            result = manual_upload_subtitle(path=episodePath,
                                            language=language,
                                            forced=forced,
                                            hi=hi,
                                            title=title,
                                            scene_name=sceneName,
                                            media_type='series',
                                            subtitle=subFile,
                                            audio_language=audio_language)

            if result is not None:
                message = result[0]
                path = result[1]
                subs_path = result[2]
                if hi:
                    language_code = language + ":hi"
                elif forced:
                    language_code = language + ":forced"
                else:
                    language_code = language
                provider = "manual"
                score = 360
                history_log(4,
                            sonarrSeriesId,
                            sonarrEpisodeId,
                            message,
                            path,
                            language_code,
                            provider,
                            score,
                            subtitles_path=subs_path)
                if not settings.general.getboolean(
                        'dont_notify_manual_actions'):
                    send_notifications(sonarrSeriesId, sonarrEpisodeId,
                                       message)
                store_subtitles(path, episodePath)

        except OSError:
            pass

        return '', 204
Exemple #28
0
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
Exemple #29
0
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()
Exemple #30
0
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)