mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-10 07:22:37 -07:00
cache image, download log etc.
This commit is contained in:
parent
21fcbd85d8
commit
9ae441b75a
7 changed files with 452 additions and 375 deletions
|
@ -21,6 +21,7 @@
|
||||||
<span><i class="fa fa-list-alt"></i> Logs</span>
|
<span><i class="fa fa-list-alt"></i> Logs</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-bar">
|
<div class="button-bar">
|
||||||
|
<button class="btn btn-dark" id="download-plexpylog"><i class="fa fa-download"></i> Download log</button>
|
||||||
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear log</button>
|
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear log</button>
|
||||||
<button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear log</button>
|
<button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear log</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,7 +121,7 @@
|
||||||
LoadPlexPyLogs();
|
LoadPlexPyLogs();
|
||||||
clearSearchButton('log_table', log_table);
|
clearSearchButton('log_table', log_table);
|
||||||
});
|
});
|
||||||
|
|
||||||
function LoadPlexPyLogs() {
|
function LoadPlexPyLogs() {
|
||||||
log_table_options.ajax = {
|
log_table_options.ajax = {
|
||||||
url: "getLog"
|
url: "getLog"
|
||||||
|
@ -157,6 +158,7 @@
|
||||||
|
|
||||||
$("#plexpy-logs-btn").click(function () {
|
$("#plexpy-logs-btn").click(function () {
|
||||||
$("#clear-logs").show();
|
$("#clear-logs").show();
|
||||||
|
$("#download-plexpylog").show()
|
||||||
$("#clear-notify-logs").hide();
|
$("#clear-notify-logs").hide();
|
||||||
LoadPlexPyLogs();
|
LoadPlexPyLogs();
|
||||||
clearSearchButton('log_table', log_table);
|
clearSearchButton('log_table', log_table);
|
||||||
|
@ -164,6 +166,7 @@
|
||||||
|
|
||||||
$("#plex-logs-btn").click(function () {
|
$("#plex-logs-btn").click(function () {
|
||||||
$("#clear-logs").hide();
|
$("#clear-logs").hide();
|
||||||
|
$("#download-plexpylog").hide()
|
||||||
$("#clear-notify-logs").hide();
|
$("#clear-notify-logs").hide();
|
||||||
LoadPlexLogs();
|
LoadPlexLogs();
|
||||||
clearSearchButton('plex_log_table', plex_log_table);
|
clearSearchButton('plex_log_table', plex_log_table);
|
||||||
|
@ -171,6 +174,7 @@
|
||||||
|
|
||||||
$("#plex-scanner-logs-btn").click(function () {
|
$("#plex-scanner-logs-btn").click(function () {
|
||||||
$("#clear-logs").hide();
|
$("#clear-logs").hide();
|
||||||
|
$("#download-plexpylog").hide()
|
||||||
$("#clear-notify-logs").hide();
|
$("#clear-notify-logs").hide();
|
||||||
LoadPlexScannerLogs();
|
LoadPlexScannerLogs();
|
||||||
clearSearchButton('plex_scanner_log_table', plex_scanner_log_table);
|
clearSearchButton('plex_scanner_log_table', plex_scanner_log_table);
|
||||||
|
@ -178,6 +182,7 @@
|
||||||
|
|
||||||
$("#notification-logs-btn").click(function () {
|
$("#notification-logs-btn").click(function () {
|
||||||
$("#clear-logs").hide();
|
$("#clear-logs").hide();
|
||||||
|
$("#download-plexpylog").hide()
|
||||||
$("#clear-notify-logs").show();
|
$("#clear-notify-logs").show();
|
||||||
LoadNotificationLogs();
|
LoadNotificationLogs();
|
||||||
clearSearchButton('notification_log_table', notification_log_table);
|
clearSearchButton('notification_log_table', notification_log_table);
|
||||||
|
@ -190,6 +195,11 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#download-plexpylog").click(function () {
|
||||||
|
window.location.href = "download_log";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
$("#clear-notify-logs").click(function () {
|
$("#clear-notify-logs").click(function () {
|
||||||
var r = confirm("Are you sure you want to clear the PlexPy notification log?");
|
var r = confirm("Are you sure you want to clear the PlexPy notification log?");
|
||||||
if (r == true) {
|
if (r == true) {
|
||||||
|
|
|
@ -67,8 +67,6 @@ CONFIG_FILE = None
|
||||||
|
|
||||||
DB_FILE = None
|
DB_FILE = None
|
||||||
|
|
||||||
LOG_LIST = []
|
|
||||||
|
|
||||||
INSTALL_TYPE = None
|
INSTALL_TYPE = None
|
||||||
CURRENT_VERSION = None
|
CURRENT_VERSION = None
|
||||||
LATEST_VERSION = None
|
LATEST_VERSION = None
|
||||||
|
@ -134,7 +132,7 @@ def initialize(config_file):
|
||||||
try:
|
try:
|
||||||
os.makedirs(CONFIG.BACKUP_DIR)
|
os.makedirs(CONFIG.BACKUP_DIR)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logger.error("Could not create backup dir '%s': %s", BACKUP_DIR, e)
|
logger.error("Could not create backup dir '%s': %s" % (BACKUP_DIR, e))
|
||||||
|
|
||||||
if not CONFIG.CACHE_DIR:
|
if not CONFIG.CACHE_DIR:
|
||||||
CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache')
|
CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache')
|
||||||
|
@ -142,14 +140,14 @@ def initialize(config_file):
|
||||||
try:
|
try:
|
||||||
os.makedirs(CONFIG.CACHE_DIR)
|
os.makedirs(CONFIG.CACHE_DIR)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logger.error("Could not create cache dir '%s': %s", CACHE_DIR, e)
|
logger.error("Could not create cache dir '%s': %s" % (CACHE_DIR, e))
|
||||||
|
|
||||||
# Initialize the database
|
# Initialize the database
|
||||||
logger.info('Checking to see if the database has all tables....')
|
logger.info('Checking to see if the database has all tables....')
|
||||||
try:
|
try:
|
||||||
dbcheck()
|
dbcheck()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Can't connect to the database: %s", e)
|
logger.error("Can't connect to the database: %s" % e)
|
||||||
|
|
||||||
# Check if PlexPy has a uuid
|
# Check if PlexPy has a uuid
|
||||||
if CONFIG.PMS_UUID == '' or not CONFIG.PMS_UUID:
|
if CONFIG.PMS_UUID == '' or not CONFIG.PMS_UUID:
|
||||||
|
@ -171,8 +169,8 @@ def initialize(config_file):
|
||||||
with open(version_lock_file, "w") as fp:
|
with open(version_lock_file, "w") as fp:
|
||||||
fp.write(CURRENT_VERSION)
|
fp.write(CURRENT_VERSION)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error("Unable to write current version to file '%s': %s",
|
logger.error("Unable to write current version to file '%s': %s" %
|
||||||
version_lock_file, e)
|
(version_lock_file, e))
|
||||||
|
|
||||||
# Check for new versions
|
# Check for new versions
|
||||||
if CONFIG.CHECK_GITHUB_ON_STARTUP and CONFIG.CHECK_GITHUB:
|
if CONFIG.CHECK_GITHUB_ON_STARTUP and CONFIG.CHECK_GITHUB:
|
||||||
|
@ -219,7 +217,7 @@ def daemonize():
|
||||||
pid = os.fork() # @UndefinedVariable - only available in UNIX
|
pid = os.fork() # @UndefinedVariable - only available in UNIX
|
||||||
if pid != 0:
|
if pid != 0:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except OSError, e:
|
except OSError as e:
|
||||||
raise RuntimeError("1st fork failed: %s [%d]", e.strerror, e.errno)
|
raise RuntimeError("1st fork failed: %s [%d]", e.strerror, e.errno)
|
||||||
|
|
||||||
os.setsid()
|
os.setsid()
|
||||||
|
@ -233,7 +231,7 @@ def daemonize():
|
||||||
pid = os.fork() # @UndefinedVariable - only available in UNIX
|
pid = os.fork() # @UndefinedVariable - only available in UNIX
|
||||||
if pid != 0:
|
if pid != 0:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except OSError, e:
|
except OSError as e:
|
||||||
raise RuntimeError("2nd fork failed: %s [%d]", e.strerror, e.errno)
|
raise RuntimeError("2nd fork failed: %s [%d]", e.strerror, e.errno)
|
||||||
|
|
||||||
dev_null = file('/dev/null', 'r')
|
dev_null = file('/dev/null', 'r')
|
||||||
|
@ -269,7 +267,7 @@ def launch_browser(host, port, root):
|
||||||
try:
|
try:
|
||||||
webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root))
|
webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Could not launch browser: %s', e)
|
logger.error('Could not launch browser: %s' % e)
|
||||||
|
|
||||||
|
|
||||||
def initialize_scheduler():
|
def initialize_scheduler():
|
||||||
|
@ -949,7 +947,7 @@ def shutdown(restart=False, update=False):
|
||||||
try:
|
try:
|
||||||
versioncheck.update()
|
versioncheck.update()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn('PlexPy failed to update: %s. Restarting.', e)
|
logger.warn('PlexPy failed to update: %s. Restarting.' % e)
|
||||||
|
|
||||||
if CREATEPID:
|
if CREATEPID:
|
||||||
logger.info('Removing pidfile %s', PIDFILE)
|
logger.info('Removing pidfile %s', PIDFILE)
|
||||||
|
@ -963,7 +961,7 @@ def shutdown(restart=False, update=False):
|
||||||
if '--nolaunch' not in args:
|
if '--nolaunch' not in args:
|
||||||
args += ['--nolaunch']
|
args += ['--nolaunch']
|
||||||
logger.info('Restarting PlexPy with %s', args)
|
logger.info('Restarting PlexPy with %s', args)
|
||||||
|
|
||||||
# os.execv fails with spaced names on Windows
|
# os.execv fails with spaced names on Windows
|
||||||
# https://bugs.python.org/issue19066
|
# https://bugs.python.org/issue19066
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
|
|
|
@ -443,6 +443,9 @@ General optional parameters:
|
||||||
if self._api_cmd == 'docs_md':
|
if self._api_cmd == 'docs_md':
|
||||||
return out['response']['data']
|
return out['response']['data']
|
||||||
|
|
||||||
|
elif self._api_cmd == 'download_log':
|
||||||
|
return
|
||||||
|
|
||||||
if self._api_out_type == 'json':
|
if self._api_out_type == 'json':
|
||||||
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
|
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -31,7 +31,7 @@ import helpers
|
||||||
|
|
||||||
# These settings are for file logging only
|
# These settings are for file logging only
|
||||||
FILENAME = "plexpy.log"
|
FILENAME = "plexpy.log"
|
||||||
MAX_SIZE = 1000000 # 1 MB
|
MAX_SIZE = 5000000 # 5 MB
|
||||||
MAX_FILES = 5
|
MAX_FILES = 5
|
||||||
|
|
||||||
_BLACKLIST_WORDS = []
|
_BLACKLIST_WORDS = []
|
||||||
|
@ -42,18 +42,6 @@ logger = logging.getLogger("plexpy")
|
||||||
# Global queue for multiprocessing logging
|
# Global queue for multiprocessing logging
|
||||||
queue = None
|
queue = None
|
||||||
|
|
||||||
class LogListHandler(logging.Handler):
|
|
||||||
"""
|
|
||||||
Log handler for Web UI.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def emit(self, record):
|
|
||||||
message = self.format(record)
|
|
||||||
message = message.replace("\n", "<br />")
|
|
||||||
|
|
||||||
plexpy.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
|
|
||||||
|
|
||||||
|
|
||||||
class NoThreadFilter(logging.Filter):
|
class NoThreadFilter(logging.Filter):
|
||||||
"""
|
"""
|
||||||
Log filter for the current thread
|
Log filter for the current thread
|
||||||
|
@ -209,12 +197,6 @@ def initLogger(console=False, log_dir=False, verbose=False):
|
||||||
logger.propagate = False
|
logger.propagate = False
|
||||||
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
||||||
|
|
||||||
# Add list logger
|
|
||||||
loglist_handler = LogListHandler()
|
|
||||||
loglist_handler.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
logger.addHandler(loglist_handler)
|
|
||||||
|
|
||||||
# Setup file logger
|
# Setup file logger
|
||||||
if log_dir:
|
if log_dir:
|
||||||
filename = os.path.join(log_dir, FILENAME)
|
filename = os.path.join(log_dir, FILENAME)
|
||||||
|
|
|
@ -1890,7 +1890,7 @@ class PmsConnect(object):
|
||||||
|
|
||||||
return labels_list
|
return labels_list
|
||||||
|
|
||||||
def get_image(self, img=None, width=None, height=None, fallback=None):
|
def get_image(self, img=None, width=None, height=None):
|
||||||
"""
|
"""
|
||||||
Return image data as array.
|
Return image data as array.
|
||||||
Array contains the image content type and image binary
|
Array contains the image content type and image binary
|
||||||
|
@ -1900,51 +1900,24 @@ class PmsConnect(object):
|
||||||
height { the image height }
|
height { the image height }
|
||||||
Output: array
|
Output: array
|
||||||
"""
|
"""
|
||||||
if not img:
|
|
||||||
logger.error(u"PlexPy Pmsconnect :: Image proxy queried but no input received.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Remove the timestamp from PMS image uri
|
if img:
|
||||||
image_uri = img.rsplit('/', 1)[0]
|
uri = '/photo/:/transcode?url=http://127.0.0.1:32400%s' % img
|
||||||
|
|
||||||
# Try to retrieve the image from cache if it isn't a bif index.
|
|
||||||
if not 'indexes' in image_uri:
|
|
||||||
image_path, content_type = helpers.cache_image(image_uri)
|
|
||||||
|
|
||||||
if image_path and content_type:
|
|
||||||
return [open(image_path, 'rb'), content_type]
|
|
||||||
|
|
||||||
try:
|
|
||||||
if width.isdigit() and height.isdigit():
|
if width.isdigit() and height.isdigit():
|
||||||
uri = '/photo/:/transcode?url=http://127.0.0.1:32400' + img + '&width=' + width + '&height=' + height
|
uri += '&width=%s&height=%s' % (width, height)
|
||||||
|
|
||||||
|
result = self.request_handler.make_request(uri=uri,
|
||||||
|
proto=self.protocol,
|
||||||
|
request_type='GET',
|
||||||
|
return_type=True)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
logger.error(u"PlexPy Pmsconnect :: Image proxy queried but no width or height specified.")
|
return result[0], result[1]
|
||||||
raise Exception
|
|
||||||
|
|
||||||
request, content_type = self.request_handler.make_request(uri=uri,
|
else:
|
||||||
proto=self.protocol,
|
logger.error(u"PlexPy Pmsconnect :: Image proxy queries but no input received.")
|
||||||
request_type='GET',
|
|
||||||
return_type=True)
|
|
||||||
# Save the image to cache if it isn't a bif index.
|
|
||||||
if not 'indexes' in image_uri:
|
|
||||||
helpers.cache_image(image_uri, request)
|
|
||||||
|
|
||||||
return [request, content_type]
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
if fallback:
|
|
||||||
logger.debug(u"PlexPy Pmsconnect :: Trying fallback %s image." % fallback)
|
|
||||||
try:
|
|
||||||
if fallback == 'poster':
|
|
||||||
return [open(common.DEFAULT_POSTER_THUMB, 'rb'), 'image/png']
|
|
||||||
elif fallback == 'cover':
|
|
||||||
return [open(common.DEFAULT_COVER_THUMB, 'rb'), 'image/png']
|
|
||||||
elif fallback == 'art':
|
|
||||||
return [open(common.DEFAULT_ART, 'rb'), 'image/png']
|
|
||||||
except IOError as e:
|
|
||||||
logger.error(u"PlexPy Pmsconnect :: Unable to read fallback %s image: %s" % (fallback, e))
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_search_results(self, query=''):
|
def get_search_results(self, query=''):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -20,6 +20,9 @@ import random
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
from cherrypy.lib.static import serve_file, serve_download
|
||||||
|
from cherrypy._cperror import NotFound
|
||||||
|
|
||||||
from hashing_passwords import make_hash
|
from hashing_passwords import make_hash
|
||||||
from mako.lookup import TemplateLookup
|
from mako.lookup import TemplateLookup
|
||||||
from mako import exceptions
|
from mako import exceptions
|
||||||
|
@ -59,7 +62,7 @@ def serve_template(templatename, **kwargs):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
template = _hplookup.get_template(templatename)
|
template = _hplookup.get_template(templatename)
|
||||||
return template.render(http_root=plexpy.HTTP_ROOT, server_name=server_name,
|
return template.render(http_root=plexpy.HTTP_ROOT, server_name=server_name,
|
||||||
_session=_session, **kwargs)
|
_session=_session, **kwargs)
|
||||||
except:
|
except:
|
||||||
return exceptions.html_error_template().render()
|
return exceptions.html_error_template().render()
|
||||||
|
@ -68,7 +71,7 @@ def serve_template(templatename, **kwargs):
|
||||||
class WebInterface(object):
|
class WebInterface(object):
|
||||||
|
|
||||||
auth = AuthController()
|
auth = AuthController()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.interface_dir = os.path.join(str(plexpy.PROG_DIR), 'data/')
|
self.interface_dir = os.path.join(str(plexpy.PROG_DIR), 'data/')
|
||||||
|
|
||||||
|
@ -133,12 +136,12 @@ class WebInterface(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
[{"clientIdentifier": "ds48g4r354a8v9byrrtr697g3g79w",
|
[{"clientIdentifier": "ds48g4r354a8v9byrrtr697g3g79w",
|
||||||
"httpsRequired": "0",
|
"httpsRequired": "0",
|
||||||
"ip": "xxx.xxx.xxx.xxx",
|
"ip": "xxx.xxx.xxx.xxx",
|
||||||
"label": "Winterfell-Server",
|
"label": "Winterfell-Server",
|
||||||
"local": "1",
|
"local": "1",
|
||||||
"port": "32400",
|
"port": "32400",
|
||||||
"value": "xxx.xxx.xxx.xxx"
|
"value": "xxx.xxx.xxx.xxx"
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
|
@ -296,7 +299,7 @@ class WebInterface(object):
|
||||||
try:
|
try:
|
||||||
pms_connect = pmsconnect.PmsConnect()
|
pms_connect = pmsconnect.PmsConnect()
|
||||||
result = pms_connect.get_recently_added_details(count=count)
|
result = pms_connect.get_recently_added_details(count=count)
|
||||||
except IOError, e:
|
except IOError as e:
|
||||||
return serve_template(templatename="recently_added.html", data=None)
|
return serve_template(templatename="recently_added.html", data=None)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
|
@ -309,7 +312,7 @@ class WebInterface(object):
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def delete_temp_sessions(self):
|
def delete_temp_sessions(self):
|
||||||
|
|
||||||
result = database.delete_sessions()
|
result = database.delete_sessions()
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
|
@ -354,30 +357,30 @@ class WebInterface(object):
|
||||||
"recordsTotal": 10,
|
"recordsTotal": 10,
|
||||||
"recordsFiltered": 10,
|
"recordsFiltered": 10,
|
||||||
"data":
|
"data":
|
||||||
[{"child_count": 3745,
|
[{"child_count": 3745,
|
||||||
"content_rating": "TV-MA",
|
"content_rating": "TV-MA",
|
||||||
"count": 62,
|
"count": 62,
|
||||||
"do_notify": "Checked",
|
"do_notify": "Checked",
|
||||||
"do_notify_created": "Checked",
|
"do_notify_created": "Checked",
|
||||||
"duration": 1578037,
|
"duration": 1578037,
|
||||||
"id": 1128,
|
"id": 1128,
|
||||||
"keep_history": "Checked",
|
"keep_history": "Checked",
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"last_accessed": 1462693216,
|
"last_accessed": 1462693216,
|
||||||
"last_played": "Game of Thrones - The Red Woman",
|
"last_played": "Game of Thrones - The Red Woman",
|
||||||
"library_art": "/:/resources/show-fanart.jpg",
|
"library_art": "/:/resources/show-fanart.jpg",
|
||||||
"library_thumb": "",
|
"library_thumb": "",
|
||||||
"media_index": 1,
|
"media_index": 1,
|
||||||
"media_type": "episode",
|
"media_type": "episode",
|
||||||
"parent_count": 240,
|
"parent_count": 240,
|
||||||
"parent_media_index": 6,
|
"parent_media_index": 6,
|
||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"plays": 772,
|
"plays": 772,
|
||||||
"rating_key": 153037,
|
"rating_key": 153037,
|
||||||
"section_id": 2,
|
"section_id": 2,
|
||||||
"section_name": "TV Shows",
|
"section_name": "TV Shows",
|
||||||
"section_type": "Show",
|
"section_type": "Show",
|
||||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"year": 2016
|
"year": 2016
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
|
@ -623,27 +626,27 @@ class WebInterface(object):
|
||||||
"filtered_file_size": 2616760056742,
|
"filtered_file_size": 2616760056742,
|
||||||
"total_file_size": 2616760056742,
|
"total_file_size": 2616760056742,
|
||||||
"data":
|
"data":
|
||||||
[{"added_at": "1403553078",
|
[{"added_at": "1403553078",
|
||||||
"audio_channels": "",
|
"audio_channels": "",
|
||||||
"audio_codec": "",
|
"audio_codec": "",
|
||||||
"bitrate": "",
|
"bitrate": "",
|
||||||
"container": "",
|
"container": "",
|
||||||
"file_size": 253660175293,
|
"file_size": 253660175293,
|
||||||
"grandparent_rating_key": "",
|
"grandparent_rating_key": "",
|
||||||
"last_played": 1462380698,
|
"last_played": 1462380698,
|
||||||
"media_index": "1",
|
"media_index": "1",
|
||||||
"media_type": "show",
|
"media_type": "show",
|
||||||
"parent_media_index": "",
|
"parent_media_index": "",
|
||||||
"parent_rating_key": "",
|
"parent_rating_key": "",
|
||||||
"play_count": 15,
|
"play_count": 15,
|
||||||
"rating_key": "1219",
|
"rating_key": "1219",
|
||||||
"section_id": 2,
|
"section_id": 2,
|
||||||
"section_type": "show",
|
"section_type": "show",
|
||||||
"thumb": "/library/metadata/1219/thumb/1436265995",
|
"thumb": "/library/metadata/1219/thumb/1436265995",
|
||||||
"title": "Game of Thrones",
|
"title": "Game of Thrones",
|
||||||
"video_codec": "",
|
"video_codec": "",
|
||||||
"video_framerate": "",
|
"video_framerate": "",
|
||||||
"video_resolution": "",
|
"video_resolution": "",
|
||||||
"year": "2011"
|
"year": "2011"
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
|
@ -894,29 +897,29 @@ class WebInterface(object):
|
||||||
"recordsTotal": 10,
|
"recordsTotal": 10,
|
||||||
"recordsFiltered": 10,
|
"recordsFiltered": 10,
|
||||||
"data":
|
"data":
|
||||||
[{"allow_guest": "Checked",
|
[{"allow_guest": "Checked",
|
||||||
"do_notify": "Checked",
|
"do_notify": "Checked",
|
||||||
"duration": 2998290,
|
"duration": 2998290,
|
||||||
"friendly_name": "Jon Snow",
|
"friendly_name": "Jon Snow",
|
||||||
"id": 1121,
|
"id": 1121,
|
||||||
"ip_address": "xxx.xxx.xxx.xxx",
|
"ip_address": "xxx.xxx.xxx.xxx",
|
||||||
"keep_history": "Checked",
|
"keep_history": "Checked",
|
||||||
"last_played": "Game of Thrones - The Red Woman",
|
"last_played": "Game of Thrones - The Red Woman",
|
||||||
"last_seen": 1462591869,
|
"last_seen": 1462591869,
|
||||||
"media_index": 1,
|
"media_index": 1,
|
||||||
"media_type": "episode",
|
"media_type": "episode",
|
||||||
"parent_media_index": 6,
|
"parent_media_index": 6,
|
||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"platform": "Chrome",
|
"platform": "Chrome",
|
||||||
"player": "Plex Web (Chrome)",
|
"player": "Plex Web (Chrome)",
|
||||||
"plays": 487,
|
"plays": 487,
|
||||||
"rating_key": 153037,
|
"rating_key": 153037,
|
||||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"transcode_decision": "transcode",
|
"transcode_decision": "transcode",
|
||||||
"user_id": 328871,
|
"user_id": 328871,
|
||||||
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
|
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
|
||||||
"year": 2016
|
"year": 2016
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
{...}
|
{...}
|
||||||
]
|
]
|
||||||
|
@ -1104,24 +1107,24 @@ class WebInterface(object):
|
||||||
"recordsTotal": 2344,
|
"recordsTotal": 2344,
|
||||||
"recordsFiltered": 10,
|
"recordsFiltered": 10,
|
||||||
"data":
|
"data":
|
||||||
[{"friendly_name": "Jon Snow",
|
[{"friendly_name": "Jon Snow",
|
||||||
"id": 1121,
|
"id": 1121,
|
||||||
"ip_address": "xxx.xxx.xxx.xxx",
|
"ip_address": "xxx.xxx.xxx.xxx",
|
||||||
"last_played": "Game of Thrones - The Red Woman",
|
"last_played": "Game of Thrones - The Red Woman",
|
||||||
"last_seen": 1462591869,
|
"last_seen": 1462591869,
|
||||||
"media_index": 1,
|
"media_index": 1,
|
||||||
"media_type": "episode",
|
"media_type": "episode",
|
||||||
"parent_media_index": 6,
|
"parent_media_index": 6,
|
||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"platform": "Chrome",
|
"platform": "Chrome",
|
||||||
"play_count": 149,
|
"play_count": 149,
|
||||||
"player": "Plex Web (Chrome)",
|
"player": "Plex Web (Chrome)",
|
||||||
"rating_key": 153037,
|
"rating_key": 153037,
|
||||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"transcode_decision": "transcode",
|
"transcode_decision": "transcode",
|
||||||
"user_id": 328871,
|
"user_id": 328871,
|
||||||
"year": 2016
|
"year": 2016
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
{...}
|
{...}
|
||||||
]
|
]
|
||||||
|
@ -1227,7 +1230,7 @@ class WebInterface(object):
|
||||||
if delete_row:
|
if delete_row:
|
||||||
return {'message': delete_row}
|
return {'message': delete_row}
|
||||||
elif username:
|
elif username:
|
||||||
delete_row = delete_user.undelete(username=username)
|
delete_row = user_data.undelete(username=username)
|
||||||
|
|
||||||
if delete_row:
|
if delete_row:
|
||||||
return {'message': delete_row}
|
return {'message': delete_row}
|
||||||
|
@ -1440,7 +1443,7 @@ class WebInterface(object):
|
||||||
plexpy.CONFIG.write()
|
plexpy.CONFIG.write()
|
||||||
|
|
||||||
return "Updated graphs config values."
|
return "Updated graphs config values."
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
|
@ -1468,7 +1471,7 @@ class WebInterface(object):
|
||||||
user_names = user_data.get_user_names(kwargs=kwargs)
|
user_names = user_data.get_user_names(kwargs=kwargs)
|
||||||
|
|
||||||
return user_names
|
return user_names
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
|
@ -1902,11 +1905,17 @@ class WebInterface(object):
|
||||||
|
|
||||||
|
|
||||||
##### Logs #####
|
##### Logs #####
|
||||||
|
@cherrypy.expose
|
||||||
|
def makeerror(self):
|
||||||
|
try:
|
||||||
|
1 / 0
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def logs(self):
|
def logs(self):
|
||||||
return serve_template(templatename="logs.html", title="Log", lineList=plexpy.LOG_LIST)
|
return serve_template(templatename="logs.html", title="Log")
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
|
@ -1930,11 +1939,26 @@ class WebInterface(object):
|
||||||
if 'search[regex]' in kwargs:
|
if 'search[regex]' in kwargs:
|
||||||
search_regex = kwargs.get('search[regex]', "")
|
search_regex = kwargs.get('search[regex]', "")
|
||||||
|
|
||||||
|
filt = []
|
||||||
|
fa = filt.append
|
||||||
|
with open(os.path.join(plexpy.CONFIG.LOG_DIR, 'plexpy.log')) as f:
|
||||||
|
for l in f.readlines():
|
||||||
|
try:
|
||||||
|
temp_loglevel_and_time = l.split('- ')
|
||||||
|
loglvl = temp_loglevel_and_time[1].split(' :')[0].strip()
|
||||||
|
msg = l.split(' : ')[1].replace('\n', '')
|
||||||
|
fa([temp_loglevel_and_time[0], loglvl, msg])
|
||||||
|
except IndexError:
|
||||||
|
# Add traceback message to previous msg..
|
||||||
|
tl = (len(filt) - 1)
|
||||||
|
filt[tl][2] += ' ' + l.replace('\n', '')
|
||||||
|
continue
|
||||||
|
|
||||||
filtered = []
|
filtered = []
|
||||||
if search_value == "":
|
if search_value == '':
|
||||||
filtered = plexpy.LOG_LIST[::]
|
filtered = filt
|
||||||
else:
|
else:
|
||||||
filtered = [row for row in plexpy.LOG_LIST for column in row if search_value.lower() in column.lower()]
|
filtered = [row for row in filt for column in row if search_value.lower() in column.lower()]
|
||||||
|
|
||||||
sortcolumn = 0
|
sortcolumn = 0
|
||||||
if order_column == '1':
|
if order_column == '1':
|
||||||
|
@ -1944,11 +1968,10 @@ class WebInterface(object):
|
||||||
filtered.sort(key=lambda x: x[sortcolumn], reverse=order_dir == "desc")
|
filtered.sort(key=lambda x: x[sortcolumn], reverse=order_dir == "desc")
|
||||||
|
|
||||||
rows = filtered[start:(start + length)]
|
rows = filtered[start:(start + length)]
|
||||||
rows = [[row[0], row[2], row[1]] for row in rows]
|
|
||||||
|
|
||||||
return json.dumps({
|
return json.dumps({
|
||||||
'recordsFiltered': len(filtered),
|
'recordsFiltered': len(filtered),
|
||||||
'recordsTotal': len(plexpy.LOG_LIST),
|
'recordsTotal': len(filt),
|
||||||
'data': rows,
|
'data': rows,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1969,8 +1992,8 @@ class WebInterface(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
[["May 08, 2016 09:35:37",
|
[["May 08, 2016 09:35:37",
|
||||||
"DEBUG",
|
"DEBUG",
|
||||||
"Auth: Came in with a super-token, authorization succeeded."
|
"Auth: Came in with a super-token, authorization succeeded."
|
||||||
],
|
],
|
||||||
[...],
|
[...],
|
||||||
|
@ -2013,20 +2036,20 @@ class WebInterface(object):
|
||||||
"recordsTotal": 1039,
|
"recordsTotal": 1039,
|
||||||
"recordsFiltered": 163,
|
"recordsFiltered": 163,
|
||||||
"data":
|
"data":
|
||||||
[{"agent_id": 13,
|
[{"agent_id": 13,
|
||||||
"agent_name": "Telegram",
|
"agent_name": "Telegram",
|
||||||
"body_text": "Game of Thrones - S06E01 - The Red Woman [Transcode].",
|
"body_text": "Game of Thrones - S06E01 - The Red Woman [Transcode].",
|
||||||
"id": 1000,
|
"id": 1000,
|
||||||
"notify_action": "play",
|
"notify_action": "play",
|
||||||
"poster_url": "http://i.imgur.com/ZSqS8Ri.jpg",
|
"poster_url": "http://i.imgur.com/ZSqS8Ri.jpg",
|
||||||
"rating_key": 153037,
|
"rating_key": 153037,
|
||||||
"script_args": "[]",
|
"script_args": "[]",
|
||||||
"session_key": 147,
|
"session_key": 147,
|
||||||
"subject_text": "PlexPy (Winterfell-Server)",
|
"subject_text": "PlexPy (Winterfell-Server)",
|
||||||
"timestamp": 1462253821,
|
"timestamp": 1462253821,
|
||||||
"user": "DanyKhaleesi69",
|
"user": "DanyKhaleesi69",
|
||||||
"user_id": 8008135
|
"user_id": 8008135
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
{...}
|
{...}
|
||||||
]
|
]
|
||||||
|
@ -2070,17 +2093,19 @@ class WebInterface(object):
|
||||||
"""
|
"""
|
||||||
data_factory = datafactory.DataFactory()
|
data_factory = datafactory.DataFactory()
|
||||||
result = data_factory.delete_notification_log()
|
result = data_factory.delete_notification_log()
|
||||||
|
result = result if result else 'no data received'
|
||||||
|
|
||||||
if result:
|
return {'message': result}
|
||||||
return {'message': result}
|
|
||||||
else:
|
|
||||||
return {'message': 'no data received'}
|
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def clearLogs(self):
|
def clearLogs(self):
|
||||||
plexpy.LOG_LIST = []
|
try:
|
||||||
logger.info(u"Web logs cleared")
|
open(os.path.join(plexpy.CONFIG.LOG_DIR, 'plexpy.log'), 'w').close()
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(u'Failed to delete plexpy.log %s' % e)
|
||||||
|
|
||||||
|
logger.info(u'plexpy.log cleared')
|
||||||
raise cherrypy.HTTPRedirect("logs")
|
raise cherrypy.HTTPRedirect("logs")
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
|
@ -2277,7 +2302,7 @@ class WebInterface(object):
|
||||||
kwargs['http_hashed_password'] = 1
|
kwargs['http_hashed_password'] = 1
|
||||||
else:
|
else:
|
||||||
kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD
|
kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD
|
||||||
|
|
||||||
elif kwargs['http_password'] and kwargs.get('http_hash_password'):
|
elif kwargs['http_password'] and kwargs.get('http_hash_password'):
|
||||||
kwargs['http_password'] = make_hash(kwargs['http_password'])
|
kwargs['http_password'] = make_hash(kwargs['http_password'])
|
||||||
kwargs['http_hashed_password'] = 1
|
kwargs['http_hashed_password'] = 1
|
||||||
|
@ -2403,7 +2428,6 @@ class WebInterface(object):
|
||||||
else:
|
else:
|
||||||
return {'message': 'Database backup failed.'}
|
return {'message': 'Database backup failed.'}
|
||||||
|
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def get_notification_agent_config(self, agent_id, **kwargs):
|
def get_notification_agent_config(self, agent_id, **kwargs):
|
||||||
|
@ -2791,22 +2815,98 @@ class WebInterface(object):
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
def pms_image_proxy(self, img='', width='0', height='0', fallback=None, **kwargs):
|
def pms_image_proxy(self, img='', ratingkey=None, width='0', height='0', fallback=None, **kwargs):
|
||||||
|
""" Grabs the images from pms and saved them to disk """
|
||||||
|
|
||||||
pms_connect = pmsconnect.PmsConnect()
|
if not img and not ratingkey:
|
||||||
result = pms_connect.get_image(img, width, height, fallback)
|
logger.debug('No image input received')
|
||||||
|
return
|
||||||
|
|
||||||
if result:
|
if ratingkey and not img:
|
||||||
cherrypy.response.headers['Content-type'] = result[1]
|
img = '/library/metadata/%s/thumb/1337' % ratingkey
|
||||||
return result[0]
|
|
||||||
else:
|
img_string = img.rsplit('/', 1)[0]
|
||||||
return None
|
img_string += '%s%s' % (width, height)
|
||||||
|
fp = hashlib.md5(img_string).hexdigest()
|
||||||
|
fp += '.jpg' # we want to be able to preview the thumbs
|
||||||
|
c_dir = os.path.join(plexpy.CONFIG.CACHE_DIR, 'images')
|
||||||
|
ffp = os.path.join(c_dir, fp)
|
||||||
|
|
||||||
|
if not os.path.exists(c_dir):
|
||||||
|
os.mkdir(c_dir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if 'indexes' in img:
|
||||||
|
raise
|
||||||
|
return serve_file(path=ffp, content_type='image/jpeg')
|
||||||
|
|
||||||
|
except NotFound:
|
||||||
|
# the image does not exist, download it from pms
|
||||||
|
try:
|
||||||
|
pms_connect = pmsconnect.PmsConnect()
|
||||||
|
result = pms_connect.get_image(img, width, height)
|
||||||
|
|
||||||
|
if result and result[0]:
|
||||||
|
cherrypy.response.headers['Content-type'] = result[1]
|
||||||
|
if 'indexes' not in img:
|
||||||
|
with open(ffp, 'wb') as f:
|
||||||
|
f.write(result[0])
|
||||||
|
|
||||||
|
return result[0]
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug('Failed to get image %s file %s falling back to %s' % (img, fp, e))
|
||||||
|
fbi = None
|
||||||
|
if fallback == 'poster':
|
||||||
|
fbi = common.DEFAULT_POSTER_THUMB
|
||||||
|
elif fallback == 'cover':
|
||||||
|
fbi = common.DEFAULT_COVER_THUMB
|
||||||
|
elif fallback == 'art':
|
||||||
|
fbi = common.DEFAULT_ART
|
||||||
|
|
||||||
|
if fbi:
|
||||||
|
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
|
||||||
|
return serve_file(path=fp, content_type='image/png')
|
||||||
|
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@requireAuth(member_of("admin"))
|
||||||
|
@addtoapi()
|
||||||
|
def download_log(self):
|
||||||
|
try:
|
||||||
|
logger.logger.flush()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return serve_download(os.path.join(plexpy.CONFIG.LOG_DIR, 'plexpy.log'), name='plexpy.log')
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@requireAuth(member_of("admin"))
|
||||||
|
@addtoapi()
|
||||||
|
def delete_image_cache(self):
|
||||||
|
""" Deletes the image cache dir and recreates it """
|
||||||
|
cache_dir = os.path.join(plexpy.CONFIG.CACHE_DIR, 'images')
|
||||||
|
try:
|
||||||
|
os.rmdir(cache_dir)
|
||||||
|
except OSError as e:
|
||||||
|
logger.exception('Failed to delete %s %s' % (cache_dir, e))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.makedirs(cache_dir)
|
||||||
|
except OSError as e:
|
||||||
|
logger.exception('Faild to make %s %s' % (cache_dir, e))
|
||||||
|
return
|
||||||
|
|
||||||
|
return {'result': 'success', 'message': 'Deleted image cache'}
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def delete_poster_url(self, poster_url=''):
|
def delete_poster_url(self, poster_url=''):
|
||||||
|
|
||||||
if poster_url:
|
if poster_url:
|
||||||
data_factory = datafactory.DataFactory()
|
data_factory = datafactory.DataFactory()
|
||||||
result = data_factory.delete_poster_url(poster_url=poster_url)
|
result = data_factory.delete_poster_url(poster_url=poster_url)
|
||||||
|
@ -2839,7 +2939,7 @@ class WebInterface(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
{"results_count": 69,
|
{"results_count": 69,
|
||||||
"results_list":
|
"results_list":
|
||||||
{"movie":
|
{"movie":
|
||||||
[{...},
|
[{...},
|
||||||
|
@ -2883,8 +2983,8 @@ class WebInterface(object):
|
||||||
return serve_template(templatename="info_search_results_list.html", data=None, title="Search Result List")
|
return serve_template(templatename="info_search_results_list.html", data=None, title="Search Result List")
|
||||||
|
|
||||||
|
|
||||||
##### Update Metadata #####
|
##### Update Metadata #####
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def update_metadata(self, rating_key=None, query=None, update=False, **kwargs):
|
def update_metadata(self, rating_key=None, query=None, update=False, **kwargs):
|
||||||
|
@ -3033,51 +3133,51 @@ class WebInterface(object):
|
||||||
json:
|
json:
|
||||||
{"metadata":
|
{"metadata":
|
||||||
{"actors": [
|
{"actors": [
|
||||||
"Kit Harington",
|
"Kit Harington",
|
||||||
"Emilia Clarke",
|
"Emilia Clarke",
|
||||||
"Isaac Hempstead-Wright",
|
"Isaac Hempstead-Wright",
|
||||||
"Maisie Williams",
|
"Maisie Williams",
|
||||||
"Liam Cunningham",
|
"Liam Cunningham",
|
||||||
],
|
],
|
||||||
"added_at": "1461572396",
|
"added_at": "1461572396",
|
||||||
"art": "/library/metadata/1219/art/1462175063",
|
"art": "/library/metadata/1219/art/1462175063",
|
||||||
"content_rating": "TV-MA",
|
"content_rating": "TV-MA",
|
||||||
"directors": [
|
"directors": [
|
||||||
"Jeremy Podeswa"
|
"Jeremy Podeswa"
|
||||||
],
|
],
|
||||||
"duration": "2998290",
|
"duration": "2998290",
|
||||||
"genres": [
|
"genres": [
|
||||||
"Adventure",
|
"Adventure",
|
||||||
"Drama",
|
"Drama",
|
||||||
"Fantasy"
|
"Fantasy"
|
||||||
],
|
],
|
||||||
"grandparent_rating_key": "1219",
|
"grandparent_rating_key": "1219",
|
||||||
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
|
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
|
||||||
"grandparent_title": "Game of Thrones",
|
"grandparent_title": "Game of Thrones",
|
||||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"last_viewed_at": "1462165717",
|
"last_viewed_at": "1462165717",
|
||||||
"library_name": "TV Shows",
|
"library_name": "TV Shows",
|
||||||
"media_index": "1",
|
"media_index": "1",
|
||||||
"media_type": "episode",
|
"media_type": "episode",
|
||||||
"originally_available_at": "2016-04-24",
|
"originally_available_at": "2016-04-24",
|
||||||
"parent_media_index": "6",
|
"parent_media_index": "6",
|
||||||
"parent_rating_key": "153036",
|
"parent_rating_key": "153036",
|
||||||
"parent_thumb": "/library/metadata/153036/thumb/1462175062",
|
"parent_thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"rating": "7.8",
|
"rating": "7.8",
|
||||||
"rating_key": "153037",
|
"rating_key": "153037",
|
||||||
"section_id": "2",
|
"section_id": "2",
|
||||||
"studio": "HBO",
|
"studio": "HBO",
|
||||||
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
|
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
|
||||||
"tagline": "",
|
"tagline": "",
|
||||||
"thumb": "/library/metadata/153037/thumb/1462175060",
|
"thumb": "/library/metadata/153037/thumb/1462175060",
|
||||||
"title": "The Red Woman",
|
"title": "The Red Woman",
|
||||||
"updated_at": "1462175060",
|
"updated_at": "1462175060",
|
||||||
"writers": [
|
"writers": [
|
||||||
"David Benioff",
|
"David Benioff",
|
||||||
"D. B. Weiss"
|
"D. B. Weiss"
|
||||||
],
|
],
|
||||||
"year": "2016"
|
"year": "2016"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3108,21 +3208,21 @@ class WebInterface(object):
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
{"recently_added":
|
{"recently_added":
|
||||||
[{"added_at": "1461572396",
|
[{"added_at": "1461572396",
|
||||||
"grandparent_rating_key": "1219",
|
"grandparent_rating_key": "1219",
|
||||||
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
|
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
|
||||||
"grandparent_title": "Game of Thrones",
|
"grandparent_title": "Game of Thrones",
|
||||||
"library_name": "",
|
"library_name": "",
|
||||||
"media_index": "1",
|
"media_index": "1",
|
||||||
"media_type": "episode",
|
"media_type": "episode",
|
||||||
"parent_media_index": "6",
|
"parent_media_index": "6",
|
||||||
"parent_rating_key": "153036",
|
"parent_rating_key": "153036",
|
||||||
"parent_thumb": "/library/metadata/153036/thumb/1462175062",
|
"parent_thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"rating_key": "153037",
|
"rating_key": "153037",
|
||||||
"section_id": "2",
|
"section_id": "2",
|
||||||
"thumb": "/library/metadata/153037/thumb/1462175060",
|
"thumb": "/library/metadata/153037/thumb/1462175060",
|
||||||
"title": "The Red Woman",
|
"title": "The Red Woman",
|
||||||
"year": "2016"
|
"year": "2016"
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
|
@ -3312,60 +3412,60 @@ class WebInterface(object):
|
||||||
json:
|
json:
|
||||||
{"stream_count": 3,
|
{"stream_count": 3,
|
||||||
"session":
|
"session":
|
||||||
[{"art": "/library/metadata/1219/art/1462175063",
|
[{"art": "/library/metadata/1219/art/1462175063",
|
||||||
"aspect_ratio": "1.78",
|
"aspect_ratio": "1.78",
|
||||||
"audio_channels": "6",
|
"audio_channels": "6",
|
||||||
"audio_codec": "ac3",
|
"audio_codec": "ac3",
|
||||||
"audio_decision": "transcode",
|
"audio_decision": "transcode",
|
||||||
"bif_thumb": "/library/parts/274169/indexes/sd/",
|
"bif_thumb": "/library/parts/274169/indexes/sd/",
|
||||||
"bitrate": "10617",
|
"bitrate": "10617",
|
||||||
"container": "mkv",
|
"container": "mkv",
|
||||||
"content_rating": "TV-MA",
|
"content_rating": "TV-MA",
|
||||||
"duration": "2998290",
|
"duration": "2998290",
|
||||||
"friendly_name": "Mother of Dragons",
|
"friendly_name": "Mother of Dragons",
|
||||||
"grandparent_rating_key": "1219",
|
"grandparent_rating_key": "1219",
|
||||||
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
|
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
|
||||||
"grandparent_title": "Game of Thrones",
|
"grandparent_title": "Game of Thrones",
|
||||||
"height": "1078",
|
"height": "1078",
|
||||||
"indexes": 1,
|
"indexes": 1,
|
||||||
"ip_address": "xxx.xxx.xxx.xxx",
|
"ip_address": "xxx.xxx.xxx.xxx",
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"machine_id": "83f189w617623ccs6a1lqpby",
|
"machine_id": "83f189w617623ccs6a1lqpby",
|
||||||
"media_index": "1",
|
"media_index": "1",
|
||||||
"media_type": "episode",
|
"media_type": "episode",
|
||||||
"parent_media_index": "6",
|
"parent_media_index": "6",
|
||||||
"parent_rating_key": "153036",
|
"parent_rating_key": "153036",
|
||||||
"parent_thumb": "/library/metadata/153036/thumb/1462175062",
|
"parent_thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"platform": "Chrome",
|
"platform": "Chrome",
|
||||||
"player": "Plex Web (Chrome)",
|
"player": "Plex Web (Chrome)",
|
||||||
"progress_percent": "0",
|
"progress_percent": "0",
|
||||||
"rating_key": "153037",
|
"rating_key": "153037",
|
||||||
"section_id": "2",
|
"section_id": "2",
|
||||||
"session_key": "291",
|
"session_key": "291",
|
||||||
"state": "playing",
|
"state": "playing",
|
||||||
"throttled": "1",
|
"throttled": "1",
|
||||||
"thumb": "/library/metadata/153037/thumb/1462175060",
|
"thumb": "/library/metadata/153037/thumb/1462175060",
|
||||||
"title": "The Red Woman",
|
"title": "The Red Woman",
|
||||||
"transcode_audio_channels": "2",
|
"transcode_audio_channels": "2",
|
||||||
"transcode_audio_codec": "aac",
|
"transcode_audio_codec": "aac",
|
||||||
"transcode_container": "mkv",
|
"transcode_container": "mkv",
|
||||||
"transcode_height": "1078",
|
"transcode_height": "1078",
|
||||||
"transcode_key": "tiv5p524wcupe8nxegc26s9k9",
|
"transcode_key": "tiv5p524wcupe8nxegc26s9k9",
|
||||||
"transcode_progress": 2,
|
"transcode_progress": 2,
|
||||||
"transcode_protocol": "http",
|
"transcode_protocol": "http",
|
||||||
"transcode_speed": "0.0",
|
"transcode_speed": "0.0",
|
||||||
"transcode_video_codec": "h264",
|
"transcode_video_codec": "h264",
|
||||||
"transcode_width": "1920",
|
"transcode_width": "1920",
|
||||||
"user": "DanyKhaleesi69",
|
"user": "DanyKhaleesi69",
|
||||||
"user_id": 8008135,
|
"user_id": 8008135,
|
||||||
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
|
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
|
||||||
"video_codec": "h264",
|
"video_codec": "h264",
|
||||||
"video_decision": "copy",
|
"video_decision": "copy",
|
||||||
"video_framerate": "24p",
|
"video_framerate": "24p",
|
||||||
"video_resolution": "1080",
|
"video_resolution": "1080",
|
||||||
"view_offset": "",
|
"view_offset": "",
|
||||||
"width": "1920",
|
"width": "1920",
|
||||||
"year": "2016"
|
"year": "2016"
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
|
@ -3398,13 +3498,13 @@ class WebInterface(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
[{"art": "/:/resources/show-fanart.jpg",
|
[{"art": "/:/resources/show-fanart.jpg",
|
||||||
"child_count": "3745",
|
"child_count": "3745",
|
||||||
"count": "62",
|
"count": "62",
|
||||||
"parent_count": "240",
|
"parent_count": "240",
|
||||||
"section_id": "2",
|
"section_id": "2",
|
||||||
"section_name": "TV Shows",
|
"section_name": "TV Shows",
|
||||||
"section_type": "show",
|
"section_type": "show",
|
||||||
"thumb": "/:/resources/show.png"
|
"thumb": "/:/resources/show.png"
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
|
@ -3436,17 +3536,17 @@ class WebInterface(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
[{"email": "Jon.Snow.1337@CastleBlack.com",
|
[{"email": "Jon.Snow.1337@CastleBlack.com",
|
||||||
"filter_all": "",
|
"filter_all": "",
|
||||||
"filter_movies": "",
|
"filter_movies": "",
|
||||||
"filter_music": "",
|
"filter_music": "",
|
||||||
"filter_photos": "",
|
"filter_photos": "",
|
||||||
"filter_tv": "",
|
"filter_tv": "",
|
||||||
"is_allow_sync": null,
|
"is_allow_sync": null,
|
||||||
"is_home_user": "1",
|
"is_home_user": "1",
|
||||||
"is_restricted": "0",
|
"is_restricted": "0",
|
||||||
"thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
"thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
||||||
"user_id": "328871",
|
"user_id": "328871",
|
||||||
"username": "Jon Snow"
|
"username": "Jon Snow"
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
|
@ -3478,26 +3578,26 @@ class WebInterface(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
[{"content_type": "video",
|
[{"content_type": "video",
|
||||||
"device_name": "Tyrion's iPad",
|
"device_name": "Tyrion's iPad",
|
||||||
"failure": "",
|
"failure": "",
|
||||||
"friendly_name": "Tyrion Lannister",
|
"friendly_name": "Tyrion Lannister",
|
||||||
"item_complete_count": "0",
|
"item_complete_count": "0",
|
||||||
"item_count": "1",
|
"item_count": "1",
|
||||||
"item_downloaded_count": "0",
|
"item_downloaded_count": "0",
|
||||||
"item_downloaded_percent_complete": 0,
|
"item_downloaded_percent_complete": 0,
|
||||||
"metadata_type": "movie",
|
"metadata_type": "movie",
|
||||||
"music_bitrate": "192",
|
"music_bitrate": "192",
|
||||||
"photo_quality": "74",
|
"photo_quality": "74",
|
||||||
"platform": "iOS",
|
"platform": "iOS",
|
||||||
"rating_key": "154092",
|
"rating_key": "154092",
|
||||||
"root_title": "Deadpool",
|
"root_title": "Deadpool",
|
||||||
"state": "pending",
|
"state": "pending",
|
||||||
"sync_id": "11617019",
|
"sync_id": "11617019",
|
||||||
"title": "Deadpool",
|
"title": "Deadpool",
|
||||||
"total_size": "0",
|
"total_size": "0",
|
||||||
"user_id": "328871",
|
"user_id": "328871",
|
||||||
"username": "DrukenDwarfMan",
|
"username": "DrukenDwarfMan",
|
||||||
"video_quality": "60"
|
"video_quality": "60"
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
|
@ -3555,22 +3655,22 @@ class WebInterface(object):
|
||||||
{"stat_id": "top_tv",
|
{"stat_id": "top_tv",
|
||||||
"stat_type": "total_plays",
|
"stat_type": "total_plays",
|
||||||
"rows":
|
"rows":
|
||||||
[{"content_rating": "TV-MA",
|
[{"content_rating": "TV-MA",
|
||||||
"friendly_name": "",
|
"friendly_name": "",
|
||||||
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
|
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"last_play": 1462380698,
|
"last_play": 1462380698,
|
||||||
"media_type": "episode",
|
"media_type": "episode",
|
||||||
"platform": "",
|
"platform": "",
|
||||||
"platform_type": "",
|
"platform_type": "",
|
||||||
"rating_key": 1219,
|
"rating_key": 1219,
|
||||||
"row_id": 1116,
|
"row_id": 1116,
|
||||||
"section_id": 2,
|
"section_id": 2,
|
||||||
"thumb": "",
|
"thumb": "",
|
||||||
"title": "Game of Thrones",
|
"title": "Game of Thrones",
|
||||||
"total_duration": 213302,
|
"total_duration": 213302,
|
||||||
"total_plays": 69,
|
"total_plays": 69,
|
||||||
"user": "",
|
"user": "",
|
||||||
"users_watched": ""
|
"users_watched": ""
|
||||||
},
|
},
|
||||||
{...},
|
{...},
|
||||||
|
@ -3678,4 +3778,4 @@ class WebInterface(object):
|
||||||
def check_pms_updater(self):
|
def check_pms_updater(self):
|
||||||
pms_connect = pmsconnect.PmsConnect()
|
pms_connect = pmsconnect.PmsConnect()
|
||||||
result = pms_connect.get_update_staus()
|
result = pms_connect.get_update_staus()
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -75,7 +75,7 @@ def initialize(options):
|
||||||
plexpy.HTTP_ROOT = options['http_root'] = '/'
|
plexpy.HTTP_ROOT = options['http_root'] = '/'
|
||||||
else:
|
else:
|
||||||
plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/'
|
plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/'
|
||||||
|
|
||||||
cherrypy.config.update(options_dict)
|
cherrypy.config.update(options_dict)
|
||||||
|
|
||||||
conf = {
|
conf = {
|
||||||
|
@ -178,6 +178,17 @@ def initialize(options):
|
||||||
'tools.auth.on': False,
|
'tools.auth.on': False,
|
||||||
'tools.sessions.on': False
|
'tools.sessions.on': False
|
||||||
},
|
},
|
||||||
|
'/pms_image_proxy': {
|
||||||
|
'tools.staticdir.on': True,
|
||||||
|
'tools.staticdir.dir': os.path.join(plexpy.CONFIG.CACHE_DIR, 'images'),
|
||||||
|
'tools.caching.on': True,
|
||||||
|
'tools.caching.force': True,
|
||||||
|
'tools.caching.delay': 0,
|
||||||
|
'tools.expires.on': True,
|
||||||
|
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
|
||||||
|
'tools.auth.on': False,
|
||||||
|
'tools.sessions.on': False
|
||||||
|
},
|
||||||
'/favicon.ico': {
|
'/favicon.ico': {
|
||||||
'tools.staticfile.on': True,
|
'tools.staticfile.on': True,
|
||||||
'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon.ico')),
|
'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon.ico')),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue