diff --git a/data/interfaces/default/current_activity_instance.html b/data/interfaces/default/current_activity_instance.html index 9b8253ff..2e840763 100644 --- a/data/interfaces/default/current_activity_instance.html +++ b/data/interfaces/default/current_activity_instance.html @@ -75,36 +75,36 @@ DOCUMENTATION :: END % endif
% if not data['art'].startswith('interfaces') or not data['thumb'].startswith('interfaces'): - % if (data['media_type'] == 'movie' and not data['indexes']) or (data['indexes'] and not data['view_offset']): -
- % elif (data['media_type'] == 'episode' and not data['indexes']) or (data['indexes'] and not data['view_offset']): -
- % elif data['indexes']: - - % else: - % if data['media_type'] == 'track': -
-
- % elif data['media_type'] == 'clip': - % if data['art'].startswith('http'): -
- % elif data['thumb'].startswith('http'): -
- % else: - % if data['art']: - -
- % else: - -
- % endif - % endif - % elif data['media_type'] == 'photo': -
- % else: -
- % endif - % endif + % if (data['media_type'] == 'movie' and not data['indexes']) or (data['indexes'] and not data['view_offset']): +
+ % elif (data['media_type'] == 'episode' and not data['indexes']) or (data['indexes'] and not data['view_offset']): +
+ % elif data['indexes']: + + % else: + % if data['media_type'] == 'track': +
+
+ % elif data['media_type'] == 'clip': + % if data['art'].startswith('http'): +
+ % elif data['thumb'].startswith('http'): +
+ % else: + % if data['art']: + +
+ % else: + +
+ % endif + % endif + % elif data['media_type'] == 'photo': +
+ % else: +
+ % endif + % endif % else:
% endif diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 7f887eda..f5958390 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1586,13 +1586,21 @@ {username} The username of the person streaming. + + {device} + The type of client device being used for playback. + {platform} - The type of client being used for playback. + The type of client platform being used for playback. + + + {product} + The type of client product being used for playback. {player} - The name of the device being used for playback. + The name of the player being used for playback. {ip_address} @@ -1644,12 +1652,16 @@ {quality_profile} - The Plex quality profile of the stream. (e.g. 4 Mbps 720p) + The Plex quality profile of the stream. (e.g. Original, 4 Mbps 720p, etc.) {optimized_version} If the stream is an optimized version. (0 or 1) + + {optimized_version_profile} + The optimized version profile of the stream. + {stream_local} If the stream is local. (0 or 1) @@ -1681,14 +1693,26 @@ {stream_video_codec} The video codec of the stream. + + {stream_video_codec_level} + The video codec level of the stream. + {stream_video_bitrate} The video bitrate (in kbps) of the stream. + + {stream_video_bit_depth} + The video bit depth of the stream. + {stream_video_framerate} The video framerate of the stream. + + {stream_video_ref_frames} + The video reference frames of the stream. + {stream_video_resolution} The video resolution of the stream. @@ -1715,7 +1739,7 @@ {stream_audio_bitrate_mode} - The audio bitrage mode of the stream. (cbr or vbr) + The audio bitrate mode of the stream. (cbr or vbr) {stream_audio_codec} @@ -1725,6 +1749,14 @@ {stream_audio_channels} The audio channels of the stream. + + {stream_audio_channel_layout} + The audio channel layout of the stream. + + + {stream_audio_sample_rate} + The audio sample rate (in Hz) of the stream. + {stream_audio_language} The audio language of the stream. @@ -1745,6 +1777,10 @@ {stream_subtitle_format} The subtitle format of the stream. + + {stream_subtitle_forced} + If the subtitles are forced. (0 or 1) + {stream_subtitle_language} The subtitle language of the stream. @@ -1820,46 +1856,6 @@ {media_type} The type of media. (movie, show, season, episode, artist, album, track) - - {container} - The media container of the original media. - - - {aspect_ratio} - The aspect ratio of the original media. - - - {video_codec} - The video codec of the original media. - - - {video_bitrate} - The video bitrate of the original media. - - - {video_framerate} - The video framerate of the original media. - - - {video_resolution} - The video resolution of the original media. - - - {video_width} - The video width of the original media. - - - {video_height} - The video height of the original media. - - - {audio_codec} - The audio codec of the original media. - - - {audio_channels} - The audio channels of the original media. - {title} The full title of the item. @@ -2037,6 +2033,118 @@ {trakt_url} The trakt.tv URL for the movie or TV show. + + {container} + The media container of the original media. + + + {bitrate} + The bitrate of the original media. + + + {aspect_ratio} + The aspect ratio of the original media. + + + {video_codec} + The video codec of the original media. + + + {video_codec_level} + The video codec level of the original media. + + + {video_bitrate} + The video bitrate of the original media. + + + {video_bit_depth} + The video bit depth of the original media. + + + {video_framerate} + The video framerate of the original media. + + + {video_ref_frames} + The video reference frames of the original media. + + + {video_resolution} + The video resolution of the original media. + + + {video_height} + The video height of the original media. + + + {video_width} + The video width of the original media. + + + {video_language} + The video language of the original media. + + + {video_language_code} + The video language code of the original media. + + + {audio_bitrate} + The audio bitrate of the original media. + + + {audio_bitrate_mode} + The audio bitrate mode of the original media. (cbr or vbr) + + + {audio_codec} + The audio codec of the original media. + + + {audio_channels} + The audio channels of the original media. + + + {audio_channel_layout} + The audio channel layout of the original media. + + + {audio_language} + The audio language of the original media. + + + {audio_language_code} + The audio language code of the original media. + + + {subtitle_codec} + The subtitle codec of the original media. + + + {subtitle_container} + The subtitle container of the original media. + + + {subtitle_format} + The subtitle format of the original media. + + + {subtitle_forced} + If the subtitles are forced. (0 or 1) + + + {subtitle_location} + The subtitle location of the original media. + + + {subtitle_language} + The subtitle language of the original media. + + + {subtitle_language_code} + The subtitle language code of the original media. + {file} The file path to the item. diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index cdadda65..ac74e8b0 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -36,69 +36,69 @@ class ActivityProcessor(object): def write_session(self, session=None, notify=True): if session: - values = {'session_key': session['session_key'], - 'transcode_key': session['transcode_key'], - 'section_id': session['section_id'], - 'rating_key': session['rating_key'], - 'media_type': session['media_type'], - 'state': session['state'], - 'user_id': session['user_id'], - 'user': session['user'], - 'machine_id': session['machine_id'], - 'title': session['title'], - 'parent_title': session['parent_title'], - 'grandparent_title': session['grandparent_title'], - 'full_title': session['full_title'], - 'media_index': session['media_index'], - 'parent_media_index': session['parent_media_index'], - 'thumb': session['thumb'], - 'parent_thumb': session['parent_thumb'], - 'grandparent_thumb': session['grandparent_thumb'], - 'year': session['year'], - 'friendly_name': session['friendly_name'], - #'ip_address': session['ip_address'], - 'player': session['player'], - 'platform': session['platform'], - 'parent_rating_key': session['parent_rating_key'], - 'grandparent_rating_key': session['grandparent_rating_key'], - 'view_offset': session['view_offset'], - 'duration': session['duration'], - 'video_decision': session['video_decision'], - 'audio_decision': session['audio_decision'], - 'transcode_decision': session['transcode_decision'], - 'width': session['width'], - 'height': session['height'], - 'container': session['container'], - 'video_codec': session['video_codec'], - 'audio_codec': session['audio_codec'], - 'bitrate': session['bitrate'], - 'video_resolution': session['video_resolution'], - 'video_framerate': session['video_framerate'], - 'aspect_ratio': session['aspect_ratio'], - 'audio_channels': session['audio_channels'], - 'transcode_protocol': session['transcode_protocol'], - 'transcode_container': session['transcode_container'], - 'transcode_video_codec': session['transcode_video_codec'], - 'transcode_audio_codec': session['transcode_audio_codec'], - 'transcode_audio_channels': session['transcode_audio_channels'], - 'transcode_width': session['transcode_width'], - 'transcode_height': session['transcode_height'], + values = {'session_key': session.get('session_key', ''), + 'transcode_key': session.get('transcode_key', ''), + 'section_id': session.get('section_id', ''), + 'rating_key': session.get('rating_key', ''), + 'media_type': session.get('media_type', ''), + 'state': session.get('state', ''), + 'user_id': session.get('user_id', ''), + 'user': session.get('user', ''), + 'machine_id': session.get('machine_id', ''), + 'title': session.get('title', ''), + 'parent_title': session.get('parent_title', ''), + 'grandparent_title': session.get('grandparent_title', ''), + 'full_title': session.get('full_title', ''), + 'media_index': session.get('media_index', ''), + 'parent_media_index': session.get('parent_media_index', ''), + 'thumb': session.get('thumb', ''), + 'parent_thumb': session.get('parent_thumb', ''), + 'grandparent_thumb': session.get('grandparent_thumb', ''), + 'year': session.get('year', ''), + 'friendly_name': session.get('friendly_name', ''), + #'ip_address': session.get('ip_address', ''), + 'player': session.get('player', ''), + 'platform': session.get('platform', ''), + 'parent_rating_key': session.get('parent_rating_key', ''), + 'grandparent_rating_key': session.get('grandparent_rating_key', ''), + 'view_offset': session.get('view_offset', ''), + 'duration': session.get('duration', ''), + 'video_decision': session.get('video_decision', ''), + 'audio_decision': session.get('audio_decision', ''), + 'transcode_decision': session.get('transcode_decision', ''), + 'width': session.get('width', ''), + 'height': session.get('height', ''), + 'container': session.get('container', ''), + 'video_codec': session.get('video_codec', ''), + 'audio_codec': session.get('audio_codec', ''), + 'bitrate': session.get('bitrate', ''), + 'video_resolution': session.get('video_resolution', ''), + 'video_framerate': session.get('video_framerate', ''), + 'aspect_ratio': session.get('aspect_ratio', ''), + 'audio_channels': session.get('audio_channels', ''), + 'transcode_protocol': session.get('transcode_protocol', ''), + 'transcode_container': session.get('transcode_container', ''), + 'transcode_video_codec': session.get('transcode_video_codec', ''), + 'transcode_audio_codec': session.get('transcode_audio_codec', ''), + 'transcode_audio_channels': session.get('transcode_audio_channels', ''), + 'transcode_width': session.get('transcode_width', ''), + 'transcode_height': session.get('transcode_height', ''), 'stopped': None } # Add ip_address back into values if session['ip_address']: - values.update({'ip_address': session['ip_address']}) + values.update({'ip_address': session.get('ip_address', 'N/A')}) - keys = {'session_key': session['session_key'], - 'rating_key': session['rating_key']} + keys = {'session_key': session.get('session_key', ''), + 'rating_key': session.get('rating_key', '')} result = self.db.upsert('sessions', values, keys) if result == 'insert': # Check if any notification agents have notifications enabled if notify: - values.update({'ip_address': session['ip_address']}) + values.update({'ip_address': session.get('ip_address', 'N/A')}) plexpy.NOTIFY_QUEUE.put({'stream_data': values, 'notify_action': 'on_play'}) # If it's our first write then time stamp it. diff --git a/plexpy/libraries.py b/plexpy/libraries.py index bd5ca6cb..2e5b479d 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -404,9 +404,10 @@ class Libraries(object): for item in children_list: ## TODO: Check list of media info items, currently only grabs first item media_info = item['media_info'][0] if item['media_info'] else {} + media_part_info = media_info['parts'][0] if media_info['parts'] else {} cached_file_size = cached_items.get(item['rating_key'], None) - file_size = cached_file_size if cached_file_size else media_info.get('file_size', '') + file_size = cached_file_size if cached_file_size else media_part_info.get('file_size', '') row = {'section_id': library_details['section_id'], 'section_type': library_details['section_type'], @@ -567,6 +568,7 @@ class Libraries(object): for child_metadata in metadata: ## TODO: Check list of media info items, currently only grabs first item media_info = item['media_info'][0] if item['media_info'] else {} + media_part_info = media_info['parts'][0] if media_info['parts'] else {} file_size += helpers.cast_to_int(media_info.get('file_size', 0)) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 5980c81d..d3ce24b5 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -118,6 +118,10 @@ def add_notifier_each(notify_action=None, stream_data=None, timeline_data=None, def notify_conditions(notifier=None, notify_action=None, stream_data=None, timeline_data=None): if stream_data: + + if stream_data['media_type'] == 'clip': + return False + # Check if notifications enabled for user and library user_data = users.Users() user_details = user_data.get_details(user_id=stream_data['user_id']) @@ -283,6 +287,16 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, * ## TODO: Check list of media info items, currently only grabs first item media_info = metadata['media_info'][0] if metadata['media_info'] else {} + media_part_info = media_info['parts'][0] if media_info['parts'] else {} + + stream_video = stream_audio = stream_subtitle = False + for stream in media_part_info['streams']: + if not stream_video and stream['type'] == '1': + media_part_info.update(stream) + if not stream_audio and stream['type'] == '2': + media_part_info.update(stream) + if not stream_subtitle and stream['type'] == '3': + media_part_info.update(stream) child_metadata = grandchild_metadata = [] for key in kwargs.pop('child_keys', []): @@ -414,7 +428,9 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, * 'user_streams': user_stream_count, 'user': session.get('friendly_name',''), 'username': session.get('user',''), + 'device': session.get('device',''), 'platform': session.get('platform',''), + 'product': session.get('product',''), 'player': session.get('player',''), 'ip_address': session.get('ip_address','N/A'), 'stream_duration': stream_duration, @@ -430,6 +446,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, * 'subtitle_decision': session.get('subtitle_decision',''), 'quality_profile': session.get('quality_profile',''), 'optimized_version': session.get('optimized_version',''), + 'optimized_version_profile': session.get('optimized_version_profile',''), 'stream_local': session.get('local', ''), 'stream_location': session.get('location', ''), 'stream_bandwidth': session.get('bandwidth', ''), @@ -437,22 +454,28 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, * 'stream_bitrate': session.get('stream_bitrate', ''), 'stream_aspect_ratio': session.get('stream_aspect_ratio', ''), 'stream_video_codec': session.get('stream_video_codec', ''), + 'stream_video_codec_level': session.get('stream_video_codec_level', ''), 'stream_video_bitrate': session.get('stream_video_bitrate', ''), + 'stream_video_bit_depth': session.get('stream_video_bit_depth', ''), 'stream_video_framerate': session.get('stream_video_framerate', ''), + 'stream_video_ref_frames': session.get('stream_video_ref_frames', ''), 'stream_video_resolution': session.get('stream_video_resolution', ''), - 'stream_video_height': session.get('stream_height', ''), - 'stream_video_width': session.get('stream_width', ''), + 'stream_video_height': session.get('stream_video_height', ''), + 'stream_video_width': session.get('stream_video_width', ''), 'stream_video_language': session.get('stream_video_language', ''), 'stream_video_language_code': session.get('stream_video_language_code', ''), 'stream_audio_bitrate': session.get('stream_audio_bitrate', ''), 'stream_audio_bitrate_mode': session.get('stream_audio_bitrate_mode', ''), 'stream_audio_codec': session.get('stream_audio_codec', ''), 'stream_audio_channels': session.get('stream_audio_channels', ''), + 'stream_audio_channel_layout': session.get('stream_audio_channel_layout', ''), + 'stream_audio_sample_rate': session.get('stream_audio_sample_rate', ''), 'stream_audio_language': session.get('stream_audio_language', ''), 'stream_audio_language_code': session.get('stream_audio_language_code', ''), 'stream_subtitle_codec': session.get('stream_subtitle_codec', ''), 'stream_subtitle_container': session.get('stream_subtitle_container', ''), 'stream_subtitle_format': session.get('stream_subtitle_format', ''), + 'stream_subtitle_forced': session.get('stream_subtitle_forced', ''), 'stream_subtitle_language': session.get('stream_subtitle_language', ''), 'stream_subtitle_language_code': session.get('stream_subtitle_language_code', ''), 'stream_subtitle_location': session.get('stream_subtitle_location', ''), @@ -470,16 +493,6 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, * 'machine_id': session.get('machine_id',''), # Source metadata parameters 'media_type': metadata['media_type'], - 'container': session.get('container', media_info.get('container','')), - 'aspect_ratio': session.get('aspect_ratio', media_info.get('aspect_ratio','')), - 'video_codec': session.get('video_codec', media_info.get('video_codec','')), - 'video_bitrate': session.get('bitrate', media_info.get('bitrate','')), - 'video_framerate': session.get('video_framerate', media_info.get('video_framerate','')), - 'video_resolution': session.get('video_resolution', media_info.get('video_resolution','')), - 'video_width': session.get('width', media_info.get('width','')), - 'video_height': session.get('height', media_info.get('height','')), - 'audio_codec': session.get('audio_codec', media_info.get('audio_codec','')), - 'audio_channels': session.get('audio_channels', media_info.get('audio_channels','')), 'title': metadata['full_title'], 'library_name': metadata['library_name'], 'show_name': show_name, @@ -525,8 +538,38 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, * 'themoviedb_url': metadata.get('themoviedb_url',''), 'lastfm_url': metadata.get('lastfm_url',''), 'trakt_url': metadata.get('trakt_url',''), - 'file': media_info.get('file',''), - 'file_size': helpers.humanFileSize(media_info.get('file_size','')), + 'container': session.get('container', media_info.get('container','')), + 'bitrate': session.get('bitrate', media_info.get('bitrate','')), + 'aspect_ratio': session.get('aspect_ratio', media_info.get('aspect_ratio','')), + 'video_codec': session.get('video_codec', media_info.get('video_codec','')), + 'video_codec_level': session.get('video_codec_level', media_info.get('video_codec_level','')), + 'video_bitrate': session.get('video_bitrate', media_info.get('video_bitrate','')), + 'video_bit_depth': session.get('video_bit_depth', media_info.get('video_bit_depth','')), + 'video_framerate': session.get('video_framerate', media_info.get('video_framerate','')), + 'video_ref_frames': session.get('video_ref_frames', media_info.get('video_ref_frames','')), + 'video_resolution': session.get('video_resolution', media_info.get('video_resolution','')), + 'video_height': session.get('height', media_info.get('height','')), + 'video_width': session.get('width', media_info.get('width','')), + 'video_language': session.get('video_language', media_info.get('video_language','')), + 'video_language_code': session.get('video_language_code', media_info.get('video_language_code','')), + 'audio_bitrate': session.get('audio_bitrate', media_info.get('audio_bitrate','')), + 'audio_bitrate_mode': session.get('audio_bitrate_mode', media_info.get('audio_bitrate_mode','')), + 'audio_codec': session.get('audio_codec', media_info.get('audio_codec','')), + 'audio_channels': session.get('audio_channels', media_info.get('audio_channels','')), + 'audio_channel_layout': session.get('audio_channel_layout', media_info.get('audio_channel_layout','')), + 'audio_sample_rate': session.get('audio_sample_rate', media_info.get('audio_sample_rate','')), + 'audio_language': session.get('audio_language', media_info.get('audio_language','')), + 'audio_language_code': session.get('audio_language_code', media_info.get('audio_language_code','')), + 'subtitle_codec': session.get('subtitle_codec', media_info.get('subtitle_codec','')), + 'subtitle_container': session.get('subtitle_container', media_info.get('subtitle_container','')), + 'subtitle_format': session.get('subtitle_format', media_info.get('subtitle_format','')), + 'subtitle_forced': session.get('subtitle_forced', media_info.get('subtitle_forced','')), + 'subtitle_location': session.get('subtitle_location', media_info.get('subtitle_location','')), + 'subtitle_language': session.get('subtitle_language', media_info.get('subtitle_language','')), + 'subtitle_language_code': session.get('subtitle_language_code', media_info.get('subtitle_language_code','')), + 'file': media_part_info.get('file',''), + 'file_size': helpers.humanFileSize(media_part_info.get('file_size','')), + 'indexes': media_part_info.get('indexes',''), 'section_id': metadata['section_id'], 'rating_key': metadata['rating_key'], 'parent_rating_key': metadata['parent_rating_key'], diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 5b6911c8..24857cc5 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -573,14 +573,14 @@ class PmsConnect(object): xml_head = metadata.getElementsByTagName('MediaContainer') except Exception as e: logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_metadata: %s." % e) - return [] + return {} - metadata_list = [] + metadata = {} for a in xml_head: if a.getAttribute('size'): if a.getAttribute('size') != '1': - return metadata_list + return metadata if a.getElementsByTagName('Directory'): metadata_main = a.getElementsByTagName('Directory')[0] @@ -598,7 +598,7 @@ class PmsConnect(object): metadata_type = helpers.get_xml_attr(metadata_main, 'type') else: logger.debug(u"PlexPy Pmsconnect :: Metadata failed") - return [] + return {} section_id = helpers.get_xml_attr(a, 'librarySectionID') library_name = helpers.get_xml_attr(a, 'librarySectionTitle') @@ -664,7 +664,6 @@ class PmsConnect(object): 'labels': labels, 'full_title': helpers.get_xml_attr(metadata_main, 'title') } - metadata_list.append(metadata) elif metadata_type == 'show': metadata = {'media_type': metadata_type, @@ -701,7 +700,6 @@ class PmsConnect(object): 'labels': labels, 'full_title': helpers.get_xml_attr(metadata_main, 'title') } - metadata_list.append(metadata) elif metadata_type == 'season': parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey') @@ -741,7 +739,6 @@ class PmsConnect(object): 'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'), helpers.get_xml_attr(metadata_main, 'title')) } - metadata_list.append(metadata) elif metadata_type == 'episode': grandparent_rating_key = helpers.get_xml_attr(metadata_main, 'grandparentRatingKey') @@ -781,7 +778,6 @@ class PmsConnect(object): 'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'grandparentTitle'), helpers.get_xml_attr(metadata_main, 'title')) } - metadata_list.append(metadata) elif metadata_type == 'artist': metadata = {'media_type': metadata_type, @@ -818,7 +814,6 @@ class PmsConnect(object): 'labels': labels, 'full_title': helpers.get_xml_attr(metadata_main, 'title') } - metadata_list.append(metadata) elif metadata_type == 'album': parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey') @@ -858,7 +853,6 @@ class PmsConnect(object): 'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'), helpers.get_xml_attr(metadata_main, 'title')) } - metadata_list.append(metadata) elif metadata_type == 'track': parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey') @@ -898,7 +892,6 @@ class PmsConnect(object): 'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'grandparentTitle'), helpers.get_xml_attr(metadata_main, 'title')) } - metadata_list.append(metadata) elif metadata_type == 'photo_album': metadata = {'media_type': metadata_type, @@ -935,7 +928,6 @@ class PmsConnect(object): 'labels': labels, 'full_title': helpers.get_xml_attr(metadata_main, 'title') } - metadata_list.append(metadata) elif metadata_type == 'photo': parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey') @@ -975,36 +967,84 @@ class PmsConnect(object): 'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'), helpers.get_xml_attr(metadata_main, 'title')) } - metadata_list.append(metadata) else: - return [] + return {} - media_info_list = [] - media_items = metadata_main.getElementsByTagName('Media') - for item in media_items: - media_info = {'id': helpers.get_xml_attr(item, 'id'), - 'container': helpers.get_xml_attr(item, 'container'), - 'bitrate': helpers.get_xml_attr(item, 'bitrate'), - 'height': helpers.get_xml_attr(item, 'height'), - 'width': helpers.get_xml_attr(item, 'width'), - 'aspect_ratio': helpers.get_xml_attr(item, 'aspectRatio'), - 'video_codec': helpers.get_xml_attr(item, 'videoCodec'), - 'video_resolution': helpers.get_xml_attr(item, 'videoResolution'), - 'video_framerate': helpers.get_xml_attr(item, 'videoFrameRate'), - 'audio_codec': helpers.get_xml_attr(item, 'audioCodec'), - 'audio_channels': helpers.get_xml_attr(item, 'audioChannels'), - 'file': helpers.get_xml_attr(item.getElementsByTagName('Part')[0], 'file'), - 'file_size': helpers.get_xml_attr(item.getElementsByTagName('Part')[0], 'size') - } - media_info_list.append(media_info) + if metadata: + medias = [] + media_items = metadata_main.getElementsByTagName('Media') + for media in media_items: + + parts = [] + part_items = media.getElementsByTagName('Part') + for part in part_items: + + streams = [] + stream_items = part.getElementsByTagName('Stream') + for stream in stream_items: + if helpers.get_xml_attr(stream, 'streamType') == '1': + streams.append({'id': helpers.get_xml_attr(stream, 'id'), + 'type': helpers.get_xml_attr(stream, 'streamType'), + 'video_bitrate': helpers.get_xml_attr(stream, 'bitrate'), + 'video_bit_depth': helpers.get_xml_attr(stream, 'bitDepth'), + 'video_codec_level': helpers.get_xml_attr(stream, 'level'), + 'video_ref_frames': helpers.get_xml_attr(stream, 'refFrames'), + 'video_language': helpers.get_xml_attr(stream, 'language'), + 'video_language_code': helpers.get_xml_attr(stream, 'languageCode') + }) + + elif helpers.get_xml_attr(stream, 'streamType') == '2': + streams.append({'id': helpers.get_xml_attr(stream, 'id'), + 'type': helpers.get_xml_attr(stream, 'streamType'), + 'audio_bitrate': helpers.get_xml_attr(stream, 'bitrate'), + 'audio_bitrate_mode': helpers.get_xml_attr(stream, 'bitrateMode'), + 'audio_channel_layout': helpers.get_xml_attr(stream, 'audioChannelLayout'), + 'audio_sample_rate': helpers.get_xml_attr(stream, 'samplingRate'), + 'audio_language': helpers.get_xml_attr(stream, 'language'), + 'audio_language_code': helpers.get_xml_attr(stream, 'languageCode'), + }) + + elif helpers.get_xml_attr(stream, 'streamType') == '3': + streams.append({'id': helpers.get_xml_attr(stream, 'id'), + 'type': helpers.get_xml_attr(stream, 'streamType'), + 'subtitle_codec': helpers.get_xml_attr(stream, 'codec'), + 'subtitle_container': helpers.get_xml_attr(stream, 'container'), + 'subtitle_format': helpers.get_xml_attr(stream, 'format'), + 'subtitle_forced': 1 if helpers.get_xml_attr(stream, 'forced') == '1' else 0, + 'subtitle_location': 'external' if helpers.get_xml_attr(stream, 'key') else 'embedded', + 'subtitle_language': helpers.get_xml_attr(stream, 'language'), + 'subtitle_language_code': helpers.get_xml_attr(stream, 'languageCode') + }) + + parts.append({'id': helpers.get_xml_attr(part, 'id'), + 'file': helpers.get_xml_attr(part, 'file'), + 'file_size': helpers.get_xml_attr(part, 'size'), + 'indexes': 1 if helpers.get_xml_attr(part, 'indexes') == 'sd' else 0, + 'streams': streams + }) + + 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': helpers.get_xml_attr(media, 'videoResolution'), + 'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'), + 'audio_codec': helpers.get_xml_attr(media, 'audioCodec'), + 'audio_channels': helpers.get_xml_attr(media, 'audioChannels'), + 'optimized_version': 1 if helpers.get_xml_attr(media, 'optimizedForStreaming') else 0, + 'parts': parts + }) - metadata['media_info'] = media_info_list + metadata['media_info'] = medias - if metadata_list: - return metadata_list[0] + if metadata: + return metadata else: - return [] + return {} def get_metadata_children_details(self, rating_key='', get_children=False): """ @@ -1257,9 +1297,14 @@ class PmsConnect(object): media_parts_stream_info = [] for stream_media_info in media_info_all: - stream_media_parts_info = stream_media_info.getElementsByTagName('Part')[0] - if stream_media_parts_info.getElementsByTagName('Stream'): - media_parts_stream_info = stream_media_parts_info.getElementsByTagName('Stream') + media_part_info_all = stream_media_info.getElementsByTagName('Part') + + for stream_media_parts_info in media_part_info_all: + if stream_media_parts_info.getElementsByTagName('Stream'): + media_parts_stream_info = stream_media_parts_info.getElementsByTagName('Stream') + break + + if media_parts_stream_info: break # Get the stream details @@ -1274,30 +1319,40 @@ class PmsConnect(object): elif helpers.get_xml_attr(stream, 'streamType') == '3': subtitle_stream_info = stream + video_id = audio_id = subtitle_id = None video_details = audio_details = subtitle_details = {} if video_stream_info: + video_id = helpers.get_xml_attr(video_stream_info, 'id') video_details = {'stream_video_bitrate': helpers.get_xml_attr(video_stream_info, 'bitrate'), + 'stream_video_bit_depth': helpers.get_xml_attr(video_stream_info, 'bitDepth'), + 'stream_video_codec_level': helpers.get_xml_attr(video_stream_info, 'level'), + 'stream_video_ref_frames': helpers.get_xml_attr(video_stream_info, 'refFrames'), 'stream_video_language': helpers.get_xml_attr(video_stream_info, 'language'), 'stream_video_language_code': helpers.get_xml_attr(video_stream_info, 'languageCode'), 'stream_video_decision': helpers.get_xml_attr(video_stream_info, 'decision') or 'direct play' } if audio_stream_info: + audio_id = helpers.get_xml_attr(audio_stream_info, 'id') audio_details = {'stream_audio_bitrate': helpers.get_xml_attr(audio_stream_info, 'bitrate'), 'stream_audio_bitrate_mode': helpers.get_xml_attr(audio_stream_info, 'bitrateMode'), + 'stream_audio_channel_layout': helpers.get_xml_attr(audio_stream_info, 'audioChannelLayout'), + 'stream_audio_sample_rate': helpers.get_xml_attr(audio_stream_info, 'samplingRate'), 'stream_audio_language': helpers.get_xml_attr(audio_stream_info, 'language'), 'stream_audio_language_code': helpers.get_xml_attr(audio_stream_info, 'languageCode'), 'stream_audio_decision': helpers.get_xml_attr(audio_stream_info, 'decision') or 'direct play' } if subtitle_stream_info: - subtitle_details = {'stream_subtitle_codec': helpers.get_xml_attr(audio_stream_info, 'codec'), - 'stream_subtitle_container': helpers.get_xml_attr(audio_stream_info, 'container'), - 'stream_subtitle_format': helpers.get_xml_attr(audio_stream_info, 'format'), - 'stream_subtitle_language': helpers.get_xml_attr(audio_stream_info, 'language'), - 'stream_subtitle_language_code': helpers.get_xml_attr(audio_stream_info, 'languageCode'), - 'stream_subtitle_location': helpers.get_xml_attr(audio_stream_info, 'location'), - 'stream_subtitle_decision': helpers.get_xml_attr(audio_stream_info, 'decision') + subtitle_id = helpers.get_xml_attr(subtitle_stream_info, 'id') + 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'), + 'stream_subtitle_forced': 1 if helpers.get_xml_attr(subtitle_stream_info, 'forced') == '1' else 0, + 'stream_subtitle_location': helpers.get_xml_attr(subtitle_stream_info, 'location'), + 'stream_subtitle_language': helpers.get_xml_attr(subtitle_stream_info, 'language'), + 'stream_subtitle_language_code': helpers.get_xml_attr(subtitle_stream_info, 'languageCode'), + 'stream_subtitle_decision': helpers.get_xml_attr(subtitle_stream_info, 'decision') } # Get the bif thumbnail @@ -1309,22 +1364,12 @@ class PmsConnect(object): else: bif_thumb = '' - # Check if it is an optimized version - if helpers.get_xml_attr(stream_media_parts_info, 'optimizedForStreaming'): - optimized_version = 1 - else: - optimized_version = 0 - # Get the quality profile if video_details: quality_profile = common.QUALITY_PROFILES.get(video_details['stream_video_bitrate'], 'Original') else: quality_profile = '' - transcode_decision = helpers.get_xml_attr(stream_media_parts_info, 'decision') - if transcode_decision == 'directplay': - transcode_decision = 'direct play' - # Check if it is an optimized version stream_details = {'stream_container': helpers.get_xml_attr(stream_media_info, 'container'), 'stream_bitrate': helpers.get_xml_attr(stream_media_info, 'bitrate'), @@ -1337,8 +1382,9 @@ 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'), - 'transcode_decision': transcode_decision, - 'optimized_version': optimized_version, + 'transcode_decision': helpers.get_xml_attr(stream_media_parts_info, 'decision').replace('directplay', 'direct play'), + 'optimized_version': 1 if helpers.get_xml_attr(stream_media_info, 'optimizedForStreaming') else 0, + 'optimized_version_profile': helpers.get_xml_attr(stream_media_info, 'title'), 'quality_profile': quality_profile, 'indexes': 1 if indexes == 'sd' else 0, 'bif_thumb': bif_thumb, @@ -1346,23 +1392,62 @@ class PmsConnect(object): } # Get the source media info - media_id = helpers.get_xml_attr(stream_media_info, 'id') + media_type = helpers.get_xml_attr(session, 'type') - metadata_details = self.get_metadata_details(rating_key=helpers.get_xml_attr(session, 'ratingKey')) - source_media_details = next((m for m in metadata_details.pop('media_info') if m['id'] == media_id), {}) + source_media_details = source_media_part_details = \ + source_video_details = source_audio_details = source_subtitle_details = {} + + if media_type == 'clip': + clip_media = session.getElementsByTagName('Media')[0] + metadata_details = {'rating_key': helpers.get_xml_attr(session, 'ratingKey'), + 'title': helpers.get_xml_attr(session, 'title'), + 'summary': helpers.get_xml_attr(session, 'summary'), + 'duration': helpers.get_xml_attr(session, 'duration'), + 'year': helpers.get_xml_attr(session, 'year'), + 'thumb': helpers.get_xml_attr(session, 'thumb'), + 'art': helpers.get_xml_attr(session, 'art'), + 'full_title': helpers.get_xml_attr(session, 'title'), + 'container': helpers.get_xml_attr(clip_media, 'container'), + 'height': helpers.get_xml_attr(clip_media, 'height'), + 'width': helpers.get_xml_attr(clip_media, 'width'), + 'video_codec': helpers.get_xml_attr(clip_media, 'videoCodec'), + 'video_resolution': helpers.get_xml_attr(clip_media, 'videoResolution'), + 'audio_codec': helpers.get_xml_attr(clip_media, 'audioCodec'), + 'audio_channels': helpers.get_xml_attr(clip_media, 'audioChannels'), + } + else: + 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')) + source_media_details = next((m for m in metadata_details.pop('media_info', []) if m['id'] == media_id), {}) + source_media_part_details = next((p for p in source_media_details.pop('parts', []) if p['id'] == part_id), {}) + source_media_part_streams = source_media_part_details.pop('streams', []) + + if video_id: + source_video_details = next((p for p in source_media_part_streams if p['id'] == video_id), {}) + if audio_id: + source_audio_details = next((p for p in source_media_part_streams if p['id'] == audio_id), {}) + if subtitle_id: + source_subtitle_details = next((p for p in source_media_part_streams if p['id'] == subtitle_id), {}) # Entire session output (single dict for backwards compatibility) session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), + 'media_type': media_type, 'view_offset': progress, 'progress_percent': str(helpers.get_percent(progress, stream_details['stream_duration'])), 'user': user_details['username'] # Keep for backwards compatibility } + + if 'rating_key' not in session_output: + session_output['rating_key'] = helpers.get_xml_attr(session, 'ratingKey') - if helpers.get_xml_attr(session, 'ratingKey').isdigit(): - session_output['media_type'] = helpers.get_xml_attr(session, 'type') - else: - session_output['media_type'] = 'clip' - + session_output.update(metadata_details) + session_output.update(source_media_details) + session_output.update(source_media_part_details) + session_output.update(source_video_details) + session_output.update(source_audio_details) + session_output.update(source_subtitle_details) session_output.update(user_details) session_output.update(player_details) session_output.update(session_details) @@ -1371,8 +1456,6 @@ class PmsConnect(object): session_output.update(video_details) session_output.update(audio_details) session_output.update(subtitle_details) - session_output.update(metadata_details) - session_output.update(source_media_details) return session_output @@ -1776,7 +1859,7 @@ class PmsConnect(object): return labels_list - def get_image(self, img=None, width='1000', height='1500'): + def get_image(self, img=None, width='1000', height='1500', clip=False): """ Return image data as array. Array contains the image content type and image binary @@ -1788,7 +1871,11 @@ class PmsConnect(object): """ if img: - params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urllib.urlencode({'X-Plex-Token': self.token}))} + if clip: + params = {'url': '%s&%s' % (img, urllib.urlencode({'X-Plex-Token': self.token}))} + else: + params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urllib.urlencode({'X-Plex-Token': self.token}))} + if width.isdigit() and height.isdigit(): params['width'] = width params['height'] = height diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 2e11f9a2..0972afb2 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -3509,7 +3509,7 @@ class WebInterface(object): @addtoapi('pms_image_proxy') def real_pms_image_proxy(self, img='', rating_key=None, width='0', height='0', - fallback=None, refresh=False, **kwargs): + fallback=None, refresh=False, clip=False, **kwargs): """ Gets an image from the PMS and saves it to the image cache directory. ``` @@ -3545,6 +3545,8 @@ class WebInterface(object): if not os.path.exists(c_dir): os.mkdir(c_dir) + clip = True if clip == 'true' else False + try: if not plexpy.CONFIG.CACHE_IMAGES or refresh or 'indexes' in img: raise NotFound @@ -3555,7 +3557,7 @@ class WebInterface(object): # the image does not exist, download it from pms try: pms_connect = pmsconnect.PmsConnect() - result = pms_connect.get_image(img, width, height) + result = pms_connect.get_image(img, width, height, clip=clip) if result and result[0]: cherrypy.response.headers['Content-type'] = result[1]