Compare commits
19 Commits
v2.0.15-be
...
v2.0.17-be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5d2219f2f8 | ||
![]() |
56dc28eed3 | ||
![]() |
3e723d4373 | ||
![]() |
f5e341e655 | ||
![]() |
3c81100957 | ||
![]() |
304378f93b | ||
![]() |
de6b6e8124 | ||
![]() |
d15223fb1a | ||
![]() |
d29a12b6db | ||
![]() |
9100e25a21 | ||
![]() |
7672f1955e | ||
![]() |
5f52171fc4 | ||
![]() |
31ac82ad71 | ||
![]() |
38ca4e37a6 | ||
![]() |
3c55550702 | ||
![]() |
7dff6b121b | ||
![]() |
d77d889695 | ||
![]() |
318a21438f | ||
![]() |
7175b57a28 |
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,5 +1,29 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.0.17-beta (2018-02-03)
|
||||||
|
|
||||||
|
* Notifications:
|
||||||
|
* Fix: Unable to use @ mentions tags for Discord and Slack.
|
||||||
|
* New: Added Zapier notification agent.
|
||||||
|
* API:
|
||||||
|
* Fix: get_synced_items returning no results.
|
||||||
|
* Fix: get_library_media_info returning incorrect media type for photo albums.
|
||||||
|
* Fix: get_library_media_info not being able to sort by title.
|
||||||
|
|
||||||
|
|
||||||
|
## v2.0.16-beta (2018-01-30)
|
||||||
|
|
||||||
|
* Monitoring:
|
||||||
|
* Fix: Timestamp sometimes showing as "0:60" on the activity cards.
|
||||||
|
* Fix: Incorrect session information being shown for playback of synced content.
|
||||||
|
* Fix: Sessions not being stopped when "Playback Stopped" notifications were enabled.
|
||||||
|
* UI:
|
||||||
|
* Fix: Stream resolution showing up as "unknown" on the graphs.
|
||||||
|
* New: Added user filter to the Synced Items table.
|
||||||
|
* Other:
|
||||||
|
* New: Option to use the Plex server update channel when checking for updates.
|
||||||
|
|
||||||
|
|
||||||
## v2.0.15-beta (2018-01-27)
|
## v2.0.15-beta (2018-01-27)
|
||||||
|
|
||||||
* Monitoring:
|
* Monitoring:
|
||||||
|
@@ -114,7 +114,7 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'get_user_names',
|
url: 'get_user_names',
|
||||||
type: 'get',
|
type: 'get',
|
||||||
dataType: "json",
|
dataType: 'json',
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
var select = $('#history-user');
|
var select = $('#history-user');
|
||||||
data.sort(function (a, b) {
|
data.sort(function (a, b) {
|
||||||
@@ -130,7 +130,6 @@
|
|||||||
function loadHistoryTable(media_type, selected_user_id) {
|
function loadHistoryTable(media_type, selected_user_id) {
|
||||||
history_table_options.ajax = {
|
history_table_options.ajax = {
|
||||||
url: 'get_history',
|
url: 'get_history',
|
||||||
type: 'post',
|
|
||||||
data: function (d) {
|
data: function (d) {
|
||||||
return {
|
return {
|
||||||
json_data: JSON.stringify(d),
|
json_data: JSON.stringify(d),
|
||||||
@@ -138,9 +137,13 @@
|
|||||||
user_id: selected_user_id
|
user_id: selected_user_id
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
history_table = $('#history_table').DataTable(history_table_options);
|
history_table = $('#history_table').DataTable(history_table_options);
|
||||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
|
var colvis = new $.fn.dataTable.ColVis(history_table, {
|
||||||
|
buttonText: '<i class="fa fa-columns"></i> Select columns',
|
||||||
|
buttonClass: 'btn btn-dark',
|
||||||
|
exclude: [0, 11]
|
||||||
|
});
|
||||||
$(colvis.button()).appendTo('div.colvis-button-bar');
|
$(colvis.button()).appendTo('div.colvis-button-bar');
|
||||||
|
|
||||||
clearSearchButton('history_table', history_table);
|
clearSearchButton('history_table', history_table);
|
||||||
@@ -160,7 +163,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var media_type = null;
|
var media_type = null;
|
||||||
var selected_user_id = "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
var selected_user_id = "${_session['user_id']}" == "None" ? null : "${_session['user_id']}";
|
||||||
loadHistoryTable(media_type, selected_user_id);
|
loadHistoryTable(media_type, selected_user_id);
|
||||||
|
|
||||||
% if _session['user_group'] == 'admin':
|
% if _session['user_group'] == 'admin':
|
||||||
|
@@ -309,14 +309,17 @@
|
|||||||
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'));
|
||||||
|
var lan_wan_bandwidth_header = '';
|
||||||
if (lan_bw) {
|
if (lan_bw) {
|
||||||
bandwidth_header += 'LAN: ' + ((lan_bw > 1000) ? ((lan_bw / 1000).toFixed(1) + ' Mbps') : (lan_bw + ' kbps')) + ', ';
|
lan_wan_bandwidth_header += 'LAN: ' + ((lan_bw > 1000) ? ((lan_bw / 1000).toFixed(1) + ' Mbps') : (lan_bw + ' kbps')) + ', ';
|
||||||
}
|
}
|
||||||
if (wan_bw) {
|
if (wan_bw) {
|
||||||
bandwidth_header += 'WAN: ' + ((wan_bw > 1000) ? ((wan_bw / 1000).toFixed(1) + ' Mbps') : (wan_bw + ' kbps')) + ', ';
|
lan_wan_bandwidth_header += 'WAN: ' + ((wan_bw > 1000) ? ((wan_bw / 1000).toFixed(1) + ' Mbps') : (wan_bw + ' kbps')) + ', ';
|
||||||
|
}
|
||||||
|
if (lan_wan_bandwidth_header) {
|
||||||
|
bandwidth_header += ' (' + lan_wan_bandwidth_header.replace(/, $/, '') + ')';
|
||||||
}
|
}
|
||||||
bandwidth_header = bandwidth_header.replace(/, $/, '') + ')';
|
|
||||||
$('#currentActivityHeader-bandwidth').text(bandwidth_header);
|
$('#currentActivityHeader-bandwidth').text(bandwidth_header);
|
||||||
|
|
||||||
$('#currentActivityHeader').show();
|
$('#currentActivityHeader').show();
|
||||||
|
@@ -290,19 +290,9 @@ String.prototype.toProperCase = function () {
|
|||||||
|
|
||||||
function millisecondsToMinutes(ms, roundToMinute) {
|
function millisecondsToMinutes(ms, roundToMinute) {
|
||||||
if (ms > 0) {
|
if (ms > 0) {
|
||||||
seconds = ms / 1000;
|
var minutes = Math.floor(ms / 60000);
|
||||||
minutes = seconds / 60;
|
var seconds = ((ms % 60000) / 1000).toFixed(0);
|
||||||
if (roundToMinute) {
|
return (seconds == 60 ? (minutes+1) + ":00" : minutes + ":" + (seconds < 10 ? "0" : "") + seconds);
|
||||||
output = Math.round(minutes, 0)
|
|
||||||
} else {
|
|
||||||
minutesFloor = Math.floor(minutes);
|
|
||||||
secondsReal = Math.round((seconds - (minutesFloor * 60)), 0);
|
|
||||||
if (secondsReal < 10) {
|
|
||||||
secondsReal = '0' + secondsReal;
|
|
||||||
}
|
|
||||||
output = minutesFloor + ':' + secondsReal;
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
} else {
|
} else {
|
||||||
if (roundToMinute) {
|
if (roundToMinute) {
|
||||||
return '0';
|
return '0';
|
||||||
|
@@ -21,7 +21,7 @@ history_table_options = {
|
|||||||
"infoFiltered": "<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
"infoFiltered": "<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
||||||
"emptyTable": "No data in table",
|
"emptyTable": "No data in table",
|
||||||
"loadingRecords": '<i class="fa fa-refresh fa-spin"></i> Loading items...</div>'
|
"loadingRecords": '<i class="fa fa-refresh fa-spin"></i> Loading items...</div>'
|
||||||
},
|
},
|
||||||
"pagingType": "full_numbers",
|
"pagingType": "full_numbers",
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"processing": false,
|
"processing": false,
|
||||||
@@ -172,7 +172,7 @@ history_table_options = {
|
|||||||
},
|
},
|
||||||
"width": "33%",
|
"width": "33%",
|
||||||
"className": "datatable-wrap"
|
"className": "datatable-wrap"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [7],
|
"targets": [7],
|
||||||
"data":"started",
|
"data":"started",
|
||||||
@@ -322,7 +322,7 @@ history_table_options = {
|
|||||||
$(row).addClass('current-activity-row');
|
$(row).addClass('current-activity-row');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Parent table platform modal
|
// Parent table platform modal
|
||||||
$('.history_table').on('click', '> tbody > tr > td.modal-control', function () {
|
$('.history_table').on('click', '> tbody > tr > td.modal-control', function () {
|
||||||
|
@@ -98,7 +98,7 @@ sync_table_options = {
|
|||||||
"data": "total_size",
|
"data": "total_size",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData > 0 ) {
|
if (cellData > 0 ) {
|
||||||
megabytes = Math.round((cellData/1024)/1024, 0)
|
megabytes = Math.round((cellData/1024)/1024, 0);
|
||||||
$(td).html(megabytes + 'MB');
|
$(td).html(megabytes + 'MB');
|
||||||
} else {
|
} else {
|
||||||
$(td).html('0MB');
|
$(td).html('0MB');
|
||||||
@@ -144,14 +144,16 @@ sync_table_options = {
|
|||||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||||
showMsg(msg, false, false, 0)
|
showMsg(msg, false, false, 0)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
$('#sync_table').on('click', 'td.delete-control > .edit-sync-toggles > button.delete-sync', function () {
|
$('#sync_table').on('click', 'td.delete-control > .edit-sync-toggles > button.delete-sync', function () {
|
||||||
var tr = $(this).parents('tr');
|
var tr = $(this).parents('tr');
|
||||||
var row = sync_table.row(tr);
|
var row = sync_table.row(tr);
|
||||||
var rowData = row.data();
|
var rowData = row.data();
|
||||||
|
|
||||||
var index_delete = syncs_to_delete.findIndex(x => x.client_id == rowData['client_id'] && x.sync_id == rowData['sync_id']);
|
var index_delete = syncs_to_delete.findIndex(function (x) {
|
||||||
|
return x.client_id === rowData['client_id'] && x.sync_id === rowData['sync_id'];
|
||||||
|
});
|
||||||
|
|
||||||
if (index_delete === -1) {
|
if (index_delete === -1) {
|
||||||
syncs_to_delete.push({ client_id: rowData['client_id'], sync_id: rowData['sync_id'] });
|
syncs_to_delete.push({ client_id: rowData['client_id'], sync_id: rowData['sync_id'] });
|
||||||
|
@@ -580,6 +580,18 @@
|
|||||||
});
|
});
|
||||||
var join_device_names = $join_device_names[0].selectize;
|
var join_device_names = $join_device_names[0].selectize;
|
||||||
join_device_names.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'join_device_names'), [])) | n});
|
join_device_names.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'join_device_names'), [])) | n});
|
||||||
|
|
||||||
|
% elif notifier['agent_name'] == 'zapier':
|
||||||
|
$('#zapier_test_hook').click(function () {
|
||||||
|
$.get('zapier_test_hook', { 'zapier_hook': $('#zapier_hook').val() }, function (data) {
|
||||||
|
if (data.result === 'success') {
|
||||||
|
showMsg('<i class="fa fa-check"></i> ' + data.msg, false, true, 5000);
|
||||||
|
} else {
|
||||||
|
showMsg('<i class="fa fa-times"></i> ' + data.msg, false, true, 5000, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
function validateLogic() {
|
function validateLogic() {
|
||||||
|
@@ -553,9 +553,10 @@
|
|||||||
<div id="pms_update_options">
|
<div id="pms_update_options">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2">
|
<div class="col-md-3">
|
||||||
<label for="pms_update_channel">Update Channel</label>
|
<label for="pms_update_channel">Update Channel</label>
|
||||||
<select class="form-control" id="pms_update_channel" name="pms_update_channel">
|
<select class="form-control" id="pms_update_channel" name="pms_update_channel">
|
||||||
|
<option value="plex">Use Server Setting</option>
|
||||||
<option value="public">Public</option>
|
<option value="public">Public</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -2104,32 +2105,41 @@ $(document).ready(function() {
|
|||||||
var update_channel = update_params.pms_update_channel;
|
var update_channel = update_params.pms_update_channel;
|
||||||
var update_distro = update_params.pms_update_distro;
|
var update_distro = update_params.pms_update_distro;
|
||||||
var update_distro_build = update_params.pms_update_distro_build;
|
var update_distro_build = update_params.pms_update_distro_build;
|
||||||
|
var plex_update_channel = update_params.plex_update_channel;
|
||||||
|
|
||||||
$("#pms_update_channel option[value='plexpass']").remove();
|
$('#pms_update_channel option[value=beta]').remove();
|
||||||
if (plexpass) {
|
if (plexpass) {
|
||||||
var selected = (update_channel == 'plexpass') ? true : false;
|
var selected = (update_channel == 'beta') ? true : false;
|
||||||
$('#pms_update_channel')
|
$('#pms_update_channel')
|
||||||
.append($('<option></option>')
|
.append($('<option></option>')
|
||||||
.text('Plex Pass')
|
.text('Beta')
|
||||||
.val('plexpass')
|
.val('beta')
|
||||||
.prop('selected', selected));
|
.prop('selected', selected));
|
||||||
}
|
}
|
||||||
|
|
||||||
$.getJSON('https://plex.tv/api/downloads/1.json?channel=' + update_channel, function (downloads) {
|
$.ajax({
|
||||||
platform_downloads = downloads.computer[platform] || downloads.nas[platform];
|
url: 'https://plex.tv/api/downloads/1.json?channel=' + plex_update_channel,
|
||||||
if (platform_downloads) {
|
type: 'GET',
|
||||||
$("#pms_update_distro_build option").remove();
|
dataType: 'json',
|
||||||
$.each(platform_downloads.releases, function (index, item) {
|
beforeSend: function (xhr) {
|
||||||
var label = (platform_downloads.releases.length == 1) ? platform_downloads.name : platform_downloads.name + ' - ' + item.label;
|
xhr.setRequestHeader('X-Plex-Token', $('#pms_token').val());
|
||||||
var selected = (item.distro == update_distro && item.build == update_distro_build) ? true : false;
|
},
|
||||||
$('#pms_update_distro_build')
|
success: function (downloads) {
|
||||||
.append($('<option></option>')
|
var platform_downloads = downloads.computer[platform] || downloads.nas[platform];
|
||||||
.text(label)
|
if (platform_downloads) {
|
||||||
.val(item.build)
|
$("#pms_update_distro_build option").remove();
|
||||||
.attr('data-distro', item.distro)
|
$.each(platform_downloads.releases, function (index, item) {
|
||||||
.prop('selected', selected));
|
var label = (platform_downloads.releases.length === 1) ? platform_downloads.name : platform_downloads.name + ' - ' + item.label;
|
||||||
})
|
var selected = (item.distro === update_distro && item.build === update_distro_build) ? true : false;
|
||||||
$('#pms_update_distro').val($("#pms_update_distro_build option:selected").data('distro'))
|
$('#pms_update_distro_build')
|
||||||
|
.append($('<option></option>')
|
||||||
|
.text(label)
|
||||||
|
.val(item.build)
|
||||||
|
.attr('data-distro', item.distro)
|
||||||
|
.prop('selected', selected));
|
||||||
|
});
|
||||||
|
$('#pms_update_distro').val($('#pms_update_distro_build option:selected').data('distro'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -27,6 +27,16 @@
|
|||||||
</button> 
|
</button> 
|
||||||
</div>
|
</div>
|
||||||
% endif
|
% endif
|
||||||
|
% if _session['user_group'] == 'admin':
|
||||||
|
<div class="btn-group" id="user-selection">
|
||||||
|
<label>
|
||||||
|
<select name="sync-user" id="sync-user" class="btn" style="color: inherit;">
|
||||||
|
<option value="">All Users</option>
|
||||||
|
<option disabled>────────────</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-dark refresh-syncs-button" id="refresh-syncs-list"><i class="fa fa-refresh"></i> Refresh synced items</button>
|
<button class="btn btn-dark refresh-syncs-button" id="refresh-syncs-list"><i class="fa fa-refresh"></i> Refresh synced items</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,17 +97,45 @@
|
|||||||
<script src="${http_root}js/tables/sync_table.js${cache_param}"></script>
|
<script src="${http_root}js/tables/sync_table.js${cache_param}"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
sync_table_options.ajax = {
|
// Load user ids and names (for the selector)
|
||||||
url: 'get_sync',
|
$.ajax({
|
||||||
data: function (d) {
|
url: 'get_user_names',
|
||||||
d.user_id = "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
type: 'get',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (data) {
|
||||||
|
var select = $('#sync-user');
|
||||||
|
data.sort(function (a, b) {
|
||||||
|
return a.friendly_name.localeCompare(b.friendly_name);
|
||||||
|
});
|
||||||
|
data.forEach(function (item) {
|
||||||
|
select.append('<option value="' + item.user_id + '">' +
|
||||||
|
item.friendly_name + '</option>');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
sync_table = $('#sync_table').DataTable(sync_table_options);
|
|
||||||
var colvis = new $.fn.dataTable.ColVis( sync_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0] } );
|
|
||||||
$( colvis.button() ).appendTo('div.colvis-button-bar');
|
|
||||||
|
|
||||||
clearSearchButton('sync_table', sync_table);
|
function loadSyncTable(selected_user_id) {
|
||||||
|
sync_table_options.ajax = {
|
||||||
|
url: 'get_sync?user_id=' + selected_user_id
|
||||||
|
};
|
||||||
|
sync_table = $('#sync_table').DataTable(sync_table_options);
|
||||||
|
var colvis = new $.fn.dataTable.ColVis(sync_table, {
|
||||||
|
buttonText: '<i class="fa fa-columns"></i> Select columns',
|
||||||
|
buttonClass: 'btn btn-dark',
|
||||||
|
exclude: [0]
|
||||||
|
});
|
||||||
|
$(colvis.button()).appendTo('div.colvis-button-bar');
|
||||||
|
|
||||||
|
clearSearchButton('sync_table', sync_table);
|
||||||
|
|
||||||
|
$('#sync-user').on('change', function () {
|
||||||
|
selected_user_id = $(this).val() || null;
|
||||||
|
sync_table.ajax.url('get_sync?user_id=' + selected_user_id).load();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected_user_id = "${_session['user_id']}" == "None" ? null : "${_session['user_id']}";
|
||||||
|
loadSyncTable(selected_user_id);
|
||||||
|
|
||||||
% if _session['user_group'] == 'admin':
|
% if _session['user_group'] == 'admin':
|
||||||
$('#row-edit-mode').on('click', function() {
|
$('#row-edit-mode').on('click', function() {
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
|
import shutil
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -156,6 +157,16 @@ def initialize(config_file):
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
logger.error(u"Could not create cache dir '%s': %s" % (CONFIG.CACHE_DIR, e))
|
logger.error(u"Could not create cache dir '%s': %s" % (CONFIG.CACHE_DIR, e))
|
||||||
|
|
||||||
|
if CONFIG.CACHE_DIR:
|
||||||
|
session_metadata_folder = os.path.join(CONFIG.CACHE_DIR, 'session_metadata')
|
||||||
|
try:
|
||||||
|
shutil.rmtree(session_metadata_folder, ignore_errors=True)
|
||||||
|
except OSError as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not os.path.exists(session_metadata_folder):
|
||||||
|
os.mkdir(session_metadata_folder)
|
||||||
|
|
||||||
# Initialize the database
|
# Initialize the database
|
||||||
logger.info(u"Checking if the database upgrades are required...")
|
logger.info(u"Checking if the database upgrades are required...")
|
||||||
try:
|
try:
|
||||||
|
@@ -97,14 +97,15 @@ class ActivityHandler(object):
|
|||||||
% (str(session['session_key']), str(session['user_id']), session['username'],
|
% (str(session['session_key']), str(session['user_id']), session['username'],
|
||||||
str(session['rating_key']), session['full_title']))
|
str(session['rating_key']), session['full_title']))
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': session, 'notify_action': 'on_play'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': session.copy(), 'notify_action': 'on_play'})
|
||||||
|
|
||||||
# Write the new session to our temp session table
|
# Write the new session to our temp session table
|
||||||
self.update_db_session(session=session)
|
self.update_db_session(session=session)
|
||||||
|
|
||||||
def on_stop(self, force_stop=False):
|
def on_stop(self, force_stop=False):
|
||||||
if self.is_valid_session():
|
if self.is_valid_session():
|
||||||
logger.debug(u"Tautulli ActivityHandler :: Session %s stopped." % str(self.get_session_key()))
|
logger.debug(u"Tautulli ActivityHandler :: Session %s %sstopped."
|
||||||
|
% (str(self.get_session_key()), 'force ' if force_stop else ''))
|
||||||
|
|
||||||
# Set the session last_paused timestamp
|
# Set the session last_paused timestamp
|
||||||
ap = activity_processor.ActivityProcessor()
|
ap = activity_processor.ActivityProcessor()
|
||||||
@@ -121,7 +122,7 @@ class ActivityHandler(object):
|
|||||||
# Retrieve the session data from our temp table
|
# Retrieve the session data from our temp table
|
||||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_stop'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_stop'})
|
||||||
|
|
||||||
# Write it to the history table
|
# Write it to the history table
|
||||||
monitor_proc = activity_processor.ActivityProcessor()
|
monitor_proc = activity_processor.ActivityProcessor()
|
||||||
@@ -158,7 +159,7 @@ class ActivityHandler(object):
|
|||||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||||
|
|
||||||
if not still_paused:
|
if not still_paused:
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_pause'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_pause'})
|
||||||
|
|
||||||
def on_resume(self):
|
def on_resume(self):
|
||||||
if self.is_valid_session():
|
if self.is_valid_session():
|
||||||
@@ -177,7 +178,7 @@ class ActivityHandler(object):
|
|||||||
# Retrieve the session data from our temp table
|
# Retrieve the session data from our temp table
|
||||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_resume'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_resume'})
|
||||||
|
|
||||||
def on_buffer(self):
|
def on_buffer(self):
|
||||||
if self.is_valid_session():
|
if self.is_valid_session():
|
||||||
@@ -215,7 +216,7 @@ class ActivityHandler(object):
|
|||||||
# Retrieve the session data from our temp table
|
# Retrieve the session data from our temp table
|
||||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_buffer'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_buffer'})
|
||||||
|
|
||||||
# This function receives events from our websocket connection
|
# This function receives events from our websocket connection
|
||||||
def process(self):
|
def process(self):
|
||||||
@@ -230,7 +231,7 @@ class ActivityHandler(object):
|
|||||||
if db_session:
|
if db_session:
|
||||||
# Re-schedule the callback to reset the 5 minutes timer
|
# Re-schedule the callback to reset the 5 minutes timer
|
||||||
schedule_callback('session_key-{}'.format(self.get_session_key()),
|
schedule_callback('session_key-{}'.format(self.get_session_key()),
|
||||||
function=force_stop_stream, args=[self.get_session_key()], minutes=5)
|
func=force_stop_stream, args=[self.get_session_key()], minutes=5)
|
||||||
|
|
||||||
last_state = db_session['state']
|
last_state = db_session['state']
|
||||||
last_key = str(db_session['rating_key'])
|
last_key = str(db_session['rating_key'])
|
||||||
@@ -278,7 +279,7 @@ class ActivityHandler(object):
|
|||||||
db_session['media_type'] == 'episode' and progress_percent >= plexpy.CONFIG.TV_WATCHED_PERCENT or
|
db_session['media_type'] == 'episode' and progress_percent >= plexpy.CONFIG.TV_WATCHED_PERCENT or
|
||||||
db_session['media_type'] == 'track' and progress_percent >= plexpy.CONFIG.MUSIC_WATCHED_PERCENT) \
|
db_session['media_type'] == 'track' and progress_percent >= plexpy.CONFIG.MUSIC_WATCHED_PERCENT) \
|
||||||
and not any(d['notify_action'] == 'on_watched' for d in notify_states):
|
and not any(d['notify_action'] == 'on_watched' for d in notify_states):
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_watched'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_watched'})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# We don't have this session in our table yet, start a new one.
|
# We don't have this session in our table yet, start a new one.
|
||||||
@@ -287,7 +288,7 @@ class ActivityHandler(object):
|
|||||||
|
|
||||||
# Schedule a callback to force stop a stale stream 5 minutes later
|
# Schedule a callback to force stop a stale stream 5 minutes later
|
||||||
schedule_callback('session_key-{}'.format(self.get_session_key()),
|
schedule_callback('session_key-{}'.format(self.get_session_key()),
|
||||||
function=force_stop_stream, args=[self.get_session_key()], minutes=5)
|
func=force_stop_stream, args=[self.get_session_key()], minutes=5)
|
||||||
|
|
||||||
|
|
||||||
class TimelineHandler(object):
|
class TimelineHandler(object):
|
||||||
@@ -370,7 +371,7 @@ class TimelineHandler(object):
|
|||||||
% (title, str(rating_key), str(grandparent_rating_key)))
|
% (title, str(rating_key), str(grandparent_rating_key)))
|
||||||
|
|
||||||
# Schedule a callback to clear the recently added queue
|
# Schedule a callback to clear the recently added queue
|
||||||
schedule_callback('rating_key-{}'.format(grandparent_rating_key), function=clear_recently_added_queue,
|
schedule_callback('rating_key-{}'.format(grandparent_rating_key), func=clear_recently_added_queue,
|
||||||
args=[grandparent_rating_key], seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY)
|
args=[grandparent_rating_key], seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY)
|
||||||
|
|
||||||
elif media_type in ('season', 'album'):
|
elif media_type in ('season', 'album'):
|
||||||
@@ -386,7 +387,7 @@ class TimelineHandler(object):
|
|||||||
% (title, str(rating_key), str(parent_rating_key)))
|
% (title, str(rating_key), str(parent_rating_key)))
|
||||||
|
|
||||||
# Schedule a callback to clear the recently added queue
|
# Schedule a callback to clear the recently added queue
|
||||||
schedule_callback('rating_key-{}'.format(parent_rating_key), function=clear_recently_added_queue,
|
schedule_callback('rating_key-{}'.format(parent_rating_key), func=clear_recently_added_queue,
|
||||||
args=[parent_rating_key], seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY)
|
args=[parent_rating_key], seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -397,7 +398,7 @@ class TimelineHandler(object):
|
|||||||
% (title, str(rating_key)))
|
% (title, str(rating_key)))
|
||||||
|
|
||||||
# Schedule a callback to clear the recently added queue
|
# Schedule a callback to clear the recently added queue
|
||||||
schedule_callback('rating_key-{}'.format(rating_key), function=clear_recently_added_queue,
|
schedule_callback('rating_key-{}'.format(rating_key), func=clear_recently_added_queue,
|
||||||
args=[rating_key], seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY)
|
args=[rating_key], seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY)
|
||||||
|
|
||||||
# A movie, show, or artist is done processing
|
# A movie, show, or artist is done processing
|
||||||
@@ -427,7 +428,7 @@ def del_keys(key):
|
|||||||
del_keys(RECENTLY_ADDED_QUEUE.pop(key))
|
del_keys(RECENTLY_ADDED_QUEUE.pop(key))
|
||||||
|
|
||||||
|
|
||||||
def schedule_callback(id, function=None, remove_job=False, args=None, **kwargs):
|
def schedule_callback(id, func=None, remove_job=False, args=None, **kwargs):
|
||||||
if ACTIVITY_SCHED.get_job(id):
|
if ACTIVITY_SCHED.get_job(id):
|
||||||
if remove_job:
|
if remove_job:
|
||||||
ACTIVITY_SCHED.remove_job(id)
|
ACTIVITY_SCHED.remove_job(id)
|
||||||
@@ -437,7 +438,7 @@ def schedule_callback(id, function=None, remove_job=False, args=None, **kwargs):
|
|||||||
run_date=datetime.datetime.now() + datetime.timedelta(**kwargs)))
|
run_date=datetime.datetime.now() + datetime.timedelta(**kwargs)))
|
||||||
elif not remove_job:
|
elif not remove_job:
|
||||||
ACTIVITY_SCHED.add_job(
|
ACTIVITY_SCHED.add_job(
|
||||||
function, args=args, id=id, trigger=DateTrigger(
|
func, args=args, id=id, trigger=DateTrigger(
|
||||||
run_date=datetime.datetime.now() + datetime.timedelta(**kwargs)))
|
run_date=datetime.datetime.now() + datetime.timedelta(**kwargs)))
|
||||||
|
|
||||||
|
|
||||||
@@ -448,7 +449,7 @@ def force_stop_stream(session_key):
|
|||||||
row_id = ap.write_session_history(session=session)
|
row_id = ap.write_session_history(session=session)
|
||||||
|
|
||||||
if row_id:
|
if row_id:
|
||||||
# If session is written to the databaase successfully, remove the session from the session table
|
# If session is written to the database successfully, remove the session from the session table
|
||||||
logger.info(u"Tautulli ActivityHandler :: Removing stale stream with sessionKey %s ratingKey %s from session queue"
|
logger.info(u"Tautulli ActivityHandler :: Removing stale stream with sessionKey %s ratingKey %s from session queue"
|
||||||
% (session['session_key'], session['rating_key']))
|
% (session['session_key'], session['rating_key']))
|
||||||
ap.delete_session(row_id=row_id)
|
ap.delete_session(row_id=row_id)
|
||||||
@@ -464,7 +465,7 @@ def force_stop_stream(session_key):
|
|||||||
ap.increment_write_attempts(session_key=session_key)
|
ap.increment_write_attempts(session_key=session_key)
|
||||||
|
|
||||||
# Reschedule for 30 seconds later
|
# Reschedule for 30 seconds later
|
||||||
schedule_callback('session_key-{}'.format(session_key), function=force_stop_stream,
|
schedule_callback('session_key-{}'.format(session_key), func=force_stop_stream,
|
||||||
args=[session_key], seconds=30)
|
args=[session_key], seconds=30)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -545,7 +546,7 @@ def on_created(rating_key, **kwargs):
|
|||||||
|
|
||||||
def delete_metadata_cache(session_key):
|
def delete_metadata_cache(session_key):
|
||||||
try:
|
try:
|
||||||
os.remove(os.path.join(plexpy.CONFIG.CACHE_DIR, 'metadata-sessionKey-%s.json' % session_key))
|
os.remove(os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata/metadata-sessionKey-%s.json' % session_key))
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error(u"Tautulli ActivityHandler :: Failed to remove metadata cache file (sessionKey %s): %s"
|
logger.error(u"Tautulli ActivityHandler :: Failed to remove metadata cache file (sessionKey %s): %s"
|
||||||
% (session_key, e))
|
% (session_key, e))
|
||||||
|
@@ -61,12 +61,12 @@ def check_active_sessions(ws_request=False):
|
|||||||
if session['state'] == 'paused':
|
if session['state'] == 'paused':
|
||||||
logger.debug(u"Tautulli Monitor :: Session %s paused." % stream['session_key'])
|
logger.debug(u"Tautulli Monitor :: Session %s paused." % stream['session_key'])
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream, 'notify_action': 'on_pause'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream.copy(), 'notify_action': 'on_pause'})
|
||||||
|
|
||||||
if session['state'] == 'playing' and stream['state'] == 'paused':
|
if session['state'] == 'playing' and stream['state'] == 'paused':
|
||||||
logger.debug(u"Tautulli Monitor :: Session %s resumed." % stream['session_key'])
|
logger.debug(u"Tautulli Monitor :: Session %s resumed." % stream['session_key'])
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream, 'notify_action': 'on_resume'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream.copy(), 'notify_action': 'on_resume'})
|
||||||
|
|
||||||
if stream['state'] == 'paused' and not ws_request:
|
if stream['state'] == 'paused' and not ws_request:
|
||||||
# The stream is still paused so we need to increment the paused_counter
|
# The stream is still paused so we need to increment the paused_counter
|
||||||
@@ -104,7 +104,7 @@ def check_active_sessions(ws_request=False):
|
|||||||
'WHERE session_key = ? AND rating_key = ?',
|
'WHERE session_key = ? AND rating_key = ?',
|
||||||
[stream['session_key'], stream['rating_key']])
|
[stream['session_key'], stream['rating_key']])
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream, 'notify_action': 'on_buffer'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream.copy(), 'notify_action': 'on_buffer'})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Subsequent buffer notifications after wait time
|
# Subsequent buffer notifications after wait time
|
||||||
@@ -118,7 +118,7 @@ def check_active_sessions(ws_request=False):
|
|||||||
'WHERE session_key = ? AND rating_key = ?',
|
'WHERE session_key = ? AND rating_key = ?',
|
||||||
[stream['session_key'], stream['rating_key']])
|
[stream['session_key'], stream['rating_key']])
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream, 'notify_action': 'on_buffer'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream.copy(), 'notify_action': 'on_buffer'})
|
||||||
|
|
||||||
logger.debug(u"Tautulli Monitor :: Session %s is buffering. Count is now %s. Last triggered %s."
|
logger.debug(u"Tautulli Monitor :: Session %s is buffering. Count is now %s. Last triggered %s."
|
||||||
% (stream['session_key'],
|
% (stream['session_key'],
|
||||||
@@ -135,7 +135,7 @@ def check_active_sessions(ws_request=False):
|
|||||||
session['media_type'] == 'episode' and progress_percent >= plexpy.CONFIG.TV_WATCHED_PERCENT or
|
session['media_type'] == 'episode' and progress_percent >= plexpy.CONFIG.TV_WATCHED_PERCENT or
|
||||||
session['media_type'] == 'track' and progress_percent >= plexpy.CONFIG.MUSIC_WATCHED_PERCENT) \
|
session['media_type'] == 'track' and progress_percent >= plexpy.CONFIG.MUSIC_WATCHED_PERCENT) \
|
||||||
and not any(d['notify_action'] == 'on_watched' for d in notify_states):
|
and not any(d['notify_action'] == 'on_watched' for d in notify_states):
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream, 'notify_action': 'on_watched'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream.copy(), 'notify_action': 'on_watched'})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# The user has stopped playing a stream
|
# The user has stopped playing a stream
|
||||||
@@ -155,9 +155,9 @@ def check_active_sessions(ws_request=False):
|
|||||||
stream['media_type'] == 'episode' and progress_percent >= plexpy.CONFIG.TV_WATCHED_PERCENT or
|
stream['media_type'] == 'episode' and progress_percent >= plexpy.CONFIG.TV_WATCHED_PERCENT or
|
||||||
stream['media_type'] == 'track' and progress_percent >= plexpy.CONFIG.MUSIC_WATCHED_PERCENT) \
|
stream['media_type'] == 'track' and progress_percent >= plexpy.CONFIG.MUSIC_WATCHED_PERCENT) \
|
||||||
and not any(d['notify_action'] == 'on_watched' for d in notify_states):
|
and not any(d['notify_action'] == 'on_watched' for d in notify_states):
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream, 'notify_action': 'on_watched'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream.copy(), 'notify_action': 'on_watched'})
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream, 'notify_action': 'on_stop'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream.copy(), 'notify_action': 'on_stop'})
|
||||||
|
|
||||||
# Write the item history on playback stop
|
# Write the item history on playback stop
|
||||||
row_id = monitor_process.write_session_history(session=stream)
|
row_id = monitor_process.write_session_history(session=stream)
|
||||||
@@ -243,7 +243,7 @@ def check_recently_added():
|
|||||||
if 0 < time_threshold - int(item['added_at']) <= time_interval:
|
if 0 < time_threshold - int(item['added_at']) <= time_interval:
|
||||||
logger.debug(u"Tautulli Monitor :: Library item %s added to Plex." % str(item['rating_key']))
|
logger.debug(u"Tautulli Monitor :: Library item %s added to Plex." % str(item['rating_key']))
|
||||||
|
|
||||||
plexpy.NOTIFY_QUEUE.put({'timeline_data': item, 'notify_action': 'on_created'})
|
plexpy.NOTIFY_QUEUE.put({'timeline_data': item.copy(), 'notify_action': 'on_created'})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
item = max(metadata, key=lambda x:x['added_at'])
|
item = max(metadata, key=lambda x:x['added_at'])
|
||||||
@@ -261,7 +261,7 @@ def check_recently_added():
|
|||||||
logger.debug(u"Tautulli Monitor :: Library item %s added to Plex." % str(item['rating_key']))
|
logger.debug(u"Tautulli Monitor :: Library item %s added to Plex." % str(item['rating_key']))
|
||||||
|
|
||||||
# Check if any notification agents have notifications enabled
|
# Check if any notification agents have notifications enabled
|
||||||
plexpy.NOTIFY_QUEUE.put({'timeline_data': item, 'notify_action': 'on_created'})
|
plexpy.NOTIFY_QUEUE.put({'timeline_data': item.copy(), 'notify_action': 'on_created'})
|
||||||
|
|
||||||
|
|
||||||
def check_server_response():
|
def check_server_response():
|
||||||
|
@@ -127,7 +127,7 @@ class ActivityProcessor(object):
|
|||||||
if result == 'insert':
|
if result == 'insert':
|
||||||
# Check if any notification agents have notifications enabled
|
# Check if any notification agents have notifications enabled
|
||||||
if notify:
|
if notify:
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': values, 'notify_action': 'on_play'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': values.copy(), 'notify_action': 'on_play'})
|
||||||
|
|
||||||
# If it's our first write then time stamp it.
|
# If it's our first write then time stamp it.
|
||||||
started = int(time.time())
|
started = int(time.time())
|
||||||
@@ -235,7 +235,8 @@ class ActivityProcessor(object):
|
|||||||
## TODO: Fix media info from imports. Temporary media info from import session.
|
## TODO: Fix media info from imports. Temporary media info from import session.
|
||||||
media_info = session
|
media_info = session
|
||||||
|
|
||||||
# logger.debug(u"Tautulli ActivityProcessor :: Attempting to write to session_history table...")
|
# logger.debug(u"Tautulli ActivityProcessor :: Attempting to write sessionKey %s to session_history table..."
|
||||||
|
# % session['session_key'])
|
||||||
keys = {'id': None}
|
keys = {'id': None}
|
||||||
values = {'started': session['started'],
|
values = {'started': session['started'],
|
||||||
'stopped': stopped,
|
'stopped': stopped,
|
||||||
@@ -260,7 +261,8 @@ class ActivityProcessor(object):
|
|||||||
'view_offset': session['view_offset']
|
'view_offset': session['view_offset']
|
||||||
}
|
}
|
||||||
|
|
||||||
# logger.debug(u"Tautulli ActivityProcessor :: Writing session_history transaction...")
|
# logger.debug(u"Tautulli ActivityProcessor :: Writing sessionKey %s session_history transaction..."
|
||||||
|
# % session['session_key'])
|
||||||
self.db.upsert(table_name='session_history', key_dict=keys, value_dict=values)
|
self.db.upsert(table_name='session_history', key_dict=keys, value_dict=values)
|
||||||
|
|
||||||
# Check if we should group the session, select the last two rows from the user
|
# Check if we should group the session, select the last two rows from the user
|
||||||
@@ -304,7 +306,8 @@ class ActivityProcessor(object):
|
|||||||
|
|
||||||
# Write the session_history_media_info table
|
# Write the session_history_media_info table
|
||||||
|
|
||||||
# logger.debug(u"Tautulli ActivityProcessor :: Attempting to write to session_history_media_info table...")
|
# logger.debug(u"Tautulli ActivityProcessor :: Attempting to write to sessionKey %s session_history_media_info table..."
|
||||||
|
# % session['session_key'])
|
||||||
keys = {'id': last_id}
|
keys = {'id': last_id}
|
||||||
values = {'rating_key': session['rating_key'],
|
values = {'rating_key': session['rating_key'],
|
||||||
'video_decision': session['video_decision'],
|
'video_decision': session['video_decision'],
|
||||||
@@ -371,7 +374,8 @@ class ActivityProcessor(object):
|
|||||||
'optimized_version_title': session['optimized_version_title']
|
'optimized_version_title': session['optimized_version_title']
|
||||||
}
|
}
|
||||||
|
|
||||||
# logger.debug(u"Tautulli ActivityProcessor :: Writing session_history_media_info transaction...")
|
# logger.debug(u"Tautulli ActivityProcessor :: Writing sessionKey %s session_history_media_info transaction..."
|
||||||
|
# % session['session_key'])
|
||||||
self.db.upsert(table_name='session_history_media_info', key_dict=keys, value_dict=values)
|
self.db.upsert(table_name='session_history_media_info', key_dict=keys, value_dict=values)
|
||||||
|
|
||||||
# Write the session_history_metadata table
|
# Write the session_history_metadata table
|
||||||
@@ -381,7 +385,8 @@ class ActivityProcessor(object):
|
|||||||
genres = ";".join(metadata['genres'])
|
genres = ";".join(metadata['genres'])
|
||||||
labels = ";".join(metadata['labels'])
|
labels = ";".join(metadata['labels'])
|
||||||
|
|
||||||
# logger.debug(u"Tautulli ActivityProcessor :: Attempting to write to session_history_metadata table...")
|
# logger.debug(u"Tautulli ActivityProcessor :: Attempting to write to sessionKey %s session_history_metadata table..."
|
||||||
|
# % session['session_key'])
|
||||||
keys = {'id': last_id}
|
keys = {'id': last_id}
|
||||||
values = {'rating_key': session['rating_key'],
|
values = {'rating_key': session['rating_key'],
|
||||||
'parent_rating_key': session['parent_rating_key'],
|
'parent_rating_key': session['parent_rating_key'],
|
||||||
@@ -417,7 +422,8 @@ class ActivityProcessor(object):
|
|||||||
'labels': labels
|
'labels': labels
|
||||||
}
|
}
|
||||||
|
|
||||||
# logger.debug(u"Tautulli ActivityProcessor :: Writing session_history_metadata transaction...")
|
# logger.debug(u"Tautulli ActivityProcessor :: Writing sessionKey %s session_history_metadata transaction..."
|
||||||
|
# % session['session_key'])
|
||||||
self.db.upsert(table_name='session_history_metadata', key_dict=keys, value_dict=values)
|
self.db.upsert(table_name='session_history_metadata', key_dict=keys, value_dict=values)
|
||||||
|
|
||||||
# Return the session row id when the session is successfully written to the database
|
# Return the session row id when the session is successfully written to the database
|
||||||
|
@@ -61,7 +61,7 @@ _CONFIG_DEFINITIONS = {
|
|||||||
'PMS_PLEXPASS': (int, 'PMS', 0),
|
'PMS_PLEXPASS': (int, 'PMS', 0),
|
||||||
'PMS_PLATFORM': (str, 'PMS', ''),
|
'PMS_PLATFORM': (str, 'PMS', ''),
|
||||||
'PMS_VERSION': (str, 'PMS', ''),
|
'PMS_VERSION': (str, 'PMS', ''),
|
||||||
'PMS_UPDATE_CHANNEL': (str, 'PMS', 'public'),
|
'PMS_UPDATE_CHANNEL': (str, 'PMS', 'plex'),
|
||||||
'PMS_UPDATE_DISTRO': (str, 'PMS', ''),
|
'PMS_UPDATE_DISTRO': (str, 'PMS', ''),
|
||||||
'PMS_UPDATE_DISTRO_BUILD': (str, 'PMS', ''),
|
'PMS_UPDATE_DISTRO_BUILD': (str, 'PMS', ''),
|
||||||
'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'),
|
'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'),
|
||||||
@@ -876,3 +876,9 @@ class Config(object):
|
|||||||
self.MUSIC_WATCHED_PERCENT = self.NOTIFY_WATCHED_PERCENT
|
self.MUSIC_WATCHED_PERCENT = self.NOTIFY_WATCHED_PERCENT
|
||||||
|
|
||||||
self.CONFIG_VERSION = 9
|
self.CONFIG_VERSION = 9
|
||||||
|
|
||||||
|
if self.CONFIG_VERSION == 9:
|
||||||
|
if self.PMS_UPDATE_CHANNEL == 'plexpass':
|
||||||
|
self.PMS_UPDATE_CHANNEL = 'beta'
|
||||||
|
|
||||||
|
self.CONFIG_VERSION = 10
|
||||||
|
@@ -698,6 +698,10 @@ class Graphs(object):
|
|||||||
series_3 = []
|
series_3 = []
|
||||||
|
|
||||||
for item in result:
|
for item in result:
|
||||||
|
if item['resolution'] not in ('4k', 'unknown'):
|
||||||
|
item['resolution'] = item['resolution'].upper()
|
||||||
|
if item['resolution'].isdigit():
|
||||||
|
item['resolution'] += 'p'
|
||||||
categories.append(item['resolution'])
|
categories.append(item['resolution'])
|
||||||
series_1.append(item['dp_count'])
|
series_1.append(item['dp_count'])
|
||||||
series_2.append(item['ds_count'])
|
series_2.append(item['ds_count'])
|
||||||
@@ -729,16 +733,18 @@ class Graphs(object):
|
|||||||
try:
|
try:
|
||||||
if y_axis == 'plays':
|
if y_axis == 'plays':
|
||||||
query = 'SELECT ' \
|
query = 'SELECT ' \
|
||||||
|
'(CASE WHEN session_history_media_info.stream_video_resolution IS NULL THEN ' \
|
||||||
'(CASE WHEN session_history_media_info.video_decision = "transcode" THEN ' \
|
'(CASE WHEN session_history_media_info.video_decision = "transcode" THEN ' \
|
||||||
'(CASE ' \
|
'(CASE ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 360 THEN "sd" ' \
|
'WHEN session_history_media_info.transcode_height <= 360 THEN "SD" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 480 THEN "480" ' \
|
'WHEN session_history_media_info.transcode_height <= 480 THEN "480" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 576 THEN "576" ' \
|
'WHEN session_history_media_info.transcode_height <= 576 THEN "576" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 720 THEN "720" ' \
|
'WHEN session_history_media_info.transcode_height <= 720 THEN "720" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 1080 THEN "1080" ' \
|
'WHEN session_history_media_info.transcode_height <= 1080 THEN "1080" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
|
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \
|
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4k" ' \
|
||||||
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
|
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) ' \
|
||||||
|
'ELSE session_history_media_info.stream_video_resolution END) AS resolution, ' \
|
||||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||||
'THEN 1 ELSE 0 END) AS dp_count, ' \
|
'THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||||
@@ -758,16 +764,18 @@ class Graphs(object):
|
|||||||
result = monitor_db.select(query)
|
result = monitor_db.select(query)
|
||||||
else:
|
else:
|
||||||
query = 'SELECT ' \
|
query = 'SELECT ' \
|
||||||
|
'(CASE WHEN session_history_media_info.stream_video_resolution IS NULL THEN ' \
|
||||||
'(CASE WHEN session_history_media_info.video_decision = "transcode" THEN ' \
|
'(CASE WHEN session_history_media_info.video_decision = "transcode" THEN ' \
|
||||||
'(CASE ' \
|
'(CASE ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 360 THEN "sd" ' \
|
'WHEN session_history_media_info.transcode_height <= 360 THEN "SD" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 480 THEN "480" ' \
|
'WHEN session_history_media_info.transcode_height <= 480 THEN "480" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 576 THEN "576" ' \
|
'WHEN session_history_media_info.transcode_height <= 576 THEN "576" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 720 THEN "720" ' \
|
'WHEN session_history_media_info.transcode_height <= 720 THEN "720" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 1080 THEN "1080" ' \
|
'WHEN session_history_media_info.transcode_height <= 1080 THEN "1080" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
|
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
|
||||||
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \
|
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4k" ' \
|
||||||
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
|
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) ' \
|
||||||
|
'ELSE session_history_media_info.stream_video_resolution END) AS resolution, ' \
|
||||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||||
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
|
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
|
||||||
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
|
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
|
||||||
@@ -799,6 +807,10 @@ class Graphs(object):
|
|||||||
series_3 = []
|
series_3 = []
|
||||||
|
|
||||||
for item in result:
|
for item in result:
|
||||||
|
if item['resolution'] not in ('4k', 'unknown'):
|
||||||
|
item['resolution'] = item['resolution'].upper()
|
||||||
|
if item['resolution'].isdigit():
|
||||||
|
item['resolution'] += 'p'
|
||||||
categories.append(item['resolution'])
|
categories.append(item['resolution'])
|
||||||
series_1.append(item['dp_count'])
|
series_1.append(item['dp_count'])
|
||||||
series_2.append(item['ds_count'])
|
series_2.append(item['ds_count'])
|
||||||
|
@@ -544,19 +544,19 @@ class Libraries(object):
|
|||||||
filtered_count = len(results)
|
filtered_count = len(results)
|
||||||
|
|
||||||
# Sort results
|
# Sort results
|
||||||
results = sorted(results, key=lambda k: k['sort_title'])
|
results = sorted(results, key=lambda k: k['sort_title'].lower())
|
||||||
sort_order = json_data['order']
|
sort_order = json_data['order']
|
||||||
for order in reversed(sort_order):
|
for order in reversed(sort_order):
|
||||||
sort_key = json_data['columns'][int(order['column'])]['data']
|
sort_key = json_data['columns'][int(order['column'])]['data']
|
||||||
reverse = True if order['dir'] == 'desc' else False
|
reverse = True if order['dir'] == 'desc' else False
|
||||||
if rating_key and sort_key == 'sort_title':
|
if rating_key and sort_key == 'sort_title':
|
||||||
results = sorted(results, key=lambda k: helpers.cast_to_int(k['media_index']), reverse=reverse)
|
results = sorted(results, key=lambda k: helpers.cast_to_int(k['media_index']), reverse=reverse)
|
||||||
elif sort_key == 'file_size' or sort_key == 'bitrate':
|
elif sort_key in ('file_size', 'bitrate', 'added_at', 'last_played', 'play_count'):
|
||||||
results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key]), reverse=reverse)
|
results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key]), reverse=reverse)
|
||||||
elif sort_key == 'video_resolution':
|
elif sort_key == 'video_resolution':
|
||||||
results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key].replace('4k', '2160p').rstrip('p')), reverse=reverse)
|
results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key].replace('4k', '2160p').rstrip('p')), reverse=reverse)
|
||||||
else:
|
else:
|
||||||
results = sorted(results, key=lambda k: k[sort_key], reverse=reverse)
|
results = sorted(results, key=lambda k: k[sort_key].lower(), reverse=reverse)
|
||||||
|
|
||||||
total_file_size = sum([helpers.cast_to_int(d['file_size']) for d in results])
|
total_file_size = sum([helpers.cast_to_int(d['file_size']) for d in results])
|
||||||
|
|
||||||
|
@@ -122,8 +122,8 @@ def add_notifier_each(notifier_id=None, notify_action=None, stream_data=None, ti
|
|||||||
|
|
||||||
# Add on_concurrent and on_newdevice to queue if action is on_play
|
# Add on_concurrent and on_newdevice to queue if action is on_play
|
||||||
if notify_action == 'on_play':
|
if notify_action == 'on_play':
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream_data, 'notify_action': 'on_concurrent'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream_data.copy(), 'notify_action': 'on_concurrent'})
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream_data, 'notify_action': 'on_newdevice'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream_data.copy(), 'notify_action': 'on_newdevice'})
|
||||||
|
|
||||||
|
|
||||||
def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
|
def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
|
||||||
@@ -839,6 +839,8 @@ def build_server_notify_params(notify_action=None, **kwargs):
|
|||||||
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
|
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
|
||||||
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
|
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
|
||||||
|
|
||||||
|
update_channel = pmsconnect.PmsConnect().get_server_update_channel()
|
||||||
|
|
||||||
pms_download_info = defaultdict(str, kwargs.pop('pms_download_info', {}))
|
pms_download_info = defaultdict(str, kwargs.pop('pms_download_info', {}))
|
||||||
plexpy_download_info = defaultdict(str, kwargs.pop('plexpy_download_info', {}))
|
plexpy_download_info = defaultdict(str, kwargs.pop('plexpy_download_info', {}))
|
||||||
|
|
||||||
@@ -864,7 +866,7 @@ def build_server_notify_params(notify_action=None, **kwargs):
|
|||||||
'update_url': pms_download_info['download_url'],
|
'update_url': pms_download_info['download_url'],
|
||||||
'update_release_date': arrow.get(pms_download_info['release_date']).format(date_format)
|
'update_release_date': arrow.get(pms_download_info['release_date']).format(date_format)
|
||||||
if pms_download_info['release_date'] else '',
|
if pms_download_info['release_date'] else '',
|
||||||
'update_channel': 'Beta' if plexpy.CONFIG.PMS_UPDATE_CHANNEL == 'plexpass' else 'Public',
|
'update_channel': 'Beta' if update_channel == 'beta' else 'Public',
|
||||||
'update_platform': pms_download_info['platform'],
|
'update_platform': pms_download_info['platform'],
|
||||||
'update_distro': pms_download_info['distro'],
|
'update_distro': pms_download_info['distro'],
|
||||||
'update_distro_build': pms_download_info['build'],
|
'update_distro_build': pms_download_info['build'],
|
||||||
@@ -978,8 +980,8 @@ def strip_tag(data, agent_id=None):
|
|||||||
'font': ['color']}
|
'font': ['color']}
|
||||||
return bleach.clean(data, tags=whitelist.keys(), attributes=whitelist, strip=True)
|
return bleach.clean(data, tags=whitelist.keys(), attributes=whitelist, strip=True)
|
||||||
|
|
||||||
elif agent_id == 10:
|
elif agent_id in (10, 14, 20):
|
||||||
# Don't remove tags for email
|
# Don't remove tags for Email, Slack, and Discord
|
||||||
return data
|
return data
|
||||||
|
|
||||||
elif agent_id == 13:
|
elif agent_id == 13:
|
||||||
|
@@ -90,7 +90,8 @@ AGENT_IDS = {'growl': 0,
|
|||||||
'discord': 20,
|
'discord': 20,
|
||||||
'androidapp': 21,
|
'androidapp': 21,
|
||||||
'groupme': 22,
|
'groupme': 22,
|
||||||
'mqtt': 23
|
'mqtt': 23,
|
||||||
|
'zapier': 24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -186,6 +187,10 @@ def available_notification_agents():
|
|||||||
{'label': 'XBMC',
|
{'label': 'XBMC',
|
||||||
'name': 'xbmc',
|
'name': 'xbmc',
|
||||||
'id': AGENT_IDS['xbmc']
|
'id': AGENT_IDS['xbmc']
|
||||||
|
},
|
||||||
|
{'label': 'Zapier',
|
||||||
|
'name': 'zapier',
|
||||||
|
'id': AGENT_IDS['zapier']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -377,6 +382,8 @@ def get_agent_class(agent_id=None, config=None):
|
|||||||
return GROUPME(config=config)
|
return GROUPME(config=config)
|
||||||
elif agent_id == 23:
|
elif agent_id == 23:
|
||||||
return MQTT(config=config)
|
return MQTT(config=config)
|
||||||
|
elif agent_id == 24:
|
||||||
|
return ZAPIER(config=config)
|
||||||
else:
|
else:
|
||||||
return Notifier(config=config)
|
return Notifier(config=config)
|
||||||
else:
|
else:
|
||||||
@@ -652,13 +659,28 @@ class PrettyMetadata(object):
|
|||||||
provider_name = 'Trakt.tv'
|
provider_name = 'Trakt.tv'
|
||||||
elif provider == 'lastfm':
|
elif provider == 'lastfm':
|
||||||
provider_name = 'Last.fm'
|
provider_name = 'Last.fm'
|
||||||
|
else:
|
||||||
|
if self.media_type == 'movie':
|
||||||
|
provider_name = 'IMDb'
|
||||||
|
elif self.media_type in ('show', 'season', 'episode'):
|
||||||
|
provider_name = 'TheTVDB'
|
||||||
|
elif self.media_type in ('artist', 'album', 'track'):
|
||||||
|
provider_name = 'Last.fm'
|
||||||
return provider_name
|
return provider_name
|
||||||
|
|
||||||
def get_provider_link(self, provider=None):
|
def get_provider_link(self, provider=None):
|
||||||
|
provider_link = ''
|
||||||
if provider == 'plexweb':
|
if provider == 'plexweb':
|
||||||
provider_link = self.get_plex_url()
|
provider_link = self.get_plex_url()
|
||||||
else:
|
elif provider:
|
||||||
provider_link = self.parameters.get(provider + '_url', '')
|
provider_link = self.parameters.get(provider + '_url', '')
|
||||||
|
else:
|
||||||
|
if self.media_type == 'movie':
|
||||||
|
provider_link = self.parameters.get('imdb_url', '')
|
||||||
|
elif self.media_type in ('show', 'season', 'episode'):
|
||||||
|
provider_link = self.parameters.get('thetvdb_url', '')
|
||||||
|
elif self.media_type in ('artist', 'album', 'track'):
|
||||||
|
provider_link = self.parameters.get('lastfm_url', '')
|
||||||
return provider_link
|
return provider_link
|
||||||
|
|
||||||
def get_caption(self, provider):
|
def get_caption(self, provider):
|
||||||
@@ -1919,23 +1941,24 @@ class IFTTT(Notifier):
|
|||||||
headers=headers, json=data)
|
headers=headers, json=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def return_config_options(self):
|
||||||
config_option = [{'label': 'Ifttt Maker Channel Key',
|
config_option = [{'label': 'IFTTT Webhook Key',
|
||||||
'value': self.config['key'],
|
'value': self.config['key'],
|
||||||
'name': 'ifttt_key',
|
'name': 'ifttt_key',
|
||||||
'description': 'Your Ifttt key. You can get a key from'
|
'description': 'Your IFTTT webhook key. You can get a key from'
|
||||||
' <a href="' + helpers.anon_url('https://ifttt.com/maker') + '" target="_blank">here</a>.',
|
' <a href="' + helpers.anon_url('https://ifttt.com/maker_webhooks') + '" target="_blank">here</a>.',
|
||||||
'input_type': 'text'
|
'input_type': 'text'
|
||||||
},
|
},
|
||||||
{'label': 'Ifttt Event',
|
{'label': 'IFTTT Event',
|
||||||
'value': self.config['event'],
|
'value': self.config['event'],
|
||||||
'name': 'ifttt_event',
|
'name': 'ifttt_event',
|
||||||
'description': 'The Ifttt maker event to fire. You can include'
|
'description': 'The IFTTT maker event to fire. You can include'
|
||||||
' the {action} to be substituted with the action name.'
|
' <span class="inline-pre">{action}</span>'
|
||||||
|
' to be substituted with the action name.'
|
||||||
' The notification subject and body will be sent'
|
' The notification subject and body will be sent'
|
||||||
' as value1 and value2 respectively.',
|
' as <span class="inline-pre">value1</span>'
|
||||||
|
' and <span class="inline-pre">value2</span> respectively.',
|
||||||
'input_type': 'text'
|
'input_type': 'text'
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return config_option
|
return config_option
|
||||||
@@ -2072,7 +2095,7 @@ class JOIN(Notifier):
|
|||||||
{'label': 'Movie Link Source',
|
{'label': 'Movie Link Source',
|
||||||
'value': self.config['movie_provider'],
|
'value': self.config['movie_provider'],
|
||||||
'name': 'join_movie_provider',
|
'name': 'join_movie_provider',
|
||||||
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br>'
|
'description': 'Select the source for movie links in the notificaation. Leave blank for default.<br>'
|
||||||
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
||||||
'input_type': 'select',
|
'input_type': 'select',
|
||||||
'select_options': PrettyMetadata().get_movie_providers()
|
'select_options': PrettyMetadata().get_movie_providers()
|
||||||
@@ -2080,7 +2103,7 @@ class JOIN(Notifier):
|
|||||||
{'label': 'TV Show Link Source',
|
{'label': 'TV Show Link Source',
|
||||||
'value': self.config['tv_provider'],
|
'value': self.config['tv_provider'],
|
||||||
'name': 'join_tv_provider',
|
'name': 'join_tv_provider',
|
||||||
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br>'
|
'description': 'Select the source for tv show links in the notificaation. Leave blank for default.<br>'
|
||||||
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
||||||
'input_type': 'select',
|
'input_type': 'select',
|
||||||
'select_options': PrettyMetadata().get_tv_providers()
|
'select_options': PrettyMetadata().get_tv_providers()
|
||||||
@@ -2088,7 +2111,7 @@ class JOIN(Notifier):
|
|||||||
{'label': 'Music Link Source',
|
{'label': 'Music Link Source',
|
||||||
'value': self.config['music_provider'],
|
'value': self.config['music_provider'],
|
||||||
'name': 'join_music_provider',
|
'name': 'join_music_provider',
|
||||||
'description': 'Select the source for music links on the info cards. Leave blank for default.',
|
'description': 'Select the source for music links in the notificaation. Leave blank for default.',
|
||||||
'input_type': 'select',
|
'input_type': 'select',
|
||||||
'select_options': PrettyMetadata().get_music_providers()
|
'select_options': PrettyMetadata().get_music_providers()
|
||||||
}
|
}
|
||||||
@@ -2714,7 +2737,7 @@ class PUSHOVER(Notifier):
|
|||||||
{'label': 'Movie Link Source',
|
{'label': 'Movie Link Source',
|
||||||
'value': self.config['movie_provider'],
|
'value': self.config['movie_provider'],
|
||||||
'name': 'pushover_movie_provider',
|
'name': 'pushover_movie_provider',
|
||||||
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br>'
|
'description': 'Select the source for movie links in the notification. Leave blank for default.<br>'
|
||||||
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
||||||
'input_type': 'select',
|
'input_type': 'select',
|
||||||
'select_options': PrettyMetadata().get_movie_providers()
|
'select_options': PrettyMetadata().get_movie_providers()
|
||||||
@@ -2722,7 +2745,7 @@ class PUSHOVER(Notifier):
|
|||||||
{'label': 'TV Show Link Source',
|
{'label': 'TV Show Link Source',
|
||||||
'value': self.config['tv_provider'],
|
'value': self.config['tv_provider'],
|
||||||
'name': 'pushover_tv_provider',
|
'name': 'pushover_tv_provider',
|
||||||
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br>'
|
'description': 'Select the source for tv show links in the notification. Leave blank for default.<br>'
|
||||||
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
||||||
'input_type': 'select',
|
'input_type': 'select',
|
||||||
'select_options': PrettyMetadata().get_tv_providers()
|
'select_options': PrettyMetadata().get_tv_providers()
|
||||||
@@ -2730,7 +2753,7 @@ class PUSHOVER(Notifier):
|
|||||||
{'label': 'Music Link Source',
|
{'label': 'Music Link Source',
|
||||||
'value': self.config['music_provider'],
|
'value': self.config['music_provider'],
|
||||||
'name': 'pushover_music_provider',
|
'name': 'pushover_music_provider',
|
||||||
'description': 'Select the source for music links on the info cards. Leave blank for default.',
|
'description': 'Select the source for music links in the notification. Leave blank for default.',
|
||||||
'input_type': 'select',
|
'input_type': 'select',
|
||||||
'select_options': PrettyMetadata().get_music_providers()
|
'select_options': PrettyMetadata().get_music_providers()
|
||||||
}
|
}
|
||||||
@@ -3403,6 +3426,104 @@ class XBMC(Notifier):
|
|||||||
return config_option
|
return config_option
|
||||||
|
|
||||||
|
|
||||||
|
class ZAPIER(Notifier):
|
||||||
|
"""
|
||||||
|
Zapier notifications
|
||||||
|
"""
|
||||||
|
NAME = 'Zapier'
|
||||||
|
_DEFAULT_CONFIG = {'hook': '',
|
||||||
|
'movie_provider': '',
|
||||||
|
'tv_provider': '',
|
||||||
|
'music_provider': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
def _test_hook(self):
|
||||||
|
_test_data = {'subject': 'Subject',
|
||||||
|
'body': 'Body',
|
||||||
|
'action': 'Action',
|
||||||
|
'poster_url': 'https://i.imgur.com',
|
||||||
|
'provider_name': 'Provider Name',
|
||||||
|
'provider_link': 'http://www.imdb.com',
|
||||||
|
'plex_url': 'https://app.plex.tv/desktop'}
|
||||||
|
|
||||||
|
return self.agent_notify(_test_data=_test_data)
|
||||||
|
|
||||||
|
def agent_notify(self, subject='', body='', action='', **kwargs):
|
||||||
|
data = {'subject': subject.encode("utf-8"),
|
||||||
|
'body': body.encode("utf-8"),
|
||||||
|
'action': action.encode("utf-8")}
|
||||||
|
|
||||||
|
if kwargs.get('parameters', {}).get('media_type'):
|
||||||
|
# Grab formatted metadata
|
||||||
|
pretty_metadata = PrettyMetadata(kwargs['parameters'])
|
||||||
|
|
||||||
|
if pretty_metadata.media_type == 'movie':
|
||||||
|
provider = self.config['movie_provider']
|
||||||
|
elif pretty_metadata.media_type in ('show', 'season', 'episode'):
|
||||||
|
provider = self.config['tv_provider']
|
||||||
|
elif pretty_metadata.media_type in ('artist', 'album', 'track'):
|
||||||
|
provider = self.config['music_provider']
|
||||||
|
else:
|
||||||
|
provider = None
|
||||||
|
|
||||||
|
poster_url = pretty_metadata.get_poster_url()
|
||||||
|
provider_name = pretty_metadata.get_provider_name(provider)
|
||||||
|
provider_link = pretty_metadata.get_provider_link(provider)
|
||||||
|
plex_url = pretty_metadata.get_plex_url()
|
||||||
|
|
||||||
|
data['poster_url'] = poster_url
|
||||||
|
data['provider_name'] = provider_name
|
||||||
|
data['provider_link'] = provider_link
|
||||||
|
data['plex_url'] = plex_url
|
||||||
|
|
||||||
|
if kwargs.get('_test_data'):
|
||||||
|
data.update(kwargs['_test_data'])
|
||||||
|
|
||||||
|
headers = {'Content-type': 'application/json'}
|
||||||
|
|
||||||
|
return self.make_request(self.config['hook'], headers=headers, json=data)
|
||||||
|
|
||||||
|
def return_config_options(self):
|
||||||
|
config_option = [{'label': 'Zapier Webhook URL',
|
||||||
|
'value': self.config['hook'],
|
||||||
|
'name': 'zapier_hook',
|
||||||
|
'description': 'Your Zapier webhook URL.',
|
||||||
|
'input_type': 'text'
|
||||||
|
},
|
||||||
|
{'label': 'Test Zapier Webhook',
|
||||||
|
'value': 'Send Test Data',
|
||||||
|
'name': 'zapier_test_hook',
|
||||||
|
'description': 'Click this button when prompted on then "Test Webhooks by Zapier" step.',
|
||||||
|
'input_type': 'button'
|
||||||
|
},
|
||||||
|
{'label': 'Movie Link Source',
|
||||||
|
'value': self.config['movie_provider'],
|
||||||
|
'name': 'zapier_movie_provider',
|
||||||
|
'description': 'Select the source for movie links in the notification. Leave blank for default.<br>'
|
||||||
|
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
||||||
|
'input_type': 'select',
|
||||||
|
'select_options': PrettyMetadata().get_movie_providers()
|
||||||
|
},
|
||||||
|
{'label': 'TV Show Link Source',
|
||||||
|
'value': self.config['tv_provider'],
|
||||||
|
'name': 'zapier_tv_provider',
|
||||||
|
'description': 'Select the source for tv show links in the notification. Leave blank for default.<br>'
|
||||||
|
'3rd party API lookup may need to be enabled under the notifications settings tab.',
|
||||||
|
'input_type': 'select',
|
||||||
|
'select_options': PrettyMetadata().get_tv_providers()
|
||||||
|
},
|
||||||
|
{'label': 'Music Link Source',
|
||||||
|
'value': self.config['music_provider'],
|
||||||
|
'name': 'zapier_music_provider',
|
||||||
|
'description': 'Select the source for music links in the notification. Leave blank for default.',
|
||||||
|
'input_type': 'select',
|
||||||
|
'select_options': PrettyMetadata().get_music_providers()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return config_option
|
||||||
|
|
||||||
|
|
||||||
def upgrade_config_to_db():
|
def upgrade_config_to_db():
|
||||||
logger.info(u"Tautulli Notifiers :: Upgrading to new notification system...")
|
logger.info(u"Tautulli Notifiers :: Upgrading to new notification system...")
|
||||||
|
|
||||||
|
@@ -376,9 +376,19 @@ class PlexTV(object):
|
|||||||
def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None,
|
def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None,
|
||||||
rating_key_filter=None, sync_id_filter=None):
|
rating_key_filter=None, sync_id_filter=None):
|
||||||
|
|
||||||
if machine_id is None:
|
if not machine_id:
|
||||||
machine_id = plexpy.CONFIG.PMS_IDENTIFIER
|
machine_id = plexpy.CONFIG.PMS_IDENTIFIER
|
||||||
|
|
||||||
|
if isinstance(rating_key_filter, list):
|
||||||
|
rating_key_filter = [str(k) for k in rating_key_filter]
|
||||||
|
elif rating_key_filter:
|
||||||
|
rating_key_filter = [str(rating_key_filter)]
|
||||||
|
|
||||||
|
if isinstance(user_id_filter, list):
|
||||||
|
user_id_filter = [str(k) for k in user_id_filter]
|
||||||
|
elif user_id_filter:
|
||||||
|
user_id_filter = [str(user_id_filter)]
|
||||||
|
|
||||||
sync_list = self.get_plextv_sync_lists(machine_id, output_format='xml')
|
sync_list = self.get_plextv_sync_lists(machine_id, output_format='xml')
|
||||||
user_data = users.Users()
|
user_data = users.Users()
|
||||||
|
|
||||||
@@ -418,7 +428,7 @@ class PlexTV(object):
|
|||||||
device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt')
|
device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt')
|
||||||
|
|
||||||
# Filter by user_id
|
# Filter by user_id
|
||||||
if user_id_filter and str(user_id_filter) != device_user_id:
|
if user_id_filter and device_user_id not in user_id_filter:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for synced in a.getElementsByTagName('SyncItems'):
|
for synced in a.getElementsByTagName('SyncItems'):
|
||||||
@@ -432,7 +442,7 @@ class PlexTV(object):
|
|||||||
for idx, item in enumerate(clean_uri) if item == 'metadata'), None)
|
for idx, item in enumerate(clean_uri) if item == 'metadata'), None)
|
||||||
|
|
||||||
# Filter by rating_key
|
# Filter by rating_key
|
||||||
if rating_key_filter and str(rating_key_filter) != rating_key:
|
if rating_key_filter and rating_key not in rating_key_filter:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sync_id = helpers.get_xml_attr(item, 'id')
|
sync_id = helpers.get_xml_attr(item, 'id')
|
||||||
@@ -461,12 +471,13 @@ class PlexTV(object):
|
|||||||
status_item_downloaded_count, status_item_count)
|
status_item_downloaded_count, status_item_count)
|
||||||
|
|
||||||
for settings in item.getElementsByTagName('MediaSettings'):
|
for settings in item.getElementsByTagName('MediaSettings'):
|
||||||
settings_audio_boost = helpers.get_xml_attr(settings, 'audioBoost')
|
settings_video_bitrate = helpers.get_xml_attr(settings, 'maxVideoBitrate')
|
||||||
settings_music_bitrate = helpers.get_xml_attr(settings, 'musicBitrate')
|
|
||||||
settings_photo_quality = helpers.get_xml_attr(settings, 'photoQuality')
|
|
||||||
settings_photo_resolution = helpers.get_xml_attr(settings, 'photoResolution')
|
|
||||||
settings_video_quality = helpers.get_xml_attr(settings, 'videoQuality')
|
settings_video_quality = helpers.get_xml_attr(settings, 'videoQuality')
|
||||||
settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution')
|
settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution')
|
||||||
|
settings_audio_boost = helpers.get_xml_attr(settings, 'audioBoost')
|
||||||
|
settings_audio_bitrate = helpers.get_xml_attr(settings, 'musicBitrate')
|
||||||
|
settings_photo_quality = helpers.get_xml_attr(settings, 'photoQuality')
|
||||||
|
settings_photo_resolution = helpers.get_xml_attr(settings, 'photoResolution')
|
||||||
|
|
||||||
sync_details = {"device_name": helpers.sanitize(device_name),
|
sync_details = {"device_name": helpers.sanitize(device_name),
|
||||||
"platform": helpers.sanitize(device_platform),
|
"platform": helpers.sanitize(device_platform),
|
||||||
@@ -483,7 +494,8 @@ class PlexTV(object):
|
|||||||
"item_complete_count": status_item_complete_count,
|
"item_complete_count": status_item_complete_count,
|
||||||
"item_downloaded_count": status_item_downloaded_count,
|
"item_downloaded_count": status_item_downloaded_count,
|
||||||
"item_downloaded_percent_complete": status_item_download_percent_complete,
|
"item_downloaded_percent_complete": status_item_download_percent_complete,
|
||||||
"music_bitrate": settings_music_bitrate,
|
"video_bitrate": settings_video_bitrate,
|
||||||
|
"audio_bitrate": settings_audio_bitrate,
|
||||||
"photo_quality": settings_photo_quality,
|
"photo_quality": settings_photo_quality,
|
||||||
"video_quality": settings_video_quality,
|
"video_quality": settings_video_quality,
|
||||||
"total_size": status_total_size,
|
"total_size": status_total_size,
|
||||||
@@ -641,10 +653,14 @@ class PlexTV(object):
|
|||||||
|
|
||||||
def get_plex_downloads(self):
|
def get_plex_downloads(self):
|
||||||
logger.debug(u"Tautulli PlexTV :: Retrieving current server version.")
|
logger.debug(u"Tautulli PlexTV :: Retrieving current server version.")
|
||||||
pmsconnect.PmsConnect().set_server_version()
|
|
||||||
|
|
||||||
logger.debug(u"Tautulli PlexTV :: Plex update channel is %s." % plexpy.CONFIG.PMS_UPDATE_CHANNEL)
|
pms_connect = pmsconnect.PmsConnect()
|
||||||
plex_downloads = self.get_plextv_downloads(plexpass=(plexpy.CONFIG.PMS_UPDATE_CHANNEL == 'plexpass'))
|
pms_connect.set_server_version()
|
||||||
|
|
||||||
|
update_channel = pms_connect.get_server_update_channel()
|
||||||
|
|
||||||
|
logger.debug(u"Tautulli PlexTV :: Plex update channel is %s." % update_channel)
|
||||||
|
plex_downloads = self.get_plextv_downloads(plexpass=(update_channel == 'beta'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
available_downloads = json.loads(plex_downloads)
|
available_downloads = json.loads(plex_downloads)
|
||||||
|
@@ -533,7 +533,7 @@ class PmsConnect(object):
|
|||||||
metadata = {}
|
metadata = {}
|
||||||
|
|
||||||
if cache_key:
|
if cache_key:
|
||||||
in_file_path = os.path.join(plexpy.CONFIG.CACHE_DIR, 'metadata-sessionKey-%s.json' % cache_key)
|
in_file_path = os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata/metadata-sessionKey-%s.json' % cache_key)
|
||||||
try:
|
try:
|
||||||
with open(in_file_path, 'r') as inFile:
|
with open(in_file_path, 'r') as inFile:
|
||||||
metadata = json.load(inFile)
|
metadata = json.load(inFile)
|
||||||
@@ -559,27 +559,32 @@ class PmsConnect(object):
|
|||||||
|
|
||||||
for a in xml_head:
|
for a in xml_head:
|
||||||
if a.getAttribute('size'):
|
if a.getAttribute('size'):
|
||||||
if a.getAttribute('size') != '1':
|
if a.getAttribute('size') == '0':
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
if a.getElementsByTagName('Directory'):
|
if a.getElementsByTagName('Directory'):
|
||||||
metadata_main = a.getElementsByTagName('Directory')[0]
|
metadata_main_list = a.getElementsByTagName('Directory')
|
||||||
metadata_type = helpers.get_xml_attr(metadata_main, 'type')
|
|
||||||
if metadata_type == 'photo':
|
|
||||||
metadata_type = 'photo_album'
|
|
||||||
elif a.getElementsByTagName('Video'):
|
elif a.getElementsByTagName('Video'):
|
||||||
metadata_main = a.getElementsByTagName('Video')[0]
|
metadata_main_list = a.getElementsByTagName('Video')
|
||||||
metadata_type = helpers.get_xml_attr(metadata_main, 'type')
|
|
||||||
elif a.getElementsByTagName('Track'):
|
elif a.getElementsByTagName('Track'):
|
||||||
metadata_main = a.getElementsByTagName('Track')[0]
|
metadata_main_list = a.getElementsByTagName('Track')
|
||||||
metadata_type = helpers.get_xml_attr(metadata_main, 'type')
|
|
||||||
elif a.getElementsByTagName('Photo'):
|
elif a.getElementsByTagName('Photo'):
|
||||||
metadata_main = a.getElementsByTagName('Photo')[0]
|
metadata_main_list = a.getElementsByTagName('Photo')
|
||||||
metadata_type = helpers.get_xml_attr(metadata_main, 'type')
|
|
||||||
else:
|
else:
|
||||||
logger.debug(u"Tautulli Pmsconnect :: Metadata failed")
|
logger.debug(u"Tautulli Pmsconnect :: Metadata failed")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
if sync_id and len(metadata_main_list) > 1:
|
||||||
|
for metadata_main in metadata_main_list:
|
||||||
|
if helpers.get_xml_attr(metadata_main, 'ratingKey') == rating_key:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
metadata_main = metadata_main_list[0]
|
||||||
|
|
||||||
|
metadata_type = helpers.get_xml_attr(metadata_main, 'type')
|
||||||
|
if metadata_main.nodeName == 'Directory' and metadata_type == 'photo':
|
||||||
|
metadata_type = 'photo_album'
|
||||||
|
|
||||||
section_id = helpers.get_xml_attr(a, 'librarySectionID')
|
section_id = helpers.get_xml_attr(a, 'librarySectionID')
|
||||||
library_name = helpers.get_xml_attr(a, 'librarySectionTitle')
|
library_name = helpers.get_xml_attr(a, 'librarySectionTitle')
|
||||||
|
|
||||||
@@ -1174,7 +1179,7 @@ class PmsConnect(object):
|
|||||||
if cache_key:
|
if cache_key:
|
||||||
metadata['_cache_time'] = int(time.time())
|
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, 'session_metadata/metadata-sessionKey-%s.json' % cache_key)
|
||||||
try:
|
try:
|
||||||
with open(out_file_path, 'w') as outFile:
|
with open(out_file_path, 'w') as outFile:
|
||||||
json.dump(metadata, outFile)
|
json.dump(metadata, outFile)
|
||||||
@@ -1386,7 +1391,7 @@ class PmsConnect(object):
|
|||||||
else:
|
else:
|
||||||
session_details = {'session_id': '',
|
session_details = {'session_id': '',
|
||||||
'bandwidth': '',
|
'bandwidth': '',
|
||||||
'location': 'Unknown'
|
'location': 'wan' if player_details['local'] == '0' else 'lan'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the transcode details
|
# Get the transcode details
|
||||||
@@ -1459,16 +1464,24 @@ class PmsConnect(object):
|
|||||||
if media_type not in ('photo', 'clip') and not session.getElementsByTagName('Session') \
|
if media_type not in ('photo', 'clip') and not session.getElementsByTagName('Session') \
|
||||||
and helpers.get_xml_attr(session, 'ratingKey').isdigit() and transcode_decision == 'direct play':
|
and helpers.get_xml_attr(session, 'ratingKey').isdigit() and transcode_decision == 'direct play':
|
||||||
plex_tv = plextv.PlexTV()
|
plex_tv = plextv.PlexTV()
|
||||||
|
parent_rating_key = helpers.get_xml_attr(session, 'parentRatingKey')
|
||||||
|
grandparent_rating_key = helpers.get_xml_attr(session, 'grandparentRatingKey')
|
||||||
|
|
||||||
synced_items = plex_tv.get_synced_items(client_id_filter=player_details['machine_id'],
|
synced_items = plex_tv.get_synced_items(client_id_filter=player_details['machine_id'],
|
||||||
rating_key_filter=rating_key)
|
rating_key_filter=[rating_key, parent_rating_key, grandparent_rating_key])
|
||||||
if synced_items:
|
if synced_items:
|
||||||
sync_id = synced_items[0]['sync_id']
|
synced_item_details = synced_items[0]
|
||||||
|
sync_id = synced_item_details['sync_id']
|
||||||
synced_xml = self.get_sync_item(sync_id=sync_id, output_format='xml')
|
synced_xml = self.get_sync_item(sync_id=sync_id, output_format='xml')
|
||||||
synced_xml_head = synced_xml.getElementsByTagName('MediaContainer')
|
synced_xml_head = synced_xml.getElementsByTagName('MediaContainer')
|
||||||
if synced_xml_head[0].getElementsByTagName('Track'):
|
if synced_xml_head[0].getElementsByTagName('Track'):
|
||||||
synced_session_data = synced_xml_head[0].getElementsByTagName('Track')[0]
|
synced_xml_items = synced_xml_head[0].getElementsByTagName('Track')
|
||||||
elif synced_xml_head[0].getElementsByTagName('Video'):
|
elif synced_xml_head[0].getElementsByTagName('Video'):
|
||||||
synced_session_data = synced_xml_head[0].getElementsByTagName('Video')[0]
|
synced_xml_items = synced_xml_head[0].getElementsByTagName('Video')
|
||||||
|
|
||||||
|
for synced_session_data in synced_xml_items:
|
||||||
|
if helpers.get_xml_attr(synced_session_data, 'ratingKey') == rating_key:
|
||||||
|
break
|
||||||
|
|
||||||
# Figure out which version is being played
|
# Figure out which version is being played
|
||||||
if sync_id:
|
if sync_id:
|
||||||
@@ -1661,7 +1674,7 @@ class PmsConnect(object):
|
|||||||
part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
|
part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
|
||||||
|
|
||||||
if sync_id:
|
if sync_id:
|
||||||
metadata_details = self.get_metadata_details(sync_id=sync_id, cache_key=session_key)
|
metadata_details = self.get_metadata_details(rating_key=rating_key, sync_id=sync_id, cache_key=session_key)
|
||||||
else:
|
else:
|
||||||
metadata_details = self.get_metadata_details(rating_key=rating_key, cache_key=session_key)
|
metadata_details = self.get_metadata_details(rating_key=rating_key, cache_key=session_key)
|
||||||
|
|
||||||
@@ -1735,49 +1748,55 @@ class PmsConnect(object):
|
|||||||
|
|
||||||
# Get the quality profile
|
# Get the quality profile
|
||||||
if media_type in ('movie', 'episode', 'clip') and 'stream_bitrate' in stream_details:
|
if media_type in ('movie', 'episode', 'clip') and 'stream_bitrate' in stream_details:
|
||||||
stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate'])
|
if sync_id:
|
||||||
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
quailtiy_bitrate = min(b for b in common.VIDEO_QUALITY_PROFILES if stream_bitrate <= b <= source_bitrate)
|
|
||||||
quality_profile = common.VIDEO_QUALITY_PROFILES[quailtiy_bitrate]
|
|
||||||
except ValueError:
|
|
||||||
quality_profile = 'Original'
|
quality_profile = 'Original'
|
||||||
|
|
||||||
if sync_id:
|
synced_item_bitrate = helpers.cast_to_int(synced_item_details['video_bitrate'])
|
||||||
try:
|
try:
|
||||||
synced_bitrate = min(b for b in common.VIDEO_QUALITY_PROFILES if source_bitrate <= b)
|
synced_bitrate = max(b for b in common.VIDEO_QUALITY_PROFILES if b <= synced_item_bitrate)
|
||||||
synced_version_profile = common.VIDEO_QUALITY_PROFILES[synced_bitrate]
|
synced_version_profile = common.VIDEO_QUALITY_PROFILES[synced_bitrate]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
synced_version_profile = 'Original'
|
synced_version_profile = 'Original'
|
||||||
else:
|
else:
|
||||||
synced_version_profile = ''
|
synced_version_profile = ''
|
||||||
|
|
||||||
|
stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate'])
|
||||||
|
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
|
||||||
|
try:
|
||||||
|
quailtiy_bitrate = min(
|
||||||
|
b for b in common.VIDEO_QUALITY_PROFILES if stream_bitrate <= b <= source_bitrate)
|
||||||
|
quality_profile = common.VIDEO_QUALITY_PROFILES[quailtiy_bitrate]
|
||||||
|
except ValueError:
|
||||||
|
quality_profile = 'Original'
|
||||||
|
|
||||||
if stream_details['optimized_version']:
|
if stream_details['optimized_version']:
|
||||||
optimized_version_profile = '{} Mbps {}'.format(round(source_bitrate / 1000.0, 1),
|
optimized_version_profile = '{} Mbps {}'.format(round(source_bitrate / 1000.0, 1),
|
||||||
plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(source_media_details['video_resolution'], source_media_details['video_resolution']))
|
plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(source_media_details['video_resolution'],
|
||||||
|
source_media_details['video_resolution']))
|
||||||
else:
|
else:
|
||||||
optimized_version_profile = ''
|
optimized_version_profile = ''
|
||||||
|
|
||||||
elif media_type == 'track' and 'stream_bitrate' in stream_details:
|
elif media_type == 'track' and 'stream_bitrate' in stream_details:
|
||||||
stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate'])
|
if sync_id:
|
||||||
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
quailtiy_bitrate = min(b for b in common.AUDIO_QUALITY_PROFILES if stream_bitrate <= b <= source_bitrate)
|
|
||||||
quality_profile = common.AUDIO_QUALITY_PROFILES[quailtiy_bitrate]
|
|
||||||
except ValueError:
|
|
||||||
quality_profile = 'Original'
|
quality_profile = 'Original'
|
||||||
|
|
||||||
if sync_id:
|
synced_item_bitrate = helpers.cast_to_int(synced_item_details['audio_bitrate'])
|
||||||
try:
|
try:
|
||||||
synced_bitrate = min(b for b in common.AUDIO_QUALITY_PROFILES if source_bitrate <= b)
|
synced_bitrate = max(b for b in common.AUDIO_QUALITY_PROFILES if b <= synced_item_bitrate)
|
||||||
synced_version_profile = common.AUDIO_QUALITY_PROFILES[synced_bitrate]
|
synced_version_profile = common.AUDIO_QUALITY_PROFILES[synced_bitrate]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
synced_version_profile = 'Original'
|
synced_version_profile = 'Original'
|
||||||
else:
|
else:
|
||||||
synced_version_profile = ''
|
synced_version_profile = ''
|
||||||
|
|
||||||
|
stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate'])
|
||||||
|
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
|
||||||
|
try:
|
||||||
|
quailtiy_bitrate = min(b for b in common.AUDIO_QUALITY_PROFILES if stream_bitrate <= b <= source_bitrate)
|
||||||
|
quality_profile = common.AUDIO_QUALITY_PROFILES[quailtiy_bitrate]
|
||||||
|
except ValueError:
|
||||||
|
quality_profile = 'Original'
|
||||||
|
|
||||||
optimized_version_profile = ''
|
optimized_version_profile = ''
|
||||||
|
|
||||||
elif media_type == 'photo':
|
elif media_type == 'photo':
|
||||||
@@ -2156,8 +2175,12 @@ class PmsConnect(object):
|
|||||||
item_main += a.getElementsByTagName('Photo')
|
item_main += a.getElementsByTagName('Photo')
|
||||||
|
|
||||||
for item in item_main:
|
for item in item_main:
|
||||||
|
media_type = helpers.get_xml_attr(item, 'type')
|
||||||
|
if item.nodeName == 'Directory' and media_type == 'photo':
|
||||||
|
media_type = 'photo_album'
|
||||||
|
|
||||||
item_info = {'section_id': helpers.get_xml_attr(a, 'librarySectionID'),
|
item_info = {'section_id': helpers.get_xml_attr(a, 'librarySectionID'),
|
||||||
'media_type': helpers.get_xml_attr(item, 'type'),
|
'media_type': media_type,
|
||||||
'rating_key': helpers.get_xml_attr(item, 'ratingKey'),
|
'rating_key': helpers.get_xml_attr(item, 'ratingKey'),
|
||||||
'parent_rating_key': helpers.get_xml_attr(item, 'parentRatingKey'),
|
'parent_rating_key': helpers.get_xml_attr(item, 'parentRatingKey'),
|
||||||
'grandparent_rating_key': helpers.get_xml_attr(item, 'grandparentRatingKey'),
|
'grandparent_rating_key': helpers.get_xml_attr(item, 'grandparentRatingKey'),
|
||||||
@@ -2567,4 +2590,15 @@ class PmsConnect(object):
|
|||||||
version = identity.get('version', plexpy.CONFIG.PMS_VERSION)
|
version = identity.get('version', plexpy.CONFIG.PMS_VERSION)
|
||||||
|
|
||||||
plexpy.CONFIG.__setattr__('PMS_VERSION', version)
|
plexpy.CONFIG.__setattr__('PMS_VERSION', version)
|
||||||
plexpy.CONFIG.write()
|
plexpy.CONFIG.write()
|
||||||
|
|
||||||
|
def get_server_update_channel(self):
|
||||||
|
if plexpy.CONFIG.PMS_UPDATE_CHANNEL == 'plex':
|
||||||
|
update_channel_value = self.get_server_pref('ButlerUpdateChannel')
|
||||||
|
|
||||||
|
if update_channel_value == '8':
|
||||||
|
return 'beta'
|
||||||
|
else:
|
||||||
|
return 'public'
|
||||||
|
|
||||||
|
return plexpy.CONFIG.PMS_UPDATE_CHANNEL
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
PLEXPY_BRANCH = "beta"
|
PLEXPY_BRANCH = "beta"
|
||||||
PLEXPY_RELEASE_VERSION = "v2.0.15-beta"
|
PLEXPY_RELEASE_VERSION = "v2.0.17-beta"
|
||||||
|
@@ -674,7 +674,7 @@ class WebInterface(object):
|
|||||||
if not kwargs.get('json_data'):
|
if not kwargs.get('json_data'):
|
||||||
# Alias 'title' to 'sort_title'
|
# Alias 'title' to 'sort_title'
|
||||||
if kwargs.get('order_column') == 'title':
|
if kwargs.get('order_column') == 'title':
|
||||||
kwargs['order_column'] == 'sort_title'
|
kwargs['order_column'] = 'sort_title'
|
||||||
|
|
||||||
# TODO: Find some one way to automatically get the columns
|
# TODO: Find some one way to automatically get the columns
|
||||||
dt_columns = [("added_at", True, False),
|
dt_columns = [("added_at", True, False),
|
||||||
@@ -2201,9 +2201,8 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
def get_sync(self, machine_id=None, user_id=None, **kwargs):
|
def get_sync(self, machine_id=None, user_id=None, **kwargs):
|
||||||
|
if user_id == 'null':
|
||||||
if not machine_id:
|
user_id = None
|
||||||
machine_id = plexpy.CONFIG.PMS_IDENTIFIER
|
|
||||||
|
|
||||||
plex_tv = plextv.PlexTV()
|
plex_tv = plextv.PlexTV()
|
||||||
result = plex_tv.get_synced_items(machine_id=machine_id, user_id_filter=user_id)
|
result = plex_tv.get_synced_items(machine_id=machine_id, user_id_filter=user_id)
|
||||||
@@ -2798,12 +2797,16 @@ class WebInterface(object):
|
|||||||
def get_server_update_params(self, **kwargs):
|
def get_server_update_params(self, **kwargs):
|
||||||
plex_tv = plextv.PlexTV()
|
plex_tv = plextv.PlexTV()
|
||||||
plexpass = plex_tv.get_plexpass_status()
|
plexpass = plex_tv.get_plexpass_status()
|
||||||
|
|
||||||
|
update_channel = pmsconnect.PmsConnect().get_server_update_channel()
|
||||||
|
|
||||||
return {'plexpass': plexpass,
|
return {'plexpass': plexpass,
|
||||||
'pms_platform': common.PMS_PLATFORM_NAME_OVERRIDES.get(
|
'pms_platform': common.PMS_PLATFORM_NAME_OVERRIDES.get(
|
||||||
plexpy.CONFIG.PMS_PLATFORM, plexpy.CONFIG.PMS_PLATFORM),
|
plexpy.CONFIG.PMS_PLATFORM, plexpy.CONFIG.PMS_PLATFORM),
|
||||||
'pms_update_channel': plexpy.CONFIG.PMS_UPDATE_CHANNEL,
|
'pms_update_channel': plexpy.CONFIG.PMS_UPDATE_CHANNEL,
|
||||||
'pms_update_distro': plexpy.CONFIG.PMS_UPDATE_DISTRO,
|
'pms_update_distro': plexpy.CONFIG.PMS_UPDATE_DISTRO,
|
||||||
'pms_update_distro_build': plexpy.CONFIG.PMS_UPDATE_DISTRO_BUILD}
|
'pms_update_distro_build': plexpy.CONFIG.PMS_UPDATE_DISTRO_BUILD,
|
||||||
|
'plex_update_channel': 'plexpass' if update_channel == 'beta' else 'public'}
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@@ -3197,6 +3200,16 @@ class WebInterface(object):
|
|||||||
logger.warn(msg)
|
logger.warn(msg)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@cherrypy.tools.json_out()
|
||||||
|
@requireAuth(member_of("admin"))
|
||||||
|
def zapier_test_hook(self, zapier_hook='', **kwargs):
|
||||||
|
success = notifiers.ZAPIER(config={'hook': zapier_hook})._test_hook()
|
||||||
|
if success:
|
||||||
|
return {'result': 'success', 'msg': 'Test Zapier webhook sent.'}
|
||||||
|
else:
|
||||||
|
return {'result': 'error', 'msg': 'Failed to send test Zapier webhook.'}
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def set_notification_config(self, **kwargs):
|
def set_notification_config(self, **kwargs):
|
||||||
|
Reference in New Issue
Block a user