Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions bazarr/radarr/sync/movies.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ def update_movies(job_id=None, wait_for_completion=False):
movies_count = len(movies)
jobs_queue.update_job_progress(job_id=job_id, progress_max=movies_count)
# Get current movies in DB
current_movies_id_db = [x.radarrId for x in
database.execute(
select(TableMovies.radarrId))
.all()]
current_movies_db_kv = [x.items() for x in [y._asdict()['TableMovies'].__dict__ for y in
database.execute(
select(TableMovies))
.all()]]
current_movies_in_db = {
row[0].radarrId: {
column.name: getattr(row[0], column.name)
for column in row[0].__table__.columns
}
for row in database.execute(select(TableMovies)).all()
}
current_movies_id_db = set(current_movies_in_db)

current_movies_radarr = [movie['id'] for movie in movies if movie['hasFile'] and
'movieFile' in movie and
Expand Down Expand Up @@ -188,7 +188,7 @@ def update_movies(job_id=None, wait_for_completion=False):
language_profiles=language_profiles,
movie_default_profile=movie_default_profile,
audio_profiles=audio_profiles)
if not any([parsed_movie.items() <= x for x in current_movies_db_kv]):
if not parsed_movie.items() <= current_movies_in_db[movie['id']].items():
update_movie(parsed_movie)
movies_updated.append(parsed_movie['title'])
else:
Expand Down
20 changes: 11 additions & 9 deletions bazarr/sonarr/sync/episodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,20 @@ def sync_episodes(series_id, defer_search=False, is_signalr=False):

# Get current episodes id in DB
if series_id:
current_episodes_id_db_list = [row.sonarrEpisodeId for row in
database.execute(
select(TableEpisodes.sonarrEpisodeId,
TableEpisodes.path,
TableEpisodes.sonarrSeriesId)
.where(TableEpisodes.sonarrSeriesId == series_id)).all()]
current_episodes_in_db_row_as_dict = {row[0].sonarrEpisodeId: row[0].to_dict() for row in
database.execute(
select(TableEpisodes)
.where(TableEpisodes.sonarrSeriesId == series_id))
.all()}
current_episodes_id_db_list = list(current_episodes_in_db_row_as_dict)
else:
return

current_episodes_sonarr = []
episodes_to_update = []
episodes_to_add = []
enable_strm_support = settings.general.enable_strm_support
parse_embedded_audio_track = settings.general.parse_embedded_audio_track

# Get episodes data for a series from Sonarr
episodes = get_episodes_from_sonarr_api(apikey_sonarr=apikey_sonarr, series_id=series_id)
Expand Down Expand Up @@ -126,17 +123,22 @@ def sync_episodes(series_id, defer_search=False, is_signalr=False):

if (episode['episodeFile']['size'] > MINIMUM_VIDEO_SIZE or
check_actual_file_size(episode['episodeFile']['path']) or
(settings.general.enable_strm_support and episode['episodeFile']['path'].lower().endswith('.strm'))):
(enable_strm_support and episode['episodeFile']['path'].lower().endswith('.strm'))):
# Add episodes in sonarr to current episode list
current_episodes_sonarr.append(episode['id'])

# Parse episode data
if episode['id'] in current_episodes_in_db_row_as_dict:
parsed_episode = episodeParser(episode)
parsed_episode = episodeParser(
episode, enable_strm_support=enable_strm_support,
parse_embedded_audio_track=parse_embedded_audio_track)
if not set(parsed_episode.items()).issubset(set(current_episodes_in_db_row_as_dict[episode['id']].items())):
episodes_to_update.append(parsed_episode)
else:
episodes_to_add.append(episodeParser(episode))
episodes_to_add.append(
episodeParser(
episode, enable_strm_support=enable_strm_support,
parse_embedded_audio_track=parse_embedded_audio_track))
else:
return

Expand Down
22 changes: 15 additions & 7 deletions bazarr/sonarr/sync/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,30 @@ def profile_id_to_language(id_, profiles):
return profiles_to_return


def episodeParser(episode):
def episodeParser(episode, enable_strm_support=None, parse_embedded_audio_track=None):
if enable_strm_support is None:
enable_strm_support = settings.general.enable_strm_support
if parse_embedded_audio_track is None:
parse_embedded_audio_track = settings.general.parse_embedded_audio_track

if 'hasFile' in episode:
if episode['hasFile'] is True:
if 'episodeFile' in episode:
try:
bazarr_file_size = os.path.getsize(path_mappings.path_replace(episode['episodeFile']['path']))
except OSError:
bazarr_file_size = 0
bazarr_file_size = episode['episodeFile']['size']
is_supported_strm = enable_strm_support and episode['episodeFile']['path'].lower().endswith('.strm')
if bazarr_file_size <= MINIMUM_VIDEO_SIZE and not is_supported_strm:
try:
bazarr_file_size = os.path.getsize(path_mappings.path_replace(episode['episodeFile']['path']))
except OSError:
bazarr_file_size = 0
if (episode['episodeFile']['size'] > MINIMUM_VIDEO_SIZE or bazarr_file_size > MINIMUM_VIDEO_SIZE or
(settings.general.enable_strm_support and episode['episodeFile']['path'].lower().endswith('.strm'))):
is_supported_strm):
if 'sceneName' in episode['episodeFile']:
sceneName = episode['episodeFile']['sceneName']
else:
sceneName = None

if settings.general.parse_embedded_audio_track:
if parse_embedded_audio_track:
audio_language = embedded_audio_reader(path_mappings.path_replace(episode['episodeFile']
['path']),
file_size=episode['episodeFile']['size'],
Expand Down
129 changes: 75 additions & 54 deletions bazarr/sonarr/sync/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ def update_series(job_id=None, wait_for_completion=False):
# Get current series monitored status in DB
series_monitored = get_series_monitored_table()

audio_profiles = get_profile_list()
tagsDict = get_tags()
language_profiles = get_language_profiles()

trace(f"Starting sync for {series_count} shows")

jobs_queue.update_job_progress(job_id=job_id, progress_max=series_count)
Expand Down Expand Up @@ -105,7 +109,9 @@ def update_series(job_id=None, wait_for_completion=False):
current_shows_sonarr.append(show['id'])

# Update series in DB
update_one_series(show['id'], action='updated')
update_one_series(show['id'], action='updated', sync_episodes_after_update=False,
series_data=[show], audio_profiles=audio_profiles, tagsDict=tagsDict,
language_profiles=language_profiles)

# Update episodes in DB
sync_episodes(series_id=show['id'])
Expand All @@ -127,7 +133,8 @@ def update_series(job_id=None, wait_for_completion=False):
gc.collect()


def update_one_series(series_id, action, is_signalr=False):
def update_one_series(series_id, action, is_signalr=False, sync_episodes_after_update=True, series_data=None,
audio_profiles=None, tagsDict=None, language_profiles=None):
logging.debug(f'BAZARR syncing this specific series from Sonarr: {series_id}')

# Check if there's a row in database for this series ID
Expand All @@ -152,59 +159,73 @@ def update_one_series(series_id, action, is_signalr=False):
else:
serie_default_profile = None

audio_profiles = get_profile_list()
tagsDict = get_tags()
language_profiles = get_language_profiles()
if audio_profiles is None:
audio_profiles = get_profile_list()
if tagsDict is None:
tagsDict = get_tags()
if language_profiles is None:
language_profiles = get_language_profiles()

if series_data is None:
try:
# Get series data from sonarr api
series_data = get_series_from_sonarr_api(apikey_sonarr=settings.sonarr.apikey, sonarr_series_id=int(series_id))
except Exception:
logging.exception(f'BAZARR cannot get series with ID {series_id} from Sonarr API.')
return

try:
# Get series data from sonarr api
series_data = get_series_from_sonarr_api(apikey_sonarr=settings.sonarr.apikey, sonarr_series_id=int(series_id))
except Exception:
logging.exception(f'BAZARR cannot get series with ID {series_id} from Sonarr API.')
if not series_data:
return
else:
if not series_data:

if action == 'updated' and existing_series:
# Update existing series in DB
series = seriesParser(series_data[0], action='update', tags_dict=tagsDict,
language_profiles=language_profiles,
serie_default_profile=serie_default_profile,
audio_profiles=audio_profiles)
existing_series_model = existing_series[0]
existing_series_values = {
column.name: getattr(existing_series_model, column.name)
for column in existing_series_model.__table__.columns
}
if series.items() <= existing_series_values.items():
if sync_episodes_after_update and not is_signalr:
sync_episodes(series_id=int(series_id))
return
else:
if action == 'updated' and existing_series:
# Update existing series in DB
series = seriesParser(series_data[0], action='update', tags_dict=tagsDict,
language_profiles=language_profiles,
serie_default_profile=serie_default_profile,
audio_profiles=audio_profiles)
try:
series['updated_at_timestamp'] = datetime.now()
database.execute(
update(TableShows)
.values(series)
.where(TableShows.sonarrSeriesId == series['sonarrSeriesId']))
except IntegrityError as e:
logging.error(f"BAZARR cannot update series {series['path']} because of {e}")
else:
if not is_signalr:
# Sonarr emit two SignalR events when episodes must be refreshed.
# The one that gets there doesn't include the episodeChanged flag.
# The episodes are synced only when this function is called from the
# frontend sync button in the episodes' page.
sync_episodes(series_id=int(series_id))
event_stream(type='series', action='update', payload=int(series_id))
logging.debug(
f'BAZARR updated this series into the database:{path_mappings.path_replace(series["path"])}')
elif action == 'updated' and not existing_series:
# Insert new series in DB
series = seriesParser(series_data[0], action='insert', tags_dict=tagsDict,
language_profiles=language_profiles,
serie_default_profile=serie_default_profile,
audio_profiles=audio_profiles)

try:
series['created_at_timestamp'] = datetime.now()
database.execute(
insert(TableShows)
.values(series))
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(
f'BAZARR inserted this series into the database:{path_mappings.path_replace(series["path"])}')
try:
series['updated_at_timestamp'] = datetime.now()
database.execute(
update(TableShows)
.values(series)
.where(TableShows.sonarrSeriesId == series['sonarrSeriesId']))
except IntegrityError as e:
logging.error(f"BAZARR cannot update series {series['path']} because of {e}")
else:
if sync_episodes_after_update and not is_signalr:
# Sonarr emit two SignalR events when episodes must be refreshed.
# The one that gets there doesn't include the episodeChanged flag.
# The episodes are synced only when this function is called from the
# frontend sync button in the episodes' page.
sync_episodes(series_id=int(series_id))
event_stream(type='series', action='update', payload=int(series_id))
logging.debug(
f'BAZARR updated this series into the database:{path_mappings.path_replace(series["path"])}')
elif action == 'updated' and not existing_series:
# Insert new series in DB
series = seriesParser(series_data[0], action='insert', tags_dict=tagsDict,
language_profiles=language_profiles,
serie_default_profile=serie_default_profile,
audio_profiles=audio_profiles)

try:
series['created_at_timestamp'] = datetime.now()
database.execute(
insert(TableShows)
.values(series))
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(
f'BAZARR inserted this series into the database:{path_mappings.path_replace(series["path"])}')
56 changes: 56 additions & 0 deletions migrations/versions/6f0b2c8d9a1e_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Add indexes for subtitle sync queries

Revision ID: 6f0b2c8d9a1e
Revises: 309dc062d2e4
Create Date: 2026-04-22 12:35:00.000000

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '6f0b2c8d9a1e'
down_revision = '309dc062d2e4'
branch_labels = None
depends_on = None

bind = op.get_context().bind
insp = sa.inspect(bind)


def index_exists(table_name, index_name):
indexes = insp.get_indexes(table_name)
return any(i["name"] == index_name for i in indexes)


def upgrade():
if not index_exists('table_episodes', 'idx_table_episodes_sonarrSeriesId'):
op.create_index('idx_table_episodes_sonarrSeriesId', 'table_episodes', ['sonarrSeriesId'])

if not index_exists('table_episodes', 'idx_table_episodes_missing_subtitles'):
op.create_index(
'idx_table_episodes_missing_subtitles',
'table_episodes',
['missing_subtitles'],
sqlite_where=sa.text("missing_subtitles IS NOT NULL AND missing_subtitles != '[]'"),
)

if not index_exists('table_movies', 'idx_table_movies_missing_subtitles'):
op.create_index(
'idx_table_movies_missing_subtitles',
'table_movies',
['missing_subtitles'],
sqlite_where=sa.text("missing_subtitles IS NOT NULL AND missing_subtitles != '[]'"),
)


def downgrade():
if index_exists('table_movies', 'idx_table_movies_missing_subtitles'):
op.drop_index('idx_table_movies_missing_subtitles', table_name='table_movies')

if index_exists('table_episodes', 'idx_table_episodes_missing_subtitles'):
op.drop_index('idx_table_episodes_missing_subtitles', table_name='table_episodes')

if index_exists('table_episodes', 'idx_table_episodes_sonarrSeriesId'):
op.drop_index('idx_table_episodes_sonarrSeriesId', table_name='table_episodes')
Loading