Compare commits
22 Commits
v2.1.19-be
...
v2.1.20
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4944ce1ca0 | ||
![]() |
f04873446a | ||
![]() |
505b6b616e | ||
![]() |
87dd43d699 | ||
![]() |
a48ebef9ae | ||
![]() |
e40483525b | ||
![]() |
ebc563fd26 | ||
![]() |
5bb3e189fe | ||
![]() |
ed08df5224 | ||
![]() |
ae2584b6f6 | ||
![]() |
f0e2355231 | ||
![]() |
878c48b491 | ||
![]() |
ecfbb4de9b | ||
![]() |
731af75c54 | ||
![]() |
9817da6012 | ||
![]() |
dd3f75f154 | ||
![]() |
1eee03fa8f | ||
![]() |
02af6c4e6c | ||
![]() |
b8a9c4f5b7 | ||
![]() |
0b227dc69e | ||
![]() |
8228018dd0 | ||
![]() |
5c3086a049 |
27
API.md
27
API.md
@@ -1,9 +1,15 @@
|
|||||||
# API Reference
|
# API Reference
|
||||||
|
|
||||||
The API is still pretty new and needs some serious cleaning up on the backend but should be reasonably functional. There are no error codes yet.
|
|
||||||
|
|
||||||
## General structure
|
## General structure
|
||||||
The API endpoint is `http://ip:port + HTTP_ROOT + /api/v2?apikey=$apikey&cmd=$command`
|
The API endpoint is
|
||||||
|
```
|
||||||
|
http://IP_ADDRESS:PORT + [/HTTP_ROOT] + /api/v2?apikey=$apikey&cmd=$command
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
http://localhost:8181/api/v2?apikey=66198313a092496b8a725867d2223b5f&cmd=get_metadata&rating_key=153037
|
||||||
|
```
|
||||||
|
|
||||||
Response example (default `json`)
|
Response example (default `json`)
|
||||||
```
|
```
|
||||||
@@ -354,7 +360,8 @@ Required parameters:
|
|||||||
None
|
None
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
session_key (int): Session key for the session info to return, OR
|
||||||
|
session_id (str): Session ID for the session info to return
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
@@ -1140,7 +1147,8 @@ Returns:
|
|||||||
"video_language_code": "",
|
"video_language_code": "",
|
||||||
"video_profile": "high",
|
"video_profile": "high",
|
||||||
"video_ref_frames": "4",
|
"video_ref_frames": "4",
|
||||||
"video_width": "1920"
|
"video_width": "1920",
|
||||||
|
"selected": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"audio_bitrate": "384",
|
"audio_bitrate": "384",
|
||||||
@@ -1153,7 +1161,8 @@ Returns:
|
|||||||
"audio_profile": "",
|
"audio_profile": "",
|
||||||
"audio_sample_rate": "48000",
|
"audio_sample_rate": "48000",
|
||||||
"id": "511664",
|
"id": "511664",
|
||||||
"type": "2"
|
"type": "2",
|
||||||
|
"selected": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "511953",
|
"id": "511953",
|
||||||
@@ -1164,7 +1173,8 @@ Returns:
|
|||||||
"subtitle_language": "English",
|
"subtitle_language": "English",
|
||||||
"subtitle_language_code": "eng",
|
"subtitle_language_code": "eng",
|
||||||
"subtitle_location": "external",
|
"subtitle_location": "external",
|
||||||
"type": "3"
|
"type": "3",
|
||||||
|
"selected": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2435,7 +2445,7 @@ Required parameters:
|
|||||||
body (str): The body of the message
|
body (str): The body of the message
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
script_args (str): The arguments for script notifications
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
@@ -2496,6 +2506,7 @@ Optional parameters:
|
|||||||
img_format (str): png
|
img_format (str): png
|
||||||
fallback (str): "poster", "cover", "art"
|
fallback (str): "poster", "cover", "art"
|
||||||
refresh (bool): True or False whether to refresh the image cache
|
refresh (bool): True or False whether to refresh the image cache
|
||||||
|
return_hash (bool): True or False to return the self-hosted image hash instead of the image
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
|
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.1.20 (2018-09-05)
|
||||||
|
* No changes.
|
||||||
|
|
||||||
|
|
||||||
|
## v2.1.20-beta (2018-09-02)
|
||||||
|
|
||||||
|
* Monitoring:
|
||||||
|
* Fix: Fetch messing season info when "Hide Seasons" is enabled for a show.
|
||||||
|
* Fix: Video and Audio details sometimes missing on activity cards.
|
||||||
|
* Notifications:
|
||||||
|
* New: Added UTC timestamp to notification parameters. (Thanks @samwiseg00)
|
||||||
|
* New: Added TAUTULLI_PUBLIC_URL to script environment variables. (Thanks @samwiseg00)
|
||||||
|
* UI:
|
||||||
|
* Change: Automatically redirect '/' to HTTP root if enabled.
|
||||||
|
* API:
|
||||||
|
* New: Added return_hash parameter to pms_image_proxy command.
|
||||||
|
* New: Added session_id parameter to get_activity command.
|
||||||
|
* Other:
|
||||||
|
* Change: Linux systemd startup script to use the "tautulli" group permission. (Thanks @samwiseg00)
|
||||||
|
|
||||||
|
|
||||||
## v2.1.19-beta (2018-08-19)
|
## v2.1.19-beta (2018-08-19)
|
||||||
|
|
||||||
* Notifications:
|
* Notifications:
|
||||||
|
@@ -439,7 +439,7 @@
|
|||||||
$('#transcode_container-' + key).html(transcode_container);
|
$('#transcode_container-' + key).html(transcode_container);
|
||||||
|
|
||||||
var video_decision = '';
|
var video_decision = '';
|
||||||
if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.video_decision !== '') {
|
if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.stream_video_decision) {
|
||||||
var v_res= '';
|
var v_res= '';
|
||||||
switch (s.video_resolution.toLowerCase()) {
|
switch (s.video_resolution.toLowerCase()) {
|
||||||
case 'sd':
|
case 'sd':
|
||||||
@@ -477,7 +477,7 @@
|
|||||||
$('#video_decision-' + key).html(video_decision);
|
$('#video_decision-' + key).html(video_decision);
|
||||||
|
|
||||||
var audio_decision = '';
|
var audio_decision = '';
|
||||||
if (['movie', 'episode', 'clip', 'track'].indexOf(s.media_type) > -1 && s.audio_decision) {
|
if (['movie', 'episode', 'clip', 'track'].indexOf(s.media_type) > -1 && s.stream_audio_decision) {
|
||||||
var a_codec = (s.audio_codec === 'truehd') ? 'TrueHD' : s.audio_codec.toUpperCase();
|
var a_codec = (s.audio_codec === 'truehd') ? 'TrueHD' : s.audio_codec.toUpperCase();
|
||||||
var sa_codec = (s.stream_audio_codec === 'truehd') ? 'TrueHD' : s.stream_audio_codec.toUpperCase();
|
var sa_codec = (s.stream_audio_codec === 'truehd') ? 'TrueHD' : s.stream_audio_codec.toUpperCase();
|
||||||
if (s.stream_audio_decision === 'transcode') {
|
if (s.stream_audio_decision === 'transcode') {
|
||||||
|
@@ -141,7 +141,7 @@ function doAjaxCall(url, elem, reload, form, showMsg, callback) {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
data: dataString,
|
data: dataString,
|
||||||
type: 'post',
|
type: 'POST',
|
||||||
beforeSend: function (jqXHR, settings) {
|
beforeSend: function (jqXHR, settings) {
|
||||||
// Start loader etc.
|
// Start loader etc.
|
||||||
feedback.prepend(loader);
|
feedback.prepend(loader);
|
||||||
|
@@ -12,7 +12,6 @@
|
|||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="author" content="">
|
<meta name="author" content="">
|
||||||
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||||
<link href="${http_root}css/pnotify.custom.min.css" rel="stylesheet" />
|
|
||||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||||
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
||||||
<link href="${http_root}css/font-awesome.all.min.css" rel="stylesheet">
|
<link href="${http_root}css/font-awesome.all.min.css" rel="stylesheet">
|
||||||
|
@@ -779,6 +779,7 @@
|
|||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'get_notify_text_preview',
|
url: 'get_notify_text_preview',
|
||||||
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
notify_action: action,
|
notify_action: action,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
#
|
#
|
||||||
# - To create this user and give it ownership of the Tautulli directory:
|
# - To create this user and give it ownership of the Tautulli directory:
|
||||||
# sudo adduser --system --no-create-home tautulli
|
# sudo adduser --system --no-create-home tautulli
|
||||||
# sudo chown tautulli:nogroup -R /opt/Tautulli
|
# sudo chown tautulli:tautulli -R /opt/Tautulli
|
||||||
#
|
#
|
||||||
# - Adjust ExecStart= to point to:
|
# - Adjust ExecStart= to point to:
|
||||||
# 1. Your Tautulli executable
|
# 1. Your Tautulli executable
|
||||||
@@ -51,7 +51,7 @@ ExecStart=/opt/Tautulli/Tautulli.py --config /opt/Tautulli/config.ini --datadir
|
|||||||
GuessMainPID=no
|
GuessMainPID=no
|
||||||
Type=forking
|
Type=forking
|
||||||
User=tautulli
|
User=tautulli
|
||||||
Group=nogroup
|
Group=tautlli
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@@ -167,6 +167,15 @@ def initialize(config_file):
|
|||||||
logger.info(u"Python {}".format(
|
logger.info(u"Python {}".format(
|
||||||
sys.version
|
sys.version
|
||||||
))
|
))
|
||||||
|
logger.info(u"Program Dir: {}".format(
|
||||||
|
PROG_DIR
|
||||||
|
))
|
||||||
|
logger.info(u"Config File: {}".format(
|
||||||
|
CONFIG_FILE
|
||||||
|
))
|
||||||
|
logger.info(u"Database File: {}".format(
|
||||||
|
DB_FILE
|
||||||
|
))
|
||||||
|
|
||||||
if not CONFIG.BACKUP_DIR:
|
if not CONFIG.BACKUP_DIR:
|
||||||
CONFIG.BACKUP_DIR = os.path.join(DATA_DIR, 'backups')
|
CONFIG.BACKUP_DIR = os.path.join(DATA_DIR, 'backups')
|
||||||
|
@@ -413,7 +413,7 @@ class API2:
|
|||||||
body (str): The body of the message
|
body (str): The body of the message
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
script_args (str): The arguments for script notifications
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
@@ -496,10 +496,16 @@ class API2:
|
|||||||
""" Tries to make a API.md to simplify the api docs. """
|
""" Tries to make a API.md to simplify the api docs. """
|
||||||
|
|
||||||
head = '''# API Reference\n
|
head = '''# API Reference\n
|
||||||
The API is still pretty new and needs some serious cleaning up on the backend but should be reasonably functional. There are no error codes yet.
|
|
||||||
|
|
||||||
## General structure
|
## General structure
|
||||||
The API endpoint is `http://ip:port + HTTP_ROOT + /api/v2?apikey=$apikey&cmd=$command`
|
The API endpoint is
|
||||||
|
```
|
||||||
|
http://IP_ADDRESS:PORT + [/HTTP_ROOT] + /api/v2?apikey=$apikey&cmd=$command
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
http://localhost:8181/api/v2?apikey=66198313a092496b8a725867d2223b5f&cmd=get_metadata&rating_key=153037
|
||||||
|
```
|
||||||
|
|
||||||
Response example (default `json`)
|
Response example (default `json`)
|
||||||
```
|
```
|
||||||
@@ -596,6 +602,7 @@ General optional parameters:
|
|||||||
return
|
return
|
||||||
|
|
||||||
elif self._api_cmd == 'pms_image_proxy':
|
elif self._api_cmd == 'pms_image_proxy':
|
||||||
|
if 'return_hash' not in self._api_kwargs:
|
||||||
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
|
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
|
||||||
return out['response']['data']
|
return out['response']['data']
|
||||||
|
|
||||||
|
@@ -321,6 +321,7 @@ NOTIFICATION_PARAMETERS = [
|
|||||||
{'name': 'Datestamp', 'type': 'str', 'value': 'datestamp', 'description': 'The date (in date format) when the notification is triggered.'},
|
{'name': 'Datestamp', 'type': 'str', 'value': 'datestamp', 'description': 'The date (in date format) when the notification is triggered.'},
|
||||||
{'name': 'Timestamp', 'type': 'str', 'value': 'timestamp', 'description': 'The time (in time format) when the notification is triggered.'},
|
{'name': 'Timestamp', 'type': 'str', 'value': 'timestamp', 'description': 'The time (in time format) when the notification is triggered.'},
|
||||||
{'name': 'Unix Time', 'type': 'int', 'value': 'unixtime', 'description': 'The unix timestamp when the notification is triggered.'},
|
{'name': 'Unix Time', 'type': 'int', 'value': 'unixtime', 'description': 'The unix timestamp when the notification is triggered.'},
|
||||||
|
{'name': 'UTC Time', 'type': 'int', 'value': 'utctime', 'description': 'The UTC timestamp in ISO format when the notification is triggered.'},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -33,6 +33,7 @@ import maxminddb
|
|||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shlex
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@@ -202,17 +203,22 @@ def convert_seconds_to_minutes(s):
|
|||||||
def today():
|
def today():
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
yyyymmdd = datetime.date.isoformat(today)
|
yyyymmdd = datetime.date.isoformat(today)
|
||||||
|
|
||||||
return yyyymmdd
|
return yyyymmdd
|
||||||
|
|
||||||
|
|
||||||
def now():
|
def now():
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
return now.strftime("%Y-%m-%d %H:%M:%S")
|
return now.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
|
||||||
def utc_now_iso():
|
def utc_now_iso():
|
||||||
utcnow = datetime.datetime.utcnow()
|
utcnow = datetime.datetime.utcnow()
|
||||||
|
|
||||||
return utcnow.isoformat()
|
return utcnow.isoformat()
|
||||||
|
|
||||||
|
|
||||||
def human_duration(s, sig='dhms'):
|
def human_duration(s, sig='dhms'):
|
||||||
|
|
||||||
hd = ''
|
hd = ''
|
||||||
@@ -1132,3 +1138,12 @@ def traverse_map(obj, func):
|
|||||||
new_obj = func(obj)
|
new_obj = func(obj)
|
||||||
|
|
||||||
return new_obj
|
return new_obj
|
||||||
|
|
||||||
|
|
||||||
|
def split_args(args=None):
|
||||||
|
if isinstance(args, list):
|
||||||
|
return args
|
||||||
|
elif isinstance(args, basestring):
|
||||||
|
return [arg.decode(plexpy.SYS_ENCODING, 'ignore')
|
||||||
|
for arg in shlex.split(args.encode(plexpy.SYS_ENCODING, 'ignore'))]
|
||||||
|
return []
|
||||||
|
@@ -23,7 +23,6 @@ import json
|
|||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -337,12 +336,7 @@ def notify(notifier_id=None, notify_action=None, stream_data=None, timeline_data
|
|||||||
if notify_action in ('test', 'api'):
|
if notify_action in ('test', 'api'):
|
||||||
subject = kwargs.pop('subject', 'Tautulli')
|
subject = kwargs.pop('subject', 'Tautulli')
|
||||||
body = kwargs.pop('body', 'Test Notification')
|
body = kwargs.pop('body', 'Test Notification')
|
||||||
script_args = kwargs.pop('script_args', [])
|
script_args = helpers.split_args(kwargs.pop('script_args', []))
|
||||||
|
|
||||||
if script_args and isinstance(script_args, basestring):
|
|
||||||
# Attemps to format test script args for the user
|
|
||||||
script_args = [arg.decode(plexpy.SYS_ENCODING, 'ignore')
|
|
||||||
for arg in shlex.split(script_args.encode(plexpy.SYS_ENCODING, 'ignore'))]
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Get the subject and body strings
|
# Get the subject and body strings
|
||||||
@@ -749,6 +743,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||||||
'datestamp': now.format(date_format),
|
'datestamp': now.format(date_format),
|
||||||
'timestamp': now.format(time_format),
|
'timestamp': now.format(time_format),
|
||||||
'unixtime': int(time.time()),
|
'unixtime': int(time.time()),
|
||||||
|
'utctime': helpers.utc_now_iso(),
|
||||||
# Stream parameters
|
# Stream parameters
|
||||||
'streams': stream_count,
|
'streams': stream_count,
|
||||||
'user_streams': user_stream_count,
|
'user_streams': user_stream_count,
|
||||||
@@ -969,6 +964,7 @@ def build_server_notify_params(notify_action=None, **kwargs):
|
|||||||
'datestamp': now.format(date_format),
|
'datestamp': now.format(date_format),
|
||||||
'timestamp': now.format(time_format),
|
'timestamp': now.format(time_format),
|
||||||
'unixtime': int(time.time()),
|
'unixtime': int(time.time()),
|
||||||
|
'utctime': helpers.utc_now_iso(),
|
||||||
# Plex Media Server update parameters
|
# Plex Media Server update parameters
|
||||||
'update_version': pms_download_info['version'],
|
'update_version': pms_download_info['version'],
|
||||||
'update_url': pms_download_info['download_url'],
|
'update_url': pms_download_info['download_url'],
|
||||||
@@ -1048,8 +1044,7 @@ def build_notify_text(subject='', body='', notify_action=None, parameters=None,
|
|||||||
|
|
||||||
if agent_id == 15:
|
if agent_id == 15:
|
||||||
try:
|
try:
|
||||||
script_args = [custom_formatter.format(arg.decode(plexpy.SYS_ENCODING, 'ignore'), **parameters)
|
script_args = [custom_formatter.format(arg, **parameters) for arg in helpers.split_args(subject)]
|
||||||
for arg in shlex.split(subject.encode(plexpy.SYS_ENCODING, 'ignore'))]
|
|
||||||
except LookupError as e:
|
except LookupError as e:
|
||||||
logger.error(u"Tautulli NotificationHandler :: Unable to parse parameter %s in script argument. Using fallback." % e)
|
logger.error(u"Tautulli NotificationHandler :: Unable to parse parameter %s in script argument. Using fallback." % e)
|
||||||
script_args = []
|
script_args = []
|
||||||
@@ -1254,7 +1249,8 @@ def get_img_info(img=None, rating_key=None, title='', width=1000, height=1500,
|
|||||||
|
|
||||||
|
|
||||||
def set_hash_image_info(img=None, rating_key=None, width=750, height=1000,
|
def set_hash_image_info(img=None, rating_key=None, width=750, height=1000,
|
||||||
opacity=100, background='000000', blur=0, fallback=None):
|
opacity=100, background='000000', blur=0, fallback=None,
|
||||||
|
add_to_db=True):
|
||||||
if not rating_key and not img:
|
if not rating_key and not img:
|
||||||
return fallback
|
return fallback
|
||||||
|
|
||||||
@@ -1272,6 +1268,7 @@ def set_hash_image_info(img=None, rating_key=None, width=750, height=1000,
|
|||||||
plexpy.CONFIG.PMS_UUID, img, rating_key, width, height, opacity, background, blur, fallback)
|
plexpy.CONFIG.PMS_UUID, img, rating_key, width, height, opacity, background, blur, fallback)
|
||||||
img_hash = hashlib.sha256(img_string).hexdigest()
|
img_hash = hashlib.sha256(img_string).hexdigest()
|
||||||
|
|
||||||
|
if add_to_db:
|
||||||
keys = {'img_hash': img_hash}
|
keys = {'img_hash': img_hash}
|
||||||
values = {'img': img,
|
values = {'img': img,
|
||||||
'rating_key': rating_key,
|
'rating_key': rating_key,
|
||||||
|
@@ -795,6 +795,7 @@ class Notifier(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def make_request(self, url, method='POST', **kwargs):
|
def make_request(self, url, method='POST', **kwargs):
|
||||||
|
logger.info(u"Tautulli Notifiers :: Sending {name} notification...".format(name=self.NAME))
|
||||||
response, err_msg, req_msg = request.request_response2(url, method, **kwargs)
|
response, err_msg, req_msg = request.request_response2(url, method, **kwargs)
|
||||||
|
|
||||||
if response and not err_msg:
|
if response and not err_msg:
|
||||||
@@ -1145,7 +1146,7 @@ class DISCORD(Notifier):
|
|||||||
|
|
||||||
# Build Discord post attachment
|
# Build Discord post attachment
|
||||||
attachment = {'title': title,
|
attachment = {'title': title,
|
||||||
'timestamp': helpers.utc_now_iso()
|
'timestamp': pretty_metadata.parameters['utctime']
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.config['color']:
|
if self.config['color']:
|
||||||
@@ -3014,6 +3015,7 @@ class SCRIPTS(Notifier):
|
|||||||
'PLEX_URL': plexpy.CONFIG.PMS_URL,
|
'PLEX_URL': plexpy.CONFIG.PMS_URL,
|
||||||
'PLEX_TOKEN': plexpy.CONFIG.PMS_TOKEN,
|
'PLEX_TOKEN': plexpy.CONFIG.PMS_TOKEN,
|
||||||
'TAUTULLI_URL': helpers.get_plexpy_url(hostname='localhost'),
|
'TAUTULLI_URL': helpers.get_plexpy_url(hostname='localhost'),
|
||||||
|
'TAUTULLI_PUBLIC_URL': plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT,
|
||||||
'TAUTULLI_APIKEY': plexpy.CONFIG.API_KEY,
|
'TAUTULLI_APIKEY': plexpy.CONFIG.API_KEY,
|
||||||
'TAUTULLI_ENCODING': plexpy.SYS_ENCODING
|
'TAUTULLI_ENCODING': plexpy.SYS_ENCODING
|
||||||
})
|
})
|
||||||
@@ -3076,7 +3078,7 @@ class SCRIPTS(Notifier):
|
|||||||
logger.error(u"Tautulli Notifiers :: No script folder specified.")
|
logger.error(u"Tautulli Notifiers :: No script folder specified.")
|
||||||
return
|
return
|
||||||
|
|
||||||
script_args = kwargs.get('script_args', [])
|
script_args = helpers.split_args(kwargs.get('script_args', subject))
|
||||||
|
|
||||||
logger.debug(u"Tautulli Notifiers :: Trying to run notify script, action: %s, arguments: %s"
|
logger.debug(u"Tautulli Notifiers :: Trying to run notify script, action: %s, arguments: %s"
|
||||||
% (action, script_args))
|
% (action, script_args))
|
||||||
|
@@ -809,11 +809,27 @@ class PmsConnect(object):
|
|||||||
elif metadata_type == 'episode':
|
elif metadata_type == 'episode':
|
||||||
grandparent_rating_key = helpers.get_xml_attr(metadata_main, 'grandparentRatingKey')
|
grandparent_rating_key = helpers.get_xml_attr(metadata_main, 'grandparentRatingKey')
|
||||||
show_details = self.get_metadata_details(grandparent_rating_key)
|
show_details = self.get_metadata_details(grandparent_rating_key)
|
||||||
|
|
||||||
|
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
|
||||||
|
parent_media_index = helpers.get_xml_attr(metadata_main, 'parentIndex')
|
||||||
|
parent_thumb = helpers.get_xml_attr(metadata_main, 'parentThumb')
|
||||||
|
|
||||||
|
if not parent_rating_key:
|
||||||
|
# Try getting the parent_rating_key from the parent_thumb
|
||||||
|
if parent_thumb.startswith('/library/metadata/'):
|
||||||
|
parent_rating_key = parent_thumb.split('/')[3]
|
||||||
|
|
||||||
|
# Try getting the parent_rating_key from the grandparent's children
|
||||||
|
if not parent_rating_key:
|
||||||
|
children_list = self.get_item_children(grandparent_rating_key)
|
||||||
|
parent_rating_key = next((c['rating_key'] for c in children_list['children_list']
|
||||||
|
if c['media_index'] == parent_media_index), '')
|
||||||
|
|
||||||
metadata = {'media_type': metadata_type,
|
metadata = {'media_type': metadata_type,
|
||||||
'section_id': section_id,
|
'section_id': section_id,
|
||||||
'library_name': library_name,
|
'library_name': library_name,
|
||||||
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
|
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
|
||||||
'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
|
'parent_rating_key': parent_rating_key,
|
||||||
'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
|
'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
|
||||||
'title': helpers.get_xml_attr(metadata_main, 'title'),
|
'title': helpers.get_xml_attr(metadata_main, 'title'),
|
||||||
'parent_title': 'Season %s' % helpers.get_xml_attr(metadata_main, 'parentIndex'),
|
'parent_title': 'Season %s' % helpers.get_xml_attr(metadata_main, 'parentIndex'),
|
||||||
@@ -821,7 +837,7 @@ class PmsConnect(object):
|
|||||||
'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
|
'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
|
||||||
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
|
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
|
||||||
'media_index': helpers.get_xml_attr(metadata_main, 'index'),
|
'media_index': helpers.get_xml_attr(metadata_main, 'index'),
|
||||||
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
|
'parent_media_index': parent_media_index,
|
||||||
'studio': show_details['studio'],
|
'studio': show_details['studio'],
|
||||||
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
|
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
|
||||||
'summary': helpers.get_xml_attr(metadata_main, 'summary'),
|
'summary': helpers.get_xml_attr(metadata_main, 'summary'),
|
||||||
@@ -834,7 +850,7 @@ class PmsConnect(object):
|
|||||||
'duration': helpers.get_xml_attr(metadata_main, 'duration'),
|
'duration': helpers.get_xml_attr(metadata_main, 'duration'),
|
||||||
'year': helpers.get_xml_attr(metadata_main, 'year'),
|
'year': helpers.get_xml_attr(metadata_main, 'year'),
|
||||||
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
|
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
|
||||||
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
|
'parent_thumb': parent_thumb,
|
||||||
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
|
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
|
||||||
'art': helpers.get_xml_attr(metadata_main, 'art'),
|
'art': helpers.get_xml_attr(metadata_main, 'art'),
|
||||||
'banner': show_details['banner'],
|
'banner': show_details['banner'],
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
PLEXPY_BRANCH = "beta"
|
PLEXPY_BRANCH = "master"
|
||||||
PLEXPY_RELEASE_VERSION = "v2.1.19-beta"
|
PLEXPY_RELEASE_VERSION = "v2.1.20"
|
||||||
|
@@ -4031,7 +4031,7 @@ class WebInterface(object):
|
|||||||
return self.real_pms_image_proxy(**kwargs)
|
return self.real_pms_image_proxy(**kwargs)
|
||||||
|
|
||||||
@addtoapi('pms_image_proxy')
|
@addtoapi('pms_image_proxy')
|
||||||
def real_pms_image_proxy(self, img='', rating_key=None, width=0, height=0,
|
def real_pms_image_proxy(self, img=None, rating_key=None, width=750, height=1000,
|
||||||
opacity=100, background='000000', blur=0, img_format='png',
|
opacity=100, background='000000', blur=0, img_format='png',
|
||||||
fallback=None, refresh=False, clip=False, **kwargs):
|
fallback=None, refresh=False, clip=False, **kwargs):
|
||||||
""" Gets an image from the PMS and saves it to the image cache directory.
|
""" Gets an image from the PMS and saves it to the image cache directory.
|
||||||
@@ -4051,6 +4051,7 @@ class WebInterface(object):
|
|||||||
img_format (str): png
|
img_format (str): png
|
||||||
fallback (str): "poster", "cover", "art"
|
fallback (str): "poster", "cover", "art"
|
||||||
refresh (bool): True or False whether to refresh the image cache
|
refresh (bool): True or False whether to refresh the image cache
|
||||||
|
return_hash (bool): True or False to return the self-hosted image hash instead of the image
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
@@ -4060,6 +4061,8 @@ class WebInterface(object):
|
|||||||
logger.warn('No image input received.')
|
logger.warn('No image input received.')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
return_hash = (kwargs.get('return_hash') == 'true')
|
||||||
|
|
||||||
if rating_key and not img:
|
if rating_key and not img:
|
||||||
if fallback == 'art':
|
if fallback == 'art':
|
||||||
img = '/library/metadata/{}/art'.format(rating_key)
|
img = '/library/metadata/{}/art'.format(rating_key)
|
||||||
@@ -4070,9 +4073,13 @@ class WebInterface(object):
|
|||||||
img = '/'.join(img_split[:5])
|
img = '/'.join(img_split[:5])
|
||||||
rating_key = rating_key or img_split[3]
|
rating_key = rating_key or img_split[3]
|
||||||
|
|
||||||
img_string = '{}.{}.{}.{}.{}.{}.{}.{}'.format(
|
img_hash = notification_handler.set_hash_image_info(
|
||||||
plexpy.CONFIG.PMS_UUID, img, rating_key, width, height, opacity, background, blur, fallback)
|
img=img, rating_key=rating_key, width=width, height=height,
|
||||||
img_hash = hashlib.sha256(img_string).hexdigest()
|
opacity=opacity, background=background, blur=blur, fallback=fallback,
|
||||||
|
add_to_db=return_hash)
|
||||||
|
|
||||||
|
if return_hash:
|
||||||
|
return {'img_hash': img_hash}
|
||||||
|
|
||||||
fp = '{}.{}'.format(img_hash, img_format) # we want to be able to preview the thumbs
|
fp = '{}.{}'.format(img_hash, img_format) # we want to be able to preview the thumbs
|
||||||
c_dir = os.path.join(plexpy.CONFIG.CACHE_DIR, 'images')
|
c_dir = os.path.join(plexpy.CONFIG.CACHE_DIR, 'images')
|
||||||
@@ -4899,7 +4906,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_activity(self, session_key=None, **kwargs):
|
def get_activity(self, session_key=None, session_id=None, **kwargs):
|
||||||
""" Get the current activity on the PMS.
|
""" Get the current activity on the PMS.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -4907,7 +4914,8 @@ class WebInterface(object):
|
|||||||
None
|
None
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
session_key (int): Session key for the session info to return, OR
|
||||||
|
session_id (str): Session ID for the session info to return
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
@@ -5138,6 +5146,8 @@ class WebInterface(object):
|
|||||||
if result:
|
if result:
|
||||||
if session_key:
|
if session_key:
|
||||||
return next((s for s in result['sessions'] if s['session_key'] == session_key), {})
|
return next((s for s in result['sessions'] if s['session_key'] == session_key), {})
|
||||||
|
if session_id:
|
||||||
|
return next((s for s in result['sessions'] if s['session_id'] == session_id), {})
|
||||||
|
|
||||||
counts = {'stream_count_direct_play': 0,
|
counts = {'stream_count_direct_play': 0,
|
||||||
'stream_count_direct_stream': 0,
|
'stream_count_direct_stream': 0,
|
||||||
|
@@ -202,6 +202,8 @@ def initialize(options):
|
|||||||
# Prevent time-outs
|
# Prevent time-outs
|
||||||
cherrypy.engine.timeout_monitor.unsubscribe()
|
cherrypy.engine.timeout_monitor.unsubscribe()
|
||||||
cherrypy.tree.mount(WebInterface(), options['http_root'], config=conf)
|
cherrypy.tree.mount(WebInterface(), options['http_root'], config=conf)
|
||||||
|
if plexpy.HTTP_ROOT != '/':
|
||||||
|
cherrypy.tree.mount(BaseRedirect(), '/')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(u"Tautulli WebStart :: Starting Tautulli web server on %s://%s:%d%s", protocol,
|
logger.info(u"Tautulli WebStart :: Starting Tautulli web server on %s://%s:%d%s", protocol,
|
||||||
@@ -218,3 +220,9 @@ def initialize(options):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
cherrypy.server.wait()
|
cherrypy.server.wait()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseRedirect(object):
|
||||||
|
@cherrypy.expose
|
||||||
|
def index(self):
|
||||||
|
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
|
||||||
|
Reference in New Issue
Block a user