Merge branch 'nightly' into python3

# Conflicts:
#	data/interfaces/default/current_activity_instance.html
#	plexpy/activity_handler.py
#	plexpy/graphs.py
#	plexpy/helpers.py
#	plexpy/pmsconnect.py
#	plexpy/version.py
#	plexpy/webserve.py
This commit is contained in:
JonnyWong16 2020-02-29 15:26:33 -08:00
commit 8d5bc88fd9
75 changed files with 2227 additions and 794 deletions

View file

@ -590,6 +590,7 @@ def dbcheck():
'media_index INTEGER, parent_media_index INTEGER, '
'thumb TEXT, parent_thumb TEXT, grandparent_thumb TEXT, year INTEGER, '
'parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
'originally_available_at TEXT, added_at INTEGER, guid TEXT, '
'view_offset INTEGER DEFAULT 0, duration INTEGER, video_decision TEXT, audio_decision TEXT, '
'transcode_decision TEXT, container TEXT, bitrate INTEGER, width INTEGER, height INTEGER, '
'video_codec TEXT, video_bitrate INTEGER, video_resolution TEXT, video_width INTEGER, video_height INTEGER, '
@ -609,7 +610,8 @@ def dbcheck():
'transcode_hw_decoding INTEGER, transcode_hw_encoding INTEGER, '
'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT, '
'synced_version INTEGER, synced_version_profile TEXT, '
'live INTEGER, live_uuid TEXT, secure INTEGER, relayed INTEGER, '
'live INTEGER, live_uuid TEXT, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT, '
'secure INTEGER, relayed INTEGER, '
'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, watched INTEGER DEFAULT 0, '
'write_attempts INTEGER DEFAULT 0, raw_stream_info TEXT)'
)
@ -657,7 +659,7 @@ def dbcheck():
'art TEXT, media_type TEXT, year INTEGER, originally_available_at TEXT, added_at INTEGER, updated_at INTEGER, '
'last_viewed_at INTEGER, content_rating TEXT, summary TEXT, tagline TEXT, rating TEXT, '
'duration INTEGER DEFAULT 0, guid TEXT, directors TEXT, writers TEXT, actors TEXT, genres TEXT, studio TEXT, '
'labels TEXT)'
'labels TEXT, live INTEGER DEFAULT 0, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT)'
)
# users table :: This table keeps record of the friends list
@ -1225,6 +1227,42 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN stream_video_dynamic_range TEXT'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT channel_identifier FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN channel_call_sign TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN channel_identifier TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN channel_thumb TEXT'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT originally_available_at FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN originally_available_at TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN added_at INTEGER'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT guid FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN guid TEXT'
)
# Upgrade session_history table from earlier versions
try:
c_db.execute('SELECT reference_id FROM session_history')
@ -1332,6 +1370,24 @@ def dbcheck():
'ALTER TABLE session_history_metadata ADD COLUMN original_title TEXT'
)
# Upgrade session_history_metadata table from earlier versions
try:
c_db.execute('SELECT live FROM session_history_metadata')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table session_history_metadata.")
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN live INTEGER DEFAULT 0'
)
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN channel_call_sign TEXT'
)
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN channel_identifier TEXT'
)
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN channel_thumb TEXT'
)
# Upgrade session_history_media_info table from earlier versions
try:
c_db.execute('SELECT transcode_decision FROM session_history_media_info')
@ -1574,6 +1630,15 @@ def dbcheck():
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN stream_video_dynamic_range TEXT '
)
result = c_db.execute('SELECT * FROM session_history_media_info '
'WHERE video_dynamic_range = "SDR" AND stream_video_dynamic_range = "HDR"').fetchone()
if result:
c_db.execute(
'UPDATE session_history_media_info SET stream_video_dynamic_range = "SDR" '
'WHERE video_dynamic_range = "SDR" AND stream_video_dynamic_range = "HDR"'
)
# Upgrade users table from earlier versions
try:
c_db.execute('SELECT do_notify FROM users')

View file

@ -63,9 +63,19 @@ class ActivityHandler(object):
return None
def get_live_session(self):
def get_metadata(self, skip_cache=False):
cache_key = None if skip_cache else self.get_session_key()
pms_connect = pmsconnect.PmsConnect()
session_list = pms_connect.get_current_activity()
metadata = pms_connect.get_metadata_details(rating_key=self.get_rating_key(), cache_key=cache_key)
if metadata:
return metadata
return None
def get_live_session(self, skip_cache=False):
pms_connect = pmsconnect.PmsConnect()
session_list = pms_connect.get_current_activity(skip_cache=skip_cache)
if session_list:
for session in session_list['sessions']:
@ -99,7 +109,7 @@ class ActivityHandler(object):
def on_start(self):
if self.is_valid_session():
session = self.get_live_session()
session = self.get_live_session(skip_cache=True)
if not session:
return
@ -112,9 +122,9 @@ class ActivityHandler(object):
if not session:
return
logger.debug("Tautulli ActivityHandler :: Session %s started by user %s (%s) with ratingKey %s (%s)."
logger.debug("Tautulli ActivityHandler :: Session %s started by user %s (%s) with ratingKey %s (%s)%s."
% (str(session['session_key']), str(session['user_id']), session['username'],
str(session['rating_key']), session['full_title']))
str(session['rating_key']), session['full_title'], '[Live TV]' if session['live'] else ''))
plexpy.NOTIFY_QUEUE.put({'stream_data': session.copy(), 'notify_action': 'on_play'})
@ -274,11 +284,20 @@ class ActivityHandler(object):
last_transcode_key = db_session['transcode_key'].split('/')[-1]
last_paused = db_session['last_paused']
last_rating_key_websocket = db_session['rating_key_websocket']
last_guid = db_session['guid']
this_guid = last_guid
# Check guid for live TV metadata every 60 seconds
if db_session['live'] and int(time.time()) - db_session['stopped'] > 60:
metadata = self.get_metadata(skip_cache=True)
if metadata:
this_guid = metadata['guid']
# Make sure the same item is being played
if this_rating_key == last_rating_key \
or this_rating_key == last_rating_key_websocket \
or this_live_uuid == last_live_uuid:
if (this_rating_key == last_rating_key
or this_rating_key == last_rating_key_websocket
or this_live_uuid == last_live_uuid) \
and this_guid == last_guid:
# Update the session state and viewOffset
if this_state == 'playing':
# Update the session in our temp session table

View file

@ -66,6 +66,9 @@ class ActivityProcessor(object):
'platform': session.get('platform', ''),
'parent_rating_key': session.get('parent_rating_key', ''),
'grandparent_rating_key': session.get('grandparent_rating_key', ''),
'originally_available_at': session.get('originally_available_at', ''),
'added_at': session.get('added_at', ''),
'guid': session.get('guid', ''),
'view_offset': session.get('view_offset', ''),
'duration': session.get('duration', ''),
'video_decision': session.get('video_decision', ''),
@ -130,6 +133,9 @@ class ActivityProcessor(object):
'relayed': session.get('relayed', 0),
'rating_key_websocket': session.get('rating_key_websocket', ''),
'raw_stream_info': json.dumps(session),
'channel_call_sign': session.get('channel_call_sign', ''),
'channel_identifier': session.get('channel_identifier', ''),
'channel_thumb': session.get('channel_thumb', ''),
'stopped': int(time.time())
}
@ -148,6 +154,10 @@ class ActivityProcessor(object):
timestamp = {'started': started}
self.db.upsert('sessions', timestamp, keys)
# Add Live TV library if it hasn't been added
if values['live']:
libraries.add_live_tv_library()
return True
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0):
@ -240,7 +250,12 @@ class ActivityProcessor(object):
if not is_import:
logger.debug("Tautulli ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
metadata = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
if session['live']:
metadata = pms_connect.get_metadata_details(rating_key=str(session['rating_key']),
cache_key=session['session_key'],
return_cache=True)
else:
metadata = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
if not metadata:
return False
else:
@ -284,38 +299,57 @@ class ActivityProcessor(object):
# % session['session_key'])
self.db.upsert(table_name='session_history', key_dict=keys, value_dict=values)
# Check if we should group the session, select the last two rows from the user
query = 'SELECT id, rating_key, view_offset, user_id, reference_id FROM session_history ' \
'WHERE user_id = ? AND rating_key = ? ORDER BY id DESC LIMIT 2 '
args = [session['user_id'], session['rating_key']]
result = self.db.select(query=query, args=args)
new_session = prev_session = None
prev_progress_percent = media_watched_percent = 0
# Get the last insert row id
last_id = self.db.last_insert_id()
new_session = prev_session = None
prev_progress_percent = media_watched_percent = 0
if len(result) > 1:
new_session = {'id': result[0]['id'],
'rating_key': result[0]['rating_key'],
'view_offset': result[0]['view_offset'],
'user_id': result[0]['user_id'],
'reference_id': result[0]['reference_id']}
if session['live']:
# Check if we should group the session, select the last guid from the user
query = 'SELECT session_history.id, session_history_metadata.guid, session_history.reference_id ' \
'FROM session_history ' \
'JOIN session_history_metadata ON session_history.id == session_history_metadata.id ' \
'WHERE session_history.user_id = ? ORDER BY session_history.id DESC LIMIT 1 '
prev_session = {'id': result[1]['id'],
'rating_key': result[1]['rating_key'],
'view_offset': result[1]['view_offset'],
'user_id': result[1]['user_id'],
'reference_id': result[1]['reference_id']}
args = [session['user_id']]
watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT,
'episode': plexpy.CONFIG.TV_WATCHED_PERCENT,
'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT
}
prev_progress_percent = helpers.get_percent(prev_session['view_offset'], session['duration'])
media_watched_percent = watched_percent.get(session['media_type'], 0)
result = self.db.select(query=query, args=args)
if len(result) > 0:
new_session = {'id': last_id,
'guid': metadata['guid'],
'reference_id': last_id}
prev_session = {'id': result[0]['id'],
'guid': result[0]['guid'],
'reference_id': result[0]['reference_id']}
else:
# Check if we should group the session, select the last two rows from the user
query = 'SELECT id, rating_key, view_offset, reference_id FROM session_history ' \
'WHERE user_id = ? AND rating_key = ? ORDER BY id DESC LIMIT 2 '
args = [session['user_id'], session['rating_key']]
result = self.db.select(query=query, args=args)
if len(result) > 1:
new_session = {'id': result[0]['id'],
'rating_key': result[0]['rating_key'],
'view_offset': result[0]['view_offset'],
'reference_id': result[0]['reference_id']}
prev_session = {'id': result[1]['id'],
'rating_key': result[1]['rating_key'],
'view_offset': result[1]['view_offset'],
'reference_id': result[1]['reference_id']}
watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT,
'episode': plexpy.CONFIG.TV_WATCHED_PERCENT,
'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT
}
prev_progress_percent = helpers.get_percent(prev_session['view_offset'], session['duration'])
media_watched_percent = watched_percent.get(session['media_type'], 0)
query = 'UPDATE session_history SET reference_id = ? WHERE id = ? '
@ -326,7 +360,8 @@ class ActivityProcessor(object):
if prev_session is None and new_session is None:
args = [last_id, last_id]
elif prev_progress_percent < media_watched_percent and \
prev_session['view_offset'] <= new_session['view_offset']:
prev_session['view_offset'] <= new_session['view_offset'] or \
session['live'] and prev_session['guid'] == new_session['guid']:
args = [prev_session['reference_id'], new_session['id']]
else:
args = [new_session['id'], new_session['id']]
@ -458,7 +493,11 @@ class ActivityProcessor(object):
'actors': actors,
'genres': genres,
'studio': metadata['studio'],
'labels': labels
'labels': labels,
'live': session['live'],
'channel_call_sign': media_info.get('channel_call_sign', ''),
'channel_identifier': media_info.get('channel_identifier', ''),
'channel_thumb': media_info.get('channel_thumb', '')
}
# logger.debug("Tautulli ActivityProcessor :: Writing sessionKey %s session_history_metadata transaction..."

View file

@ -120,7 +120,7 @@ class API2(object):
# Allow override for the api.
self._api_out_type = kwargs.pop('out_type', 'json')
if 'app' in kwargs and kwargs.pop('app') == 'true':
if 'app' in kwargs and helpers.bool_true(kwargs.pop('app')):
self._api_app = True
if plexpy.CONFIG.API_ENABLED and not self._api_msg or self._api_cmd in ('get_apikey', 'docs', 'docs_md'):

View file

@ -41,11 +41,26 @@ DEFAULT_USER_THUMB = "interfaces/default/images/gravatar-default-80x80.png"
DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png"
DEFAULT_ART = "interfaces/default/images/art.png"
DEFAULT_LIVE_TV_POSTER_THUMB = "interfaces/default/images/poster-live.png"
DEFAULT_LIVE_TV_ART = "interfaces/default/images/art-live.png"
DEFAULT_LIVE_TV_ART_FULL = "interfaces/default/images/art-live-full.png"
ONLINE_POSTER_THUMB = "https://tautulli.com/images/poster.png"
ONLINE_COVER_THUMB = "https://tautulli.com/images/cover.png"
ONLINE_ART = "https://tautulli.com/images/art.png"
LIVE_TV_SECTION_ID = 999999 # Fake section_id for Live TV library
LIVE_TV_SECTION_NAME = "Live TV" # Fake section_name for Live TV library
DEFAULT_IMAGES = {
'poster': DEFAULT_POSTER_THUMB,
'cover': DEFAULT_COVER_THUMB,
'art': DEFAULT_ART,
'poster-live': DEFAULT_LIVE_TV_POSTER_THUMB,
'art-live': DEFAULT_LIVE_TV_ART,
'art-live-full': DEFAULT_LIVE_TV_ART_FULL
}
MEDIA_TYPE_HEADERS = {
'movie': 'Movies',
'show': 'TV Shows',
@ -358,6 +373,9 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Optimized Version Profile', 'type': 'str', 'value': 'optimized_version_profile', 'description': 'The optimized version profile of the stream.'},
{'name': 'Synced Version', 'type': 'int', 'value': 'synced_version', 'description': 'If the stream is an synced version.', 'example': '0 or 1'},
{'name': 'Live', 'type': 'int', 'value': 'live', 'description': 'If the stream is live TV.', 'example': '0 or 1'},
{'name': 'Channel Call Sign', 'type': 'str', 'value': 'channel_call_sign', 'description': 'The Live TV channel call sign.'},
{'name': 'Channel Identifier', 'type': 'str', 'value': 'channel_identifier', 'description': 'The Live TV channel number.'},
{'name': 'Channel Thumb', 'type': 'str', 'value': 'channel_thumb', 'description': 'The URL for the Live TV channel logo.'},
{'name': 'Secure', 'type': 'int', 'value': 'secure', 'description': 'If the stream is using a secure connection.', 'example': '0 or 1'},
{'name': 'Relayed', 'type': 'int', 'value': 'relayed', 'description': 'If the stream is using Plex Relay.', 'example': '0 or 1'},
{'name': 'Stream Local', 'type': 'int', 'value': 'stream_local', 'description': 'If the stream is local.', 'example': '0 or 1'},
@ -371,8 +389,8 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Stream Video Bitrate', 'type': 'int', 'value': 'stream_video_bitrate', 'description': 'The video bitrate (in kbps) of the stream.'},
{'name': 'Stream Video Bit Depth', 'type': 'int', 'value': 'stream_video_bit_depth', 'description': 'The video bit depth of the stream.'},
{'name': 'Stream Video Chroma Subsampling', 'type': 'str', 'value': 'stream_video_chroma_subsampling', 'description': 'The video chroma subsampling of the stream.'},
{'name': 'Stream Video Color Primaries', 'type': 'srt', 'value': 'stream_video_color_primaries', 'description': 'The video color primaries of the stream.'},
{'name': 'Stream Video Color Range', 'type': 'srt', 'value': 'stream_video_color_range', 'description': 'The video color range of the stream.'},
{'name': 'Stream Video Color Primaries', 'type': 'str', 'value': 'stream_video_color_primaries', 'description': 'The video color primaries of the stream.'},
{'name': 'Stream Video Color Range', 'type': 'str', 'value': 'stream_video_color_range', 'description': 'The video color range of the stream.'},
{'name': 'Stream Video Color Space', 'type': 'str', 'value': 'stream_video_color_space', 'description': 'The video color space of the stream.'},
{'name': 'Stream Video Color Transfer Function', 'type': 'str', 'value': 'stream_video_color_trc', 'description': 'The video transfer function of the stream.'},
{'name': 'Stream Video Dynamic Range', 'type': 'str', 'value': 'stream_video_dynamic_range', 'description': 'The video dynamic range of the stream.', 'example': 'HDR or SDR'},
@ -484,8 +502,8 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Video Bitrate', 'type': 'int', 'value': 'video_bitrate', 'description': 'The video bitrate of the original media.'},
{'name': 'Video Bit Depth', 'type': 'int', 'value': 'video_bit_depth', 'description': 'The video bit depth of the original media.'},
{'name': 'Video Chroma Subsampling', 'type': 'str', 'value': 'video_chroma_subsampling', 'description': 'The video chroma subsampling of the original media.'},
{'name': 'Video Color Primaries', 'type': 'srt', 'value': 'video_color_primaries', 'description': 'The video color primaries of the original media.'},
{'name': 'Video Color Range', 'type': 'srt', 'value': 'video_color_range', 'description': 'The video color range of the original media.'},
{'name': 'Video Color Primaries', 'type': 'str', 'value': 'video_color_primaries', 'description': 'The video color primaries of the original media.'},
{'name': 'Video Color Range', 'type': 'str', 'value': 'video_color_range', 'description': 'The video color range of the original media.'},
{'name': 'Video Color Space', 'type': 'str', 'value': 'video_color_space', 'description': 'The video color space of the original media.'},
{'name': 'Video Color Transfer Function', 'type': 'str', 'value': 'video_color_trc', 'description': 'The video transfer function of the original media.'},
{'name': 'Video Dynamic Range', 'type': 'str', 'value': 'video_dynamic_range', 'description': 'The video dynamic range of the original media.', 'example': 'HDR or SDR'},

View file

@ -73,6 +73,7 @@ _CONFIG_DEFINITIONS = {
'PMS_UPDATE_CHECK_INTERVAL': (int, 'Advanced', 24),
'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'),
'TIME_FORMAT': (str, 'General', 'HH:mm'),
'ADD_LIVE_TV_LIBRARY': (int, 'Advanced', 1),
'ANON_REDIRECT': (str, 'General', 'http://www.nullrefer.com/?'),
'API_ENABLED': (int, 'General', 1),
'API_KEY': (str, 'General', ''),
@ -944,3 +945,9 @@ class Config(object):
self.GEOIP_DB = os.path.join(plexpy.DATA_DIR, 'GeoLite2-City.mmdb')
self.CONFIG_VERSION = 14
if self.CONFIG_VERSION == 14:
if plexpy.DOCKER:
self.PLEXPY_AUTO_UPDATE = 0
self.CONFIG_VERSION == 15

View file

@ -104,6 +104,10 @@ class DataFactory(object):
'session_history_metadata.thumb',
'session_history_metadata.parent_thumb',
'session_history_metadata.grandparent_thumb',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'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',
@ -152,6 +156,10 @@ class DataFactory(object):
'thumb',
'parent_thumb',
'grandparent_thumb',
'live',
'added_at',
'originally_available_at',
'guid',
'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',
@ -216,6 +224,9 @@ class DataFactory(object):
else:
thumb = item['thumb']
if item['live']:
item['percent_complete'] = 100
if item['percent_complete'] >= watched_percent[item['media_type']]:
watched_status = 1
elif item['percent_complete'] >= old_div(watched_percent[item['media_type']],2):
@ -240,6 +251,7 @@ class DataFactory(object):
'product': item['product'],
'player': item['player'],
'ip_address': item['ip_address'],
'live': item['live'],
'media_type': item['media_type'],
'rating_key': item['rating_key'],
'parent_rating_key': item['parent_rating_key'],
@ -253,6 +265,8 @@ class DataFactory(object):
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'thumb': thumb,
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'transcode_decision': item['transcode_decision'],
'percent_complete': int(round(item['percent_complete'])),
'watched_status': watched_status,
@ -296,7 +310,7 @@ class DataFactory(object):
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, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'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) ' \
@ -333,6 +347,8 @@ class DataFactory(object):
'friendly_name': '',
'platform': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
top_movies.append(row)
@ -346,7 +362,7 @@ class DataFactory(object):
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, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'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) - ' \
@ -382,6 +398,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
popular_movies.append(row)
@ -394,7 +412,7 @@ class DataFactory(object):
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, ' \
't.rating_key, t.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'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) ' \
@ -418,7 +436,7 @@ class DataFactory(object):
'total_plays': item['total_plays'],
'total_duration': item['total_duration'],
'users_watched': '',
'rating_key': item['grandparent_rating_key'],
'rating_key': item['rating_key'] if item['live'] else item['grandparent_rating_key'],
'last_play': item['last_watch'],
'grandparent_thumb': item['grandparent_thumb'],
'thumb': item['grandparent_thumb'],
@ -430,6 +448,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
top_tv.append(row)
@ -443,7 +463,7 @@ class DataFactory(object):
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, ' \
't.rating_key, t.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'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) - ' \
@ -466,7 +486,7 @@ class DataFactory(object):
for item in result:
row = {'title': item['grandparent_title'],
'users_watched': item['users_watched'],
'rating_key': item['grandparent_rating_key'],
'rating_key': item['rating_key'] if item['live'] else item['grandparent_rating_key'],
'last_play': item['last_watch'],
'total_plays': item['total_plays'],
'grandparent_thumb': item['grandparent_thumb'],
@ -479,6 +499,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
popular_tv.append(row)
@ -492,7 +514,7 @@ class DataFactory(object):
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, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'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) ' \
@ -528,6 +550,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
top_music.append(row)
@ -542,7 +566,7 @@ class DataFactory(object):
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, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'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) - ' \
@ -578,6 +602,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
popular_music.append(row)
@ -694,7 +720,7 @@ class DataFactory(object):
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, ' \
't.art, t.media_type, t.content_rating, t.labels, t.live, t.guid, ' \
'(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \
' AS friendly_name, ' \
'MAX(t.started) AS last_watch, ' \
@ -740,6 +766,8 @@ class DataFactory(object):
'content_rating': item['content_rating'],
'labels': item['labels'].split(';') if item['labels'] else (),
'last_watch': item['last_watch'],
'live': item['live'],
'guid': item['guid'],
'player': item['player']
}
last_watched.append(row)
@ -1002,10 +1030,17 @@ class DataFactory(object):
stream_output = {k: v or '' for k, v in list(stream_output.items())}
return stream_output
def get_metadata_details(self, rating_key):
def get_metadata_details(self, rating_key='', guid=''):
monitor_db = database.MonitorDatabase()
if rating_key:
if rating_key or guid:
if guid:
where = 'session_history_metadata.guid LIKE ?'
args = [guid.split('?')[0] + '%'] # SQLite LIKE wildcard
else:
where = 'session_history_metadata.rating_key = ?'
args = [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, ' \
@ -1025,15 +1060,18 @@ class DataFactory(object):
'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_full_resolution, ' \
'session_history_media_info.video_framerate, session_history_media_info.audio_codec, ' \
'session_history_media_info.audio_channels ' \
'session_history_media_info.audio_channels, session_history_metadata.live, ' \
'session_history_metadata.channel_call_sign, session_history_metadata.channel_identifier, ' \
'session_history_metadata.channel_thumb ' \
'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 = ? ' \
'WHERE %s ' \
'ORDER BY session_history_metadata.id DESC ' \
'LIMIT 1'
result = monitor_db.select(query=query, args=[rating_key])
'LIMIT 1' % where
result = monitor_db.select(query=query, args=args)
else:
result = []
@ -1050,9 +1088,13 @@ class DataFactory(object):
'bitrate': item['bitrate'],
'video_codec': item['video_codec'],
'video_resolution': item['video_resolution'],
'video_full_resolution': item['video_full_resolution'],
'video_framerate': item['video_framerate'],
'audio_codec': item['audio_codec'],
'audio_channels': item['audio_channels']
'audio_channels': item['audio_channels'],
'channel_call_sign': item['channel_call_sign'],
'channel_identifier': item['channel_identifier'],
'channel_thumb': item['channel_thumb']
}]
metadata = {'media_type': item['media_type'],
@ -1066,6 +1108,7 @@ class DataFactory(object):
'media_index': item['media_index'],
'studio': item['studio'],
'title': item['title'],
'full_title': item['full_title'],
'content_rating': item['content_rating'],
'summary': item['summary'],
'tagline': item['tagline'],
@ -1088,6 +1131,7 @@ class DataFactory(object):
'labels': labels,
'library_name': item['section_name'],
'section_id': item['section_id'],
'live': item['live'],
'media_info': media_info
}
metadata_list.append(metadata)

View file

@ -146,6 +146,9 @@ class DataTables(object):
for w_ in w[1]:
if w_ == None:
c_where += w[0] + ' IS NULL OR '
elif str(w_).startswith('LIKE '):
c_where += w[0] + ' LIKE ? OR '
args.append(w_[5:])
else:
c_where += w[0] + ' = ? OR '
args.append(w_)
@ -153,6 +156,9 @@ class DataTables(object):
else:
if w[1] == None:
c_where += w[0] + ' IS NULL AND '
elif str(w[1]).startswith('LIKE '):
c_where += w[0] + ' LIKE ? AND '
args.append(w[1][5:])
else:
c_where += w[0] + ' = ? AND '
args.append(w[1])

View file

@ -27,6 +27,7 @@ import plexpy
from plexpy import common
from plexpy import database
from plexpy import logger
from plexpy import libraries
from plexpy import session
@ -55,10 +56,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT date(started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY date(started, "unixepoch", "localtime"), %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
@ -68,13 +71,17 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT date(started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count ' \
'FROM session_history ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY date_played ' \
'ORDER BY started ASC' % (time_range, user_cond)
@ -93,6 +100,7 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for date_item in sorted(date_list):
date_string = date_item.strftime('%Y-%m-%d')
@ -100,20 +108,24 @@ class Graphs(object):
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
for item in result:
if date_string == item['date_played']:
series_1_value = item['tv_count']
series_2_value = item['movie_count']
series_3_value = item['music_count']
series_4_value = item['live_count']
break
else:
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
series_1.append(series_1_value)
series_2.append(series_2_value)
series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV',
'data': series_1}
@ -121,9 +133,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_per_dayofweek(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@ -154,10 +178,12 @@ class Graphs(object):
'WHEN 4 THEN "Thursday" ' \
'WHEN 5 THEN "Friday" ' \
'ELSE "Saturday" END) AS dayofweek, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY strftime("%%w", datetime(started, "unixepoch", "localtime")), %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
@ -175,13 +201,17 @@ class Graphs(object):
'WHEN 4 THEN "Thursday" ' \
'WHEN 5 THEN "Friday" ' \
'ELSE "Saturday" END) AS dayofweek, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count ' \
'FROM session_history ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY dayofweek ' \
'ORDER BY daynumber' % (time_range, user_cond)
@ -202,26 +232,31 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for day_item in days_list:
categories.append(day_item)
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
for item in result:
if day_item == item['dayofweek']:
series_1_value = item['tv_count']
series_2_value = item['movie_count']
series_3_value = item['music_count']
series_4_value = item['live_count']
break
else:
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
series_1.append(series_1_value)
series_2.append(series_2_value)
series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV',
'data': series_1}
@ -229,9 +264,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_per_hourofday(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@ -254,10 +301,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT strftime("%%H", datetime(started, "unixepoch", "localtime")) AS hourofday, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY strftime("%%H", datetime(started, "unixepoch", "localtime")) , %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
@ -267,13 +316,17 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT strftime("%%H", datetime(started, "unixepoch", "localtime")) AS hourofday, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count ' \
'FROM session_history ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY hourofday ' \
'ORDER BY hourofday' % (time_range, user_cond)
@ -283,35 +336,40 @@ class Graphs(object):
logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_plays_per_hourofday: %s." % e)
return None
hours_list = ['00','01','02','03','04','05',
'06','07','08','09','10','11',
'12','13','14','15','16','17',
'18','19','20','21','22','23']
hours_list = ['00', '01', '02', '03', '04', '05',
'06', '07', '08', '09', '10', '11',
'12', '13', '14', '15', '16', '17',
'18', '19', '20', '21', '22', '23']
categories = []
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for hour_item in hours_list:
categories.append(hour_item)
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
for item in result:
if hour_item == item['hourofday']:
series_1_value = item['tv_count']
series_2_value = item['movie_count']
series_3_value = item['music_count']
series_4_value = item['live_count']
break
else:
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
series_1.append(series_1_value)
series_2.append(series_2_value)
series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV',
'data': series_1}
@ -319,14 +377,24 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_per_month(self, time_range='12', y_axis='plays', user_id=None, grouping=None):
import time as time
if not time_range.isdigit():
time_range = '12'
@ -346,10 +414,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) AS datestring, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")), %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \
@ -359,13 +429,17 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) AS datestring, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count ' \
'FROM session_history ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \
'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) ' \
'ORDER BY datestring DESC LIMIT %s' % (time_range, user_cond, time_range)
@ -391,6 +465,7 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for dt in sorted(month_range):
date_string = dt.strftime('%Y-%m')
@ -398,20 +473,24 @@ class Graphs(object):
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
for item in result:
if date_string == item['datestring']:
series_1_value = item['tv_count']
series_2_value = item['movie_count']
series_3_value = item['music_count']
series_4_value = item['live_count']
break
else:
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
series_1.append(series_1_value)
series_2.append(series_2_value)
series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV',
'data': series_1}
@ -419,9 +498,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_by_top_10_platforms(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@ -444,11 +535,15 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT platform, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count, ' \
'COUNT(id) AS total_count ' \
'FROM (SELECT * FROM session_history GROUP BY %s) AS session_history ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY %s) ' \
'AS session_history ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY platform ' \
'ORDER BY total_count DESC ' \
@ -457,15 +552,19 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT platform, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS total_duration ' \
'FROM session_history ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY platform ' \
'ORDER BY total_duration DESC ' \
@ -480,12 +579,14 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for item in result:
categories.append(common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']))
series_1.append(item['tv_count'])
series_2.append(item['movie_count'])
series_3.append(item['music_count'])
series_4.append(item['live_count'])
series_1_output = {'name': 'TV',
'data': series_1}
@ -493,9 +594,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_by_top_10_users(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@ -521,11 +634,15 @@ class Graphs(object):
'users.user_id, users.username, ' \
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \
' THEN users.username ELSE users.friendly_name END) AS friendly_name,' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM (SELECT * FROM session_history GROUP BY %s) AS session_history ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY %s) ' \
'AS session_history ' \
'JOIN users ON session_history.user_id = users.user_id ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY session_history.user_id ' \
@ -538,15 +655,19 @@ class Graphs(object):
'users.user_id, users.username, ' \
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \
' THEN users.username ELSE users.friendly_name END) AS friendly_name,' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS total_duration ' \
'FROM session_history ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'JOIN users ON session_history.user_id = users.user_id ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY session_history.user_id ' \
@ -562,6 +683,7 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
session_user_id = session.get_session_user_id()
@ -573,6 +695,7 @@ class Graphs(object):
series_1.append(item['tv_count'])
series_2.append(item['movie_count'])
series_3.append(item['music_count'])
series_4.append(item['live_count'])
series_1_output = {'name': 'TV',
'data': series_1}
@ -580,9 +703,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_per_stream_type(self, time_range='30', y_axis='plays', user_id=None, grouping=None):

View file

@ -26,6 +26,7 @@ from builtins import str
from past.builtins import basestring
from past.utils import old_div
import arrow
import base64
import certifi
import cloudinary
@ -56,6 +57,7 @@ import sys
import tarfile
import time
import unicodedata
import urllib
import urllib3
from xml.dom import minidom
import xmltodict
@ -237,6 +239,22 @@ def utc_now_iso():
return utcnow.isoformat()
def timestamp_to_YMD(timestamp):
return timestamp_to_datetime(timestamp).strftime("%Y-%m-%d")
def timestamp_to_datetime(timestamp):
return datetime.datetime.fromtimestamp(cast_to_int(str(timestamp)))
def iso_to_YMD(iso):
return iso_to_datetime(iso).strftime("%Y-%m-%d")
def iso_to_datetime(iso):
return arrow.get(iso).datetime
def human_duration(s, sig='dhms'):
hd = ''
@ -1256,3 +1274,92 @@ def mask_config_passwords(config):
config[cfg] = ' '
return config
def bool_true(value):
if value is True or value == 1:
return True
elif isinstance(value, basestring) and value.lower() in ('1', 'true', 't', 'yes', 'y', 'on'):
return True
return False
def page(endpoint, *args, **kwargs):
endpoints = {
'pms_image_proxy': pms_image_proxy,
'info': info_page,
'library': library_page,
'user': user_page
}
params = {}
if endpoint in endpoints:
params = endpoints[endpoint](*args, **kwargs)
return endpoint + '?' + urllib.urlencode(params)
def pms_image_proxy(img=None, rating_key=None, width=None, height=None,
opacity=None, background=None, blur=None, img_format=None,
fallback=None, refresh=None, clip=None):
params = {}
if img is not None:
params['img'] = img
if rating_key is not None:
params['rating_key'] = rating_key
if width is not None:
params['width'] = width
if height is not None:
params['height'] = height
if opacity is not None:
params['opacity'] = opacity
if background is not None:
params['background'] = background
if blur is not None:
params['blur'] = blur
if img_format is not None:
params['img_format'] = img_format
if fallback is not None:
params['fallback'] = fallback
if refresh is not None:
params['refresh'] = 'true'
if clip is not None:
params['clip'] = 'true'
return params
def info_page(rating_key=None, guid=None, history=None, live=None):
params = {}
if live and history:
params['guid'] = guid
else:
params['rating_key'] = rating_key
if history:
params['source'] = 'history'
return params
def library_page(section_id=None):
params = {}
if section_id is not None:
params['section_id'] = section_id
return params
def user_page(user_id=None, user=None):
params = {}
if user_id is not None:
params['user_id'] = user_id
if user is not None:
params['user'] = user
return params

View file

@ -96,6 +96,37 @@ def refresh_libraries():
return False
def add_live_tv_library():
if not plexpy.CONFIG.ADD_LIVE_TV_LIBRARY:
return
logger.info(u"Tautulli Libraries :: Adding Live TV library to the database.")
monitor_db = database.MonitorDatabase()
section_keys = {'server_id': plexpy.CONFIG.PMS_IDENTIFIER,
'section_id': common.LIVE_TV_SECTION_ID}
section_values = {'server_id': plexpy.CONFIG.PMS_IDENTIFIER,
'section_id': common.LIVE_TV_SECTION_ID,
'section_name': common.LIVE_TV_SECTION_NAME,
'section_type': 'live'
}
result = monitor_db.upsert('library_sections', key_dict=section_keys, value_dict=section_values)
if result == 'insert':
plexpy.CONFIG.__setattr__('ADD_LIVE_TV_LIBRARY', 0)
plexpy.CONFIG.write()
def has_library_type(section_type):
monitor_db = database.MonitorDatabase()
query = 'SELECT * FROM library_sections WHERE section_type = ? AND deleted_section = 0'
args = [section_type]
result = monitor_db.select_single(query=query, args=args)
return bool(result)
def update_section_ids():
plexpy.CONFIG.UPDATE_SECTION_IDS = -1
@ -293,6 +324,10 @@ class Libraries(object):
'session_history_metadata.parent_media_index',
'session_history_metadata.content_rating',
'session_history_metadata.labels',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'library_sections.do_notify',
'library_sections.do_notify_created',
'library_sections.keep_history'
@ -356,6 +391,9 @@ class Libraries(object):
'parent_media_index': item['parent_media_index'],
'content_rating': item['content_rating'],
'labels': item['labels'].split(';') if item['labels'] else (),
'live': item['live'],
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'do_notify': helpers.checked(item['do_notify']),
'do_notify_created': helpers.checked(item['do_notify_created']),
'keep_history': helpers.checked(item['keep_history'])
@ -707,7 +745,7 @@ class Libraries(object):
'deleted_section': 0
}
if not section_id or helpers.cast_to_int(section_id) <= 0:
if not section_id:
return default_return
def get_library_details(section_id=section_id):
@ -887,11 +925,11 @@ class Libraries(object):
try:
if str(section_id).isdigit():
query = 'SELECT session_history.id, session_history.media_type, ' \
query = 'SELECT session_history.id, session_history.media_type, guid, ' \
'session_history.rating_key, session_history.parent_rating_key, session_history.grandparent_rating_key, ' \
'title, parent_title, grandparent_title, original_title, ' \
'thumb, parent_thumb, grandparent_thumb, media_index, parent_media_index, ' \
'year, started, user, content_rating, labels, section_id ' \
'year, originally_available_at, added_at, live, started, user, content_rating, labels, section_id ' \
'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'WHERE section_id = ? ' \
@ -925,6 +963,9 @@ class Libraries(object):
'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'],
'year': row['year'],
'originally_available_at': row['originally_available_at'],
'live': row['live'],
'guid': row['guid'],
'time': row['started'],
'user': row['user'],
'section_id': row['section_id'],

View file

@ -547,8 +547,15 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
ap = activity_processor.ActivityProcessor()
sessions = ap.get_sessions()
stream_count = len(sessions)
user_sessions = ap.get_sessions(user_id=session.get('user_id'))
# Filter out the session_key from the database sessions for playback stopped events
# to prevent race condition between the database and notifications
if notify_action == 'on_stop':
sessions = [s for s in sessions if str(s['session_key']) != notify_params['session_key']]
user_sessions = [s for s in user_sessions if str(s['session_key']) != notify_params['session_key']]
stream_count = len(sessions)
user_stream_count = len(user_sessions)
# Generate a combined transcode decision value
@ -700,12 +707,13 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
poster_title = ''
img_service = helpers.get_img_service(include_self=True)
fallback = 'poster-live' if notify_params['live'] else 'poster'
if img_service not in (None, 'self-hosted'):
img_info = get_img_info(img=poster_thumb, rating_key=poster_key, title=poster_title, fallback='poster')
img_info = get_img_info(img=poster_thumb, rating_key=poster_key, title=poster_title, fallback=fallback)
poster_info = {'poster_title': img_info['img_title'], 'poster_url': img_info['img_url']}
notify_params.update(poster_info)
elif img_service == 'self-hosted' and plexpy.CONFIG.HTTP_BASE_URL:
img_hash = set_hash_image_info(img=poster_thumb, fallback='poster')
img_hash = set_hash_image_info(img=poster_thumb, fallback=fallback)
poster_info = {'poster_title': poster_title,
'poster_url': plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'image/' + img_hash}
notify_params.update(poster_info)
@ -829,6 +837,9 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'optimized_version_profile': notify_params['optimized_version_profile'],
'synced_version': notify_params['synced_version'],
'live': notify_params['live'],
'channel_call_sign': notify_params['channel_call_sign'],
'channel_identifier': notify_params['channel_identifier'],
'channel_thumb': notify_params['channel_thumb'],
'secure': 'unknown' if notify_params['secure'] is None else notify_params['secure'],
'relayed': notify_params['relayed'],
'stream_local': notify_params['local'],
@ -1248,14 +1259,17 @@ def get_img_info(img=None, rating_key=None, title='', width=1000, height=1500,
return img_info
if rating_key and not img:
if fallback == 'art':
if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key)
else:
img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/')
img = '/'.join(img_split[:5])
rating_key = rating_key or img_split[3]
if img.startswith('/library/metadata'):
img_split = img.split('/')
img = '/'.join(img_split[:5])
img_rating_key = img_split[3]
if rating_key != img_rating_key:
rating_key = img_rating_key
service = helpers.get_img_service()
@ -1265,7 +1279,7 @@ def get_img_info(img=None, rating_key=None, title='', width=1000, height=1500,
elif service == 'cloudinary':
if fallback == 'cover':
w, h = 1000, 1000
elif fallback == 'art':
elif fallback and fallback.startswith('art'):
w, h = 1920, 1080
else:
w, h = 1000, 1500
@ -1349,14 +1363,17 @@ def set_hash_image_info(img=None, rating_key=None, width=750, height=1000,
return fallback
if rating_key and not img:
if fallback == 'art':
if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key)
else:
img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/')
img = '/'.join(img_split[:5])
rating_key = rating_key or img_split[3]
if img.startswith('/library/metadata'):
img_split = img.split('/')
img = '/'.join(img_split[:5])
img_rating_key = img_split[3]
if rating_key != img_rating_key:
rating_key = img_rating_key
img_string = '{}.{}.{}.{}.{}.{}.{}.{}'.format(
plexpy.CONFIG.PMS_UUID, img, rating_key, width, height, opacity, background, blur, fallback)

View file

@ -3533,7 +3533,7 @@ class WEBHOOK(Notifier):
"""
NAME = 'Webhook'
_DEFAULT_CONFIG = {'hook': '',
'method': ''
'method': 'POST'
}
def agent_notify(self, subject='', body='', action='', **kwargs):
@ -3579,8 +3579,7 @@ class WEBHOOK(Notifier):
'name': 'webhook_method',
'description': 'The Webhook HTTP request method.',
'input_type': 'select',
'select_options': {'': '',
'GET': 'GET',
'select_options': {'GET': 'GET',
'POST': 'POST',
'PUT': 'PUT',
'DELETE': 'DELETE'}

View file

@ -581,7 +581,8 @@ class PmsConnect(object):
return output
def get_metadata_details(self, rating_key='', sync_id='', cache_key=None, media_info=True):
def get_metadata_details(self, rating_key='', sync_id='', plex_guid='',
skip_cache=False, cache_key=None, return_cache=False, media_info=True):
"""
Return processed and validated metadata list for requested item.
@ -591,7 +592,7 @@ class PmsConnect(object):
"""
metadata = {}
if cache_key:
if not skip_cache and cache_key:
in_file_folder = os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata')
in_file_path = os.path.join(in_file_folder, 'metadata-sessionKey-%s.json' % cache_key)
@ -606,14 +607,18 @@ class PmsConnect(object):
if metadata:
_cache_time = metadata.pop('_cache_time', 0)
# Return cached metadata if less than METADATA_CACHE_SECONDS ago
if int(time.time()) - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS:
# Return cached metadata if less than cache_seconds ago
if return_cache or int(time.time()) - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS:
return metadata
if rating_key:
metadata_xml = self.get_metadata(str(rating_key), output_format='xml')
elif sync_id:
metadata_xml = self.get_sync_item(str(sync_id), output_format='xml')
elif plex_guid.startswith(('plex://movie', 'plex://episode')):
rating_key = plex_guid.rsplit('/', 1)[-1]
plextv_metadata = PmsConnect(url='https://metadata.provider.plex.tv', token=plexpy.CONFIG.PMS_TOKEN)
metadata_xml = plextv_metadata.get_metadata(rating_key, output_format='xml')
else:
return metadata
@ -729,7 +734,8 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'show':
@ -781,12 +787,19 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'season':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
show_details = self.get_metadata_details(parent_rating_key)
parent_guid = helpers.get_xml_attr(metadata_main, 'parentGuid')
show_details = {}
if plex_guid and parent_guid:
show_details = self.get_metadata_details(plex_guid=parent_guid)
elif not plex_guid and parent_rating_key:
show_details = self.get_metadata_details(parent_rating_key)
metadata = {'media_type': metadata_type,
'section_id': section_id,
'library_name': library_name,
@ -800,22 +813,22 @@ class PmsConnect(object):
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': show_details['studio'],
'content_rating': show_details['content_rating'],
'summary': show_details['summary'],
'studio': show_details.get('studio', ''),
'content_rating': show_details.get('content_rating', ''),
'summary': show_details.get('summary', ''),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
'duration': show_details['duration'],
'duration': show_details.get('duration', ''),
'year': helpers.get_xml_attr(metadata_main, 'year'),
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': show_details['banner'],
'banner': show_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -823,32 +836,38 @@ class PmsConnect(object):
'guid': helpers.get_xml_attr(metadata_main, 'guid'),
'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
'directors': show_details['directors'],
'writers': show_details['writers'],
'actors': show_details['actors'],
'genres': show_details['genres'],
'labels': show_details['labels'],
'collections': show_details['collections'],
'directors': show_details.get('directors', []),
'writers': show_details.get('writers', []),
'actors': show_details.get('actors', []),
'genres': show_details.get('genres', []),
'labels': show_details.get('labels', []),
'collections': show_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'episode':
grandparent_rating_key = helpers.get_xml_attr(metadata_main, 'grandparentRatingKey')
show_details = self.get_metadata_details(grandparent_rating_key)
grandparent_guid = helpers.get_xml_attr(metadata_main, 'grandparentGuid')
show_details = {}
if plex_guid and grandparent_guid:
show_details = self.get_metadata_details(plex_guid=grandparent_guid)
elif not plex_guid and grandparent_rating_key:
show_details = self.get_metadata_details(grandparent_rating_key)
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
parent_media_index = helpers.get_xml_attr(metadata_main, 'parentIndex')
parent_thumb = helpers.get_xml_attr(metadata_main, 'parentThumb')
if not parent_rating_key:
if not plex_guid and not parent_rating_key:
# Try getting the parent_rating_key from the parent_thumb
if parent_thumb.startswith('/library/metadata/'):
parent_rating_key = parent_thumb.split('/')[3]
# Try getting the parent_rating_key from the grandparent's children
if not parent_rating_key:
if not parent_rating_key and grandparent_rating_key:
children_list = self.get_item_children(grandparent_rating_key)
parent_rating_key = next((c['rating_key'] for c in children_list['children_list']
if c['media_index'] == parent_media_index), '')
@ -866,7 +885,7 @@ class PmsConnect(object):
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': parent_media_index,
'studio': show_details['studio'],
'studio': show_details.get('studio', ''),
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
'summary': helpers.get_xml_attr(metadata_main, 'summary'),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
@ -881,7 +900,7 @@ class PmsConnect(object):
'parent_thumb': parent_thumb,
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': show_details['banner'],
'banner': show_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -891,13 +910,14 @@ class PmsConnect(object):
'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
'directors': directors,
'writers': writers,
'actors': show_details['actors'],
'genres': show_details['genres'],
'labels': show_details['labels'],
'collections': show_details['collections'],
'actors': show_details.get('actors', []),
'genres': show_details.get('genres', []),
'labels': show_details.get('labels', []),
'collections': show_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'artist':
@ -944,12 +964,13 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'album':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
artist_details = self.get_metadata_details(parent_rating_key)
artist_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
metadata = {'media_type': metadata_type,
'section_id': section_id,
'library_name': library_name,
@ -965,7 +986,7 @@ class PmsConnect(object):
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'),
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
'summary': helpers.get_xml_attr(metadata_main, 'summary') or artist_details['summary'],
'summary': helpers.get_xml_attr(metadata_main, 'summary') or artist_details.get('summary', ''),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
@ -978,7 +999,7 @@ class PmsConnect(object):
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': artist_details['banner'],
'banner': artist_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -994,12 +1015,13 @@ class PmsConnect(object):
'collections': collections,
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'track':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
album_details = self.get_metadata_details(parent_rating_key)
album_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
track_artist = helpers.get_xml_attr(metadata_main, 'originalTitle') or \
helpers.get_xml_attr(metadata_main, 'grandparentTitle')
metadata = {'media_type': metadata_type,
@ -1025,12 +1047,12 @@ class PmsConnect(object):
'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
'duration': helpers.get_xml_attr(metadata_main, 'duration'),
'year': album_details['year'],
'year': album_details.get('year', ''),
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': album_details['banner'],
'banner': album_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -1041,12 +1063,13 @@ class PmsConnect(object):
'directors': directors,
'writers': writers,
'actors': actors,
'genres': album_details['genres'],
'labels': album_details['labels'],
'collections': album_details['collections'],
'genres': album_details.get('genres', []),
'labels': album_details.get('labels', []),
'collections': album_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'title'),
track_artist),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'photo_album':
@ -1093,12 +1116,13 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'photo':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
photo_album_details = self.get_metadata_details(parent_rating_key)
photo_album_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
metadata = {'media_type': metadata_type,
'section_id': section_id,
'library_name': library_name,
@ -1138,12 +1162,13 @@ class PmsConnect(object):
'directors': directors,
'writers': writers,
'actors': actors,
'genres': photo_album_details.get('genres', ''),
'labels': photo_album_details.get('labels', ''),
'collections': photo_album_details.get('collections', ''),
'genres': photo_album_details.get('genres', []),
'labels': photo_album_details.get('labels', []),
'collections': photo_album_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle') or library_name,
helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'collection':
@ -1194,7 +1219,8 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount'))
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'clip':
@ -1242,17 +1268,31 @@ class PmsConnect(object):
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'extra_type': helpers.get_xml_attr(metadata_main, 'extraType'),
'sub_type': helpers.get_xml_attr(metadata_main, 'subtype')
'sub_type': helpers.get_xml_attr(metadata_main, 'subtype'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
else:
return metadata
# Get additional metadata from metadata.provider.plex.tv
if not plex_guid and metadata['live']:
metadata['section_id'] = common.LIVE_TV_SECTION_ID
metadata['library_name'] = common.LIVE_TV_SECTION_NAME
plextv_metadata = self.get_metadata_details(plex_guid=metadata['guid'])
if plextv_metadata:
keys_to_update = ['summary', 'rating', 'thumb', 'grandparent_thumb', 'duration',
'guid', 'grandparent_guid', 'genres']
for key in keys_to_update:
metadata[key] = plextv_metadata[key]
metadata['originally_available_at'] = helpers.iso_to_YMD(plextv_metadata['originally_available_at'])
if metadata and media_info:
medias = []
media_items = metadata_main.getElementsByTagName('Media')
for media in media_items:
video_full_resolution_scan_type = None
video_full_resolution_scan_type = ''
parts = []
part_items = media.getElementsByTagName('Part')
@ -1263,8 +1303,7 @@ class PmsConnect(object):
for stream in stream_items:
if helpers.get_xml_attr(stream, 'streamType') == '1':
video_scan_type = helpers.get_xml_attr(stream, 'scanType')
if video_full_resolution_scan_type is None:
video_full_resolution_scan_type = video_scan_type
video_full_resolution_scan_type = (video_full_resolution_scan_type or video_scan_type)
streams.append({'id': helpers.get_xml_attr(stream, 'id'),
'type': helpers.get_xml_attr(stream, 'streamType'),
@ -1324,35 +1363,36 @@ class PmsConnect(object):
'selected': int(helpers.get_xml_attr(part, 'selected') == '1')
})
video_resolution = helpers.get_xml_attr(media, 'videoResolution').lower()
video_full_resolution = ''
if video_full_resolution_scan_type is not None:
video_full_resolution = common.VIDEO_RESOLUTION_OVERRIDES.get(
video_resolution, video_resolution + (video_full_resolution_scan_type[:1] or 'p')
)
video_resolution = helpers.get_xml_attr(media, 'videoResolution').lower().rstrip('ip')
video_full_resolution = common.VIDEO_RESOLUTION_OVERRIDES.get(
video_resolution, video_resolution + (video_full_resolution_scan_type[:1] or 'p')
)
audio_channels = helpers.get_xml_attr(media, 'audioChannels')
medias.append({'id': helpers.get_xml_attr(media, 'id'),
'container': helpers.get_xml_attr(media, 'container'),
'bitrate': helpers.get_xml_attr(media, 'bitrate'),
'height': helpers.get_xml_attr(media, 'height'),
'width': helpers.get_xml_attr(media, 'width'),
'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(media, 'videoCodec'),
'video_resolution': video_resolution,
'video_full_resolution': video_full_resolution,
'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(media, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(media, 'audioProfile'),
'optimized_version': int(helpers.get_xml_attr(media, 'proxyType') == '42'),
'parts': parts
})
media_info = {'id': helpers.get_xml_attr(media, 'id'),
'container': helpers.get_xml_attr(media, 'container'),
'bitrate': helpers.get_xml_attr(media, 'bitrate'),
'height': helpers.get_xml_attr(media, 'height'),
'width': helpers.get_xml_attr(media, 'width'),
'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(media, 'videoCodec'),
'video_resolution': video_resolution,
'video_full_resolution': video_full_resolution,
'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(media, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(media, 'audioProfile'),
'optimized_version': int(helpers.get_xml_attr(media, 'proxyType') == '42'),
'channel_call_sign': helpers.get_xml_attr(media, 'channelCallSign'),
'channel_identifier': helpers.get_xml_attr(media, 'channelIdentifier'),
'channel_thumb': helpers.get_xml_attr(media, 'channelThumb'),
'parts': parts
}
video_full_resolution = helpers.get_xml_attr(media, 'videoResolution').lower()
medias.append(media_info)
metadata['media_info'] = medias
@ -1474,7 +1514,7 @@ class PmsConnect(object):
return metadata_list
def get_current_activity(self):
def get_current_activity(self, skip_cache=False):
"""
Return processed and validated session list.
@ -1501,17 +1541,17 @@ class PmsConnect(object):
if a.getElementsByTagName('Track'):
session_data = a.getElementsByTagName('Track')
for session_ in session_data:
session_output = self.get_session_each(session_)
session_output = self.get_session_each(session_, skip_cache=skip_cache)
session_list.append(session_output)
if a.getElementsByTagName('Video'):
session_data = a.getElementsByTagName('Video')
for session_ in session_data:
session_output = self.get_session_each(session_)
session_output = self.get_session_each(session_, skip_cache=skip_cache)
session_list.append(session_output)
if a.getElementsByTagName('Photo'):
session_data = a.getElementsByTagName('Photo')
for session_ in session_data:
session_output = self.get_session_each(session_)
session_output = self.get_session_each(session_, skip_cache=skip_cache)
session_list.append(session_output)
session_list = sorted(session_list, key=lambda k: k['session_key'])
@ -1522,7 +1562,7 @@ class PmsConnect(object):
return output
def get_session_each(self, session=None):
def get_session_each(self, session=None, skip_cache=False):
"""
Return selected data from current sessions.
This function processes and validates session data
@ -1798,7 +1838,7 @@ class PmsConnect(object):
if helpers.cast_to_int(stream_video_width) >= 3840:
stream_video_resolution = '4k'
else:
stream_video_resolution = helpers.get_xml_attr(stream_media_info, 'videoResolution').rstrip('p').lower()
stream_video_resolution = helpers.get_xml_attr(stream_media_info, 'videoResolution').lower().rstrip('ip')
stream_audio_channels = helpers.get_xml_attr(stream_media_info, 'audioChannels')
@ -1875,13 +1915,19 @@ class PmsConnect(object):
'full_title': helpers.get_xml_attr(session, 'title'),
'container': helpers.get_xml_attr(stream_media_info, 'container') \
or helpers.get_xml_attr(stream_media_parts_info, 'container'),
'bitrate': helpers.get_xml_attr(stream_media_info, 'bitrate'),
'height': helpers.get_xml_attr(stream_media_info, 'height'),
'width': helpers.get_xml_attr(stream_media_info, 'width'),
'aspect_ratio': helpers.get_xml_attr(stream_media_info, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(stream_media_info, 'videoCodec'),
'video_resolution': helpers.get_xml_attr(stream_media_info, 'videoResolution').lower(),
'video_full_resolution': helpers.get_xml_attr(stream_media_info, 'videoResolution').lower(),
'video_framerate': helpers.get_xml_attr(stream_media_info, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(stream_media_info, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(stream_media_info, 'audioCodec'),
'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(stream_media_info, 'audioProfile'),
'channel_icon': helpers.get_xml_attr(session, 'sourceIcon'),
'channel_title': helpers.get_xml_attr(session, 'sourceTitle'),
'extra_type': helpers.get_xml_attr(session, 'extraType'),
@ -1894,9 +1940,11 @@ class PmsConnect(object):
part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
if sync_id:
metadata_details = self.get_metadata_details(rating_key=rating_key, sync_id=sync_id, cache_key=session_key)
metadata_details = self.get_metadata_details(rating_key=rating_key, sync_id=sync_id,
skip_cache=skip_cache, cache_key=session_key)
else:
metadata_details = self.get_metadata_details(rating_key=rating_key, cache_key=session_key)
metadata_details = self.get_metadata_details(rating_key=rating_key,
skip_cache=skip_cache, cache_key=session_key)
# Get the media info, fallback to first item if match id is not found
source_medias = metadata_details.pop('media_info', [])
@ -1983,15 +2031,15 @@ class PmsConnect(object):
# Override * in audio codecs
if stream_details['stream_audio_codec'] == '*':
stream_details['stream_audio_codec'] = source_audio_details['audio_codec']
stream_details['stream_audio_codec'] = source_audio_details.get('audio_codec', '')
if transcode_details['transcode_audio_codec'] == '*':
transcode_details['transcode_audio_codec'] = source_audio_details['audio_codec']
transcode_details['transcode_audio_codec'] = source_audio_details.get('audio_codec', '')
# Override * in video codecs
if stream_details['stream_video_codec'] == '*':
stream_details['stream_video_codec'] = source_video_details['video_codec']
stream_details['stream_video_codec'] = source_video_details.get('video_codec', '')
if transcode_details['transcode_video_codec'] == '*':
transcode_details['transcode_video_codec'] = source_video_details['video_codec']
transcode_details['transcode_video_codec'] = source_video_details.get('video_codec', '')
if media_type in ('movie', 'episode', 'clip'):
# Set the full resolution by combining stream_video_resolution and stream_video_scan_type
@ -1999,13 +2047,15 @@ class PmsConnect(object):
stream_details['stream_video_resolution'],
stream_details['stream_video_resolution'] + (video_details['stream_video_scan_type'][:1] or 'p'))
if helpers.cast_to_int(source_video_details['video_bit_depth']) > 8 \
and source_video_details['video_color_space'] == 'bt2020nc':
if helpers.cast_to_int(source_video_details.get('video_bit_depth')) > 8 \
and source_video_details.get('video_color_space') == 'bt2020nc':
stream_details['video_dynamic_range'] = 'HDR'
else:
stream_details['video_dynamic_range'] = 'SDR'
if helpers.cast_to_int(video_details['stream_video_bit_depth']) > 8 \
if stream_details['video_dynamic_range'] == 'HDR' \
and video_details['stream_video_decision'] != 'transcode' \
or helpers.cast_to_int(video_details['stream_video_bit_depth']) > 8 \
and video_details['stream_video_color_space'] == 'bt2020nc':
stream_details['stream_video_dynamic_range'] = 'HDR'
else:
@ -2040,7 +2090,7 @@ class PmsConnect(object):
if stream_details['optimized_version']:
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
optimized_version_profile = '{} Mbps {}'.format(round(source_bitrate / 1000.0, 1),
source_media_details['video_full_resolution'])
source_media_details.get('video_full_resolution'))
else:
optimized_version_profile = ''
@ -2689,10 +2739,14 @@ class PmsConnect(object):
height = height or 1500
if img:
if refresh:
web_img = img.startswith('http')
if refresh and not web_img:
img = '{}/{}'.format(img.rstrip('/'), int(time.time()))
if clip:
if web_img:
params = {'url': '%s' % img}
elif clip:
params = {'url': '%s&%s' % (img, urllib.parse.urlencode({'X-Plex-Token': self.token}))}
else:
params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urllib.parse.urlencode({'X-Plex-Token': self.token}))}

View file

@ -126,6 +126,10 @@ class Users(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'session_history_media_info.transcode_decision',
'users.do_notify as do_notify',
'users.keep_history as keep_history',
@ -189,6 +193,9 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'live': item['live'],
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'transcode_decision': item['transcode_decision'],
'do_notify': helpers.checked(item['do_notify']),
'keep_history': helpers.checked(item['keep_history']),
@ -235,6 +242,10 @@ class Users(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'session_history_media_info.transcode_decision',
'session_history.user',
'session_history.user_id as custom_user_id',
@ -289,6 +300,9 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'live': item['live'],
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'transcode_decision': item['transcode_decision'],
'friendly_name': item['friendly_name'],
'user_id': item['custom_user_id']
@ -544,11 +558,11 @@ class Users(object):
try:
if str(user_id).isdigit():
query = 'SELECT session_history.id, session_history.media_type, ' \
query = 'SELECT session_history.id, session_history.media_type, guid, ' \
'session_history.rating_key, session_history.parent_rating_key, session_history.grandparent_rating_key, ' \
'title, parent_title, grandparent_title, original_title, ' \
'thumb, parent_thumb, grandparent_thumb, media_index, parent_media_index, ' \
'year, started, user ' \
'year, originally_available_at, added_at, live, started, user ' \
'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'WHERE user_id = ? ' \
@ -563,30 +577,33 @@ class Users(object):
result = []
for row in result:
if row['media_type'] == 'episode' and row['parent_thumb']:
thumb = row['parent_thumb']
elif row['media_type'] == 'episode':
thumb = row['grandparent_thumb']
else:
thumb = row['thumb']
if row['media_type'] == 'episode' and row['parent_thumb']:
thumb = row['parent_thumb']
elif row['media_type'] == 'episode':
thumb = row['grandparent_thumb']
else:
thumb = row['thumb']
recent_output = {'row_id': row['id'],
'media_type': row['media_type'],
'rating_key': row['rating_key'],
'parent_rating_key': row['parent_rating_key'],
'grandparent_rating_key': row['grandparent_rating_key'],
'title': row['title'],
'parent_title': row['parent_title'],
'grandparent_title': row['grandparent_title'],
'original_title': row['original_title'],
'thumb': thumb,
'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'],
'year': row['year'],
'time': row['started'],
'user': row['user']
}
recently_watched.append(recent_output)
recent_output = {'row_id': row['id'],
'media_type': row['media_type'],
'rating_key': row['rating_key'],
'parent_rating_key': row['parent_rating_key'],
'grandparent_rating_key': row['grandparent_rating_key'],
'title': row['title'],
'parent_title': row['parent_title'],
'grandparent_title': row['grandparent_title'],
'original_title': row['original_title'],
'thumb': thumb,
'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'],
'year': row['year'],
'originally_available_at': row['originally_available_at'],
'live': row['live'],
'guid': row['guid'],
'time': row['started'],
'user': row['user']
}
recently_watched.append(recent_output)
return recently_watched

View file

@ -1,3 +1,21 @@
from __future__ import unicode_literals
PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.1.42"
# -*- 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 <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.2.0-beta"

View file

@ -124,20 +124,24 @@ def getVersion():
else:
plexpy.INSTALL_TYPE = 'source'
plexpy.INSTALL_TYPE = 'docker' if plexpy.DOCKER else 'source'
version_file = os.path.join(plexpy.PROG_DIR, 'version.txt')
branch_file = os.path.join(plexpy.PROG_DIR, 'branch.txt')
if not os.path.isfile(version_file):
return None, 'origin', common.BRANCH
with open(version_file, 'r') as f:
current_version = f.read().strip(' \n\r')
if current_version:
return current_version, 'origin', common.BRANCH
if os.path.isfile(version_file):
with open(version_file, 'r') as f:
current_version = f.read().strip(' \n\r')
else:
return None, 'origin', common.BRANCH
current_version = None
if os.path.isfile(branch_file):
with open(branch_file, 'r') as f:
current_branch = f.read().strip(' \n\r')
else:
current_branch = common.BRANCH
return current_version, 'origin', current_branch
def check_update(auto_update=False, notify=False):
@ -167,13 +171,17 @@ def check_update(auto_update=False, notify=False):
def check_github(auto_update=False, notify=False):
plexpy.COMMITS_BEHIND = 0
if plexpy.CONFIG.GIT_TOKEN:
headers = {'Authorization': 'token {}'.format(plexpy.CONFIG.GIT_TOKEN)}
else:
headers = {}
# Get the latest version available from github
logger.info('Retrieving latest version information from GitHub')
url = 'https://api.github.com/repos/%s/%s/commits/%s' % (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.CONFIG.GIT_BRANCH)
if plexpy.CONFIG.GIT_TOKEN: url = url + '?access_token=%s' % plexpy.CONFIG.GIT_TOKEN
version = request.request_json(url, timeout=20, validator=lambda x: type(x) == dict)
version = request.request_json(url, headers=headers, timeout=20, validator=lambda x: type(x) == dict)
if version is None:
logger.warn('Could not get the latest version from GitHub. Are you running a local development version?')
@ -196,8 +204,8 @@ def check_github(auto_update=False, notify=False):
plexpy.CONFIG.GIT_REPO,
plexpy.LATEST_VERSION,
plexpy.CURRENT_VERSION)
if plexpy.CONFIG.GIT_TOKEN: url = url + '?access_token=%s' % plexpy.CONFIG.GIT_TOKEN
commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict)
commits = request.request_json(url, headers=headers, timeout=20, whitelist_status_code=404,
validator=lambda x: type(x) == dict)
if commits is None:
logger.warn('Could not get commits behind from GitHub.')
@ -237,7 +245,7 @@ def check_github(auto_update=False, notify=False):
'plexpy_update_commit': plexpy.LATEST_VERSION,
'plexpy_update_behind': plexpy.COMMITS_BEHIND})
if auto_update:
if auto_update and not plexpy.DOCKER:
logger.info('Running automatic update.')
plexpy.shutdown(restart=True, update=True)
@ -252,23 +260,26 @@ def update():
logger.info('Windows .exe updating not supported yet.')
elif plexpy.INSTALL_TYPE == 'git':
output, err = runGit('pull ' + plexpy.CONFIG.GIT_REMOTE + ' ' + plexpy.CONFIG.GIT_BRANCH)
output, err = runGit('pull {} {} --ff-only'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
if not output:
logger.error('Unable to download latest version')
return
for line in output.split('\n'):
if 'Already up-to-date.' in line:
if 'Already up-to-date.' in line or 'Already up to date.' in line:
logger.info('No update available, not updating')
logger.info('Output: ' + str(output))
elif line.endswith(('Aborting', 'Aborting.')):
logger.error('Unable to update from git: ' + line)
logger.info('Output: ' + str(output))
elif plexpy.INSTALL_TYPE == 'docker':
return
else:
tar_download_url = 'https://github.com/{}/{}/tarball/{}'.format(plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_BRANCH)
tar_download_url = 'https://github.com/{}/{}/tarball/{}'.format(plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.CONFIG.GIT_BRANCH)
update_dir = os.path.join(plexpy.PROG_DIR, 'update')
version_path = os.path.join(plexpy.PROG_DIR, 'version.txt')
@ -326,6 +337,34 @@ def update():
return
def reset():
if plexpy.INSTALL_TYPE == 'git':
logger.info('Attempting to reset git install to "%s/%s"' % (plexpy.CONFIG.GIT_REMOTE, plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('remote set-url {} https://github.com/{}/{}.git'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO))
output, err = runGit('fetch {}'.format(plexpy.CONFIG.GIT_REMOTE))
output, err = runGit('checkout {}'.format(plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('branch -u {}/{}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('reset --hard {}/{}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('pull {} {}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
if not output:
logger.error('Unable to reset Tautulli installation.')
return False
for line in output.split('\n'):
if 'Already up-to-date.' in line or 'Already up to date.' in line:
logger.info('Tautulli installation reset successfully.')
return True
elif line.endswith(('Aborting', 'Aborting.')):
logger.error('Unable to reset Tautulli installation: ' + line)
return False
def checkout_git_branch():
if plexpy.INSTALL_TYPE == 'git':
output, err = runGit('fetch %s' % plexpy.CONFIG.GIT_REMOTE)
@ -338,9 +377,10 @@ def checkout_git_branch():
for line in output.split('\n'):
if line.endswith(('Aborting', 'Aborting.')):
logger.error('Unable to checkout from git: ' + line)
logger.info('Output: ' + str(output))
return
output, err = runGit('pull %s %s' % (plexpy.CONFIG.GIT_REMOTE, plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('pull {} {}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
def read_changelog(latest_only=False, since_prev_release=False):

View file

@ -288,7 +288,7 @@ class WebInterface(object):
def return_plex_xml_url(self, endpoint='', plextv=False, **kwargs):
kwargs['X-Plex-Token'] = plexpy.CONFIG.PMS_TOKEN
if plextv == 'true':
if helpers.bool_true(plextv):
base_url = 'https://plex.tv'
else:
if plexpy.CONFIG.PMS_URL_OVERRIDE:
@ -393,15 +393,18 @@ class WebInterface(object):
"do_notify": "Checked",
"do_notify_created": "Checked",
"duration": 1578037,
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1128,
"keep_history": "Checked",
"labels": [],
"last_accessed": 1462693216,
"last_played": "Game of Thrones - The Red Woman",
"library_art": "/:/resources/show-fanart.jpg",
"library_thumb": "",
"library_thumb": "/:/resources/show.png",
"live": 0,
"media_index": 1,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_count": 240,
"parent_media_index": 6,
"parent_title": "",
@ -677,6 +680,7 @@ class WebInterface(object):
"rating_key": "1219",
"section_id": 2,
"section_type": "show",
"sort_title": "Game of Thrones",
"thumb": "/library/metadata/1219/thumb/1436265995",
"title": "Game of Thrones",
"video_codec": "",
@ -712,7 +716,7 @@ class WebInterface(object):
("play_count", True, False)]
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "sort_title")
if refresh == 'true':
if helpers.bool_true(refresh):
refresh = True
else:
refresh = False
@ -1055,13 +1059,16 @@ class WebInterface(object):
"do_notify": "Checked",
"duration": 2998290,
"friendly_name": "Jon Snow",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1121,
"ip_address": "xxx.xxx.xxx.xxx",
"keep_history": "Checked",
"last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869,
"live": 0,
"media_index": 1,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 6,
"parent_title": "",
"platform": "Chrome",
@ -1267,12 +1274,15 @@ class WebInterface(object):
"recordsFiltered": 10,
"data":
[{"friendly_name": "Jon Snow",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1121,
"ip_address": "xxx.xxx.xxx.xxx",
"last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869,
"live": 0,
"media_index": 1,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 6,
"parent_title": "",
"platform": "Chrome",
@ -1607,8 +1617,9 @@ class WebInterface(object):
grandparent_rating_key (int): 351
start_date (str): "YYYY-MM-DD"
section_id (int): 2
media_type (str): "movie", "episode", "track"
media_type (str): "movie", "episode", "track", "live"
transcode_decision (str): "direct play", "copy", "transcode",
guid (str): Plex guid for an item, e.g. "com.plexapp.agents.thetvdb://121361/6/1"
order_column (str): "date", "friendly_name", "ip_address", "platform", "player",
"full_title", "started", "paused_counter", "stopped", "duration"
order_dir (str): "desc" or "asc"
@ -1633,10 +1644,13 @@ class WebInterface(object):
"original_title": "",
"group_count": 1,
"group_ids": "1124",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1124,
"ip_address": "xxx.xxx.xxx.xxx",
"live": 0,
"media_index": 17,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 7,
"parent_rating_key": 544,
"parent_title": "",
@ -1694,31 +1708,37 @@ class WebInterface(object):
elif user:
custom_where.append(['session_history.user', user])
if 'rating_key' in kwargs:
rating_key = kwargs.get('rating_key', "")
rating_key = kwargs.get('rating_key', '')
custom_where.append(['session_history.rating_key', rating_key])
if 'parent_rating_key' in kwargs:
rating_key = kwargs.get('parent_rating_key', "")
rating_key = kwargs.get('parent_rating_key', '')
custom_where.append(['session_history.parent_rating_key', rating_key])
if 'grandparent_rating_key' in kwargs:
rating_key = kwargs.get('grandparent_rating_key', "")
rating_key = kwargs.get('grandparent_rating_key', '')
custom_where.append(['session_history.grandparent_rating_key', rating_key])
if 'start_date' in kwargs:
start_date = kwargs.get('start_date', "")
start_date = kwargs.get('start_date', '')
custom_where.append(['strftime("%Y-%m-%d", datetime(started, "unixepoch", "localtime"))', start_date])
if 'reference_id' in kwargs:
reference_id = kwargs.get('reference_id', "")
reference_id = kwargs.get('reference_id', '')
custom_where.append(['session_history.reference_id', reference_id])
if 'section_id' in kwargs:
section_id = kwargs.get('section_id', "")
section_id = kwargs.get('section_id', '')
custom_where.append(['session_history_metadata.section_id', section_id])
if 'media_type' in kwargs:
media_type = kwargs.get('media_type', "")
if media_type != 'all':
media_type = kwargs.get('media_type', '')
if media_type not in ('all', 'live'):
custom_where.append(['session_history.media_type', media_type])
custom_where.append(['session_history_metadata.live', '0'])
elif media_type == 'live':
custom_where.append(['session_history_metadata.live', '1'])
if 'transcode_decision' in kwargs:
transcode_decision = kwargs.get('transcode_decision', "")
transcode_decision = kwargs.get('transcode_decision', '')
if transcode_decision:
custom_where.append(['session_history_media_info.transcode_decision', transcode_decision])
if 'guid' in kwargs:
guid = kwargs.get('guid', '').split('?')[0]
custom_where.append(['session_history_metadata.guid', 'LIKE ' + guid + '%']) # SQLite LIKE wildcard
data_factory = datafactory.DataFactory()
history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping)
@ -1779,6 +1799,7 @@ class WebInterface(object):
"stream_video_bitrate": 527,
"stream_video_codec": "h264",
"stream_video_decision": "transcode",
"stream_video_dynamic_range": "SDR",
"stream_video_framerate": "24p",
"stream_video_height": 306,
"stream_video_resolution": "SD",
@ -1793,6 +1814,7 @@ class WebInterface(object):
"video_bitrate": 2500,
"video_codec": "h264",
"video_decision": "transcode",
"video_dynamic_range": "SDR",
"video_framerate": "24p",
"video_height": 816,
"video_resolution": "1080",
@ -1888,7 +1910,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@ -1927,7 +1950,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@ -1966,7 +1990,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@ -2005,7 +2030,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@ -2044,7 +2070,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@ -2083,7 +2110,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@ -3076,7 +3104,7 @@ class WebInterface(object):
def install_geoip_db(self, update=False, **kwargs):
""" Downloads and installs the GeoLite2 database """
update = True if update == 'true' else False
update = helpers.bool_true(update)
result = helpers.install_geoip_db(update=update)
@ -3482,7 +3510,7 @@ class WebInterface(object):
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def verify_mobile_device(self, device_token='', cancel=False, **kwargs):
if cancel == 'true':
if helpers.bool_true(cancel):
mobile_app.TEMP_DEVICE_TOKEN = None
return {'result': 'error', 'message': 'Device registration cancelled.'}
@ -3647,7 +3675,7 @@ class WebInterface(object):
if not username and not password:
return None
force = True if force == 'true' else False
force = helpers.bool_true(force)
plex_tv = plextv.PlexTV(username=username, password=password)
token = plex_tv.get_plexpy_pms_token(force=force)
@ -3710,7 +3738,7 @@ class WebInterface(object):
result = {'identifier': identifier}
if identifier:
if get_url == 'true':
if helpers.bool_true(get_url):
server = self.get_server_resources(pms_ip=hostname,
pms_port=port,
pms_ssl=ssl,
@ -3720,7 +3748,7 @@ class WebInterface(object):
result['url'] = server['pms_url']
result['ws'] = None
if test_websocket == 'true':
if helpers.bool_true(test_websocket):
# Quick test websocket connection
ws_url = result['url'].replace('http', 'ws', 1) + '/:/websockets/notifications'
header = ['X-Plex-Token: %s' % plexpy.CONFIG.PMS_TOKEN]
@ -3774,7 +3802,7 @@ class WebInterface(object):
logger.info("New API key generated.")
logger._BLACKLIST_WORDS.add(apikey)
if device == 'true':
if helpers.bool_true(device):
mobile_app.TEMP_DEVICE_TOKEN = apikey
return apikey
@ -3804,46 +3832,51 @@ class WebInterface(object):
versioncheck.check_update()
if plexpy.UPDATE_AVAILABLE is None:
return {'result': 'error',
'update': None,
'message': 'You are running an unknown version of Tautulli.'
}
update = {'result': 'error',
'update': None,
'message': 'You are running an unknown version of Tautulli.'
}
elif plexpy.UPDATE_AVAILABLE == 'release':
return {'result': 'success',
'update': True,
'release': True,
'message': 'A new release (%s) of Tautulli is available.' % plexpy.LATEST_RELEASE,
'current_release': plexpy.common.RELEASE,
'latest_release': plexpy.LATEST_RELEASE,
'release_url': helpers.anon_url(
'https://github.com/%s/%s/releases/tag/%s'
% (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.LATEST_RELEASE))
}
update = {'result': 'success',
'update': True,
'release': True,
'message': 'A new release (%s) of Tautulli is available.' % plexpy.LATEST_RELEASE,
'current_release': plexpy.common.RELEASE,
'latest_release': plexpy.LATEST_RELEASE,
'release_url': helpers.anon_url(
'https://github.com/%s/%s/releases/tag/%s'
% (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.LATEST_RELEASE))
}
elif plexpy.UPDATE_AVAILABLE == 'commit':
return {'result': 'success',
'update': True,
'release': False,
'message': 'A newer version of Tautulli is available.',
'current_version': plexpy.CURRENT_VERSION,
'latest_version': plexpy.LATEST_VERSION,
'commits_behind': plexpy.COMMITS_BEHIND,
'compare_url': helpers.anon_url(
'https://github.com/%s/%s/compare/%s...%s'
% (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.CURRENT_VERSION,
plexpy.LATEST_VERSION))
update = {'result': 'success',
'update': True,
'release': False,
'message': 'A newer version of Tautulli is available.',
'current_version': plexpy.CURRENT_VERSION,
'latest_version': plexpy.LATEST_VERSION,
'commits_behind': plexpy.COMMITS_BEHIND,
'compare_url': helpers.anon_url(
'https://github.com/%s/%s/compare/%s...%s'
% (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.CURRENT_VERSION,
plexpy.LATEST_VERSION))
}
else:
return {'result': 'success',
'update': False,
'message': 'Tautulli is up to date.'
}
update = {'result': 'success',
'update': False,
'message': 'Tautulli is up to date.'
}
if plexpy.DOCKER:
update['docker'] = plexpy.DOCKER
return update
@cherrypy.expose
@requireAuth(member_of("admin"))
@ -3873,6 +3906,9 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth(member_of("admin"))
def update(self, **kwargs):
if plexpy.DOCKER:
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
# Show changelog after updating
plexpy.CONFIG.__setattr__('UPDATE_SHOW_CHANGELOG', 1)
plexpy.CONFIG.write()
@ -3891,18 +3927,30 @@ class WebInterface(object):
plexpy.CONFIG.write()
return self.do_state_change('checkout', 'Switching Git Branches', 120)
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def reset_git_install(self, **kwargs):
result = versioncheck.reset()
if result:
return {'result': 'success', 'message': 'Tautulli installation reset.'}
else:
return {'result': 'error', 'message': 'Reset installation failed.'}
@cherrypy.expose
@requireAuth(member_of("admin"))
def get_changelog(self, latest_only=False, since_prev_release=False, update_shown=False, **kwargs):
latest_only = (latest_only == 'true')
since_prev_release = (since_prev_release == 'true')
latest_only = helpers.bool_true(latest_only)
since_prev_release = helpers.bool_true(since_prev_release)
if since_prev_release and plexpy.PREV_RELEASE == common.RELEASE:
latest_only = True
since_prev_release = False
# Set update changelog shown status
if update_shown == 'true':
if helpers.bool_true(update_shown):
plexpy.CONFIG.__setattr__('UPDATE_SHOW_CHANGELOG', 0)
plexpy.CONFIG.write()
@ -3912,7 +3960,7 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth()
def info(self, rating_key=None, source=None, query=None, **kwargs):
def info(self, rating_key=None, guid=None, source=None, **kwargs):
if rating_key and not str(rating_key).isdigit():
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
@ -3925,7 +3973,7 @@ class WebInterface(object):
if source == 'history':
data_factory = datafactory.DataFactory()
metadata = data_factory.get_metadata_details(rating_key=rating_key)
metadata = data_factory.get_metadata_details(rating_key=rating_key, guid=guid)
if metadata:
poster_info = data_factory.get_poster_info(metadata=metadata)
metadata.update(poster_info)
@ -3945,12 +3993,12 @@ class WebInterface(object):
if metadata['section_id'] and not allow_session_library(metadata['section_id']):
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
return serve_template(templatename="info.html", data=metadata, title="Info", config=config, source=source)
return serve_template(templatename="info.html", metadata=metadata, title="Info", config=config, source=source)
else:
if get_session_user_id():
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
else:
return self.update_metadata(rating_key, query)
return self.update_metadata(rating_key)
@cherrypy.expose
@requireAuth()
@ -4046,10 +4094,10 @@ class WebInterface(object):
width (str): 300
height (str): 450
opacity (str): 25
background (str): 282828
background (str): Hex color, e.g. 282828
blur (str): 3
img_format (str): png
fallback (str): "poster", "cover", "art"
fallback (str): "poster", "cover", "art", "poster-live", "art-live", "art-live-full"
refresh (bool): True or False whether to refresh the image cache
return_hash (bool): True or False to return the self-hosted image hash instead of the image
@ -4058,20 +4106,27 @@ class WebInterface(object):
```
"""
if not img and not rating_key:
if fallback in common.DEFAULT_IMAGES:
fbi = common.DEFAULT_IMAGES[fallback]
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
return serve_file(path=fp, content_type='image/png')
logger.warn('No image input received.')
return
return_hash = (kwargs.get('return_hash') == 'true')
return_hash = helpers.bool_true(kwargs.get('return_hash'))
if rating_key and not img:
if fallback == 'art':
if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key)
else:
img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/')
img = '/'.join(img_split[:5])
rating_key = rating_key or img_split[3]
if img.startswith('/library/metadata'):
img_split = img.split('/')
img = '/'.join(img_split[:5])
img_rating_key = img_split[3]
if rating_key != img_rating_key:
rating_key = img_rating_key
img_hash = notification_handler.set_hash_image_info(
img=img, rating_key=rating_key, width=width, height=height,
@ -4088,7 +4143,7 @@ class WebInterface(object):
if not os.path.exists(c_dir):
os.mkdir(c_dir)
clip = True if clip == 'true' else False
clip = helpers.bool_true(clip)
try:
if not plexpy.CONFIG.CACHE_IMAGES or refresh or 'indexes' in img:
@ -4121,16 +4176,9 @@ class WebInterface(object):
raise Exception('PMS image request failed')
except Exception as e:
logger.warn('Failed to get image %s, falling back to %s.' % (img, fallback))
fbi = None
if fallback == 'poster':
fbi = common.DEFAULT_POSTER_THUMB
elif fallback == 'cover':
fbi = common.DEFAULT_COVER_THUMB
elif fallback == 'art':
fbi = common.DEFAULT_ART
if fbi:
logger.warn(u'Failed to get image %s, falling back to %s.' % (img, fallback))
if fallback in common.DEFAULT_IMAGES:
fbi = common.DEFAULT_IMAGES[fallback]
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
return serve_file(path=fp, content_type='image/png')
@ -4148,14 +4196,8 @@ class WebInterface(object):
img_hash = args[0].split('.')[0]
if img_hash in ('poster', 'cover', 'art'):
if img_hash == 'poster':
fbi = common.DEFAULT_POSTER_THUMB
elif img_hash == 'cover':
fbi = common.DEFAULT_COVER_THUMB
elif img_hash == 'art':
fbi = common.DEFAULT_ART
if img_hash in common.DEFAULT_IMAGES:
fbi = common.DEFAULT_IMAGES[img_hash]
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
return serve_file(path=fp, content_type='image/png')
@ -4304,7 +4346,7 @@ class WebInterface(object):
```
"""
delete_all = (delete_all == 'true')
delete_all = helpers.bool_true(delete_all)
data_factory = datafactory.DataFactory()
result = data_factory.delete_img_info(rating_key=rating_key, service=service, delete_all=delete_all)
@ -4417,7 +4459,7 @@ class WebInterface(object):
@requireAuth(member_of("admin"))
def update_metadata(self, rating_key=None, query=None, update=False, **kwargs):
query_string = query
update = True if update == 'True' else False
update = helpers.bool_true(update)
data_factory = datafactory.DataFactory()
query = data_factory.get_search_query(rating_key=rating_key)
@ -4590,6 +4632,7 @@ class WebInterface(object):
"labels": [],
"last_viewed_at": "1462165717",
"library_name": "TV Shows",
"live": 0,
"media_index": "1",
"media_info": [
{
@ -4599,6 +4642,9 @@ class WebInterface(object):
"audio_codec": "ac3",
"audio_profile": "",
"bitrate": "10617",
"channel_call_sign": "",
"channel_identifier": "",
"channel_thumb": "",
"container": "mkv",
"height": "1078",
"id": "257925",
@ -4617,6 +4663,10 @@ class WebInterface(object):
"video_bitrate": "10233",
"video_codec": "h264",
"video_codec_level": "41",
"video_color_primaries": "",
"video_color_range": "tv",
"video_color_space": "bt709",
"video_color_trc": "",
"video_frame_rate": "23.976",
"video_height": "1078",
"video_language": "",
@ -4676,7 +4726,7 @@ class WebInterface(object):
"rating_image": "rottentomatoes://image.rating.ripe",
"rating_key": "153037",
"section_id": "2",
"sort_title": "Game of Thrones",
"sort_title": "Red Woman",
"studio": "HBO",
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
"tagline": "",
@ -4719,22 +4769,59 @@ class WebInterface(object):
Returns:
json:
{"recently_added":
[{"added_at": "1461572396",
[{"actors": [
"Kit Harington",
"Emilia Clarke",
"Isaac Hempstead-Wright",
"Maisie Williams",
"Liam Cunningham",
],
"added_at": "1461572396",
"art": "/library/metadata/1219/art/1462175063",
"audience_rating": "8",
"audience_rating_image": "rottentomatoes://image.rating.upright",
"banner": "/library/metadata/1219/banner/1462175063",
"directors": [
"Jeremy Podeswa"
],
"duration": "2998290",
"full_title": "Game of Thrones - The Red Woman",
"genres": [
"Adventure",
"Drama",
"Fantasy"
],
"grandparent_rating_key": "1219",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"grandparent_title": "Game of Thrones",
"library_name": "",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"labels": [],
"last_viewed_at": "1462165717",
"library_name": "TV Shows",
"media_index": "1",
"media_type": "episode",
"original_title": "",
"originally_available_at": "2016-04-24",
"parent_media_index": "6",
"parent_rating_key": "153036",
"parent_thumb": "/library/metadata/153036/thumb/1462175062",
"parent_title": "",
"rating": "7.8",
"rating_image": "rottentomatoes://image.rating.ripe",
"rating_key": "153037",
"section_id": "2",
"sort_title": "Red Woman",
"studio": "HBO",
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
"tagline": "",
"thumb": "/library/metadata/153037/thumb/1462175060",
"title": "The Red Woman",
"user_rating": "9.0",
"updated_at": "1462175060",
"writers": [
"David Benioff",
"D. B. Weiss"
],
"year": "2016"
},
{...},
@ -4957,7 +5044,11 @@ class WebInterface(object):
"banner": "/library/metadata/1219/banner/1503306930",
"bif_thumb": "/library/parts/274169/indexes/sd/1000",
"bitrate": "10617",
"channel_call_sign": "",
"channel_identifier": "",
"channel_stream": 0,
"channel_thumb": "",
"children_count": "",
"collections": [],
"container": "mkv",
"content_rating": "TV-MA",
@ -4989,13 +5080,15 @@ class WebInterface(object):
"ip_address": "10.10.10.1",
"ip_address_public": "64.123.23.111",
"is_admin": 1,
"is_allow_sync": null,
"is_allow_sync": 1,
"is_home_user": 1,
"is_restricted": 0,
"keep_history": 1,
"labels": [],
"last_viewed_at": "1462165717",
"library_name": "TV Shows",
"live": 0,
"live_uuid": "",
"local": "1",
"location": "lan",
"machine_id": "lmd93nkn12k29j2lnm",
@ -5004,8 +5097,8 @@ class WebInterface(object):
"optimized_version": 0,
"optimized_version_profile": "",
"optimized_version_title": "",
"originally_available_at": "2016-04-24",
"original_title": "",
"originally_available_at": "2016-04-24",
"parent_guid": "com.plexapp.agents.thetvdb://121361/6?lang=en",
"parent_media_index": "6",
"parent_rating_key": "153036",
@ -5025,6 +5118,7 @@ class WebInterface(object):
"rating_key": "153037",
"relay": 0,
"section_id": "2",
"secure": 1,
"session_id": "helf15l3rxgw01xxe0jf3l3d",
"session_key": "27",
"shared_libraries": [
@ -5063,15 +5157,21 @@ class WebInterface(object):
"stream_subtitle_location": "",
"stream_video_bit_depth": "8",
"stream_video_bitrate": "10233",
"stream_video_chroma_subsampling": "4:2:0",
"stream_video_codec": "h264",
"stream_video_codec_level": "41",
"stream_video_color_primaries": "",
"stream_video_color_range": "tv",
"stream_video_color_space": "bt709",
"stream_video_color_trc": "",
"stream_video_decision": "direct play",
"stream_video_dynamic_range": "SDR",
"stream_video_framerate": "24p",
"stream_video_full_resolution": "1080p",
"stream_video_height": "1078",
"stream_video_language": "",
"stream_video_language_code": "",
"stream_video_ref_frames": "4",
"stream_video_full_resolution": "1080p",
"stream_video_resolution": "1080",
"stream_video_scan_type": "progressive",
"stream_video_width": "1920",
@ -5121,9 +5221,15 @@ class WebInterface(object):
"username": "LordCommanderSnow",
"video_bit_depth": "8",
"video_bitrate": "10233",
"video_chroma_subsampling": "4:2:0",
"video_codec": "h264",
"video_codec_level": "41",
"video_color_primaries": "",
"video_color_range": "tv",
"video_color_space": "bt709",
"video_color_trc": ",
"video_decision": "direct play",
"video_dynamic_range": "SDR",
"video_frame_rate": "23.976",
"video_framerate": "24p",
"video_full_resolution": "1080p",
@ -5377,8 +5483,10 @@ class WebInterface(object):
[{"content_rating": "TV-MA",
"friendly_name": "",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"labels": [],
"last_play": 1462380698,
"live": 0,
"media_type": "episode",
"platform": "",
"platform_type": "",
@ -5884,8 +5992,8 @@ class WebInterface(object):
subject=newsletter['subject'],
body=newsletter['body'],
message=newsletter['message'])
preview = (preview == 'true')
raw = (raw == 'true')
preview = helpers.bool_true(preview)
raw = helpers.bool_true(raw)
if raw:
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'