diff --git a/bazarr/app/config.py b/bazarr/app/config.py index 2e65d5913..e32f86539 100644 --- a/bazarr/app/config.py +++ b/bazarr/app/config.py @@ -221,7 +221,11 @@ validators = [ Validator('plex.ssl', must_exist=True, default=False, is_type_of=bool), Validator('plex.apikey', must_exist=True, default='', is_type_of=str), Validator('plex.movie_library', must_exist=True, default='', is_type_of=str), - Validator('plex.set_added', must_exist=True, default=False, is_type_of=bool), + Validator('plex.series_library', must_exist=True, default='', is_type_of=str), + Validator('plex.set_movie_added', must_exist=True, default=False, is_type_of=bool), + Validator('plex.set_episode_added', must_exist=True, default=False, is_type_of=bool), + Validator('plex.update_movie_library', must_exist=True, default=False, is_type_of=bool), + Validator('plex.update_series_library', must_exist=True, default=False, is_type_of=bool), # proxy section Validator('proxy.type', must_exist=True, default=None, is_type_of=(NoneType, str), diff --git a/bazarr/plex/operations.py b/bazarr/plex/operations.py index 2ae326ff2..6600aa2a8 100644 --- a/bazarr/plex/operations.py +++ b/bazarr/plex/operations.py @@ -6,22 +6,76 @@ import logging logger = logging.getLogger(__name__) +# Constants +DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' -def plex_set_added_date_now(movie_metadata): + +def get_plex_server() -> PlexServer: + """Connect to the Plex server and return the server instance.""" + try: + protocol = "https://" if settings.plex.ssl else "http://" + baseurl = f"{protocol}{settings.plex.ip}:{settings.plex.port}" + return PlexServer(baseurl, settings.plex.apikey) + except Exception as e: + logger.error(f"Failed to connect to Plex server: {e}") + raise + + +def update_added_date(video, added_date: str) -> None: + """Update the added date of a video in Plex.""" + try: + updates = {"addedAt.value": added_date} + video.edit(**updates) + logger.info(f"Updated added date for {video.title} to {added_date}") + except Exception as e: + logger.error(f"Failed to update added date for {video.title}: {e}") + raise + + +def plex_set_movie_added_date_now(movie_metadata) -> None: + """ + Update the added date of a movie in Plex to the current datetime. + + :param movie_metadata: Metadata object containing the movie's IMDb ID. + """ try: - if settings.plex.ssl: - protocol_plex = "https://" - else: - protocol_plex = "http://" - - baseurl = f'{protocol_plex}{settings.plex.ip}:{settings.plex.port}' - token = settings.plex.apikey - plex = PlexServer(baseurl, token) + plex = get_plex_server() library = plex.library.section(settings.plex.movie_library) video = library.getGuid(guid=movie_metadata.imdbId) - # Get the current date and time in the desired format - current_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - updates = {"addedAt.value": current_date} - video.edit(**updates) + current_date = datetime.now().strftime(DATETIME_FORMAT) + update_added_date(video, current_date) + except Exception as e: + logger.error(f"Error in plex_set_movie_added_date_now: {e}") + + +def plex_set_episode_added_date_now(episode_metadata) -> None: + """ + Update the added date of a TV episode in Plex to the current datetime. + + :param episode_metadata: Metadata object containing the episode's IMDb ID, season, and episode number. + """ + try: + plex = get_plex_server() + library = plex.library.section(settings.plex.series_library) + show = library.getGuid(episode_metadata.imdbId) + episode = show.episode(season=episode_metadata.season, episode=episode_metadata.episode) + current_date = datetime.now().strftime(DATETIME_FORMAT) + update_added_date(episode, current_date) + except Exception as e: + logger.error(f"Error in plex_set_episode_added_date_now: {e}") + + +def plex_update_library(is_movie_library: bool) -> None: + """ + Trigger a library update for the specified library type. + + :param is_movie_library: True for movie library, False for series library. + """ + try: + plex = get_plex_server() + library_name = settings.plex.movie_library if is_movie_library else settings.plex.series_library + library = plex.library.section(library_name) + library.update() + logger.info(f"Triggered update for library: {library_name}") except Exception as e: - logger.error(f"A Plex error occurred: {e}") \ No newline at end of file + logger.error(f"Error in plex_update_library: {e}") \ No newline at end of file diff --git a/bazarr/subtitles/processing.py b/bazarr/subtitles/processing.py index 8617cbed6..0c37c22fb 100644 --- a/bazarr/subtitles/processing.py +++ b/bazarr/subtitles/processing.py @@ -7,11 +7,11 @@ from app.config import settings, sync_checker as _defaul_sync_checker from utilities.path_mappings import path_mappings from utilities.post_processing import pp_replace, set_chmod from languages.get_languages import alpha2_from_alpha3, alpha2_from_language, alpha3_from_language, language_from_alpha3 -from app.database import TableEpisodes, TableMovies, database, select +from app.database import TableShows, TableEpisodes, TableMovies, database, select from utilities.analytics import event_tracker from radarr.notify import notify_radarr from sonarr.notify import notify_sonarr -from plex.operations import plex_set_added_date_now +from plex.operations import plex_set_movie_added_date_now, plex_update_library, plex_set_episode_added_date_now from app.event_handler import event_stream from .utils import _get_download_code3 @@ -77,8 +77,10 @@ def process_subtitle(subtitle, media_type, audio_language, path, max_score, is_u if media_type == 'series': episode_metadata = database.execute( - select(TableEpisodes.sonarrSeriesId, TableEpisodes.sonarrEpisodeId) - .where(TableEpisodes.path == path_mappings.path_replace_reverse(path)))\ + select(TableShows.imdbId, TableEpisodes.sonarrSeriesId, TableEpisodes.sonarrEpisodeId, + TableEpisodes.season, TableEpisodes.episode) + .join(TableShows)\ + .where(TableEpisodes.path == path_mappings.path_replace_reverse(path)))\ .first() if not episode_metadata: return @@ -97,7 +99,7 @@ def process_subtitle(subtitle, media_type, audio_language, path, max_score, is_u else: movie_metadata = database.execute( select(TableMovies.radarrId, TableMovies.imdbId) - .where(TableMovies.path == path_mappings.path_replace_reverse_movie(path)))\ + .where(TableMovies.path == path_mappings.path_replace_reverse_movie(path)))\ .first() if not movie_metadata: return @@ -116,7 +118,8 @@ def process_subtitle(subtitle, media_type, audio_language, path, max_score, is_u 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, - percent_score, subtitle_id, downloaded_provider, uploader, release_info, series_id, episode_id) + percent_score, subtitle_id, downloaded_provider, uploader, release_info, series_id, + episode_id) if media_type == 'series': use_pp_threshold = settings.general.use_postprocessing_threshold @@ -140,14 +143,22 @@ def process_subtitle(subtitle, media_type, audio_language, path, max_score, is_u event_stream(type='series', action='update', payload=episode_metadata.sonarrSeriesId) event_stream(type='episode-wanted', action='delete', payload=episode_metadata.sonarrEpisodeId) + if settings.general.use_plex is True: + if settings.plex.update_series_library is True: + plex_update_library(is_movie_library=False) + if settings.plex.set_episode_added is True: + plex_set_episode_added_date_now(episode_metadata) 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) - if settings.plex.set_added is True: - plex_set_added_date_now(movie_metadata) + if settings.general.use_plex is True: + if settings.plex.set_movie_added is True: + plex_set_movie_added_date_now(movie_metadata) + if settings.plex.update_movie_library is True: + plex_update_library(is_movie_library=True) event_tracker.track_subtitles(provider=downloaded_provider, action=action, language=downloaded_language) diff --git a/frontend/src/pages/Settings/Plex/index.tsx b/frontend/src/pages/Settings/Plex/index.tsx index 135ef6116..c93bf011d 100644 --- a/frontend/src/pages/Settings/Plex/index.tsx +++ b/frontend/src/pages/Settings/Plex/index.tsx @@ -13,7 +13,7 @@ import { plexEnabledKey } from "@/pages/Settings/keys"; const SettingsPlexView: FunctionComponent = () => { return ( -
+
@@ -28,15 +28,35 @@ const SettingsPlexView: FunctionComponent = () => {
-
+
+ + Can be helpful for remote media files +
+
+ + + + Can be helpful for remote media files
diff --git a/frontend/src/types/settings.d.ts b/frontend/src/types/settings.d.ts index 6b11c5752..51e21a709 100644 --- a/frontend/src/types/settings.d.ts +++ b/frontend/src/types/settings.d.ts @@ -178,8 +178,12 @@ declare namespace Settings { port: number; apikey?: string; ssl?: boolean; - set_added?: boolean; + set_movie_added?: boolean; + set_episode_added?: boolean; movie_library?: string; + series_library?: string; + update_movie_library?: boolean; + update_series_library?: boolean; } interface Anticaptcha {