Add watch time and user stats to info pages (#1471)

* watch time stats for show

* watch time stats for movie

* User Stats for movies and shows

* implementation for missing media types

* cleanup

* no data + rescrict guest access implementation

Co-authored-by: herby2212 <jannes.nagel@gmx.de>
This commit is contained in:
herby2212 2021-10-22 02:33:29 +02:00 committed by GitHub
parent 22f4b33087
commit c6ff8940b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 322 additions and 1 deletions

View file

@ -539,6 +539,34 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
% endif % endif
% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track'):
<div class="col-md-12">
<div class="table-card-header">
<div class="header-bar">
<span><i class="fa fa-line-chart"></i> Global Stats</span>
</div>
</div>
<div class="table-card-back">
<div id="watch-time-stats" class="user-overview-stats-wrapper">
<div class='muted'><i class="fa fa-refresh fa-spin"></i> Loading data...</div>
<br>
</div>
</div>
</div>
<div class="col-md-12">
<div class="table-card-header">
<div class="header-bar">
<span><i class="fa fa-group"></i> User Stats</span>
</div>
</div>
<div class="table-card-back">
<div id="user-stats" class="user-player">
<div class='muted'><i class="fa fa-refresh fa-spin"></i> Loading data...</div>
<br>
</div>
</div>
</div>
% endif
<% <%
history_type = data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track') history_type = data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track')
history_active = 'active' if history_type else '' history_active = 'active' if history_type else ''
@ -894,6 +922,28 @@ DOCUMENTATION :: END
}); });
</script> </script>
% endif % endif
% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track'):
<script>
// Populate watch time stats
$.ajax({
url: 'item_watch_time_stats',
async: true,
data: { rating_key: "${data['rating_key']}", media_type: "${data['media_type']}" },
complete: function(xhr, status) {
$("#watch-time-stats").html(xhr.responseText);
}
});
// Populate user stats
$.ajax({
url: 'item_user_stats',
async: true,
data: { rating_key: "${data['rating_key']}", media_type: "${data['media_type']}" },
complete: function(xhr, status) {
$("#user-stats").html(xhr.responseText);
}
});
</script>
% endif
% if data['media_type'] == 'collection': % if data['media_type'] == 'collection':
<script> <script>
$.ajax({ $.ajax({

View file

@ -1017,6 +1017,148 @@ class DataFactory(object):
return library_stats return library_stats
def get_watch_time_stats(self, rating_key=None, media_type=None, grouping=None, query_days=None):
if rating_key is None:
return []
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
if query_days and query_days is not None:
query_days = map(helpers.cast_to_int, str(query_days).split(','))
else:
query_days = [1, 7, 30, 0]
timestamp = helpers.timestamp()
monitor_db = database.MonitorDatabase()
item_watch_time_stats = []
section_ids = set()
if media_type in ('show', 'artist'):
media_type_key = 'session_history.grandparent_rating_key'
elif media_type in ('season', 'album'):
media_type_key = 'session_history.parent_rating_key'
else: # movie, episode, track
media_type_key = 'session_history.rating_key'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
for days in query_days:
timestamp_query = timestamp - days * 24 * 60 * 60
try:
if days > 0:
if str(rating_key).isdigit():
query = 'SELECT (SUM(stopped - started) - ' \
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END)) AS total_time, ' \
'COUNT(DISTINCT %s) AS total_plays, section_id ' \
'FROM session_history ' \
'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
'WHERE stopped >= %s ' \
'AND %s = ?' % (group_by, timestamp_query, media_type_key)
result = monitor_db.select(query, args=[rating_key])
else:
result = []
else:
if str(rating_key).isdigit():
query = 'SELECT (SUM(stopped - started) - ' \
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END)) AS total_time, ' \
'COUNT(DISTINCT %s) AS total_plays, section_id ' \
'FROM session_history ' \
'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
'WHERE %s = ?' % (group_by, media_type_key)
result = monitor_db.select(query, args=[rating_key])
else:
result = []
except Exception as e:
logger.warn("Tautulli Libraries :: Unable to execute database query for get_watch_time_stats: %s." % e)
result = []
for item in result:
section_ids.add(item['section_id'])
if item['total_time']:
total_time = item['total_time']
total_plays = item['total_plays']
else:
total_time = 0
total_plays = 0
row = {'query_days': days,
'total_time': total_time,
'total_plays': total_plays
}
item_watch_time_stats.append(row)
if any(not session.allow_session_library(section_id) for section_id in section_ids):
return []
return item_watch_time_stats
def get_user_stats(self, rating_key=None, media_type=None, grouping=None):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
monitor_db = database.MonitorDatabase()
user_stats = []
section_ids = set()
if media_type in ('show', 'artist'):
media_type_key = 'session_history.grandparent_rating_key'
elif media_type in ('season', 'album'):
media_type_key = 'session_history.parent_rating_key'
else: # movie, episode, track
media_type_key = 'session_history.rating_key'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if str(rating_key).isdigit():
query = 'SELECT (CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \
'THEN users.username ELSE users.friendly_name END) AS friendly_name, ' \
'users.user_id, users.username, users.thumb, users.custom_avatar_url AS custom_thumb, ' \
'COUNT(DISTINCT %s) AS user_count, section_id ' \
'FROM session_history ' \
'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
'JOIN users ON users.user_id = session_history.user_id ' \
'WHERE %s = ? ' \
'GROUP BY users.user_id ' \
'ORDER BY user_count DESC' % (group_by, media_type_key)
result = monitor_db.select(query, args=[rating_key])
else:
result = []
except Exception as e:
logger.warn("Tautulli Libraries :: Unable to execute database query for get_user_stats: %s." % e)
result = []
for item in result:
section_ids.add(item['section_id'])
if item['custom_thumb'] and item['custom_thumb'] != item['thumb']:
user_thumb = item['custom_thumb']
elif item['thumb']:
user_thumb = item['thumb']
else:
user_thumb = common.DEFAULT_USER_THUMB
row = {'friendly_name': item['friendly_name'],
'user_id': item['user_id'],
'user_thumb': user_thumb,
'username': item['username'],
'total_plays': item['user_count']
}
user_stats.append(row)
if any(not session.allow_session_library(section_id) for section_id in section_ids):
return []
return session.mask_session_info(user_stats, mask_metadata=False)
def get_stream_details(self, row_id=None, session_key=None): def get_stream_details(self, row_id=None, session_key=None):
monitor_db = database.MonitorDatabase() monitor_db = database.MonitorDatabase()

View file

@ -458,7 +458,6 @@ class WebInterface(object):
else: else:
return {'result': 'error', 'message': 'Flush recently added failed.'} return {'result': 'error', 'message': 'Flush recently added failed.'}
##### Libraries ##### ##### Libraries #####
@cherrypy.expose @cherrypy.expose
@ -4486,6 +4485,136 @@ class WebInterface(object):
else: else:
return serve_template(templatename="info_collection_list.html", data=None, title=title) return serve_template(templatename="info_collection_list.html", data=None, title=title)
@cherrypy.expose
@requireAuth()
def item_watch_time_stats(self, rating_key=None, media_type=None, **kwargs):
if rating_key:
item_data = datafactory.DataFactory()
result = item_data.get_watch_time_stats(rating_key=rating_key, media_type=media_type)
else:
result = None
if result:
return serve_template(templatename="user_watch_time_stats.html", data=result, title="Watch Stats")
else:
logger.warn("Unable to retrieve data for item_watch_time_stats.")
return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats")
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_item_watch_time_stats(self, rating_key=None, media_type=None, **kwargs):
""" Get the watch time stats for the media item.
```
Required parameters:
rating_key (str): Rating key of the item
media_type (str): Media type of the item
Optional parameters:
None
Returns:
json:
[
{
"query_days": 1,
"total_time": 0,
"total_plays": 0
},
{
"query_days": 7,
"total_time": 0,
"total_plays": 0
},
{
"query_days": 30,
"total_time": 0,
"total_plays": 0
},
{
"query_days": 0,
"total_time": 57776,
"total_plays": 13
}
]
```
"""
if rating_key:
item_data = datafactory.DataFactory()
stats = item_data.get_watch_time_stats(rating_key=rating_key, media_type=media_type)
else:
stats = None
if stats:
return stats
else:
logger.warn("Unable to retrieve data for get_item_watch_time_stats.")
return stats
@cherrypy.expose
@requireAuth()
def item_user_stats(self, rating_key=None, media_type=None, **kwargs):
if rating_key:
item_data = datafactory.DataFactory()
result = item_data.get_user_stats(rating_key=rating_key, media_type=media_type)
else:
result = None
if result:
return serve_template(templatename="library_user_stats.html", data=result, title="Player Stats")
else:
logger.warn("Unable to retrieve data for item_user_stats.")
return serve_template(templatename="library_user_stats.html", data=None, title="Player Stats")
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_item_user_stats(self, rating_key=None, media_type=None, **kwargs):
""" Get the user stats for the media item.
```
Required parameters:
rating_key (str): Rating key of the item
media_type (str): Media type of the item
Optional parameters:
None
Returns:
json:
[
{
"friendly_name": "Jon Snow",
"user_id": 1601089,
"user_thumb": "",
"username": "jsnow@thewinteriscoming.com",
"total_plays": 6
},
{
"friendly_name": "DanyKhaleesi69",
"user_id": 8008135,
"user_thumb": "",
"username": "DanyKhaleesi69",
"total_plays": 5
}
]
```
"""
if rating_key:
item_data = datafactory.DataFactory()
stats = item_data.get_user_stats(rating_key=rating_key, media_type=media_type)
else:
stats = None
if stats:
return stats
else:
logger.warn("Unable to retrieve data for get_item_user_stats.")
return stats
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))