diff --git a/data/interfaces/default/current_activity_instance.html b/data/interfaces/default/current_activity_instance.html
index b7bfa25c..608810ba 100644
--- a/data/interfaces/default/current_activity_instance.html
+++ b/data/interfaces/default/current_activity_instance.html
@@ -162,7 +162,15 @@ DOCUMENTATION :: END
Optimized
- ${data['optimized_version_profile']}
+ ${data['optimized_version_profile']} (${data['optimized_version_title']})
+
+
+ % endif
+ % if data['synced_version'] == 1:
+
+ Synced
+
+ ${data['synced_version_profile']}
% endif
@@ -185,7 +193,7 @@ DOCUMENTATION :: END
% elif data['transcode_decision'] == 'copy':
Direct Stream
% else:
- Direct Play ${'(Synced)' if data['synced_version'] == 1 else ''}
+ Direct Play
% endif
@@ -250,7 +258,7 @@ DOCUMENTATION :: END
% elif data['stream_subtitle_decision'] == 'burn':
Burn (${data['subtitle_codec'].upper()})
% else:
- Direct Play (${data['subtitle_codec'].upper()})
+ Direct Play (${data['stream_subtitle_codec'].upper() if data['synced_version'] else data['subtitle_codec'].upper()})
% endif
% else:
None
diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html
index a76e48b1..6d722f98 100644
--- a/data/interfaces/default/index.html
+++ b/data/interfaces/default/index.html
@@ -257,7 +257,7 @@
if (!(current_activity)) {
% if _session['user_group'] == 'admin':
- var msg_settings = ' Verify your server in the settings.';
+ var msg_settings = ' Verify your server connection in the settings.';
% else:
var msg_settings = ''
% endif
@@ -357,7 +357,7 @@
} else if (s.transcode_decision === 'copy') {
transcode_decision = 'Direct Stream';
} else {
- transcode_decision = 'Direct Play' + ((s.synced_version == 1) ? ' (Synced)' : '');
+ transcode_decision = 'Direct Play';
}
$('#transcode_decision-' + key).html(transcode_decision);
@@ -434,7 +434,7 @@
} else if (s.stream_subtitle_decision === 'burn') {
subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')';
} else {
- subtitle_decision = 'Direct Play (' + s.subtitle_codec.toUpperCase() + ')';
+ subtitle_decision = 'Direct Play (' + ((s.synced_version == '1') ? s.stream_subtitle_codec.toUpperCase() : s.subtitle_codec.toUpperCase()) + ')';
}
}
$('#subtitle_decision-' + key).html(subtitle_decision);
@@ -453,7 +453,8 @@
} else {
$('#stream_quality-' + key).html(s.quality_profile);
}
- $('#optimized_version-' + key).html(s.optimized_version_profile);
+ $('#optimized_version-' + key).html(s.optimized_version_profile + ' (' + s.optimized_version_title + ')');
+ $('#synced_quality_profile-' + key).html(s.synced_quality_profile);
if (s.media_type != 'photo' && parseInt(s.bandwidth)) {
var bw = parseInt(s.bandwidth);
diff --git a/data/interfaces/default/stream_data.html b/data/interfaces/default/stream_data.html
index a890efe0..548845d9 100644
--- a/data/interfaces/default/stream_data.html
+++ b/data/interfaces/default/stream_data.html
@@ -98,14 +98,14 @@ DOCUMENTATION :: END
Optimized Version |
- |
- ${data['optimized_version_profile']} |
+ ${data['optimized_version_profile']} (${data['optimized_version_title']}) |
% endif
% if data['synced_version'] == 1:
Synced Version |
- |
- yes |
+ ${data['synced_version_profile']} |
% endif
diff --git a/plexpy/__init__.py b/plexpy/__init__.py
index 47ffe411..ec243428 100644
--- a/plexpy/__init__.py
+++ b/plexpy/__init__.py
@@ -450,7 +450,8 @@ def dbcheck():
'transcode_protocol TEXT, transcode_container TEXT, '
'transcode_video_codec TEXT, transcode_audio_codec TEXT, transcode_audio_channels INTEGER,'
'transcode_width INTEGER, transcode_height INTEGER, '
- 'optimized_version INTEGER, optimized_version_profile TEXT, synced_version INTEGER, '
+ 'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT, '
+ 'synced_version INTEGER, synced_version_profile TEXT, '
'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, write_attempts INTEGER DEFAULT 0, '
'raw_stream_info TEXT)'
)
@@ -482,7 +483,8 @@ def dbcheck():
'stream_video_framerate TEXT, '
'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, '
'stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, stream_subtitle_container TEXT, stream_subtitle_forced INTEGER, '
- 'subtitles INTEGER, subtitle_codec TEXT, synced_version INTEGER, optimized_version INTEGER, optimized_version_profile TEXT)'
+ 'subtitles INTEGER, subtitle_codec TEXT, synced_version INTEGER, synced_version_profile TEXT, '
+ 'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT)'
)
# session_history_metadata table :: This is a table which logs each session's media metadata
@@ -892,6 +894,15 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN raw_stream_info TEXT'
)
+ # Upgrade sessions table from earlier versions
+ try:
+ c_db.execute('SELECT video_height FROM sessions')
+ except sqlite3.OperationalError:
+ logger.debug(u"Altering database. Updating database table sessions.")
+ c_db.execute(
+ 'ALTER TABLE sessions ADD COLUMN video_height INTEGER'
+ )
+
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT subtitles FROM sessions')
@@ -900,13 +911,17 @@ def dbcheck():
c_db.execute(
'ALTER TABLE sessions ADD COLUMN subtitles INTEGER'
)
+
# Upgrade sessions table from earlier versions
try:
- c_db.execute('SELECT video_height FROM sessions')
+ c_db.execute('SELECT synced_version_profile FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
- 'ALTER TABLE sessions ADD COLUMN video_height INTEGER'
+ 'ALTER TABLE sessions ADD COLUMN synced_version_profile TEXT'
+ )
+ c_db.execute(
+ 'ALTER TABLE sessions ADD COLUMN optimized_version_title TEXT'
)
# Upgrade session_history table from earlier versions
@@ -1139,6 +1154,18 @@ def dbcheck():
'ALTER TABLE session_history_media_info ADD COLUMN subtitle_codec TEXT '
)
+ # Upgrade session_history_media_info table from earlier versions
+ try:
+ c_db.execute('SELECT synced_version_profile FROM session_history_media_info')
+ except sqlite3.OperationalError:
+ logger.debug(u"Altering database. Updating database table session_history_media_info.")
+ c_db.execute(
+ 'ALTER TABLE session_history_media_info ADD COLUMN synced_version_profile TEXT '
+ )
+ c_db.execute(
+ 'ALTER TABLE session_history_media_info ADD COLUMN optimized_version_title TEXT '
+ )
+
# Upgrade users table from earlier versions
try:
c_db.execute('SELECT do_notify FROM users')
diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py
index b62cd4fc..87bccc90 100644
--- a/plexpy/activity_processor.py
+++ b/plexpy/activity_processor.py
@@ -90,9 +90,11 @@ class ActivityProcessor(object):
'transcode_audio_channels': session.get('transcode_audio_channels', ''),
'transcode_width': session.get('stream_video_width', ''),
'transcode_height': session.get('stream_video_height', ''),
+ 'synced_version': session.get('synced_version', ''),
+ 'synced_version_profile': session.get('synced_version_profile', ''),
'optimized_version': session.get('optimized_version', ''),
'optimized_version_profile': session.get('optimized_version_profile', ''),
- 'synced_version': session.get('synced_version', ''),
+ 'optimized_version_title': session.get('optimized_version_title', ''),
'stream_bitrate': session.get('stream_bitrate', ''),
'stream_video_resolution': session.get('stream_video_resolution', ''),
'quality_profile': session.get('quality_profile', ''),
@@ -110,6 +112,7 @@ class ActivityProcessor(object):
'stream_audio_channels': session.get('stream_audio_channels', ''),
'stream_subtitle_decision': session.get('stream_subtitle_decision', ''),
'stream_subtitle_codec': session.get('stream_subtitle_codec', ''),
+ 'subtitles': session.get('subtitles', ''),
'raw_stream_info': json.dumps(session),
'stopped': int(time.time())
}
@@ -356,6 +359,8 @@ class ActivityProcessor(object):
'stream_subtitle_forced': session['stream_subtitle_forced'],
'subtitles': session['subtitles'],
'synced_version': session['synced_version'],
+ 'synced_version_profile': session['synced_version_profile'],
+ 'synced_version_title': session['synced_version_title'],
'optimized_version': session['optimized_version'],
'optimized_version_profile': session['optimized_version_profile']
}
diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py
index ce9bbfa1..856645ac 100644
--- a/plexpy/datafactory.py
+++ b/plexpy/datafactory.py
@@ -872,8 +872,10 @@ class DataFactory(object):
user_cond = 'AND %s.user_id = %s ' % (table, session.get_session_user_id())
if row_id:
- query = 'SELECT bitrate, video_resolution, optimized_version, optimized_version_profile, synced_version, ' \
- 'container, video_codec, video_Bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \
+ query = 'SELECT bitrate, video_resolution, ' \
+ 'optimized_version, optimized_version_profile, optimized_version_title, ' \
+ 'synced_version, synced_version_profile, ' \
+ 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \
'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \
'stream_bitrate, stream_video_resolution, quality_profile, stream_container_decision, stream_container, ' \
'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \
@@ -887,8 +889,10 @@ class DataFactory(object):
'WHERE session_history_media_info.id = ? %s' % user_cond
result = monitor_db.select(query, args=[row_id])
elif session_key:
- query = 'SELECT bitrate, video_resolution, optimized_version, optimized_version_profile, synced_version, ' \
- 'container, video_codec, video_Bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \
+ query = 'SELECT bitrate, video_resolution, ' \
+ 'optimized_version, optimized_version_profile, optimized_version_title, ' \
+ 'synced_version, synced_version_profile, ' \
+ 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \
'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \
'stream_bitrate, stream_video_resolution, quality_profile, stream_container_decision, stream_container, ' \
'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \
@@ -909,7 +913,9 @@ class DataFactory(object):
'video_resolution': item['video_resolution'],
'optimized_version': item['optimized_version'],
'optimized_version_profile': item['optimized_version_profile'],
+ 'optimized_version_title': item['optimized_version_title'],
'synced_version': item['synced_version'],
+ 'synced_version_profile': item['synced_version_profile'],
'container': item['container'],
'video_codec': item['video_codec'],
'video_bitrate': item['video_bitrate'],
diff --git a/plexpy/plextv.py b/plexpy/plextv.py
index 80ae3b6b..3782fc1c 100644
--- a/plexpy/plextv.py
+++ b/plexpy/plextv.py
@@ -425,7 +425,7 @@ class PlexTV(object):
return users_list
- def get_synced_items(self, machine_id=None, user_id=None):
+ def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None, rating_key_filter=None):
sync_list = self.get_plextv_sync_lists(machine_id)
user_data = users.Users()
@@ -446,9 +446,15 @@ class PlexTV(object):
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items.")
else:
for a in xml_head:
- sync_id = helpers.get_xml_attr(a, 'id')
client_id = helpers.get_xml_attr(a, 'clientIdentifier')
+
+ # Filter by client_id
+ if client_id_filter and client_id_filter != client_id:
+ continue
+
+ sync_id = helpers.get_xml_attr(a, 'id')
sync_device = a.getElementsByTagName('Device')
+
for device in sync_device:
device_user_id = helpers.get_xml_attr(device, 'userID')
try:
@@ -467,12 +473,23 @@ class PlexTV(object):
device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt')
# Filter by user_id
- if user_id and user_id != device_user_id:
+ if user_id_filter and user_id_filter != device_user_id:
continue
for synced in a.getElementsByTagName('SyncItems'):
sync_item = synced.getElementsByTagName('SyncItem')
for item in sync_item:
+
+ for location in item.getElementsByTagName('Location'):
+ clean_uri = helpers.get_xml_attr(location, 'uri').split('%2F')
+
+ rating_key = next((clean_uri[(idx + 1) % len(clean_uri)]
+ for idx, item in enumerate(clean_uri) if item == 'metadata'), None)
+
+ # Filter by rating_key
+ if rating_key_filter and rating_key_filter != rating_key:
+ continue
+
sync_id = helpers.get_xml_attr(item, 'id')
sync_version = helpers.get_xml_attr(item, 'version')
sync_root_title = helpers.get_xml_attr(item, 'rootTitle')
@@ -501,12 +518,6 @@ class PlexTV(object):
settings_video_quality = helpers.get_xml_attr(settings, 'videoQuality')
settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution')
- for location in item.getElementsByTagName('Location'):
- clean_uri = helpers.get_xml_attr(location, 'uri').split('%2F')
-
- rating_key = next((clean_uri[(idx + 1) % len(clean_uri)]
- for idx, item in enumerate(clean_uri) if item == 'metadata'), None)
-
sync_details = {"device_name": helpers.sanitize(device_name),
"platform": helpers.sanitize(device_platform),
"username": helpers.sanitize(device_username),
diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py
index 4f255d9b..96f5e224 100644
--- a/plexpy/pmsconnect.py
+++ b/plexpy/pmsconnect.py
@@ -24,6 +24,7 @@ import helpers
import http_handler
import libraries
import logger
+import plextv
import session
import users
@@ -590,7 +591,7 @@ class PmsConnect(object):
return output
- def get_metadata_details(self, rating_key=''):
+ def get_metadata_details(self, rating_key='', sync_id=''):
"""
Return processed and validated metadata list for requested item.
@@ -598,7 +599,10 @@ class PmsConnect(object):
Output: array
"""
- metadata = self.get_metadata(str(rating_key), output_format='xml')
+ if rating_key:
+ metadata = self.get_metadata(str(rating_key), output_format='xml')
+ elif sync_id:
+ metadata = self.get_sync_item(str(sync_id), output_format='xml')
try:
xml_head = metadata.getElementsByTagName('MediaContainer')
@@ -1322,6 +1326,7 @@ class PmsConnect(object):
# Get the source media type
media_type = helpers.get_xml_attr(session, 'type')
+ rating_key = helpers.get_xml_attr(session, 'ratingKey')
# Get the user details
user_info = session.getElementsByTagName('User')[0]
@@ -1348,7 +1353,7 @@ class PmsConnect(object):
'product_version': helpers.get_xml_attr(player_info, 'version'),
'profile': helpers.get_xml_attr(player_info, 'profile'),
'player': helpers.get_xml_attr(player_info, 'title') or helpers.get_xml_attr(player_info, 'product'),
- 'machine_id': helpers.get_xml_attr(player_info, 'machineIdentifier').rstrip('_Video').rstrip('_Track'),
+ 'machine_id': helpers.get_xml_attr(player_info, 'machineIdentifier'),
'state': helpers.get_xml_attr(player_info, 'state'),
'local': helpers.get_xml_attr(player_info, 'local')
}
@@ -1431,14 +1436,29 @@ class PmsConnect(object):
# Determine if a synced version is being played
if media_type not in ('photo', 'clip') and not session.getElementsByTagName('Session') \
and helpers.get_xml_attr(session, 'ratingKey').isdigit() and transcode_decision == 'direct play':
- synced_version = 1
+ plex_tv = plextv.PlexTV()
+ synced_items = plex_tv.get_synced_items(machine_id=plexpy.CONFIG.PMS_IDENTIFIER,
+ client_id_filter=player_details['machine_id'],
+ rating_key_filter=rating_key)
+ if synced_items:
+ sync_id = synced_items[0]['sync_id']
+ synced_xml = self.get_sync_item(sync_id=sync_id, output_format='xml')
+ synced_xml_head = synced_xml.getElementsByTagName('MediaContainer')
+ if synced_xml_head[0].getElementsByTagName('Track'):
+ synced_session_data = synced_xml_head[0].getElementsByTagName('Track')[0]
+ elif synced_xml_head[0].getElementsByTagName('Video'):
+ synced_session_data = synced_xml_head[0].getElementsByTagName('Video')[0]
else:
- synced_version = 0
+ sync_id = None
# Figure out which version is being played
- media_info_all = session.getElementsByTagName('Media')
+ if sync_id:
+ media_info_all = synced_session_data.getElementsByTagName('Media')
+ else:
+ media_info_all = session.getElementsByTagName('Media')
stream_media_info = next((m for m in media_info_all if helpers.get_xml_attr(m, 'selected') == '1'), media_info_all[0])
- stream_media_parts_info = stream_media_info.getElementsByTagName('Part')[0]
+ part_info_all = stream_media_info.getElementsByTagName('Part')
+ stream_media_parts_info = next((p for p in part_info_all if helpers.get_xml_attr(p, 'selected') == '1'), part_info_all[0])
# Get the stream details
video_stream_info = audio_stream_info = subtitle_stream_info = None
@@ -1495,6 +1515,7 @@ class PmsConnect(object):
if subtitle_stream_info:
subtitle_id = helpers.get_xml_attr(subtitle_stream_info, 'id')
+ subtitle_selected = helpers.get_xml_attr(subtitle_stream_info, 'selected')
subtitle_details = {'stream_subtitle_codec': helpers.get_xml_attr(subtitle_stream_info, 'codec'),
'stream_subtitle_container': helpers.get_xml_attr(subtitle_stream_info, 'container'),
'stream_subtitle_format': helpers.get_xml_attr(subtitle_stream_info, 'format'),
@@ -1544,14 +1565,14 @@ class PmsConnect(object):
'stream_video_height': helpers.get_xml_attr(stream_media_info, 'height'),
'stream_video_width': helpers.get_xml_attr(stream_media_info, 'width'),
'stream_duration': helpers.get_xml_attr(stream_media_info, 'duration') or helpers.get_xml_attr(session, 'duration'),
- 'stream_container_decision': helpers.get_xml_attr(stream_media_parts_info, 'decision').replace('directplay', 'direct play'),
+ 'stream_container_decision': 'direct play' if sync_id else helpers.get_xml_attr(stream_media_parts_info, 'decision').replace('directplay', 'direct play'),
'transcode_decision': transcode_decision,
'optimized_version': 1 if helpers.get_xml_attr(stream_media_info, 'proxyType') == '42' else 0,
- 'optimized_version_profile': helpers.get_xml_attr(stream_media_info, 'title'),
- 'synced_version': synced_version,
+ 'optimized_version_title': helpers.get_xml_attr(stream_media_info, 'title'),
+ 'synced_version': 1 if sync_id else 0,
'indexes': 1 if indexes == 'sd' else 0,
'bif_thumb': bif_thumb,
- 'subtitles': 1 if subtitle_id else 0
+ 'subtitles': 1 if subtitle_id and subtitle_selected else 0
}
# Get the source media info
@@ -1617,7 +1638,10 @@ class PmsConnect(object):
media_id = helpers.get_xml_attr(stream_media_info, 'id')
part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
- metadata_details = self.get_metadata_details(rating_key=helpers.get_xml_attr(session, 'ratingKey'))
+ if sync_id:
+ metadata_details = self.get_metadata_details(sync_id=sync_id)
+ else:
+ metadata_details = self.get_metadata_details(rating_key=rating_key)
# Get the media info, fallback to first item if match id is not found
source_medias = metadata_details.pop('media_info', [])
@@ -1682,7 +1706,22 @@ class PmsConnect(object):
quality_profile = common.VIDEO_QUALITY_PROFILES[quailtiy_bitrate]
except ValueError:
quality_profile = 'Original'
-
+
+ if sync_id:
+ try:
+ synced_bitrate = min(b for b in common.VIDEO_QUALITY_PROFILES if source_bitrate <= b)
+ synced_version_profile = common.VIDEO_QUALITY_PROFILES[synced_bitrate]
+ except ValueError:
+ synced_version_profile = 'Original'
+ else:
+ synced_version_profile = ''
+
+ if stream_details['optimized_version']:
+ optimized_version_profile = '{} Mbps {}'.format(round(source_bitrate / 1000.0, 1),
+ plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(source_media_details['video_resolution'], source_media_details['video_resolution']))
+ else:
+ optimized_version_profile = ''
+
elif media_type == 'track' and 'stream_bitrate' in stream_details:
stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate'])
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
@@ -1693,11 +1732,26 @@ class PmsConnect(object):
except ValueError:
quality_profile = 'Original'
+ if sync_id:
+ try:
+ synced_bitrate = min(b for b in common.AUDIO_QUALITY_PROFILES if source_bitrate <= b)
+ synced_version_profile = common.AUDIO_QUALITY_PROFILES[synced_bitrate]
+ except ValueError:
+ synced_version_profile = 'Original'
+ else:
+ synced_version_profile = ''
+
+ optimized_version_profile = ''
+
elif media_type == 'photo':
quality_profile = 'Original'
+ synced_version_profile = ''
+ optimized_version_profile = ''
else:
quality_profile = 'Unknown'
+ synced_version_profile = ''
+ optimized_version_profile = ''
# Entire session output (single dict for backwards compatibility)
session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'),
@@ -1705,6 +1759,8 @@ class PmsConnect(object):
'view_offset': view_offset,
'progress_percent': str(helpers.get_percent(view_offset, stream_details['stream_duration'])),
'quality_profile': quality_profile,
+ 'synced_version_profile': synced_version_profile,
+ 'optimized_version_profile': optimized_version_profile,
'user': user_details['username'], # Keep for backwards compatibility
'channel_stream': channel_stream
}
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index da4086ab..dd6939fe 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -2195,7 +2195,7 @@ class WebInterface(object):
machine_id = plexpy.CONFIG.PMS_IDENTIFIER
plex_tv = plextv.PlexTV()
- result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id)
+ result = plex_tv.get_synced_items(machine_id=machine_id, user_id_filter=user_id)
if result:
output = {"data": result}
@@ -4560,7 +4560,7 @@ class WebInterface(object):
```
"""
plex_tv = plextv.PlexTV()
- result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id)
+ result = plex_tv.get_synced_items(machine_id=machine_id, user_id_filter=user_id)
if result:
return result