diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 81cb12ca..1298e841 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -38,6 +38,7 @@ import activity_handler import activity_pinger import common import database +import datafactory import libraries import logger import mobile_app @@ -649,18 +650,6 @@ def dbcheck(): 'uuid TEXT UNIQUE, success INTEGER DEFAULT 0)' ) - # poster_urls table :: This table keeps record of the notification poster urls - c_db.execute( - 'CREATE TABLE IF NOT EXISTS poster_urls (id INTEGER PRIMARY KEY AUTOINCREMENT, ' - 'rating_key INTEGER, poster_title TEXT, poster_url TEXT, delete_hash TEXT)' - ) - - # art_urls table :: This table keeps record of the notification art urls - c_db.execute( - 'CREATE TABLE IF NOT EXISTS art_urls (id INTEGER PRIMARY KEY AUTOINCREMENT, ' - 'rating_key INTEGER, art_title TEXT, art_url TEXT, delete_hash TEXT, blur INTEGER DEFAULT 0)' - ) - # recently_added table :: This table keeps record of recently added items c_db.execute( 'CREATE TABLE IF NOT EXISTS recently_added (id INTEGER PRIMARY KEY AUTOINCREMENT, ' @@ -697,6 +686,12 @@ def dbcheck(): 'opacity INTEGER, background TEXT, blur INTEGER, fallback TEXT)' ) + # imgur_lookup table :: This table keeps record of the Imgur uploads + c_db.execute( + 'CREATE TABLE IF NOT EXISTS imgur_lookup (id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'img_hash TEXT, imgur_title TEXT, imgur_url TEXT, delete_hash TEXT)' + ) + # Upgrade sessions table from earlier versions try: c_db.execute('SELECT started FROM sessions') @@ -1629,15 +1624,6 @@ def dbcheck(): 'ALTER TABLE user_login ADD COLUMN success INTEGER DEFAULT 1' ) - # Upgrade poster_urls table from earlier versions - try: - c_db.execute('SELECT delete_hash FROM poster_urls') - except sqlite3.OperationalError: - logger.debug(u"Altering database. Updating database table poster_urls.") - c_db.execute( - 'ALTER TABLE poster_urls ADD COLUMN delete_hash TEXT' - ) - # Rename notifiers in the database logger.debug(u"Altering database. Renaming notifiers.") c_db.execute( @@ -1665,6 +1651,27 @@ def dbcheck(): c_db.close() + # Migrate poster_urls to imgur_lookup table + try: + db = database.MonitorDatabase() + result = db.select('SELECT SQL FROM sqlite_master WHERE type="table" AND name="poster_urls"') + if result: + result = db.select('SELECT * FROM poster_urls') + logger.debug(u"Altering database. Updating database table imgur_lookup.") + + data_factory = datafactory.DataFactory() + + for row in result: + img_hash = notification_handler.set_hash_image_info( + rating_key=row['rating_key'], width=600, height=1000, fallback='poster') + data_factory.set_imgur_info(img_hash=img_hash, imgur_title=row['poster_title'], + imgur_url=row['poster_url'], delete_hash=row['delete_hash']) + + db.action('DROP TABLE poster_urls') + except sqlite3.OperationalError: + pass + + def upgrade(): if CONFIG.UPDATE_NOTIFIERS_DB: notifiers.upgrade_config_to_db() diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 2608e99d..9727699b 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -1132,9 +1132,94 @@ class DataFactory(object): return ip_address - def get_poster_info(self, rating_key='', metadata=None, art=False, blur=None): + def get_imgur_info(self, img=None, rating_key=None, width=None, height=None, + opacity=None, background=None, blur=None, fallback=None, + order_by=''): monitor_db = database.MonitorDatabase() + imgur_info = [] + + where_params = [] + args = [] + + if img is not None: + where_params.append('img') + args.append(img) + if rating_key is not None: + where_params.append('rating_key') + args.append(rating_key) + if width is not None: + where_params.append('width') + args.append(width) + if height is not None: + where_params.append('height') + args.append(height) + if opacity is not None: + where_params.append('opacity') + args.append(opacity) + if background is not None: + where_params.append('background') + args.append(background) + if blur is not None: + where_params.append('blur') + args.append(blur) + if fallback is not None: + where_params.append('fallback') + args.append(fallback) + + where = '' + if where_params: + where = 'WHERE ' + ' AND '.join([w + ' = ?' for w in where_params]) + + if order_by: + order_by = 'ORDER BY ' + order_by + ' DESC' + + query = 'SELECT imgur_title, imgur_url FROM imgur_lookup ' \ + 'JOIN image_hash_lookup ON imgur_lookup.img_hash = image_hash_lookup.img_hash ' \ + '%s %s' % (where, order_by) + + try: + imgur_info = monitor_db.select(query, args=args) + except Exception as e: + logger.warn(u"Tautulli DataFactory :: Unable to execute database query for get_imgur_info: %s." % e) + + return imgur_info + + def set_imgur_info(self, img_hash=None, imgur_title=None, imgur_url=None, delete_hash=None): + monitor_db = database.MonitorDatabase() + + keys = {'img_hash': img_hash} + values = {'imgur_title': imgur_title, + 'imgur_url': imgur_url, + 'delete_hash': delete_hash} + + monitor_db.upsert('imgur_lookup', key_dict=keys, value_dict=values) + + def delete_imgur_info(self, rating_key=None): + monitor_db = database.MonitorDatabase() + + if rating_key: + query = 'SELECT imgur_title, delete_hash, fallback FROM imgur_lookup ' \ + 'JOIN image_hash_lookup ON imgur_lookup.img_hash = image_hash_lookup.img_hash ' \ + 'WHERE rating_key = ? ' + args = [rating_key] + results = monitor_db.select(query, args=args) + + for imgur_info in results: + if imgur_info['delete_hash']: + helpers.delete_from_imgur(delete_hash=imgur_info['delete_hash'], + img_title=imgur_info['imgur_title'], + fallback=imgur_info['fallback']) + + logger.info(u"Tautulli DataFactory :: Deleting Imgur info for rating_key %s from the database." + % rating_key) + result = monitor_db.action('DELETE FROM imgur_lookup WHERE img_hash ' + 'IN (SELECT img_hash FROM image_hash_lookup WHERE rating_key = ?)', + [rating_key]) + + return True if result else False + + def get_poster_info(self, rating_key='', metadata=None): poster_key = '' if str(rating_key).isdigit(): poster_key = rating_key @@ -1149,56 +1234,13 @@ class DataFactory(object): poster_info = {} if poster_key: - try: - if art: - query = 'SELECT art_title, art_url, blur FROM art_urls ' \ - 'WHERE rating_key = ? AND blur = ?' - args = [poster_key, int(blur is not None)] - else: - query = 'SELECT poster_title, poster_url FROM poster_urls ' \ - 'WHERE rating_key = ?' - args = [poster_key] - poster_info = monitor_db.select_single(query, args=args) - except Exception as e: - logger.warn(u"Tautulli DataFactory :: Unable to execute database query for get_poster_url: %s." % e) + imgur_info = self.get_imgur_info(rating_key=poster_key, order_by='height', fallback='poster') + if imgur_info: + poster_info = {'poster_title': imgur_info[0]['imgur_title'], + 'poster_url': imgur_info[0]['imgur_url']} return poster_info - def set_poster_url(self, rating_key='', poster_title='', poster_url='', delete_hash='', art=False, blur=None): - monitor_db = database.MonitorDatabase() - - if str(rating_key).isdigit(): - keys = {'rating_key': int(rating_key)} - - if art: - keys['blur'] = int(blur is not None) - table = 'art_urls' - values = {'art_title': poster_title, - 'art_url': poster_url, - 'delete_hash': delete_hash} - else: - table = 'poster_urls' - values = {'poster_title': poster_title, - 'poster_url': poster_url, - 'delete_hash': delete_hash} - - monitor_db.upsert(table_name=table, key_dict=keys, value_dict=values) - - def delete_poster_url(self, rating_key=''): - monitor_db = database.MonitorDatabase() - - if rating_key: - poster_info = monitor_db.select_single('SELECT poster_title, delete_hash ' - 'FROM poster_urls WHERE rating_key = ?', - [rating_key]) - if poster_info['delete_hash']: - helpers.delete_from_imgur(poster_info['delete_hash'], poster_info['poster_title']) - - logger.info(u"Tautulli DataFactory :: Deleting poster_url for '%s' (rating_key %s) from the database." - % (poster_info['poster_title'], rating_key)) - result = monitor_db.action('DELETE FROM poster_urls WHERE rating_key = ?', [rating_key]) - return True if result else False - def get_lookup_info(self, rating_key='', metadata=None): monitor_db = database.MonitorDatabase() diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 175588c9..917b139c 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -681,7 +681,8 @@ def anon_url(*url): """ return '' if None in url else '%s%s' % (plexpy.CONFIG.ANON_REDIRECT, ''.join(str(s) for s in url)) -def upload_to_imgur(imgPath, imgTitle=''): + +def upload_to_imgur(img_data, img_title='', rating_key='', fallback=''): """ Uploads an image to Imgur """ client_id = plexpy.CONFIG.IMGUR_CLIENT_ID img_url = delete_hash = '' @@ -690,41 +691,33 @@ def upload_to_imgur(imgPath, imgTitle=''): logger.error(u"Tautulli Helpers :: Cannot upload poster to Imgur. No Imgur client id specified in the settings.") return img_url, delete_hash - try: - with open(imgPath, 'rb') as imgFile: - img = imgFile.read() - except IOError as e: - logger.error(u"Tautulli Helpers :: Unable to read image file for Imgur: %s" % e) - return img_url, delete_hash - headers = {'Authorization': 'Client-ID %s' % client_id} - data = {'type': 'base64', - 'image': base64.b64encode(img)} - if imgTitle: - data['title'] = imgTitle.encode('utf-8') - data['name'] = imgTitle.encode('utf-8') + '.jpg' + data = {'image': base64.b64encode(img_data), + 'title': img_title.encode('utf-8'), + 'name': str(rating_key) + '.png', + 'type': 'png'} response, err_msg, req_msg = request.request_response2('https://api.imgur.com/3/image', 'POST', headers=headers, data=data) if response and not err_msg: - t = '\'' + imgTitle + '\' ' if imgTitle else '' - logger.debug(u"Tautulli Helpers :: Image {}uploaded to Imgur.".format(t)) + logger.debug(u"Tautulli Helpers :: Image '{}' ({}) uploaded to Imgur.".format(img_title, fallback)) imgur_response_data = response.json().get('data') img_url = imgur_response_data.get('link', '').replace('http://', 'https://') delete_hash = imgur_response_data.get('deletehash', '') else: if err_msg: - logger.error(u"Tautulli Helpers :: Unable to upload image to Imgur: {}".format(err_msg)) + logger.error(u"Tautulli Helpers :: Unable to upload image '{}' to Imgur: {}".format(img_title, err_msg)) else: - logger.error(u"Tautulli Helpers :: Unable to upload image to Imgur.") + logger.error(u"Tautulli Helpers :: Unable to upload image '{}' to Imgur.".format(img_title)) if req_msg: logger.debug(u"Tautulli Helpers :: Request response: {}".format(req_msg)) return img_url, delete_hash -def delete_from_imgur(delete_hash, imgTitle=''): + +def delete_from_imgur(delete_hash, img_title='', fallback=''): """ Deletes an image from Imgur """ client_id = plexpy.CONFIG.IMGUR_CLIENT_ID @@ -734,16 +727,16 @@ def delete_from_imgur(delete_hash, imgTitle=''): headers=headers) if response and not err_msg: - t = '\'' + imgTitle + '\' ' if imgTitle else '' - logger.debug(u"Tautulli Helpers :: Image {}deleted from Imgur.".format(t)) + logger.debug(u"Tautulli Helpers :: Image '{}' ({}) deleted from Imgur.".format(img_title, fallback)) return True else: if err_msg: - logger.error(u"Tautulli Helpers :: Unable to delete image from Imgur: {}".format(err_msg)) + logger.error(u"Tautulli Helpers :: Unable to delete image '{}' from Imgur: {}".format(img_title, err_msg)) else: - logger.error(u"Tautulli Helpers :: Unable to delete image from Imgur.") + logger.error(u"Tautulli Helpers :: Unable to delete image '{}' from Imgur.".format(img_title)) return False + def cache_image(url, image=None): """ Saves an image to the cache directory. diff --git a/plexpy/newsletters.py b/plexpy/newsletters.py index db17f956..e375ee93 100644 --- a/plexpy/newsletters.py +++ b/plexpy/newsletters.py @@ -650,7 +650,7 @@ class RecentlyAdded(Newsletter): return recently_added def retrieve_data(self): - from notification_handler import get_poster_info, set_hash_image_info + from notification_handler import get_imgur_info, set_hash_image_info if not self.config['incl_libraries']: logger.warn(u"Tautulli Newsletters :: Failed to retrieve %s newsletter data: no libraries selected." % self.NAME) @@ -670,17 +670,12 @@ class RecentlyAdded(Newsletter): if self.is_preview or plexpy.CONFIG.NEWSLETTER_SELF_HOSTED: for item in movies + shows + albums: - item['thumb_hash'] = set_hash_image_info(img=item['thumb'], - width=150, - height=225, - fallback='poster') - item['art_hash'] = set_hash_image_info(img=item['art'], - width=500, - height=280, - opacity=25, - background='282828', - blur=3, - fallback='art') + item['thumb_hash'] = set_hash_image_info( + img=item['thumb'], width=150, height=225, fallback='poster') + + item['art_hash'] = set_hash_image_info( + img=item['art'], width=500, height=280, + opacity=25, background='282828', blur=3, fallback='art') item['poster_url'] = '' item['art_url'] = '' @@ -688,22 +683,17 @@ class RecentlyAdded(Newsletter): else: # Upload posters and art to Imgur for item in movies + shows + albums: - poster_info = get_poster_info(poster_thumb=item['thumb'], - poster_key=item['rating_key'], - poster_title=item['title']) - if poster_info: - item['poster_url'] = poster_info['poster_url'] or common.ONLINE_POSTER_THUMB + imgur_info = get_imgur_info( + img=item['thumb'], rating_key=item['rating_key'], title=item['title'], + width=150, height=225, fallback='poster') - art_info = get_poster_info(poster_thumb=item['art'], - poster_key=item['rating_key'], - poster_title=item['title'], - art=True, - width='500', - height='280', - opacity='25', - background='282828', - blur='3') - item['art_url'] = art_info.get('art_url', '') + item['poster_url'] = imgur_info.get('imgur_url') or common.ONLINE_POSTER_THUMB + + imgur_info = get_imgur_info( + img=item['art'], rating_key=item['rating_key'], title=item['title'], + width=500, height=280, opacity=25, background='282828', blur=3, fallback='art') + + item['art_url'] = imgur_info.get('imgur_url') or common.ONLINE_ART item['thumb_hash'] = '' item['art_hash'] = '' diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index ff47148c..609719c7 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -633,7 +633,8 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m poster_thumb = '' if plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS: - poster_info = get_poster_info(poster_thumb=poster_thumb, poster_key=poster_key, poster_title=poster_title) + imgur_info = get_imgur_info(img=poster_thumb, rating_key=poster_key, title=poster_title, fallback='poster') + poster_info = {'poster_title': imgur_info['imgur_title'], 'poster_url': imgur_info['imgur_url']} notify_params.update(poster_info) if ((manual_trigger or plexpy.CONFIG.NOTIFY_GROUP_RECENTLY_ADDED_GRANDPARENT) @@ -1069,69 +1070,55 @@ def format_group_index(group_keys): return ','.join(num) or '0', ','.join(num00) or '00' -def get_poster_info(poster_thumb='', poster_key='', poster_title='', art=False, - width='', height='', opacity=None, background=None, blur=None): - default_poster_info = {'poster_title': '', 'poster_url': ''} - default_art_info = {'art_title': '', 'art_url': ''} +def get_imgur_info(img=None, rating_key=None, title='', width=600, height=1000, + opacity=100, background='000000', blur=0, fallback=None): + imgur_info = {'imgur_title': '', 'imgur_url': ''} + + image_info = {'img': img, + 'rating_key': rating_key, + 'width': width, + 'height': height, + 'opacity': opacity, + 'background': background, + 'blur': blur, + 'fallback': fallback} # Try to retrieve poster info from the database data_factory = datafactory.DataFactory() - poster_info = data_factory.get_poster_info(rating_key=poster_key, art=art, blur=blur) + database_imgur_info = data_factory.get_imgur_info(**image_info) - # If no previous poster info - if not poster_info and poster_thumb: - try: - thread_name = str(threading.current_thread().ident) - poster_file = os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-image-%s.png' % thread_name) + if database_imgur_info: + imgur_info = database_imgur_info[0] - # Retrieve the poster from Plex and cache to file - pms_connect = pmsconnect.PmsConnect() - result = pms_connect.get_image(img=poster_thumb, - width=width, - height=height, - opacity=opacity, - background=background, - blur=blur) - if result and result[0]: - with open(poster_file, 'wb') as f: - f.write(result[0]) - else: - raise Exception(u'PMS image request failed') + elif not database_imgur_info and img: + pms_connect = pmsconnect.PmsConnect() + result = pms_connect.get_image(**image_info) - # Upload poster_thumb to Imgur and get link - poster_url, delete_hash = helpers.upload_to_imgur(poster_file, poster_title) + if result and result[0]: + imgur_url, delete_hash = helpers.upload_to_imgur(img_data=result[0], + img_title=title, + rating_key=rating_key, + fallback=fallback) - if poster_url: - # Create poster info - if art: - poster_info = {'art_title': poster_title, 'art_url': poster_url} - else: - poster_info = {'poster_title': poster_title, 'poster_url': poster_url} + if imgur_url: + img_hash = set_hash_image_info(**image_info) + data_factory.set_imgur_info(img_hash=img_hash, + imgur_title=title, + imgur_url=imgur_url, + delete_hash=delete_hash) - # Save the poster url in the database - data_factory.set_poster_url(rating_key=poster_key, - poster_title=poster_title, - poster_url=poster_url, - delete_hash=delete_hash, - art=art, - blur=blur) + imgur_info = {'imgur_title': title, 'imgur_url': imgur_url} - # Delete the cached poster - os.remove(poster_file) - except Exception as e: - logger.error(u"Tautulli NotificationHandler :: Unable to retrieve poster for rating_key %s: %s." - % (poster_key, e)) - - if art: - return poster_info or default_art_info - else: - return poster_info or default_poster_info + return imgur_info def set_hash_image_info(img=None, rating_key=None, width=600, height=1000, opacity=100, background='000000', blur=0, fallback=None): if rating_key and not img: - img = '/library/metadata/{}/thumb'.format(rating_key) + if fallback == 'art': + img = '/library/metadata/{}/art'.format(rating_key) + else: + img = '/library/metadata/{}/thumb'.format(rating_key) img_split = img.split('/') img = '/'.join(img_split[:5]) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 203157cb..9ec1d300 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -2426,7 +2426,7 @@ class PmsConnect(object): return labels_list def get_image(self, img=None, width=600, height=1000, opacity=None, background=None, blur=None, - img_format='png', clip=False): + img_format='png', clip=False, **kwargs): """ Return image data as array. Array contains the image content type and image binary diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 7ecb1d2b..b9189f8f 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -4140,7 +4140,7 @@ class WebInterface(object): """ data_factory = datafactory.DataFactory() - result = data_factory.delete_poster_url(rating_key=rating_key) + result = data_factory.delete_imgur_info(rating_key=rating_key) if result: return {'result': 'success', 'message': 'Deleted Imgur poster.'}