From 0fb362d4eed47f12f81a99ad27e7b3df9174ab63 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:38:34 +0200 Subject: [PATCH 01/12] initial release for movie media type --- data/interfaces/default/css/tautulli.css | 18 ++++++++++++++++++ data/interfaces/default/library.html | 16 +++++++++++++++- plexpy/libraries.py | 16 ++++++++++++---- plexpy/pmsconnect.py | 3 ++- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/css/tautulli.css b/data/interfaces/default/css/tautulli.css index 0c81f0e5..09f6e153 100644 --- a/data/interfaces/default/css/tautulli.css +++ b/data/interfaces/default/css/tautulli.css @@ -2940,6 +2940,12 @@ a .home-platforms-list-cover-face:hover max-width: 1750px; display: flow-root; } +.table-card-header.spaced { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + align-content: center; +} .table-card-back td { font-size: 12px; } @@ -2951,6 +2957,18 @@ a .home-platforms-list-cover-face:hover font-weight: bold; line-height: 34px; } +.info-bar { + display: inline; +} +.info-element { + display: inline-block; + border-radius: 1rem; + border: 0.2rem solid #242424; + padding: 0.7rem; + background-color: #3B3B3B; + font-style: italic; + color: #676767; +} .button-bar { float: right; } diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index b1fe8b6f..26153402 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -271,7 +271,7 @@ DOCUMENTATION :: END You may leave this page and check back later. % endif -
+
Media Info for @@ -279,6 +279,16 @@ DOCUMENTATION :: END
+ % if config['get_file_sizes'] and not data['section_id'] in config['get_file_sizes_hold']['section_ids']: +
+
+ Total File Size: +
+
+ Total Media Runtime: +
+
+ % endif
% if _session['user_group'] == 'admin':
@@ -842,6 +852,10 @@ DOCUMENTATION :: END section_id: section_id, refresh: refresh_table }; + }, + complete: function(xhr, status) { + $("#info-element-total-media-runtime").html(humanDuration(xhr.responseJSON.total_duration)); + $("#info-element-total-file-size").html(humanFileSize(xhr.responseJSON.total_file_size)); } }; media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options); diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 33832aba..a5fcd4a9 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -517,8 +517,11 @@ class Libraries(object): # Import media info cache from json file cache_time, rows, library_count = self._load_media_info_cache(section_id=section_id, rating_key=rating_key) + # Check if duration is also included in cache else refresh cache to prevent update issues + refresh = refresh if None not in {d.get('duration') for d in rows} else True + # If no cache was imported, get all library children items - cached_items = {d['rating_key']: d['file_size'] for d in rows} if not refresh else {} + cached_items = {d['rating_key']: [d['file_size'], d['duration']] for d in rows} if not refresh else {} if refresh or not rows: pms_connect = pmsconnect.PmsConnect() @@ -543,8 +546,10 @@ class Libraries(object): for item in children_list: ## TODO: Check list of media info items, currently only grabs first item - cached_file_size = cached_items.get(item['rating_key'], None) - file_size = cached_file_size if cached_file_size else item.get('file_size', '') + cached_item_data = cached_items.get(item['rating_key'], None) + + file_size = cached_item_data['file_size'] if cached_item_data else item.get('file_size', '') + duration = cached_item_data['duration'] if cached_item_data else item['duration'] row = {'section_id': library_details['section_id'], 'section_type': library_details['section_type'], @@ -566,7 +571,8 @@ class Libraries(object): 'video_framerate': item.get('video_framerate', ''), 'audio_codec': item.get('audio_codec', ''), 'audio_channels': item.get('audio_channels', ''), - 'file_size': file_size + 'file_size': file_size, + 'duration': duration } new_rows.append(row) @@ -624,6 +630,7 @@ class Libraries(object): results = sorted(results, key=lambda k: k[sort_key].lower(), reverse=reverse) total_file_size = sum([helpers.cast_to_int(d['file_size']) for d in results]) + total_duration = sum([helpers.cast_to_int(d['duration']) for d in results]) # Paginate results results = results[json_data['start']:(json_data['start'] + json_data['length'])] @@ -637,6 +644,7 @@ class Libraries(object): 'draw': int(json_data['draw']), 'filtered_file_size': filtered_file_size, 'total_file_size': total_file_size, + 'total_duration': total_duration, 'last_refreshed': cache_time } diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index cf662be4..e7dbdfed 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -2805,7 +2805,8 @@ class PmsConnect(object): 'thumb': helpers.get_xml_attr(item, 'thumb'), 'parent_thumb': helpers.get_xml_attr(item, 'thumb'), 'grandparent_thumb': helpers.get_xml_attr(item, 'grandparentThumb'), - 'added_at': helpers.get_xml_attr(item, 'addedAt') + 'added_at': helpers.get_xml_attr(item, 'addedAt'), + 'duration': helpers.get_xml_attr(item, 'duration') } if get_media_info: From 2b1d7b46ebfc90b1efddb89f2d01300f03421e1b Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:40:40 +0200 Subject: [PATCH 02/12] total duration for shows --- plexpy/libraries.py | 8 ++++++-- plexpy/pmsconnect.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/plexpy/libraries.py b/plexpy/libraries.py index a5fcd4a9..e8c4b28d 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -459,6 +459,7 @@ class Libraries(object): 'data': [], 'filtered_file_size': 0, 'total_file_size': 0, + 'total_media_duration': 0, 'last_refreshed': None} if not session.allow_session_library(section_id): @@ -676,13 +677,14 @@ class Libraries(object): if rating_key: logger.debug("Tautulli Libraries :: Getting file sizes for rating_key %s." % rating_key) elif section_id: - logger.debug("Tautulli Libraries :: Fetting file sizes for section_id %s." % section_id) + logger.debug("Tautulli Libraries :: Fetching file sizes for section_id %s." % section_id) pms_connect = pmsconnect.PmsConnect() for item in rows: - if item['rating_key'] and not item['file_size']: + if item['rating_key'] and (not item['file_size'] or not item['duration']): file_size = 0 + duration = 0 metadata = pms_connect.get_metadata_children_details(rating_key=item['rating_key'], get_children=True, @@ -699,8 +701,10 @@ class Libraries(object): media_info['parts'][0]) file_size += helpers.cast_to_int(media_part_info.get('file_size', 0)) + duration += helpers.cast_to_int(child_metadata.get('duration', 0)) item['file_size'] = file_size + item['duration'] = duration # Cache the media info to a json file self._save_media_info_cache(section_id=section_id, rating_key=rating_key, rows=rows) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index e7dbdfed..90de5c89 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -2806,7 +2806,7 @@ class PmsConnect(object): 'parent_thumb': helpers.get_xml_attr(item, 'thumb'), 'grandparent_thumb': helpers.get_xml_attr(item, 'grandparentThumb'), 'added_at': helpers.get_xml_attr(item, 'addedAt'), - 'duration': helpers.get_xml_attr(item, 'duration') + 'duration': helpers.get_xml_attr(item, 'duration') if section_type == 'movie' else 0 } if get_media_info: From e29cbdf9f968615dfb399ef293ff02810f060bc2 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:42:18 +0200 Subject: [PATCH 03/12] update naming and API doc --- data/interfaces/default/library.html | 2 +- plexpy/libraries.py | 4 ++-- plexpy/webserve.py | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index 26153402..dd1bebb0 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -854,7 +854,7 @@ DOCUMENTATION :: END }; }, complete: function(xhr, status) { - $("#info-element-total-media-runtime").html(humanDuration(xhr.responseJSON.total_duration)); + $("#info-element-total-media-runtime").html(humanDuration(xhr.responseJSON.total_media_duration)); $("#info-element-total-file-size").html(humanFileSize(xhr.responseJSON.total_file_size)); } }; diff --git a/plexpy/libraries.py b/plexpy/libraries.py index e8c4b28d..19ab6ff4 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -631,7 +631,7 @@ class Libraries(object): results = sorted(results, key=lambda k: k[sort_key].lower(), reverse=reverse) total_file_size = sum([helpers.cast_to_int(d['file_size']) for d in results]) - total_duration = sum([helpers.cast_to_int(d['duration']) for d in results]) + total_media_duration = sum([helpers.cast_to_int(d['duration']) for d in results]) # Paginate results results = results[json_data['start']:(json_data['start'] + json_data['length'])] @@ -645,7 +645,7 @@ class Libraries(object): 'draw': int(json_data['draw']), 'filtered_file_size': filtered_file_size, 'total_file_size': total_file_size, - 'total_duration': total_duration, + 'total_media_duration': total_media_duration, 'last_refreshed': cache_time } diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 196bbe5b..12daa164 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -798,12 +798,14 @@ class WebInterface(object): "recordsFiltered": 82, "filtered_file_size": 2616760056742, "total_file_size": 2616760056742, + "total_media_duration": 7947375522, "data": [{"added_at": "1403553078", "audio_channels": "", "audio_codec": "", "bitrate": "", "container": "", + "duration": "", "file_size": 253660175293, "grandparent_rating_key": "", "last_played": 1462380698, From aed84852c0b3b82084e31ea04fca8ee9833e15bb Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 29 Oct 2023 12:18:56 +0100 Subject: [PATCH 04/12] get_libray_media_stats API call --- plexpy/libraries.py | 35 +++++++++++++++++++++++++++++++++++ plexpy/plex.py | 9 ++++++++- plexpy/webserve.py | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 19ab6ff4..82768123 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -143,6 +143,36 @@ def has_library_type(section_type): result = monitor_db.select_single(query=query, args=args) return bool(result) +def get_library_media_stats(section_id=None): + plex = Plex(token=session.get_session_user_token()) + + default_return = { + 'total_size': 0, + 'total_storage': 0, + 'total_duration': 0 + } + + if section_id and not str(section_id).isdigit(): + logger.warn("Tautulli Libraries :: Library media stats requested but invalid section_id provided.") + return default_return + + if not session.allow_session_library(section_id): + logger.warn("Tautulli Libraries :: Library media stats requested but library is not allowed for this session.") + return default_return + + library = plex.get_library(section_id) + + if library is None: + logger.warn("Tautulli Libraries :: Library media stats requested but no library was found section_id %s.", section_id) + return default_return + + library_media_stats = { + 'total_size': library.totalSize, + 'total_storage': library.totalStorage, + 'total_duration': library.totalDuration + } + + return library_media_stats def get_collections(section_id=None): plex = Plex(token=session.get_session_user_token()) @@ -409,18 +439,23 @@ class Libraries(object): else: library_art = item['library_art'] + library_media_stats = get_library_media_stats(item['section_id']) + row = {'row_id': item['row_id'], 'server_id': item['server_id'], 'section_id': item['section_id'], 'section_name': item['section_name'], 'section_type': item['section_type'], 'count': item['count'], + 'total_size': library_media_stats['total_size'], 'parent_count': item['parent_count'], 'child_count': item['child_count'], 'library_thumb': library_thumb, 'library_art': library_art, 'plays': item['plays'], + 'total_storage': library_media_stats['total_storage'], 'duration': item['duration'], + 'total_duration': library_media_stats['total_duration'], 'last_accessed': item['last_accessed'], 'history_row_id': item['history_row_id'], 'last_played': item['last_played'], diff --git a/plexpy/plex.py b/plexpy/plex.py index 29379053..d25bd478 100644 --- a/plexpy/plex.py +++ b/plexpy/plex.py @@ -59,7 +59,14 @@ class Plex(object): self.PlexServer = PlexObject(url, token) def get_library(self, section_id): - return self.PlexServer.library.sectionByID(int(section_id)) + from plexapi.exceptions import NotFound + + try: + library = self.PlexServer.library.sectionByID(int(section_id)) + except NotFound: + library = None + + return library def get_library_items(self, section_id): return self.get_library(section_id).all() diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 12daa164..7be86c04 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -540,6 +540,9 @@ class WebInterface(object): "section_type": "Show", "server_id": "ds48g4r354a8v9byrrtr697g3g79w", "thumb": "/library/metadata/153036/thumb/1462175062", + "total_duration": 3048551210, + "total_size": 62, + "total_storage": 1866078986762, "year": 2016 }, {...}, @@ -561,7 +564,9 @@ class WebInterface(object): ("last_accessed", True, False), ("last_played", True, True), ("plays", True, False), - ("duration", True, False)] + ("duration", True, False), + ("total_storage", True, False), + ("total_duration", True, False)] kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "section_name") grouping = helpers.bool_true(grouping, return_none=True) @@ -695,6 +700,32 @@ class WebInterface(object): except: return "Failed to update library." + @cherrypy.expose + @requireAuth(member_of("admin")) + @addtoapi() + def get_library_media_stats(self, section_id=None): + """ Get the media stats of a library section on Tautulli. + + ``` + Required parameters: + section_id (str): The id of the Plex library section + + Optional parameters: + None + + Returns: + { + "total_duration": 3048551210, + "total_size": 62, + "total_storage": 1866078986762 + } + ``` + """ + + logger.info("Getting library media stats for section %s.", section_id) + + return libraries.get_library_media_stats(section_id) + @cherrypy.expose @requireAuth() def library_watch_time_stats(self, section_id=None, **kwargs): From 7bc94cd95d230d735c33bccbe469fa51fcd5e3a3 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 29 Oct 2023 12:19:10 +0100 Subject: [PATCH 05/12] library_media_stats for libraries page --- .../interfaces/default/js/tables/libraries.js | 24 +++++++++++++++++++ data/interfaces/default/libraries.html | 2 ++ 2 files changed, 26 insertions(+) diff --git a/data/interfaces/default/js/tables/libraries.js b/data/interfaces/default/js/tables/libraries.js index b3e702a0..ee8aa488 100644 --- a/data/interfaces/default/js/tables/libraries.js +++ b/data/interfaces/default/js/tables/libraries.js @@ -202,6 +202,30 @@ libraries_list_table_options = { "searchable": false, "width": "10%", "className": "no-wrap" + }, + { + "targets": [11], + "data": "total_storage", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== null && cellData !== '') { + $(td).html(humanFileSize(cellData)); + } + }, + "searchable": false, + "width": "10%", + "className": "no-wrap" + }, + { + "targets": [12], + "data": "total_duration", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== null && cellData !== '') { + $(td).html(humanDuration(cellData)); + } + }, + "searchable": false, + "width": "10%", + "className": "no-wrap" } ], diff --git a/data/interfaces/default/libraries.html b/data/interfaces/default/libraries.html index 70468ff3..5ad1dc06 100644 --- a/data/interfaces/default/libraries.html +++ b/data/interfaces/default/libraries.html @@ -41,6 +41,8 @@ Last Played Total Plays Total Played Duration + Total Storage + Total Runtime From 8c1248ed1b1e82965563649c76d4ab2e4654dde7 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 29 Oct 2023 12:40:23 +0100 Subject: [PATCH 06/12] using new API call for getting library media stats --- data/interfaces/default/library.html | 22 ++++++++++++++-------- plexpy/webserve.py | 7 +++++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index dd1bebb0..bfdcb79d 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -279,16 +279,14 @@ DOCUMENTATION :: END
- % if config['get_file_sizes'] and not data['section_id'] in config['get_file_sizes_hold']['section_ids']:
- Total File Size: + Total File Size:
- Total Media Runtime: + Total Media Runtime:
- % endif
% if _session['user_group'] == 'admin':
@@ -852,10 +850,6 @@ DOCUMENTATION :: END section_id: section_id, refresh: refresh_table }; - }, - complete: function(xhr, status) { - $("#info-element-total-media-runtime").html(humanDuration(xhr.responseJSON.total_media_duration)); - $("#info-element-total-file-size").html(humanFileSize(xhr.responseJSON.total_file_size)); } }; media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options); @@ -866,6 +860,18 @@ DOCUMENTATION :: END clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table); } + $(document).ready(function () { + // Populate media stats + $.ajax({ + url: 'get_library_media_stats', + data: { section_id: section_id }, + complete: function(xhr, status) { + $("#info-element-total-runtime").html(humanDuration(xhr.responseJSON.total_duration)); + $("#info-element-total-storage").html(humanFileSize(xhr.responseJSON.total_storage)); + } + }); + }); + $('#nav-tabs-mediainfo').on('shown.bs.tab', function() { if (typeof(media_info_table) === 'undefined') { loadMediaInfoTable(); diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 7be86c04..cd367f91 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -701,6 +701,7 @@ class WebInterface(object): return "Failed to update library." @cherrypy.expose + @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_library_media_stats(self, section_id=None): @@ -723,8 +724,10 @@ class WebInterface(object): """ logger.info("Getting library media stats for section %s.", section_id) - - return libraries.get_library_media_stats(section_id) + result = libraries.get_library_media_stats(section_id) + logger.debug("test") + logger.debug(result) + return result @cherrypy.expose @requireAuth() From 0235ebe4d223779e1e5aca9fe65f2d16929b89d2 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 29 Oct 2023 12:40:57 +0100 Subject: [PATCH 07/12] remove debug logs --- plexpy/webserve.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index cd367f91..4707e78a 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -725,8 +725,7 @@ class WebInterface(object): logger.info("Getting library media stats for section %s.", section_id) result = libraries.get_library_media_stats(section_id) - logger.debug("test") - logger.debug(result) + return result @cherrypy.expose From 672835e2ac0de571d05b53633fd636b2a21b2520 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 29 Oct 2023 17:35:27 +0100 Subject: [PATCH 08/12] duration for items in media info --- .../default/js/tables/media_info_table.js | 19 ++++++++++++++++++- data/interfaces/default/library.html | 1 + plexpy/pmsconnect.py | 2 +- plexpy/webserve.py | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/js/tables/media_info_table.js b/data/interfaces/default/js/tables/media_info_table.js index f4d80d53..cf020c2f 100644 --- a/data/interfaces/default/js/tables/media_info_table.js +++ b/data/interfaces/default/js/tables/media_info_table.js @@ -224,6 +224,22 @@ media_info_table_options = { }, { "targets": [10], + "data": "duration", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== null && cellData !== '') { + $(td).html(humanDuration(cellData)); + } else { + if (rowData['section_type'] != 'photo' && get_file_sizes != null) { + get_file_sizes = true; + } + } + }, + "width": "7%", + "className": "no-wrap", + "searchable": false + }, + { + "targets": [11], "data": "last_played", "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== null && cellData !== '') { @@ -236,7 +252,7 @@ media_info_table_options = { "searchable": false }, { - "targets": [11], + "targets": [12], "data": "play_count", "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== null && cellData !== '') { @@ -457,6 +473,7 @@ function childTableFormatMedia(rowData) { 'Audio Codec' + 'Audio Channels' + 'File Size' + + 'Duration' + 'Last Played' + 'Total Plays' + '' + diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index bfdcb79d..94064523 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -318,6 +318,7 @@ DOCUMENTATION :: END Audio Codec Audio Channels File Size + Duration Last Played Total Plays diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 90de5c89..e0138799 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -2806,7 +2806,7 @@ class PmsConnect(object): 'parent_thumb': helpers.get_xml_attr(item, 'thumb'), 'grandparent_thumb': helpers.get_xml_attr(item, 'grandparentThumb'), 'added_at': helpers.get_xml_attr(item, 'addedAt'), - 'duration': helpers.get_xml_attr(item, 'duration') if section_type == 'movie' else 0 + 'duration': helpers.get_xml_attr(item, 'duration') if section_type in ('movie', 'episode', 'track') else 0 } if get_media_info: diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 4707e78a..833a8d9b 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -881,6 +881,7 @@ class WebInterface(object): ("video_framerate", True, True), ("audio_codec", True, True), ("audio_channels", True, True), + ("duration", True, False), ("file_size", True, False), ("last_played", True, False), ("play_count", True, False)] From ad4d3c30d5763808b772f52b900c6eb269504da9 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 29 Oct 2023 18:57:24 +0100 Subject: [PATCH 09/12] rebuilt to generic cache handling and cache for library media stats --- plexpy/libraries.py | 105 +++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 60 deletions(-) diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 82768123..67808a37 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -145,6 +145,7 @@ def has_library_type(section_type): def get_library_media_stats(section_id=None): plex = Plex(token=session.get_session_user_token()) + libraries = Libraries() default_return = { 'total_size': 0, @@ -160,18 +161,24 @@ def get_library_media_stats(section_id=None): logger.warn("Tautulli Libraries :: Library media stats requested but library is not allowed for this session.") return default_return - library = plex.get_library(section_id) + # Import media info cache from json file + _, cached_library_media_stats, _ = libraries._load_data_from_cache(section_id=section_id, path='media_stats') - if library is None: - logger.warn("Tautulli Libraries :: Library media stats requested but no library was found section_id %s.", section_id) - return default_return + if not cached_library_media_stats: + library = plex.get_library(section_id) + + if library is None: + logger.warn("Tautulli Libraries :: Library media stats requested but no library was found section_id %s.", section_id) + return default_return library_media_stats = { - 'total_size': library.totalSize, - 'total_storage': library.totalStorage, - 'total_duration': library.totalDuration + 'total_size': cached_library_media_stats.get('total_size', 0) if cached_library_media_stats else library.totalSize, + 'total_storage': cached_library_media_stats.get('total_storage', 0) if cached_library_media_stats else library.totalStorage, + 'total_duration': cached_library_media_stats.get('total_duration', 0) if cached_library_media_stats else library.totalDuration } + libraries._save_data_to_cache(section_id=section_id, rows=library_media_stats, path='media_stats') + return library_media_stats def get_collections(section_id=None): @@ -551,7 +558,7 @@ class Libraries(object): 'play_count': item['play_count']} # Import media info cache from json file - cache_time, rows, library_count = self._load_media_info_cache(section_id=section_id, rating_key=rating_key) + cache_time, rows, library_count = self._load_data_from_cache(section_id=section_id, rating_key=rating_key, path='media_info') # Check if duration is also included in cache else refresh cache to prevent update issues refresh = refresh if None not in {d.get('duration') for d in rows} else True @@ -617,7 +624,7 @@ class Libraries(object): return default_return # Cache the media info to a json file - self._save_media_info_cache(section_id=section_id, rating_key=rating_key, rows=rows) + self._save_data_to_cache(section_id=section_id, rating_key=rating_key, rows=rows, path='media_info') # Update the last_played and play_count for item in rows: @@ -706,7 +713,7 @@ class Libraries(object): return False # Import media info cache from json file - _, rows, _ = self._load_media_info_cache(section_id=section_id, rating_key=rating_key) + _, rows, _ = self._load_data_from_cache(section_id=section_id, rating_key=rating_key, path='media_info') # Get the total file size for each item if rating_key: @@ -742,7 +749,7 @@ class Libraries(object): item['duration'] = duration # Cache the media info to a json file - self._save_media_info_cache(section_id=section_id, rating_key=rating_key, rows=rows) + self._save_data_to_cache(section_id=section_id, rating_key=rating_key, rows=rows, path='media_info') if rating_key: logger.debug("Tautulli Libraries :: File sizes updated for rating_key %s." % rating_key) @@ -751,67 +758,45 @@ class Libraries(object): return True - def _load_media_info_cache(self, section_id=None, rating_key=None): + def _load_data_from_cache(self, section_id=None, rating_key=None, path=None): cache_time = None rows = [] library_count = 0 - # Import media info cache from json file - if rating_key: - try: - inFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR,'media_info_%s-%s.json' % (section_id, rating_key)) - with open(inFilePath, 'r') as inFile: - data = json.load(inFile) - if isinstance(data, dict): - cache_time = data['last_refreshed'] - rows = data['rows'] - else: - rows = data - library_count = len(rows) - logger.debug("Tautulli Libraries :: Loaded media info from cache for rating_key %s (%s items)." % (rating_key, library_count)) - except IOError as e: - logger.debug("Tautulli Libraries :: No media info cache for rating_key %s." % rating_key) - - elif section_id: - try: - inFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR,'media_info_%s.json' % section_id) - with open(inFilePath, 'r') as inFile: - data = json.load(inFile) - if isinstance(data, dict): - cache_time = data['last_refreshed'] - rows = data['rows'] - else: - rows = data - library_count = len(rows) - logger.debug("Tautulli Libraries :: Loaded media info from cache for section_id %s (%s items)." % (section_id, library_count)) - except IOError as e: - logger.debug("Tautulli Libraries :: No media info cache for section_id %s." % section_id) + # Import data cache from json file + try: + inFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR, (path + '_%s%s' % (section_id, ('-' + rating_key) if rating_key else ''))) + with open(inFilePath, 'r') as inFile: + data = json.load(inFile) + if isinstance(data, dict): + cache_time = data['last_refreshed'] + rows = data['rows'] + else: + rows = data + library_count = len(rows) + logger.debug("Tautulli Libraries :: Loaded %s from cache for section_id %s%s (%s items)." % + (path, section_id, (' and rating key ' + rating_key) if rating_key else '', library_count)) + except IOError as e: + logger.debug("Tautulli Libraries :: No media info cache for section_id %s%s." % + (section_id, (' and rating key ' + rating_key) if rating_key else '')) return cache_time, rows, library_count - def _save_media_info_cache(self, section_id=None, rating_key=None, rows=None): + def _save_data_to_cache(self, section_id, rating_key=None, rows=None, path=None): cache_time = helpers.timestamp() if rows is None: rows = [] - - if rating_key: - try: - outFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR,'media_info_%s-%s.json' % (section_id, rating_key)) - with open(outFilePath, 'w') as outFile: - json.dump({'last_refreshed': cache_time, 'rows': rows}, outFile) - logger.debug("Tautulli Libraries :: Saved media info cache for rating_key %s." % rating_key) - except IOError as e: - logger.debug("Tautulli Libraries :: Unable to create cache file for rating_key %s." % rating_key) - elif section_id: - try: - outFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR,'media_info_%s.json' % section_id) - with open(outFilePath, 'w') as outFile: - json.dump({'last_refreshed': cache_time, 'rows': rows}, outFile) - logger.debug("Tautulli Libraries :: Saved media info cache for section_id %s." % section_id) - except IOError as e: - logger.debug("Tautulli Libraries :: Unable to create cache file for section_id %s." % section_id) + try: + outFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR, (path + '_%s%s' % (section_id, ('-' + rating_key) if rating_key else ''))) + with open(outFilePath, 'w') as outFile: + json.dump({'last_refreshed': cache_time, 'rows': rows}, outFile) + logger.debug("Tautulli Libraries :: Saved %s cache for section_id %s%s." % + (path, section_id, (' and rating key ' + rating_key) if rating_key else '')) + except IOError as e: + logger.debug("Tautulli Libraries :: Unable to create cache file for section_id %s%s with error %s." % + (section_id, (' and rating key ' + rating_key) if rating_key else '', e)) def set_config(self, section_id=None, custom_thumb='', custom_art='', do_notify=1, keep_history=1, do_notify_created=1): From 2689646f54da3a08b38d939d88687c93720321d1 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:21:36 +0100 Subject: [PATCH 10/12] fix cell formatting and concat of rating_key and section_id --- data/interfaces/default/js/tables/media_info_table.js | 2 +- plexpy/libraries.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/js/tables/media_info_table.js b/data/interfaces/default/js/tables/media_info_table.js index cf020c2f..c27eadb3 100644 --- a/data/interfaces/default/js/tables/media_info_table.js +++ b/data/interfaces/default/js/tables/media_info_table.js @@ -226,7 +226,7 @@ media_info_table_options = { "targets": [10], "data": "duration", "createdCell": function (td, cellData, rowData, row, col) { - if (cellData !== null && cellData !== '') { + if (cellData !== null && cellData !== '' && cellData !== 0) { $(td).html(humanDuration(cellData)); } else { if (rowData['section_type'] != 'photo' && get_file_sizes != null) { diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 67808a37..89e21898 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -763,6 +763,9 @@ class Libraries(object): rows = [] library_count = 0 + section_id = str(section_id) if section_id else section_id + rating_key = str(rating_key) if rating_key else rating_key + # Import data cache from json file try: inFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR, (path + '_%s%s' % (section_id, ('-' + rating_key) if rating_key else ''))) @@ -788,6 +791,9 @@ class Libraries(object): if rows is None: rows = [] + section_id = str(section_id) if section_id else section_id + rating_key = str(rating_key) if rating_key else rating_key + try: outFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR, (path + '_%s%s' % (section_id, ('-' + rating_key) if rating_key else ''))) with open(outFilePath, 'w') as outFile: From 31f5dcd9dae6ade6d633d23aac96615076c255b7 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:51:00 +0100 Subject: [PATCH 11/12] refresh for library page --- data/interfaces/default/library.html | 9 +++++++-- plexpy/libraries.py | 11 ++++++----- plexpy/webserve.py | 10 ++++++++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index 94064523..5a0cf90a 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -862,16 +862,20 @@ DOCUMENTATION :: END } $(document).ready(function () { + loadLibraryMediaStats(); + }); + + function loadLibraryMediaStats(refresh) { // Populate media stats $.ajax({ url: 'get_library_media_stats', - data: { section_id: section_id }, + data: { section_id: section_id, refresh: refresh }, complete: function(xhr, status) { $("#info-element-total-runtime").html(humanDuration(xhr.responseJSON.total_duration)); $("#info-element-total-storage").html(humanFileSize(xhr.responseJSON.total_storage)); } }); - }); + } $('#nav-tabs-mediainfo').on('shown.bs.tab', function() { if (typeof(media_info_table) === 'undefined') { @@ -884,6 +888,7 @@ DOCUMENTATION :: END refresh_table = true; refresh_child_tables = true; media_info_table.draw(); + loadLibraryMediaStats(true); refresh_table = false; }); diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 89e21898..b03568a5 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -143,7 +143,7 @@ def has_library_type(section_type): result = monitor_db.select_single(query=query, args=args) return bool(result) -def get_library_media_stats(section_id=None): +def get_library_media_stats(section_id=None, refresh=False): plex = Plex(token=session.get_session_user_token()) libraries = Libraries() @@ -164,7 +164,8 @@ def get_library_media_stats(section_id=None): # Import media info cache from json file _, cached_library_media_stats, _ = libraries._load_data_from_cache(section_id=section_id, path='media_stats') - if not cached_library_media_stats: + _live_data = not cached_library_media_stats or refresh + if _live_data: library = plex.get_library(section_id) if library is None: @@ -172,9 +173,9 @@ def get_library_media_stats(section_id=None): return default_return library_media_stats = { - 'total_size': cached_library_media_stats.get('total_size', 0) if cached_library_media_stats else library.totalSize, - 'total_storage': cached_library_media_stats.get('total_storage', 0) if cached_library_media_stats else library.totalStorage, - 'total_duration': cached_library_media_stats.get('total_duration', 0) if cached_library_media_stats else library.totalDuration + 'total_size': library.totalSize if _live_data else cached_library_media_stats.get('total_size', 0), + 'total_storage': library.totalStorage if _live_data else cached_library_media_stats.get('total_storage', 0), + 'total_duration': library.totalDuration if _live_data else cached_library_media_stats.get('total_duration', 0) } libraries._save_data_to_cache(section_id=section_id, rows=library_media_stats, path='media_stats') diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 833a8d9b..b00036f6 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -704,12 +704,13 @@ class WebInterface(object): @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() - def get_library_media_stats(self, section_id=None): + def get_library_media_stats(self, section_id=None, refresh=''): """ Get the media stats of a library section on Tautulli. ``` Required parameters: section_id (str): The id of the Plex library section + refresh (str): "true" to force a refresh of the stats Optional parameters: None @@ -722,9 +723,14 @@ class WebInterface(object): } ``` """ + + if helpers.bool_true(refresh): + refresh = True + else: + refresh = False logger.info("Getting library media stats for section %s.", section_id) - result = libraries.get_library_media_stats(section_id) + result = libraries.get_library_media_stats(section_id=section_id, refresh=refresh) return result From b447c37774fa91f8c5eaac900551ca0a5d1f412d Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 29 Oct 2023 20:09:53 +0100 Subject: [PATCH 12/12] refresh of library media stats for libraries page --- plexpy/libraries.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plexpy/libraries.py b/plexpy/libraries.py index b03568a5..0d4a5e74 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -93,6 +93,8 @@ def refresh_libraries(): if result == 'insert': new_keys.append(section['section_id']) + get_library_media_stats(section_id=section['section_id'], refresh=True) + add_live_tv_library(refresh=True) query = "UPDATE library_sections SET is_active = 0 WHERE server_id != ? OR " \