From 0871588821a21de38fc9da60ce529f9043e53d79 Mon Sep 17 00:00:00 2001 From: Peter McConnell Date: Tue, 7 Nov 2023 12:16:30 +0000 Subject: [PATCH] chore: removing some unnecessary attributes --- backend/plexwrapper.py | 103 ++++++++++++++++++++++++++--------------- backend/utils.py | 30 ++++++++++++ 2 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 backend/utils.py diff --git a/backend/plexwrapper.py b/backend/plexwrapper.py index c84d369..f7c77dd 100644 --- a/backend/plexwrapper.py +++ b/backend/plexwrapper.py @@ -7,6 +7,7 @@ from plexapi.server import PlexServer from plexapi.video import Movie, Video, Episode +from utils import trace_time from database import Database from logger import get_logger @@ -39,22 +40,27 @@ def __init__(self): self.db = Database() logger.debug("Initialized DB!") + self.traces = {} + @trace_time def _get_sections(self): return [self.plex.library.section(title=library) for library in self.libraries] + @trace_time def get_deleted_sizes(self): sizes = {} for library_name in self.libraries: sizes[library_name] = self.db.get_deleted_size(library_name) return sizes + @trace_time def get_server_info(self): return { 'name': self.plex.friendlyName, 'url': self.baseurl + '/web/index.html' } + @trace_time def get_dupe_content(self, page=1): logger.debug("START") dupes = [] @@ -90,6 +96,7 @@ def get_dupe_content(self, page=1): return dupes + @trace_time def get_content_sample_files(self): content = [] @@ -111,9 +118,11 @@ def get_content_sample_files(self): content.append(_media) return content + @trace_time def get_content(self, media_id): return self.plex.fetchItem(media_id) + @trace_time def delete_media(self, library_name, content_key, media_id): content = self.get_content(content_key) deleted_size = self.db.get_deleted_size(library_name) @@ -126,6 +135,7 @@ def delete_media(self, library_name, content_key, media_id): self.db.set_deleted_size(library_name, deleted_size) + @trace_time def video_to_dict(self, video: Video) -> dict: # https://python-plexapi.readthedocs.io/en/latest/modules/video.html#plexapi.video.Video ignored = self.db.get_ignored_item(video.key) @@ -133,17 +143,17 @@ def video_to_dict(self, video: Video) -> dict: "ignored": ignored is not None, } attributes_to_fetch = { - "addedAt": lambda: str(video.addedAt), + # "addedAt": lambda: str(video.addedAt), "key": lambda: video.key, - "lastViewedAt": lambda: str(video.lastViewedAt), + # "lastViewedAt": lambda: str(video.lastViewedAt), "librarySectionID": lambda: video.librarySectionID, - "summary": lambda: video.summary, + # "summary": lambda: video.summary, "thumbUrl": lambda: video.thumbUrl, "title": lambda: video.title, - "titleSort": lambda: video.titleSort, + # "titleSort": lambda: video.titleSort, "type": lambda: video.type, - "updatedAt": lambda: str(video.updatedAt), - "viewCount": lambda: str(video.viewCount), + # "updatedAt": lambda: str(video.updatedAt), + # "viewCount": lambda: str(video.viewCount), "url": lambda: self.baseurl + '/web/index.html#!/server/' + self.plex.machineIdentifier + '/details?key=' + urllib.parse.quote_plus(video.key) } with ThreadPoolExecutor(max_workers=len(attributes_to_fetch)) as executor: @@ -161,18 +171,20 @@ def fetch_attribute(func, *args, **kwargs): except Exception as e: logger.error(f"Error fetching attribute: {e}") + @trace_time def movie_to_dict(self, movie: Movie, library: str) -> dict: # https://python-plexapi.readthedocs.io/en/latest/modules/video.html#plexapi.video.Movie attributes_to_fetch = { "duration": lambda: movie.duration, "guid": lambda: movie.guid, - "originalTitle": lambda: movie.originalTitle, - "originallyAvailableAt": lambda: str(movie.originallyAvailableAt), - "rating": lambda: movie.rating, - "ratingImage": lambda: movie.ratingImage, - "studio": lambda: movie.studio, - "tagline": lambda: movie.tagline, - "userRating": lambda: movie.userRating, + "originalTitle": lambda: movie.title, + # "originalTitle": lambda: movie.originalTitle, + # "originallyAvailableAt": lambda: str(movie.originallyAvailableAt), + # "rating": lambda: movie.rating, + # "ratingImage": lambda: movie.ratingImage, + # "studio": lambda: movie.studio, + # "tagline": lambda: movie.tagline, + # "userRating": lambda: movie.userRating, "year": lambda: movie.year, "media": lambda: [self.media_to_dict(media) for media in movie.media], } @@ -189,6 +201,7 @@ def movie_to_dict(self, movie: Movie, library: str) -> dict: return results + @trace_time def episode_to_dict(self, episode: Episode, library: str) -> dict: # https://python-plexapi.readthedocs.io/en/latest/modules/video.html#plexapi.video.Movie results = { @@ -200,8 +213,8 @@ def episode_to_dict(self, episode: Episode, library: str) -> dict: "duration": lambda: episode.duration, "guid": lambda: episode.guid, "originalTitle": lambda: episode.title, - "originallyAvailableAt": lambda: str(episode.originallyAvailableAt), - "rating": lambda: episode.rating, + # "originallyAvailableAt": lambda: str(episode.originallyAvailableAt), + # "rating": lambda: episode.rating, "year": lambda: episode.year, "seasonNumber": lambda: episode.seasonNumber, "seasonEpisode": lambda: episode.seasonEpisode, @@ -216,6 +229,7 @@ def episode_to_dict(self, episode: Episode, library: str) -> dict: return results + @trace_time def get_thumbnail_url(self, content_key): item = self.get_content(content_key) if item is not None: @@ -224,6 +238,7 @@ def get_thumbnail_url(self, content_key): return ""; @classmethod + @trace_time def media_to_dict(cls, media: Media) -> dict: # https://python-plexapi.readthedocs.io/en/latest/modules/media.html#plexapi.media.Media return { @@ -250,37 +265,49 @@ def media_to_dict(cls, media: Media) -> dict: } @classmethod + @trace_time def media_part_to_dict(cls, media_part: MediaPart) -> dict: # https://python-plexapi.readthedocs.io/en/latest/modules/media.html#plexapi.media.MediaPart - return { - "id": media_part.id, - # 'media_id': media_part.media.id, - # 'initpath': media_part.initpath, - "container": media_part.container, - "duration": media_part.duration, - "file": media_part.file, - "indexes": media_part.indexes, - "key": media_part.key, - "size": media_part.size, - "exists": media_part.exists, - "accessible": media_part.accessible, - "streams": [ + results = {} + attributes_to_fetch = { + "id": lambda: media_part.id, + "container": lambda: media_part.container, + "duration": lambda: media_part.duration, + "file": lambda: media_part.file, + "indexes": lambda: media_part.indexes, + "key": lambda: media_part.key, + "size": lambda: media_part.size, + "exists": lambda: media_part.exists, + "accessible": lambda: media_part.accessible, + "streams": lambda: [ cls.media_part_stream_to_dict(media_part_stream) for media_part_stream in media_part.videoStreams() ], } + with ThreadPoolExecutor(max_workers=len(attributes_to_fetch)) as executor: + future_to_attr = {executor.submit(cls.fetch_attribute, func): attr for attr, func in attributes_to_fetch.items()} + for future in as_completed(future_to_attr): + attr = future_to_attr[future] + results[attr] = future.result() + return results @classmethod + @trace_time def media_part_stream_to_dict(cls, media_part_stream: MediaPartStream) -> dict: # https://python-plexapi.readthedocs.io/en/latest/modules/media.html#plexapi.media.MediaPartStream - return { - "id": media_part_stream.id, - # 'media_id': media_part.media.id, - # 'initpath': media_part.initpath, - "codec": media_part_stream.codec, - "codecID": media_part_stream.codecID, - "language": media_part_stream.language, - "languageCode": media_part_stream.languageCode, - "selected": media_part_stream.selected, - "type": media_part_stream.type, + results = {} + attributes_to_fetch = { + "id": lambda: media_part_stream.id, + "codec": lambda: media_part_stream.codec, + "codecID": lambda: media_part_stream.codecID, + "language": lambda: media_part_stream.language, + "languageCode": lambda: media_part_stream.languageCode, + "selected": lambda: media_part_stream.selected, + "type": lambda: media_part_stream.type, } + with ThreadPoolExecutor(max_workers=len(attributes_to_fetch)) as executor: + future_to_attr = {executor.submit(cls.fetch_attribute, func): attr for attr, func in attributes_to_fetch.items()} + for future in as_completed(future_to_attr): + attr = future_to_attr[future] + results[attr] = future.result() + return results diff --git a/backend/utils.py b/backend/utils.py new file mode 100644 index 0000000..bfe527a --- /dev/null +++ b/backend/utils.py @@ -0,0 +1,30 @@ +import os +import time +from functools import wraps +from logger import get_logger + +logger = get_logger(__name__) +traces = [] + + +def trace_time(method): + @wraps(method) + def timed(*args, **kw): + if os.getenv("DEBUG") != "1": + return method(*args, **kw) + start_time = time.perf_counter() + result = method(*args, **kw) + end_time = time.perf_counter() + duration = end_time - start_time + traces.append((method.__name__, duration)) + logger.debug(f"{method.__name__} took {duration} seconds") + return result + return timed + +def print_top_traces(n): + # Sort the traces by the execution time and print the top N + top_traces = sorted(traces, key=lambda record: record[1], reverse=True)[:n] + print("==================================================================") + for method, time_taken in top_traces: + print(f"{method} took {time_taken} seconds") + print("==================================================================")