def process(self): if self.is_valid_session(): from plexcs 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 >= plexcs.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 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"Plex:CS Monitor :: Checking for active streams.") global int_ping_count if session_list: 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 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']) + plexcs.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 plexcs.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'] >= plexcs.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'] == plexcs.CONFIG.BUFFER_THRESHOLD: logger.info(u"Plex:CS 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'] + \ plexcs.CONFIG.BUFFER_WAIT: logger.info(u"Plex:CS 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"Plex:CS 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']) > plexcs.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"Plex:CS 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']) > plexcs.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"Plex:CS Monitor :: Unable to read session list.") int_ping_count += 1 logger.warn(u"Plex:CS 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 build_notify_text(session=None, timeline=None, state=None): import re # Get the server name server_name = plexcs.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"Plex:CS 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"Plex:CS 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( '\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, '', plexcs.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_created_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) else: on_start_subject = plexcs.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexcs.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexcs.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexcs.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexcs.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexcs.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexcs.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexcs.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexcs.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexcs.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexcs.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexcs.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_created_subject = plexcs.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_body = plexcs.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 = 'Plex:CS (%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 as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_start_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'stop': # Default body text body_text = '%s (%s) has stopped %s' % (session['friendly_name'], session['player'], full_title) if on_stop_subject and on_stop_body: try: subject_text = unicode(on_stop_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_stop_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'pause': # Default body text body_text = '%s (%s) has paused %s' % (session['friendly_name'], session['player'], full_title) if on_pause_subject and on_pause_body: try: subject_text = unicode(on_pause_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_pause_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'resume': # Default body text body_text = '%s (%s) has resumed %s' % (session['friendly_name'], session['player'], full_title) if on_resume_subject and on_resume_body: try: subject_text = unicode(on_resume_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_resume_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'buffer': # Default body text body_text = '%s (%s) is buffering %s' % (session['friendly_name'], session['player'], full_title) if on_buffer_subject and on_buffer_body: try: subject_text = unicode(on_buffer_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_buffer_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'watched': # Default body text body_text = '%s (%s) has watched %s' % (session['friendly_name'], session['player'], full_title) if on_watched_subject and on_watched_body: try: subject_text = unicode(on_watched_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_watched_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'created': # Default body text body_text = '%s was recently added to Plex.' % full_title if on_created_subject and on_created_body: try: subject_text = unicode(on_created_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_created_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] else: return None
def notify(stream_data=None, notify_action=None): from plexcs 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 plexcs.CONFIG.MOVIE_NOTIFY_ENABLE) \ or (stream_data['media_type'] == 'episode' and plexcs.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 (plexcs.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexcs.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 (plexcs.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 (plexcs.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 plexcs.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"Plex:CS Notifier :: Notify called with unsupported media type.") pass else: logger.debug( u"Plex:CS Notifier :: Notify called but incomplete data received.")
def build_notify_text(session=None, timeline=None, state=None): import re # Get the server name server_name = plexcs.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"Plex:CS 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"Plex:CS 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("\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, "", plexcs.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_created_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) else: on_start_subject = plexcs.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexcs.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexcs.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexcs.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexcs.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexcs.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexcs.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexcs.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexcs.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexcs.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexcs.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexcs.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_created_subject = plexcs.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_body = plexcs.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 = "Plex:CS (%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 as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_start_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "stop": # Default body text body_text = "%s (%s) has stopped %s" % (session["friendly_name"], session["player"], full_title) if on_stop_subject and on_stop_body: try: subject_text = unicode(on_stop_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_stop_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "pause": # Default body text body_text = "%s (%s) has paused %s" % (session["friendly_name"], session["player"], full_title) if on_pause_subject and on_pause_body: try: subject_text = unicode(on_pause_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_pause_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "resume": # Default body text body_text = "%s (%s) has resumed %s" % (session["friendly_name"], session["player"], full_title) if on_resume_subject and on_resume_body: try: subject_text = unicode(on_resume_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_resume_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "buffer": # Default body text body_text = "%s (%s) is buffering %s" % (session["friendly_name"], session["player"], full_title) if on_buffer_subject and on_buffer_body: try: subject_text = unicode(on_buffer_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_buffer_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "watched": # Default body text body_text = "%s (%s) has watched %s" % (session["friendly_name"], session["player"], full_title) if on_watched_subject and on_watched_body: try: subject_text = unicode(on_watched_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_watched_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "created": # Default body text body_text = "%s was recently added to Plex." % full_title if on_created_subject and on_created_body: try: subject_text = unicode(on_created_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_created_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] else: return None
def notify(stream_data=None, notify_action=None): from plexcs 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 plexcs.CONFIG.MOVIE_NOTIFY_ENABLE) or ( stream_data["media_type"] == "episode" and plexcs.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 (plexcs.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexcs.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 (plexcs.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 (plexcs.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 plexcs.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"Plex:CS Notifier :: Notify called with unsupported media type.") pass else: logger.debug(u"Plex:CS 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("Error parsing XML for Plex sync lists: %s" % e) return [] except: logger.warn("Error parsing XML for Plex sync lists.") return [] xml_head = xml_parse.getElementsByTagName('SyncList') if not xml_head: logger.warn("Error parsing XML for Plex sync lists.") 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_user_details(user_id=device_user_id)['username'] device_friendly_name = user_data.get_user_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') 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: clean_uri = helpers.get_xml_attr(item.getElementsByTagName('Location')[0], 'uri') rating_key = clean_uri.rpartition('%2F')[-1] 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