def run(): from websocket import create_connection uri = 'ws://%s:%s/:/websockets/notifications' % ( plexcs.CONFIG.PMS_IP, plexcs.CONFIG.PMS_PORT ) # Set authentication token (if one is available) if plexcs.CONFIG.PMS_TOKEN: uri += '?X-Plex-Token=' + plexcs.CONFIG.PMS_TOKEN ws_connected = False reconnects = 0 # Try an open the websocket connection - if it fails after 15 retries fallback to polling while not ws_connected and reconnects <= 15: try: logger.info(u'Plex:CS WebSocket :: Opening websocket, connection attempt %s.' % str(reconnects + 1)) ws = create_connection(uri) reconnects = 0 ws_connected = True logger.info(u'Plex:CS WebSocket :: Ready') except IOError as e: logger.error(u'Plex:CS WebSocket :: %s.' % e) reconnects += 1 time.sleep(5) while ws_connected: try: process(*receive(ws)) # successfully received data, reset reconnects counter reconnects = 0 except websocket.WebSocketConnectionClosedException: if reconnects <= 15: reconnects += 1 # Sleep 5 between connection attempts if reconnects > 1: time.sleep(5) logger.warn(u'Plex:CS WebSocket :: Connection has closed, reconnecting...') try: ws = create_connection(uri) except IOError as e: logger.info(u'Plex:CS WebSocket :: %s.' % e) else: ws_connected = False break if not ws_connected: logger.error(u'Plex:CS WebSocket :: Connection unavailable, falling back to polling.') plexcs.POLLING_FAILOVER = True plexcs.initialize_scheduler() logger.debug(u'Plex:CS WebSocket :: Leaving thread.')
def import_from_plexwatch(database=None, table_name=None, import_ignore_interval=0): try: connection = sqlite3.connect(database, timeout=20) connection.row_factory = sqlite3.Row except sqlite3.OperationalError: logger.error('Plex:CS Importer :: Invalid filename.') return None except ValueError: logger.error('Plex:CS Importer :: Invalid filename.') return None try: connection.execute('SELECT ratingKey from %s' % table_name) except sqlite3.OperationalError: logger.error('Plex:CS Importer :: Database specified does not contain the required fields.') return None logger.debug(u"Plex:CS Importer :: PlexWatch data import in progress...") logger.debug(u"Plex:CS Importer :: Disabling monitoring while import in progress.") plexcs.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=0) plexcs.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', hours=0, minutes=0, seconds=0) ap = activity_processor.ActivityProcessor() user_data = users.Users() # Get the latest friends list so we can pull user id's try: plextv.refresh_users() except: logger.debug(u"Plex:CS Importer :: Unable to refresh the users list. Aborting import.") return None query = 'SELECT time AS started, ' \ 'stopped, ' \ 'cast(ratingKey as text) AS rating_key, ' \ 'null AS user_id, ' \ 'user, ' \ 'ip_address, ' \ 'paused_counter, ' \ 'platform AS player, ' \ 'null AS platform, ' \ 'null as machine_id, ' \ 'parentRatingKey as parent_rating_key, ' \ 'grandparentRatingKey as grandparent_rating_key, ' \ 'null AS media_type, ' \ 'null AS view_offset, ' \ 'xml, ' \ 'rating as content_rating,' \ 'summary,' \ 'title AS full_title,' \ '(case when orig_title_ep = "" then orig_title else ' \ 'orig_title_ep end) as title,' \ '(case when orig_title_ep != "" then orig_title else ' \ 'null end) as grandparent_title ' \ 'FROM ' + table_name + ' ORDER BY id' result = connection.execute(query) for row in result: # Extract the xml from the Plexwatch db xml field. extracted_xml = extract_plexwatch_xml(row['xml']) # If we get back None from our xml extractor skip over the record and log error. if not extracted_xml: logger.error(u"Plex:CS Importer :: Skipping record with ratingKey %s due to malformed xml." % str(row['rating_key'])) continue # Skip line if we don't have a ratingKey to work with if not row['rating_key']: logger.error(u"Plex:CS Importer :: Skipping record due to null ratingRey.") continue # If the user_id no longer exists in the friends list, pull it from the xml. if user_data.get_user_id(user=row['user']): user_id = user_data.get_user_id(user=row['user']) else: user_id = extracted_xml['user_id'] session_history = {'started': row['started'], 'stopped': row['stopped'], 'rating_key': row['rating_key'], 'title': row['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': row['grandparent_title'], 'user_id': user_id, 'user': row['user'], 'ip_address': row['ip_address'], 'paused_counter': row['paused_counter'], 'player': row['player'], 'platform': extracted_xml['platform'], 'machine_id': extracted_xml['machine_id'], 'parent_rating_key': row['parent_rating_key'], 'grandparent_rating_key': row['grandparent_rating_key'], 'media_type': extracted_xml['media_type'], 'view_offset': extracted_xml['view_offset'], 'video_decision': extracted_xml['video_decision'], 'audio_decision': extracted_xml['audio_decision'], 'duration': extracted_xml['duration'], 'width': extracted_xml['width'], 'height': extracted_xml['height'], 'container': extracted_xml['container'], 'video_codec': extracted_xml['video_codec'], 'audio_codec': extracted_xml['audio_codec'], 'bitrate': extracted_xml['bitrate'], 'video_resolution': extracted_xml['video_resolution'], 'video_framerate': extracted_xml['video_framerate'], 'aspect_ratio': extracted_xml['aspect_ratio'], 'audio_channels': extracted_xml['audio_channels'], 'transcode_protocol': extracted_xml['transcode_protocol'], 'transcode_container': extracted_xml['transcode_container'], 'transcode_video_codec': extracted_xml['transcode_video_codec'], 'transcode_audio_codec': extracted_xml['transcode_audio_codec'], 'transcode_audio_channels': extracted_xml['transcode_audio_channels'], 'transcode_width': extracted_xml['transcode_width'], 'transcode_height': extracted_xml['transcode_height'] } session_history_metadata = {'rating_key': helpers.latinToAscii(row['rating_key']), 'parent_rating_key': row['parent_rating_key'], 'grandparent_rating_key': row['grandparent_rating_key'], 'title': row['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': row['grandparent_title'], 'index': extracted_xml['media_index'], 'parent_index': extracted_xml['parent_media_index'], 'thumb': extracted_xml['thumb'], 'parent_thumb': extracted_xml['parent_thumb'], 'grandparent_thumb': extracted_xml['grandparent_thumb'], 'art': extracted_xml['art'], 'media_type': extracted_xml['media_type'], 'year': extracted_xml['year'], 'originally_available_at': extracted_xml['originally_available_at'], 'added_at': extracted_xml['added_at'], 'updated_at': extracted_xml['updated_at'], 'last_viewed_at': extracted_xml['last_viewed_at'], 'content_rating': row['content_rating'], 'summary': row['summary'], 'tagline': extracted_xml['tagline'], 'rating': extracted_xml['rating'], 'duration': extracted_xml['duration'], 'guid': extracted_xml['guid'], 'directors': extracted_xml['directors'], 'writers': extracted_xml['writers'], 'actors': extracted_xml['actors'], 'genres': extracted_xml['genres'], 'studio': extracted_xml['studio'], 'full_title': row['full_title'] } # On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values # Just make sure that the ratingKey is indeed an integer if session_history_metadata['rating_key'].isdigit(): ap.write_session_history(session=session_history, import_metadata=session_history_metadata, is_import=True, import_ignore_interval=import_ignore_interval) else: logger.debug(u"Plex:CS Importer :: Item has bad rating_key: %s" % session_history_metadata['rating_key']) logger.debug(u"Plex:CS Importer :: PlexWatch data import complete.") import_users() logger.debug(u"Plex:CS Importer :: Re-enabling monitoring.") plexcs.initialize_scheduler()
def main(): """ Plex:CS application entry point. Parses arguments, setups encoding and initializes the application. """ # Fixed paths to Plex:CS if hasattr(sys, 'frozen'): plexcs.FULL_PATH = os.path.abspath(sys.executable) else: plexcs.FULL_PATH = os.path.abspath(__file__) plexcs.PROG_DIR = os.path.dirname(plexcs.FULL_PATH) plexcs.ARGS = sys.argv[1:] # From sickbeard plexcs.SYS_PLATFORM = sys.platform plexcs.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, "") plexcs.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # for OSes that are poorly configured I'll just force UTF-8 if not plexcs.SYS_ENCODING or plexcs.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): plexcs.SYS_ENCODING = 'UTF-8' # Set up and gather command line arguments parser = argparse.ArgumentParser( description= 'A Python based monitoring and tracking tool for Plex Media Server.') parser.add_argument('-v', '--verbose', action='store_true', help='Increase console logging verbosity') parser.add_argument('-q', '--quiet', action='store_true', help='Turn off console logging') parser.add_argument('-d', '--daemon', action='store_true', help='Run as a daemon') parser.add_argument('-p', '--port', type=int, help='Force Plex:CS to run on a specified port') parser.add_argument( '--datadir', help='Specify a directory where to store your data files') parser.add_argument('--config', help='Specify a config file to use') parser.add_argument('--nolaunch', action='store_true', help='Prevent browser from launching on startup') parser.add_argument( '--pidfile', help='Create a pid file (only relevant when running as a daemon)') args = parser.parse_args() if args.verbose: plexcs.VERBOSE = True if args.quiet: plexcs.QUIET = True # Do an intial setup of the logger. logger.initLogger(console=not plexcs.QUIET, log_dir=False, verbose=plexcs.VERBOSE) if args.daemon: if sys.platform == 'win32': sys.stderr.write( "Daemonizing not supported under Windows, starting normally\n") else: plexcs.DAEMON = True plexcs.QUIET = True if args.pidfile: plexcs.PIDFILE = str(args.pidfile) # If the pidfile already exists, plexcs may still be running, so # exit if os.path.exists(plexcs.PIDFILE): raise SystemExit("PID file '%s' already exists. Exiting." % plexcs.PIDFILE) # The pidfile is only useful in daemon mode, make sure we can write the # file properly if plexcs.DAEMON: plexcs.CREATEPID = True try: with open(plexcs.PIDFILE, 'w') as fp: fp.write("pid\n") except IOError as e: raise SystemExit("Unable to write PID file: %s", e) else: logger.warn("Not running in daemon mode. PID file creation " \ "disabled.") # Determine which data directory and config file to use if args.datadir: plexcs.DATA_DIR = args.datadir else: plexcs.DATA_DIR = plexcs.PROG_DIR if args.config: config_file = args.config else: config_file = os.path.join(plexcs.DATA_DIR, 'config.ini') # Try to create the DATA_DIR if it doesn't exist if not os.path.exists(plexcs.DATA_DIR): try: os.makedirs(plexcs.DATA_DIR) except OSError: raise SystemExit('Could not create data directory: ' + plexcs.DATA_DIR + '. Exiting....') # Make sure the DATA_DIR is writeable if not os.access(plexcs.DATA_DIR, os.W_OK): raise SystemExit('Cannot write to the data directory: ' + plexcs.DATA_DIR + '. Exiting...') # Put the database in the DATA_DIR plexcs.DB_FILE = os.path.join(plexcs.DATA_DIR, 'plexcs.db') # Read config and start logging plexcs.initialize(config_file) if plexcs.DAEMON: plexcs.daemonize() # Force the http port if neccessary if args.port: http_port = args.port logger.info('Using forced web server port: %i', http_port) else: http_port = int(plexcs.CONFIG.HTTP_PORT) # Check if pyOpenSSL is installed. It is required for certificate generation # and for CherryPy. if plexcs.CONFIG.ENABLE_HTTPS: try: import OpenSSL except ImportError: logger.warn("The pyOpenSSL module is missing. Install this " \ "module to enable HTTPS. HTTPS will be disabled.") plexcs.CONFIG.ENABLE_HTTPS = False # Try to start the server. Will exit here is address is already in use. web_config = { 'http_port': http_port, 'http_host': plexcs.CONFIG.HTTP_HOST, 'http_root': plexcs.CONFIG.HTTP_ROOT, 'http_proxy': plexcs.CONFIG.HTTP_PROXY, 'enable_https': plexcs.CONFIG.ENABLE_HTTPS, 'https_cert': plexcs.CONFIG.HTTPS_CERT, 'https_key': plexcs.CONFIG.HTTPS_KEY, 'http_username': plexcs.CONFIG.HTTP_USERNAME, 'http_password': plexcs.CONFIG.HTTP_PASSWORD, } webstart.initialize(web_config) # Start the background threads plexcs.start() # Open connection for websocket if plexcs.CONFIG.MONITORING_USE_WEBSOCKET: try: web_socket.start_thread() except: logger.warn(u"Websocket :: Unable to open connection.") # Fallback to polling plexcs.POLLING_FAILOVER = True plexcs.initialize_scheduler() # Open webbrowser if plexcs.CONFIG.LAUNCH_BROWSER and not args.nolaunch: plexcs.launch_browser(plexcs.CONFIG.HTTP_HOST, http_port, plexcs.CONFIG.HTTP_ROOT) # Wait endlessy for a signal to happen while True: if not plexcs.SIGNAL: try: time.sleep(1) except KeyboardInterrupt: plexcs.SIGNAL = 'shutdown' else: logger.info('Received signal: %s', plexcs.SIGNAL) if plexcs.SIGNAL == 'shutdown': plexcs.shutdown() elif plexcs.SIGNAL == 'restart': plexcs.shutdown(restart=True) else: plexcs.shutdown(restart=True, update=True) plexcs.SIGNAL = None
def main(): """ Plex:CS application entry point. Parses arguments, setups encoding and initializes the application. """ # Fixed paths to Plex:CS if hasattr(sys, 'frozen'): plexcs.FULL_PATH = os.path.abspath(sys.executable) else: plexcs.FULL_PATH = os.path.abspath(__file__) plexcs.PROG_DIR = os.path.dirname(plexcs.FULL_PATH) plexcs.ARGS = sys.argv[1:] # From sickbeard plexcs.SYS_PLATFORM = sys.platform plexcs.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, "") plexcs.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # for OSes that are poorly configured I'll just force UTF-8 if not plexcs.SYS_ENCODING or plexcs.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): plexcs.SYS_ENCODING = 'UTF-8' # Set up and gather command line arguments parser = argparse.ArgumentParser( description='A Python based monitoring and tracking tool for Plex Media Server.') parser.add_argument( '-v', '--verbose', action='store_true', help='Increase console logging verbosity') parser.add_argument( '-q', '--quiet', action='store_true', help='Turn off console logging') parser.add_argument( '-d', '--daemon', action='store_true', help='Run as a daemon') parser.add_argument( '-p', '--port', type=int, help='Force Plex:CS to run on a specified port') parser.add_argument( '--datadir', help='Specify a directory where to store your data files') parser.add_argument('--config', help='Specify a config file to use') parser.add_argument('--nolaunch', action='store_true', help='Prevent browser from launching on startup') parser.add_argument( '--pidfile', help='Create a pid file (only relevant when running as a daemon)') args = parser.parse_args() if args.verbose: plexcs.VERBOSE = True if args.quiet: plexcs.QUIET = True # Do an intial setup of the logger. logger.initLogger(console=not plexcs.QUIET, log_dir=False, verbose=plexcs.VERBOSE) if args.daemon: if sys.platform == 'win32': sys.stderr.write( "Daemonizing not supported under Windows, starting normally\n") else: plexcs.DAEMON = True plexcs.QUIET = True if args.pidfile: plexcs.PIDFILE = str(args.pidfile) # If the pidfile already exists, plexcs may still be running, so # exit if os.path.exists(plexcs.PIDFILE): raise SystemExit("PID file '%s' already exists. Exiting." % plexcs.PIDFILE) # The pidfile is only useful in daemon mode, make sure we can write the # file properly if plexcs.DAEMON: plexcs.CREATEPID = True try: with open(plexcs.PIDFILE, 'w') as fp: fp.write("pid\n") except IOError as e: raise SystemExit("Unable to write PID file: %s", e) else: logger.warn("Not running in daemon mode. PID file creation " \ "disabled.") # Determine which data directory and config file to use if args.datadir: plexcs.DATA_DIR = args.datadir else: plexcs.DATA_DIR = plexcs.PROG_DIR if args.config: config_file = args.config else: config_file = os.path.join(plexcs.DATA_DIR, 'config.ini') # Try to create the DATA_DIR if it doesn't exist if not os.path.exists(plexcs.DATA_DIR): try: os.makedirs(plexcs.DATA_DIR) except OSError: raise SystemExit( 'Could not create data directory: ' + plexcs.DATA_DIR + '. Exiting....') # Make sure the DATA_DIR is writeable if not os.access(plexcs.DATA_DIR, os.W_OK): raise SystemExit( 'Cannot write to the data directory: ' + plexcs.DATA_DIR + '. Exiting...') # Put the database in the DATA_DIR plexcs.DB_FILE = os.path.join(plexcs.DATA_DIR, 'plexcs.db') # Read config and start logging plexcs.initialize(config_file) if plexcs.DAEMON: plexcs.daemonize() # Force the http port if neccessary if args.port: http_port = args.port logger.info('Using forced web server port: %i', http_port) else: http_port = int(plexcs.CONFIG.HTTP_PORT) # Check if pyOpenSSL is installed. It is required for certificate generation # and for CherryPy. if plexcs.CONFIG.ENABLE_HTTPS: try: import OpenSSL except ImportError: logger.warn("The pyOpenSSL module is missing. Install this " \ "module to enable HTTPS. HTTPS will be disabled.") plexcs.CONFIG.ENABLE_HTTPS = False # Try to start the server. Will exit here is address is already in use. web_config = { 'http_port': http_port, 'http_host': plexcs.CONFIG.HTTP_HOST, 'http_root': plexcs.CONFIG.HTTP_ROOT, 'http_proxy': plexcs.CONFIG.HTTP_PROXY, 'enable_https': plexcs.CONFIG.ENABLE_HTTPS, 'https_cert': plexcs.CONFIG.HTTPS_CERT, 'https_key': plexcs.CONFIG.HTTPS_KEY, 'http_username': plexcs.CONFIG.HTTP_USERNAME, 'http_password': plexcs.CONFIG.HTTP_PASSWORD, } webstart.initialize(web_config) # Start the background threads plexcs.start() # Open connection for websocket if plexcs.CONFIG.MONITORING_USE_WEBSOCKET: try: web_socket.start_thread() except: logger.warn(u"Websocket :: Unable to open connection.") # Fallback to polling plexcs.POLLING_FAILOVER = True plexcs.initialize_scheduler() # Open webbrowser if plexcs.CONFIG.LAUNCH_BROWSER and not args.nolaunch: plexcs.launch_browser(plexcs.CONFIG.HTTP_HOST, http_port, plexcs.CONFIG.HTTP_ROOT) # Wait endlessy for a signal to happen while True: if not plexcs.SIGNAL: try: time.sleep(1) except KeyboardInterrupt: plexcs.SIGNAL = 'shutdown' else: logger.info('Received signal: %s', plexcs.SIGNAL) if plexcs.SIGNAL == 'shutdown': plexcs.shutdown() elif plexcs.SIGNAL == 'restart': plexcs.shutdown(restart=True) else: plexcs.shutdown(restart=True, update=True) plexcs.SIGNAL = None
def import_from_plexwatch(database=None, table_name=None, import_ignore_interval=0): try: connection = sqlite3.connect(database, timeout=20) connection.row_factory = sqlite3.Row except sqlite3.OperationalError: logger.error('Plex:CS Importer :: Invalid filename.') return None except ValueError: logger.error('Plex:CS Importer :: Invalid filename.') return None try: connection.execute('SELECT ratingKey from %s' % table_name) except sqlite3.OperationalError: logger.error( 'Plex:CS Importer :: Database specified does not contain the required fields.' ) return None logger.debug(u"Plex:CS Importer :: PlexWatch data import in progress...") logger.debug( u"Plex:CS Importer :: Disabling monitoring while import in progress.") plexcs.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=0) plexcs.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', hours=0, minutes=0, seconds=0) ap = activity_processor.ActivityProcessor() user_data = users.Users() # Get the latest friends list so we can pull user id's try: plextv.refresh_users() except: logger.debug( u"Plex:CS Importer :: Unable to refresh the users list. Aborting import." ) return None query = 'SELECT time AS started, ' \ 'stopped, ' \ 'cast(ratingKey as text) AS rating_key, ' \ 'null AS user_id, ' \ 'user, ' \ 'ip_address, ' \ 'paused_counter, ' \ 'platform AS player, ' \ 'null AS platform, ' \ 'null as machine_id, ' \ 'parentRatingKey as parent_rating_key, ' \ 'grandparentRatingKey as grandparent_rating_key, ' \ 'null AS media_type, ' \ 'null AS view_offset, ' \ 'xml, ' \ 'rating as content_rating,' \ 'summary,' \ 'title AS full_title,' \ '(case when orig_title_ep = "" then orig_title else ' \ 'orig_title_ep end) as title,' \ '(case when orig_title_ep != "" then orig_title else ' \ 'null end) as grandparent_title ' \ 'FROM ' + table_name + ' ORDER BY id' result = connection.execute(query) for row in result: # Extract the xml from the Plexwatch db xml field. extracted_xml = extract_plexwatch_xml(row['xml']) # If we get back None from our xml extractor skip over the record and log error. if not extracted_xml: logger.error( u"Plex:CS Importer :: Skipping record with ratingKey %s due to malformed xml." % str(row['rating_key'])) continue # Skip line if we don't have a ratingKey to work with if not row['rating_key']: logger.error( u"Plex:CS Importer :: Skipping record due to null ratingRey.") continue # If the user_id no longer exists in the friends list, pull it from the xml. if user_data.get_user_id(user=row['user']): user_id = user_data.get_user_id(user=row['user']) else: user_id = extracted_xml['user_id'] session_history = { 'started': row['started'], 'stopped': row['stopped'], 'rating_key': row['rating_key'], 'title': row['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': row['grandparent_title'], 'user_id': user_id, 'user': row['user'], 'ip_address': row['ip_address'], 'paused_counter': row['paused_counter'], 'player': row['player'], 'platform': extracted_xml['platform'], 'machine_id': extracted_xml['machine_id'], 'parent_rating_key': row['parent_rating_key'], 'grandparent_rating_key': row['grandparent_rating_key'], 'media_type': extracted_xml['media_type'], 'view_offset': extracted_xml['view_offset'], 'video_decision': extracted_xml['video_decision'], 'audio_decision': extracted_xml['audio_decision'], 'duration': extracted_xml['duration'], 'width': extracted_xml['width'], 'height': extracted_xml['height'], 'container': extracted_xml['container'], 'video_codec': extracted_xml['video_codec'], 'audio_codec': extracted_xml['audio_codec'], 'bitrate': extracted_xml['bitrate'], 'video_resolution': extracted_xml['video_resolution'], 'video_framerate': extracted_xml['video_framerate'], 'aspect_ratio': extracted_xml['aspect_ratio'], 'audio_channels': extracted_xml['audio_channels'], 'transcode_protocol': extracted_xml['transcode_protocol'], 'transcode_container': extracted_xml['transcode_container'], 'transcode_video_codec': extracted_xml['transcode_video_codec'], 'transcode_audio_codec': extracted_xml['transcode_audio_codec'], 'transcode_audio_channels': extracted_xml['transcode_audio_channels'], 'transcode_width': extracted_xml['transcode_width'], 'transcode_height': extracted_xml['transcode_height'] } session_history_metadata = { 'rating_key': helpers.latinToAscii(row['rating_key']), 'parent_rating_key': row['parent_rating_key'], 'grandparent_rating_key': row['grandparent_rating_key'], 'title': row['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': row['grandparent_title'], 'index': extracted_xml['media_index'], 'parent_index': extracted_xml['parent_media_index'], 'thumb': extracted_xml['thumb'], 'parent_thumb': extracted_xml['parent_thumb'], 'grandparent_thumb': extracted_xml['grandparent_thumb'], 'art': extracted_xml['art'], 'media_type': extracted_xml['media_type'], 'year': extracted_xml['year'], 'originally_available_at': extracted_xml['originally_available_at'], 'added_at': extracted_xml['added_at'], 'updated_at': extracted_xml['updated_at'], 'last_viewed_at': extracted_xml['last_viewed_at'], 'content_rating': row['content_rating'], 'summary': row['summary'], 'tagline': extracted_xml['tagline'], 'rating': extracted_xml['rating'], 'duration': extracted_xml['duration'], 'guid': extracted_xml['guid'], 'directors': extracted_xml['directors'], 'writers': extracted_xml['writers'], 'actors': extracted_xml['actors'], 'genres': extracted_xml['genres'], 'studio': extracted_xml['studio'], 'full_title': row['full_title'] } # On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values # Just make sure that the ratingKey is indeed an integer if session_history_metadata['rating_key'].isdigit(): ap.write_session_history( session=session_history, import_metadata=session_history_metadata, is_import=True, import_ignore_interval=import_ignore_interval) else: logger.debug(u"Plex:CS Importer :: Item has bad rating_key: %s" % session_history_metadata['rating_key']) logger.debug(u"Plex:CS Importer :: PlexWatch data import complete.") import_users() logger.debug(u"Plex:CS Importer :: Re-enabling monitoring.") plexcs.initialize_scheduler()