From 8e57df53fd306c83a65eff66e897a4dff9d858e5 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 16 Aug 2015 13:48:39 +0200 Subject: [PATCH 01/19] Report the correct version numbers. --- data/interfaces/default/settings.html | 6 ++++-- data/interfaces/default/welcome.html | 6 ++++-- plexpy/common.py | 11 +++++++---- plexpy/version.py | 1 + 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 7023c2d8..8654f99a 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1,7 +1,7 @@ <%inherit file="base.html"/> <%! import plexpy -from plexpy import notifiers +from plexpy import notifiers, common available_notification_agents = notifiers.available_notification_agents() %> @@ -1023,7 +1023,9 @@ $(document).ready(function() { headers: {'Content-Type': 'application/xml; charset=utf-8', 'X-Plex-Device-Name': 'PlexPy', 'X-Plex-Product': 'PlexPy', - 'X-Plex-Version': 'v0.1 dev', + 'X-Plex-Version': '${common.VERSION_NUMBER}', + 'X-Plex-Platform': '${common.PLATFORM}', + 'X-Plex-Platform-Version': '${common.PLATFORM_VERSION}', 'X-Plex-Client-Identifier': '${config['pms_uuid']}', 'Authorization': 'Basic ' + btoa($("#pms_username").val() + ':' + $("#pms_password").val()) }, diff --git a/data/interfaces/default/welcome.html b/data/interfaces/default/welcome.html index 27958ace..bbbeabb3 100644 --- a/data/interfaces/default/welcome.html +++ b/data/interfaces/default/welcome.html @@ -1,6 +1,6 @@ <% import plexpy -from plexpy import version +from plexpy import common %> @@ -355,7 +355,9 @@ from plexpy import version headers: {'Content-Type': 'application/xml; charset=utf-8', 'X-Plex-Device-Name': 'PlexPy', 'X-Plex-Product': 'PlexPy', - 'X-Plex-Version': 'v0.1 dev', + 'X-Plex-Version': '${common.VERSION_NUMBER}', + 'X-Plex-Platform': '${common.PLATFORM}', + 'X-Plex-Platform-Version': '${common.PLATFORM_VERSION}', 'X-Plex-Client-Identifier': '${config['pms_uuid']}', 'Authorization': 'Basic ' + btoa($("#pms_username").val() + ':' + $("#pms_password").val()) }, diff --git a/plexpy/common.py b/plexpy/common.py index c0f270d7..d3f740a2 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -19,14 +19,17 @@ Created on Aug 1, 2011 @author: Michael ''' import platform -import operator -import os -import re from plexpy import version # Identify Our Application -USER_AGENT = 'PlexPy/-' + version.PLEXPY_VERSION + ' (' + platform.system() + ' ' + platform.release() + ')' +USER_AGENT = 'PlexPy/-' + version.PLEXPY_VERSION + ' v' + version.PLEXPY_RELEASE_VERSION + ' (' + platform.system() + \ + ' ' + platform.release() + ')' + +PLATFORM = platform.system() +PLATFORM_VERSION = platform.release() +BRANCH = version.PLEXPY_VERSION +VERSION_NUMBER = version.PLEXPY_RELEASE_VERSION # Notification Types NOTIFY_STARTED = 1 diff --git a/plexpy/version.py b/plexpy/version.py index 9ea604be..839bf7aa 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -1 +1,2 @@ PLEXPY_VERSION = "master" +PLEXPY_RELEASE_VERSION = "1.1.2" From 6b1a57e650b46308e05a482c7cbb350277b150fd Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 16 Aug 2015 22:52:08 +0200 Subject: [PATCH 02/19] Add "delete mode" on history table allows individual rows to be deleted permanently. Add user history purge option in edit user screen. Will remove all history for selected user. --- data/interfaces/default/edit_user.html | 21 +++++++ data/interfaces/default/history.html | 20 ++++++- data/interfaces/default/info.html | 4 +- .../default/js/tables/history_table.js | 60 +++++++++++++++---- data/interfaces/default/sync.html | 2 +- data/interfaces/default/user.html | 4 +- plexpy/datafactory.py | 46 +++++++++++++- plexpy/webserve.py | 29 +++++++++ 8 files changed, 165 insertions(+), 21 deletions(-) diff --git a/data/interfaces/default/edit_user.html b/data/interfaces/default/edit_user.html index 37aba8bb..0a266196 100644 --- a/data/interfaces/default/edit_user.html +++ b/data/interfaces/default/edit_user.html @@ -58,6 +58,12 @@ DOCUMENTATION :: END

Uncheck this if you do not want this keep any history on this user's activity.

+ % if data['user_id']: +
+ +

DANGER ZONE! Click the purge button to remote all history logged for this user. This is permanent!

+
+ % endif @@ -226,6 +224,8 @@ from plexpy import helpers +
From dd27f9bf72f38b1b5f8a842fcdd37ce240d1e1c3 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 17 Aug 2015 13:03:44 -0700 Subject: [PATCH 08/19] Fixed recently added and recently watched posters. Changed posters to match Plex/Web style, and fixed stretching on "home video" posters. --- data/interfaces/default/css/plexpy.css | 53 +++++++++---------- data/interfaces/default/recently_added.html | 46 ++++++++-------- .../default/user_recently_watched.html | 28 +++++----- 3 files changed, 63 insertions(+), 64 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index da015f42..173446b4 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -407,39 +407,31 @@ input[type="color"], } .poster { float: left; - min-height: 232px; - min-width: 155px; + min-height: 225px; + min-width: 150px; + margin-bottom: 8px; position: relative; } -.poster-face img { - bottom: 0; - overflow: hidden; +.poster-face { + background-position: center; + background-size: cover; height: 225px; - width: 153px; + width: 150px; + position: relative; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); +} +.cover-face { + background-position: center; + background-size: cover; + height: 150px; + width: 150px; position: absolute; - bottom: 5px; - left: 0; - border: 1px solid rgba(128, 128, 128, 0.3); -} -.poster-face img:hover { - webkit-box-shadow: 0 0 0 2px #e9a049; - -moz-box-shadow: 0 0 0 2px #e9a049; - box-shadow: 0 0 0 2px #e9a049; -} -.cover-face img { bottom: 0; - overflow: hidden; - height: 153px; - width: 153px; - border: 1px solid rgba(128, 128, 128, 0.3); - position: absolute; - bottom: 5px; - left: 0; -} -.cover-face img:hover { - webkit-box-shadow: 0 0 0 2px #e9a049; - -moz-box-shadow: 0 0 0 2px #e9a049; - box-shadow: 0 0 0 2px #e9a049; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } .users-poster-face img { bottom: 0; @@ -630,6 +622,11 @@ input[type="color"], } .dashboard-recent-media-instance { } +.dashboard-recent-media-instance a:hover .poster-face { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; +} .dashboard-recent-media li { margin-right: 27px; position: relative; diff --git a/data/interfaces/default/recently_added.html b/data/interfaces/default/recently_added.html index af986e44..3037d4f8 100644 --- a/data/interfaces/default/recently_added.html +++ b/data/interfaces/default/recently_added.html @@ -29,29 +29,31 @@ DOCUMENTATION :: END % for item in data: diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 70e6f6a0..581bb715 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -280,8 +280,12 @@ available_notification_agents = notifiers.available_notification_agents()

If you have media indexing enabled on your server, use these on the activity pane.

+
+

Homepage Statistics

+
+
- +
@@ -289,6 +293,12 @@ available_notification_agents = notifiers.available_notification_agents()

Specify the number of days for the statistics on the home page. Default is 30 days.

+
+ +

Use play duration instead of play count to generate statistics.

+

Plex Logs

diff --git a/plexpy/config.py b/plexpy/config.py index 55411795..5fe59e29 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -83,6 +83,7 @@ _CONFIG_DEFINITIONS = { 'GROWL_ON_BUFFER': (int, 'Growl', 0), 'GROWL_ON_WATCHED': (int, 'Growl', 0), 'HOME_STATS_LENGTH': (int, 'General', 30), + 'HOME_STATS_TYPE': (int, 'General', 0), 'HTTPS_CERT': (str, 'General', ''), 'HTTPS_KEY': (str, 'General', ''), 'HTTP_HOST': (str, 'General', '0.0.0.0'), diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 3f8e89ef..005f6d07 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -110,14 +110,16 @@ class DataFactory(object): return dict - def get_home_stats(self, time_range='30'): + def get_home_stats(self, time_range='30', stat_type='0'): monitor_db = database.MonitorDatabase() if not time_range.isdigit(): time_range = '30' + sort_type = 'total_plays' if stat_type == '0' else 'total_duration' + # This actually determines the output order in the home page - stats_queries = ["top_tv", "popular_tv", "top_movies", "top_users", "top_platforms"] + stats_queries = ["top_tv", "popular_tv", "top_movies", "popular_movies", "top_users", "top_platforms"] home_stats = [] for stat in stats_queries: @@ -127,6 +129,10 @@ class DataFactory(object): query = 'SELECT session_history_metadata.id, ' \ 'session_history_metadata.grandparent_title, ' \ 'COUNT(session_history_metadata.grandparent_title) as total_plays, ' \ + 'cast(round(SUM(round((julianday(datetime(session_history.stopped, "unixepoch", "localtime")) - ' \ + 'julianday(datetime(session_history.started, "unixepoch", "localtime"))) * 86400) - ' \ + '(CASE WHEN session_history.paused_counter IS NULL THEN 0 ' \ + 'ELSE session_history.paused_counter END))/60) as integer) as total_duration,' \ 'session_history_metadata.grandparent_rating_key, ' \ 'MAX(session_history.started) as last_watch,' \ 'session_history_metadata.grandparent_thumb ' \ @@ -136,7 +142,7 @@ class DataFactory(object): '>= datetime("now", "-%s days", "localtime") ' \ 'AND session_history_metadata.media_type = "episode" ' \ 'GROUP BY session_history_metadata.grandparent_title ' \ - 'ORDER BY total_plays DESC LIMIT 10' % time_range + 'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -145,10 +151,11 @@ class DataFactory(object): for item in result: row = {'title': item[1], 'total_plays': item[2], + 'total_duration': item[3], 'users_watched': '', - 'rating_key': item[3], - 'last_play': item[4], - 'grandparent_thumb': item[5], + 'rating_key': item[4], + 'last_play': item[5], + 'grandparent_thumb': item[6], 'thumb': '', 'user': '', 'friendly_name': '', @@ -159,6 +166,7 @@ class DataFactory(object): top_tv.append(row) home_stats.append({'stat_id': stat, + 'stat_type': sort_type, 'rows': top_tv}) elif 'top_movies' in stat: @@ -167,6 +175,10 @@ class DataFactory(object): query = 'SELECT session_history_metadata.id, ' \ 'session_history_metadata.full_title, ' \ 'COUNT(session_history_metadata.full_title) as total_plays, ' \ + 'cast(round(SUM(round((julianday(datetime(session_history.stopped, "unixepoch", "localtime")) - ' \ + 'julianday(datetime(session_history.started, "unixepoch", "localtime"))) * 86400) - ' \ + '(CASE WHEN session_history.paused_counter IS NULL THEN 0 ' \ + 'ELSE session_history.paused_counter END))/60) as integer) as total_duration,' \ 'session_history_metadata.rating_key, ' \ 'MAX(session_history.started) as last_watch,' \ 'session_history_metadata.thumb ' \ @@ -176,7 +188,7 @@ class DataFactory(object): '>= datetime("now", "-%s days", "localtime") ' \ 'AND session_history_metadata.media_type = "movie" ' \ 'GROUP BY session_history_metadata.full_title ' \ - 'ORDER BY total_plays DESC LIMIT 10' % time_range + 'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -185,11 +197,12 @@ class DataFactory(object): for item in result: row = {'title': item[1], 'total_plays': item[2], + 'total_duration': item[3], 'users_watched': '', - 'rating_key': item[3], - 'last_play': item[4], + 'rating_key': item[4], + 'last_play': item[5], 'grandparent_thumb': '', - 'thumb': item[5], + 'thumb': item[6], 'user': '', 'friendly_name': '', 'platform_type': '', @@ -199,6 +212,7 @@ class DataFactory(object): top_movies.append(row) home_stats.append({'stat_id': stat, + 'stat_type': sort_type, 'rows': top_movies}) elif 'popular_tv' in stat: @@ -243,6 +257,48 @@ class DataFactory(object): home_stats.append({'stat_id': stat, 'rows': popular_tv}) + elif 'popular_movies' in stat: + popular_movies = [] + try: + query = 'SELECT session_history_metadata.id, ' \ + 'session_history_metadata.full_title, ' \ + 'COUNT(DISTINCT session_history.user_id) as users_watched, ' \ + 'session_history_metadata.rating_key, ' \ + 'MAX(session_history.started) as last_watch, ' \ + 'COUNT(session_history.id) as total_plays, ' \ + 'session_history_metadata.thumb ' \ + 'FROM session_history_metadata ' \ + 'JOIN session_history ON session_history_metadata.id = session_history.id ' \ + 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + '>= datetime("now", "-%s days", "localtime") ' \ + 'AND session_history_metadata.media_type = "movie" ' \ + 'GROUP BY session_history_metadata.full_title ' \ + 'ORDER BY users_watched DESC, total_plays DESC ' \ + 'LIMIT 10' % time_range + result = monitor_db.select(query) + except: + logger.warn("Unable to execute database query.") + return None + + for item in result: + row = {'title': item[1], + 'users_watched': item[2], + 'rating_key': item[3], + 'last_play': item[4], + 'total_plays': item[5], + 'grandparent_thumb': '', + 'thumb': item[6], + 'user': '', + 'friendly_name': '', + 'platform_type': '', + 'platform': '', + 'row_id': item[0] + } + popular_movies.append(row) + + home_stats.append({'stat_id': stat, + 'rows': popular_movies}) + elif 'top_users' in stat: top_users = [] try: @@ -250,6 +306,10 @@ class DataFactory(object): '(case when users.friendly_name is null then session_history.user else ' \ 'users.friendly_name end) as friendly_name,' \ 'COUNT(session_history.id) as total_plays, ' \ + 'cast(round(SUM(round((julianday(datetime(session_history.stopped, "unixepoch", "localtime")) - ' \ + 'julianday(datetime(session_history.started, "unixepoch", "localtime"))) * 86400) - ' \ + '(CASE WHEN session_history.paused_counter IS NULL THEN 0 ' \ + 'ELSE session_history.paused_counter END))/60) as integer) as total_duration,' \ 'MAX(session_history.started) as last_watch, ' \ 'users.custom_avatar_url as thumb, ' \ 'users.user_id ' \ @@ -259,23 +319,24 @@ class DataFactory(object): 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'datetime("now", "-%s days", "localtime") '\ 'GROUP BY session_history.user_id ' \ - 'ORDER BY total_plays DESC LIMIT 10' % time_range + 'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") return None for item in result: - if not item[4] or item[4] == '': + if not item[5] or item[5] == '': user_thumb = common.DEFAULT_USER_THUMB else: - user_thumb = item[4] + user_thumb = item[5] row = {'user': item[0], - 'user_id': item[5], + 'user_id': item[6], 'friendly_name': item[1], 'total_plays': item[2], - 'last_play': item[3], + 'total_duration': item[3], + 'last_play': item[4], 'thumb': user_thumb, 'grandparent_thumb': '', 'users_watched': '', @@ -288,6 +349,7 @@ class DataFactory(object): top_users.append(row) home_stats.append({'stat_id': stat, + 'stat_type': sort_type, 'rows': top_users}) elif 'top_platforms' in stat: @@ -296,6 +358,10 @@ class DataFactory(object): try: query = 'SELECT session_history.platform, ' \ 'COUNT(session_history.id) as total_plays, ' \ + 'cast(round(SUM(round((julianday(datetime(session_history.stopped, "unixepoch", "localtime")) - ' \ + 'julianday(datetime(session_history.started, "unixepoch", "localtime"))) * 86400) - ' \ + '(CASE WHEN session_history.paused_counter IS NULL THEN 0 ' \ + 'ELSE session_history.paused_counter END))/60) as integer) as total_duration,' \ 'MAX(session_history.started) as last_watch ' \ 'FROM session_history ' \ 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ @@ -310,7 +376,8 @@ class DataFactory(object): for item in result: row = {'platform': item[0], 'total_plays': item[1], - 'last_play': item[2], + 'total_duration': item[2], + 'last_play': item[3], 'platform_type': item[0], 'title': '', 'thumb': '', @@ -324,6 +391,7 @@ class DataFactory(object): top_platform.append(row) home_stats.append({'stat_id': stat, + 'stat_type': sort_type, 'rows': top_platform}) return home_stats diff --git a/plexpy/webserve.py b/plexpy/webserve.py index ef2ad933..8b7e3c6d 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -65,7 +65,8 @@ class WebInterface(object): @cherrypy.expose def home(self): config = { - "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH + "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, + "home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE } return serve_template(templatename="index.html", title="Home", config=config) @@ -118,9 +119,9 @@ class WebInterface(object): return json.dumps(formats) @cherrypy.expose - def home_stats(self, time_range='30', **kwargs): + def home_stats(self, time_range='30', stat_type='0', **kwargs): data_factory = datafactory.DataFactory() - stats_data = data_factory.get_home_stats(time_range=time_range) + stats_data = data_factory.get_home_stats(time_range=time_range, stat_type=stat_type) return serve_template(templatename="home_stats.html", title="Stats", data=stats_data) @@ -451,6 +452,7 @@ class WebInterface(object): "notify_on_watched_subject_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT, "notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, + "home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE), "buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD, "buffer_wait": plexpy.CONFIG.BUFFER_WAIT } @@ -473,7 +475,7 @@ class WebInterface(object): "tv_notify_on_start", "movie_notify_on_start", "music_notify_on_start", "tv_notify_on_stop", "movie_notify_on_stop", "music_notify_on_stop", "tv_notify_on_pause", "movie_notify_on_pause", "music_notify_on_pause", "refresh_users_on_startup", - "ip_logging_enable", "video_logging_enable", "music_logging_enable", "pms_is_remote" + "ip_logging_enable", "video_logging_enable", "music_logging_enable", "pms_is_remote", "home_stats_type" ] for checked_config in checked_configs: if checked_config not in kwargs: From 7dfd063138c4c1c5e1a6c4287e066ab40c257bf0 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 18 Aug 2015 12:36:10 -0700 Subject: [PATCH 14/19] Update documentation for home stats --- data/interfaces/default/home_stats.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index 5c54d771..b4b9e600 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -9,7 +9,8 @@ Variable names: data [array] data[array_index] :: Usable parameters -data['stat_id'] Returns the name of the stat. Either 'top_tv', 'popular_tv', 'top_user' or 'top_platform' +data['stat_id'] Returns the name of the stat. Either 'top_tv', 'top_movies', 'popular_tv', 'popular_movies', 'top_user' or 'top_platform' +data['stat_type'] Returns the type of the stat. Either 'total_plays' or 'total_duration' data['rows'] Returns an array containing stat data data[array_index]['rows'] :: Usable parameters From bbaf428fd8b21a8c2977cd091d8b535d8bcb3cb7 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 18 Aug 2015 13:41:34 -0700 Subject: [PATCH 15/19] Fix "Select columns" button text --- data/interfaces/default/history.html | 4 ++-- data/interfaces/default/info.html | 4 ++-- data/interfaces/default/sync.html | 2 +- data/interfaces/default/user.html | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index 5bdd5c7d..a7a0dfcf 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -14,7 +14,7 @@ History
-   +  
@@ -63,7 +63,7 @@ } } history_table = $('#history_table').DataTable(history_table_options); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); $(colvis.button()).appendTo('div.colvis-button-bar'); $('#row-edit-mode').click(function() { diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 0cc8aba5..1f45fe4d 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -256,7 +256,7 @@ DOCUMENTATION :: END } history_table = $('#history_table').DataTable(history_table_options); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); $(colvis.button()).appendTo('div.colvis-button-bar'); }); @@ -274,7 +274,7 @@ DOCUMENTATION :: END } } history_table = $('#history_table').DataTable(history_table_options); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); $(colvis.button()).appendTo('div.colvis-button-bar'); }); diff --git a/data/interfaces/default/sync.html b/data/interfaces/default/sync.html index 343f8f9f..b856051c 100644 --- a/data/interfaces/default/sync.html +++ b/data/interfaces/default/sync.html @@ -58,7 +58,7 @@ } sync_table = $('#sync_table').DataTable(sync_table_options); - var colvis = new $.fn.dataTable.ColVis( sync_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark' } ); + var colvis = new $.fn.dataTable.ColVis( sync_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark' } ); $( colvis.button() ).appendTo('div.colvis-button-bar'); }); diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index 47c311f0..a4194a63 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -295,7 +295,7 @@ from plexpy import helpers history_table = $('#history_table').DataTable(history_table_options); history_table.column(2).visible(false); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); $(colvis.button()).appendTo('#button-bar-history'); }); From 2536fdf17bb4b5cf1bb3d92f276f28c02577a1bb Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 18 Aug 2015 22:59:24 +0200 Subject: [PATCH 16/19] Fix month display showing "invalid date" on totals graph. Make duration on home stats human readable. --- data/interfaces/default/graphs.html | 6 +---- data/interfaces/default/home_stats.html | 36 ++++++++++++++++--------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index ade1e6ac..bc234861 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -458,12 +458,8 @@ data: { y_axis: yaxis }, dataType: "json", success: function(data) { - var dateArray = []; - for (var i = 0; i < data.categories.length; i++) { - dateArray.push(moment(data.categories[i], 'YYYY-MM').format('MMM YYYY')); - } hc_plays_by_month_options.yAxis.min = 0; - hc_plays_by_month_options.xAxis.categories = dateArray; + hc_plays_by_month_options.xAxis.categories = data.categories; hc_plays_by_month_options.series = data.series; var hc_plays_by_month = new Highcharts.Chart(hc_plays_by_month_options); } diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index b4b9e600..3e65a5ef 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -41,6 +41,22 @@ platform_type Returns the platform name for the associated stat. DOCUMENTATION :: END +<%! + from plexpy import helpers + + # Human readable duration + def hd(minutes): + if int(minutes) > 60: + hours = int(helpers.cast_to_float(minutes) / 60) + minutes = int(helpers.cast_to_float(minutes) % hours) + if minutes > 0: + return "

" + str(hours) + "

hrs

" + str(minutes) + "

mins

" + else: + return "

" + str(hours) + "

hrs

" + else: + return "

" + minutes + "

mins

" +%> + % if data: % if data[0]['rows'] or data[2]['rows']:
    @@ -69,9 +85,8 @@ DOCUMENTATION :: END

    ${a['rows'][0]['total_plays']}

    plays

    % else: -

    ${a['rows'][0]['total_duration']}

    -

    mins

    - % endif + ${a['rows'][0]['total_duration'] | hd} + % endif @@ -124,9 +139,8 @@ DOCUMENTATION :: END

    ${a['rows'][0]['total_plays']}

    plays

    % else: -

    ${a['rows'][0]['total_duration']}

    -

    mins

    - % endif + ${a['rows'][0]['total_duration'] | hd} + % endif @@ -190,9 +204,8 @@ DOCUMENTATION :: END

    ${a['rows'][0]['total_plays']}

    plays

    % else: -

    ${a['rows'][0]['total_duration']}

    -

    mins

    - % endif + ${a['rows'][0]['total_duration'] | hd} + % endif @@ -211,9 +224,8 @@ DOCUMENTATION :: END

    ${a['rows'][0]['total_plays']}

    plays

    % else: -

    ${a['rows'][0]['total_duration']}

    -

    mins

    - % endif + ${a['rows'][0]['total_duration'] | hd} + % endif From 6efaabb6307298ddf058356d47f6bffb71b63ac3 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 18 Aug 2015 23:27:09 +0200 Subject: [PATCH 17/19] Remove some more Headphones references. --- init-scripts/init.fedora.centos.systemd | 4 ++-- init-scripts/init.upstart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/init-scripts/init.fedora.centos.systemd b/init-scripts/init.fedora.centos.systemd index e4a21133..708ddefa 100755 --- a/init-scripts/init.fedora.centos.systemd +++ b/init-scripts/init.fedora.centos.systemd @@ -1,4 +1,4 @@ -# PlexPy - Automatic music downloader for SABnzbd +# PlexPy # # Service Unit file for systemd system manager # @@ -53,7 +53,7 @@ # graphical.target equates to runlevel 5 (multi-user X11 graphical mode) [Unit] -Description=PlexPy - Automatic music downloader for SABnzbd +Description=PlexPy [Service] ExecStart=/home/sabnzbd/plexpy/PlexPy.py --daemon --config /etc/plexpy/plexpy.ini --datadir /home/sabnzbd/.plexpy --nolaunch --quiet diff --git a/init-scripts/init.upstart b/init-scripts/init.upstart index 255db796..6e11225a 100755 --- a/init-scripts/init.upstart +++ b/init-scripts/init.upstart @@ -1,4 +1,4 @@ -# plexpy - Automatic music downloader +# plexpy # # This is a session/user job. Install this file into /usr/share/upstart/sessions # if plexpy is installed system wide, and into $XDG_CONFIG_HOME/upstart if From 7e99eb7a2a2c1e3b71854bb959d87209602a8abb Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 18 Aug 2015 23:39:20 +0200 Subject: [PATCH 18/19] Update README. --- README.md | 61 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4370d213..e6839cbd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This project is based on code from Headphones (https://github.com/rembo10/headph * plexPy forum thread: https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program -If you'd like to buy me a beer, hit the donate button below. +If you'd like to buy me a beer, hit the donate button below. All donations go to the project maintainer (primarily for the procurement of liquid refreshment). [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G9HZK9BDJLKT6) @@ -34,6 +34,14 @@ If you'd like to buy me a beer, hit the donate button below. * stream type (direct, transcoded) * video type & resolution * audio type & channel count. + +* Top statistics on home page with configurable duration and measurement metric: + * Most watched TV + * Most popular TV + * Most watched Movie + * Most popular Movie + * Most active user + * Most active platform * Recently added media and how long ago it was added @@ -41,42 +49,49 @@ If you'd like to buy me a beer, hit the donate button below. * date * user * platform - * ip address (if enabled in plexWatch) + * ip address * title * stream information details * start time * paused duration length * stop time * duration length - * percentage completed + * watched progress + * show/hide columns + * delete mode - allows deletion of specific history items * Full user list with general information and comparison stats * Individual user information - - username and gravatar (if available) - - daily, weekly, monthly, all time stats for play count and duration length - - individual platform stats for each user - - public ip address history with last seen date and geo tag location - - recently watched content - - watching history - - synced items + * username and gravatar (if available) + * daily, weekly, monthly, all time stats for play count and duration length + * individual platform stats for each user + * public ip address history with last seen date and geo tag location + * recently watched content + * watching history + * synced items + * assign users custom friendly names within PlexPy + * assign users custom avatar URL within PlexPy + * disable history logging per user + * disable notifications per user + * option to purge all history per user. * Rich analytics presented using Highcharts graphing - - user-selectable time periods of 30, 90 or 365 days - - daily watch count and duration - - totals by day of week and hours of the day - - totals by top 10 platform - - totals by top 10 users - - detailed breakdown by transcode decision - - source and stream resolutions - - transcode decision counts by user and platform - - total monthly counts + * user-selectable time periods of 30, 90 or 365 days + * daily watch count and duration + * totals by day of week and hours of the day + * totals by top 10 platform + * totals by top 10 users + * detailed breakdown by transcode decision + * source and stream resolutions + * transcode decision counts by user and platform + * total monthly counts * Content information pages - - movies (includes watching history) - - tv shows (includes watching history) - - tv seasons - - tv episodes (includes watching history) + * movies (includes watching history) + * tv shows (includes watching history) + * tv seasons + * tv episodes (includes watching history) * Full sync list data on all users syncing items from your library From 13e6a70a30cd8466e3d108ba514ba8292ea9970d Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 19 Aug 2015 00:03:49 +0200 Subject: [PATCH 19/19] Fix typo on user edit page. Change styling of version number and changelog button. --- data/interfaces/default/edit_user.html | 2 +- data/interfaces/default/settings.html | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/edit_user.html b/data/interfaces/default/edit_user.html index 0a266196..cb1a5775 100644 --- a/data/interfaces/default/edit_user.html +++ b/data/interfaces/default/edit_user.html @@ -61,7 +61,7 @@ DOCUMENTATION :: END % if data['user_id']:
    -

    DANGER ZONE! Click the purge button to remote all history logged for this user. This is permanent!

    +

    DANGER ZONE! Click the purge button to remove all history logged for this user. This is permanent!

    % endif diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 581bb715..de7cdeb5 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -48,12 +48,10 @@ available_notification_agents = notifiers.available_notification_agents()
    -
    -

    Software Updates

    -
    % if common.VERSION_NUMBER: -
    Version ${common.VERSION_NUMBER} Changelog
    -
    +
    +

    Version ${common.VERSION_NUMBER} Changelog

    +
    % endif