+
+
-
+
-
+
-
-
- % if recently_added.get('movie'):
-
-
-
+ |
-
-
-
-
- % for movie in recently_added['movie']:
-
-
-
-
-
-
-
-
- % if movie['tagline']:
-
- ${movie['tagline']}
-
- % endif
-
- ${movie['summary'][:410] + (movie['summary'][410:] and '...')}
-
-
-
-
+
+ % if recently_added.get('movie'):
+
+
+
+
+ % for movie in recently_added['movie']:
+ % if loop.index == len(recently_added['movie'])-1 and loop.index % 2 == 0:
+
+ % else:
+
+ % endif
+ % if PILLOW:
+ % if preview:
+
+
+ % else:
+
+ % endif
+ % else:
+
+ % endif
+
+
+
+
+ % if movie['tagline']:
+
+ ${movie['tagline']}
+
+ % endif
+
+ ${movie['summary'][:450] + (movie['summary'][450:] and '...')}
-
- % endfor
- |
- |
-
- |
-
- % endif
- % if recently_added.get('show'):
-
-
-
- |
-
-
-
-
-
-
- % for show in recently_added['show']:
-
-
-
-
-
- <%
- if show['season_count'] == 1 and show['season'][0]['episode_count'] == 1:
- link_rating_key = show['season'][0]['episode'][0]['rating_key']
- link_title = show['title'] + " - " + show['season'][0]['episode'][0]['title']
- else:
- link_rating_key = show['rating_key']
- link_title = show['title']
- %>
-
-
-
+
- % endfor
- |
-
-
- |
-
- % endif
- % if recently_added.get('artist'):
-
-
-
- |
-
-
-
-
-
-
- <% album_count = 0 %>
- % for artist in recently_added['artist']:
- % for album in artist['album']:
- <% album_count += 1 %>
-
-
-
-
-
-
-
-
-
- ${artist['title']} · ${album['track_count']} track${'s' if album['track_count'] > 1 else ''}
-
- % if artist['title'].lower() != 'various artists':
-
- ${album['summary'][:150] + (album['summary'][150:] and '...')}
-
- % endif
-
-
-
+
+
+ % endfor
+
+ % endif
+ % if recently_added.get('show'):
+
+
+
+
+ % for show in recently_added['show']:
+ <%
+ if show['season_count'] == 1 and show['season'][0]['episode_count'] == 1:
+ link_rating_key = show['season'][0]['episode'][0]['rating_key']
+ link_title = show['title'] + " - " + show['season'][0]['episode'][0]['title']
+ else:
+ link_rating_key = show['rating_key']
+ link_title = show['title']
+ %>
+ % if loop.index == len(recently_added['show'])-1 and loop.index % 2 == 0:
+
+ % else:
+
+ % endif
+ % if PILLOW:
+ % if preview:
+
+
+ % else:
+
+ % endif
+ % else:
+
+ % endif
+
+
+
+
+
+ % if show['season_count'] > 1:
+ ${show['season_count']} seasons /
+ % endif
+ <% total_show_episodes = sum(s['episode_count'] for s in show['season']) %>
+ ${total_show_episodes} episode${'s' if total_show_episodes > 1 else ''}
+
+
+ % for i, season in enumerate(show['season'][:8]):
+ Season ${season['media_index']} ·
+ % if season['episode_count'] == 1:
+ Episode ${season['episode'][0]['media_index']} - ${season['episode'][0]['title']}
+ % else:
+ Episodes ${season['episode_range']}
+ % endif
+ % if i < min(show['season_count'], 7):
+
+ % elif i == 7 and show['season_count'] > 8:
+ ...plus ${show['season_count'] - 8} more seasons!
+ % endif
+ % endfor
+
+
+ % if show['season_count'] == 1 and show['season'][0]['episode_count'] == 1:
+ ${show['season'][0]['episode'][0]['summary'][:350] + (show['season'][0]['episode'][0]['summary'][350:] and '...')}
+ % else:
+ <% length = max(0, 350 - 50 * (show['season_count'] - 1)) %>
+ % if length:
+ ${show['summary'][:length] + (show['summary'][length:] and '...')}
+ % endif
+ % endif
+
- % endfor
- % endfor
- |
-
-
+
+
+ % endfor
+
+ % endif
+ % if recently_added.get('artist'):
+
+
+
+
+ <% album_count = 0 %>
+ % for artist in recently_added['artist']:
+ % for album in artist['album']:
+ <% album_count += 1 %>
+ % if album_count == total_albums and album_count % 2 == 1:
+
+ % else:
+
+ % endif
+ % if PILLOW:
+ % if preview:
+
+
+ % else:
+
+ % endif
+ % else:
+
+ % endif
+
+
+
+
+
+ ${artist['title']} · ${album['track_count']} track${'s' if album['track_count'] > 1 else ''}
+
+ % if artist['title'].lower() != 'various artists':
+
+ ${album['summary'][:200] + (album['summary'][200:] and '...')}
+
+ % endif
+
+
+
+
+
+ % endfor
+ % endfor
+
+ % endif
+
+
+
+
+
|
- % endif
-
-
-
-
diff --git a/data/interfaces/newsletters/recently_added_master.html b/data/interfaces/newsletters/recently_added_master.html
new file mode 100644
index 00000000..52e89232
--- /dev/null
+++ b/data/interfaces/newsletters/recently_added_master.html
@@ -0,0 +1,883 @@
+<% from notification_handler import PILLOW %>
+
+
+
+
+
+ Tautulli ${title} Newsletter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ % if recently_added.get('movie'):
+
+
+
+
+ % for movie in recently_added['movie']:
+ % if loop.index == len(recently_added['movie'])-1 and loop.index % 2 == 0:
+
+ % else:
+
+ % endif
+ % if PILLOW:
+ % if preview:
+
+
+ % else:
+
+ % endif
+ % else:
+
+ % endif
+
+
+
+
+ % if movie['tagline']:
+
+ ${movie['tagline']}
+
+ % endif
+
+ ${movie['summary'][:450] + (movie['summary'][450:] and '...')}
+
+
+
+
+
+
+ % endfor
+
+ % endif
+ % if recently_added.get('show'):
+
+
+
+
+ % for show in recently_added['show']:
+ <%
+ if show['season_count'] == 1 and show['season'][0]['episode_count'] == 1:
+ link_rating_key = show['season'][0]['episode'][0]['rating_key']
+ link_title = show['title'] + " - " + show['season'][0]['episode'][0]['title']
+ else:
+ link_rating_key = show['rating_key']
+ link_title = show['title']
+ %>
+ % if loop.index == len(recently_added['show'])-1 and loop.index % 2 == 0:
+
+ % else:
+
+ % endif
+ % if PILLOW:
+ % if preview:
+
+
+ % else:
+
+ % endif
+ % else:
+
+ % endif
+
+
+
+
+
+ % if show['season_count'] > 1:
+ ${show['season_count']} seasons /
+ % endif
+ <% total_show_episodes = sum(s['episode_count'] for s in show['season']) %>
+ ${total_show_episodes} episode${'s' if total_show_episodes > 1 else ''}
+
+
+ % for i, season in enumerate(show['season'][:8]):
+ Season ${season['media_index']} ·
+ % if season['episode_count'] == 1:
+ Episode ${season['episode'][0]['media_index']} - ${season['episode'][0]['title']}
+ % else:
+ Episodes ${season['episode_range']}
+ % endif
+ % if i < min(show['season_count'], 7):
+
+ % elif i == 7 and show['season_count'] > 8:
+ ...plus ${show['season_count'] - 8} more seasons!
+ % endif
+ % endfor
+
+
+ % if show['season_count'] == 1 and show['season'][0]['episode_count'] == 1:
+ ${show['season'][0]['episode'][0]['summary'][:350] + (show['season'][0]['episode'][0]['summary'][350:] and '...')}
+ % else:
+ <% length = max(0, 350 - 50 * (show['season_count'] - 1)) %>
+ % if length:
+ ${show['summary'][:length] + (show['summary'][length:] and '...')}
+ % endif
+ % endif
+
+
+
+
+
+
+ % endfor
+
+ % endif
+ % if recently_added.get('artist'):
+
+
+
+
+ <% album_count = 0 %>
+ % for artist in recently_added['artist']:
+ % for album in artist['album']:
+ <% album_count += 1 %>
+ % if album_count == total_albums and album_count % 2 == 1:
+
+ % else:
+
+ % endif
+ % if PILLOW:
+ % if preview:
+
+
+ % else:
+
+ % endif
+ % else:
+
+ % endif
+
+
+
+
+
+ ${artist['title']} · ${album['track_count']} track${'s' if album['track_count'] > 1 else ''}
+
+ % if artist['title'].lower() != 'various artists':
+
+ ${album['summary'][:200] + (album['summary'][200:] and '...')}
+
+ % endif
+
+
+
+
+
+ % endfor
+ % endfor
+
+ % endif
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+
\ No newline at end of file
diff --git a/plexpy/__init__.py b/plexpy/__init__.py
index 70b82480..680c5451 100644
--- a/plexpy/__init__.py
+++ b/plexpy/__init__.py
@@ -634,6 +634,12 @@ def dbcheck():
'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, blur_art_url TEXT)'
+ )
+
# 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, '
diff --git a/plexpy/common.py b/plexpy/common.py
index 553b85d9..62c2a355 100644
--- a/plexpy/common.py
+++ b/plexpy/common.py
@@ -31,6 +31,10 @@ DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png"
DEFAULT_ART = "interfaces/default/images/art.png"
+ONLINE_POSTER_THUMB = "http://tautulli.com/images/poster.png"
+ONLINE_COVER_THUMB = "http://tautulli.com/images/cover.png"
+ONLINE_ART = "http://tautulli.com/images/art.png"
+
MEDIA_TYPE_HEADERS = {
'movie': 'Movies',
'show': 'TV Shows',
diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py
index 1c109e3d..67f5594d 100644
--- a/plexpy/datafactory.py
+++ b/plexpy/datafactory.py
@@ -1106,7 +1106,7 @@ class DataFactory(object):
return ip_address
- def get_poster_info(self, rating_key='', metadata=None):
+ def get_poster_info(self, rating_key='', metadata=None, art=False):
monitor_db = database.MonitorDatabase()
poster_key = ''
@@ -1124,25 +1124,39 @@ class DataFactory(object):
if poster_key:
try:
- query = 'SELECT poster_title, poster_url FROM poster_urls ' \
- 'WHERE rating_key = ?'
+ if art:
+ query = 'SELECT art_title, art_url, blur_art_url FROM art_urls ' \
+ 'WHERE rating_key = ?'
+ else:
+ query = 'SELECT poster_title, poster_url FROM poster_urls ' \
+ 'WHERE rating_key = ?'
poster_info = monitor_db.select_single(query, args=[poster_key])
except Exception as e:
logger.warn(u"Tautulli DataFactory :: Unable to execute database query for get_poster_url: %s." % e)
return poster_info
- def set_poster_url(self, rating_key='', poster_title='', poster_url='', delete_hash=''):
+ def set_poster_url(self, rating_key='', poster_title='', poster_url='', delete_hash='', art=False, blur=False):
monitor_db = database.MonitorDatabase()
if str(rating_key).isdigit():
keys = {'rating_key': int(rating_key)}
- values = {'poster_title': poster_title,
- 'poster_url': poster_url,
- 'delete_hash': delete_hash}
+ if art:
+ table = 'art_urls'
+ values = {'art_title': poster_title,
+ 'delete_hash': delete_hash}
+ if blur:
+ values['blur_art_url'] = poster_url
+ else:
+ values['art_url'] = poster_url
+ else:
+ table = 'poster_urls'
+ values = {'poster_title': poster_title,
+ 'poster_url': poster_url,
+ 'delete_hash': delete_hash}
- monitor_db.upsert(table_name='poster_urls', key_dict=keys, value_dict=values)
+ monitor_db.upsert(table_name=table, key_dict=keys, value_dict=values)
def delete_poster_url(self, rating_key=''):
monitor_db = database.MonitorDatabase()
diff --git a/plexpy/newsletters.py b/plexpy/newsletters.py
index 6e239f30..99ff0d39 100644
--- a/plexpy/newsletters.py
+++ b/plexpy/newsletters.py
@@ -22,12 +22,14 @@ import os
import time
import plexpy
+import common
import database
import helpers
import libraries
import logger
import notification_handler
import pmsconnect
+from notification_handler import PILLOW, get_poster_info
from notifiers import send_notification, EMAIL
@@ -242,6 +244,7 @@ class Newsletter(object):
_DEFAULT_EMAIL_CONFIG['from_name'] = 'Tautulli Newsletter'
_DEFAULT_EMAIL_CONFIG['notifier'] = 0
_DEFAULT_EMAIL_CONFIG['subject'] = 'Tautulli Newsletter'
+ _TEMPLATE_MASTER = ''
_TEMPLATE = ''
def __init__(self, config=None, email_config=None):
@@ -250,6 +253,7 @@ class Newsletter(object):
self.parameters = {'server_name': plexpy.CONFIG.PMS_NAME}
self.is_preview = False
+ self.master_template = False
def set_config(self, config=None, default=None):
return self._validate_config(config=config, default=default)
@@ -268,26 +272,31 @@ class Newsletter(object):
return new_config
def _render_template(self, **kwargs):
+ if self.master_template:
+ template = self._TEMPLATE_MASTER
+ else:
+ template = self._TEMPLATE
+
return serve_template(
- templatename=self._TEMPLATE,
+ templatename=template,
title=self.NAME,
parameters=self.parameters,
**kwargs
)
def _format_subject(self, subject):
- subject = subject or self._default_email_config['subject']
+ subject = subject or self._DEFAULT_EMAIL_CONFIG['subject']
try:
subject = unicode(subject).format(**self.parameters)
except LookupError as e:
logger.error(
u"Tautulli Newsletter :: Unable to parse parameter %s in newsletter subject. Using fallback." % e)
- subject = unicode(self._default_email_config['subject']).format(**self.parameters)
+ subject = unicode(self._DEFAULT_EMAIL_CONFIG['subject']).format(**self.parameters)
except Exception as e:
logger.error(
u"Tautulli Newsletter :: Unable to parse custom newsletter subject: %s. Using fallback." % e)
- subject = unicode(self._default_email_config['subject']).format(**self.parameters)
+ subject = unicode(self._DEFAULT_EMAIL_CONFIG['subject']).format(**self.parameters)
return subject
@@ -297,7 +306,10 @@ class Newsletter(object):
def generate_newsletter(self):
pass
- def preview(self):
+ def preview(self, master=False):
+ self.is_preview = True
+ if master:
+ self.master_template = True
self.retrieve_data()
return self.generate_newsletter()
@@ -337,6 +349,7 @@ class RecentlyAdded(Newsletter):
_DEFAULT_CONFIG = {'last_days': 7,
'incl_libraries': None
}
+ _TEMPLATE_MASTER = 'recently_added_master.html'
_TEMPLATE = 'recently_added.html'
def __init__(self, config=None, email_config=None):
@@ -490,12 +503,38 @@ class RecentlyAdded(Newsletter):
if media_type not in self.recently_added:
self.recently_added[media_type] = self._get_recently_added(media_type)
+ if not self.is_preview:
+ # Upload posters and art to Imgur
+ movies = self.recently_added.get('movie', [])
+ shows = self.recently_added.get('show', [])
+ artists = self.recently_added.get('artist', [])
+ albums = [a for artist in artists for a in artist['album']]
+
+ 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
+
+ art_info = {}
+ if PILLOW:
+ art_info = get_poster_info(poster_thumb=item['art'],
+ poster_key=item['rating_key'],
+ poster_title=item['title'],
+ art=True,
+ width='500',
+ height='280',
+ blur=True)
+ item['art_url'] = art_info.get('blur_art_url', '')
+
return self.recently_added
def generate_newsletter(self):
return self._render_template(
recently_added=self.recently_added,
- plexpy_config=self.plexpy_config
+ plexpy_config=self.plexpy_config,
+ preview=self.is_preview
)
def _get_sections(self):
diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py
index f3276161..ab0a80cb 100644
--- a/plexpy/notification_handler.py
+++ b/plexpy/notification_handler.py
@@ -26,6 +26,12 @@ from string import Formatter
import threading
import time
+try:
+ from PIL import Image, ImageFilter
+ PILLOW = True
+except ImportError:
+ PILLOW = False
+
import plexpy
import activity_processor
import common
@@ -1065,45 +1071,76 @@ 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):
+def get_poster_info(poster_thumb='', poster_key='', poster_title='', art=False, width='', height='', blur=False):
+ default_poster_info = {'poster_title': '', 'poster_url': ''}
+ default_art_info = {'art_title': '', 'art_url': ''}
+
# Try to retrieve poster info from the database
data_factory = datafactory.DataFactory()
- poster_info = data_factory.get_poster_info(rating_key=poster_key)
+ poster_info = data_factory.get_poster_info(rating_key=poster_key, art=art)
# 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-poster-%s' % thread_name)
+ poster_file = os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-image-%s.jpg' % thread_name)
# Retrieve the poster from Plex and cache to file
pms_connect = pmsconnect.PmsConnect()
- result = pms_connect.get_image(img=poster_thumb)
+ result = pms_connect.get_image(img=poster_thumb, width=width, height=height)
if result and result[0]:
with open(poster_file, 'wb') as f:
f.write(result[0])
else:
raise Exception(u'PMS image request failed')
+ if blur and PILLOW:
+ img = Image.open(poster_file)
+ img = img.convert("RGBA")
+ img = img.filter(ImageFilter.GaussianBlur(3)) # 3px blur
+ img.putalpha(102) # 40% opacity
+
+ # Save as a png
+ poster_file_blur = os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-image-%s.png' % thread_name)
+ img.save(poster_file_blur)
+
# Upload poster_thumb to Imgur and get link
- poster_url, delete_hash = helpers.upload_to_imgur(poster_file, poster_title)
+ if blur:
+ poster_url, delete_hash = helpers.uploadToImgur(poster_file_blur, poster_title)
+ else:
+ poster_url, delete_hash = helpers.uploadToImgur(poster_file, poster_title)
if poster_url:
# Create poster info
- poster_info = {'poster_title': poster_title, 'poster_url': poster_url}
+ if art:
+ poster_info = {'art_title': poster_title}
+ if blur:
+ poster_info['blur_art_url'] = poster_url
+ else:
+ poster_info['art_url'] = poster_url
+ else:
+ poster_info = {'poster_title': poster_title, 'poster_url': poster_url}
# 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)
+ delete_hash=delete_hash,
+ art=art,
+ blur=blur)
# Delete the cached poster
os.remove(poster_file)
+ if blur:
+ os.remove(poster_file_blur)
except Exception as e:
- logger.error(u"Tautulli NotificationHandler :: Unable to retrieve poster for rating_key %s: %s." % (str(metadata['rating_key']), e))
+ logger.error(u"Tautulli NotificationHandler :: Unable to retrieve poster for rating_key %s: %s."
+ % (poster_key, e))
- return poster_info
+ if art:
+ return poster_info or default_art_info
+ else:
+ return poster_info or default_poster_info
def lookup_tvmaze_by_id(rating_key=None, thetvdb_id=None, imdb_id=None):
diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py
index ecdc9f45..22fa35f0 100644
--- a/plexpy/pmsconnect.py
+++ b/plexpy/pmsconnect.py
@@ -2436,15 +2436,17 @@ class PmsConnect(object):
Output: array
"""
+ width = width or '1000'
+ height = height or '1500'
+
if img:
if clip:
params = {'url': '%s&%s' % (img, urllib.urlencode({'X-Plex-Token': self.token}))}
else:
params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urllib.urlencode({'X-Plex-Token': self.token}))}
- if width.isdigit() and height.isdigit():
- params['width'] = width
- params['height'] = height
+ params['width'] = width
+ params['height'] = height
uri = '/photo/:/transcode?%s' % urllib.urlencode(params)
result = self.request_handler.make_request(uri=uri,
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index 00e1b7ca..fc3f16c8 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -5514,11 +5514,11 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth(member_of("admin"))
- def preview_newsletter(self, newsletter_id=None, **kwargs):
+ def preview_newsletter(self, newsletter_id=None, master=False, **kwargs):
if newsletter_id:
newsletter = newsletters.get_newsletter_config(newsletter_id=newsletter_id)
newsletter_agent = newsletters.get_agent_class(agent_id=newsletter['agent_id'], config=newsletter['config'])
if newsletter_agent:
- return newsletter_agent.preview()
+ return newsletter_agent.preview(master=master)
return
|