# -*- coding: utf-8 -*- # This file is part of Tautulli. # # Tautulli is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Tautulli is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Tautulli. If not, see . from __future__ import division from __future__ import absolute_import from builtins import next from builtins import str from builtins import object from past.utils import old_div import json from itertools import groupby import plexpy from plexpy import common from plexpy import database from plexpy import datatables from plexpy import helpers from plexpy import logger from plexpy import pmsconnect from plexpy import session class DataFactory(object): """ Retrieve and process data from the monitor database """ def __init__(self): pass def get_datatables_history(self, kwargs=None, custom_where=None, grouping=None): data_tables = datatables.DataTables() if custom_where is None: custon_where = [] if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES if session.get_session_user_id(): session_user_id = str(session.get_session_user_id()) added = False for c_where in custom_where: if 'user_id' in c_where[0]: # This currently only works if c_where[1] is not a list or tuple if str(c_where[1]) == session_user_id: added = True break else: c_where[1] = (c_where[1], session_user_id) added = True if not added: custom_where.append(['session_history.user_id', session.get_session_user_id()]) group_by = ['session_history.reference_id'] if grouping else ['session_history.id'] columns = [ 'session_history.reference_id', 'session_history.id', 'MAX(started) AS date', 'MIN(started) AS started', 'MAX(stopped) AS stopped', 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - \ SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS duration', 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter', 'session_history.user_id', 'session_history.user', '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \ THEN users.username ELSE users.friendly_name END) AS friendly_name', 'platform', 'product', 'player', 'ip_address', 'session_history.media_type', 'session_history_metadata.rating_key', 'session_history_metadata.parent_rating_key', 'session_history_metadata.grandparent_rating_key', 'session_history_metadata.full_title', 'session_history_metadata.title', 'session_history_metadata.parent_title', 'session_history_metadata.grandparent_title', 'session_history_metadata.original_title', 'session_history_metadata.year', 'session_history_metadata.media_index', 'session_history_metadata.parent_media_index', 'session_history_metadata.thumb', 'session_history_metadata.parent_thumb', 'session_history_metadata.grandparent_thumb', 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \ (CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \ THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete', 'session_history_media_info.transcode_decision', 'COUNT(*) AS group_count', 'GROUP_CONCAT(session_history.id) AS group_ids', 'NULL AS state', 'NULL AS session_key' ] if plexpy.CONFIG.HISTORY_TABLE_ACTIVITY: table_name_union = 'sessions' # Very hacky way to match the custom where parameters for the unioned table custom_where_union = [[c[0].split('.')[-1], c[1]] for c in custom_where] group_by_union = ['session_key'] columns_union = [ 'NULL AS reference_id', 'NULL AS id', 'started AS date', 'started', 'stopped', 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE (strftime("%s", "now") - started) END) - \ SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS duration', 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter', 'user_id', 'user', '(CASE WHEN friendly_name IS NULL OR TRIM(friendly_name) = "" \ THEN user ELSE friendly_name END) AS friendly_name', 'platform', 'product', 'player', 'ip_address', 'media_type', 'rating_key', 'parent_rating_key', 'grandparent_rating_key', 'full_title', 'title', 'parent_title', 'grandparent_title', 'original_title', 'year', 'media_index', 'parent_media_index', 'thumb', 'parent_thumb', 'grandparent_thumb', 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \ (CASE WHEN (duration IS NULL OR duration = "") \ THEN 1.0 ELSE duration * 1.0 END) * 100) AS percent_complete', 'transcode_decision', 'NULL AS group_count', 'NULL AS group_ids', 'state', 'session_key' ] else: table_name_union = None custom_where_union = group_by_union = columns_union = [] try: query = data_tables.ssp_query(table_name='session_history', table_name_union=table_name_union, columns=columns, columns_union=columns_union, custom_where=custom_where, custom_where_union=custom_where_union, group_by=group_by, group_by_union=group_by_union, join_types=['LEFT OUTER JOIN', 'JOIN', 'JOIN'], join_tables=['users', 'session_history_metadata', 'session_history_media_info'], join_evals=[['session_history.user_id', 'users.user_id'], ['session_history.id', 'session_history_metadata.id'], ['session_history.id', 'session_history_media_info.id']], kwargs=kwargs) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_history: %s." % e) return {'recordsFiltered': 0, 'recordsTotal': 0, 'draw': 0, 'data': 'null', 'error': 'Unable to execute database query.'} history = query['result'] filter_duration = 0 total_duration = self.get_total_duration(custom_where=custom_where) watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT, 'episode': plexpy.CONFIG.TV_WATCHED_PERCENT, 'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT, 'photo': 0, 'clip': plexpy.CONFIG.TV_WATCHED_PERCENT } rows = [] for item in history: filter_duration += int(item['duration']) if item['media_type'] == 'episode' and item['parent_thumb']: thumb = item['parent_thumb'] elif item['media_type'] == 'episode': thumb = item['grandparent_thumb'] else: thumb = item['thumb'] if item['percent_complete'] >= watched_percent[item['media_type']]: watched_status = 1 elif item['percent_complete'] >= old_div(watched_percent[item['media_type']],2): watched_status = 0.5 else: watched_status = 0 # Rename Mystery platform names platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']) row = {'reference_id': item['reference_id'], 'id': item['id'], 'date': item['date'], 'started': item['started'], 'stopped': item['stopped'], 'duration': item['duration'], 'paused_counter': item['paused_counter'], 'user_id': item['user_id'], 'user': item['user'], 'friendly_name': item['friendly_name'], 'platform': platform, 'product': item['product'], 'player': item['player'], 'ip_address': item['ip_address'], 'media_type': item['media_type'], 'rating_key': item['rating_key'], 'parent_rating_key': item['parent_rating_key'], 'grandparent_rating_key': item['grandparent_rating_key'], 'full_title': item['full_title'], 'title': item['parent_title'], 'parent_title': item['parent_title'], 'grandparent_title': item['grandparent_title'], 'original_title': item['original_title'], 'year': item['year'], 'media_index': item['media_index'], 'parent_media_index': item['parent_media_index'], 'thumb': thumb, 'transcode_decision': item['transcode_decision'], 'percent_complete': int(round(item['percent_complete'])), 'watched_status': watched_status, 'group_count': item['group_count'], 'group_ids': item['group_ids'], 'state': item['state'], 'session_key': item['session_key'] } rows.append(row) dict = {'recordsFiltered': query['filteredCount'], 'recordsTotal': query['totalCount'], 'data': session.friendly_name_to_username(rows), 'draw': query['draw'], 'filter_duration': helpers.human_duration(filter_duration, sig='dhm'), 'total_duration': helpers.human_duration(total_duration, sig='dhm') } return dict def get_home_stats(self, grouping=None, time_range=30, stats_type='plays', stats_count=10, stats_cards=None): monitor_db = database.MonitorDatabase() if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES if stats_cards is None: stats_cards = plexpy.CONFIG.HOME_STATS_CARDS movie_watched_percent = plexpy.CONFIG.MOVIE_WATCHED_PERCENT tv_watched_percent = plexpy.CONFIG.TV_WATCHED_PERCENT music_watched_percent = plexpy.CONFIG.MUSIC_WATCHED_PERCENT group_by = 'session_history.reference_id' if grouping else 'session_history.id' sort_type = 'total_duration' if stats_type == 'duration' else 'total_plays' home_stats = [] for stat in stats_cards: if stat == 'top_movies': top_movies = [] try: query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \ 't.art, t.media_type, t.content_rating, t.labels, t.started, ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \ ' AND session_history.media_type = "movie" ' \ ' GROUP BY %s) AS t ' \ 'GROUP BY t.full_title, t.year ' \ 'ORDER BY %s DESC, started DESC ' \ 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_movies: %s." % e) return None for item in result: row = {'title': item['full_title'], 'total_plays': item['total_plays'], 'total_duration': item['total_duration'], 'users_watched': '', 'rating_key': item['rating_key'], 'last_play': item['last_watch'], 'grandparent_thumb': '', 'thumb': item['thumb'], 'art': item['art'], 'section_id': item['section_id'], 'media_type': item['media_type'], 'content_rating': item['content_rating'], 'labels': item['labels'].split(';') if item['labels'] else (), 'user': '', 'friendly_name': '', 'platform': '', 'platform': '', 'row_id': item['id'] } top_movies.append(row) home_stats.append({'stat_id': stat, 'stat_type': sort_type, 'stat_title': 'Most Watched Movies', 'rows': session.mask_session_info(top_movies)}) elif stat == 'popular_movies': popular_movies = [] try: query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \ 't.art, t.media_type, t.content_rating, t.labels, t.started, ' \ 'COUNT(DISTINCT t.user_id) AS users_watched, ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \ ' AND session_history.media_type = "movie" ' \ ' GROUP BY %s) AS t ' \ 'GROUP BY t.full_title, t.year ' \ 'ORDER BY users_watched DESC, %s DESC, started DESC ' \ 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: popular_movies: %s." % e) return None for item in result: row = {'title': item['full_title'], 'users_watched': item['users_watched'], 'rating_key': item['rating_key'], 'last_play': item['last_watch'], 'total_plays': item['total_plays'], 'grandparent_thumb': '', 'thumb': item['thumb'], 'art': item['art'], 'section_id': item['section_id'], 'media_type': item['media_type'], 'content_rating': item['content_rating'], 'labels': item['labels'].split(';') if item['labels'] else (), 'user': '', 'friendly_name': '', 'platform': '', 'row_id': item['id'] } popular_movies.append(row) home_stats.append({'stat_id': stat, 'stat_title': 'Most Popular Movies', 'rows': session.mask_session_info(popular_movies)}) elif stat == 'top_tv': top_tv = [] try: query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ 't.art, t.media_type, t.content_rating, t.labels, t.started, ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \ ' AND session_history.media_type = "episode" ' \ ' GROUP BY %s) AS t ' \ 'GROUP BY t.grandparent_title ' \ 'ORDER BY %s DESC, started DESC ' \ 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_tv: %s." % e) return None for item in result: row = {'title': item['grandparent_title'], 'total_plays': item['total_plays'], 'total_duration': item['total_duration'], 'users_watched': '', 'rating_key': item['grandparent_rating_key'], 'last_play': item['last_watch'], 'grandparent_thumb': item['grandparent_thumb'], 'thumb': item['grandparent_thumb'], 'art': item['art'], 'section_id': item['section_id'], 'media_type': item['media_type'], 'content_rating': item['content_rating'], 'labels': item['labels'].split(';') if item['labels'] else (), 'user': '', 'friendly_name': '', 'platform': '', 'row_id': item['id'] } top_tv.append(row) home_stats.append({'stat_id': stat, 'stat_type': sort_type, 'stat_title': 'Most Watched TV Shows', 'rows': session.mask_session_info(top_tv)}) elif stat == 'popular_tv': popular_tv = [] try: query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ 't.art, t.media_type, t.content_rating, t.labels, t.started, ' \ 'COUNT(DISTINCT t.user_id) AS users_watched, ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \ ' AND session_history.media_type = "episode" ' \ ' GROUP BY %s) AS t ' \ 'GROUP BY t.grandparent_title ' \ 'ORDER BY users_watched DESC, %s DESC, started DESC ' \ 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: popular_tv: %s." % e) return None for item in result: row = {'title': item['grandparent_title'], 'users_watched': item['users_watched'], 'rating_key': item['grandparent_rating_key'], 'last_play': item['last_watch'], 'total_plays': item['total_plays'], 'grandparent_thumb': item['grandparent_thumb'], 'thumb': item['grandparent_thumb'], 'art': item['art'], 'section_id': item['section_id'], 'media_type': item['media_type'], 'content_rating': item['content_rating'], 'labels': item['labels'].split(';') if item['labels'] else (), 'user': '', 'friendly_name': '', 'platform': '', 'row_id': item['id'] } popular_tv.append(row) home_stats.append({'stat_id': stat, 'stat_title': 'Most Popular TV Shows', 'rows': session.mask_session_info(popular_tv)}) elif stat == 'top_music': top_music = [] try: query = 'SELECT t.id, t.grandparent_title, t.original_title, ' \ 't.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ 't.art, t.media_type, t.content_rating, t.labels, t.started, ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \ ' AND session_history.media_type = "track" ' \ ' GROUP BY %s) AS t ' \ 'GROUP BY t.original_title, t.grandparent_title ' \ 'ORDER BY %s DESC, started DESC ' \ 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_music: %s." % e) return None for item in result: row = {'title': item['original_title'] or item['grandparent_title'], 'total_plays': item['total_plays'], 'total_duration': item['total_duration'], 'users_watched': '', 'rating_key': item['grandparent_rating_key'], 'last_play': item['last_watch'], 'grandparent_thumb': item['grandparent_thumb'], 'thumb': item['grandparent_thumb'], 'art': item['art'], 'section_id': item['section_id'], 'media_type': item['media_type'], 'content_rating': item['content_rating'], 'labels': item['labels'].split(';') if item['labels'] else (), 'user': '', 'friendly_name': '', 'platform': '', 'row_id': item['id'] } top_music.append(row) home_stats.append({'stat_id': stat, 'stat_type': sort_type, 'stat_title': 'Most Played Artists', 'rows': session.mask_session_info(top_music)}) elif stat == 'popular_music': popular_music = [] try: query = 'SELECT t.id, t.grandparent_title, t.original_title, ' \ 't.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ 't.art, t.media_type, t.content_rating, t.labels, t.started, ' \ 'COUNT(DISTINCT t.user_id) AS users_watched, ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \ ' AND session_history.media_type = "track" ' \ ' GROUP BY %s) AS t ' \ 'GROUP BY t.original_title, t.grandparent_title ' \ 'ORDER BY users_watched DESC, %s DESC, started DESC ' \ 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: popular_music: %s." % e) return None for item in result: row = {'title': item['original_title'] or item['grandparent_title'], 'users_watched': item['users_watched'], 'rating_key': item['grandparent_rating_key'], 'last_play': item['last_watch'], 'total_plays': item['total_plays'], 'grandparent_thumb': item['grandparent_thumb'], 'thumb': item['grandparent_thumb'], 'art': item['art'], 'section_id': item['section_id'], 'media_type': item['media_type'], 'content_rating': item['content_rating'], 'labels': item['labels'].split(';') if item['labels'] else (), 'user': '', 'friendly_name': '', 'platform': '', 'row_id': item['id'] } popular_music.append(row) home_stats.append({'stat_id': stat, 'stat_title': 'Most Popular Artists', 'rows': session.mask_session_info(popular_music)}) elif stat == 'top_users': top_users = [] try: query = 'SELECT t.user, t.user_id, t.user_thumb, t.custom_thumb, t.started, ' \ '(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \ ' AS friendly_name, ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d, users.thumb AS user_thumb, users.custom_avatar_url AS custom_thumb ' \ ' FROM session_history ' \ ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \ ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \ ' GROUP BY %s) AS t ' \ 'GROUP BY t.user_id ' \ 'ORDER BY %s DESC, started DESC ' \ 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_users: %s." % e) return None for item in result: if item['custom_thumb'] and item['custom_thumb'] != item['user_thumb']: user_thumb = item['custom_thumb'] elif item['user_thumb']: user_thumb = item['user_thumb'] else: user_thumb = common.DEFAULT_USER_THUMB row = {'user': item['user'], 'user_id': item['user_id'], 'friendly_name': item['friendly_name'], 'total_plays': item['total_plays'], 'total_duration': item['total_duration'], 'last_play': item['last_watch'], 'user_thumb': user_thumb, 'grandparent_thumb': '', 'art': '', 'users_watched': '', 'rating_key': '', 'title': '', 'platform': '', 'row_id': '' } top_users.append(row) home_stats.append({'stat_id': stat, 'stat_type': sort_type, 'stat_title': 'Most Active Users', 'rows': session.mask_session_info(top_users, mask_metadata=False)}) elif stat == 'top_platforms': top_platform = [] try: query = 'SELECT t.platform, t.started, ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \ ' GROUP BY %s) AS t ' \ 'GROUP BY t.platform ' \ 'ORDER BY %s DESC, started DESC ' \ 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_platforms: %s." % e) return None for item in result: # Rename Mystery platform names platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']) platform_name = next((v for k, v in common.PLATFORM_NAMES.items() if k in platform.lower()), 'default') row = {'total_plays': item['total_plays'], 'total_duration': item['total_duration'], 'last_play': item['last_watch'], 'platform': platform, 'platform_name': platform_name, 'title': '', 'thumb': '', 'grandparent_thumb': '', 'art': '', 'users_watched': '', 'rating_key': '', 'user': '', 'friendly_name': '', 'row_id': '' } top_platform.append(row) home_stats.append({'stat_id': stat, 'stat_type': sort_type, 'stat_title': 'Most Active Platforms', 'rows': session.mask_session_info(top_platform, mask_metadata=False)}) elif stat == 'last_watched': last_watched = [] try: query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.grandparent_thumb, ' \ 't.user, t.user_id, t.custom_avatar_url as user_thumb, t.player, t.section_id, ' \ 't.art, t.media_type, t.content_rating, t.labels, ' \ '(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \ ' AS friendly_name, ' \ 'MAX(t.started) AS last_watch, ' \ '((CASE WHEN t.view_offset IS NULL THEN 0.1 ELSE t.view_offset * 1.0 END) / ' \ ' (CASE WHEN t.duration IS NULL THEN 1.0 ELSE t.duration * 1.0 END) * 100) ' \ ' AS percent_complete ' \ 'FROM (SELECT *, MAX(session_history.id) FROM session_history ' \ ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \ ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \ ' AND (session_history.media_type = "movie" ' \ ' OR session_history_metadata.media_type = "episode") ' \ ' GROUP BY %s) AS t ' \ 'WHERE t.media_type == "movie" AND percent_complete >= %s ' \ ' OR t.media_type == "episode" AND percent_complete >= %s ' \ 'GROUP BY t.id ' \ 'ORDER BY last_watch DESC ' \ 'LIMIT %s' % (time_range, group_by, movie_watched_percent, tv_watched_percent, stats_count) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: last_watched: %s." % e) return None for item in result: if not item['grandparent_thumb'] or item['grandparent_thumb'] == '': thumb = item['thumb'] else: thumb = item['grandparent_thumb'] row = {'row_id': item['id'], 'user': item['user'], 'friendly_name': item['friendly_name'], 'user_id': item['user_id'], 'user_thumb': item['user_thumb'], 'title': item['full_title'], 'rating_key': item['rating_key'], 'thumb': thumb, 'grandparent_thumb': item['grandparent_thumb'], 'art': item['art'], 'section_id': item['section_id'], 'media_type': item['media_type'], 'content_rating': item['content_rating'], 'labels': item['labels'].split(';') if item['labels'] else (), 'last_watch': item['last_watch'], 'player': item['player'] } last_watched.append(row) home_stats.append({'stat_id': stat, 'stat_title': 'Recently Watched', 'rows': session.mask_session_info(last_watched)}) elif stat == 'most_concurrent': def calc_most_concurrent(title, result): ''' Function to calculate most concurrent streams Input: Stat title, SQLite query result Output: Dict {title, count, started, stopped} ''' times = [] for item in result: times.append({'time': str(item['started']) + 'B', 'count': 1}) times.append({'time': str(item['stopped']) + 'A', 'count': -1}) times = sorted(times, key=lambda k: k['time']) count = 0 last_count = 0 last_start = '' concurrent = {'title': title, 'count': 0, 'started': None, 'stopped': None } for d in times: if d['count'] == 1: count += d['count'] if count >= last_count: last_start = d['time'] else: if count >= last_count: last_count = count concurrent['count'] = count concurrent['started'] = last_start[:-1] concurrent['stopped'] = d['time'][:-1] count += d['count'] return concurrent most_concurrent = [] try: base_query = 'SELECT session_history.started, session_history.stopped ' \ 'FROM session_history ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'WHERE datetime(stopped, "unixepoch", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' % time_range title = 'Concurrent Streams' query = base_query result = monitor_db.select(query) if result: most_concurrent.append(calc_most_concurrent(title, result)) title = 'Concurrent Transcodes' query = base_query \ + 'AND session_history_media_info.transcode_decision = "transcode" ' result = monitor_db.select(query) if result: most_concurrent.append(calc_most_concurrent(title, result)) title = 'Concurrent Direct Streams' query = base_query \ + 'AND session_history_media_info.transcode_decision = "copy" ' result = monitor_db.select(query) if result: most_concurrent.append(calc_most_concurrent(title, result)) title = 'Concurrent Direct Plays' query = base_query \ + 'AND session_history_media_info.transcode_decision = "direct play" ' result = monitor_db.select(query) if result: most_concurrent.append(calc_most_concurrent(title, result)) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: most_concurrent: %s." % e) return None home_stats.append({'stat_id': stat, 'stat_title': 'Most Concurrent Streams', 'rows': most_concurrent}) return home_stats def get_library_stats(self, library_cards=[]): monitor_db = database.MonitorDatabase() if session.get_session_shared_libraries(): library_cards = session.get_session_shared_libraries() if 'first_run_wizard' in library_cards: return None library_stats = [] try: query = 'SELECT section_id, section_name, section_type, thumb AS library_thumb, ' \ 'custom_thumb_url AS custom_thumb, art, count, parent_count, child_count ' \ 'FROM library_sections ' \ 'WHERE section_id IN (%s) ' \ 'ORDER BY section_type, count DESC, parent_count DESC, child_count DESC ' % ','.join(library_cards) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_library_stats: %s." % e) return None for item in result: if item['custom_thumb'] and item['custom_thumb'] != item['library_thumb']: library_thumb = item['custom_thumb'] elif item['library_thumb']: library_thumb = item['library_thumb'] else: library_thumb = common.DEFAULT_COVER_THUMB library = {'section_id': item['section_id'], 'section_name': item['section_name'], 'section_type': item['section_type'], 'thumb': library_thumb, 'art': item['art'], 'count': item['count'], 'child_count': item['parent_count'], 'grandchild_count': item['child_count'] } library_stats.append(library) library_stats = {k: list(v) for k, v in groupby(library_stats, key=lambda x: x['section_type'])} return library_stats def get_stream_details(self, row_id=None, session_key=None): monitor_db = database.MonitorDatabase() user_cond = '' table = 'session_history' if row_id else 'sessions' if session.get_session_user_id(): user_cond = 'AND %s.user_id = %s ' % (table, session.get_session_user_id()) if row_id: query = 'SELECT bitrate, video_full_resolution, ' \ 'optimized_version, optimized_version_profile, optimized_version_title, ' \ 'synced_version, synced_version_profile, ' \ 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \ 'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \ 'stream_bitrate, stream_video_full_resolution, quality_profile, stream_container_decision, stream_container, ' \ 'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \ 'stream_video_framerate, ' \ 'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \ 'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \ 'transcode_hw_decoding, transcode_hw_encoding, ' \ 'video_decision, audio_decision, transcode_decision, width, height, container, ' \ 'transcode_container, transcode_video_codec, transcode_audio_codec, transcode_audio_channels, ' \ 'transcode_width, transcode_height, ' \ 'session_history_metadata.media_type, title, grandparent_title, original_title ' \ 'FROM session_history_media_info ' \ 'JOIN session_history ON session_history_media_info.id = session_history.id ' \ 'JOIN session_history_metadata ON session_history_media_info.id = session_history_metadata.id ' \ 'WHERE session_history_media_info.id = ? %s' % user_cond result = monitor_db.select(query, args=[row_id]) elif session_key: query = 'SELECT bitrate, video_full_resolution, ' \ 'optimized_version, optimized_version_profile, optimized_version_title, ' \ 'synced_version, synced_version_profile, ' \ 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \ 'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \ 'stream_bitrate, stream_video_full_resolution, quality_profile, stream_container_decision, stream_container, ' \ 'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \ 'stream_video_framerate, ' \ 'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \ 'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \ 'transcode_hw_decoding, transcode_hw_encoding, ' \ 'video_decision, audio_decision, transcode_decision, width, height, container, ' \ 'transcode_container, transcode_video_codec, transcode_audio_codec, transcode_audio_channels, ' \ 'transcode_width, transcode_height, ' \ 'media_type, title, grandparent_title, original_title ' \ 'FROM sessions ' \ 'WHERE session_key = ? %s' % user_cond result = monitor_db.select(query, args=[session_key]) else: return None stream_output = {} for item in result: pre_tautulli = 0 # For backwards compatibility. Pick one new Tautulli key to check and override with old values. if not item['stream_container']: item['stream_video_full_resolution'] = item['video_full_resolution'] item['stream_container'] = item['transcode_container'] or item['container'] item['stream_video_decision'] = item['video_decision'] item['stream_video_codec'] = item['transcode_video_codec'] or item['video_codec'] item['stream_video_width'] = item['transcode_width'] or item['width'] item['stream_video_height'] = item['transcode_height'] or item['height'] item['stream_audio_decision'] = item['audio_decision'] item['stream_audio_codec'] = item['transcode_audio_codec'] or item['audio_codec'] item['stream_audio_channels'] = item['transcode_audio_channels'] or item['audio_channels'] item['video_width'] = item['width'] item['video_height'] = item['height'] pre_tautulli = 1 stream_output = {'bitrate': item['bitrate'], 'video_full_resolution': item['video_full_resolution'], 'optimized_version': item['optimized_version'], 'optimized_version_profile': item['optimized_version_profile'], 'optimized_version_title': item['optimized_version_title'], 'synced_version': item['synced_version'], 'synced_version_profile': item['synced_version_profile'], 'container': item['container'], 'video_codec': item['video_codec'], 'video_bitrate': item['video_bitrate'], 'video_width': item['video_width'], 'video_height': item['video_height'], 'video_framerate': item['video_framerate'], 'aspect_ratio': item['aspect_ratio'], 'audio_codec': item['audio_codec'], 'audio_bitrate': item['audio_bitrate'], 'audio_channels': item['audio_channels'], 'subtitle_codec': item['subtitle_codec'], 'stream_bitrate': item['stream_bitrate'], 'stream_video_full_resolution': item['stream_video_full_resolution'], 'quality_profile': item['quality_profile'], 'stream_container_decision': item['stream_container_decision'], 'stream_container': item['stream_container'], 'stream_video_decision': item['stream_video_decision'], 'stream_video_codec': item['stream_video_codec'], 'stream_video_bitrate': item['stream_video_bitrate'], 'stream_video_width': item['stream_video_width'], 'stream_video_height': item['stream_video_height'], 'stream_video_framerate': item['stream_video_framerate'], 'stream_audio_decision': item['stream_audio_decision'], 'stream_audio_codec': item['stream_audio_codec'], 'stream_audio_bitrate': item['stream_audio_bitrate'], 'stream_audio_channels': item['stream_audio_channels'], 'subtitles': item['subtitles'], 'stream_subtitle_decision': item['stream_subtitle_decision'], 'stream_subtitle_codec': item['stream_subtitle_codec'], 'transcode_hw_decoding': item['transcode_hw_decoding'], 'transcode_hw_encoding': item['transcode_hw_encoding'], 'video_decision': item['video_decision'], 'audio_decision': item['audio_decision'], 'media_type': item['media_type'], 'title': item['title'], 'grandparent_title': item['grandparent_title'], 'original_title': item['original_title'], 'current_session': 1 if session_key else 0, 'pre_tautulli': pre_tautulli } stream_output = {k: v or '' for k, v in stream_output.items()} return stream_output def get_metadata_details(self, rating_key): monitor_db = database.MonitorDatabase() if rating_key: query = 'SELECT session_history_metadata.id, ' \ 'session_history_metadata.rating_key, session_history_metadata.parent_rating_key, ' \ 'session_history_metadata.grandparent_rating_key, session_history_metadata.title, ' \ 'session_history_metadata.parent_title, session_history_metadata.grandparent_title, ' \ 'session_history_metadata.original_title, session_history_metadata.full_title, ' \ 'library_sections.section_name, ' \ 'session_history_metadata.media_index, session_history_metadata.parent_media_index, ' \ 'session_history_metadata.section_id, session_history_metadata.thumb, ' \ 'session_history_metadata.parent_thumb, session_history_metadata.grandparent_thumb, ' \ 'session_history_metadata.art, session_history_metadata.media_type, session_history_metadata.year, ' \ 'session_history_metadata.originally_available_at, session_history_metadata.added_at, ' \ 'session_history_metadata.updated_at, session_history_metadata.last_viewed_at, ' \ 'session_history_metadata.content_rating, session_history_metadata.summary, ' \ 'session_history_metadata.tagline, session_history_metadata.rating, session_history_metadata.duration, ' \ 'session_history_metadata.guid, session_history_metadata.directors, session_history_metadata.writers, ' \ 'session_history_metadata.actors, session_history_metadata.genres, session_history_metadata.studio, ' \ 'session_history_metadata.labels, ' \ 'session_history_media_info.container, session_history_media_info.bitrate, ' \ 'session_history_media_info.video_codec, session_history_media_info.video_resolution, ' \ 'session_history_media_info.video_framerate, session_history_media_info.audio_codec, ' \ 'session_history_media_info.audio_channels ' \ 'FROM session_history_metadata ' \ 'JOIN library_sections ON session_history_metadata.section_id = library_sections.section_id ' \ 'JOIN session_history_media_info ON session_history_metadata.id = session_history_media_info.id ' \ 'WHERE session_history_metadata.rating_key = ? ' \ 'ORDER BY session_history_metadata.id DESC ' \ 'LIMIT 1' result = monitor_db.select(query=query, args=[rating_key]) else: result = [] metadata_list = [] for item in result: directors = item['directors'].split(';') if item['directors'] else [] writers = item['writers'].split(';') if item['writers'] else [] actors = item['actors'].split(';') if item['actors'] else [] genres = item['genres'].split(';') if item['genres'] else [] labels = item['labels'].split(';') if item['labels'] else [] media_info = [{'container': item['container'], 'bitrate': item['bitrate'], 'video_codec': item['video_codec'], 'video_resolution': item['video_resolution'], 'video_framerate': item['video_framerate'], 'audio_codec': item['audio_codec'], 'audio_channels': item['audio_channels'] }] metadata = {'media_type': item['media_type'], 'rating_key': item['rating_key'], 'parent_rating_key': item['parent_rating_key'], 'grandparent_rating_key': item['grandparent_rating_key'], 'grandparent_title': item['grandparent_title'], 'original_title': item['original_title'], 'parent_media_index': item['parent_media_index'], 'parent_title': item['parent_title'], 'media_index': item['media_index'], 'studio': item['studio'], 'title': item['title'], 'content_rating': item['content_rating'], 'summary': item['summary'], 'tagline': item['tagline'], 'rating': item['rating'], 'duration': item['duration'], 'year': item['year'], 'thumb': item['thumb'], 'parent_thumb': item['parent_thumb'], 'grandparent_thumb': item['grandparent_thumb'], 'art': item['art'], 'originally_available_at': item['originally_available_at'], 'added_at': item['added_at'], 'updated_at': item['updated_at'], 'last_viewed_at': item['last_viewed_at'], 'guid': item['guid'], 'directors': directors, 'writers': writers, 'actors': actors, 'genres': genres, 'labels': labels, 'library_name': item['section_name'], 'section_id': item['section_id'], 'media_info': media_info } metadata_list.append(metadata) filtered_metadata_list = session.filter_session_info(metadata_list, filter_key='section_id') if filtered_metadata_list: return filtered_metadata_list[0] else: return [] def get_total_duration(self, custom_where=None): monitor_db = database.MonitorDatabase() # Split up custom wheres if custom_where: where = 'WHERE ' + ' AND '.join([w[0] + ' = "' + w[1] + '"' for w in custom_where]) else: where = '' try: query = 'SELECT SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - ' \ 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS total_duration ' \ 'FROM session_history ' \ 'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ 'JOIN session_history_media_info ON session_history_media_info.id = session_history.id ' \ '%s ' % where result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_total_duration: %s." % e) return None total_duration = 0 for item in result: total_duration = item['total_duration'] return total_duration def get_session_ip(self, session_key=''): monitor_db = database.MonitorDatabase() ip_address = 'N/A' user_cond = '' if session.get_session_user_id(): user_cond = 'AND user_id = %s ' % session.get_session_user_id() if session_key: try: query = 'SELECT ip_address FROM sessions WHERE session_key = %d %s' % (int(session_key), user_cond) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_session_ip: %s." % e) return ip_address else: return ip_address for item in result: ip_address = item['ip_address'] return ip_address def get_img_info(self, img=None, rating_key=None, width=None, height=None, opacity=None, background=None, blur=None, fallback=None, order_by='', service=None): monitor_db = database.MonitorDatabase() img_info = [] where_params = [] args = [] if img is not None: where_params.append('img') args.append(img) if rating_key is not None: where_params.append('rating_key') args.append(rating_key) if width is not None: where_params.append('width') args.append(width) if height is not None: where_params.append('height') args.append(height) if opacity is not None: where_params.append('opacity') args.append(opacity) if background is not None: where_params.append('background') args.append(background) if blur is not None: where_params.append('blur') args.append(blur) if fallback is not None: where_params.append('fallback') args.append(fallback) where = '' if where_params: where = 'WHERE ' + ' AND '.join([w + ' = ?' for w in where_params]) if order_by: order_by = 'ORDER BY ' + order_by + ' DESC' if service == 'imgur': query = 'SELECT imgur_title AS img_title, imgur_url AS img_url FROM imgur_lookup ' \ 'JOIN image_hash_lookup ON imgur_lookup.img_hash = image_hash_lookup.img_hash ' \ '%s %s' % (where, order_by) elif service == 'cloudinary': query = 'SELECT cloudinary_title AS img_title, cloudinary_url AS img_url FROM cloudinary_lookup ' \ 'JOIN image_hash_lookup ON cloudinary_lookup.img_hash = image_hash_lookup.img_hash ' \ '%s %s' % (where, order_by) else: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_img_info: " "service not provided.") return img_info try: img_info = monitor_db.select(query, args=args) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_img_info: %s." % e) return img_info def set_img_info(self, img_hash=None, img_title=None, img_url=None, delete_hash=None, service=None): monitor_db = database.MonitorDatabase() keys = {'img_hash': img_hash} if service == 'imgur': table = 'imgur_lookup' values = {'imgur_title': img_title, 'imgur_url': img_url, 'delete_hash': delete_hash} elif service == 'cloudinary': table = 'cloudinary_lookup' values = {'cloudinary_title': img_title, 'cloudinary_url': img_url} else: logger.warn("Tautulli DataFactory :: Unable to execute database query for set_img_info: " "service not provided.") return monitor_db.upsert(table, key_dict=keys, value_dict=values) def delete_img_info(self, rating_key=None, service='', delete_all=False): monitor_db = database.MonitorDatabase() if not delete_all: service = helpers.get_img_service() if not rating_key and not delete_all: logger.error("Tautulli DataFactory :: Unable to delete hosted images: rating_key not provided.") return False where = '' args = [] log_msg = '' if rating_key: where = 'WHERE rating_key = ?' args = [rating_key] log_msg = ' for rating_key %s' % rating_key if service.lower() == 'imgur': # Delete from Imgur query = 'SELECT imgur_title, delete_hash, fallback FROM imgur_lookup ' \ 'JOIN image_hash_lookup ON imgur_lookup.img_hash = image_hash_lookup.img_hash %s' % where results = monitor_db.select(query, args=args) for imgur_info in results: if imgur_info['delete_hash']: helpers.delete_from_imgur(delete_hash=imgur_info['delete_hash'], img_title=imgur_info['imgur_title'], fallback=imgur_info['fallback']) logger.info("Tautulli DataFactory :: Deleting Imgur info%s from the database." % log_msg) result = monitor_db.action('DELETE FROM imgur_lookup WHERE img_hash ' 'IN (SELECT img_hash FROM image_hash_lookup %s)' % where, args) elif service.lower() == 'cloudinary': # Delete from Cloudinary query = 'SELECT cloudinary_title, rating_key, fallback FROM cloudinary_lookup ' \ 'JOIN image_hash_lookup ON cloudinary_lookup.img_hash = image_hash_lookup.img_hash %s ' \ 'GROUP BY rating_key' % where results = monitor_db.select(query, args=args) if delete_all: helpers.delete_from_cloudinary(delete_all=delete_all) else: for cloudinary_info in results: helpers.delete_from_cloudinary(rating_key=cloudinary_info['rating_key']) logger.info("Tautulli DataFactory :: Deleting Cloudinary info%s from the database." % log_msg) result = monitor_db.action('DELETE FROM cloudinary_lookup WHERE img_hash ' 'IN (SELECT img_hash FROM image_hash_lookup %s)' % where, args) else: logger.error("Tautulli DataFactory :: Unable to delete hosted images: invalid service '%s' provided." % service) return service def get_poster_info(self, rating_key='', metadata=None, service=None): poster_key = '' if str(rating_key).isdigit(): poster_key = rating_key elif metadata: if metadata['media_type'] in ('movie', 'show', 'artist', 'collection'): poster_key = metadata['rating_key'] elif metadata['media_type'] in ('season', 'album'): poster_key = metadata['rating_key'] elif metadata['media_type'] in ('episode', 'track'): poster_key = metadata['parent_rating_key'] poster_info = {} if poster_key: service = service or helpers.get_img_service() if service: img_info = self.get_img_info(rating_key=poster_key, order_by='height', fallback='poster', service=service) if img_info: poster_info = {'poster_title': img_info[0]['img_title'], 'poster_url': img_info[0]['img_url'], 'img_service': service.capitalize()} return poster_info def get_lookup_info(self, rating_key='', metadata=None): monitor_db = database.MonitorDatabase() lookup_key = '' if str(rating_key).isdigit(): lookup_key = rating_key elif metadata: if metadata['media_type'] in ('movie', 'show', 'artist', 'album', 'track'): lookup_key = metadata['rating_key'] elif metadata['media_type'] == 'season': lookup_key = metadata['parent_rating_key'] elif metadata['media_type'] == 'episode': lookup_key = metadata['grandparent_rating_key'] lookup_info = {'tvmaze_id': '', 'themoviedb_id': '', 'musizbrainz_id': ''} if lookup_key: try: query = 'SELECT tvmaze_id FROM tvmaze_lookup ' \ 'WHERE rating_key = ?' tvmaze_info = monitor_db.select_single(query, args=[lookup_key]) if tvmaze_info: lookup_info['tvmaze_id'] = tvmaze_info['tvmaze_id'] query = 'SELECT themoviedb_id FROM themoviedb_lookup ' \ 'WHERE rating_key = ?' themoviedb_info = monitor_db.select_single(query, args=[lookup_key]) if themoviedb_info: lookup_info['themoviedb_id'] = themoviedb_info['themoviedb_id'] query = 'SELECT musicbrainz_id FROM musicbrainz_lookup ' \ 'WHERE rating_key = ?' musicbrainz_info = monitor_db.select_single(query, args=[lookup_key]) if musicbrainz_info: lookup_info['musicbrainz_id'] = musicbrainz_info['musicbrainz_id'] except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_lookup_info: %s." % e) return lookup_info def delete_lookup_info(self, rating_key='', title=''): monitor_db = database.MonitorDatabase() if rating_key: logger.info("Tautulli DataFactory :: Deleting lookup info for '%s' (rating_key %s) from the database." % (title, rating_key)) result_tvmaze = monitor_db.action('DELETE FROM tvmaze_lookup WHERE rating_key = ?', [rating_key]) result_themoviedb = monitor_db.action('DELETE FROM themoviedb_lookup WHERE rating_key = ?', [rating_key]) result_musicbrainz = monitor_db.action('DELETE FROM musicbrainz_lookup WHERE rating_key = ?', [rating_key]) return True if (result_tvmaze or result_themoviedb or result_musicbrainz) else False def get_search_query(self, rating_key=''): monitor_db = database.MonitorDatabase() if rating_key: query = 'SELECT rating_key, parent_rating_key, grandparent_rating_key, title, parent_title, grandparent_title, ' \ 'media_index, parent_media_index, year, media_type ' \ 'FROM session_history_metadata ' \ 'WHERE rating_key = ? ' \ 'OR parent_rating_key = ? ' \ 'OR grandparent_rating_key = ? ' \ 'LIMIT 1' result = monitor_db.select(query=query, args=[rating_key, rating_key, rating_key]) else: result = [] query = {} query_string = None media_type = None for item in result: title = item['title'] parent_title = item['parent_title'] grandparent_title = item['grandparent_title'] media_index = item['media_index'] parent_media_index = item['parent_media_index'] year = item['year'] if str(item['rating_key']) == rating_key: query_string = item['title'] media_type = item['media_type'] elif str(item['parent_rating_key']) == rating_key: if item['media_type'] == 'episode': query_string = item['grandparent_title'] media_type = 'season' elif item['media_type'] == 'track': query_string = item['parent_title'] media_type = 'album' elif str(item['grandparent_rating_key']) == rating_key: if item['media_type'] == 'episode': query_string = item['grandparent_title'] media_type = 'show' elif item['media_type'] == 'track': query_string = item['grandparent_title'] media_type = 'artist' if query_string and media_type: query = {'query_string': query_string, 'title': title, 'parent_title': parent_title, 'grandparent_title': grandparent_title, 'media_index': media_index, 'parent_media_index': parent_media_index, 'year': year, 'media_type': media_type, 'rating_key': rating_key } else: return None return query def get_rating_keys_list(self, rating_key='', media_type=''): monitor_db = database.MonitorDatabase() if media_type == 'movie': key_list = {0: {'rating_key': int(rating_key)}} return key_list if media_type == 'artist' or media_type == 'album' or media_type == 'track': match_type = 'title' else: match_type = 'index' # Get the grandparent rating key try: query = 'SELECT rating_key, parent_rating_key, grandparent_rating_key ' \ 'FROM session_history_metadata ' \ 'WHERE rating_key = ? ' \ 'OR parent_rating_key = ? ' \ 'OR grandparent_rating_key = ? ' \ 'LIMIT 1' result = monitor_db.select(query=query, args=[rating_key, rating_key, rating_key]) grandparent_rating_key = result[0]['grandparent_rating_key'] except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_rating_keys_list: %s." % e) return {} query = 'SELECT rating_key, parent_rating_key, grandparent_rating_key, title, parent_title, grandparent_title, ' \ 'media_index, parent_media_index ' \ 'FROM session_history_metadata ' \ 'WHERE {0} = ? ' \ 'GROUP BY {1} ' \ 'ORDER BY {1} DESC ' # get grandparent_rating_keys grandparents = {} result = monitor_db.select(query=query.format('grandparent_rating_key', 'grandparent_rating_key'), args=[grandparent_rating_key]) for item in result: # get parent_rating_keys parents = {} result = monitor_db.select(query=query.format('grandparent_rating_key', 'parent_rating_key'), args=[item['grandparent_rating_key']]) for item in result: # get rating_keys children = {} result = monitor_db.select(query=query.format('parent_rating_key', 'rating_key'), args=[item['parent_rating_key']]) for item in result: key = item['media_index'] if item['media_index'] else item['title'] children.update({key: {'rating_key': item['rating_key']}}) key = item['parent_media_index'] if match_type == 'index' else item['parent_title'] parents.update({key: {'rating_key': item['parent_rating_key'], 'children': children} }) key = 0 if match_type == 'index' else item['grandparent_title'] grandparents.update({key: {'rating_key': item['grandparent_rating_key'], 'children': parents} }) key_list = grandparents return key_list def delete_session_history_rows(self, row_id=None): monitor_db = database.MonitorDatabase() if row_id.isdigit(): logger.info("Tautulli DataFactory :: Deleting row id %s from the session history database." % row_id) session_history_del = \ monitor_db.action('DELETE FROM session_history WHERE id = ?', [row_id]) session_history_media_info_del = \ monitor_db.action('DELETE FROM session_history_media_info WHERE id = ?', [row_id]) session_history_metadata_del = \ monitor_db.action('DELETE FROM session_history_metadata WHERE id = ?', [row_id]) return 'Deleted rows %s.' % row_id else: return 'Unable to delete rows. Input row not valid.' def update_metadata(self, old_key_list='', new_key_list='', media_type=''): pms_connect = pmsconnect.PmsConnect() monitor_db = database.MonitorDatabase() # function to map rating keys pairs def get_pairs(old, new): pairs = {} for k, v in old.items(): if k in new: pairs.update({v['rating_key']: new[k]['rating_key']}) if 'children' in old[k]: pairs.update(get_pairs(old[k]['children'], new[k]['children'])) return pairs # map rating keys pairs mapping = {} if old_key_list and new_key_list: mapping = get_pairs(old_key_list, new_key_list) if mapping: logger.info("Tautulli DataFactory :: Updating metadata in the database.") for old_key, new_key in mapping.items(): metadata = pms_connect.get_metadata_details(new_key) if metadata: if metadata['media_type'] == 'show' or metadata['media_type'] == 'artist': # check grandparent_rating_key (2 tables) monitor_db.action('UPDATE session_history SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?', [new_key, old_key]) monitor_db.action('UPDATE session_history_metadata SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?', [new_key, old_key]) elif metadata['media_type'] == 'season' or metadata['media_type'] == 'album': # check parent_rating_key (2 tables) monitor_db.action('UPDATE session_history SET parent_rating_key = ? WHERE parent_rating_key = ?', [new_key, old_key]) monitor_db.action('UPDATE session_history_metadata SET parent_rating_key = ? WHERE parent_rating_key = ?', [new_key, old_key]) else: # check rating_key (2 tables) monitor_db.action('UPDATE session_history SET rating_key = ? WHERE rating_key = ?', [new_key, old_key]) monitor_db.action('UPDATE session_history_media_info SET rating_key = ? WHERE rating_key = ?', [new_key, old_key]) # update session_history_metadata table self.update_metadata_details(old_key, new_key, metadata) return 'Updated metadata in database.' else: return 'Unable to update metadata in database. No changes were made.' def update_metadata_details(self, old_rating_key='', new_rating_key='', metadata=None): if metadata: # Create full_title if metadata['media_type'] == 'episode': full_title = '%s - %s' % (metadata['grandparent_title'], metadata['title']) elif metadata['media_type'] == 'track': full_title = '%s - %s' % (metadata['title'], metadata['original_title'] or metadata['grandparent_title']) else: full_title = metadata['title'] directors = ";".join(metadata['directors']) writers = ";".join(metadata['writers']) actors = ";".join(metadata['actors']) genres = ";".join(metadata['genres']) labels = ";".join(metadata['labels']) #logger.info("Tautulli DataFactory :: Updating metadata in the database for rating key: %s." % new_rating_key) monitor_db = database.MonitorDatabase() # Update the session_history_metadata table query = 'UPDATE session_history_metadata SET rating_key = ?, parent_rating_key = ?, ' \ 'grandparent_rating_key = ?, title = ?, parent_title = ?, grandparent_title = ?, ' \ 'original_title = ?, full_title = ?, ' \ 'media_index = ?, parent_media_index = ?, section_id = ?, thumb = ?, parent_thumb = ?, ' \ 'grandparent_thumb = ?, art = ?, media_type = ?, year = ?, originally_available_at = ?, ' \ 'added_at = ?, updated_at = ?, last_viewed_at = ?, content_rating = ?, summary = ?, ' \ 'tagline = ?, rating = ?, duration = ?, guid = ?, directors = ?, writers = ?, actors = ?, ' \ 'genres = ?, studio = ?, labels = ? ' \ 'WHERE rating_key = ?' args = [metadata['rating_key'], metadata['parent_rating_key'], metadata['grandparent_rating_key'], metadata['title'], metadata['parent_title'], metadata['grandparent_title'], metadata['original_title'], full_title, metadata['media_index'], metadata['parent_media_index'], metadata['section_id'], metadata['thumb'], metadata['parent_thumb'], metadata['grandparent_thumb'], metadata['art'], metadata['media_type'], metadata['year'], metadata['originally_available_at'], metadata['added_at'], metadata['updated_at'], metadata['last_viewed_at'], metadata['content_rating'], metadata['summary'], metadata['tagline'], metadata['rating'], metadata['duration'], metadata['guid'], directors, writers, actors, genres, metadata['studio'], labels, old_rating_key] monitor_db.action(query=query, args=args) def get_notification_log(self, kwargs=None): data_tables = datatables.DataTables() columns = ['notify_log.id', 'notify_log.timestamp', 'notify_log.session_key', 'notify_log.rating_key', 'notify_log.user_id', 'notify_log.user', 'notify_log.notifier_id', 'notify_log.agent_id', 'notify_log.agent_name', 'notify_log.notify_action', 'notify_log.subject_text', 'notify_log.body_text', 'notify_log.success' ] try: query = data_tables.ssp_query(table_name='notify_log', columns=columns, custom_where=[], group_by=[], join_types=[], join_tables=[], join_evals=[], kwargs=kwargs) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_notification_log: %s." % e) return {'recordsFiltered': 0, 'recordsTotal': 0, 'draw': 0, 'data': 'null', 'error': 'Unable to execute database query.'} notifications = query['result'] rows = [] for item in notifications: if item['body_text']: body_text = item['body_text'].replace('\r\n', '
').replace('\n', '
') else: body_text = '' row = {'id': item['id'], 'timestamp': item['timestamp'], 'session_key': item['session_key'], 'rating_key': item['rating_key'], 'user_id': item['user_id'], 'user': item['user'], 'notifier_id': item['notifier_id'], 'agent_id': item['agent_id'], 'agent_name': item['agent_name'], 'notify_action': item['notify_action'], 'subject_text': item['subject_text'], 'body_text': body_text, 'success': item['success'] } rows.append(row) dict = {'recordsFiltered': query['filteredCount'], 'recordsTotal': query['totalCount'], 'data': rows, 'draw': query['draw'] } return dict def delete_notification_log(self): monitor_db = database.MonitorDatabase() try: logger.info("Tautulli DataFactory :: Clearing notification logs from database.") monitor_db.action('DELETE FROM notify_log') monitor_db.action('VACUUM') return True except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for delete_notification_log: %s." % e) return False def get_newsletter_log(self, kwargs=None): data_tables = datatables.DataTables() columns = ['newsletter_log.id', 'newsletter_log.timestamp', 'newsletter_log.newsletter_id', 'newsletter_log.agent_id', 'newsletter_log.agent_name', 'newsletter_log.notify_action', 'newsletter_log.subject_text', 'newsletter_log.body_text', 'newsletter_log.start_date', 'newsletter_log.end_date', 'newsletter_log.uuid', 'newsletter_log.success' ] try: query = data_tables.ssp_query(table_name='newsletter_log', columns=columns, custom_where=[], group_by=[], join_types=[], join_tables=[], join_evals=[], kwargs=kwargs) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_newsletter_log: %s." % e) return {'recordsFiltered': 0, 'recordsTotal': 0, 'draw': 0, 'data': 'null', 'error': 'Unable to execute database query.'} newsletters = query['result'] rows = [] for item in newsletters: row = {'id': item['id'], 'timestamp': item['timestamp'], 'newsletter_id': item['newsletter_id'], 'agent_id': item['agent_id'], 'agent_name': item['agent_name'], 'notify_action': item['notify_action'], 'subject_text': item['subject_text'], 'body_text': item['body_text'], 'start_date': item['start_date'], 'end_date': item['end_date'], 'uuid': item['uuid'], 'success': item['success'] } rows.append(row) dict = {'recordsFiltered': query['filteredCount'], 'recordsTotal': query['totalCount'], 'data': rows, 'draw': query['draw'] } return dict def delete_newsletter_log(self): monitor_db = database.MonitorDatabase() try: logger.info("Tautulli DataFactory :: Clearing newsletter logs from database.") monitor_db.action('DELETE FROM newsletter_log') monitor_db.action('VACUUM') return True except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for delete_newsletter_log: %s." % e) return False def get_user_devices(self, user_id=''): monitor_db = database.MonitorDatabase() if user_id: try: query = 'SELECT machine_id FROM session_history WHERE user_id = ? GROUP BY machine_id' result = monitor_db.select(query=query, args=[user_id]) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_user_devices: %s." % e) return [] else: return [] return [d['machine_id'] for d in result] def get_recently_added_item(self, rating_key=''): monitor_db = database.MonitorDatabase() if rating_key: try: query = 'SELECT * FROM recently_added WHERE rating_key = ?' result = monitor_db.select(query=query, args=[rating_key]) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_recently_added_item: %s." % e) return [] else: return [] return result def set_recently_added_item(self, rating_key=''): monitor_db = database.MonitorDatabase() pms_connect = pmsconnect.PmsConnect() metadata = pms_connect.get_metadata_details(rating_key) keys = {'rating_key': metadata['rating_key']} values = {'added_at': metadata['added_at'], 'section_id': metadata['section_id'], 'parent_rating_key': metadata['parent_rating_key'], 'grandparent_rating_key': metadata['grandparent_rating_key'], 'media_type': metadata['media_type'], 'media_info': json.dumps(metadata['media_info']) } try: monitor_db.upsert(table_name='recently_added', key_dict=keys, value_dict=values) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for set_recently_added_item: %s." % e) return False return True