def process(self): if self.is_valid_session(): from plexpy import helpers ap = activity_processor.ActivityProcessor() db_session = ap.get_session_by_key( session_key=self.get_session_key()) this_state = self.timeline['state'] this_key = str(self.timeline['ratingKey']) # If we already have this session in the temp table, check for state changes if db_session: last_state = db_session['state'] last_key = str(db_session['rating_key']) # Make sure the same item is being played if this_key == last_key: # Update the session state and viewOffset if this_state == 'playing': ap.set_session_state( session_key=self.get_session_key(), state=this_state, view_offset=self.timeline['viewOffset']) # Start our state checks if this_state != last_state: if this_state == 'paused': self.on_pause() elif last_state == 'paused' and this_state == 'playing': self.on_resume() elif this_state == 'stopped': self.on_stop() elif this_state == 'buffering': self.on_buffer() # If a client doesn't register stop events (I'm looking at you PHT!) check if the ratingKey has changed else: # Manually stop and start # Set force_stop so that we don't overwrite our last viewOffset self.on_stop(force_stop=True) self.on_start() # Monitor if the stream has reached the watch percentage for notifications # The only purpose of this is for notifications progress_percent = helpers.get_percent( self.timeline['viewOffset'], db_session['duration']) if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT and this_state != 'buffering': threading.Thread(target=notification_handler.notify, kwargs=dict( stream_data=db_session, notify_action='watched')).start() else: # We don't have this session in our table yet, start a new one. if this_state != 'buffering': self.on_start()
def process(self): if self.is_valid_session(): ap = activity_processor.ActivityProcessor() db_session = ap.get_session_by_key(session_key=self.get_session_key()) this_state = self.timeline['state'] this_key = str(self.timeline['ratingKey']) # If we already have this session in the temp table, check for state changes if db_session: last_state = db_session['state'] last_key = str(db_session['rating_key']) # Make sure the same item is being played if this_key == last_key: # Update the session state and viewOffset if this_state == 'playing': ap.set_session_state(session_key=self.get_session_key(), state=this_state, view_offset=self.timeline['viewOffset']) # Start our state checks if this_state != last_state: if this_state == 'paused': self.on_pause() elif last_state == 'paused' and this_state == 'playing': self.on_resume() elif this_state == 'stopped': self.on_stop() elif this_state == 'buffering': self.on_buffer() # If a client doesn't register stop events (I'm looking at you PHT!) check if the ratingKey has changed else: # Manually stop and start # Set force_stop so that we don't overwrite our last viewOffset self.on_stop(force_stop=True) self.on_start() # Monitor if the stream has reached the watch percentage for notifications # The only purpose of this is for notifications # Check if any notification agents have notifications enabled notify_agents = [d['id'] for d in notifiers.available_notification_agents() if d['on_watched']] # Get the current states for notifications from our db notified_agents = [d['agent_id'] for d in notification_handler.get_notify_state(session=db_session) if d['notify_action'] == 'watched'] if notify_agents else [] if any(a not in notified_agents for a in notify_agents): progress_percent = helpers.get_percent(self.timeline['viewOffset'], db_session['duration']) if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT and this_state != 'buffering': # Rather not put this on it's own thread so we know it completes before our next event. notification_handler.notify(stream_data=db_session, notify_action='watched') else: # We don't have this session in our table yet, start a new one. if this_state != 'buffering': self.on_start()
def process(self): if self.is_valid_session(): from plexpy import helpers ap = activity_processor.ActivityProcessor() db_session = ap.get_session_by_key(session_key=self.get_session_key()) this_state = self.timeline["state"] this_key = str(self.timeline["ratingKey"]) # If we already have this session in the temp table, check for state changes if db_session: last_state = db_session["state"] last_key = str(db_session["rating_key"]) # Make sure the same item is being played if this_key == last_key: # Update the session state and viewOffset if this_state == "playing": ap.set_session_state( session_key=self.get_session_key(), state=this_state, view_offset=self.timeline["viewOffset"], ) # Start our state checks if this_state != last_state: if this_state == "paused": self.on_pause() elif last_state == "paused" and this_state == "playing": self.on_resume() elif this_state == "stopped": self.on_stop() elif this_state == "buffering": self.on_buffer() # If a client doesn't register stop events (I'm looking at you PHT!) check if the ratingKey has changed else: # Manually stop and start # Set force_stop so that we don't overwrite our last viewOffset self.on_stop(force_stop=True) self.on_start() # Monitor if the stream has reached the watch percentage for notifications # The only purpose of this is for notifications progress_percent = helpers.get_percent(self.timeline["viewOffset"], db_session["duration"]) if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT and this_state != "buffering": threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=db_session, notify_action="watched") ).start() else: # We don't have this session in our table yet, start a new one. if this_state != "buffering": self.on_start()
def build_notify_text(session, state): from plexpy import pmsconnect, helpers import re # Get the server name pms_connect = pmsconnect.PmsConnect() server_name = pms_connect.get_server_pref(pref='FriendlyName') # Get metadata feed for item metadata = pms_connect.get_metadata_details( rating_key=session['rating_key']) if metadata: item_metadata = metadata['metadata'] else: logger.error( u"PlexPy Notifier :: Unable to retrieve metadata for rating_key %s" % str(session['rating_key'])) return [] # Check for exclusion tags if session['media_type'] == 'episode': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<movie>[^>]+.</movie>|<music>[^>]+.</music>', re.IGNORECASE) elif session['media_type'] == 'movie': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<tv>[^>]+.</tv>|<music>[^>]+.</music>', re.IGNORECASE) elif session['media_type'] == 'track': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<tv>[^>]+.</tv>|<movie>[^>]+.</movie>', re.IGNORECASE) else: pattern = None if session['media_type'] == 'episode' or session['media_type'] == 'movie' or session['media_type'] == 'track' \ and pattern: # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) else: on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT # Create a title if session['media_type'] == 'episode': full_title = '%s - %s' % (session['grandparent_title'], session['title']) elif session['media_type'] == 'track': full_title = '%s - %s' % (session['grandparent_title'], session['title']) else: full_title = session['title'] # Generate a combined transcode decision value if session['video_decision']: if session['video_decision'] == 'transcode': transcode_decision = 'Transcode' elif session['video_decision'] == 'copy' or session[ 'audio_decision'] == 'copy': transcode_decision = 'Direct Stream' else: transcode_decision = 'Direct Play' else: if session['audio_decision'] == 'transcode': transcode_decision = 'Transcode' else: transcode_decision = 'Direct Play' duration = helpers.convert_milliseconds_to_minutes( item_metadata['duration']) view_offset = helpers.convert_milliseconds_to_minutes( session['view_offset']) stream_duration = 0 if state != 'play': if session['paused_counter']: stream_duration = int( (time.time() - helpers.cast_to_float(session['started']) - helpers.cast_to_float(session['paused_counter'])) / 60) else: stream_duration = int( (time.time() - helpers.cast_to_float(session['started'])) / 60) progress_percent = helpers.get_percent(view_offset, duration) available_params = { 'server_name': server_name, 'user': session['friendly_name'], 'platform': session['platform'], 'player': session['player'], 'media_type': session['media_type'], 'title': full_title, 'show_name': item_metadata['grandparent_title'], 'episode_name': item_metadata['title'], 'artist_name': item_metadata['grandparent_title'], 'album_name': item_metadata['parent_title'], 'season_num': item_metadata['parent_index'], 'season_num00': item_metadata['parent_index'].zfill(2), 'episode_num': item_metadata['index'], 'episode_num00': item_metadata['index'].zfill(2), 'transcode_decision': transcode_decision, 'year': item_metadata['year'], 'studio': item_metadata['studio'], 'content_rating': item_metadata['content_rating'], 'summary': item_metadata['summary'], 'rating': item_metadata['rating'], 'duration': duration, 'stream_duration': stream_duration, 'remaining_duration': duration - view_offset, 'progress': view_offset, 'progress_percent': progress_percent } # Default subject text subject_text = 'PlexPy (%s)' % server_name if state == 'play': # Default body text body_text = '%s (%s) is watching %s' % (session['friendly_name'], session['player'], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format( **available_params) except LookupError, e: logger.error( u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except:
def notify(stream_data=None, notify_action=None): from plexpy import users if stream_data and notify_action: # Check if notifications enabled for user user_data = users.Users() user_details = user_data.get_user_friendly_name(user=stream_data['user']) if not user_details['do_notify']: return if (stream_data['media_type'] == 'movie' and plexpy.CONFIG.MOVIE_NOTIFY_ENABLE) \ or (stream_data['media_type'] == 'episode' and plexpy.CONFIG.TV_NOTIFY_ENABLE): progress_percent = helpers.get_percent(stream_data['view_offset'], stream_data['duration']) for agent in notifiers.available_notification_agents(): if agent['on_play'] and notify_action == 'play': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_stop'] and notify_action == 'stop' \ and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexpy.CONFIG.NOTIFY_WATCHED_PERCENT): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_pause'] and notify_action == 'pause' \ and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_resume'] and notify_action == 'resume' \ and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_buffer'] and notify_action == 'buffer': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_watched'] and notify_action == 'watched': # Get the current states for notifications from our db notify_states = get_notify_state(session=stream_data) # If there is nothing in the notify_log for our agent id but it is enabled we should notify if not any(d['agent_id'] == agent['id'] for d in notify_states): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) else: # Check in our notify log if the notification has already been sent for notify_state in notify_states: if not notify_state['on_watched'] and (notify_state['agent_id'] == agent['id']): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif (stream_data['media_type'] == 'track' and plexpy.CONFIG.MUSIC_NOTIFY_ENABLE): for agent in notifiers.available_notification_agents(): if agent['on_play'] and notify_action == 'play': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_stop'] and notify_action == 'stop': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_pause'] and notify_action == 'pause': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_resume'] and notify_action == 'resume': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_buffer'] and notify_action == 'buffer': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif stream_data['media_type'] == 'clip': pass else: #logger.debug(u"PlexPy Notifier :: Notify called with unsupported media type.") pass else: logger.debug(u"PlexPy Notifier :: Notify called but incomplete data received.")
def get_session_each(self, stream_type="", session=None): session_output = None data_factory = datafactory.DataFactory() if stream_type == "track": media_info = session.getElementsByTagName("Media")[0] audio_decision = "direct play" audio_channels = helpers.get_xml_attr(media_info, "audioChannels") audio_codec = helpers.get_xml_attr(media_info, "audioCodec") container = helpers.get_xml_attr(media_info, "container") bitrate = helpers.get_xml_attr(media_info, "bitrate") duration = helpers.get_xml_attr(media_info, "duration") progress = helpers.get_xml_attr(session, "viewOffset") if session.getElementsByTagName("TranscodeSession"): transcode_session = session.getElementsByTagName("TranscodeSession")[0] audio_decision = helpers.get_xml_attr(transcode_session, "audioDecision") transcode_audio_channels = helpers.get_xml_attr(transcode_session, "audioChannels") transcode_audio_codec = helpers.get_xml_attr(transcode_session, "audioCodec") transcode_container = helpers.get_xml_attr(transcode_session, "container") transcode_protocol = helpers.get_xml_attr(transcode_session, "protocol") duration = helpers.get_xml_attr(transcode_session, "duration") else: transcode_audio_channels = "" transcode_audio_codec = "" transcode_container = "" transcode_protocol = "" user_details = data_factory.get_user_details( user=helpers.get_xml_attr(session.getElementsByTagName("User")[0], "title") ) if helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "machineIdentifier").endswith("_Track"): machine_id = helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "machineIdentifier")[:-6] else: machine_id = helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "machineIdentifier") session_output = { "session_key": helpers.get_xml_attr(session, "sessionKey"), "media_index": helpers.get_xml_attr(session, "index"), "parent_media_index": helpers.get_xml_attr(session, "parentIndex"), "art": helpers.get_xml_attr(session, "art"), "parent_thumb": helpers.get_xml_attr(session, "parentThumb"), "grandparent_thumb": helpers.get_xml_attr(session, "grandparentThumb"), "thumb": helpers.get_xml_attr(session, "thumb"), "bif_thumb": "", "user": helpers.get_xml_attr(session.getElementsByTagName("User")[0], "title"), "user_id": user_details["user_id"], "friendly_name": user_details["friendly_name"], "player": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "title"), "platform": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "platform"), "machine_id": machine_id, "state": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "state"), "grandparent_title": helpers.get_xml_attr(session, "grandparentTitle"), "parent_title": helpers.get_xml_attr(session, "parentTitle"), "title": helpers.get_xml_attr(session, "title"), "rating_key": helpers.get_xml_attr(session, "ratingKey"), "parent_rating_key": helpers.get_xml_attr(session, "parentRatingKey"), "grandparent_rating_key": helpers.get_xml_attr(session, "grandparentRatingKey"), "audio_decision": audio_decision, "audio_channels": audio_channels, "audio_codec": audio_codec, "video_decision": "", "video_codec": "", "height": "", "width": "", "container": container, "bitrate": bitrate, "video_resolution": "", "video_framerate": "", "aspect_ratio": "", "transcode_audio_channels": transcode_audio_channels, "transcode_audio_codec": transcode_audio_codec, "transcode_video_codec": "", "transcode_width": "", "transcode_height": "", "transcode_container": transcode_container, "transcode_protocol": transcode_protocol, "duration": duration, "progress": progress, "progress_percent": str(helpers.get_percent(progress, duration)), "type": "track", "indexes": 0, } elif stream_type == "video": media_info = session.getElementsByTagName("Media")[0] audio_decision = "direct play" audio_channels = helpers.get_xml_attr(media_info, "audioChannels") audio_codec = helpers.get_xml_attr(media_info, "audioCodec") video_decision = "direct play" video_codec = helpers.get_xml_attr(media_info, "videoCodec") container = helpers.get_xml_attr(media_info, "container") bitrate = helpers.get_xml_attr(media_info, "bitrate") video_resolution = helpers.get_xml_attr(media_info, "videoResolution") video_framerate = helpers.get_xml_attr(media_info, "videoFrameRate") aspect_ratio = helpers.get_xml_attr(media_info, "aspectRatio") width = helpers.get_xml_attr(media_info, "width") height = helpers.get_xml_attr(media_info, "height") duration = helpers.get_xml_attr(media_info, "duration") progress = helpers.get_xml_attr(session, "viewOffset") if session.getElementsByTagName("TranscodeSession"): transcode_session = session.getElementsByTagName("TranscodeSession")[0] audio_decision = helpers.get_xml_attr(transcode_session, "audioDecision") transcode_audio_channels = helpers.get_xml_attr(transcode_session, "audioChannels") transcode_audio_codec = helpers.get_xml_attr(transcode_session, "audioCodec") video_decision = helpers.get_xml_attr(transcode_session, "videoDecision") transcode_video_codec = helpers.get_xml_attr(transcode_session, "videoCodec") transcode_width = helpers.get_xml_attr(transcode_session, "width") transcode_height = helpers.get_xml_attr(transcode_session, "height") transcode_container = helpers.get_xml_attr(transcode_session, "container") transcode_protocol = helpers.get_xml_attr(transcode_session, "protocol") else: transcode_audio_channels = "" transcode_audio_codec = "" transcode_video_codec = "" transcode_width = "" transcode_height = "" transcode_container = "" transcode_protocol = "" media_info = session.getElementsByTagName("Media")[0] if media_info.getElementsByTagName("Part"): indexes = helpers.get_xml_attr(media_info.getElementsByTagName("Part")[0], "indexes") part_id = helpers.get_xml_attr(media_info.getElementsByTagName("Part")[0], "id") if indexes == "sd": bif_thumb = "/library/parts/" + part_id + "/indexes/sd/" + progress else: bif_thumb = "" else: indexes = "" bif_thumb = "" if plexpy.CONFIG.PMS_USE_BIF and indexes == "sd": use_indexes = 1 else: use_indexes = 0 user_details = data_factory.get_user_details( user=helpers.get_xml_attr(session.getElementsByTagName("User")[0], "title") ) if helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "machineIdentifier").endswith("_Video"): machine_id = helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "machineIdentifier")[:-6] else: machine_id = helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "machineIdentifier") if helpers.get_xml_attr(session, "type") == "episode": session_output = { "session_key": helpers.get_xml_attr(session, "sessionKey"), "media_index": helpers.get_xml_attr(session, "index"), "parent_media_index": helpers.get_xml_attr(session, "parentIndex"), "art": helpers.get_xml_attr(session, "art"), "parent_thumb": helpers.get_xml_attr(session, "parentThumb"), "grandparent_thumb": helpers.get_xml_attr(session, "grandparentThumb"), "thumb": helpers.get_xml_attr(session, "thumb"), "bif_thumb": bif_thumb, "user": helpers.get_xml_attr(session.getElementsByTagName("User")[0], "title"), "user_id": user_details["user_id"], "friendly_name": user_details["friendly_name"], "player": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "title"), "platform": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "platform"), "machine_id": machine_id, "state": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "state"), "grandparent_title": helpers.get_xml_attr(session, "grandparentTitle"), "parent_title": helpers.get_xml_attr(session, "parentTitle"), "title": helpers.get_xml_attr(session, "title"), "rating_key": helpers.get_xml_attr(session, "ratingKey"), "parent_rating_key": helpers.get_xml_attr(session, "parentRatingKey"), "grandparent_rating_key": helpers.get_xml_attr(session, "grandparentRatingKey"), "audio_decision": audio_decision, "audio_channels": audio_channels, "audio_codec": audio_codec, "video_decision": video_decision, "video_codec": video_codec, "height": height, "width": width, "container": container, "bitrate": bitrate, "video_resolution": video_resolution, "video_framerate": video_framerate, "aspect_ratio": aspect_ratio, "transcode_audio_channels": transcode_audio_channels, "transcode_audio_codec": transcode_audio_codec, "transcode_video_codec": transcode_video_codec, "transcode_width": transcode_width, "transcode_height": transcode_height, "transcode_container": transcode_container, "transcode_protocol": transcode_protocol, "duration": duration, "progress": progress, "progress_percent": str(helpers.get_percent(progress, duration)), "type": helpers.get_xml_attr(session, "type"), "indexes": use_indexes, } elif helpers.get_xml_attr(session, "type") == "movie": session_output = { "session_key": helpers.get_xml_attr(session, "sessionKey"), "media_index": helpers.get_xml_attr(session, "index"), "parent_media_index": helpers.get_xml_attr(session, "parentIndex"), "art": helpers.get_xml_attr(session, "art"), "thumb": helpers.get_xml_attr(session, "thumb"), "bif_thumb": bif_thumb, "parent_thumb": helpers.get_xml_attr(session, "parentThumb"), "grandparent_thumb": helpers.get_xml_attr(session, "grandparentThumb"), "user": helpers.get_xml_attr(session.getElementsByTagName("User")[0], "title"), "user_id": user_details["user_id"], "friendly_name": user_details["friendly_name"], "player": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "title"), "platform": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "platform"), "machine_id": machine_id, "state": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "state"), "grandparent_title": helpers.get_xml_attr(session, "grandparentTitle"), "parent_title": helpers.get_xml_attr(session, "parentTitle"), "title": helpers.get_xml_attr(session, "title"), "rating_key": helpers.get_xml_attr(session, "ratingKey"), "parent_rating_key": helpers.get_xml_attr(session, "parentRatingKey"), "grandparent_rating_key": helpers.get_xml_attr(session, "grandparentRatingKey"), "audio_decision": audio_decision, "audio_channels": audio_channels, "audio_codec": audio_codec, "video_decision": video_decision, "video_codec": video_codec, "height": height, "width": width, "container": container, "bitrate": bitrate, "video_resolution": video_resolution, "video_framerate": video_framerate, "aspect_ratio": aspect_ratio, "transcode_audio_channels": transcode_audio_channels, "transcode_audio_codec": transcode_audio_codec, "transcode_video_codec": transcode_video_codec, "transcode_width": transcode_width, "transcode_height": transcode_height, "transcode_container": transcode_container, "transcode_protocol": transcode_protocol, "duration": duration, "progress": progress, "progress_percent": str(helpers.get_percent(progress, duration)), "type": helpers.get_xml_attr(session, "type"), "indexes": use_indexes, } elif helpers.get_xml_attr(session, "type") == "clip": session_output = { "session_key": helpers.get_xml_attr(session, "sessionKey"), "media_index": helpers.get_xml_attr(session, "index"), "parent_media_index": helpers.get_xml_attr(session, "parentIndex"), "art": helpers.get_xml_attr(session, "art"), "thumb": helpers.get_xml_attr(session, "thumb"), "bif_thumb": bif_thumb, "parent_thumb": helpers.get_xml_attr(session, "parentThumb"), "grandparent_thumb": helpers.get_xml_attr(session, "grandparentThumb"), "user": helpers.get_xml_attr(session.getElementsByTagName("User")[0], "title"), "user_id": user_details["user_id"], "friendly_name": user_details["friendly_name"], "player": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "title"), "platform": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "platform"), "machine_id": machine_id, "state": helpers.get_xml_attr(session.getElementsByTagName("Player")[0], "state"), "grandparent_title": helpers.get_xml_attr(session, "grandparentTitle"), "parent_title": helpers.get_xml_attr(session, "parentTitle"), "title": helpers.get_xml_attr(session, "title"), "rating_key": helpers.get_xml_attr(session, "ratingKey"), "parent_rating_key": helpers.get_xml_attr(session, "parentRatingKey"), "grandparent_rating_key": helpers.get_xml_attr(session, "grandparentRatingKey"), "audio_decision": audio_decision, "audio_channels": audio_channels, "audio_codec": audio_codec, "video_decision": video_decision, "video_codec": video_codec, "height": height, "width": width, "container": container, "bitrate": bitrate, "video_resolution": video_resolution, "video_framerate": video_framerate, "aspect_ratio": aspect_ratio, "transcode_audio_channels": transcode_audio_channels, "transcode_audio_codec": transcode_audio_codec, "transcode_video_codec": transcode_video_codec, "transcode_width": transcode_width, "transcode_height": transcode_height, "transcode_container": transcode_container, "transcode_protocol": transcode_protocol, "duration": duration, "progress": progress, "progress_percent": str(helpers.get_percent(progress, duration)), "type": helpers.get_xml_attr(session, "type"), "indexes": 0, } else: logger.warn(u"No known stream types found in session list.") return session_output
status_state = helpers.get_xml_attr( status, 'state') status_item_count = helpers.get_xml_attr( status, 'itemsCount') status_item_complete_count = helpers.get_xml_attr( status, 'itemsCompleteCount') status_item_downloaded_count = helpers.get_xml_attr( status, 'itemsDownloadedCount') status_item_ready_count = helpers.get_xml_attr( status, 'itemsReadyCount') status_item_successful_count = helpers.get_xml_attr( status, 'itemsSuccessfulCount') status_total_size = helpers.get_xml_attr( status, 'totalSize') status_item_download_percent_complete = helpers.get_percent( status_item_downloaded_count, status_item_count) for settings in item.getElementsByTagName( 'MediaSettings'): settings_audio_boost = helpers.get_xml_attr( settings, 'audioBoost') settings_music_bitrate = helpers.get_xml_attr( settings, 'musicBitrate') settings_photo_quality = helpers.get_xml_attr( settings, 'photoQuality') settings_photo_resolution = helpers.get_xml_attr( settings, 'photoResolution') settings_video_quality = helpers.get_xml_attr( settings, 'videoQuality') settings_video_resolution = helpers.get_xml_attr(
def check_active_sessions(ws_request=False): with monitor_lock: pms_connect = pmsconnect.PmsConnect() session_list = pms_connect.get_current_activity() monitor_db = database.MonitorDatabase() monitor_process = activity_processor.ActivityProcessor() # logger.debug(u"PlexPy Monitor :: Checking for active streams.") global int_ping_count if session_list: if int_ping_count >= 3: logger.info(u"PlexPy Monitor :: The Plex Media Server is back up.") # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, kwargs=dict(notify_action='intup')).start() int_ping_count = 0 media_container = session_list['sessions'] # Check our temp table for what we must do with the new streams db_streams = monitor_db.select('SELECT * FROM sessions') for stream in db_streams: if any(d['session_key'] == str(stream['session_key']) and d['rating_key'] == str(stream['rating_key']) for d in media_container): # The user's session is still active for session in media_container: if session['session_key'] == str(stream['session_key']) and \ session['rating_key'] == str(stream['rating_key']): # The user is still playing the same media item # Here we can check the play states if session['state'] != stream['state']: if session['state'] == 'paused': # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='pause')).start() if session['state'] == 'playing' and stream['state'] == 'paused': # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='resume')).start() if stream['state'] == 'paused' and not ws_request: # The stream is still paused so we need to increment the paused_counter # Using the set config parameter as the interval, probably not the most accurate but # it will have to do for now. If it's a websocket request don't use this method. paused_counter = int(stream['paused_counter']) + plexpy.CONFIG.MONITORING_INTERVAL monitor_db.action('UPDATE sessions SET paused_counter = ? ' 'WHERE session_key = ? AND rating_key = ?', [paused_counter, stream['session_key'], stream['rating_key']]) if session['state'] == 'buffering' and plexpy.CONFIG.BUFFER_THRESHOLD > 0: # The stream is buffering so we need to increment the buffer_count # We're going just increment on every monitor ping, # would be difficult to keep track otherwise monitor_db.action('UPDATE sessions SET buffer_count = buffer_count + 1 ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) # Check the current buffer count and last buffer to determine if we should notify buffer_values = monitor_db.select('SELECT buffer_count, buffer_last_triggered ' 'FROM sessions ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) if buffer_values[0]['buffer_count'] >= plexpy.CONFIG.BUFFER_THRESHOLD: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions # Our first buffer notification if buffer_values[0]['buffer_count'] == plexpy.CONFIG.BUFFER_THRESHOLD: logger.info(u"PlexPy Monitor :: User '%s' has triggered a buffer warning." % stream['user']) # Set the buffer trigger time monitor_db.action('UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='buffer')).start() else: # Subsequent buffer notifications after wait time if int(time.time()) > buffer_values[0]['buffer_last_triggered'] + \ plexpy.CONFIG.BUFFER_WAIT: logger.info(u"PlexPy Monitor :: User '%s' has triggered multiple buffer warnings." % stream['user']) # Set the buffer trigger time monitor_db.action('UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='buffer')).start() logger.debug(u"PlexPy Monitor :: Stream buffering. Count is now %s. Last triggered %s." % (buffer_values[0]['buffer_count'], buffer_values[0]['buffer_last_triggered'])) # Check if the user has reached the offset in the media we defined as the "watched" percent # Don't trigger if state is buffer as some clients push the progress to the end when # buffering on start. if session['view_offset'] and session['duration'] and session['state'] != 'buffering': if helpers.get_percent(session['view_offset'], session['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='watched')).start() else: # The user has stopped playing a stream logger.debug(u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue" % (stream['session_key'], stream['rating_key'])) monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) # Check if the user has reached the offset in the media we defined as the "watched" percent if stream['view_offset'] and stream['duration']: if helpers.get_percent(stream['view_offset'], stream['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='watched')).start() # Push any notifications - Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='stop')).start() # Write the item history on playback stop monitor_process.write_session_history(session=stream) # Process the newly received session data for session in media_container: monitor_process.write_session(session) else: logger.debug(u"PlexPy Monitor :: Unable to read session list.") int_ping_count += 1 logger.warn(u"PlexPy Monitor :: Unable to get an internal response from the server, ping attempt %s." \ % str(int_ping_count)) if int_ping_count == 3: # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, kwargs=dict(notify_action='intdown')).start()
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0): server_name = plexpy.PMS_SERVERS.get_server_by_id( session['server_id']).CONFIG.PMS_NAME library_id = plexpy.libraries.get_section_index( session['server_id'], session['section_id']) if not is_import: user_data = users.Users() user_details = user_data.get_details(user_id=session['user_id']) library_data = libraries.Libraries() library_details = library_data.get_details(id=library_id) # Return false if failed to retrieve user or library details if not user_details or not library_details: return False if session: logging_enabled = False # Reload json from raw stream info if session.get('raw_stream_info'): raw_stream_info = json.loads(session['raw_stream_info']) # Don't overwrite id, session_key, stopped, view_offset raw_stream_info.pop('id', None) raw_stream_info.pop('session_key', None) raw_stream_info.pop('stopped', None) raw_stream_info.pop('view_offset', None) session.update(raw_stream_info) session = defaultdict(str, session) if is_import: if str(session['stopped']).isdigit(): stopped = int(session['stopped']) else: stopped = int(time.time()) elif session['stopped']: stopped = int(session['stopped']) else: stopped = int(time.time()) self.set_session_state(session_key=session['session_key'], state='stopped', stopped=stopped) if str(session['rating_key']).isdigit( ) and session['media_type'] in ('movie', 'episode', 'track'): logging_enabled = True else: logger.debug( u"Tautulli ActivityProcessor :: %s: Session %s ratingKey %s not logged. " u"Does not meet logging criteria. Media type is '%s'" % (server_name, session['session_key'], session['rating_key'], session['media_type'])) return session['id'] if str(session['paused_counter']).isdigit(): real_play_time = stopped - int(session['started']) - int( session['paused_counter']) else: real_play_time = stopped - int(session['started']) if not is_import and plexpy.CONFIG.LOGGING_IGNORE_INTERVAL: if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \ (real_play_time < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)): logging_enabled = False logger.debug( u"Tautulli ActivityProcessor :: %s: Play duration for session %s ratingKey %s is %s secs " u"which is less than %s seconds, so we're not logging it." % (server_name, session['session_key'], session['rating_key'], str(real_play_time), plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)) if not is_import and session['media_type'] == 'track': if real_play_time < 15 and int(session['duration']) >= 30: logging_enabled = False logger.debug( u"Tautulli ActivityProcessor :: %s: Play duration for session %s ratingKey %s is %s secs, " u"looks like it was skipped so we're not logging it" % (server_name, session['session_key'], session['rating_key'], str(real_play_time))) elif is_import and import_ignore_interval: if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \ (real_play_time < int(import_ignore_interval)): logging_enabled = False logger.debug( u"Tautulli ActivityProcessor :: %s: Play duration for ratingKey %s is %s secs which is less than %s " u"seconds, so we're not logging it." % (server_name, session['rating_key'], str(real_play_time), import_ignore_interval)) if not is_import and not user_details['keep_history']: logging_enabled = False logger.debug( u"Tautulli ActivityProcessor :: %s: History logging for user '%s' is disabled." % (server_name, user_details['username'])) elif not is_import and not library_details['keep_history']: logging_enabled = False logger.debug( u"Tautulli ActivityProcessor :: %s: History logging for library '%s' is disabled." % (server_name, library_details['section_name'])) if logging_enabled: # Fetch metadata first so we can return false if it fails if not is_import: logger.debug( u"Tautulli ActivityProcessor :: %s: Fetching metadata for item ratingKey %s" % (server_name, session['rating_key'])) metadata = self.server.get_metadata_details( rating_key=str(session['rating_key'])) if not metadata: return False else: media_info = {} if 'media_info' in metadata and len( metadata['media_info']) > 0: media_info = metadata['media_info'][0] else: metadata = import_metadata ## TODO: Fix media info from imports. Temporary media info from import session. media_info = session # logger.debug(u"Tautulli ActivityProcessor :: %s: Attempting to write sessionKey %s to session_history table..." # % (server_name, session['session_key'])) keys = {'id': None} values = { 'started': session['started'], 'stopped': stopped, 'rating_key': session['rating_key'], 'server_id': session['server_id'], 'parent_rating_key': session['parent_rating_key'], 'grandparent_rating_key': session['grandparent_rating_key'], 'media_type': session['media_type'], 'user_id': session['user_id'], 'user': session['user'], 'ip_address': session['ip_address'], 'paused_counter': session['paused_counter'], 'player': session['player'], 'product': session['product'], 'product_version': session['product_version'], 'platform': session['platform'], 'platform_version': session['platform_version'], 'profile': session['profile'], 'machine_id': session['machine_id'], 'bandwidth': session['bandwidth'], 'location': session['location'], 'quality_profile': session['quality_profile'], 'view_offset': session['view_offset'] } # logger.debug(u"Tautulli ActivityProcessor :: %s: Writing sessionKey %s session_history transaction..." # % (server_name, 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, server_id, rating_key, view_offset, user_id, reference_id FROM session_history ' \ 'WHERE user_id = ? AND server_id = ? AND rating_key = ? ORDER BY id DESC LIMIT 2 ' args = [ session['user_id'], session['server_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() if len(result) > 1: new_session = { 'id': result[0]['id'], 'server_id': result[0]['server_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'] } prev_session = { 'id': result[1]['id'], 'server_id': result[1]['server_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'] } 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 = ? ' # If previous session view offset less than watched percent, # and new session view offset is greater, # then set the reference_id to the previous row, # else set the reference_id to the new id 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']: args = [prev_session['reference_id'], new_session['id']] else: args = [new_session['id'], new_session['id']] self.db.action(query=query, args=args) # logger.debug(u"Tautulli ActivityProcessor :: %s: Successfully written history item, last id for session_history is %s" # % (server_name, last_id)) # Write the session_history_media_info table # logger.debug(u"Tautulli ActivityProcessor :: %s: Attempting to write to sessionKey %s session_history_media_info table..." # % (server_name, session['session_key'])) keys = {'id': last_id} values = { 'rating_key': session['rating_key'], 'server_id': session['server_id'], 'video_decision': session['video_decision'], 'audio_decision': session['audio_decision'], 'transcode_decision': session['transcode_decision'], 'duration': session['duration'], 'container': session['container'], 'bitrate': session['bitrate'], 'width': session['width'], 'height': session['height'], 'video_bit_depth': session['video_bit_depth'], 'video_bitrate': session['video_bitrate'], 'video_codec': session['video_codec'], 'video_codec_level': session['video_codec_level'], 'video_width': session['video_width'], 'video_height': session['video_height'], 'video_resolution': session['video_resolution'], 'video_framerate': session['video_framerate'], 'aspect_ratio': session['aspect_ratio'], 'audio_codec': session['audio_codec'], 'audio_bitrate': session['audio_bitrate'], 'audio_channels': session['audio_channels'], 'subtitle_codec': session['subtitle_codec'], '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'], 'transcode_hw_requested': session['transcode_hw_requested'], 'transcode_hw_full_pipeline': session['transcode_hw_full_pipeline'], 'transcode_hw_decoding': session['transcode_hw_decoding'], 'transcode_hw_decode': session['transcode_hw_decode'], 'transcode_hw_decode_title': session['transcode_hw_decode_title'], 'transcode_hw_encoding': session['transcode_hw_encoding'], 'transcode_hw_encode': session['transcode_hw_encode'], 'transcode_hw_encode_title': session['transcode_hw_encode_title'], 'stream_container': session['stream_container'], 'stream_container_decision': session['stream_container_decision'], 'stream_bitrate': session['stream_bitrate'], 'stream_video_decision': session['stream_video_decision'], 'stream_video_bitrate': session['stream_video_bitrate'], 'stream_video_codec': session['stream_video_codec'], 'stream_video_codec_level': session['stream_video_codec_level'], 'stream_video_bit_depth': session['stream_video_bit_depth'], 'stream_video_height': session['stream_video_height'], 'stream_video_width': session['stream_video_width'], 'stream_video_resolution': session['stream_video_resolution'], 'stream_video_framerate': session['stream_video_framerate'], 'stream_audio_decision': session['stream_audio_decision'], 'stream_audio_codec': session['stream_audio_codec'], 'stream_audio_bitrate': session['stream_audio_bitrate'], 'stream_audio_channels': session['stream_audio_channels'], 'stream_subtitle_decision': session['stream_subtitle_decision'], 'stream_subtitle_codec': session['stream_subtitle_codec'], 'stream_subtitle_container': session['stream_subtitle_container'], 'stream_subtitle_forced': session['stream_subtitle_forced'], 'subtitles': session['subtitles'], 'synced_version': session['synced_version'], 'synced_version_profile': session['synced_version_profile'], 'optimized_version': session['optimized_version'], 'optimized_version_profile': session['optimized_version_profile'], 'optimized_version_title': session['optimized_version_title'] } # logger.debug(u"Tautulli ActivityProcessor :: %s: Writing sessionKey %s session_history_media_info transaction..." # % (server_name, session['session_key'])) self.db.upsert(table_name='session_history_media_info', key_dict=keys, value_dict=values) # Write the session_history_metadata table directors = ";".join(metadata['directors']) writers = ";".join(metadata['writers']) actors = ";".join(metadata['actors']) genres = ";".join(metadata['genres']) labels = ";".join(metadata['labels']) # logger.debug(u"Tautulli ActivityProcessor :: %s: Attempting to write to sessionKey %s session_history_metadata table..." # % (server_name, session['session_key'])) keys = {'id': last_id} values = { 'rating_key': session['rating_key'], 'server_id': session['server_id'], 'parent_rating_key': session['parent_rating_key'], 'grandparent_rating_key': session['grandparent_rating_key'], 'title': session['title'], 'parent_title': session['parent_title'], 'grandparent_title': session['grandparent_title'], 'original_title': session['original_title'], 'full_title': session['full_title'], 'media_index': metadata['media_index'], 'parent_media_index': metadata['parent_media_index'], 'section_id': metadata['section_id'], 'library_id': library_id, 'thumb': metadata['thumb'], 'parent_thumb': metadata['parent_thumb'], 'grandparent_thumb': metadata['grandparent_thumb'], 'art': metadata['art'], 'media_type': session['media_type'], 'year': metadata['year'], 'originally_available_at': metadata['originally_available_at'], 'added_at': metadata['added_at'], 'updated_at': metadata['updated_at'], 'last_viewed_at': metadata['last_viewed_at'], 'content_rating': metadata['content_rating'], 'summary': metadata['summary'], 'tagline': metadata['tagline'], 'rating': metadata['rating'], 'duration': metadata['duration'], 'guid': metadata['guid'], 'directors': directors, 'writers': writers, 'actors': actors, 'genres': genres, 'studio': metadata['studio'], 'labels': labels } # logger.debug(u"Tautulli ActivityProcessor :: %s: Writing sessionKey %s session_history_metadata transaction..." # % (server_name, session['session_key'])) self.db.upsert(table_name='session_history_metadata', key_dict=keys, value_dict=values) # Return the session row id when the session is successfully written to the database return session['id']
def get_synced_items(self, machine_id=None, user_id=None): sync_list = self.get_plextv_sync_lists(machine_id) user_data = users.Users() synced_items = [] try: xml_parse = minidom.parseString(sync_list) except Exception as e: logger.warn(u"PlexPy PlexTV :: Unable to parse XML for get_synced_items: %s" % e) return [] except: logger.warn(u"PlexPy PlexTV :: Unable to parse XML for get_synced_items.") return [] xml_head = xml_parse.getElementsByTagName('SyncList') if not xml_head: logger.warn(u"PlexPy PlexTV :: Unable to parse XML for get_synced_items.") else: for a in xml_head: client_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: device_username = user_data.get_details(user_id=device_user_id)['username'] device_friendly_name = user_data.get_details(user_id=device_user_id)['friendly_name'] except: device_username = '' device_friendly_name = '' device_name = helpers.get_xml_attr(device, 'name') device_product = helpers.get_xml_attr(device, 'product') device_product_version = helpers.get_xml_attr(device, 'productVersion') device_platform = helpers.get_xml_attr(device, 'platform') device_platform_version = helpers.get_xml_attr(device, 'platformVersion') device_type = helpers.get_xml_attr(device, 'device') device_model = helpers.get_xml_attr(device, 'model') device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt') # Filter by user_id if user_id and user_id != device_user_id: continue for synced in a.getElementsByTagName('SyncItems'): sync_item = synced.getElementsByTagName('SyncItem') for item in sync_item: 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') sync_title = helpers.get_xml_attr(item, 'title') sync_metadata_type = helpers.get_xml_attr(item, 'metadataType') sync_content_type = helpers.get_xml_attr(item, 'contentType') for status in item.getElementsByTagName('Status'): status_failure_code = helpers.get_xml_attr(status, 'failureCode') status_failure = helpers.get_xml_attr(status, 'failure') status_state = helpers.get_xml_attr(status, 'state') status_item_count = helpers.get_xml_attr(status, 'itemsCount') status_item_complete_count = helpers.get_xml_attr(status, 'itemsCompleteCount') status_item_downloaded_count = helpers.get_xml_attr(status, 'itemsDownloadedCount') status_item_ready_count = helpers.get_xml_attr(status, 'itemsReadyCount') status_item_successful_count = helpers.get_xml_attr(status, 'itemsSuccessfulCount') status_total_size = helpers.get_xml_attr(status, 'totalSize') status_item_download_percent_complete = helpers.get_percent( status_item_downloaded_count, status_item_count) for settings in item.getElementsByTagName('MediaSettings'): settings_audio_boost = helpers.get_xml_attr(settings, 'audioBoost') settings_music_bitrate = helpers.get_xml_attr(settings, 'musicBitrate') settings_photo_quality = helpers.get_xml_attr(settings, 'photoQuality') settings_photo_resolution = helpers.get_xml_attr(settings, 'photoResolution') 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), "friendly_name": helpers.sanitize(device_friendly_name), "user_id": device_user_id, "root_title": helpers.sanitize(sync_root_title), "title": helpers.sanitize(sync_title), "metadata_type": sync_metadata_type, "content_type": sync_content_type, "rating_key": rating_key, "state": status_state, "item_count": status_item_count, "item_complete_count": status_item_complete_count, "item_downloaded_count": status_item_downloaded_count, "item_downloaded_percent_complete": status_item_download_percent_complete, "music_bitrate": settings_music_bitrate, "photo_quality": settings_photo_quality, "video_quality": settings_video_quality, "total_size": status_total_size, "failure": status_failure, "sync_id": sync_id } synced_items.append(sync_details) return synced_items
def notify(stream_data=None, notify_action=None): from plexpy import users if stream_data and notify_action: # Check if notifications enabled for user user_data = users.Users() user_details = user_data.get_user_friendly_name( user=stream_data['user']) if not user_details['do_notify']: return if (stream_data['media_type'] == 'movie' and plexpy.CONFIG.MOVIE_NOTIFY_ENABLE) \ or (stream_data['media_type'] == 'episode' and plexpy.CONFIG.TV_NOTIFY_ENABLE): progress_percent = helpers.get_percent(stream_data['view_offset'], stream_data['duration']) for agent in notifiers.available_notification_agents(): if agent['on_play'] and notify_action == 'play': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_stop'] and notify_action == 'stop' \ and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexpy.CONFIG.NOTIFY_WATCHED_PERCENT): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_pause'] and notify_action == 'pause' \ and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_resume'] and notify_action == 'resume' \ and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_buffer'] and notify_action == 'buffer': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_watched'] and notify_action == 'watched': # Get the current states for notifications from our db notify_states = get_notify_state(session=stream_data) # If there is nothing in the notify_log for our agent id but it is enabled we should notify if not any(d['agent_id'] == agent['id'] for d in notify_states): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) else: # Check in our notify log if the notification has already been sent for notify_state in notify_states: if not notify_state['on_watched'] and ( notify_state['agent_id'] == agent['id']): # Build and send notification notify_strings = build_notify_text( session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif (stream_data['media_type'] == 'track' and plexpy.CONFIG.MUSIC_NOTIFY_ENABLE): for agent in notifiers.available_notification_agents(): if agent['on_play'] and notify_action == 'play': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_stop'] and notify_action == 'stop': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_pause'] and notify_action == 'pause': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_resume'] and notify_action == 'resume': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif agent['on_buffer'] and notify_action == 'buffer': # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=stream_data, state=notify_action, agent_info=agent) elif stream_data['media_type'] == 'clip': pass else: #logger.debug(u"PlexPy Notifier :: Notify called with unsupported media type.") pass else: logger.debug( u"PlexPy Notifier :: Notify called but incomplete data received.")
def get_synced_items(self, machine_id=None, user_id=None): sync_list = self.get_plextv_sync_lists(machine_id) user_data = users.Users() synced_items = [] try: xml_parse = minidom.parseString(sync_list) except Exception as e: logger.warn( u"PlexPy PlexTV :: Unable to parse XML for get_synced_items: %s" % e) return [] except: logger.warn( u"PlexPy PlexTV :: Unable to parse XML for get_synced_items.") return [] xml_head = xml_parse.getElementsByTagName('SyncList') if not xml_head: logger.warn( u"PlexPy PlexTV :: Unable to parse XML for get_synced_items.") else: for a in xml_head: client_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: device_username = user_data.get_details( user_id=device_user_id)['username'] device_friendly_name = user_data.get_details( user_id=device_user_id)['friendly_name'] except: device_username = '' device_friendly_name = '' device_name = helpers.get_xml_attr(device, 'name') device_product = helpers.get_xml_attr(device, 'product') device_product_version = helpers.get_xml_attr( device, 'productVersion') device_platform = helpers.get_xml_attr(device, 'platform') device_platform_version = helpers.get_xml_attr( device, 'platformVersion') device_type = helpers.get_xml_attr(device, 'device') device_model = helpers.get_xml_attr(device, 'model') device_last_seen = helpers.get_xml_attr( device, 'lastSeenAt') # Filter by user_id if user_id and user_id != device_user_id: continue for synced in a.getElementsByTagName('SyncItems'): sync_item = synced.getElementsByTagName('SyncItem') for item in sync_item: 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') sync_title = helpers.get_xml_attr(item, 'title') sync_metadata_type = helpers.get_xml_attr( item, 'metadataType') sync_content_type = helpers.get_xml_attr( item, 'contentType') for status in item.getElementsByTagName('Status'): status_failure_code = helpers.get_xml_attr( status, 'failureCode') status_failure = helpers.get_xml_attr( status, 'failure') status_state = helpers.get_xml_attr( status, 'state') status_item_count = helpers.get_xml_attr( status, 'itemsCount') status_item_complete_count = helpers.get_xml_attr( status, 'itemsCompleteCount') status_item_downloaded_count = helpers.get_xml_attr( status, 'itemsDownloadedCount') status_item_ready_count = helpers.get_xml_attr( status, 'itemsReadyCount') status_item_successful_count = helpers.get_xml_attr( status, 'itemsSuccessfulCount') status_total_size = helpers.get_xml_attr( status, 'totalSize') status_item_download_percent_complete = helpers.get_percent( status_item_downloaded_count, status_item_count) for settings in item.getElementsByTagName( 'MediaSettings'): settings_audio_boost = helpers.get_xml_attr( settings, 'audioBoost') settings_music_bitrate = helpers.get_xml_attr( settings, 'musicBitrate') settings_photo_quality = helpers.get_xml_attr( settings, 'photoQuality') settings_photo_resolution = helpers.get_xml_attr( settings, 'photoResolution') 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), "friendly_name": helpers.sanitize(device_friendly_name), "user_id": device_user_id, "root_title": helpers.sanitize(sync_root_title), "title": helpers.sanitize(sync_title), "metadata_type": sync_metadata_type, "content_type": sync_content_type, "rating_key": rating_key, "state": status_state, "item_count": status_item_count, "item_complete_count": status_item_complete_count, "item_downloaded_count": status_item_downloaded_count, "item_downloaded_percent_complete": status_item_download_percent_complete, "music_bitrate": settings_music_bitrate, "photo_quality": settings_photo_quality, "video_quality": settings_video_quality, "total_size": status_total_size, "failure": status_failure, "sync_id": sync_id } synced_items.append(sync_details) return synced_items
def build_notify_text(session=None, timeline=None, state=None): # Get time formats date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','') time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','') duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','').replace('a','').replace('A','') # Get the server name server_name = plexpy.CONFIG.PMS_NAME # Get the server uptime plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() if server_times: updated_at = server_times[0]['updated_at'] server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at))) else: logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.") server_uptime = 'N/A' # Get metadata feed for item if session: rating_key = session['rating_key'] elif timeline: rating_key = timeline['rating_key'] pms_connect = pmsconnect.PmsConnect() metadata_list = pms_connect.get_metadata_details(rating_key=rating_key) stream_count = pms_connect.get_current_activity().get('stream_count', '') if metadata_list: metadata = metadata_list['metadata'] else: logger.error(u"PlexPy NotificationHandler :: Unable to retrieve metadata for rating_key %s" % str(rating_key)) return [] # Check for exclusion tags if metadata['media_type'] == 'movie': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL) elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL) elif metadata['media_type'] == 'artist' or metadata['media_type'] == 'track': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*', re.IGNORECASE | re.DOTALL) else: pattern = None if metadata['media_type'] == 'movie' \ or metadata['media_type'] == 'show' or metadata['media_type'] == 'episode' \ or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \ and pattern: # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT)) else: on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT # Create a title if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track': full_title = '%s - %s' % (metadata['grandparent_title'], metadata['title']) else: full_title = metadata['title'] # Session values if session is None: session = {} # Generate a combined transcode decision value if session.get('video_decision','') == 'transcode' or session.get('audio_decision','') == 'transcode': transcode_decision = 'Transcode' elif session.get('video_decision','') == 'copy' or session.get('audio_decision','') == 'copy': transcode_decision = 'Direct Stream' else: transcode_decision = 'Direct Play' if state != 'play': stream_duration = int((time.time() - helpers.cast_to_int(session.get('started', 0)) - helpers.cast_to_int(session.get('paused_counter', 0))) / 60) else: stream_duration = 0 view_offset = helpers.convert_milliseconds_to_minutes(session.get('view_offset', 0)) duration = helpers.convert_milliseconds_to_minutes(metadata['duration']) progress_percent = helpers.get_percent(view_offset, duration) remaining_duration = duration - view_offset # Get media IDs from guid and build URLs if 'imdb://' in metadata['guid']: metadata['imdb_id'] = metadata['guid'].split('imdb://')[1].split('?')[0] metadata['imdb_url'] = 'https://www.imdb.com/title/' + metadata['imdb_id'] metadata['trakt_url'] = 'https://trakt.tv/search/imdb/' + metadata['imdb_id'] if 'thetvdb://' in metadata['guid']: metadata['thetvdb_id'] = metadata['guid'].split('thetvdb://')[1].split('/')[0] metadata['thetvdb_url'] = 'https://thetvdb.com/?tab=series&id=' + metadata['thetvdb_id'] metadata['trakt_url'] = 'https://trakt.tv/search/tvdb/' + metadata['thetvdb_id'] + '?id_type=show' elif 'thetvdbdvdorder://' in metadata['guid']: metadata['thetvdb_id'] = metadata['guid'].split('thetvdbdvdorder://')[1].split('/')[0] metadata['thetvdb_url'] = 'https://thetvdb.com/?tab=series&id=' + metadata['thetvdb_id'] metadata['trakt_url'] = 'https://trakt.tv/search/tvdb/' + metadata['thetvdb_id'] + '?id_type=show' if 'themoviedb://' in metadata['guid']: if metadata['media_type'] == 'movie': metadata['themoviedb_id'] = metadata['guid'].split('themoviedb://')[1].split('?')[0] metadata['themoviedb_url'] = 'https://www.themoviedb.org/movie/' + metadata['themoviedb_id'] metadata['trakt_url'] = 'https://trakt.tv/search/tmdb/' + metadata['themoviedb_id'] + '?id_type=movie' elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode': metadata['themoviedb_id'] = metadata['guid'].split('themoviedb://')[1].split('/')[0] metadata['themoviedb_url'] = 'https://www.themoviedb.org/tv/' + metadata['themoviedb_id'] metadata['trakt_url'] = 'https://trakt.tv/search/tmdb/' + metadata['themoviedb_id'] + '?id_type=show' if 'lastfm://' in metadata['guid']: metadata['lastfm_id'] = metadata['guid'].split('lastfm://')[1].rsplit('/', 1)[0] metadata['lastfm_url'] = 'https://www.last.fm/music/' + metadata['lastfm_id'] if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show' or metadata['media_type'] == 'artist': thumb = metadata['thumb'] elif metadata['media_type'] == 'episode': thumb = metadata['grandparent_thumb'] elif metadata['media_type'] == 'track': thumb = metadata['parent_thumb'] else: thumb = None if thumb: # Retrieve the poster from Plex and cache to file urllib.urlretrieve(plexpy.CONFIG.PMS_URL + thumb + '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN, os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg')) # Upload thumb to Imgur and get link metadata['poster_url'] = helpers.uploadToImgur(os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'), full_title) # Fix metadata params for notify recently added grandparent if state == 'created' and plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT: show_name = metadata['title'] episode_name = '' artist_name = metadata['title'] album_name = '' track_name = '' else: show_name = metadata['grandparent_title'] episode_name = metadata['title'] artist_name = metadata['grandparent_title'] album_name = metadata['parent_title'] track_name = metadata['title'] available_params = {# Global paramaters 'server_name': server_name, 'server_uptime': server_uptime, 'action': state.title(), 'datestamp': arrow.now().format(date_format), 'timestamp': arrow.now().format(time_format), # Stream parameters 'streams': stream_count, 'user': session.get('friendly_name',''), 'platform': session.get('platform',''), 'player': session.get('player',''), 'ip_address': session.get('ip_address','N/A'), 'stream_duration': stream_duration, 'stream_time': arrow.get(stream_duration * 60).format(duration_format), 'remaining_duration': remaining_duration, 'remaining_time': arrow.get(remaining_duration * 60).format(duration_format), 'progress_duration': view_offset, 'progress_time': arrow.get(view_offset * 60).format(duration_format), 'progress_percent': progress_percent, 'container': session.get('container',''), 'video_codec': session.get('video_codec',''), 'video_bitrate': session.get('bitrate',''), 'video_width': session.get('width',''), 'video_height': session.get('height',''), 'video_resolution': session.get('video_resolution',''), 'video_framerate': session.get('video_framerate',''), 'aspect_ratio': session.get('aspect_ratio',''), 'audio_codec': session.get('audio_codec',''), 'audio_channels': session.get('audio_channels',''), 'transcode_decision': transcode_decision, 'video_decision': session.get('video_decision','').title(), 'audio_decision': session.get('audio_decision','').title(), 'transcode_container': session.get('transcode_container',''), 'transcode_video_codec': session.get('transcode_video_codec',''), 'transcode_video_width': session.get('transcode_width',''), 'transcode_video_height': session.get('transcode_height',''), 'transcode_audio_codec': session.get('transcode_audio_codec',''), 'transcode_audio_channels': session.get('transcode_audio_channels',''), 'session_key': session.get('session_key',''), 'user_id': session.get('user_id',''), 'machine_id': session.get('machine_id',''), # Metadata parameters 'media_type': metadata['media_type'], 'title': full_title, 'library_name': metadata['library_name'], 'show_name': show_name, 'episode_name': episode_name, 'artist_name': artist_name, 'album_name': album_name, 'track_name': track_name, 'season_num': metadata['parent_media_index'].zfill(1), 'season_num00': metadata['parent_media_index'].zfill(2), 'episode_num': metadata['media_index'].zfill(1), 'episode_num00': metadata['media_index'].zfill(2), 'track_num': metadata['media_index'].zfill(1), 'track_num00': metadata['media_index'].zfill(2), 'year': metadata['year'], 'studio': metadata['studio'], 'content_rating': metadata['content_rating'], 'directors': ', '.join(metadata['directors']), 'writers': ', '.join(metadata['writers']), 'actors': ', '.join(metadata['actors']), 'genres': ', '.join(metadata['genres']), 'summary': metadata['summary'], 'tagline': metadata['tagline'], 'rating': metadata['rating'], 'duration': duration, 'poster_url': metadata.get('poster_url',''), 'imdb_id': metadata.get('imdb_id',''), 'imdb_url': metadata.get('imdb_url',''), 'thetvdb_id': metadata.get('thetvdb_id',''), 'thetvdb_url': metadata.get('thetvdb_url',''), 'themoviedb_id': metadata.get('themoviedb_id',''), 'themoviedb_url': metadata.get('themoviedb_url',''), 'lastfm_url': metadata.get('lastfm_url',''), 'trakt_url': metadata.get('trakt_url',''), 'section_id': metadata['section_id'], 'rating_key': metadata['rating_key'], 'parent_rating_key': metadata['parent_rating_key'], 'grandparent_rating_key': metadata['grandparent_rating_key'] } # Default subject text subject_text = 'PlexPy (%s)' % server_name # Default scripts args script_args = [] if script_args_text: try: script_args = [unicode(arg).format(**available_params) for arg in script_args_text.split()] except LookupError as e: logger.error(u"PlexPy Notifier :: Unable to parse field %s in script argument. Using fallback." % e) except Exception as e: logger.error(u"PlexPy Notifier :: Unable to parse custom script arguments %s. Using fallback." % e) if state == 'play': # Default body text body_text = '%s (%s) started playing %s' % (session['friendly_name'], session['player'], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format(**available_params) except LookupError, e: logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification subject. Using fallback." % e) except:
def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None, rating_key_filter=None, sync_id_filter=None, server_id_filter=None): if isinstance(rating_key_filter, list): rating_key_filter = [str(k) for k in rating_key_filter] elif rating_key_filter: rating_key_filter = [str(rating_key_filter)] if isinstance(user_id_filter, list): user_id_filter = [str(k) for k in user_id_filter] elif user_id_filter: user_id_filter = [str(user_id_filter)] user_data = users.Users() synced_items = [] for server in plexpy.PMS_SERVERS: if server_id_filter and int(server_id_filter) != server.CONFIG.ID: continue if not session.allow_session_server(server.CONFIG.ID): continue machine_id = server.CONFIG.PMS_IDENTIFIER sync_list = self.get_plextv_sync_lists(machine_id, output_format='xml') try: xml_head = sync_list.getElementsByTagName('SyncList') except Exception as e: logger.warn( u"Tautulli PlexTV :: Unable to parse XML for get_synced_items: %s." % e) return {} for a in xml_head: client_id = helpers.get_xml_attr(a, 'clientIdentifier') # Filter by client_id if client_id_filter and str(client_id_filter) != client_id: continue sync_list_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: device_username = user_data.get_details( user_id=device_user_id)['username'] device_friendly_name = user_data.get_details( user_id=device_user_id)['friendly_name'] except: device_username = '' device_friendly_name = '' device_name = helpers.get_xml_attr(device, 'name') device_product = helpers.get_xml_attr(device, 'product') device_product_version = helpers.get_xml_attr( device, 'productVersion') device_platform = helpers.get_xml_attr(device, 'platform') device_platform_version = helpers.get_xml_attr( device, 'platformVersion') device_type = helpers.get_xml_attr(device, 'device') device_model = helpers.get_xml_attr(device, 'model') device_last_seen = helpers.get_xml_attr( device, 'lastSeenAt') # Filter by user_id if user_id_filter and device_user_id not in user_id_filter: 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 not in rating_key_filter: continue sync_id = helpers.get_xml_attr(item, 'id') # Filter by sync_id if sync_id_filter and str(sync_id_filter) != sync_id: continue sync_version = helpers.get_xml_attr(item, 'version') sync_root_title = helpers.get_xml_attr( item, 'rootTitle') sync_title = helpers.get_xml_attr(item, 'title') sync_metadata_type = helpers.get_xml_attr( item, 'metadataType') sync_content_type = helpers.get_xml_attr( item, 'contentType') for status in item.getElementsByTagName('Status'): status_failure_code = helpers.get_xml_attr( status, 'failureCode') status_failure = helpers.get_xml_attr( status, 'failure') status_state = helpers.get_xml_attr( status, 'state') status_item_count = helpers.get_xml_attr( status, 'itemsCount') status_item_complete_count = helpers.get_xml_attr( status, 'itemsCompleteCount') status_item_downloaded_count = helpers.get_xml_attr( status, 'itemsDownloadedCount') status_item_ready_count = helpers.get_xml_attr( status, 'itemsReadyCount') status_item_successful_count = helpers.get_xml_attr( status, 'itemsSuccessfulCount') status_total_size = helpers.get_xml_attr( status, 'totalSize') status_item_download_percent_complete = helpers.get_percent( status_item_downloaded_count, status_item_count) for settings in item.getElementsByTagName( 'MediaSettings'): settings_video_bitrate = helpers.get_xml_attr( settings, 'maxVideoBitrate') settings_video_quality = helpers.get_xml_attr( settings, 'videoQuality') settings_video_resolution = helpers.get_xml_attr( settings, 'videoResolution') settings_audio_boost = helpers.get_xml_attr( settings, 'audioBoost') settings_audio_bitrate = helpers.get_xml_attr( settings, 'musicBitrate') settings_photo_quality = helpers.get_xml_attr( settings, 'photoQuality') settings_photo_resolution = helpers.get_xml_attr( settings, 'photoResolution') sync_details = { "device_name": helpers.sanitize(device_name), "server_id": server.CONFIG.ID, "server_name": server.CONFIG.PMS_NAME, "platform": helpers.sanitize(device_platform), "user_id": device_user_id, "user": helpers.sanitize(device_friendly_name), "username": helpers.sanitize(device_username), "root_title": helpers.sanitize(sync_root_title), "sync_title": helpers.sanitize(sync_title), "metadata_type": sync_metadata_type, "content_type": sync_content_type, "rating_key": rating_key, "state": status_state, "item_count": status_item_count, "item_complete_count": status_item_complete_count, "item_downloaded_count": status_item_downloaded_count, "item_downloaded_percent_complete": status_item_download_percent_complete, "video_bitrate": settings_video_bitrate, "audio_bitrate": settings_audio_bitrate, "photo_quality": settings_photo_quality, "video_quality": settings_video_quality, "total_size": status_total_size, "failure": status_failure, "client_id": client_id, "sync_id": sync_id } synced_items.append(sync_details) return session.filter_session_info(synced_items, filter_key='user_id')
def get_session_each(self, stream_type='', session=None): session_output = None plex_watch = plexwatch.PlexWatch() if stream_type == 'track': if session.getElementsByTagName('TranscodeSession'): transcode_session = session.getElementsByTagName('TranscodeSession')[0] audio_decision = helpers.get_xml_attr(transcode_session, 'audioDecision') audio_channels = helpers.get_xml_attr(transcode_session, 'audioChannels') audio_codec = helpers.get_xml_attr(transcode_session, 'audioCodec') duration = helpers.get_xml_attr(transcode_session, 'duration') progress = helpers.get_xml_attr(session, 'viewOffset') else: media_info = session.getElementsByTagName('Media')[0] audio_decision = 'direct play' audio_channels = helpers.get_xml_attr(media_info, 'audioChannels') audio_codec = helpers.get_xml_attr(media_info, 'audioCodec') duration = helpers.get_xml_attr(media_info, 'duration') progress = helpers.get_xml_attr(session, 'viewOffset') session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'art': helpers.get_xml_attr(session, 'art'), 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'thumb': helpers.get_xml_attr(session, 'thumb'), 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'friendly_name': plex_watch.get_user_friendly_name( helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'audio_decision': audio_decision, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'video_decision': '', 'video_codec': '', 'height': '', 'width': '', 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), 'type': 'track', 'indexes': 0 } elif stream_type == 'video': if session.getElementsByTagName('TranscodeSession'): transcode_session = session.getElementsByTagName('TranscodeSession')[0] audio_decision = helpers.get_xml_attr(transcode_session, 'audioDecision') audio_channels = helpers.get_xml_attr(transcode_session, 'audioChannels') audio_codec = helpers.get_xml_attr(transcode_session, 'audioCodec') video_decision = helpers.get_xml_attr(transcode_session, 'videoDecision') video_codec = helpers.get_xml_attr(transcode_session, 'videoCodec') width = helpers.get_xml_attr(transcode_session, 'width') height = helpers.get_xml_attr(transcode_session, 'height') duration = helpers.get_xml_attr(session, 'duration') progress = helpers.get_xml_attr(session, 'viewOffset') else: media_info = session.getElementsByTagName('Media')[0] audio_decision = 'direct play' audio_channels = helpers.get_xml_attr(media_info, 'audioChannels') audio_codec = helpers.get_xml_attr(media_info, 'audioCodec') video_decision = 'direct play' video_codec = helpers.get_xml_attr(media_info, 'videoCodec') width = helpers.get_xml_attr(media_info, 'width') height = helpers.get_xml_attr(media_info, 'height') duration = helpers.get_xml_attr(media_info, 'duration') progress = helpers.get_xml_attr(session, 'viewOffset') media_info = session.getElementsByTagName('Media')[0] if media_info.getElementsByTagName('Part'): indexes = helpers.get_xml_attr(media_info.getElementsByTagName('Part')[0], 'indexes') part_id = helpers.get_xml_attr(media_info.getElementsByTagName('Part')[0], 'id') if indexes == 'sd': bif_thumb = '/library/parts/' + part_id + '/indexes/sd/' + progress else: bif_thumb = '' else: indexes = '' bif_thumb = '' if plexpy.CONFIG.PMS_USE_BIF and indexes == 'sd': thumb = bif_thumb use_indexes = 1 else: thumb = helpers.get_xml_attr(session, 'thumb') use_indexes = 0 if helpers.get_xml_attr(session, 'type') == 'episode': session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'art': helpers.get_xml_attr(session, 'art'), 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'thumb': thumb, 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'friendly_name': plex_watch.get_user_friendly_name( helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'audio_decision': audio_decision, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'video_decision': video_decision, 'video_codec': video_codec, 'height': height, 'width': width, 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), 'type': helpers.get_xml_attr(session, 'type'), 'indexes': use_indexes } elif helpers.get_xml_attr(session, 'type') == 'movie': session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'art': helpers.get_xml_attr(session, 'art'), 'thumb': thumb, 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'friendly_name': plex_watch.get_user_friendly_name( helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'audio_decision': audio_decision, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'video_decision': video_decision, 'video_codec': video_codec, 'height': height, 'width': width, 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), 'type': helpers.get_xml_attr(session, 'type'), 'indexes': use_indexes } elif helpers.get_xml_attr(session, 'type') == 'clip': session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'art': helpers.get_xml_attr(session, 'art'), 'thumb': thumb, 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'friendly_name': plex_watch.get_user_friendly_name( helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'audio_decision': audio_decision, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'video_decision': video_decision, 'video_codec': video_codec, 'height': height, 'width': width, 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), 'type': helpers.get_xml_attr(session, 'type'), 'indexes': 0 } else: logger.warn(u"No known stream types found in session list.") return session_output
def get_session_each(self, stream_type='', session=None): session_output = None if stream_type == 'track': if session.getElementsByTagName('TranscodeSession'): transcode_session = session.getElementsByTagName('TranscodeSession')[0] audio_decision = self.get_xml_attr(transcode_session, 'audioDecision') audio_channels = self.get_xml_attr(transcode_session, 'audioChannels') audio_codec = self.get_xml_attr(transcode_session, 'audioCodec') duration = self.get_xml_attr(transcode_session, 'duration') progress = self.get_xml_attr(session, 'viewOffset') else: media_info = session.getElementsByTagName('Media')[0] audio_decision = 'direct play' audio_channels = self.get_xml_attr(media_info, 'audioChannels') audio_codec = self.get_xml_attr(media_info, 'audioCodec') duration = self.get_xml_attr(media_info, 'duration') progress = self.get_xml_attr(session, 'viewOffset') session_output = {'sessionKey': self.get_xml_attr(session, 'sessionKey'), 'parentThumb': self.get_xml_attr(session, 'parentThumb'), 'thumb': self.get_xml_attr(session, 'thumb'), 'user': self.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'player': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'artist': self.get_xml_attr(session, 'grandparentTitle'), 'album': self.get_xml_attr(session, 'parentTitle'), 'track': self.get_xml_attr(session, 'title'), 'ratingKey': self.get_xml_attr(session, 'ratingKey'), 'audioDecision': audio_decision, 'audioChannels': audio_channels, 'audioCodec': audio_codec, 'duration': duration, 'progress': progress, 'progressPercent': str(helpers.get_percent(progress, duration)), 'type': 'track' } elif stream_type == 'video': if session.getElementsByTagName('TranscodeSession'): transcode_session = session.getElementsByTagName('TranscodeSession')[0] audio_decision = self.get_xml_attr(transcode_session, 'audioDecision') audio_channels = self.get_xml_attr(transcode_session, 'audioChannels') audio_codec = self.get_xml_attr(transcode_session, 'audioCodec') video_decision = self.get_xml_attr(transcode_session, 'videoDecision') video_codec = self.get_xml_attr(transcode_session, 'videoCodec') width = self.get_xml_attr(transcode_session, 'width') height = self.get_xml_attr(transcode_session, 'height') duration = self.get_xml_attr(session, 'duration') progress = self.get_xml_attr(session, 'viewOffset') else: media_info = session.getElementsByTagName('Media')[0] audio_decision = 'direct play' audio_channels = self.get_xml_attr(media_info, 'audioChannels') audio_codec = self.get_xml_attr(media_info, 'audioCodec') video_decision = 'direct play' video_codec = self.get_xml_attr(media_info, 'videoCodec') width = self.get_xml_attr(media_info, 'width') height = self.get_xml_attr(media_info, 'height') duration = self.get_xml_attr(media_info, 'duration') progress = self.get_xml_attr(session, 'viewOffset') if self.get_xml_attr(session, 'type') == 'episode': session_output = {'sessionKey': self.get_xml_attr(session, 'sessionKey'), 'art': self.get_xml_attr(session, 'art'), 'thumb': self.get_xml_attr(session, 'thumb'), 'user': self.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'player': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparentTitle': self.get_xml_attr(session, 'grandparentTitle'), 'title': self.get_xml_attr(session, 'title'), 'ratingKey': self.get_xml_attr(session, 'ratingKey'), 'audioDecision': audio_decision, 'audioChannels': audio_channels, 'audioCodec': audio_codec, 'videoDecision': video_decision, 'videoCodec': video_codec, 'height': height, 'width': width, 'duration': duration, 'progress': progress, 'progressPercent': str(helpers.get_percent(progress, duration)), 'type': self.get_xml_attr(session, 'type') } elif self.get_xml_attr(session, 'type') == 'movie': session_output = {'sessionKey': self.get_xml_attr(session, 'sessionKey'), 'art': self.get_xml_attr(session, 'art'), 'thumb': self.get_xml_attr(session, 'thumb'), 'user': self.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'player': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'title': self.get_xml_attr(session, 'title'), 'ratingKey': self.get_xml_attr(session, 'ratingKey'), 'audioDecision': audio_decision, 'audioChannels': audio_channels, 'audioCodec': audio_codec, 'videoDecision': video_decision, 'videoCodec': video_codec, 'height': height, 'width': width, 'duration': duration, 'progress': progress, 'progressPercent': str(helpers.get_percent(progress, duration)), 'type': self.get_xml_attr(session, 'type') } else: logger.warn(u"No known stream types found in session list.") return session_output
def build_notify_text(session=None, timeline=None, state=None): # Get time formats date_format = plexpy.CONFIG.DATE_FORMAT.replace("Do", "").replace("zz", "") time_format = plexpy.CONFIG.TIME_FORMAT.replace("Do", "").replace("zz", "") duration_format = plexpy.CONFIG.TIME_FORMAT.replace("Do", "").replace("zz", "").replace("a", "").replace("A", "") # Get the server name server_name = plexpy.CONFIG.PMS_NAME # Get the server uptime plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() if server_times: updated_at = server_times[0]["updated_at"] server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at))) else: logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.") server_uptime = "N/A" # Get metadata feed for item if session: rating_key = session["rating_key"] elif timeline: rating_key = timeline["rating_key"] pms_connect = pmsconnect.PmsConnect() metadata_list = pms_connect.get_metadata_details(rating_key=rating_key) stream_count = pms_connect.get_current_activity().get("stream_count", "") if metadata_list: metadata = metadata_list["metadata"] else: logger.error(u"PlexPy NotificationHandler :: Unable to retrieve metadata for rating_key %s" % str(rating_key)) return [] # Check for exclusion tags if metadata["media_type"] == "movie": # Regex pattern to remove the text in the tags we don't want pattern = re.compile("\n*<tv>[^>]+.</tv>\n*|\n*<music>[^>]+.</music>\n*", re.IGNORECASE | re.DOTALL) elif metadata["media_type"] == "show" or metadata["media_type"] == "episode": # Regex pattern to remove the text in the tags we don't want pattern = re.compile("\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*", re.IGNORECASE | re.DOTALL) elif metadata["media_type"] == "artist" or metadata["media_type"] == "track": # Regex pattern to remove the text in the tags we don't want pattern = re.compile("\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*", re.IGNORECASE | re.DOTALL) else: pattern = None if ( metadata["media_type"] == "movie" or metadata["media_type"] == "show" or metadata["media_type"] == "episode" or metadata["media_type"] == "artist" or metadata["media_type"] == "track" and pattern ): # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_created_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) script_args_text = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT)) else: on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT # Create a title if metadata["media_type"] == "episode" or metadata["media_type"] == "track": full_title = "%s - %s" % (metadata["grandparent_title"], metadata["title"]) else: full_title = metadata["title"] # Session values if session is None: session = {} # Generate a combined transcode decision value if session.get("video_decision", "") == "transcode" or session.get("audio_decision", "") == "transcode": transcode_decision = "Transcode" elif session.get("video_decision", "") == "copy" or session.get("audio_decision", "") == "copy": transcode_decision = "Direct Stream" else: transcode_decision = "Direct Play" if state != "play": stream_duration = helpers.convert_seconds_to_minutes( time.time() - helpers.cast_to_float(session.get("started", 0)) - helpers.cast_to_float(session.get("paused_counter", 0)) ) else: stream_duration = 0 view_offset = helpers.convert_milliseconds_to_minutes(session.get("view_offset", 0)) duration = helpers.convert_milliseconds_to_minutes(metadata["duration"]) progress_percent = helpers.get_percent(view_offset, duration) remaining_duration = duration - view_offset # Fix metadata params for notify recently added grandparent if state == "created" and plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT: show_name = metadata["title"] episode_name = "" artist_name = metadata["title"] album_name = "" track_name = "" else: show_name = metadata["grandparent_title"] episode_name = metadata["title"] artist_name = metadata["grandparent_title"] album_name = metadata["parent_title"] track_name = metadata["title"] available_params = { # Global paramaters "server_name": server_name, "server_uptime": server_uptime, "action": state.title(), "datestamp": arrow.now().format(date_format), "timestamp": arrow.now().format(time_format), # Stream parameters "streams": stream_count, "user": session.get("friendly_name", ""), "platform": session.get("platform", ""), "player": session.get("player", ""), "ip_address": session.get("ip_address", "N/A"), "stream_duration": stream_duration, "stream_time": arrow.get(stream_duration * 60).format(duration_format), "remaining_duration": remaining_duration, "remaining_time": arrow.get(remaining_duration * 60).format(duration_format), "progress_duration": view_offset, "progress_time": arrow.get(view_offset * 60).format(duration_format), "progress_percent": progress_percent, "container": session.get("container", ""), "video_codec": session.get("video_codec", ""), "video_bitrate": session.get("bitrate", ""), "video_width": session.get("width", ""), "video_height": session.get("height", ""), "video_resolution": session.get("video_resolution", ""), "video_framerate": session.get("video_framerate", ""), "aspect_ratio": session.get("aspect_ratio", ""), "audio_codec": session.get("audio_codec", ""), "audio_channels": session.get("audio_channels", ""), "transcode_decision": transcode_decision, "video_decision": session.get("video_decision", "").title(), "audio_decision": session.get("audio_decision", "").title(), "transcode_container": session.get("transcode_container", ""), "transcode_video_codec": session.get("transcode_video_codec", ""), "transcode_video_width": session.get("transcode_width", ""), "transcode_video_height": session.get("transcode_height", ""), "transcode_audio_codec": session.get("transcode_audio_codec", ""), "transcode_audio_channels": session.get("transcode_audio_channels", ""), "session_key": session.get("session_key", ""), "user_id": session.get("user_id", ""), # Metadata parameters "media_type": metadata["media_type"], "title": full_title, "library_name": metadata["library_name"], "show_name": show_name, "episode_name": episode_name, "artist_name": artist_name, "album_name": album_name, "track_name": track_name, "season_num": metadata["parent_media_index"].zfill(1), "season_num00": metadata["parent_media_index"].zfill(2), "episode_num": metadata["media_index"].zfill(1), "episode_num00": metadata["media_index"].zfill(2), "track_num": metadata["media_index"].zfill(1), "track_num00": metadata["media_index"].zfill(2), "year": metadata["year"], "studio": metadata["studio"], "content_rating": metadata["content_rating"], "directors": ", ".join(metadata["directors"]), "writers": ", ".join(metadata["writers"]), "actors": ", ".join(metadata["actors"]), "genres": ", ".join(metadata["genres"]), "summary": metadata["summary"], "tagline": metadata["tagline"], "rating": metadata["rating"], "duration": metadata["duration"], "section_id": metadata["section_id"], "rating_key": metadata["rating_key"], "parent_rating_key": metadata["parent_rating_key"], "grandparent_rating_key": metadata["grandparent_rating_key"], } # Default subject text subject_text = "PlexPy (%s)" % server_name # Default scripts args script_args = [] if script_args_text: try: script_args = [unicode(arg).format(**available_params) for arg in script_args_text.split()] except LookupError as e: logger.error(u"PlexPy Notifier :: Unable to parse field %s in script argument. Using fallback." % e) except Exception as e: logger.error(u"PlexPy Notifier :: Unable to parse custom script arguments %s. Using fallback." % e) if state == "play": # Default body text body_text = "%s (%s) started playing %s" % (session["friendly_name"], session["player"], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format(**available_params) except LookupError, e: logger.error( u"PlexPy NotificationHandler :: Unable to parse field %s in notification subject. Using fallback." % e ) except:
def build_notify_text(session=None, timeline=None, state=None): import re # Get the server name server_name = plexpy.CONFIG.PMS_NAME # Get the server uptime plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() if server_times: updated_at = server_times[0]['updated_at'] server_uptime = helpers.human_duration( int(time.time() - helpers.cast_to_float(updated_at))) else: logger.error(u"PlexPy Notifier :: Unable to retrieve server uptime.") server_uptime = 'N/A' # Get metadata feed for item if session: rating_key = session['rating_key'] elif timeline: rating_key = timeline['rating_key'] pms_connect = pmsconnect.PmsConnect() metadata_list = pms_connect.get_metadata_details(rating_key=rating_key) if metadata_list: metadata = metadata_list['metadata'] else: logger.error( u"PlexPy Notifier :: Unable to retrieve metadata for rating_key %s" % str(rating_key)) return [] # Check for exclusion tags if metadata['media_type'] == 'movie': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<tv>[^>]+.</tv>|<music>[^>]+.</music>', re.IGNORECASE) elif metadata['media_type'] == 'show' or metadata[ 'media_type'] == 'episode': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<movie>[^>]+.</movie>|<music>[^>]+.</music>', re.IGNORECASE) elif metadata['media_type'] == 'artist' or metadata[ 'media_type'] == 'track': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<tv>[^>]+.</tv>|<movie>[^>]+.</movie>', re.IGNORECASE) else: pattern = None if metadata['media_type'] == 'movie' \ or metadata['media_type'] == 'show' or metadata['media_type'] == 'episode' \ or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \ and pattern: # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_created_subject = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_body = strip_tag( re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) else: on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT # Create a title if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track': full_title = '%s - %s' % (metadata['grandparent_title'], metadata['title']) else: full_title = metadata['title'] duration = helpers.convert_milliseconds_to_minutes(metadata['duration']) # Default values video_decision = '' audio_decision = '' transcode_decision = '' stream_duration = 0 view_offset = 0 user = '' platform = '' player = '' ip_address = 'N/A' # Session values if session: # Generate a combined transcode decision value video_decision = session['video_decision'].title() audio_decision = session['audio_decision'].title() if session['video_decision'] == 'transcode' or session[ 'audio_decision'] == 'transcode': transcode_decision = 'Transcode' elif session['video_decision'] == 'copy' or session[ 'audio_decision'] == 'copy': transcode_decision = 'Direct Stream' else: transcode_decision = 'Direct Play' if state != 'play': if session['paused_counter']: stream_duration = int( (time.time() - helpers.cast_to_float(session['started']) - helpers.cast_to_float(session['paused_counter'])) / 60) else: stream_duration = int( (time.time() - helpers.cast_to_float(session['started'])) / 60) view_offset = helpers.convert_milliseconds_to_minutes( session['view_offset']) user = session['friendly_name'] platform = session['platform'] player = session['player'] ip_address = session['ip_address'] if session['ip_address'] else 'N/A' progress_percent = helpers.get_percent(view_offset, duration) available_params = { 'server_name': server_name, 'server_uptime': server_uptime, 'user': user, 'platform': platform, 'player': player, 'ip_address': ip_address, 'media_type': metadata['media_type'], 'title': full_title, 'show_name': metadata['grandparent_title'], 'episode_name': metadata['title'], 'artist_name': metadata['grandparent_title'], 'album_name': metadata['parent_title'], 'track_name': metadata['title'], 'season_num': metadata['parent_index'].zfill(1), 'season_num00': metadata['parent_index'].zfill(2), 'episode_num': metadata['index'].zfill(1), 'episode_num00': metadata['index'].zfill(2), 'video_decision': video_decision, 'audio_decision': audio_decision, 'transcode_decision': transcode_decision, 'year': metadata['year'], 'studio': metadata['studio'], 'content_rating': metadata['content_rating'], 'directors': ', '.join(metadata['directors']), 'writers': ', '.join(metadata['writers']), 'actors': ', '.join(metadata['actors']), 'genres': ', '.join(metadata['genres']), 'summary': metadata['summary'], 'tagline': metadata['tagline'], 'rating': metadata['rating'], 'duration': duration, 'stream_duration': stream_duration, 'remaining_duration': duration - view_offset, 'progress': view_offset, 'progress_percent': progress_percent } # Default subject text subject_text = 'PlexPy (%s)' % server_name if state == 'play': # Default body text body_text = '%s (%s) is watching %s' % (session['friendly_name'], session['player'], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format( **available_params) except LookupError, e: logger.error( u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except:
def check_active_sessions(ws_request=False): with monitor_lock: pms_connect = pmsconnect.PmsConnect() session_list = pms_connect.get_current_activity() monitor_db = database.MonitorDatabase() monitor_process = activity_processor.ActivityProcessor() # logger.debug(u"PlexPy Monitor :: Checking for active streams.") global int_ping_count if session_list: if int_ping_count >= 3: logger.info( u"PlexPy Monitor :: The Plex Media Server is back up.") # Check if any notification agents have notifications enabled if any(d['on_intup'] for d in notifiers.available_notification_agents()): # Fire off notifications threading.Thread( target=notification_handler.notify_timeline, kwargs=dict(notify_action='intup')).start() int_ping_count = 0 media_container = session_list['sessions'] # Check our temp table for what we must do with the new streams db_streams = monitor_db.select('SELECT * FROM sessions') for stream in db_streams: if any(d['session_key'] == str(stream['session_key']) and d['rating_key'] == str(stream['rating_key']) for d in media_container): # The user's session is still active for session in media_container: if session['session_key'] == str(stream['session_key']) and \ session['rating_key'] == str(stream['rating_key']): # The user is still playing the same media item # Here we can check the play states if session['state'] != stream['state']: if session['state'] == 'paused': logger.debug( u"PlexPy Monitor :: Session %s has been paused." % stream['session_key']) # Check if any notification agents have notifications enabled if any(d['on_pause'] for d in notifiers. available_notification_agents()): # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='pause' )).start() if session['state'] == 'playing' and stream[ 'state'] == 'paused': logger.debug( u"PlexPy Monitor :: Session %s has been resumed." % stream['session_key']) # Check if any notification agents have notifications enabled if any(d['on_resume'] for d in notifiers. available_notification_agents()): # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='resume' )).start() if stream['state'] == 'paused' and not ws_request: # The stream is still paused so we need to increment the paused_counter # Using the set config parameter as the interval, probably not the most accurate but # it will have to do for now. If it's a websocket request don't use this method. paused_counter = int( stream['paused_counter'] ) + plexpy.CONFIG.MONITORING_INTERVAL monitor_db.action( 'UPDATE sessions SET paused_counter = ? ' 'WHERE session_key = ? AND rating_key = ?', [ paused_counter, stream['session_key'], stream['rating_key'] ]) if session[ 'state'] == 'buffering' and plexpy.CONFIG.BUFFER_THRESHOLD > 0: # The stream is buffering so we need to increment the buffer_count # We're going just increment on every monitor ping, # would be difficult to keep track otherwise monitor_db.action( 'UPDATE sessions SET buffer_count = buffer_count + 1 ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) # Check the current buffer count and last buffer to determine if we should notify buffer_values = monitor_db.select( 'SELECT buffer_count, buffer_last_triggered ' 'FROM sessions ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) if buffer_values[0][ 'buffer_count'] >= plexpy.CONFIG.BUFFER_THRESHOLD: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions # Our first buffer notification if buffer_values[0][ 'buffer_count'] == plexpy.CONFIG.BUFFER_THRESHOLD: logger.info( u"PlexPy Monitor :: User '%s' has triggered a buffer warning." % stream['user']) # Set the buffer trigger time monitor_db.action( 'UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) # Check if any notification agents have notifications enabled if any(d['on_buffer'] for d in notifiers. available_notification_agents( )): # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler. notify, kwargs=dict( stream_data=stream, notify_action='buffer' )).start() else: # Subsequent buffer notifications after wait time if int(time.time()) > buffer_values[0]['buffer_last_triggered'] + \ plexpy.CONFIG.BUFFER_WAIT: logger.info( u"PlexPy Monitor :: User '%s' has triggered multiple buffer warnings." % stream['user']) # Set the buffer trigger time monitor_db.action( 'UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) # Check if any notification agents have notifications enabled if any(d['on_buffer'] for d in notifiers. available_notification_agents( )): # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler .notify, kwargs=dict( stream_data=stream, notify_action='buffer' )).start() logger.debug( u"PlexPy Monitor :: Session %s is buffering. Count is now %s. Last triggered %s." % (stream['session_key'], buffer_values[0]['buffer_count'], buffer_values[0]['buffer_last_triggered']) ) # Check if the user has reached the offset in the media we defined as the "watched" percent # Don't trigger if state is buffer as some clients push the progress to the end when # buffering on start. if session['view_offset'] and session[ 'duration'] and session[ 'state'] != 'buffering': if helpers.get_percent( session['view_offset'], session['duration'] ) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT: # Check if any notification agents have notifications enabled if any(d['on_watched'] for d in notifiers. available_notification_agents()): # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='watched' )).start() else: # The user has stopped playing a stream if stream['state'] != 'stopped': logger.debug( u"PlexPy Monitor :: Session %s has stopped." % stream['session_key']) # Set the stream stop time stream['stopped'] = int(time.time()) monitor_db.action( 'UPDATE sessions SET stopped = ?, state = ? ' 'WHERE session_key = ? AND rating_key = ?', [ stream['stopped'], 'stopped', stream['session_key'], stream['rating_key'] ]) # Check if the user has reached the offset in the media we defined as the "watched" percent if stream['view_offset'] and stream['duration']: if helpers.get_percent( stream['view_offset'], stream['duration'] ) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT: # Check if any notification agents have notifications enabled if any(d['on_watched'] for d in notifiers. available_notification_agents()): # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict( stream_data=stream, notify_action='watched')).start() # Check if any notification agents have notifications enabled if any(d['on_stop'] for d in notifiers.available_notification_agents()): # Push any notifications - Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='stop')).start() # Write the item history on playback stop success = monitor_process.write_session_history( session=stream) if success: # If session is written to the databaase successfully, remove the session from the session table logger.debug( u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue" % (stream['session_key'], stream['rating_key'])) monitor_db.action( 'DELETE FROM sessions WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) else: logger.warn(u"PlexPy Monitor :: Failed to write sessionKey %s ratingKey %s to the database. " \ "Will try again on the next pass." % (stream['session_key'], stream['rating_key'])) # Process the newly received session data for session in media_container: new_session = monitor_process.write_session(session) if new_session: logger.debug(u"PlexPy Monitor :: Session %s has started." % session['session_key']) else: logger.debug(u"PlexPy Monitor :: Unable to read session list.") int_ping_count += 1 logger.warn(u"PlexPy Monitor :: Unable to get an internal response from the server, ping attempt %s." \ % str(int_ping_count)) if int_ping_count == 3: # Check if any notification agents have notifications enabled if any(d['on_intdown'] for d in notifiers.available_notification_agents()): # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, kwargs=dict(notify_action='intdown')).start()
def check_active_sessions(): with monitor_lock: pms_connect = pmsconnect.PmsConnect() session_list = pms_connect.get_current_activity() monitor_db = database.MonitorDatabase() monitor_process = MonitorProcessing() # logger.debug(u"PlexPy Monitor :: Checking for active streams.") if session_list: media_container = session_list["sessions"] # Check our temp table for what we must do with the new streams db_streams = monitor_db.select( "SELECT started, session_key, rating_key, media_type, title, parent_title, " "grandparent_title, user_id, user, friendly_name, ip_address, player, " "platform, machine_id, parent_rating_key, grandparent_rating_key, state, " "view_offset, duration, video_decision, audio_decision, width, height, " "container, video_codec, audio_codec, bitrate, video_resolution, " "video_framerate, aspect_ratio, audio_channels, transcode_protocol, " "transcode_container, transcode_video_codec, transcode_audio_codec, " "transcode_audio_channels, transcode_width, transcode_height, paused_counter " "FROM sessions" ) for result in db_streams: # Build a result dictionary for easier referencing stream = { "started": result[0], "session_key": result[1], "rating_key": result[2], "media_type": result[3], "title": result[4], "parent_title": result[5], "grandparent_title": result[6], "user_id": result[7], "user": result[8], "friendly_name": result[9], "ip_address": result[10], "player": result[11], "platform": result[12], "machine_id": result[13], "parent_rating_key": result[14], "grandparent_rating_key": result[15], "state": result[16], "view_offset": result[17], "duration": result[18], "video_decision": result[19], "audio_decision": result[20], "width": result[21], "height": result[22], "container": result[23], "video_codec": result[24], "audio_codec": result[25], "bitrate": result[26], "video_resolution": result[27], "video_framerate": result[28], "aspect_ratio": result[29], "audio_channels": result[30], "transcode_protocol": result[31], "transcode_container": result[32], "transcode_video_codec": result[33], "transcode_audio_codec": result[34], "transcode_audio_channels": result[35], "transcode_width": result[36], "transcode_height": result[37], "paused_counter": result[38], } if any( d["session_key"] == str(stream["session_key"]) and d["rating_key"] == str(stream["rating_key"]) for d in media_container ): # The user's session is still active for session in media_container: if session["session_key"] == str(stream["session_key"]) and session["rating_key"] == str( stream["rating_key"] ): # The user is still playing the same media item # Here we can check the play states if session["state"] != stream["state"]: if session["state"] == "paused": # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action="pause"), ).start() if stream["state"] == "paused": # The stream is still paused so we need to increment the paused_counter # Using the set config parameter as the interval, probably not the most accurate but # it will have to do for now. paused_counter = int(stream["paused_counter"]) + plexpy.CONFIG.MONITORING_INTERVAL monitor_db.action( "UPDATE sessions SET paused_counter = ? " "WHERE session_key = ? AND rating_key = ?", [paused_counter, stream["session_key"], stream["rating_key"]], ) # Check if the user has reached the offset in the media we defined as the "watched" percent if session["progress"] and session["duration"]: if ( helpers.get_percent(session["progress"], session["duration"]) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT ): # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action="watched"), ).start() else: # The user has stopped playing a stream logger.debug( u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue" % (stream["session_key"], stream["rating_key"]) ) monitor_db.action( "DELETE FROM sessions WHERE session_key = ? AND rating_key = ?", [stream["session_key"], stream["rating_key"]], ) # Check if the user has reached the offset in the media we defined as the "watched" percent if stream["view_offset"] and stream["duration"]: if ( helpers.get_percent(stream["view_offset"], stream["duration"]) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT ): # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action="watched"), ).start() # Push any notifications - Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action="stop") ).start() # Write the item history on playback stop monitor_process.write_session_history(session=stream) # Process the newly received session data for session in media_container: monitor_process.write_session(session) else: logger.debug(u"PlexPy Monitor :: Unable to read session list.")
def notify(stream_data=None, notify_action=None): from plexpy import users if stream_data and notify_action: # Check if notifications enabled for user user_data = users.Users() user_details = user_data.get_user_friendly_name(user=stream_data["user"]) if not user_details["do_notify"]: return if stream_data["media_type"] == "movie" or stream_data["media_type"] == "episode": if plexpy.CONFIG.MOVIE_NOTIFY_ENABLE or plexpy.CONFIG.TV_NOTIFY_ENABLE: progress_percent = helpers.get_percent(stream_data["view_offset"], stream_data["duration"]) for agent in notifiers.available_notification_agents(): if agent["on_play"] and notify_action == "play": # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) # Set the notification state in the db set_notify_state(session=stream_data, state="play", agent_info=agent) elif ( agent["on_stop"] and notify_action == "stop" and ( plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexpy.CONFIG.NOTIFY_WATCHED_PERCENT ) ): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) set_notify_state(session=stream_data, state="stop", agent_info=agent) elif ( agent["on_pause"] and notify_action == "pause" and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99) ): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) set_notify_state(session=stream_data, state="pause", agent_info=agent) elif ( agent["on_resume"] and notify_action == "resume" and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99) ): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) set_notify_state(session=stream_data, state="resume", agent_info=agent) elif agent["on_buffer"] and notify_action == "buffer": # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) set_notify_state(session=stream_data, state="buffer", agent_info=agent) elif agent["on_watched"] and notify_action == "watched": # Get the current states for notifications from our db notify_states = get_notify_state(session=stream_data) # If there is nothing in the notify_log for our agent id but it is enabled we should notify if not any(d["agent_id"] == agent["id"] for d in notify_states): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) # Set the notification state in the db set_notify_state(session=stream_data, state="watched", agent_info=agent) else: # Check in our notify log if the notification has already been sent for notify_state in notify_states: if not notify_state["on_watched"] and (notify_state["agent_id"] == agent["id"]): # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) # Set the notification state in the db set_notify_state(session=stream_data, state="watched", agent_info=agent) elif stream_data["media_type"] == "track": if plexpy.CONFIG.MUSIC_NOTIFY_ENABLE: for agent in notifiers.available_notification_agents(): if agent["on_play"] and notify_action == "play": # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) # Set the notification state in the db set_notify_state(session=stream_data, state="play", agent_info=agent) elif agent["on_stop"] and notify_action == "stop": # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) # Set the notification state in the db set_notify_state(session=stream_data, state="stop", agent_info=agent) elif agent["on_pause"] and notify_action == "pause": # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) # Set the notification state in the db set_notify_state(session=stream_data, state="pause", agent_info=agent) elif agent["on_resume"] and notify_action == "resume": # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) # Set the notification state in the db set_notify_state(session=stream_data, state="resume", agent_info=agent) elif agent["on_buffer"] and notify_action == "buffer": # Build and send notification notify_strings = build_notify_text(session=stream_data, state=notify_action) notifiers.send_notification( config_id=agent["id"], subject=notify_strings[0], body=notify_strings[1] ) # Set the notification state in the db set_notify_state(session=stream_data, state="buffer", agent_info=agent) elif stream_data["media_type"] == "clip": pass else: logger.debug(u"PlexPy Notifier :: Notify called with unsupported media type.") pass else: logger.debug(u"PlexPy Notifier :: Notify called but incomplete data received.")
def check_active_sessions(): with monitor_lock: pms_connect = pmsconnect.PmsConnect() session_list = pms_connect.get_current_activity() monitor_db = database.MonitorDatabase() monitor_process = MonitorProcessing() # logger.debug(u"PlexPy Monitor :: Checking for active streams.") if session_list: media_container = session_list['sessions'] # Check our temp table for what we must do with the new streams db_streams = monitor_db.select('SELECT started, session_key, rating_key, media_type, title, parent_title, ' 'grandparent_title, user_id, user, friendly_name, ip_address, player, ' 'platform, machine_id, parent_rating_key, grandparent_rating_key, state, ' 'view_offset, duration, video_decision, audio_decision, width, height, ' 'container, video_codec, audio_codec, bitrate, video_resolution, ' 'video_framerate, aspect_ratio, audio_channels, transcode_protocol, ' 'transcode_container, transcode_video_codec, transcode_audio_codec, ' 'transcode_audio_channels, transcode_width, transcode_height, paused_counter ' 'FROM sessions') for stream in db_streams: if any(d['session_key'] == str(stream['session_key']) and d['rating_key'] == str(stream['rating_key']) for d in media_container): # The user's session is still active for session in media_container: if session['session_key'] == str(stream['session_key']) and \ session['rating_key'] == str(stream['rating_key']): # The user is still playing the same media item # Here we can check the play states if session['state'] != stream['state']: if session['state'] == 'paused': # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='pause')).start() if session['state'] == 'playing' and stream['state'] == 'paused': # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='resume')).start() if stream['state'] == 'paused': # The stream is still paused so we need to increment the paused_counter # Using the set config parameter as the interval, probably not the most accurate but # it will have to do for now. paused_counter = int(stream['paused_counter']) + plexpy.CONFIG.MONITORING_INTERVAL monitor_db.action('UPDATE sessions SET paused_counter = ? ' 'WHERE session_key = ? AND rating_key = ?', [paused_counter, stream['session_key'], stream['rating_key']]) if session['state'] == 'buffering': # The stream is buffering so we need to increment the buffer_count # We're going just increment on every monitor ping, # would be difficult to keep track otherwise monitor_db.action('UPDATE sessions SET buffer_count = buffer_count + 1 ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) # Check the current buffer count and last buffer to determine if we should notify buffer_values = monitor_db.select('SELECT buffer_count, buffer_last_triggered ' 'FROM sessions ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) if buffer_values[0]['buffer_count'] >= plexpy.CONFIG.BUFFER_THRESHOLD: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions # Our first buffer notification if buffer_values[0]['buffer_count'] == plexpy.CONFIG.BUFFER_THRESHOLD: logger.info(u"PlexPy Monitor :: User '%s' has triggered a buffer warning." % stream['user']) # Set the buffer trigger time monitor_db.action('UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='buffer')).start() else: # Subsequent buffer notifications after wait time if int(time.time()) > buffer_values[0]['buffer_last_triggered'] + \ plexpy.CONFIG.BUFFER_WAIT: logger.info(u"PlexPy Monitor :: User '%s' has triggered multiple buffer warnings." % stream['user']) threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='buffer')).start() logger.debug(u"PlexPy Monitor :: Stream buffering. Count is now %s. Last triggered %s." % (buffer_values[0][0], buffer_values[0][1])) # Check if the user has reached the offset in the media we defined as the "watched" percent # Don't trigger if state is buffer as some clients push the progress to the end when # buffering on start. if session['progress'] and session['duration'] and session['state'] != 'buffering': if helpers.get_percent(session['progress'], session['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='watched')).start() else: # The user has stopped playing a stream logger.debug(u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue" % (stream['session_key'], stream['rating_key'])) monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) # Check if the user has reached the offset in the media we defined as the "watched" percent if stream['view_offset'] and stream['duration']: if helpers.get_percent(stream['view_offset'], stream['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='watched')).start() # Push any notifications - Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='stop')).start() # Write the item history on playback stop monitor_process.write_session_history(session=stream) # Process the newly received session data for session in media_container: monitor_process.write_session(session) else: logger.debug(u"PlexPy Monitor :: Unable to read session list.")
def build_notify_text(session, state): from plexpy import pmsconnect, helpers import re # Get the server name pms_connect = pmsconnect.PmsConnect() server_name = pms_connect.get_server_pref(pref="FriendlyName") # Get metadata feed for item metadata = pms_connect.get_metadata_details(rating_key=session["rating_key"]) if metadata: item_metadata = metadata["metadata"] else: logger.error(u"PlexPy Notifier :: Unable to retrieve metadata for rating_key %s" % str(session["rating_key"])) return [] # Check for exclusion tags if session["media_type"] == "episode": # Regex pattern to remove the text in the tags we don't want pattern = re.compile("<movie>[^>]+.</movie>|<music>[^>]+.</music>", re.IGNORECASE) elif session["media_type"] == "movie": # Regex pattern to remove the text in the tags we don't want pattern = re.compile("<tv>[^>]+.</tv>|<music>[^>]+.</music>", re.IGNORECASE) elif session["media_type"] == "track": # Regex pattern to remove the text in the tags we don't want pattern = re.compile("<tv>[^>]+.</tv>|<movie>[^>]+.</movie>", re.IGNORECASE) else: pattern = None if ( session["media_type"] == "episode" or session["media_type"] == "movie" or session["media_type"] == "track" and pattern ): # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag(re.sub(pattern, "", plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) else: on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT # Create a title if session["media_type"] == "episode": full_title = "%s - %s" % (session["grandparent_title"], session["title"]) elif session["media_type"] == "track": full_title = "%s - %s" % (session["grandparent_title"], session["title"]) else: full_title = session["title"] # Generate a combined transcode decision value if session["video_decision"]: if session["video_decision"] == "transcode": transcode_decision = "Transcode" elif session["video_decision"] == "copy" or session["audio_decision"] == "copy": transcode_decision = "Direct Stream" else: transcode_decision = "Direct Play" else: if session["audio_decision"] == "transcode": transcode_decision = "Transcode" else: transcode_decision = "Direct Play" duration = helpers.convert_milliseconds_to_minutes(item_metadata["duration"]) view_offset = helpers.convert_milliseconds_to_minutes(session["view_offset"]) stream_duration = 0 if state != "play": if session["paused_counter"]: stream_duration = int( ( time.time() - helpers.cast_to_float(session["started"]) - helpers.cast_to_float(session["paused_counter"]) ) / 60 ) else: stream_duration = int((time.time() - helpers.cast_to_float(session["started"])) / 60) progress_percent = helpers.get_percent(view_offset, duration) available_params = { "server_name": server_name, "user": session["friendly_name"], "platform": session["platform"], "player": session["player"], "media_type": session["media_type"], "title": full_title, "show_name": item_metadata["grandparent_title"], "episode_name": item_metadata["title"], "artist_name": item_metadata["grandparent_title"], "album_name": item_metadata["parent_title"], "season_num": item_metadata["parent_index"], "season_num00": item_metadata["parent_index"].zfill(2), "episode_num": item_metadata["index"], "episode_num00": item_metadata["index"].zfill(2), "transcode_decision": transcode_decision, "year": item_metadata["year"], "studio": item_metadata["studio"], "content_rating": item_metadata["content_rating"], "summary": item_metadata["summary"], "rating": item_metadata["rating"], "duration": duration, "stream_duration": stream_duration, "remaining_duration": duration - view_offset, "progress": view_offset, "progress_percent": progress_percent, } # Default subject text subject_text = "PlexPy (%s)" % server_name if state == "play": # Default body text body_text = "%s (%s) is watching %s" % (session["friendly_name"], session["player"], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format(**available_params) except LookupError, e: logger.error( u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except:
def check_active_sessions(server=None, ws_request=False): if server.WS and server.WS.WS_CONNECTION and server.WS.WS_CONNECTION.connected: with server.monitor_lock: session_list = server.get_current_activity() monitor_db = database.MonitorDatabase() monitor_process = activity_processor.ActivityProcessor(server) logger.debug( u"Tautulli Monitor :: %s: Checking for active streams." % server.CONFIG.PMS_NAME) if session_list: media_container = session_list['sessions'] # Check our temp table for what we must do with the new streams db_streams = monitor_process.get_sessions() for stream in db_streams: if any(d['session_key'] == str(stream['session_key']) and d['rating_key'] == str(stream['rating_key']) for d in media_container): # The user's session is still active for session in media_container: if session['session_key'] == str(stream['session_key']) and \ session['rating_key'] == str(stream['rating_key']): # The user is still playing the same media item # Here we can check the play states if session['state'] != stream['state']: if session['state'] == 'paused': logger.debug( u"Tautulli Monitor :: %s: Session %s paused." % (server.CONFIG.PMS_NAME, stream['session_key'])) plexpy.NOTIFY_QUEUE.put({ 'stream_data': stream.copy(), 'notify_action': 'on_pause' }) if session['state'] == 'playing' and stream[ 'state'] == 'paused': logger.debug( u"Tautulli Monitor :: %s: Session %s resumed." % (server.CONFIG.PMS_NAME, stream['session_key'])) plexpy.NOTIFY_QUEUE.put({ 'stream_data': stream.copy(), 'notify_action': 'on_resume' }) if stream[ 'state'] == 'paused' and not ws_request: # The stream is still paused so we need to increment the paused_counter # Using the set config parameter as the interval, probably not the most accurate but # it will have to do for now. If it's a websocket request don't use this method. paused_counter = int( stream['paused_counter'] ) + plexpy.CONFIG.MONITORING_INTERVAL monitor_db.action( 'UPDATE sessions SET paused_counter = ? ' 'WHERE session_key = ? AND rating_key = ?', [ paused_counter, stream['session_key'], stream['rating_key'] ]) if session[ 'state'] == 'buffering' and plexpy.CONFIG.BUFFER_THRESHOLD > 0: # The stream is buffering so we need to increment the buffer_count # We're going just increment on every monitor ping, # would be difficult to keep track otherwise monitor_db.action( 'UPDATE sessions SET buffer_count = buffer_count + 1 ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) # Check the current buffer count and last buffer to determine if we should notify buffer_values = monitor_db.select( 'SELECT buffer_count, buffer_last_triggered ' 'FROM sessions ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) if buffer_values[0][ 'buffer_count'] >= plexpy.CONFIG.BUFFER_THRESHOLD: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions # Our first buffer notification if buffer_values[0][ 'buffer_count'] == plexpy.CONFIG.BUFFER_THRESHOLD: logger.info( u"Tautulli Monitor :: %s: User '%s' has triggered a buffer warning." % (server.CONFIG.PMS_NAME, stream['user'])) # Set the buffer trigger time monitor_db.action( 'UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) plexpy.NOTIFY_QUEUE.put({ 'stream_data': stream.copy(), 'notify_action': 'on_buffer' }) else: # Subsequent buffer notifications after wait time if int(time.time()) > buffer_values[0]['buffer_last_triggered'] + \ plexpy.CONFIG.BUFFER_WAIT: logger.info( u"Tautulli Monitor :: %s: User '%s' has triggered multiple buffer warnings." % (server.CONFIG.PMS_NAME, stream['user'])) # Set the buffer trigger time monitor_db.action( 'UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) plexpy.NOTIFY_QUEUE.put({ 'stream_data': stream.copy(), 'notify_action': 'on_buffer' }) logger.debug( u"Tautulli Monitor :: %s: Session %s is buffering. Count is now %s. Last triggered %s." % (server.CONFIG.PMS_NAME, stream['session_key'], buffer_values[0]['buffer_count'], buffer_values[0] ['buffer_last_triggered'])) # Check if the user has reached the offset in the media we defined as the "watched" percent # Don't trigger if state is buffer as some clients push the progress to the end when # buffering on start. if session['state'] != 'buffering': progress_percent = helpers.get_percent( session['view_offset'], session['duration']) notify_states = notification_handler.get_notify_state( session=session) if (session['media_type'] == 'movie' and progress_percent >= plexpy.CONFIG.MOVIE_WATCHED_PERCENT or session['media_type'] == 'episode' and progress_percent >= plexpy.CONFIG.TV_WATCHED_PERCENT or session['media_type'] == 'track' and progress_percent >= plexpy.CONFIG.MUSIC_WATCHED_PERCENT) \ and not any(d['notify_action'] == 'on_watched' for d in notify_states): plexpy.NOTIFY_QUEUE.put({ 'stream_data': stream.copy(), 'notify_action': 'on_watched' }) else: # The user has stopped playing a stream if stream['state'] != 'stopped': logger.debug( u"Tautulli Monitor :: %s: Session %s stopped." % (server.CONFIG.PMS_NAME, stream['session_key'])) if not stream['stopped']: # Set the stream stop time stream['stopped'] = int(time.time()) monitor_db.action( 'UPDATE sessions SET stopped = ?, state = ? ' 'WHERE session_key = ? AND rating_key = ?', [ stream['stopped'], 'stopped', stream['session_key'], stream['rating_key'] ]) progress_percent = helpers.get_percent( stream['view_offset'], stream['duration']) notify_states = notification_handler.get_notify_state( session=stream) if (stream['media_type'] == 'movie' and progress_percent >= plexpy.CONFIG.MOVIE_WATCHED_PERCENT or stream['media_type'] == 'episode' and progress_percent >= plexpy.CONFIG.TV_WATCHED_PERCENT or stream['media_type'] == 'track' and progress_percent >= plexpy.CONFIG.MUSIC_WATCHED_PERCENT) \ and not any(d['notify_action'] == 'on_watched' for d in notify_states): plexpy.NOTIFY_QUEUE.put({ 'stream_data': stream.copy(), 'notify_action': 'on_watched' }) plexpy.NOTIFY_QUEUE.put({ 'stream_data': stream.copy(), 'notify_action': 'on_stop' }) # Write the item history on playback stop row_id = monitor_process.write_session_history( session=stream) if row_id: # If session is written to the databaase successfully, remove the session from the session table logger.debug( u"Tautulli Monitor :: %s: Removing sessionKey %s ratingKey %s from session queue" % (server.CONFIG.PMS_NAME, stream['session_key'], stream['rating_key'])) monitor_process.delete_session(row_id=row_id) else: stream['write_attempts'] += 1 if stream[ 'write_attempts'] < plexpy.CONFIG.SESSION_DB_WRITE_ATTEMPTS: logger.warn(u"Tautulli Monitor :: %s: Failed to write sessionKey %s ratingKey %s to the database. " \ "Will try again on the next pass. Write attempt %s." % (server.CONFIG.PMS_NAME, stream['session_key'], stream['rating_key'], str(stream['write_attempts']))) monitor_process.increment_write_attempts( session_key=stream['session_key']) else: logger.warn(u"Tautulli Monitor :: %s: Failed to write sessionKey %s ratingKey %s to the database. " \ "Removing session from the database. Write attempt %s." % (server.CONFIG.PMS_NAME, stream['session_key'], stream['rating_key'], str(stream['write_attempts']))) logger.debug( u"Tautulli Monitor :: %s: Removing sessionKey %s ratingKey %s from session queue" % (server.CONFIG.PMS_NAME, stream['session_key'], stream['rating_key'])) monitor_process.delete_session( session_key=stream['session_key']) # Process the newly received session data for session in media_container: new_session = monitor_process.write_session(session) if new_session: logger.debug( u"Tautulli Monitor :: %s: Session %s started by user %s with ratingKey %s." % (server.CONFIG.PMS_NAME, session['session_key'], session['user_id'], session['rating_key'])) else: logger.debug( u"Tautulli Monitor :: %s: Unable to read session list." % server.CONFIG.PMS_NAME)
def get_session_each(self, stream_type='', session=None): session_output = None data_factory = datafactory.DataFactory() if stream_type == 'track': media_info = session.getElementsByTagName('Media')[0] audio_decision = 'direct play' audio_channels = helpers.get_xml_attr(media_info, 'audioChannels') audio_codec = helpers.get_xml_attr(media_info, 'audioCodec') container = helpers.get_xml_attr(media_info, 'container') bitrate = helpers.get_xml_attr(media_info, 'bitrate') duration = helpers.get_xml_attr(media_info, 'duration') progress = helpers.get_xml_attr(session, 'viewOffset') if session.getElementsByTagName('TranscodeSession'): transcode_session = session.getElementsByTagName('TranscodeSession')[0] audio_decision = helpers.get_xml_attr(transcode_session, 'audioDecision') transcode_audio_channels = helpers.get_xml_attr(transcode_session, 'audioChannels') transcode_audio_codec = helpers.get_xml_attr(transcode_session, 'audioCodec') transcode_container = helpers.get_xml_attr(transcode_session, 'container') transcode_protocol = helpers.get_xml_attr(transcode_session, 'protocol') duration = helpers.get_xml_attr(transcode_session, 'duration') else: transcode_audio_channels = '' transcode_audio_codec = '' transcode_container = '' transcode_protocol = '' user_details = data_factory.get_user_details( user=helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')) if helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier').endswith('_Track'): machine_id = helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier')[:-6] else: machine_id = helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier') session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'media_index': helpers.get_xml_attr(session, 'index'), 'parent_media_index': helpers.get_xml_attr(session, 'parentIndex'), 'art': helpers.get_xml_attr(session, 'art'), 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'grandparent_thumb': helpers.get_xml_attr(session, 'grandparentThumb'), 'thumb': helpers.get_xml_attr(session, 'thumb'), 'bif_thumb': '', 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'audio_decision': audio_decision, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'video_decision': '', 'video_codec': '', 'height': '', 'width': '', 'container': container, 'bitrate': bitrate, 'video_resolution': '', 'video_framerate': '', 'aspect_ratio': '', 'transcode_audio_channels': transcode_audio_channels, 'transcode_audio_codec': transcode_audio_codec, 'transcode_video_codec': '', 'transcode_width': '', 'transcode_height': '', 'transcode_container': transcode_container, 'transcode_protocol': transcode_protocol, 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), 'type': 'track', 'indexes': 0 } elif stream_type == 'video': media_info = session.getElementsByTagName('Media')[0] audio_decision = 'direct play' audio_channels = helpers.get_xml_attr(media_info, 'audioChannels') audio_codec = helpers.get_xml_attr(media_info, 'audioCodec') video_decision = 'direct play' video_codec = helpers.get_xml_attr(media_info, 'videoCodec') container = helpers.get_xml_attr(media_info, 'container') bitrate = helpers.get_xml_attr(media_info, 'bitrate') video_resolution = helpers.get_xml_attr(media_info, 'videoResolution') video_framerate = helpers.get_xml_attr(media_info, 'videoFrameRate') aspect_ratio = helpers.get_xml_attr(media_info, 'aspectRatio') width = helpers.get_xml_attr(media_info, 'width') height = helpers.get_xml_attr(media_info, 'height') duration = helpers.get_xml_attr(media_info, 'duration') progress = helpers.get_xml_attr(session, 'viewOffset') if session.getElementsByTagName('TranscodeSession'): transcode_session = session.getElementsByTagName('TranscodeSession')[0] audio_decision = helpers.get_xml_attr(transcode_session, 'audioDecision') transcode_audio_channels = helpers.get_xml_attr(transcode_session, 'audioChannels') transcode_audio_codec = helpers.get_xml_attr(transcode_session, 'audioCodec') video_decision = helpers.get_xml_attr(transcode_session, 'videoDecision') transcode_video_codec = helpers.get_xml_attr(transcode_session, 'videoCodec') transcode_width = helpers.get_xml_attr(transcode_session, 'width') transcode_height = helpers.get_xml_attr(transcode_session, 'height') transcode_container = helpers.get_xml_attr(transcode_session, 'container') transcode_protocol = helpers.get_xml_attr(transcode_session, 'protocol') else: transcode_audio_channels = '' transcode_audio_codec = '' transcode_video_codec = '' transcode_width = '' transcode_height = '' transcode_container = '' transcode_protocol = '' media_info = session.getElementsByTagName('Media')[0] if media_info.getElementsByTagName('Part'): indexes = helpers.get_xml_attr(media_info.getElementsByTagName('Part')[0], 'indexes') part_id = helpers.get_xml_attr(media_info.getElementsByTagName('Part')[0], 'id') if indexes == 'sd': bif_thumb = '/library/parts/' + part_id + '/indexes/sd/' + progress else: bif_thumb = '' else: indexes = '' bif_thumb = '' if plexpy.CONFIG.PMS_USE_BIF and indexes == 'sd': use_indexes = 1 else: use_indexes = 0 user_details = data_factory.get_user_details( user=helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')) if helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier').endswith('_Video'): machine_id = helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier')[:-6] else: machine_id = helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier') if helpers.get_xml_attr(session, 'type') == 'episode': session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'media_index': helpers.get_xml_attr(session, 'index'), 'parent_media_index': helpers.get_xml_attr(session, 'parentIndex'), 'art': helpers.get_xml_attr(session, 'art'), 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'grandparent_thumb': helpers.get_xml_attr(session, 'grandparentThumb'), 'thumb': helpers.get_xml_attr(session, 'thumb'), 'bif_thumb': bif_thumb, 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'audio_decision': audio_decision, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'video_decision': video_decision, 'video_codec': video_codec, 'height': height, 'width': width, 'container': container, 'bitrate': bitrate, 'video_resolution': video_resolution, 'video_framerate': video_framerate, 'aspect_ratio': aspect_ratio, 'transcode_audio_channels': transcode_audio_channels, 'transcode_audio_codec': transcode_audio_codec, 'transcode_video_codec': transcode_video_codec, 'transcode_width': transcode_width, 'transcode_height': transcode_height, 'transcode_container': transcode_container, 'transcode_protocol': transcode_protocol, 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), 'type': helpers.get_xml_attr(session, 'type'), 'indexes': use_indexes } elif helpers.get_xml_attr(session, 'type') == 'movie': session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'media_index': helpers.get_xml_attr(session, 'index'), 'parent_media_index': helpers.get_xml_attr(session, 'parentIndex'), 'art': helpers.get_xml_attr(session, 'art'), 'thumb': helpers.get_xml_attr(session, 'thumb'), 'bif_thumb': bif_thumb, 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'grandparent_thumb': helpers.get_xml_attr(session, 'grandparentThumb'), 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'audio_decision': audio_decision, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'video_decision': video_decision, 'video_codec': video_codec, 'height': height, 'width': width, 'container': container, 'bitrate': bitrate, 'video_resolution': video_resolution, 'video_framerate': video_framerate, 'aspect_ratio': aspect_ratio, 'transcode_audio_channels': transcode_audio_channels, 'transcode_audio_codec': transcode_audio_codec, 'transcode_video_codec': transcode_video_codec, 'transcode_width': transcode_width, 'transcode_height': transcode_height, 'transcode_container': transcode_container, 'transcode_protocol': transcode_protocol, 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), 'type': helpers.get_xml_attr(session, 'type'), 'indexes': use_indexes } elif helpers.get_xml_attr(session, 'type') == 'clip': session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'media_index': helpers.get_xml_attr(session, 'index'), 'parent_media_index': helpers.get_xml_attr(session, 'parentIndex'), 'art': helpers.get_xml_attr(session, 'art'), 'thumb': helpers.get_xml_attr(session, 'thumb'), 'bif_thumb': bif_thumb, 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'grandparent_thumb': helpers.get_xml_attr(session, 'grandparentThumb'), 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'audio_decision': audio_decision, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'video_decision': video_decision, 'video_codec': video_codec, 'height': height, 'width': width, 'container': container, 'bitrate': bitrate, 'video_resolution': video_resolution, 'video_framerate': video_framerate, 'aspect_ratio': aspect_ratio, 'transcode_audio_channels': transcode_audio_channels, 'transcode_audio_codec': transcode_audio_codec, 'transcode_video_codec': transcode_video_codec, 'transcode_width': transcode_width, 'transcode_height': transcode_height, 'transcode_container': transcode_container, 'transcode_protocol': transcode_protocol, 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), 'type': helpers.get_xml_attr(session, 'type'), 'indexes': 0 } else: logger.warn(u"No known stream types found in session list.") return session_output
def check_active_sessions(ws_request=False): with monitor_lock: pms_connect = pmsconnect.PmsConnect() session_list = pms_connect.get_current_activity() monitor_db = database.MonitorDatabase() monitor_process = activity_processor.ActivityProcessor() # logger.debug(u"PlexPy Monitor :: Checking for active streams.") if session_list: media_container = session_list['sessions'] # Check our temp table for what we must do with the new streams db_streams = monitor_db.select( 'SELECT started, session_key, rating_key, media_type, title, parent_title, ' 'grandparent_title, user_id, user, friendly_name, ip_address, player, ' 'platform, machine_id, parent_rating_key, grandparent_rating_key, state, ' 'view_offset, duration, video_decision, audio_decision, width, height, ' 'container, video_codec, audio_codec, bitrate, video_resolution, ' 'video_framerate, aspect_ratio, audio_channels, transcode_protocol, ' 'transcode_container, transcode_video_codec, transcode_audio_codec, ' 'transcode_audio_channels, transcode_width, transcode_height, ' 'paused_counter, last_paused ' 'FROM sessions') for stream in db_streams: if any(d['session_key'] == str(stream['session_key']) and d['rating_key'] == str(stream['rating_key']) for d in media_container): # The user's session is still active for session in media_container: if session['session_key'] == str(stream['session_key']) and \ session['rating_key'] == str(stream['rating_key']): # The user is still playing the same media item # Here we can check the play states if session['state'] != stream['state']: if session['state'] == 'paused': # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict( stream_data=stream, notify_action='pause')).start() if session['state'] == 'playing' and stream[ 'state'] == 'paused': # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict( stream_data=stream, notify_action='resume')).start() if stream['state'] == 'paused' and not ws_request: # The stream is still paused so we need to increment the paused_counter # Using the set config parameter as the interval, probably not the most accurate but # it will have to do for now. If it's a websocket request don't use this method. paused_counter = int( stream['paused_counter'] ) + plexpy.CONFIG.MONITORING_INTERVAL monitor_db.action( 'UPDATE sessions SET paused_counter = ? ' 'WHERE session_key = ? AND rating_key = ?', [ paused_counter, stream['session_key'], stream['rating_key'] ]) if session[ 'state'] == 'buffering' and plexpy.CONFIG.BUFFER_THRESHOLD > 0: # The stream is buffering so we need to increment the buffer_count # We're going just increment on every monitor ping, # would be difficult to keep track otherwise monitor_db.action( 'UPDATE sessions SET buffer_count = buffer_count + 1 ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) # Check the current buffer count and last buffer to determine if we should notify buffer_values = monitor_db.select( 'SELECT buffer_count, buffer_last_triggered ' 'FROM sessions ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) if buffer_values[0][ 'buffer_count'] >= plexpy.CONFIG.BUFFER_THRESHOLD: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions # Our first buffer notification if buffer_values[0][ 'buffer_count'] == plexpy.CONFIG.BUFFER_THRESHOLD: logger.info( u"PlexPy Monitor :: User '%s' has triggered a buffer warning." % stream['user']) # Set the buffer trigger time monitor_db.action( 'UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='buffer' )).start() else: # Subsequent buffer notifications after wait time if int(time.time()) > buffer_values[0]['buffer_last_triggered'] + \ plexpy.CONFIG.BUFFER_WAIT: logger.info( u"PlexPy Monitor :: User '%s' has triggered multiple buffer warnings." % stream['user']) # Set the buffer trigger time monitor_db.action( 'UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [ stream['session_key'], stream['rating_key'] ]) threading.Thread( target=notification_handler. notify, kwargs=dict( stream_data=stream, notify_action='buffer' )).start() logger.debug( u"PlexPy Monitor :: Stream buffering. Count is now %s. Last triggered %s." % (buffer_values[0][0], buffer_values[0][1])) # Check if the user has reached the offset in the media we defined as the "watched" percent # Don't trigger if state is buffer as some clients push the progress to the end when # buffering on start. if session['view_offset'] and session[ 'duration'] and session[ 'state'] != 'buffering': if helpers.get_percent( session['view_offset'], session['duration'] ) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict( stream_data=stream, notify_action='watched')).start() else: # The user has stopped playing a stream logger.debug( u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue" % (stream['session_key'], stream['rating_key'])) monitor_db.action( 'DELETE FROM sessions WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) # Check if the user has reached the offset in the media we defined as the "watched" percent if stream['view_offset'] and stream['duration']: if helpers.get_percent( stream['view_offset'], stream['duration'] ) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions threading.Thread( target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='watched')).start() # Push any notifications - Push it on it's own thread so we don't hold up our db actions threading.Thread(target=notification_handler.notify, kwargs=dict( stream_data=stream, notify_action='stop')).start() # Write the item history on playback stop monitor_process.write_session_history(session=stream) # Process the newly received session data for session in media_container: monitor_process.write_session(session) else: logger.debug(u"PlexPy Monitor :: Unable to read session list.")
def build_notify_text(session, state): from plexpy import pmsconnect, helpers import re # Get the server name pms_connect = pmsconnect.PmsConnect() server_name = pms_connect.get_server_pref(pref='FriendlyName') # Get metadata feed for item metadata = pms_connect.get_metadata_details(rating_key=session['rating_key']) if metadata: item_metadata = metadata['metadata'] else: logger.error(u"PlexPy Notifier :: Unable to retrieve metadata for rating_key %s" % str(session['rating_key'])) return [] # Check for exclusion tags if session['media_type'] == 'episode': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<movie>[^>]+.</movie>|<music>[^>]+.</music>', re.IGNORECASE) elif session['media_type'] == 'movie': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<tv>[^>]+.</tv>|<music>[^>]+.</music>', re.IGNORECASE) elif session['media_type'] == 'track': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<tv>[^>]+.</tv>|<movie>[^>]+.</movie>', re.IGNORECASE) else: pattern = None if session['media_type'] == 'episode' or session['media_type'] == 'movie' or session['media_type'] == 'track' \ and pattern: # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) else: on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT # Create a title if session['media_type'] == 'episode': full_title = '%s - %s' % (session['grandparent_title'], session['title']) elif session['media_type'] == 'track': full_title = '%s - %s' % (session['grandparent_title'], session['title']) else: full_title = session['title'] # Generate a combined transcode decision value if session['video_decision']: if session['video_decision'] == 'transcode': transcode_decision = 'Transcode' elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy': transcode_decision = 'Direct Stream' else: transcode_decision = 'Direct Play' else: if session['audio_decision'] == 'transcode': transcode_decision = 'Transcode' else: transcode_decision = 'Direct Play' duration = helpers.convert_milliseconds_to_minutes(item_metadata['duration']) view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset']) stream_duration = 0 if state != 'play': if session['paused_counter']: stream_duration = int((time.time() - helpers.cast_to_float(session['started']) - helpers.cast_to_float(session['paused_counter'])) / 60) else: stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60) progress_percent = helpers.get_percent(view_offset, duration) available_params = {'server_name': server_name, 'user': session['friendly_name'], 'player': session['player'], 'title': full_title, 'show_name': item_metadata['grandparent_title'], 'episode_name': item_metadata['title'], 'platform': session['platform'], 'media_type': session['media_type'], 'transcode_decision': transcode_decision, 'year': item_metadata['year'], 'studio': item_metadata['studio'], 'content_rating': item_metadata['content_rating'], 'summary': item_metadata['summary'], 'season_num': item_metadata['parent_index'], 'season_num00': item_metadata['parent_index'].zfill(2), 'episode_num': item_metadata['index'], 'episode_num00': item_metadata['index'].zfill(2), 'album_name': item_metadata['parent_title'], 'rating': item_metadata['rating'], 'duration': duration, 'stream_duration': stream_duration, 'progress': view_offset, 'progress_percent': progress_percent } # Default subject text subject_text = 'PlexPy (%s)' % server_name if state == 'play': # Default body text body_text = '%s (%s) is watching %s' % (session['friendly_name'], session['player'], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format(**available_params) except LookupError, e: logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except:
sync_title = helpers.get_xml_attr(item, "title") sync_metadata_type = helpers.get_xml_attr(item, "metadataType") sync_content_type = helpers.get_xml_attr(item, "contentType") for status in item.getElementsByTagName("Status"): status_failure_code = helpers.get_xml_attr(status, "failureCode") status_failure = helpers.get_xml_attr(status, "failure") status_state = helpers.get_xml_attr(status, "state") status_item_count = helpers.get_xml_attr(status, "itemsCount") status_item_complete_count = helpers.get_xml_attr(status, "itemsCompleteCount") status_item_downloaded_count = helpers.get_xml_attr(status, "itemsDownloadedCount") status_item_ready_count = helpers.get_xml_attr(status, "itemsReadyCount") status_item_successful_count = helpers.get_xml_attr(status, "itemsSuccessfulCount") status_total_size = helpers.get_xml_attr(status, "totalSize") status_item_download_percent_complete = helpers.get_percent( status_item_downloaded_count, status_item_count ) for settings in item.getElementsByTagName("MediaSettings"): settings_audio_boost = helpers.get_xml_attr(settings, "audioBoost") settings_music_bitrate = helpers.get_xml_attr(settings, "musicBitrate") settings_photo_quality = helpers.get_xml_attr(settings, "photoQuality") settings_photo_resolution = helpers.get_xml_attr(settings, "photoResolution") settings_video_quality = helpers.get_xml_attr(settings, "videoQuality") settings_video_resolution = helpers.get_xml_attr(settings, "videoResolution") if helpers.get_xml_attr(item.getElementsByTagName("Location")[0], "uri").endswith( "%2Fchildren" ): clean_uri = helpers.get_xml_attr(item.getElementsByTagName("Location")[0], "uri")[:-11] else:
def build_notify_text(session=None, timeline=None, state=None): # Get the server name server_name = plexpy.CONFIG.PMS_NAME # Get the server uptime plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() if server_times: updated_at = server_times[0]['updated_at'] server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at))) else: logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.") server_uptime = 'N/A' # Get metadata feed for item if session: rating_key = session['rating_key'] elif timeline: rating_key = timeline['rating_key'] pms_connect = pmsconnect.PmsConnect() metadata_list = pms_connect.get_metadata_details(rating_key=rating_key) stream_count = pms_connect.get_current_activity().get('stream_count', '') if metadata_list: metadata = metadata_list['metadata'] else: logger.error(u"PlexPy NotificationHandler :: Unable to retrieve metadata for rating_key %s" % str(rating_key)) return [] # Check for exclusion tags if metadata['media_type'] == 'movie': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL) elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL) elif metadata['media_type'] == 'artist' or metadata['media_type'] == 'track': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*', re.IGNORECASE | re.DOTALL) else: pattern = None if metadata['media_type'] == 'movie' \ or metadata['media_type'] == 'show' or metadata['media_type'] == 'episode' \ or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \ and pattern: # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT)) else: on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT # Create a title if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track': full_title = '%s - %s' % (metadata['grandparent_title'], metadata['title']) else: full_title = metadata['title'] duration = helpers.convert_milliseconds_to_minutes(metadata['duration']) # Default values user = '' platform = '' player = '' ip_address = 'N/A' stream_duration = 0 view_offset = 0 container = '' video_codec = '' video_bitrate = '' video_width = '' video_height = '' video_resolution = '' video_framerate = '' aspect_ratio = '' audio_codec = '' audio_channels = '' transcode_decision = '' video_decision = '' audio_decision = '' transcode_container = '' transcode_video_codec = '' transcode_video_width = '' transcode_video_height = '' transcode_audio_codec = '' transcode_audio_channels = '' # Session values if session: # Generate a combined transcode decision value video_decision = session['video_decision'].title() audio_decision = session['audio_decision'].title() if session['video_decision'] == 'transcode' or session['audio_decision'] == 'transcode': transcode_decision = 'Transcode' elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy': transcode_decision = 'Direct Stream' else: transcode_decision = 'Direct Play' if state != 'play': if session['paused_counter']: stream_duration = int((time.time() - helpers.cast_to_float(session['started']) - helpers.cast_to_float(session['paused_counter'])) / 60) else: stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60) view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset']) user = session['friendly_name'] platform = session['platform'] player = session['player'] ip_address = session['ip_address'] if session['ip_address'] else 'N/A' container = session['container'] video_codec = session['video_codec'] video_bitrate = session['bitrate'] video_width = session['width'] video_height = session['height'] video_resolution = session['video_resolution'] video_framerate = session['video_framerate'] aspect_ratio = session['aspect_ratio'] audio_codec = session['audio_codec'] audio_channels = session['audio_channels'] transcode_container = session['transcode_container'] transcode_video_codec = session['transcode_video_codec'] transcode_video_width = session['transcode_width'] transcode_video_height = session['transcode_height'] transcode_audio_codec = session['transcode_audio_codec'] transcode_audio_channels = session['transcode_audio_channels'] progress_percent = helpers.get_percent(view_offset, duration) # Fix metadata params for notify recently added grandparent if state == 'created' and plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT: show_name = metadata['title'] episode_name = '' artist_name = metadata['title'] album_name = '' track_name = '' else: show_name = metadata['grandparent_title'] episode_name = metadata['title'] artist_name = metadata['grandparent_title'] album_name = metadata['parent_title'] track_name = metadata['title'] available_params = {'server_name': server_name, 'server_uptime': server_uptime, 'streams': stream_count, 'action': state, 'datestamp': arrow.now().format(plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')), 'timestamp': arrow.now().format(plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')), 'user': user, 'platform': platform, 'player': player, 'ip_address': ip_address, 'media_type': metadata['media_type'], 'stream_duration': stream_duration, 'remaining_duration': duration - view_offset, 'progress': view_offset, 'progress_percent': progress_percent, 'container': container, 'video_codec': video_codec, 'video_bitrate': video_bitrate, 'video_width': video_width, 'video_height': video_height, 'video_resolution': video_resolution, 'video_framerate': video_framerate, 'aspect_ratio': aspect_ratio, 'audio_codec': audio_codec, 'audio_channels': audio_channels, 'transcode_decision': transcode_decision, 'video_decision': video_decision, 'audio_decision': audio_decision, 'transcode_container': transcode_container, 'transcode_video_codec': transcode_video_codec, 'transcode_video_width': transcode_video_width, 'transcode_video_height': transcode_video_height, 'transcode_audio_codec': transcode_audio_codec, 'transcode_audio_channels': transcode_audio_channels, 'title': full_title, 'library_name': metadata['library_name'], 'show_name': show_name, 'episode_name': episode_name, 'artist_name': artist_name, 'album_name': album_name, 'track_name': track_name, 'season_num': metadata['parent_media_index'].zfill(1), 'season_num00': metadata['parent_media_index'].zfill(2), 'episode_num': metadata['media_index'].zfill(1), 'episode_num00': metadata['media_index'].zfill(2), 'year': metadata['year'], 'studio': metadata['studio'], 'content_rating': metadata['content_rating'], 'directors': ', '.join(metadata['directors']), 'writers': ', '.join(metadata['writers']), 'actors': ', '.join(metadata['actors']), 'genres': ', '.join(metadata['genres']), 'summary': metadata['summary'], 'tagline': metadata['tagline'], 'rating': metadata['rating'], 'duration': duration } # Default subject text subject_text = 'PlexPy (%s)' % server_name # Default scripts args script_args = [] # Regex to match {param} but not "{param}" params_to_quote = re.compile(r'(?<!\")([\{][^}]+[\}])(?!\"\})') script_args_text = re.sub(params_to_quote, r'"\g<0>"', script_args_text) if script_args_text: try: script_args = [unicode(arg).format(**available_params) for arg in script_args_text.split()] except LookupError as e: logger.error(u"PlexPy Notifier :: Unable to parse field %s in script argument. Using fallback." % e) except Exception as e: logger.error(u"PlexPy Notifier :: Unable to parse custom script arguments %s. Using fallback." % e) if state == 'play': # Default body text body_text = '%s (%s) is watching %s' % (session['friendly_name'], session['player'], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format(**available_params) except LookupError, e: logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification subject. Using fallback." % e) except:
def build_notify_text(session=None, timeline=None, state=None): import re # Get the server name server_name = plexpy.CONFIG.PMS_NAME # Get the server uptime plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() if server_times: updated_at = server_times[0]['updated_at'] server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at))) else: logger.error(u"PlexPy Notifier :: Unable to retrieve server uptime.") server_uptime = 'N/A' # Get metadata feed for item if session: rating_key = session['rating_key'] elif timeline: rating_key = timeline['rating_key'] pms_connect = pmsconnect.PmsConnect() metadata_list = pms_connect.get_metadata_details(rating_key=rating_key) if metadata_list: metadata = metadata_list['metadata'] else: logger.error(u"PlexPy Notifier :: Unable to retrieve metadata for rating_key %s" % str(rating_key)) return [] # Check for exclusion tags if metadata['media_type'] == 'movie': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<tv>[^>]+.</tv>|<music>[^>]+.</music>', re.IGNORECASE) elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<movie>[^>]+.</movie>|<music>[^>]+.</music>', re.IGNORECASE) elif metadata['media_type'] == 'artist' or metadata['media_type'] == 'track': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('<tv>[^>]+.</tv>|<movie>[^>]+.</movie>', re.IGNORECASE) else: pattern = None if metadata['media_type'] == 'movie' \ or metadata['media_type'] == 'show' or metadata['media_type'] == 'episode' \ or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \ and pattern: # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) else: on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT # Create a title if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track': full_title = '%s - %s' % (metadata['grandparent_title'], metadata['title']) else: full_title = metadata['title'] duration = helpers.convert_milliseconds_to_minutes(metadata['duration']) # Default values video_decision = '' audio_decision = '' transcode_decision = '' stream_duration = 0 view_offset = 0 user = '' platform = '' player = '' ip_address = 'N/A' # Session values if session: # Generate a combined transcode decision value video_decision = session['video_decision'].title() audio_decision = session['audio_decision'].title() if session['video_decision'] == 'transcode' or session['audio_decision'] == 'transcode': transcode_decision = 'Transcode' elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy': transcode_decision = 'Direct Stream' else: transcode_decision = 'Direct Play' if state != 'play': if session['paused_counter']: stream_duration = int((time.time() - helpers.cast_to_float(session['started']) - helpers.cast_to_float(session['paused_counter'])) / 60) else: stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60) view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset']) user = session['friendly_name'] platform = session['platform'] player = session['player'] ip_address = session['ip_address'] if session['ip_address'] else 'N/A' progress_percent = helpers.get_percent(view_offset, duration) available_params = {'server_name': server_name, 'server_uptime': server_uptime, 'user': user, 'platform': platform, 'player': player, 'ip_address': ip_address, 'media_type': metadata['media_type'], 'title': full_title, 'show_name': metadata['grandparent_title'], 'episode_name': metadata['title'], 'artist_name': metadata['grandparent_title'], 'album_name': metadata['parent_title'], 'track_name': metadata['title'], 'season_num': metadata['parent_index'].zfill(1), 'season_num00': metadata['parent_index'].zfill(2), 'episode_num': metadata['index'].zfill(1), 'episode_num00': metadata['index'].zfill(2), 'video_decision': video_decision, 'audio_decision': audio_decision, 'transcode_decision': transcode_decision, 'year': metadata['year'], 'studio': metadata['studio'], 'content_rating': metadata['content_rating'], 'directors': ', '.join(metadata['directors']), 'writers': ', '.join(metadata['writers']), 'actors': ', '.join(metadata['actors']), 'genres': ', '.join(metadata['genres']), 'summary': metadata['summary'], 'tagline': metadata['tagline'], 'rating': metadata['rating'], 'duration': duration, 'stream_duration': stream_duration, 'remaining_duration': duration - view_offset, 'progress': view_offset, 'progress_percent': progress_percent } # Default subject text subject_text = 'PlexPy (%s)' % server_name if state == 'play': # Default body text body_text = '%s (%s) is watching %s' % (session['friendly_name'], session['player'], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format(**available_params) except LookupError, e: logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except:
def process(self): if self.is_valid_session(): ap = activity_processor.ActivityProcessor(server=self.server) db_session = ap.get_session_by_key(session_key=self.get_session_key()) this_state = self.timeline['state'] this_rating_key = str(self.timeline['ratingKey']) this_key = self.timeline['key'] this_transcode_key = self.timeline.get('transcodeSession', '') # Get the live tv session uuid this_live_uuid = this_key.split('/')[-1] if this_key.startswith('/livetv/sessions') else None # If we already have this session in the temp table, check for state changes if db_session: # Re-schedule the callback to reset the 5 minutes timer schedule_callback('session_key-{}-{}'.format(self.server.CONFIG.ID, self.get_session_key()), func=force_stop_stream, args=[self.get_session_key(), db_session['full_title'], db_session['user'], self.server.CONFIG.PMS_NAME, self.server], minutes=5) last_state = db_session['state'] last_rating_key = str(db_session['rating_key']) last_live_uuid = db_session['live_uuid'] last_transcode_key = db_session['transcode_key'].split('/')[-1] last_paused = db_session['last_paused'] buffer_count = db_session['buffer_count'] # Make sure the same item is being played if this_rating_key == last_rating_key or this_live_uuid == last_live_uuid: # Update the session state and viewOffset if this_state == 'playing': # Update the session in our temp session table # if the last set temporary stopped time exceeds 60 seconds if int(time.time()) - db_session['stopped'] > 60: self.update_db_session() # Start our state checks if this_state != last_state: if this_state == 'paused': self.on_pause() elif last_paused and this_state == 'playing': self.on_resume() elif this_state == 'stopped': self.on_stop() elif this_state == 'paused': # Update the session last_paused timestamp self.on_pause(still_paused=True) if this_state == 'buffering': self.on_buffer() if this_transcode_key != last_transcode_key and this_state != 'stopped': self.on_change() # If a client doesn't register stop events (I'm looking at you PHT!) check if the ratingKey has changed else: # Manually stop and start # Set force_stop so that we don't overwrite our last viewOffset self.on_stop(force_stop=True) self.on_start() # Monitor if the stream has reached the watch percentage for notifications # The only purpose of this is for notifications if not db_session['watched'] and this_state != 'buffering': progress_percent = helpers.get_percent(self.timeline['viewOffset'], db_session['duration']) watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT, 'episode': plexpy.CONFIG.TV_WATCHED_PERCENT, 'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT, 'clip': plexpy.CONFIG.TV_WATCHED_PERCENT } if progress_percent >= watched_percent.get(db_session['media_type'], 101): logger.debug(u"Tautulli ActivityHandler :: %s: Session %s watched." % (self.server.CONFIG.PMS_NAME, str(self.get_session_key()))) ap.set_watched(session_key=self.get_session_key()) watched_notifiers = notification_handler.get_notify_state_enabled( session=db_session, notify_action='on_watched', notified=False) for d in watched_notifiers: plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notifier_id': d['notifier_id'], 'notify_action': 'on_watched'}) else: # We don't have this session in our table yet, start a new one. if this_state != 'buffering': self.on_start()