Compare commits

..

26 Commits

Author SHA1 Message Date
JonnyWong16
644fea6665 v2.0.12-beta 2018-01-07 23:44:18 -08:00
JonnyWong16
a1349ff8a6 Add css for selectize to match input boxes 2018-01-07 23:37:18 -08:00
JonnyWong16
71c20002b8 Update build notify text error message 2018-01-07 18:45:55 -08:00
JonnyWong16
157af84226 Only update the database sessions every 60 seconds while playing 2018-01-07 17:10:33 -08:00
JonnyWong16
9b4536f132 Move webserver notify to API 2018-01-07 14:46:01 -08:00
JonnyWong16
29ab470e42 Make metadata cache an advanced config option 2018-01-07 10:01:17 -08:00
JonnyWong16
c67fa480a7 Make condition logic optional
* Implicit "and" between all conditions if logic is blank
2018-01-07 09:42:57 -08:00
JonnyWong16
0a1a691c73 Fix Plex URL notification parameter 2018-01-07 08:28:06 -08:00
JonnyWong16
48588f23bf Add LAN/WAN bandwidth to activity header 2018-01-06 23:06:21 -08:00
JonnyWong16
cf14fbc3f0 v2.0.11-beta 2018-01-05 21:50:35 -08:00
JonnyWong16
e471d5207d Remove experimental tag from calculate file sizes 2018-01-05 21:50:24 -08:00
JonnyWong16
5722a52082 Fix None values in stream data for pre v2 history 2018-01-05 21:37:54 -08:00
JonnyWong16
08c32e875e Fix login using hashed password 2018-01-05 21:01:32 -08:00
JonnyWong16
7d3ee3afb3 Fix recently added show title 2018-01-05 21:01:10 -08:00
JonnyWong16
def8600f5c Reload notify params from raw stream info 2018-01-05 14:22:20 -08:00
JonnyWong16
74a68f3c7d v2.0.10-beta 2018-01-04 19:55:13 -08:00
JonnyWong16
64c9247dd1 Remove library/user notification toggles
* Filter out notifications using custom conditions
2018-01-04 19:39:16 -08:00
JonnyWong16
1bfcd34247 Some formatting for common.py 2018-01-04 13:40:34 -08:00
JonnyWong16
19864e97e6 Fix media type in collection header 2018-01-04 13:40:34 -08:00
JonnyWong16
ec5c5e1420 Merge pull request #1195 from Tommatheussen/patch-1
Update date formats
2018-01-04 13:39:25 -08:00
Tom Matheussen
803f4e14ca Added some additional formats 2018-01-04 22:18:40 +01:00
Tom Matheussen
6cc254b80a Update Date Formats
Added correct Year date formats, replaced generic numeric values with actual examples
2018-01-04 21:32:59 +01:00
JonnyWong16
59593ab1aa Fix HW indicator on activity refresh 2018-01-03 20:29:52 -08:00
JonnyWong16
65a0a0eb7d v2.0.9-beta 2018-01-03 19:37:12 -08:00
JonnyWong16
f4206b401f Fix season/episode numbers zfill 2018-01-03 19:24:19 -08:00
JonnyWong16
99f8d24b3e Remove bottom padding on stats info 2018-01-03 16:35:22 -08:00
25 changed files with 467 additions and 317 deletions

View File

@@ -1,10 +1,47 @@
# Changelog # Changelog
## v2.0.12-beta (2018-01-07)
* Notifications:
* Fix: Incorrect Plex URL parameter value.
* Change: Custom condition logic is now optional. An implicit "and" is applied between all conditions if the logic is blank.
* UI:
* New: Added separate required LAN/WAN bandwidth in the activity header.
* API:
* Fix: Notify API command not sending notifications.
## v2.0.11-beta (2018-01-05)
* Notifications:
* Fix: Some notification parameters showing up blank.
* UI:
* Fix: Stream data showing up as "None" for pre-v2 history.
* Other:
* Fix: Ability to login using the hashed password.
## v2.0.10-beta (2018-01-04)
* Monitoring:
* Fix: HW transcoding indicator on activity cards incorrect after refreshing.
* Notifications:
* Remove: Notification toggles from library and user settings. Use custom conditions to filter out notifications instead.
* UI:
* Fix: Incorrect examples for some date format options. Also added a few missing date format options. (Thanks @Tommatheussen)
## v2.0.9-beta (2018-01-03)
* Notifications:
* Fix: Notifications failing due to incorrect season/episode number types.
## v2.0.8-beta (2018-01-03) ## v2.0.8-beta (2018-01-03)
* Monitoring: * Monitoring:
* Fix: Fix HW transcoding indicator on activity cards. * Fix: Incorrect HW transcoding indicator on activity cards.
* Fix: Fix long product/player names hidden behind platform icon on activity cards. * Fix: Long product/player names hidden behind platform icon on activity cards.
* Notifications: * Notifications:
* Fix: Notifications failing due to some missing notification parameters. * Fix: Notifications failing due to some missing notification parameters.

View File

@@ -71,11 +71,13 @@ select.form-control {
border-radius: 3px; border-radius: 3px;
transition: background-color .3s; transition: background-color .3s;
} }
.react-selectize.root-node .react-selectize-control { .react-selectize.root-node .react-selectize-control,
.selectize-control.form-control .selectize-input {
color: #fff !important; color: #fff !important;
border: 0px solid #444 !important; border: 0px solid #444 !important;
background: #555 !important; background: #555 !important;
padding: 1px 2px; padding: 1px 2px;
transition: background-color .3s;
} }
.react-selectize.root-node .react-selectize-control .react-selectize-placeholder { .react-selectize.root-node .react-selectize-control .react-selectize-placeholder {
color: #fff !important; color: #fff !important;
@@ -83,6 +85,13 @@ select.form-control {
.react-selectize.root-node .react-selectize-control .react-selectize-toggle-button path { .react-selectize.root-node .react-selectize-control .react-selectize-toggle-button path {
fill: #fff !important; fill: #fff !important;
} }
.react-selectize.root-node .simple-value,
.selectize-control.multi .selectize-input > div {
background: #444444 !important;
color: #ffffff !important;
padding-bottom: 2px !important;
transition: background-color .3s;
}
.react-selectize.root-node .simple-value span { .react-selectize.root-node .simple-value span {
padding-bottom: 2px !important; padding-bottom: 2px !important;
} }
@@ -90,13 +99,25 @@ select.form-control {
padding-top: 3px !important; padding-top: 3px !important;
padding-bottom: 3px !important; padding-bottom: 3px !important;
} }
select.form-control:focus { select.form-control:focus,
.react-selectize.root-node.open .react-selectize-control,
.selectize-control.form-control .selectize-input.focus {
outline: 0; outline: 0;
outline: thin dotted \9; outline: thin dotted \9;
color: #555; color: #555 !important;
background-color: #fff; background-color: #fff !important;
transition: background-color .3s; transition: background-color .3s;
} }
.react-selectize.root-node.open .simple-value,
.selectize-control.multi .selectize-input.focus > div,
.selectize-control.multi .selectize-input > div.active{
background: #efefef !important;
color: #333333 !important;
transition: background-color .3s;
}
.react-selectize.root-node.open .react-selectize-control .react-selectize-toggle-button path {
fill: #999 !important;
}
select.form-control option { select.form-control option {
color: #555; color: #555;
background-color: #fff; background-color: #fff;
@@ -1273,7 +1294,7 @@ a .dashboard-activity-metadata-user-thumb:hover {
.dashboard-stats-info { .dashboard-stats-info {
width: 100%; width: 100%;
font-size: 12px; font-size: 12px;
padding: 3px 0 5px 15px; padding: 3px 0 0 15px;
position: relative; position: relative;
} }
.dashboard-stats-info-list { .dashboard-stats-info-list {
@@ -3722,7 +3743,11 @@ a:hover .overlay-refresh-image:hover {
.no-image { .no-image {
background-image: none !important; background-image: none !important;
} }
#info-modal .stream-info-current {
color: #aaa;
text-align: center;
padding-bottom: 10px;
}
#info-modal .stream-info-item { #info-modal .stream-info-item {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@@ -47,24 +47,12 @@ DOCUMENTATION :: END
</div> </div>
<p class="help-block">Change the library's picture in Tautulli. To reset to default, leave this field empty and save.</p> <p class="help-block">Change the library's picture in Tautulli. To reset to default, leave this field empty and save.</p>
</div> </div>
<div class="checkbox">
<label>
<input type="checkbox" id="do_notify" name="do_notify" value="1" ${helpers.checked(data['do_notify'])}> Enable notifications
</label>
<p class="help-block">Uncheck this if you do not want to receive notifications for this library's activity.</p>
</div>
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" id="keep_history" name="keep_history" value="1" ${helpers.checked(data['keep_history'])}> Keep history <input type="checkbox" id="keep_history" name="keep_history" value="1" ${helpers.checked(data['keep_history'])}> Keep history
</label> </label>
<p class="help-block">Uncheck this if you do not want to keep any history on this library's activity.</p> <p class="help-block">Uncheck this if you do not want to keep any history on this library's activity.</p>
</div> </div>
<div class="checkbox">
<label>
<input type="checkbox" id="do_notify_created" name="do_notify_created" value="1" ${helpers.checked(data['do_notify_created'])}> Enable recently added notifications
</label>
<p class="help-block">Uncheck this if you do not want to receive recently added notifications for this library.</p>
</div>
% if data['section_id']: % if data['section_id']:
<div class="form-group"> <div class="form-group">
<button class="btn btn-danger" id="delete-all-history">Purge</button> <button class="btn btn-danger" id="delete-all-history">Purge</button>
@@ -85,15 +73,7 @@ DOCUMENTATION :: END
// Save library options // Save library options
$("#save_library").on('click', function () { $("#save_library").on('click', function () {
var custom_thumb = $("#custom_thumb_url").val(); var custom_thumb = $("#custom_thumb_url").val();
var do_notify = 0;
var do_notify_created = 0;
var keep_history = 0; var keep_history = 0;
if ($("#do_notify").is(":checked")) {
do_notify = 1;
}
if ($("#do_notify_created").is(":checked")) {
do_notify_created = 1;
}
if ($("#keep_history").is(":checked")) { if ($("#keep_history").is(":checked")) {
keep_history = 1; keep_history = 1;
} }
@@ -103,8 +83,6 @@ DOCUMENTATION :: END
data: { data: {
section_id: '${data["section_id"]}', section_id: '${data["section_id"]}',
custom_thumb: custom_thumb, custom_thumb: custom_thumb,
do_notify: do_notify,
do_notify_created: do_notify_created,
keep_history: keep_history keep_history: keep_history
}, },
cache: false, cache: false,

View File

@@ -56,12 +56,6 @@ DOCUMENTATION :: END
</div> </div>
<p class="help-block">Change the users profile picture in Tautulli. To reset to default, leave this field empty and save.</p> <p class="help-block">Change the users profile picture in Tautulli. To reset to default, leave this field empty and save.</p>
</div> </div>
<div class="checkbox">
<label>
<input type="checkbox" id="do_notify" name="do_notify" value="1" ${helpers.checked(data['do_notify'])}> Enable notifications
</label>
<p class="help-block">Uncheck this if you do not want to receive notifications for this user's activity.</p>
</div>
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" id="keep_history" name="keep_history" value="1" ${helpers.checked(data['keep_history'])}> Keep history <input type="checkbox" id="keep_history" name="keep_history" value="1" ${helpers.checked(data['keep_history'])}> Keep history
@@ -95,12 +89,8 @@ DOCUMENTATION :: END
$("#save_user").on('click', function () { $("#save_user").on('click', function () {
var friendly_name = $("input#friendly_name").val(); var friendly_name = $("input#friendly_name").val();
var custom_thumb = $("#custom_avatar_url").val(); var custom_thumb = $("#custom_avatar_url").val();
var do_notify = 0;
var keep_history = 0; var keep_history = 0;
var allow_guest = 0; var allow_guest = 0;
if ($("#do_notify").is(":checked")) {
do_notify = 1;
}
if ($("#keep_history").is(":checked")) { if ($("#keep_history").is(":checked")) {
keep_history = 1; keep_history = 1;
} }
@@ -114,7 +104,6 @@ DOCUMENTATION :: END
user_id: '${data["user_id"]}', user_id: '${data["user_id"]}',
friendly_name: friendly_name, friendly_name: friendly_name,
custom_thumb: custom_thumb, custom_thumb: custom_thumb,
do_notify: do_notify,
keep_history: keep_history, keep_history: keep_history,
allow_guest: allow_guest allow_guest: allow_guest
}, },

View File

@@ -292,7 +292,9 @@
var sc_dp = current_activity.stream_count_direct_play, var sc_dp = current_activity.stream_count_direct_play,
sc_ds = current_activity.stream_count_direct_stream, sc_ds = current_activity.stream_count_direct_stream,
sc_tc = current_activity.stream_count_transcode, sc_tc = current_activity.stream_count_transcode,
total_bw = current_activity.total_bandwidth; total_bw = current_activity.total_bandwidth,
lan_bw = current_activity.lan_bandwidth,
wan_bw = current_activity.wan_bandwidth;
var streams_header = stream_count + ' stream' + (stream_count > 1 ? 's' : '') + ' ('; var streams_header = stream_count + ' stream' + (stream_count > 1 ? 's' : '') + ' (';
if (sc_dp) { if (sc_dp) {
streams_header += sc_dp + ' direct play' + (sc_dp > 1 ? 's' : '') + ', '; streams_header += sc_dp + ' direct play' + (sc_dp > 1 ? 's' : '') + ', ';
@@ -306,7 +308,14 @@
streams_header = streams_header.replace(/, $/, '') + ')'; streams_header = streams_header.replace(/, $/, '') + ')';
$('#currentActivityHeader-streams').text(streams_header); $('#currentActivityHeader-streams').text(streams_header);
var bandwidth_header = (total_bw > 1000) ? ((total_bw / 1000).toFixed(1) + ' Mbps') : (total_bw + ' kbps'); var bandwidth_header = ((total_bw > 1000) ? ((total_bw / 1000).toFixed(1) + ' Mbps') : (total_bw + ' kbps')) + ' (';
if (lan_bw) {
bandwidth_header += 'LAN: ' + ((lan_bw > 1000) ? ((lan_bw / 1000).toFixed(1) + ' Mbps') : (lan_bw + ' kbps')) + ', ';
}
if (wan_bw) {
bandwidth_header += 'WAN: ' + ((wan_bw > 1000) ? ((wan_bw / 1000).toFixed(1) + ' Mbps') : (wan_bw + ' kbps')) + ', ';
}
bandwidth_header = bandwidth_header.replace(/, $/, '') + ')';
$('#currentActivityHeader-bandwidth').text(bandwidth_header); $('#currentActivityHeader-bandwidth').text(bandwidth_header);
$('#currentActivityHeader').show(); $('#currentActivityHeader').show();
@@ -325,25 +334,26 @@
} }
// Update play state icon // Update play state icon
var state_icon = '';
switch (s.state) { switch (s.state) {
case 'playing': case 'playing':
var state_icon = '<i class="fa fa-fw fa-play"></i>&nbsp;'; state_icon = '<i class="fa fa-fw fa-play"></i>&nbsp;';
break; break;
case 'paused': case 'paused':
var state_icon = '<i class="fa fa-fw fa-pause"></i>&nbsp;'; state_icon = '<i class="fa fa-fw fa-pause"></i>&nbsp;';
break; break;
case 'buffering': case 'buffering':
var state_icon = '<i class="fa fa-fw fa-spinner"></i>&nbsp;'; state_icon = '<i class="fa fa-fw fa-spinner"></i>&nbsp;';
break; break;
default: default:
var state_icon = '<i class="fa fa-fw fa-question-circle"></i>&nbsp;'; state_icon = '<i class="fa fa-fw fa-question-circle"></i>&nbsp;';
} }
$('#play-state-' + key).html(state_icon).attr('title', capitalizeFirstLetter(s.state)); $('#play-state-' + key).html(state_icon).attr('title', capitalizeFirstLetter(s.state));
// Switching tracks can be under the same session key, so need to update the info. // Switching tracks can be under the same session key, so need to update the info.
if (s.media_type === 'track') { if (s.media_type === 'track') {
// Update if artist changed // Update if artist changed
if (s.grandparent_rating_key != instance.data('grandparent_rating_key')) { if (s.grandparent_rating_key !== instance.data('grandparent_rating_key')) {
$('#background-' + key).css('background-image', 'url(pms_image_proxy?img=' + s.art + '&width=500&height=280&fallback=art&refresh=true)'); $('#background-' + key).css('background-image', 'url(pms_image_proxy?img=' + s.art + '&width=500&height=280&fallback=art&refresh=true)');
$('#metadata-grandparent_title-' + key) $('#metadata-grandparent_title-' + key)
.attr('href', 'info?rating_key=' + s.grandparent_rating_key) .attr('href', 'info?rating_key=' + s.grandparent_rating_key)
@@ -351,7 +361,7 @@
.text(s.grandparent_title); .text(s.grandparent_title);
} }
// Update cover if album changed // Update cover if album changed
if (s.parent_rating_key != instance.data('parent_rating_key')) { if (s.parent_rating_key !== instance.data('parent_rating_key')) {
$('#poster-' + key).css('background-image', 'url(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&fallback=poster&refresh=true)'); $('#poster-' + key).css('background-image', 'url(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&fallback=poster&refresh=true)');
$('#poster-' + key + '-bg').css('background-image', 'url(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&fallback=poster&refresh=true)'); $('#poster-' + key + '-bg').css('background-image', 'url(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&fallback=poster&refresh=true)');
$('#poster-url-' + key) $('#poster-url-' + key)
@@ -363,7 +373,7 @@
.text(s.parent_title); .text(s.parent_title);
} }
// Update cover if track changed // Update cover if track changed
if (s.parent_rating_key != instance.data('parent_rating_key')) { if (s.parent_rating_key !== instance.data('parent_rating_key')) {
$('#metadata-title-' + key) $('#metadata-title-' + key)
.attr('href', 'info?rating_key=' + s.rating_key) .attr('href', 'info?rating_key=' + s.rating_key)
.attr('title', s.title) .attr('title', s.title)
@@ -374,7 +384,7 @@
// Update the transcode state // Update the transcode state
var transcode_decision = ''; var transcode_decision = '';
if (s.transcode_decision === 'transcode') { if (s.transcode_decision === 'transcode') {
var throttled = (s.transcode_throttled == 1) ? ' (Throttled)' : ' (Speed: ' + s.transcode_speed + ')'; var throttled = (s.transcode_throttled === 1) ? ' (Throttled)' : ' (Speed: ' + s.transcode_speed + ')';
transcode_decision = 'Transcode' + throttled; transcode_decision = 'Transcode' + throttled;
} else if (s.transcode_decision === 'copy') { } else if (s.transcode_decision === 'copy') {
transcode_decision = 'Direct Stream'; transcode_decision = 'Direct Stream';
@@ -392,36 +402,32 @@
$('#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.video_decision !== '') {
var v_res= '';
switch (s.video_resolution.toLowerCase()) { switch (s.video_resolution.toLowerCase()) {
case 'sd': case 'sd':
var v_res = 'SD'; v_res = 'SD';
break; break;
case '4k': case '4k':
var v_res = '4k'; v_res = '4k';
break; break;
default: default:
var v_res = s.video_resolution + 'p' v_res = s.video_resolution + 'p'
} }
var sv_res = '';
switch (s.stream_video_resolution.toLowerCase()) { switch (s.stream_video_resolution.toLowerCase()) {
case 'sd': case 'sd':
var sv_res = 'SD'; sv_res = 'SD';
break; break;
case '4k': case '4k':
var sv_res = '4k'; sv_res = '4k';
break; break;
default: default:
var sv_res = s.stream_video_resolution + 'p' sv_res = s.stream_video_resolution + 'p'
} }
if (s.stream_video_decision === 'transcode') { if (s.stream_video_decision === 'transcode') {
var hw_d = ''; var hw_d = (s.transcode_hw_decoding === 1) ? ' (HW)' : '';
var hw_e = ''; var hw_e = (s.transcode_hw_encoding === 1) ? ' (HW)' : '';
if (s.transcode_hw_requested === 1 && s.transcode_hw_full_pipeline === 0) {
hw_d = ' (HW)';
} else if (s.transcode_hw_requested === 1 && s.transcode_hw_full_pipeline === 1) {
hw_d = ' (HW)';
hw_e = ' (HW)';
}
video_decision = 'Transcode (' + s.video_codec.toUpperCase() + hw_d + ' ' + v_res + ' &rarr; ' + s.stream_video_codec.toUpperCase() + hw_e + ' ' + sv_res + ')'; video_decision = 'Transcode (' + s.video_codec.toUpperCase() + hw_d + ' ' + v_res + ' &rarr; ' + s.stream_video_codec.toUpperCase() + hw_e + ' ' + sv_res + ')';
} else if (s.stream_video_decision === 'copy') { } else if (s.stream_video_decision === 'copy') {
video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')'; video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
@@ -434,7 +440,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_codec) { if (['movie', 'episode', 'clip', 'track'].indexOf(s.media_type) > -1 && s.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') {
@@ -456,13 +462,13 @@
} else if (s.stream_subtitle_decision === 'burn') { } else if (s.stream_subtitle_decision === 'burn') {
subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')'; subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')';
} else { } else {
subtitle_decision = 'Direct Play (' + ((s.synced_version == '1') ? s.stream_subtitle_codec.toUpperCase() : s.subtitle_codec.toUpperCase()) + ')'; subtitle_decision = 'Direct Play (' + ((s.synced_version === '1') ? s.stream_subtitle_codec.toUpperCase() : s.subtitle_codec.toUpperCase()) + ')';
} }
} }
$('#subtitle_decision-' + key).html(subtitle_decision); $('#subtitle_decision-' + key).html(subtitle_decision);
// Update the stream quality profile and bandwidth // Update the stream quality profile and bandwidth
if (s.media_type != 'photo' && s.quality_profile != 'Unknown') { if (s.media_type !== 'photo' && s.quality_profile !== 'Unknown') {
var br = parseInt(s.stream_bitrate) || ''; var br = parseInt(s.stream_bitrate) || '';
if (br) { if (br) {
if (br > 1000) { if (br > 1000) {
@@ -478,9 +484,9 @@
$('#optimized_version-' + key).html(s.optimized_version_profile + ' (' + s.optimized_version_title + ')'); $('#optimized_version-' + key).html(s.optimized_version_profile + ' (' + s.optimized_version_title + ')');
$('#synced_quality_profile-' + key).html(s.synced_quality_profile); $('#synced_quality_profile-' + key).html(s.synced_quality_profile);
if (s.media_type != 'photo' && parseInt(s.bandwidth)) { if (s.media_type !== 'photo' && parseInt(s.bandwidth)) {
var bw = parseInt(s.bandwidth); var bw = parseInt(s.bandwidth);
if (bw != "Unknown") { if (bw !== "Unknown") {
if (bw > 1000) { if (bw > 1000) {
bw = (bw / 1000).toFixed(1) + ' Mbps'; bw = (bw / 1000).toFixed(1) + ' Mbps';
} else { } else {
@@ -492,17 +498,19 @@
// Update the stream progress times // Update the stream progress times
$('#stream-eta-' + key).html(moment().add(parseInt(s.duration) - parseInt(s.view_offset), 'milliseconds').format(time_format)); $('#stream-eta-' + key).html(moment().add(parseInt(s.duration) - parseInt(s.view_offset), 'milliseconds').format(time_format));
$('#stream-view-offset-' + key).data('state', s.state); var stream_view_offset = $('#stream-view-offset-' + key);
if ($('#stream-view-offset-' + key).data('last_view_offset') != s.view_offset) { stream_view_offset.data('state', s.state);
$('#stream-view-offset-' + key).data('last_view_offset', s.view_offset).data('view_offset', s.view_offset); if (stream_view_offset.data('last_view_offset') !== s.view_offset) {
stream_view_offset.data('last_view_offset', s.view_offset).data('view_offset', s.view_offset);
} }
// Update the progress bars, percent - 3 because of 3px padding-right // Update the progress bars, percent - 3 because of 3px padding-right
$('#buffer-bar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%') $('#buffer-bar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%')
.attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%'); .attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%');
$('#progress-bar-' + key).data('state', s.state); var progress_bar = $('#progress-bar-' + key);
if ($('#progress-bar-' + key).data('last_view_offset') != s.view_offset) { progress_bar.data('state', s.state);
$('#progress-bar-' + key).data('last_view_offset', s.view_offset).data('view_offset', s.view_offset); if (progress_bar.data('last_view_offset') !== s.view_offset) {
progress_bar.data('last_view_offset', s.view_offset).data('view_offset', s.view_offset);
} }
// Add temporary class so we know which instances are still active // Add temporary class so we know which instances are still active
@@ -771,13 +779,13 @@
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax); leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250); scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) { if (leftTotal === 0) {
$("#recently-added-page-left").addClass("disabled").blur(); $("#recently-added-page-left").addClass("disabled").blur();
} else { } else {
$("#recently-added-page-left").removeClass("disabled"); $("#recently-added-page-left").removeClass("disabled");
} }
if (leftTotal == leftMax) { if (leftTotal === leftMax) {
$("#recently-added-page-right").addClass("disabled").blur(); $("#recently-added-page-right").addClass("disabled").blur();
} else { } else {
$("#recently-added-page-right").removeClass("disabled"); $("#recently-added-page-right").removeClass("disabled");

View File

@@ -38,20 +38,21 @@ DOCUMENTATION :: END
<%! <%!
import re import re
from plexpy import common, notifiers from plexpy import notifiers
from plexpy.common import MEDIA_TYPE_HEADERS, MEDIA_FLAGS_AUDIO, MEDIA_FLAGS_VIDEO
# Get audio codec file # Get audio codec file
def af(codec): def af(codec):
for pattern, file in common.MEDIA_FLAGS_AUDIO.iteritems(): for pattern, file_type in MEDIA_FLAGS_AUDIO.iteritems():
if re.match(pattern, codec): if re.match(pattern, codec):
return file return file_type
return codec return codec
# Get audio codec file # Get audio codec file
def vf(codec): def vf(codec):
for pattern, file in common.MEDIA_FLAGS_VIDEO.iteritems(): for pattern, file_type in MEDIA_FLAGS_VIDEO.iteritems():
if re.match(pattern, codec): if re.match(pattern, codec):
return file return file_type
return codec return codec
def br(text): def br(text):
@@ -356,7 +357,7 @@ DOCUMENTATION :: END
<div class="col-md-12"> <div class="col-md-12">
<div class="table-card-header"> <div class="table-card-header">
<div class="header-bar"> <div class="header-bar">
<span>Movies in <strong>${data['title']}</strong> collection</span> <span>${MEDIA_TYPE_HEADERS[data['sub_media_type']]} in <strong>${data['title']}</strong> collection</span>
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">

View File

@@ -28,22 +28,15 @@ DOCUMENTATION :: END
% if data != None: % if data != None:
<% <%
from plexpy.common import MEDIA_TYPE_HEADERS
types = ('movie', 'show', 'artist', 'album') types = ('movie', 'show', 'artist', 'album')
headers = {'movie': 'Movies',
'show': 'TV Shows',
'season': 'Seasons',
'episode': 'Episodes',
'artist': 'Artists',
'album': 'Albums',
'track': 'Tracks',
}
%> %>
% for media_type in types: % for media_type in types:
% if data['results_list'][media_type]: % if data['results_list'][media_type]:
<div class="col-md-12"> <div class="col-md-12">
<div class="table-card-header"> <div class="table-card-header">
<div class="header-bar"> <div class="header-bar">
<span>${headers[media_type]} in <strong>${title}</strong> collection</span> <span>${MEDIA_TYPE_HEADERS[media_type]} in <strong>${title}</strong> collection</span>
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">

View File

@@ -28,9 +28,7 @@ libraries_list_table_options = {
$(td).html('<div class="edit-library-toggles">' + $(td).html('<div class="edit-library-toggles">' +
'<button class="btn btn-xs btn-warning delete-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button>&nbsp' + '<button class="btn btn-xs btn-warning delete-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button>&nbsp' +
'<button class="btn btn-xs btn-warning purge-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>&nbsp&nbsp&nbsp' + '<button class="btn btn-xs btn-warning purge-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>&nbsp&nbsp&nbsp' +
'<input type="checkbox" id="do_notify-' + rowData['section_id'] + '" name="do_notify" value="1" ' + rowData['do_notify'] + '><label class="edit-tooltip" for="do_notify-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle Notifications"><i class="fa fa-bell fa-lg fa-fw"></i></label>&nbsp' +
'<input type="checkbox" id="keep_history-' + rowData['section_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label>&nbsp' + '<input type="checkbox" id="keep_history-' + rowData['section_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label>&nbsp' +
'<input type="checkbox" id="do_notify_created-' + rowData['section_id'] + '" name="do_notify_created" value="1" ' + rowData['do_notify_created'] + '><label class="edit-tooltip" for="do_notify_created-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle Recently Added"><i class="fa fa-download fa-lg fa-fw"></i></label>&nbsp' +
'</div>'); '</div>');
}, },
"width": "7%", "width": "7%",
@@ -258,15 +256,7 @@ $('#libraries_list_table').on('change', 'td.edit-control > .edit-library-toggles
var row = libraries_list_table.row(tr); var row = libraries_list_table.row(tr);
var rowData = row.data(); var rowData = row.data();
var do_notify = 0;
var do_notify_created = 0;
var keep_history = 0; var keep_history = 0;
if ($('#do_notify-' + rowData['section_id']).is(':checked')) {
do_notify = 1;
}
if ($('#do_notify_created-' + rowData['section_id']).is(':checked')) {
do_notify_created = 1;
}
if ($('#keep_history-' + rowData['section_id']).is(':checked')) { if ($('#keep_history-' + rowData['section_id']).is(':checked')) {
keep_history = 1; keep_history = 1;
} }
@@ -280,8 +270,6 @@ $('#libraries_list_table').on('change', 'td.edit-control > .edit-library-toggles
url: 'edit_library', url: 'edit_library',
data: { data: {
section_id: rowData['section_id'], section_id: rowData['section_id'],
do_notify: do_notify,
do_notify_created: do_notify_created,
keep_history: keep_history, keep_history: keep_history,
custom_thumb: custom_thumb custom_thumb: custom_thumb
}, },

View File

@@ -45,7 +45,6 @@ users_list_table_options = {
$(td).html('<div class="edit-user-toggles">' + $(td).html('<div class="edit-user-toggles">' +
'<button class="btn btn-xs btn-warning delete-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button>&nbsp' + '<button class="btn btn-xs btn-warning delete-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button>&nbsp' +
'<button class="btn btn-xs btn-warning purge-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>&nbsp&nbsp&nbsp' + '<button class="btn btn-xs btn-warning purge-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>&nbsp&nbsp&nbsp' +
'<input type="checkbox" id="do_notify-' + rowData['user_id'] + '" name="do_notify" value="1" ' + rowData['do_notify'] + '><label class="edit-tooltip" for="do_notify-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Notifications"><i class="fa fa-bell fa-lg fa-fw"></i></label>&nbsp' +
'<input type="checkbox" id="keep_history-' + rowData['user_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label>&nbsp' + '<input type="checkbox" id="keep_history-' + rowData['user_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label>&nbsp' +
'<input type="checkbox" id="allow_guest-' + rowData['user_id'] + '" name="allow_guest" value="1" ' + rowData['allow_guest'] + '><label class="edit-tooltip" for="allow_guest-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Guest Access"><i class="fa fa-unlock-alt fa-lg fa-fw"></i></label>&nbsp' + '<input type="checkbox" id="allow_guest-' + rowData['user_id'] + '" name="allow_guest" value="1" ' + rowData['allow_guest'] + '><label class="edit-tooltip" for="allow_guest-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Guest Access"><i class="fa fa-unlock-alt fa-lg fa-fw"></i></label>&nbsp' +
'</div>'); '</div>');
@@ -284,12 +283,8 @@ $('#users_list_table').on('change', 'td.edit-control > .edit-user-toggles > inpu
var row = users_list_table.row(tr); var row = users_list_table.row(tr);
var rowData = row.data(); var rowData = row.data();
var do_notify = 0;
var keep_history = 0; var keep_history = 0;
var allow_guest = 0; var allow_guest = 0;
if ($('#do_notify-' + rowData['user_id']).is(':checked')) {
do_notify = 1;
}
if ($('#keep_history-' + rowData['user_id']).is(':checked')) { if ($('#keep_history-' + rowData['user_id']).is(':checked')) {
keep_history = 1; keep_history = 1;
} }
@@ -304,7 +299,6 @@ $('#users_list_table').on('change', 'td.edit-control > .edit-user-toggles > inpu
data: { data: {
user_id: rowData['user_id'], user_id: rowData['user_id'],
friendly_name: friendly_name, friendly_name: friendly_name,
do_notify: do_notify,
keep_history: keep_history, keep_history: keep_history,
allow_guest: allow_guest, allow_guest: allow_guest,
thumb: rowData['user_thumb'] thumb: rowData['user_thumb']

View File

@@ -132,12 +132,9 @@
<div role="tabpanel" class="tab-pane" id="tabs-notify_conditions"> <div role="tabpanel" class="tab-pane" id="tabs-notify_conditions">
<label>Notification Conditions</label> <label>Notification Conditions</label>
<p class="help-block"> <p class="help-block">
Add custom notification conditions. Add custom conditions to filter out notifications.
<a href="#notify-text-sub-modal" data-toggle="modal">Click here</a> for a description of all the parameters. <a href="#notify-text-sub-modal" data-toggle="modal">Click here</a> for a description of all the parameters.
</p> </p>
<p class="help-block">
Note: Conditions are checked after the notification trigger and the notification will only be sent if the condition logic is satisfied.
</p>
<div id="condition-widget"></div> <div id="condition-widget"></div>
<input type="hidden" name="custom_conditions" id="custom_conditions" /> <input type="hidden" name="custom_conditions" id="custom_conditions" />
@@ -146,7 +143,8 @@
<input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" required /> <input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" required />
<div id="custom_conditions_logic_error" class="alert alert-danger" role="alert" style="padding-top: 5px; padding-bottom: 5px; margin: 0; display: none;"><i class="fa fa-exclamation-triangle" style="color: #a94442;"></i> <span></span></div> <div id="custom_conditions_logic_error" class="alert alert-danger" role="alert" style="padding-top: 5px; padding-bottom: 5px; margin: 0; display: none;"><i class="fa fa-exclamation-triangle" style="color: #a94442;"></i> <span></span></div>
<p class="help-block"> <p class="help-block">
Enter the logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>). Optional: Enter custom logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>).
Leave blank for implicit <span class="inline-pre">and</span> between all conditions.
</p> </p>
<p class="help-block"> <p class="help-block">
Note: Only the keywords <span class="inline-pre">and</span>/<span class="inline-pre">or</span> and brackets <span class="inline-pre">()</span> are supported. Note: Only the keywords <span class="inline-pre">and</span>/<span class="inline-pre">or</span> and brackets <span class="inline-pre">()</span> are supported.

View File

@@ -63,7 +63,7 @@ DOCUMENTATION :: END
<h3 class="text-muted">&nbsp;</h3> <h3 class="text-muted">&nbsp;</h3>
</div> </div>
% elif item['media_type'] == 'show': % elif item['media_type'] == 'show':
<a href="info?rating_key=${item['rating_key']}" title="${item['parent_title']}"> <a href="info?rating_key=${item['rating_key']}" title="${item['title']}">
<div class="dashboard-recent-media-poster"> <div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);"> <div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-overlay"> <div class="dashboard-recent-media-overlay">

View File

@@ -918,7 +918,7 @@
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" id="get_file_sizes" name="get_file_sizes" value="1" ${config['get_file_sizes']}> Calculate Total File Sizes <span style="color: #eb8600; padding-left: 10px;">[experimental]</span> <input type="checkbox" id="get_file_sizes" name="get_file_sizes" value="1" ${config['get_file_sizes']}> Calculate Total File Sizes
</label> </label>
<p class="help-block">Enable if you want Tautulli to calculate the total file size for TV Shows/Seasons and Artists/Albums on the media info tables.</p> <p class="help-block">Enable if you want Tautulli to calculate the total file size for TV Shows/Seasons and Artists/Albums on the media info tables.</p>
</div> </div>

View File

@@ -54,6 +54,11 @@ DOCUMENTATION :: END
</h4> </h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
% if data['current_session']:
<div class="col-sm-12 text-muted stream-info-current">
<i class="fa fa-exclamation-circle"></i> Current session. Updated stream details below may be delayed.
</div>
% endif
<table class="stream-info" style="margin-top: 0;"> <table class="stream-info" style="margin-top: 0;">
<thead> <thead>
<tr> <tr>

View File

@@ -1189,6 +1189,27 @@ def dbcheck():
) )
# Upgrade session_history_media_info table from earlier versions
try:
result = c_db.execute('SELECT stream_container FROM session_history_media_info '
'WHERE stream_container IS NULL').fetchall()
if len(result) > 0:
logger.debug(u"Altering database. Removing NULL values from session_history_media_info table.")
c_db.execute(
'UPDATE session_history_media_info SET stream_container = "" WHERE stream_container IS NULL '
)
c_db.execute(
'UPDATE session_history_media_info SET stream_video_codec = "" WHERE stream_video_codec IS NULL '
)
c_db.execute(
'UPDATE session_history_media_info SET stream_audio_codec = "" WHERE stream_audio_codec IS NULL '
)
c_db.execute(
'UPDATE session_history_media_info SET stream_subtitle_codec = "" WHERE stream_subtitle_codec IS NULL '
)
except sqlite3.OperationalError:
logger.warn(u"Unable to remove NULL values from session_history_media_info table.")
# Upgrade users table from earlier versions # Upgrade users table from earlier versions
try: try:
c_db.execute('SELECT do_notify FROM users') c_db.execute('SELECT do_notify FROM users')
@@ -1370,8 +1391,8 @@ def dbcheck():
# Upgrade library_sections table from earlier versions (remove duplicated libraries) # Upgrade library_sections table from earlier versions (remove duplicated libraries)
try: try:
result = c_db.execute('SELECT * FROM library_sections WHERE server_id = ""') result = c_db.execute('SELECT * FROM library_sections WHERE server_id = ""').fetchall()
if result.rowcount > 0: if len(result) > 0:
logger.debug(u"Altering database. Removing duplicate libraries from library_sections table.") logger.debug(u"Altering database. Removing duplicate libraries from library_sections table.")
c_db.execute( c_db.execute(
'DELETE FROM library_sections WHERE server_id = ""' 'DELETE FROM library_sections WHERE server_id = ""'

View File

@@ -33,6 +33,7 @@ ACTIVITY_SCHED = BackgroundScheduler()
RECENTLY_ADDED_QUEUE = {} RECENTLY_ADDED_QUEUE = {}
class ActivityHandler(object): class ActivityHandler(object):
def __init__(self, timeline): def __init__(self, timeline):
@@ -229,9 +230,11 @@ class ActivityHandler(object):
# Update the session state and viewOffset # Update the session state and viewOffset
if this_state == 'playing': if this_state == 'playing':
# Update the session in our temp session table # Update the session in our temp session table
session = self.get_live_session() # if the last set temporary stopped time exceeds 15 seconds
if session: if int(time.time()) - db_session['stopped'] > 60:
self.update_db_session(session=session) session = self.get_live_session()
if session:
self.update_db_session(session=session)
# Start our state checks # Start our state checks
if this_state != last_state: if this_state != last_state:

View File

@@ -35,6 +35,8 @@ import database
import libraries import libraries
import logger import logger
import mobile_app import mobile_app
import notification_handler
import notifiers
import users import users
@@ -397,6 +399,50 @@ class API2:
return return
def notify(self, notifier_id='', subject='Tautulli', body='Test notification', **kwargs):
""" Send a notification using Tautulli.
```
Required parameters:
notifier_id (int): The ID number of the notification agent
subject (str): The subject of the message
body (str): The body of the message
Optional parameters:
None
Returns:
None
```
"""
if not notifier_id:
self._api_msg = 'Notification failed: no notifier id provided.'
self._api_result_type = 'error'
return
notifier = notifiers.get_notifier_config(notifier_id=notifier_id)
if not notifier:
self._api_msg = 'Notification failed: invalid notifier_id provided %s.' % notifier_id
self._api_result_type = 'error'
return
logger.api_debug(u'Tautulli APIv2 :: Sending notification.')
success = notification_handler.notify(notifier_id=notifier_id,
notify_action='api',
subject=subject,
body=body,
**kwargs)
if success:
self._api_msg = 'Notification sent.'
self._api_result_type = 'success'
else:
self._api_msg = 'Notification failed.'
self._api_result_type = 'error'
return
def _api_make_md(self): def _api_make_md(self):
""" Tries to make a API.md to simplify the api docs. """ """ Tries to make a API.md to simplify the api docs. """
@@ -581,8 +627,8 @@ General optional parameters:
if isinstance(result, (dict, list)): if isinstance(result, (dict, list)):
ret = result ret = result
else: else:
raise raise Exception
except: except Exception:
try: try:
ret = json.loads(result) ret = json.loads(result)
except (ValueError, TypeError): except (ValueError, TypeError):

View File

@@ -32,133 +32,176 @@ DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png" DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png"
DEFAULT_ART = "interfaces/default/images/art.png" DEFAULT_ART = "interfaces/default/images/art.png"
PLATFORM_NAME_OVERRIDES = {'Konvergo': 'Plex Media Player', MEDIA_TYPE_HEADERS = {
'Mystery 3': 'Playstation 3', 'movie': 'Movies',
'Mystery 4': 'Playstation 4', 'show': 'TV Shows',
'Mystery 5': 'Xbox 360', 'season': 'Seasons',
'WebMAF': 'Playstation 4' 'episode': 'Episodes',
} 'artist': 'Artists',
'album': 'Albums',
'track': 'Tracks',
}
PMS_PLATFORM_NAME_OVERRIDES = {'MacOSX': 'Mac' PLATFORM_NAME_OVERRIDES = {
} 'Konvergo': 'Plex Media Player',
'Mystery 3': 'Playstation 3',
'Mystery 4': 'Playstation 4',
'Mystery 5': 'Xbox 360',
'WebMAF': 'Playstation 4'
}
PLATFORM_NAMES = {'android': 'android', PMS_PLATFORM_NAME_OVERRIDES = {
'apple tv': 'atv', 'MacOSX': 'Mac'
'chrome': 'chrome', }
'chromecast': 'chromecast',
'dlna': 'dlna', PLATFORM_NAMES = {
'firefox': 'firefox', 'android': 'android',
'internet explorer': 'ie', 'apple tv': 'atv',
'ios': 'ios', 'chrome': 'chrome',
'ipad': 'ios', 'chromecast': 'chromecast',
'iphone': 'ios', 'dlna': 'dlna',
'kodi': 'kodi', 'firefox': 'firefox',
'linux': 'linux', 'internet explorer': 'ie',
'nexus': 'android', 'ios': 'ios',
'macos': 'macos', 'ipad': 'ios',
'microsoft edge': 'msedge', 'iphone': 'ios',
'opera': 'opera', 'kodi': 'kodi',
'osx': 'macos', 'linux': 'linux',
'playstation': 'playstation', 'nexus': 'android',
'plex home theater': 'plex', 'macos': 'macos',
'plex media player': 'plex', 'microsoft edge': 'msedge',
'plexamp': 'plexamp', 'opera': 'opera',
'plextogether': 'synclounge', 'osx': 'macos',
'roku': 'roku', 'playstation': 'playstation',
'safari': 'safari', 'plex home theater': 'plex',
'samsung': 'samsung', 'plex media player': 'plex',
'synclounge': 'synclounge', 'plexamp': 'plexamp',
'tivo': 'tivo', 'plextogether': 'synclounge',
'tvos': 'atv', 'roku': 'roku',
'vizio': 'opera', 'safari': 'safari',
'wiiu': 'wiiu', 'samsung': 'samsung',
'windows': 'windows', 'synclounge': 'synclounge',
'windows phone': 'wp', 'tivo': 'tivo',
'xbmc': 'xbmc', 'tvos': 'atv',
'xbox': 'xbox' 'vizio': 'opera',
} 'wiiu': 'wiiu',
'windows': 'windows',
'windows phone': 'wp',
'xbmc': 'xbmc',
'xbox': 'xbox'
}
PLATFORM_NAMES = OrderedDict(sorted(PLATFORM_NAMES.items(), key=lambda k: k[0], reverse=True)) PLATFORM_NAMES = OrderedDict(sorted(PLATFORM_NAMES.items(), key=lambda k: k[0], reverse=True))
MEDIA_FLAGS_AUDIO = {'ac.?3': 'dolby_digital', MEDIA_FLAGS_AUDIO = {
'truehd': 'dolby_truehd', 'ac.?3': 'dolby_digital',
'(dca|dta)': 'dts', 'truehd': 'dolby_truehd',
'dts(hd_|-hd|-)?ma': 'dca-ma', '(dca|dta)': 'dts',
'vorbis': 'ogg' 'dts(hd_|-hd|-)?ma': 'dca-ma',
} 'vorbis': 'ogg'
MEDIA_FLAGS_VIDEO = {'avc1': 'h264', }
'wmv(1|2)': 'wmv', MEDIA_FLAGS_VIDEO = {
'wmv3': 'wmvhd' 'avc1': 'h264',
} 'wmv(1|2)': 'wmv',
'wmv3': 'wmvhd'
}
AUDIO_CODEC_OVERRIDES = {'truehd': 'TrueHD'} AUDIO_CODEC_OVERRIDES = {
'truehd': 'TrueHD'
}
VIDEO_RESOLUTION_OVERRIDES = {'sd': 'SD', VIDEO_RESOLUTION_OVERRIDES = {
'480': '480p', 'sd': 'SD',
'540': '540p', '480': '480p',
'576': '576p', '540': '540p',
'720': '720p', '576': '576p',
'1080': '1080p', '720': '720p',
'4k': '4k' '1080': '1080p',
} '4k': '4k'
}
AUDIO_CHANNELS = {'1': 'Mono', AUDIO_CHANNELS = {
'2': 'Stereo', '1': 'Mono',
'3': '2.1', '2': 'Stereo',
'4': '3.1', '3': '2.1',
'6': '5.1', '4': '3.1',
'7': '6.1', '6': '5.1',
'8': '7.1' '7': '6.1',
} '8': '7.1'
}
VIDEO_QUALITY_PROFILES = {20000: '20 Mbps 1080p', VIDEO_QUALITY_PROFILES = {
12000: '12 Mbps 1080p', 20000: '20 Mbps 1080p',
10000: '10 Mbps 1080p', 12000: '12 Mbps 1080p',
8000: '8 Mbps 1080p', 10000: '10 Mbps 1080p',
4000: '4 Mbps 720p', 8000: '8 Mbps 1080p',
3000: '3 Mbps 720p', 4000: '4 Mbps 720p',
2000: '2 Mbps 720p', 3000: '3 Mbps 720p',
1500: '1.5 Mbps 480p', 2000: '2 Mbps 720p',
720: '0.7 Mbps 328p', 1500: '1.5 Mbps 480p',
320: '0.3 Mbps 240p', 720: '0.7 Mbps 328p',
208: '0.2 Mbps 160p', 320: '0.3 Mbps 240p',
96: '0.096 Mbps', 208: '0.2 Mbps 160p',
64: '0.064 Mbps' 96: '0.096 Mbps',
} 64: '0.064 Mbps'
}
VIDEO_QUALITY_PROFILES = OrderedDict(sorted(VIDEO_QUALITY_PROFILES.items(), key=lambda k: k[0], reverse=True)) VIDEO_QUALITY_PROFILES = OrderedDict(sorted(VIDEO_QUALITY_PROFILES.items(), key=lambda k: k[0], reverse=True))
AUDIO_QUALITY_PROFILES = {512: '512 kbps', AUDIO_QUALITY_PROFILES = {
320: '320 kbps', 512: '512 kbps',
256: '256 kbps', 320: '320 kbps',
192: '192 kbps', 256: '256 kbps',
128: '128 kbps', 192: '192 kbps',
96: '96 kbps' 128: '128 kbps',
} 96: '96 kbps'
}
AUDIO_QUALITY_PROFILES = OrderedDict(sorted(AUDIO_QUALITY_PROFILES.items(), key=lambda k: k[0], reverse=True)) AUDIO_QUALITY_PROFILES = OrderedDict(sorted(AUDIO_QUALITY_PROFILES.items(), key=lambda k: k[0], reverse=True))
HW_DECODERS = ['dxva2', 'videotoolbox', 'mediacodecndk', 'vaapi'] HW_DECODERS = [
HW_ENCODERS = ['qsv', 'nvenc', 'mf', 'videotoolbox', 'mediacodecndk', 'vaapi', 'nvenc'] 'dxva2',
'videotoolbox',
'mediacodecndk',
'vaapi'
]
HW_ENCODERS = [
'qsv',
'nvenc',
'mf',
'videotoolbox',
'mediacodecndk',
'vaapi',
'nvenc'
]
SCHEDULER_LIST = ['Check GitHub for updates', SCHEDULER_LIST = [
'Check for active sessions', 'Check GitHub for updates',
'Check for recently added items', 'Check for active sessions',
'Check for Plex updates', 'Check for recently added items',
'Check for Plex remote access', 'Check for Plex updates',
'Check server response', 'Check for Plex remote access',
'Refresh users list', 'Check server response',
'Refresh libraries list', 'Refresh users list',
'Refresh Plex server URLs', 'Refresh libraries list',
'Backup Tautulli database', 'Refresh Plex server URLs',
'Backup Tautulli config' 'Backup Tautulli database',
] 'Backup Tautulli config'
]
DATE_TIME_FORMATS = [ DATE_TIME_FORMATS = [
{ {
'category': 'Year', 'category': 'Year',
'parameters': [
{'value': 'YYYY', 'description': 'Numeric, four digits', 'example': '1999, 2003'},
{'value': 'YY', 'description': 'Numeric, two digits', 'example': '99, 03'}
]
},
{
'category': 'Month',
'parameters': [ 'parameters': [
{'value': 'MMMM', 'description': 'Textual, full', 'example': 'January-December'}, {'value': 'MMMM', 'description': 'Textual, full', 'example': 'January-December'},
{'value': 'MMM', 'description': 'Textual, three letters', 'example': 'Jan-Dec'}, {'value': 'MMM', 'description': 'Textual, three letters', 'example': 'Jan-Dec'},
{'value': 'MM', 'description': 'Numeric, with leading zeros', 'example': '42747'}, {'value': 'MM', 'description': 'Numeric, with leading zeros', 'example': '01-12'},
{'value': 'M', 'description': 'Numeric, without leading zeros', 'example': '42747'}, {'value': 'M', 'description': 'Numeric, without leading zeros', 'example': '1-12'},
{'value': 'Mo', 'description': 'Numeric, with suffix', 'example': '1st, 2nd ... 12th'},
] ]
}, },
{ {
@@ -166,14 +209,15 @@ DATE_TIME_FORMATS = [
'parameters': [ 'parameters': [
{'value': 'DDDD', 'description': 'Numeric, with leading zeros', 'example': '001-365'}, {'value': 'DDDD', 'description': 'Numeric, with leading zeros', 'example': '001-365'},
{'value': 'DDD', 'description': 'Numeric, without leading zeros', 'example': '1-365'}, {'value': 'DDD', 'description': 'Numeric, without leading zeros', 'example': '1-365'},
{'value': 'DDDo', 'description': 'Numeric, with suffix', 'example': '1st, 2nd, ... 365th'},
] ]
}, },
{ {
'category': 'Day of the Month', 'category': 'Day of the Month',
'parameters': [ 'parameters': [
{'value': 'DD', 'description': 'Numeric, with leading zeros', 'example': '42766'}, {'value': 'DD', 'description': 'Numeric, with leading zeros', 'example': '01-31'},
{'value': 'D', 'description': 'Numeric, without leading zeros', 'example': '42766'}, {'value': 'D', 'description': 'Numeric, without leading zeros', 'example': '1-31'},
{'value': 'Do', 'description': 'Numeric, with suffix', 'example': 'E.g. 1st, 2nd ... 31st.'}, {'value': 'Do', 'description': 'Numeric, with suffix', 'example': '1st, 2nd ... 31st'},
] ]
}, },
{ {
@@ -181,7 +225,9 @@ DATE_TIME_FORMATS = [
'parameters': [ 'parameters': [
{'value': 'dddd', 'description': 'Textual, full', 'example': 'Sunday-Saturday'}, {'value': 'dddd', 'description': 'Textual, full', 'example': 'Sunday-Saturday'},
{'value': 'ddd', 'description': 'Textual, three letters', 'example': 'Sun-Sat'}, {'value': 'ddd', 'description': 'Textual, three letters', 'example': 'Sun-Sat'},
{'value': 'dd', 'description': 'Textual, two letters', 'example': 'Su-Sa'},
{'value': 'd', 'description': 'Numeric', 'example': '0-6'}, {'value': 'd', 'description': 'Numeric', 'example': '0-6'},
{'value': 'do', 'description': 'Numeric, with suffix', 'example': '0th, 1st ... 6th'},
] ]
}, },
{ {
@@ -189,8 +235,8 @@ DATE_TIME_FORMATS = [
'parameters': [ 'parameters': [
{'value': 'HH', 'description': '24-hour, with leading zeros', 'example': '00-23'}, {'value': 'HH', 'description': '24-hour, with leading zeros', 'example': '00-23'},
{'value': 'H', 'description': '24-hour, without leading zeros', 'example': '0-23'}, {'value': 'H', 'description': '24-hour, without leading zeros', 'example': '0-23'},
{'value': 'hh', 'description': '12-hour, with leading zeros', 'example': '42747'}, {'value': 'hh', 'description': '12-hour, with leading zeros', 'example': '01-12'},
{'value': 'h', 'description': '12-hour, without leading zeros', 'example': '42747'}, {'value': 'h', 'description': '12-hour, without leading zeros', 'example': '1-12'},
] ]
}, },
{ {
@@ -217,8 +263,8 @@ DATE_TIME_FORMATS = [
{ {
'category': 'Timezone', 'category': 'Timezone',
'parameters': [ 'parameters': [
{'value': 'ZZ', 'description': 'UTC offset', 'example': 'E.g. +0100, -0700'}, {'value': 'ZZ', 'description': 'UTC offset', 'example': '+0100, -0700'},
{'value': 'Z', 'description': 'UTC offset', 'example': 'E.g. +01:00, -07:00'}, {'value': 'Z', 'description': 'UTC offset', 'example': '+01:00, -07:00'},
] ]
}, },
{ {
@@ -227,7 +273,7 @@ DATE_TIME_FORMATS = [
{'value': 'X', 'description': 'Unix timestamp', 'example': 'E.g. 1456887825'}, {'value': 'X', 'description': 'Unix timestamp', 'example': 'E.g. 1456887825'},
] ]
}, },
] ]
NOTIFICATION_PARAMETERS = [ NOTIFICATION_PARAMETERS = [
{ {
@@ -439,4 +485,4 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Plexpy Update Changelog', 'type': 'int', 'value': 'plexpy_update_changelog', 'description': 'The changelog for the available update.'}, {'name': 'Plexpy Update Changelog', 'type': 'int', 'value': 'plexpy_update_changelog', 'description': 'The changelog for the available update.'},
] ]
}, },
] ]

View File

@@ -289,6 +289,7 @@ _CONFIG_DEFINITIONS = {
'LOG_BLACKLIST': (int, 'General', 1), 'LOG_BLACKLIST': (int, 'General', 1),
'LOG_DIR': (str, 'General', ''), 'LOG_DIR': (str, 'General', ''),
'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120), 'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120),
'METADATA_CACHE_SECONDS': (int, 'Advanced', 1800),
'MOVIE_LOGGING_ENABLE': (int, 'Monitoring', 1), 'MOVIE_LOGGING_ENABLE': (int, 'Monitoring', 1),
'MOVIE_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'MOVIE_NOTIFY_ENABLE': (int, 'Monitoring', 0),
'MOVIE_NOTIFY_ON_START': (int, 'Monitoring', 1), 'MOVIE_NOTIFY_ON_START': (int, 'Monitoring', 1),

View File

@@ -951,9 +951,11 @@ class DataFactory(object):
'transcode_hw_encoding': item['transcode_hw_encoding'], 'transcode_hw_encoding': item['transcode_hw_encoding'],
'media_type': item['media_type'], 'media_type': item['media_type'],
'title': item['title'], 'title': item['title'],
'grandparent_title': item['grandparent_title'] 'grandparent_title': item['grandparent_title'],
'current_session': 1 if session_key else 0
} }
stream_output = {k: v or '' for k, v in stream_output.iteritems()}
return stream_output return stream_output
def get_metadata_details(self, rating_key): def get_metadata_details(self, rating_key):

View File

@@ -131,19 +131,19 @@ def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
if stream_data: if stream_data:
# Check if notifications enabled for user and library # Check if notifications enabled for user and library
user_data = users.Users() # user_data = users.Users()
user_details = user_data.get_details(user_id=stream_data['user_id']) # user_details = user_data.get_details(user_id=stream_data['user_id'])
#
# library_data = libraries.Libraries()
# library_details = library_data.get_details(section_id=stream_data['section_id'])
library_data = libraries.Libraries() # if not user_details['do_notify']:
library_details = library_data.get_details(section_id=stream_data['section_id']) # logger.debug(u"Tautulli NotificationHandler :: Notifications for user '%s' are disabled." % user_details['username'])
# return False
if not user_details['do_notify']: #
logger.debug(u"Tautulli NotificationHandler :: Notifications for user '%s' are disabled." % user_details['username']) # elif not library_details['do_notify'] and notify_action not in ('on_concurrent', 'on_newdevice'):
return False # logger.debug(u"Tautulli NotificationHandler :: Notifications for library '%s' are disabled." % library_details['section_name'])
# return False
elif not library_details['do_notify'] and notify_action not in ('on_concurrent', 'on_newdevice'):
logger.debug(u"Tautulli NotificationHandler :: Notifications for library '%s' are disabled." % library_details['section_name'])
return False
if notify_action == 'on_concurrent': if notify_action == 'on_concurrent':
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
@@ -188,12 +188,12 @@ def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
elif timeline_data: elif timeline_data:
# Check if notifications enabled for library # Check if notifications enabled for library
library_data = libraries.Libraries() # library_data = libraries.Libraries()
library_details = library_data.get_details(section_id=timeline_data['section_id']) # library_details = library_data.get_details(section_id=timeline_data['section_id'])
#
if not library_details['do_notify_created']: # if not library_details['do_notify_created']:
# logger.debug(u"Tautulli NotificationHandler :: Notifications for library '%s' is disabled." % library_details['section_name']) # # logger.debug(u"Tautulli NotificationHandler :: Notifications for library '%s' is disabled." % library_details['section_name'])
return False # return False
return True return True
@@ -206,19 +206,21 @@ def notify_custom_conditions(notifier_id=None, parameters=None):
notifier_config = notifiers.get_notifier_config(notifier_id=notifier_id) notifier_config = notifiers.get_notifier_config(notifier_id=notifier_id)
custom_conditions_logic = notifier_config['custom_conditions_logic'] custom_conditions_logic = notifier_config['custom_conditions_logic']
custom_conditions = json.loads(notifier_config['custom_conditions']) or []
if custom_conditions_logic: if custom_conditions_logic or any(c for c in custom_conditions if c['value']):
logger.debug(u"Tautulli NotificationHandler :: Checking custom notification conditions for notifier_id %s." % notifier_id) logger.debug(u"Tautulli NotificationHandler :: Checking custom notification conditions for notifier_id %s."
% notifier_id)
custom_conditions = json.loads(notifier_config['custom_conditions']) logic_groups = None
if custom_conditions_logic:
try: try:
# Parse and validate the custom conditions logic # Parse and validate the custom conditions logic
logic_groups = helpers.parse_condition_logic_string(custom_conditions_logic, len(custom_conditions)) logic_groups = helpers.parse_condition_logic_string(custom_conditions_logic, len(custom_conditions))
except ValueError as e: except ValueError as e:
logger.error(u"Tautulli NotificationHandler :: Unable to parse custom condition logic '%s': %s." logger.error(u"Tautulli NotificationHandler :: Unable to parse custom condition logic '%s': %s."
% (custom_conditions_logic, e)) % (custom_conditions_logic, e))
return False return False
evaluated_conditions = [None] # Set condition {0} to None evaluated_conditions = [None] # Set condition {0} to None
@@ -227,10 +229,11 @@ def notify_custom_conditions(notifier_id=None, parameters=None):
operator = condition['operator'] operator = condition['operator']
values = condition['value'] values = condition['value']
parameter_type = condition['type'] parameter_type = condition['type']
parameter_value = parameters.get(parameter, "")
# Set blank conditions to None # Set blank conditions to True (skip)
if not parameter or not operator or not values: if not parameter or not operator or not values:
evaluated_conditions.append(None) evaluated_conditions.append(True)
continue continue
# Make sure the condition values is in a list # Make sure the condition values is in a list
@@ -248,25 +251,25 @@ def notify_custom_conditions(notifier_id=None, parameters=None):
elif parameter_type == 'float': elif parameter_type == 'float':
values = [float(v) for v in values] values = [float(v) for v in values]
except Exception as e: except ValueError as e:
logger.error(u"Tautulli NotificationHandler :: Unable to cast condition '%s' to type '%s'." logger.error(u"Tautulli NotificationHandler :: Unable to cast condition '%s', values '%s', to type '%s'."
% (parameter, parameter_type)) % (parameter, values, parameter_type))
return False return False
# Cast the parameter value to the correct type # Cast the parameter value to the correct type
try: try:
if parameter_type == 'str': if parameter_type == 'str':
parameter_value = unicode(parameters[parameter]).lower() parameter_value = unicode(parameter_value).lower()
elif parameter_type == 'int': elif parameter_type == 'int':
parameter_value = int(parameters[parameter]) parameter_value = int(parameter_value)
elif parameter_type == 'float': elif parameter_type == 'float':
parameter_value = float(parameters[parameter]) parameter_value = float(parameter_value)
except Exception as e: except ValueError as e:
logger.error(u"Tautulli NotificationHandler :: Unable to cast parameter '%s' to type '%s'." logger.error(u"Tautulli NotificationHandler :: Unable to cast parameter '%s', value '%s', to type '%s'."
% (parameter, parameter_type)) % (parameter, parameter_value, parameter_type))
return False return False
# Check each condition # Check each condition
@@ -298,12 +301,15 @@ def notify_custom_conditions(notifier_id=None, parameters=None):
logger.warn(u"Tautulli NotificationHandler :: Invalid condition operator '%s'." % operator) logger.warn(u"Tautulli NotificationHandler :: Invalid condition operator '%s'." % operator)
evaluated_conditions.append(None) evaluated_conditions.append(None)
# Format and evaluate the logic string if logic_groups:
try: # Format and evaluate the logic string
evaluated_logic = helpers.eval_logic_groups_to_bool(logic_groups, evaluated_conditions) try:
except Exception as e: evaluated_logic = helpers.eval_logic_groups_to_bool(logic_groups, evaluated_conditions)
logger.error(u"Tautulli NotificationHandler :: Unable to evaluate custom condition logic: %s." % e) except Exception as e:
return False logger.error(u"Tautulli NotificationHandler :: Unable to evaluate custom condition logic: %s." % e)
return False
else:
evaluated_logic = all(evaluated_conditions[1:])
logger.debug(u"Tautulli NotificationHandler :: Custom condition evaluated to '%s'." % str(evaluated_logic)) logger.debug(u"Tautulli NotificationHandler :: Custom condition evaluated to '%s'." % str(evaluated_logic))
return evaluated_logic return evaluated_logic
@@ -326,7 +332,7 @@ def notify(notifier_id=None, notify_action=None, stream_data=None, timeline_data
if not notifier_config: if not notifier_config:
return return
if notify_action == 'test': 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 = kwargs.pop('script_args', [])
@@ -344,8 +350,8 @@ def notify(notifier_id=None, notify_action=None, stream_data=None, timeline_data
# Set the notification state in the db # Set the notification state in the db
notification_id = set_notify_state(session=stream_data or timeline_data, notification_id = set_notify_state(session=stream_data or timeline_data,
notify_action=notify_action,
notifier=notifier_config, notifier=notifier_config,
notify_action=notify_action,
subject=subject, subject=subject,
body=body, body=body,
script_args=script_args) script_args=script_args)
@@ -384,9 +390,9 @@ def get_notify_state(session):
return notify_states return notify_states
def set_notify_state(notify_action, notifier, subject, body, script_args, session=None): def set_notify_state(notifier, notify_action, subject='', body='', script_args='', session=None):
if notify_action and notifier: if notifier and notify_action:
monitor_db = database.MonitorDatabase() monitor_db = database.MonitorDatabase()
session = session or {} session = session or {}
@@ -451,7 +457,11 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
notify_params = defaultdict(str) notify_params = defaultdict(str)
if session: if session:
# Reload json from raw stream info
if session.get('raw_stream_info'):
session.update(json.loads(session['raw_stream_info']))
notify_params.update(session) notify_params.update(session)
if timeline: if timeline:
notify_params.update(timeline) notify_params.update(timeline)
@@ -513,7 +523,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
remaining_duration = duration - view_offset remaining_duration = duration - view_offset
# Build Plex URL # Build Plex URL
notify_params['plex_url'] = '{web_url}#!/server/{pms_identifier}/details?key=%2Flibrary%2Fnotify_params%2F{rating_key}'.format( notify_params['plex_url'] = '{web_url}#!/server/{pms_identifier}/details?key=%2Flibrary%2Fmetadata%2F{rating_key}'.format(
web_url=plexpy.CONFIG.PMS_WEB_URL, web_url=plexpy.CONFIG.PMS_WEB_URL,
pms_identifier=plexpy.CONFIG.PMS_IDENTIFIER, pms_identifier=plexpy.CONFIG.PMS_IDENTIFIER,
rating_key=rating_key) rating_key=rating_key)
@@ -622,8 +632,8 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
artist_name = notify_params['parent_title'] artist_name = notify_params['parent_title']
album_name = notify_params['title'] album_name = notify_params['title']
track_name = '' track_name = ''
season_num = notify_params['media_index'].zfill(1) season_num = str(notify_params['media_index']).zfill(1)
season_num00 = notify_params['media_index'].zfill(2) season_num00 = str(notify_params['media_index']).zfill(2)
num, num00 = format_group_index([helpers.cast_to_int(d['media_index']) num, num00 = format_group_index([helpers.cast_to_int(d['media_index'])
for d in child_metadata if d['parent_rating_key'] == rating_key]) for d in child_metadata if d['parent_rating_key'] == rating_key])
@@ -636,12 +646,12 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
artist_name = notify_params['grandparent_title'] artist_name = notify_params['grandparent_title']
album_name = notify_params['parent_title'] album_name = notify_params['parent_title']
track_name = notify_params['title'] track_name = notify_params['title']
season_num = notify_params['parent_media_index'].zfill(1) season_num = str(notify_params['parent_media_index']).zfill(1)
season_num00 = notify_params['parent_media_index'].zfill(2) season_num00 = str(notify_params['parent_media_index']).zfill(2)
episode_num = notify_params['media_index'].zfill(1) episode_num = str(notify_params['media_index']).zfill(1)
episode_num00 = notify_params['media_index'].zfill(2) episode_num00 = str(notify_params['media_index']).zfill(2)
track_num = notify_params['media_index'].zfill(1) track_num = str(notify_params['media_index']).zfill(1)
track_num00 = notify_params['media_index'].zfill(2) track_num00 = str(notify_params['media_index']).zfill(2)
available_params = { available_params = {
# Global paramaters # Global paramaters
@@ -651,7 +661,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'server_name': server_name, 'server_name': server_name,
'server_uptime': server_uptime, 'server_uptime': server_uptime,
'server_version': server_times.get('version', ''), 'server_version': server_times.get('version', ''),
'action': notify_action.split('on_')[-1], 'action': notify_action.lstrip('on_'),
'datestamp': arrow.now().format(date_format), 'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format), 'timestamp': arrow.now().format(time_format),
# Stream parameters # Stream parameters
@@ -855,7 +865,7 @@ def build_server_notify_params(notify_action=None, **kwargs):
'server_name': server_name, 'server_name': server_name,
'server_uptime': server_uptime, 'server_uptime': server_uptime,
'server_version': server_times.get('version', ''), 'server_version': server_times.get('version', ''),
'action': notify_action.split('on_')[-1], 'action': notify_action.lstrip('on_'),
'datestamp': arrow.now().format(date_format), 'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format), 'timestamp': arrow.now().format(time_format),
# Plex Media Server update parameters # Plex Media Server update parameters
@@ -937,7 +947,7 @@ def build_notify_text(subject='', body='', notify_action=None, parameters=None,
try: try:
script_args = [custom_formatter.format(unicode(arg), **parameters) for arg in subject.split()] script_args = [custom_formatter.format(unicode(arg), **parameters) for arg in subject.split()]
except LookupError as e: except LookupError as e:
logger.error(u"Tautulli NotificationHandler :: Unable to parse field %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 = []
except Exception as e: except Exception as e:
logger.error(u"Tautulli NotificationHandler :: Unable to parse custom script arguments: %s. Using fallback." % e) logger.error(u"Tautulli NotificationHandler :: Unable to parse custom script arguments: %s. Using fallback." % e)
@@ -948,7 +958,7 @@ def build_notify_text(subject='', body='', notify_action=None, parameters=None,
try: try:
subject = custom_formatter.format(unicode(subject), **parameters) subject = custom_formatter.format(unicode(subject), **parameters)
except LookupError as e: except LookupError as e:
logger.error(u"Tautulli NotificationHandler :: Unable to parse field %s in notification subject. Using fallback." % e) logger.error(u"Tautulli NotificationHandler :: Unable to parse parameter %s in notification subject. Using fallback." % e)
subject = unicode(default_subject).format(**parameters) subject = unicode(default_subject).format(**parameters)
except Exception as e: except Exception as e:
logger.error(u"Tautulli NotificationHandler :: Unable to parse custom notification subject: %s. Using fallback." % e) logger.error(u"Tautulli NotificationHandler :: Unable to parse custom notification subject: %s. Using fallback." % e)
@@ -957,7 +967,7 @@ def build_notify_text(subject='', body='', notify_action=None, parameters=None,
try: try:
body = custom_formatter.format(unicode(body), **parameters) body = custom_formatter.format(unicode(body), **parameters)
except LookupError as e: except LookupError as e:
logger.error(u"Tautulli NotificationHandler :: Unable to parse field %s in notification body. Using fallback." % e) logger.error(u"Tautulli NotificationHandler :: Unable to parse parameter %s in notification body. Using fallback." % e)
body = unicode(default_body).format(**parameters) body = unicode(default_body).format(**parameters)
except Exception as e: except Exception as e:
logger.error(u"Tautulli NotificationHandler :: Unable to parse custom notification body: %s. Using fallback." % e) logger.error(u"Tautulli NotificationHandler :: Unable to parse custom notification body: %s. Using fallback." % e)

View File

@@ -61,7 +61,6 @@ import mobile_app
import pmsconnect import pmsconnect
import request import request
from plexpy.config import _BLACKLIST_KEYS, _WHITELIST_KEYS from plexpy.config import _BLACKLIST_KEYS, _WHITELIST_KEYS
from plexpy.helpers import checked
AGENT_IDS = {'growl': 0, AGENT_IDS = {'growl': 0,

View File

@@ -542,8 +542,8 @@ class PmsConnect(object):
if metadata: if metadata:
_cache_time = metadata.pop('_cache_time', 0) _cache_time = metadata.pop('_cache_time', 0)
# Return cached metadata if less than 30 minutes ago # Return cached metadata if less than METADATA_CACHE_SECONDS ago
if int(time.time()) - _cache_time <= 1800: if int(time.time()) - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS:
return metadata return metadata
if rating_key: if rating_key:
@@ -1155,9 +1155,9 @@ class PmsConnect(object):
metadata['media_info'] = medias metadata['media_info'] = medias
if metadata: if metadata:
metadata['_cache_time'] = int(time.time())
if cache_key: if cache_key:
metadata['_cache_time'] = int(time.time())
out_file_path = os.path.join(plexpy.CONFIG.CACHE_DIR, 'metadata-sessionKey-%s.json' % cache_key) out_file_path = os.path.join(plexpy.CONFIG.CACHE_DIR, 'metadata-sessionKey-%s.json' % cache_key)
try: try:
with open(out_file_path, 'w') as outFile: with open(out_file_path, 'w') as outFile:

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta" PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.0.8-beta" PLEXPY_RELEASE_VERSION = "v2.0.12-beta"

View File

@@ -96,7 +96,8 @@ def check_credentials(username, password, admin_login='0'):
if plexpy.CONFIG.HTTP_HASHED_PASSWORD and \ if plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
username == plexpy.CONFIG.HTTP_USERNAME and check_hash(password, plexpy.CONFIG.HTTP_PASSWORD): username == plexpy.CONFIG.HTTP_USERNAME and check_hash(password, plexpy.CONFIG.HTTP_PASSWORD):
return True, u'admin' return True, u'admin'
elif username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD: elif not plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
return True, u'admin' return True, u'admin'
elif not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS and user_login(username, password): elif not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS and user_login(username, password):
return True, u'guest' return True, u'guest'

View File

@@ -3079,7 +3079,6 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@addtoapi("notify")
def send_notification(self, notifier_id=None, subject='Tautulli', body='Test notification', notify_action='', **kwargs): def send_notification(self, notifier_id=None, subject='Tautulli', body='Test notification', notify_action='', **kwargs):
""" Send a notification using Tautulli. """ Send a notification using Tautulli.
@@ -4440,7 +4439,9 @@ class WebInterface(object):
counts = {'stream_count_direct_play': 0, counts = {'stream_count_direct_play': 0,
'stream_count_direct_stream': 0, 'stream_count_direct_stream': 0,
'stream_count_transcode': 0, 'stream_count_transcode': 0,
'total_bandwidth': 0} 'total_bandwidth': 0,
'lan_bandwidth': 0,
'wan_bandwidth': 0}
for s in result['sessions']: for s in result['sessions']:
if s['transcode_decision'] == 'transcode': if s['transcode_decision'] == 'transcode':
@@ -4451,6 +4452,10 @@ class WebInterface(object):
counts['stream_count_direct_play'] += 1 counts['stream_count_direct_play'] += 1
counts['total_bandwidth'] += helpers.cast_to_int(s['bandwidth']) counts['total_bandwidth'] += helpers.cast_to_int(s['bandwidth'])
if s['location'] == 'lan':
counts['lan_bandwidth'] += helpers.cast_to_int(s['bandwidth'])
elif s['location'] == 'wan':
counts['wan_bandwidth'] += helpers.cast_to_int(s['bandwidth'])
result.update(counts) result.update(counts)