Compare commits
64 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9409303f24 | ||
![]() |
94a3d35c90 | ||
![]() |
851de4934b | ||
![]() |
2942640eb9 | ||
![]() |
a00d43092d | ||
![]() |
45c2f50018 | ||
![]() |
ef8c6e82e6 | ||
![]() |
3eebb58da5 | ||
![]() |
447c50fd03 | ||
![]() |
0620ebebcf | ||
![]() |
cf081ee291 | ||
![]() |
3c29b8e9c5 | ||
![]() |
6143da5a6a | ||
![]() |
664f71575c | ||
![]() |
9ae111b8a1 | ||
![]() |
18682c7a2e | ||
![]() |
b21c50dfcf | ||
![]() |
49b6965e8e | ||
![]() |
c6cc2b8831 | ||
![]() |
f9f65eae53 | ||
![]() |
b51d442673 | ||
![]() |
5863b62ccf | ||
![]() |
66bb922012 | ||
![]() |
53876e8f0d | ||
![]() |
cb7ba7fdde | ||
![]() |
9cf6793b24 | ||
![]() |
6e62ffdd22 | ||
![]() |
c042d9e39a | ||
![]() |
1262de2ae2 | ||
![]() |
307230cec8 | ||
![]() |
4fa2711e78 | ||
![]() |
5c9c2f9ab8 | ||
![]() |
604155b41b | ||
![]() |
6e4198e7be | ||
![]() |
14d4940d05 | ||
![]() |
a6b0cdef97 | ||
![]() |
6265943607 | ||
![]() |
de39f7691c | ||
![]() |
921a219beb | ||
![]() |
b9c95d49a6 | ||
![]() |
fc0be6bce2 | ||
![]() |
8db891cfe6 | ||
![]() |
f6e77cc578 | ||
![]() |
a3782f9150 | ||
![]() |
7546c7ef42 | ||
![]() |
53de8cda30 | ||
![]() |
1fb7473dc5 | ||
![]() |
cc9d09bd54 | ||
![]() |
42ff4a2f62 | ||
![]() |
3fa5f80fc4 | ||
![]() |
9b5b7ef8db | ||
![]() |
560acf62fe | ||
![]() |
27d12922da | ||
![]() |
37b92f3d88 | ||
![]() |
79d5c0c92e | ||
![]() |
0bdaedd486 | ||
![]() |
018a201688 | ||
![]() |
a5bd7e6563 | ||
![]() |
a055feccd5 | ||
![]() |
ba68a9b52b | ||
![]() |
49669dc7e0 | ||
![]() |
5bdf79606e | ||
![]() |
ff3a9e47df | ||
![]() |
a18ba24f4a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,6 +21,7 @@ cache/*
|
||||
*.crt
|
||||
*.key
|
||||
*.csr
|
||||
*.pem
|
||||
|
||||
# OS generated files #
|
||||
######################
|
||||
|
48
CHANGELOG.md
48
CHANGELOG.md
@@ -1,5 +1,53 @@
|
||||
# Changelog
|
||||
|
||||
## v1.2.16 (2015-12-22)
|
||||
|
||||
* Fix Most Concurrent stream stat for emtpy databases
|
||||
* Change logs to 50 lines by default
|
||||
|
||||
|
||||
## v1.2.15 (2015-12-20)
|
||||
|
||||
* Fix navbar covering current activity on smaller screens.
|
||||
* Fix metadata for grouped recently added notifications.
|
||||
* Fix Growl notification agent not working.
|
||||
* Change graph days selection.
|
||||
* Change watch statistics to match table history grouping.
|
||||
* Add automatic discovery of Pushbullet devices.
|
||||
* Add Most Concurrent Streams watch statistic.
|
||||
* Add precentage to current activity progress bars.
|
||||
* Add a bunch of stream details to notification options.
|
||||
* Add notification for Plex Remote Access/Plex Media Server back up.
|
||||
* Add CC/BCC and multiple recipients to email notification agent.
|
||||
* Add total watch time to history table footer.
|
||||
|
||||
|
||||
## v1.2.14 (2015-12-07)
|
||||
|
||||
* Fix regression with PlexWatch db importer and buffer warnings.
|
||||
|
||||
|
||||
## v1.2.13 (2015-12-06)
|
||||
|
||||
* Fix match newlines between tags in notification text.
|
||||
* Fix current activity not showing on PMS 0.9.12.
|
||||
|
||||
|
||||
## v1.2.12 (2015-12-06)
|
||||
|
||||
* Fix for "too many open files" error.
|
||||
|
||||
|
||||
## v1.2.11 (2015-12-06)
|
||||
|
||||
* Fix more regressions (sorry).
|
||||
|
||||
|
||||
## v1.2.10 (2015-12-06)
|
||||
|
||||
* Fix broken count graphs regression.
|
||||
|
||||
|
||||
## v1.2.9 (2015-12-06)
|
||||
|
||||
* Fix and improve text sanitization.
|
||||
|
@@ -57,7 +57,7 @@ from plexpy import version
|
||||
</div>
|
||||
<div class="collapse navbar-collapse navbar-right" id="navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li>
|
||||
<li class="hidden-sm hidden-xs">
|
||||
<form action="search" method="post" class="form" id="search_form">
|
||||
<div class="input-group">
|
||||
<span class="input-textbox">
|
||||
@@ -140,12 +140,12 @@ ${next.headerIncludes()}
|
||||
} else {
|
||||
e.preventDefault();
|
||||
$('#search_button').removeClass('btn-inactive');
|
||||
$('#query').clearQueue().val('').animate({ right: '0', width: '250px' }).addClass('active').focus();
|
||||
$('#query').clearQueue().val('').animate({ right: '0', width: '200px' }).addClass('active').focus();
|
||||
}
|
||||
})
|
||||
$('#query').on('blur', function (e) {
|
||||
if ($(this).val().trim() == '') {
|
||||
$(this).delay(200).animate({ right: '-250px', width: '0' }, function () {
|
||||
$(this).delay(200).animate({ right: '-200px', width: '0' }, function () {
|
||||
$('#search_button').addClass('btn-inactive');
|
||||
}).removeClass('active');
|
||||
}
|
||||
|
@@ -487,7 +487,8 @@ textarea.form-control:focus {
|
||||
.users-poster-face {
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
background-size: contain;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
-webkit-border-radius: 50%;
|
||||
@@ -737,14 +738,20 @@ a:hover .dashboard-activity-poster {
|
||||
transition: all 0s;
|
||||
}
|
||||
.dashboard-activity-progress .bufferbar {
|
||||
padding-top: 6px;
|
||||
padding-right: 3px;
|
||||
font-size: x-small;
|
||||
text-align: right;
|
||||
color: rgba(255, 255, 255, 0);
|
||||
background-color: #444;
|
||||
position: absolute;
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dashboard-activity-progress .bar {
|
||||
padding-top: 6px;
|
||||
padding-right: 3px;
|
||||
font-size: x-small;
|
||||
text-align: right;
|
||||
color: rgba(255, 255, 255, 0);
|
||||
background-color: #faa732;
|
||||
background-image: -moz-linear-gradient(top, #fbb450, #f89406);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
|
||||
@@ -757,6 +764,37 @@ a:hover .dashboard-activity-poster {
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dashboard-instance.hover .dashboard-activity-progress-bar {
|
||||
height: 14px;
|
||||
transform-origin: top;
|
||||
transition: all .2s ease;
|
||||
border-radius: 0px 0px 3px 3px;
|
||||
}
|
||||
.dashboard-instance.hover .bar {
|
||||
height: 14px;
|
||||
transform-origin: top;
|
||||
transition: all .2s ease;
|
||||
border-radius: 0px 0px 3px 3px;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px);
|
||||
background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||
background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||
}
|
||||
.dashboard-instance.hover .bufferbar {
|
||||
height: 14px;
|
||||
transform-origin: top;
|
||||
transition: all .2s ease;
|
||||
border-radius: 0px 0px 3px 3px;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px);
|
||||
background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||
background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||
}
|
||||
.dashboard-instance.hover .dashboard-activity-metadata-wrapper {
|
||||
margin-top: 11px;
|
||||
transform-origin: top;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
.dashboard-activity-metadata-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -1500,7 +1538,8 @@ a:hover .item-children-poster {
|
||||
float: left;
|
||||
margin-top: 15px;
|
||||
margin-right: 15px;
|
||||
background-size: contain;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
-webkit-border-radius: 50%;
|
||||
@@ -2489,12 +2528,12 @@ table[id^='history_child'] thead th {
|
||||
overflow: hidden;
|
||||
}
|
||||
#search_form {
|
||||
width: 350px;
|
||||
width: 300px;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
#search_form span.input-textbox {
|
||||
overflow: hidden;
|
||||
width: 250px;
|
||||
width: 200px;
|
||||
height: 34px;
|
||||
display: inline-flex;
|
||||
float: right;
|
||||
@@ -2505,11 +2544,11 @@ table[id^='history_child'] thead th {
|
||||
margin-top: 0;
|
||||
float: right;
|
||||
position: relative;
|
||||
right: -250px;
|
||||
right: -200px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
#search_form #query.active {
|
||||
width: 250px;
|
||||
width: 200px;
|
||||
right: 0px;
|
||||
}
|
||||
#search_form #search_button.btn-inactive {
|
||||
@@ -2520,4 +2559,38 @@ table[id^='history_child'] thead th {
|
||||
-ms-transition: background 0.3s;
|
||||
-o-transition: background 0.3s;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-params {
|
||||
margin-top: 10px;
|
||||
background-color: #282828;
|
||||
}
|
||||
.notification-params th {
|
||||
padding-left: 10px;
|
||||
height: 30px;
|
||||
}
|
||||
.notification-params td {
|
||||
height: 25px;
|
||||
}
|
||||
.notification-params td:first-child {
|
||||
padding-left: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
.notification-params td:not(:first-child) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
.notification-params tr:nth-child(odd) td {
|
||||
background-color: rgba(255,255,255,0.035);
|
||||
}
|
||||
.notification-params tr:nth-child(even) td {
|
||||
background-color: rgba(255,255,255,0.010);
|
||||
}
|
||||
|
||||
#days-selection label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#graph-days {
|
||||
margin: 0;
|
||||
width: 75px;
|
||||
height: 34px;
|
||||
}
|
||||
|
@@ -280,6 +280,13 @@ DOCUMENTATION :: END
|
||||
e.preventDefault();
|
||||
$($(this).attr('data-target')).toggle();
|
||||
});
|
||||
|
||||
// Add hover class to dashboard-instance
|
||||
$('.dashboard-activity-poster').hover(function() {
|
||||
$(this).closest('.dashboard-instance').addClass('hover');
|
||||
}, function() {
|
||||
$(this).closest('.dashboard-instance').removeClass('hover');
|
||||
});
|
||||
</script>
|
||||
% else:
|
||||
<div class="text-muted">Nothing is currently being watched.</div><br>
|
||||
|
@@ -15,11 +15,13 @@ DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
% if data != None:
|
||||
% if data == '0':
|
||||
<h3>Activity</h3>
|
||||
% if data == '0':
|
||||
<h3>Activity</h3>
|
||||
% elif data == '1':
|
||||
<h3>Activity <small>${data} stream</small></h3>
|
||||
% else:
|
||||
<h3>Activity <small>${data} streams</small></h3>
|
||||
% endif
|
||||
% else:
|
||||
<h3>Activity <small>${data} stream(s)</small></h3>
|
||||
<h3>Activity</h3>
|
||||
% endif
|
||||
% else:
|
||||
<h3>Activity</h3>
|
||||
% endif
|
@@ -13,110 +13,132 @@
|
||||
</div>
|
||||
<div class="button-bar hidden-xs">
|
||||
<div class="btn-group" data-toggle="buttons" id="yaxis-selection">
|
||||
% if config['graph_type'] == 'duration':
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" name="yaxis-options" id="yaxis-count" value="plays" autocomplete="off"> Play Count
|
||||
</label>
|
||||
<label class="btn btn-dark active">
|
||||
<input type="radio" name="yaxis-options" id="yaxis-duration" value="duration" autocomplete="off" checked> Play Duration
|
||||
</label>
|
||||
% else:
|
||||
<label class="btn btn-dark active">
|
||||
<input type="radio" name="yaxis-options" id="yaxis-count" value="plays" autocomplete="off" checked> Play Count
|
||||
</label>
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" name="yaxis-options" id="yaxis-duration" value="duration" autocomplete="off"> Play Duration
|
||||
</label>
|
||||
% endif
|
||||
</div>
|
||||
<div class="btn-group" data-toggle="buttons" id="days-selection">
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" name="date-options" id="graph-7" value="7" autocomplete="off"> 7 days
|
||||
</label>
|
||||
<label class="btn btn-dark active">
|
||||
<input type="radio" name="date-options" id="graph-30" value="30" autocomplete="off" checked> 30 days
|
||||
</label>
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" name="date-options" id="graph-90" value="90" autocomplete="off"> 90 days
|
||||
</label>
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" name="date-options" id="graph-365" value="365" autocomplete="off"> 1 year
|
||||
<div class="btn-group" id="days-selection">
|
||||
<label>
|
||||
<input type="number" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" /> days
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
<ul class="nav nav-pills" role="tablist" id="graph-tabs">
|
||||
% if config['graph_tab'] == 'tabs-3':
|
||||
<li role="presentation"><a href="#tabs-1" aria-controls="tabs-1" data-toggle="tab" role="tab">Plays by period</a></li>
|
||||
<li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" data-toggle="tab" role="tab">Stream Info</a></li>
|
||||
<li role="presentation" class="active"><a href="#tabs-3" aria-controls="tabs-3" data-toggle="tab" role="tab">Play Totals</a></li>
|
||||
% elif config['graph_tab'] == 'tabs-2':
|
||||
<li role="presentation"><a href="#tabs-1" aria-controls="tabs-1" data-toggle="tab" role="tab">Plays by period</a></li>
|
||||
<li role="presentation" class="active"><a href="#tabs-2" aria-controls="tabs-2" data-toggle="tab" role="tab">Stream Info</a></li>
|
||||
<li role="presentation"><a href="#tabs-3" aria-controls="tabs-3" data-toggle="tab" role="tab">Play Totals</a></li>
|
||||
% else:
|
||||
<li role="presentation" class="active"><a href="#tabs-1" aria-controls="tabs-1" data-toggle="tab" role="tab">Plays by period</a></li>
|
||||
<li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" data-toggle="tab" role="tab">Stream Info</a></li>
|
||||
<li role="presentation"><a href="#tabs-3" aria-controls="tabs-3" data-toggle="tab" role="tab">Play Totals</a></li>
|
||||
% endif
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
% if config['graph_tab'] != 'tabs-2' and config['graph_tab'] != 'tabs-3':
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-1">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4><i class="fa fa-history"></i> Daily <span class="yaxis-text">Play count</span> <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The total play count or duration of tv, movies, and music played per day. Click a graph point to open up a list of items played for that specific date.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_day">
|
||||
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...</div>
|
||||
<br>
|
||||
% else:
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-1">
|
||||
% endif
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4><i class="fa fa-history"></i> Daily <span class="yaxis-text">Play count</span> <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The total play count or duration of tv, movies, and music played per day. Click a graph point to open up a list of items played for that specific date.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_day">
|
||||
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4><i class="fa fa-calendar"></i> <span class="yaxis-text">Play count</span> by day of week <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music played per day of the week.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_dayofweek" style="float: left;">
|
||||
<div class="graphs-load">
|
||||
<i class="fa fa-refresh fa-spin"></i> Loading chart...
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4><i class="fa fa-clock-o"></i> <span class="yaxis-text">Play count</span> by hour of day <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music played per hour of the day.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_hourofday">
|
||||
<div class="graphs-load">
|
||||
<i class="fa fa-refresh fa-spin"></i> Loading chart...
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4><i class="fa fa-television"></i> <span class="yaxis-text">Play count</span> by top 10 platforms <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music played by top 10 most active platforms.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_platform" style="float: left;">
|
||||
<div class="graphs-load">
|
||||
<i class="fa fa-refresh fa-spin"></i> Loading chart...
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4><i class="fa fa-user"></i> <span class="yaxis-text">Play count</span> by top 10 users <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music played by top 10 most active users.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_user">
|
||||
<div class="graphs-load">
|
||||
<i class="fa fa-refresh fa-spin"></i> Loading chart...
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4><i class="fa fa-calendar"></i> <span class="yaxis-text">Play count</span> by day of week <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music played per day of the week.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_dayofweek" style="float: left;">
|
||||
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4><i class="fa fa-clock-o"></i> <span class="yaxis-text">Play count</span> by hour of day <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music played per hour of the day.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_hourofday">
|
||||
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4><i class="fa fa-television"></i> <span class="yaxis-text">Play count</span> by top 10 platforms <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music played by top 10 most active platforms.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_platform" style="float: left;">
|
||||
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4><i class="fa fa-user"></i> <span class="yaxis-text">Play count</span> by top 10 users <small>Last <span class="days">30</span> days</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music played by top 10 most active users.
|
||||
</p>
|
||||
<div class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_user">
|
||||
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
% if config['graph_tab'] == 'tabs-2':
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-2">
|
||||
% else:
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-2">
|
||||
% endif
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4><i class="fa fa-video-camera"></i> Daily Stream type breakdown <small>Last <span class="days">30</span> days</small></h4>
|
||||
@@ -189,7 +211,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
% if config['graph_tab'] == 'tabs-3':
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-3">
|
||||
% else:
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-3">
|
||||
% endif
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4><i class="fa fa-calendar"></i> Plays by Month <small>Last 12 months</small></h4>
|
||||
@@ -263,36 +289,12 @@
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
// Save graph state to cookies
|
||||
$('input[name=yaxis-options]').change(function() {
|
||||
setCookie('graphType', $(this).val(), 365, '/');
|
||||
});
|
||||
$('input[name=date-options]').change(function() {
|
||||
setCookie('graphDate', $(this).val(), 365, '/');
|
||||
});
|
||||
$('a[data-toggle=tab]').click(function() {
|
||||
setCookie('graphTab', $(this).attr('href'), 365, '/');
|
||||
});
|
||||
// Initial values for graph from config
|
||||
var yaxis = "${config['graph_type']}";
|
||||
var current_range = ${config['graph_days']};
|
||||
var current_tab = "${'#' + config['graph_tab']}";
|
||||
|
||||
// Initial values for graph if no saved state
|
||||
var yaxis = 'plays';
|
||||
var current_range = 30;
|
||||
var current_tab = '#tabs-1';
|
||||
|
||||
// Read saved graph state from cookies and set initial values
|
||||
if(getCookie('graphType')) {
|
||||
var yaxis = getCookie('graphType');
|
||||
$('input[name=yaxis-options][value=' + yaxis + ']').prop('checked', true).trigger('click');
|
||||
}
|
||||
if(getCookie('graphDate')) {
|
||||
var current_range = getCookie('graphDate');
|
||||
$('input[name=date-options][value=' + current_range + ']').prop('checked', true).trigger('click');
|
||||
$('.days').html(current_range);
|
||||
}
|
||||
if(getCookie('graphTab')) {
|
||||
var current_tab = getCookie('graphTab');
|
||||
$('a[data-toggle=tab][href=' + current_tab + ']').trigger('click');
|
||||
}
|
||||
$('.days').html(current_range);
|
||||
|
||||
var music_visible = (${config['music_logging_enable']} == 1 ? true : false);
|
||||
|
||||
@@ -476,40 +478,61 @@
|
||||
}
|
||||
|
||||
// Set initial state
|
||||
loadGraphsTab1(current_range, yaxis);
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(yaxis); }
|
||||
|
||||
// Tab1 opened
|
||||
$('#graph-tabs a[href="#tabs-1"]').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
$('#days-selection').show();
|
||||
loadGraphsTab1(current_range, yaxis);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_tab: current_tab.replace('#','') },
|
||||
async: true
|
||||
});
|
||||
})
|
||||
|
||||
// Tab2 opened
|
||||
$('#graph-tabs a[href="#tabs-2"]').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
$('#days-selection').show();
|
||||
loadGraphsTab2(current_range, yaxis);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_tab: current_tab.replace('#','') },
|
||||
async: true
|
||||
});
|
||||
})
|
||||
|
||||
// Tab3 opened
|
||||
$('#graph-tabs a[href="#tabs-3"]').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
$('#days-selection').hide();
|
||||
console.log('loading....');
|
||||
loadGraphsTab3(yaxis);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_tab: current_tab.replace('#','') },
|
||||
async: true
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
// Date range changed
|
||||
$('#days-selection').on('change', function() {
|
||||
current_range = $('input[name=date-options]:checked', '#days-selection').val();
|
||||
$('#graph-days').on('change', function() {
|
||||
current_range = $(this).val();
|
||||
if (current_range < 1) {
|
||||
$(this).val(7);
|
||||
current_range = 7;
|
||||
}
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
$('.days').html(current_range);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_days: current_range},
|
||||
async: true
|
||||
});
|
||||
});
|
||||
|
||||
// Y-axis changed
|
||||
@@ -518,6 +541,11 @@
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(yaxis); }
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_type: yaxis},
|
||||
async: true
|
||||
});
|
||||
});
|
||||
|
||||
function setGraphFormat(type) {
|
||||
|
@@ -44,6 +44,11 @@ player Returns the player name for the associated stat.
|
||||
== Only if 'stat_id' is 'last_watched' ==
|
||||
last_watch Returns the time the media item was last watched.
|
||||
|
||||
== Only if 'stat_id' is 'most_concurrent' ==
|
||||
count Returns the count of the most concurrent streams.
|
||||
started Returns the start time of the most concurrent streams.
|
||||
stopped Returns the stop time of the most concurrent streams.
|
||||
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
@@ -782,6 +787,30 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'most_concurrent' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Concurrent Streams</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4>
|
||||
<span id="most-concurrent-start">
|
||||
<script>
|
||||
$('#most-concurrent-start').text(moment(${top_stat['rows'][0]['started']},"X").format(date_format + ' ' + time_format));
|
||||
</script>
|
||||
</span>
|
||||
</h4>
|
||||
<h3>${top_stat['rows'][0]['count']}</h3>
|
||||
<p> streams</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-instance-box" style="background-image: url(interfaces/default/images/home-stat_most-concurrent.png);"></div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
</ul>
|
||||
|
BIN
data/interfaces/default/images/home-stat_most-concurrent.png
Normal file
BIN
data/interfaces/default/images/home-stat_most-concurrent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
@@ -18,7 +18,7 @@ history_table_options = {
|
||||
"lengthMenu":"Show _MENU_ entries per page",
|
||||
"info":"Showing _START_ to _END_ of _TOTAL_ history items",
|
||||
"infoEmpty":"Showing 0 to 0 of 0 entries",
|
||||
"infoFiltered":"(filtered from _MAX_ total entries)",
|
||||
"infoFiltered":"<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
||||
"emptyTable": "No data in table"
|
||||
},
|
||||
"pagingType": "bootstrap",
|
||||
@@ -265,6 +265,9 @@ history_table_options = {
|
||||
createChildTable(this, rowData)
|
||||
}
|
||||
});
|
||||
|
||||
$("#history_table_info").append('<span class="hidden-md hidden-sm hidden-xs"> with a duration of ' + settings.json.filter_duration +
|
||||
' (filtered from ' + settings.json.total_duration + ' total)</span>');
|
||||
},
|
||||
"preDrawCallback": function(settings) {
|
||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||
|
@@ -4,7 +4,7 @@ var log_table_options = {
|
||||
"processing": false,
|
||||
"pagingType": "bootstrap",
|
||||
"order": [ 0, 'desc'],
|
||||
"pageLength": 10,
|
||||
"pageLength": 50,
|
||||
"stateSave": true,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
|
@@ -6,7 +6,7 @@ from plexpy import helpers
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title" id="notification-config-modal-header">Set Config</h4>
|
||||
<h4 class="modal-title" id="notification-config-modal-header">${agent['name']} Settings</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
@@ -25,7 +25,7 @@ from plexpy import helpers
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description']}</p>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'button':
|
||||
<div class="form-group">
|
||||
@@ -34,14 +34,14 @@ from plexpy import helpers
|
||||
<input type="${item['input_type']}" class="btn btn-bright" id="${item['name']}" name="${item['name']}" value="${item['value']}">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description']}</p>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'checkbox':
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-id="${item['name']}" class="checkboxes" value="1" ${helpers.checked(item['value'])}> ${item['label']}
|
||||
</label>
|
||||
<p class="help-block">${item['description']}</p>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
<input type="hidden" id="${item['name']}" name="${item['name']}" value="${item['value']}">
|
||||
</div>
|
||||
% elif item['input_type'] == 'select':
|
||||
@@ -60,7 +60,7 @@ from plexpy import helpers
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description']}</p>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
@@ -84,24 +84,29 @@ from plexpy import helpers
|
||||
% endif
|
||||
|
||||
<script>
|
||||
$('#osxnotifyregister').click(function () {
|
||||
var osx_notify_app = $("#osx_notify_app").val();
|
||||
$.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { $('#ajaxMsg').html("<i class='fa fa-check'></i> " + data); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
})
|
||||
|
||||
$('#save-notification-item').click(function() {
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true);
|
||||
// Reload modal to update certain fields
|
||||
function reloadModal() {
|
||||
$.ajax({
|
||||
url: 'get_notification_agent_config',
|
||||
data: { config_id: '${config_id}' },
|
||||
data: { config_id: '${agent["id"]}' },
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function (xhr, status) {
|
||||
$("#notification-config-modal").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#osxnotifyregister').click(function () {
|
||||
var osx_notify_app = $("#osx_notify_app").val();
|
||||
$.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { $('#ajaxMsg').html("<i class='fa fa-check'></i> " + data); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
})
|
||||
|
||||
$('#save-notification-item').click(function () {
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true);
|
||||
// Reload modal to update certain fields
|
||||
reloadModal();
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -127,8 +132,14 @@ from plexpy import helpers
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
});
|
||||
|
||||
$('#pushbullet_apikey').on('change', function () {
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true);
|
||||
reloadModal();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Never send checkbox values directly, always substitute value in hidden input.
|
||||
$('.checkboxes').click(function() {
|
||||
$('.checkboxes').click(function () {
|
||||
var configToggle = $(this).data('id');
|
||||
if ($(this).is(":checked")) {
|
||||
$("#"+configToggle).val(1);
|
||||
|
@@ -78,6 +78,20 @@ from plexpy import helpers
|
||||
</label>
|
||||
<p class="help-block">Trigger notification when the Plex Media Server cannot be reached internally.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_extup" ${helpers.checked(data['on_extup'])} class="toggle-switches">
|
||||
Notify on Plex remote access back up
|
||||
</label>
|
||||
<p class="help-block">Trigger notification when the Plex Media Server can be reached externally after being down.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_intup" ${helpers.checked(data['on_intup'])} class="toggle-switches">
|
||||
Notify on Plex server back up
|
||||
</label>
|
||||
<p class="help-block">Trigger notification when the Plex Media Server can be reached internally after being down.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -86,9 +86,9 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="group_history_tables" name="group_history_tables" value="1" ${config['group_history_tables']}> Group Table History
|
||||
<input type="checkbox" id="group_history_tables" name="group_history_tables" value="1" ${config['group_history_tables']}> Group Table and Watch Statistics History
|
||||
</label>
|
||||
<p class="help-block">Group successive play history by the same user as a single entry in tables.</p>
|
||||
<p class="help-block">Group successive play history by the same user as a single entry in the tables and watch statistics.</p>
|
||||
</div>
|
||||
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
|
||||
</div>
|
||||
@@ -109,9 +109,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<option id="card-popular_movies" value="popular_movies">Most Popular Movie</option>
|
||||
<option id="card-top_music" value="top_music">Most Listened Music</option>
|
||||
<option id="card-popular_music" value="popular_music">Most Popular Music</option>
|
||||
<option id="card-last_watched" value="last_watched">Last Watched</option>
|
||||
<option id="card-top_users" value="top_users">Most Active User</option>
|
||||
<option id="card-top_platforms" value="top_platforms">Most Active Platform</option>
|
||||
<option id="card-last_watched" value="last_watched">Last Watched</option>
|
||||
<option id="card-most_concurrent" value="most_concurrent">Most Concurrent Streams</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,7 +295,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<label>
|
||||
<input type="checkbox" id="pms_is_remote" name="pms_is_remote" value="1" ${config['pms_is_remote']}> Remote Server
|
||||
</label>
|
||||
<p class="help-block">Check this is your Plex Server is not on the same local network as PlexPy.</p>
|
||||
<p class="help-block">Check this if your Plex Server is not on the same local network as PlexPy.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
@@ -533,7 +534,8 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<label>
|
||||
<input type="checkbox" name="notify_recently_added_grandparent" id="notify_recently_added_grandparent" value="1" ${config['notify_recently_added_grandparent']}> Group notifications for recently added TV Shows or Music
|
||||
</label>
|
||||
<p class="help-block">Enable to only get one TV Show or Artist notification for recently added Episodes or Tracks. Movies are unaffected.</p>
|
||||
<p class="help-block">Enable to only get one TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.<br />
|
||||
Note: No Season/Episode or Album/Track metadata will be available.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_recently_added_delay">Notification Delay</label>
|
||||
@@ -714,6 +716,40 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-server fa-fw"></i> Plex Remote Access Back Up<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_extup_subject_text">Subject Line</label>
|
||||
<input class="form-control" type="text" id="notify_on_extup_subject_text" name="notify_on_extup_subject_text" value="${config['notify_on_extup_subject_text']}" data-parsley-trigger="change" required>
|
||||
<p class="help-block">Set a custom subject line.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_extup_body_text">Message Body</label>
|
||||
<textarea class="form-control" id="notify_on_extup_body_text" name="notify_on_extup_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_extup_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-server fa-fw"></i> Plex Server Back Up<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_intup_subject_text">Subject Line</label>
|
||||
<input class="form-control" type="text" id="notify_on_intup_subject_text" name="notify_on_intup_subject_text" value="${config['notify_on_intup_subject_text']}" data-parsley-trigger="change" required>
|
||||
<p class="help-block">Set a custom subject line.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_intup_body_text">Message Body</label>
|
||||
<textarea class="form-control" id="notify_on_intup_body_text" name="notify_on_intup_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_intup_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
|
||||
@@ -928,155 +964,248 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i
|
||||
class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title">Notification string substitutions</h4>
|
||||
<h4 class="modal-title">Notification String Substitutions</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
<p class="help-block">
|
||||
If a value for a selected parameter cannot be provided nothing will be outputted for it.
|
||||
If the value for a selected parameter cannot be provided, it will display as blank.
|
||||
</p>
|
||||
<table>
|
||||
<table class="notification-params">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Server Details
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="150"><strong>{server_name}</strong></td>
|
||||
<td><strong>{server_name}</strong></td>
|
||||
<td>The name of your Plex Server.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{server_uptime}</strong></td>
|
||||
<td><strong>{server_uptime}</strong></td>
|
||||
<td>The uptime (in days, hours, mins, secs) of your Plex Server.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="notification-params">
|
||||
<thead>
|
||||
<tr>
|
||||
<td width="150"><strong>{user}</strong></td>
|
||||
<th>
|
||||
Stream Details
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>{user}</strong></td>
|
||||
<td>The username of the person streaming.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{platform}</strong></td>
|
||||
<td><strong>{platform}</strong></td>
|
||||
<td>The type of client being used for playback.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{player}</strong></td>
|
||||
<td><strong>{player}</strong></td>
|
||||
<td>The name of the device being used for playback.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{ip_address}</strong></td>
|
||||
<td><strong>{ip_address}</strong></td>
|
||||
<td>The IP address of the device being used for playback. (PMS 0.9.14 and above)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{media_type}</strong></td>
|
||||
<td><strong>{media_type}</strong></td>
|
||||
<td>The type of media being played (movie, episode, track).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{title}</strong></td>
|
||||
<td>The full title of the item being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{show_name}</strong></td>
|
||||
<td>The title of the TV series being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{episode_name}</strong></td>
|
||||
<td>The title of the episode being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{artist_name}</strong></td>
|
||||
<td>The name of the artist being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{album_name}</strong></td>
|
||||
<td>The title of the album being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{track_name}</strong></td>
|
||||
<td>The title of the track being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{season_num}</strong></td>
|
||||
<td>The season number for the media item if item is episode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{season_num00}</strong></td>
|
||||
<td>The two digit season number.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{episode_num}</strong></td>
|
||||
<td>The episode number for the media item if item is episode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{episode_num00}</strong></td>
|
||||
<td>The two digit episode number.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{video_decision}</strong></td>
|
||||
<td>The video transcode decisions for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{audio_decision}</strong></td>
|
||||
<td>The audio transcode decisions for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{transcode_decision}</strong></td>
|
||||
<td>The stream transcode decisions for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{year}</strong></td>
|
||||
<td>The release year for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{studio}</strong></td>
|
||||
<td>The studio for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{content_rating}</strong></td>
|
||||
<td>The content rating for the media item. (e.g. TV-MA, TV-PG, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{directors}</strong></td>
|
||||
<td>A list of directors for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{writers}</strong></td>
|
||||
<td>A list of writers for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{actors}</strong></td>
|
||||
<td>A list of actors for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{genres}</strong></td>
|
||||
<td>A list of genres for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{summary}</strong></td>
|
||||
<td>A short plot summary for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{tagline}</strong></td>
|
||||
<td>A tagline for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{rating}</strong></td>
|
||||
<td>The rating (out of 10) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{duration}</strong></td>
|
||||
<td>The duration (in minutes) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{stream_duration}</strong></td>
|
||||
<td><strong>{stream_duration}</strong></td>
|
||||
<td>The stream duration (in minutes) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{remaining_duration}</strong></td>
|
||||
<td><strong>{remaining_duration}</strong></td>
|
||||
<td>The remaining duration (in minutes) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{progress}</strong></td>
|
||||
<td><strong>{progress}</strong></td>
|
||||
<td>The last reported offset (in minutes) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{progress_percent}</strong></td>
|
||||
<td><strong>{progress_percent}</strong></td>
|
||||
<td>The last reported progress percent for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{container}</strong></td>
|
||||
<td>The media container of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{video_codec}</strong></td>
|
||||
<td>The video codec of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{video_bitrate}</strong></td>
|
||||
<td>The video bitrate of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{video_width}</strong></td>
|
||||
<td>The video width of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{video_height}</strong></td>
|
||||
<td>The video height of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{video_resolution}</strong></td>
|
||||
<td>The video resolution of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{video_framerate}</strong></td>
|
||||
<td>The video framerate of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{aspect_ratio}</strong></td>
|
||||
<td>The aspect ratio of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{audio_codec}</strong></td>
|
||||
<td>The audio codec of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{audio_channels}</strong></td>
|
||||
<td>The audio channels of the original media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{transcode_decision}</strong></td>
|
||||
<td>The stream transcode decisions for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{video_decision}</strong></td>
|
||||
<td>The video transcode decisions for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{audio_decision}</strong></td>
|
||||
<td>The audio transcode decisions for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{transcode_container}</strong></td>
|
||||
<td>The media container of the transcoded media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{transcode_video_codec}</strong></td>
|
||||
<td>The video codec of the transcoded media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{transcode_video_width}</strong></td>
|
||||
<td>The video width of the transcoded media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{transcode_video_height}</strong></td>
|
||||
<td>The video height of the transcoded media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{transcode_audio_codec}</strong></td>
|
||||
<td>The audio codec of the transcoded media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{transcode_audio_channels}</strong></td>
|
||||
<td>The audio channels of the transcoded media.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="notification-params">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Metadata Details
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>{title}</strong></td>
|
||||
<td>The full title of the item being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{show_name}</strong></td>
|
||||
<td>The title of the TV series being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{episode_name}</strong></td>
|
||||
<td>The title of the episode being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{artist_name}</strong></td>
|
||||
<td>The name of the artist being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{album_name}</strong></td>
|
||||
<td>The title of the album being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{track_name}</strong></td>
|
||||
<td>The title of the track being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{season_num}</strong></td>
|
||||
<td>The season number for the media item if item is episode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{season_num00}</strong></td>
|
||||
<td>The two digit season number.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{episode_num}</strong></td>
|
||||
<td>The episode number for the media item if item is episode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{episode_num00}</strong></td>
|
||||
<td>The two digit episode number.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{year}</strong></td>
|
||||
<td>The release year for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{studio}</strong></td>
|
||||
<td>The studio for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{content_rating}</strong></td>
|
||||
<td>The content rating for the media item. (e.g. TV-MA, TV-PG, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{directors}</strong></td>
|
||||
<td>A list of directors for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{writers}</strong></td>
|
||||
<td>A list of writers for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{actors}</strong></td>
|
||||
<td>A list of actors for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{genres}</strong></td>
|
||||
<td>A list of genres for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{summary}</strong></td>
|
||||
<td>A short plot summary for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{tagline}</strong></td>
|
||||
<td>A tagline for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{rating}</strong></td>
|
||||
<td>The rating (out of 10) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{duration}</strong></td>
|
||||
<td>The duration (in minutes) for the item.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1090,7 +1219,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title">Notification exclusion tags</h4>
|
||||
<h4 class="modal-title">Notification Exclusion Tags</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
@@ -1099,21 +1228,24 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
</div>
|
||||
<div>
|
||||
<p class="help-block">All text inside a <strong>movie</strong> tag will only be sent when the media item being played back is a movie.</p>
|
||||
<pre>Example: {user} has started playing {title} <movie>({year})</movie></pre>
|
||||
<p><strong style="color: #fff;">Example:</strong></p>
|
||||
<pre>{user} has started playing {title} <movie>({year})</movie></pre>
|
||||
</div>
|
||||
<div class="wellheader">
|
||||
<h4>TV Tag <strong><tv></tv></strong></h4>
|
||||
</div>
|
||||
<div>
|
||||
<p class="help-block">All text inside a <strong>tv</strong> tag will only be sent when the media item being played back is an episode.</p>
|
||||
<pre>Example: {user} has started playing {title} <tv>(S{season_num}E{episode_num})</tv></pre>
|
||||
<p><strong style="color: #fff;">Example:</strong></p>
|
||||
<pre>{user} has started playing {title} <tv>(S{season_num}E{episode_num})</tv></pre>
|
||||
</div>
|
||||
<div class="wellheader">
|
||||
<h4>Music Tag <strong><music></music></strong></h4>
|
||||
</div>
|
||||
<div>
|
||||
<p class="help-block">All text inside a <strong>music</strong> tag will only be sent when the media item being played back is a music track.</p>
|
||||
<pre>Example: {user} has started playing {title} <music>(Track {episode_num})</music></pre>
|
||||
<p><strong style="color: #fff;">Example:</strong></p>
|
||||
<pre>{user} has started playing {title} <music>(Track {episode_num})</music></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1130,7 +1262,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<h4 class="modal-title">Changelog</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${versioncheck.read_changelog()}
|
||||
${versioncheck.read_changelog() | n}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
|
@@ -36,6 +36,11 @@ def check_active_sessions(ws_request=False):
|
||||
global int_ping_count
|
||||
|
||||
if session_list:
|
||||
if int_ping_count >= 3:
|
||||
logger.info(u"PlexPy Monitor :: The Plex Media Server is back up.")
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(notify_action='intup')).start()
|
||||
int_ping_count = 0
|
||||
|
||||
media_container = session_list['sessions']
|
||||
@@ -127,7 +132,8 @@ def check_active_sessions(ws_request=False):
|
||||
kwargs=dict(stream_data=stream, notify_action='buffer')).start()
|
||||
|
||||
logger.debug(u"PlexPy Monitor :: Stream buffering. Count is now %s. Last triggered %s."
|
||||
% (buffer_values[0][0], buffer_values[0][1]))
|
||||
% (buffer_values[0]['buffer_count'],
|
||||
buffer_values[0]['buffer_last_triggered']))
|
||||
|
||||
# Check if the user has reached the offset in the media we defined as the "watched" percent
|
||||
# Don't trigger if state is buffer as some clients push the progress to the end when
|
||||
@@ -247,7 +253,7 @@ def check_server_response():
|
||||
server_response = pms_connect.get_server_response()
|
||||
|
||||
global ext_ping_count
|
||||
|
||||
|
||||
# Check for remote access
|
||||
if server_response:
|
||||
|
||||
@@ -266,6 +272,11 @@ def check_server_response():
|
||||
% str(ext_ping_count))
|
||||
# Reset external ping counter
|
||||
else:
|
||||
if ext_ping_count >= 3:
|
||||
logger.info(u"PlexPy Monitor :: Plex remote access is back up.")
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(notify_action='extup')).start()
|
||||
ext_ping_count = 0
|
||||
|
||||
if ext_ping_count == 3:
|
||||
|
@@ -180,18 +180,18 @@ class ActivityProcessor(object):
|
||||
|
||||
result = self.db.select(query=query, args=args)
|
||||
|
||||
new_session = {'id': result[0][0],
|
||||
'rating_key': result[0][1],
|
||||
'user_id': result[0][2],
|
||||
'reference_id': result[0][3]}
|
||||
new_session = {'id': result[0]['id'],
|
||||
'rating_key': result[0]['rating_key'],
|
||||
'user_id': result[0]['user_id'],
|
||||
'reference_id': result[0]['reference_id']}
|
||||
|
||||
if len(result) == 1:
|
||||
prev_session = None
|
||||
else:
|
||||
prev_session = {'id': result[1][0],
|
||||
'rating_key': result[1][1],
|
||||
'user_id': result[1][2],
|
||||
'reference_id': result[1][3]}
|
||||
prev_session = {'id': result[1]['id'],
|
||||
'rating_key': result[1]['rating_key'],
|
||||
'user_id': result[1]['user_id'],
|
||||
'reference_id': result[1]['reference_id']}
|
||||
|
||||
query = 'UPDATE session_history SET reference_id = ? WHERE id = ? '
|
||||
# If rating_key is the same in the previous session, then set the reference_id to the previous row, else set the reference_id to the new id
|
||||
@@ -400,7 +400,7 @@ class ActivityProcessor(object):
|
||||
'WHERE session_key = ?',
|
||||
[session_key])
|
||||
if buffer_count:
|
||||
return buffer_count
|
||||
return buffer_count['buffer_count']
|
||||
|
||||
return 0
|
||||
|
||||
@@ -417,6 +417,6 @@ class ActivityProcessor(object):
|
||||
'WHERE session_key = ?',
|
||||
[session_key])
|
||||
if last_time:
|
||||
return last_time
|
||||
return last_time['buffer_last_triggered']
|
||||
|
||||
return None
|
@@ -48,6 +48,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'BOXCAR_ON_CREATED': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_EXTDOWN': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_EXTUP': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_INTUP': (int, 'Boxcar', 0),
|
||||
'BUFFER_THRESHOLD': (int, 'Monitoring', 3),
|
||||
'BUFFER_WAIT': (int, 'Monitoring', 900),
|
||||
'CACHE_DIR': (str, 'General', ''),
|
||||
@@ -61,6 +63,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'EMAIL_ENABLED': (int, 'Email', 0),
|
||||
'EMAIL_FROM': (str, 'Email', ''),
|
||||
'EMAIL_TO': (str, 'Email', ''),
|
||||
'EMAIL_CC': (str, 'Email', ''),
|
||||
'EMAIL_BCC': (str, 'Email', ''),
|
||||
'EMAIL_SMTP_SERVER': (str, 'Email', ''),
|
||||
'EMAIL_SMTP_USER': (str, 'Email', ''),
|
||||
'EMAIL_SMTP_PASSWORD': (str, 'Email', ''),
|
||||
@@ -75,12 +79,17 @@ _CONFIG_DEFINITIONS = {
|
||||
'EMAIL_ON_CREATED': (int, 'Email', 0),
|
||||
'EMAIL_ON_EXTDOWN': (int, 'Email', 0),
|
||||
'EMAIL_ON_INTDOWN': (int, 'Email', 0),
|
||||
'EMAIL_ON_EXTUP': (int, 'Email', 0),
|
||||
'EMAIL_ON_INTUP': (int, 'Email', 0),
|
||||
'ENABLE_HTTPS': (int, 'General', 0),
|
||||
'FIRST_RUN_COMPLETE': (int, 'General', 0),
|
||||
'FREEZE_DB': (int, 'General', 0),
|
||||
'GIT_BRANCH': (str, 'General', 'master'),
|
||||
'GIT_PATH': (str, 'General', ''),
|
||||
'GIT_USER': (str, 'General', 'drzoidberg33'),
|
||||
'GRAPH_TYPE': (str, 'General', 'plays'),
|
||||
'GRAPH_DAYS': (int, 'General', 30),
|
||||
'GRAPH_TAB': (str, 'General', 'tabs-1'),
|
||||
'GROUP_HISTORY_TABLES': (int, 'General', 0),
|
||||
'GROWL_ENABLED': (int, 'Growl', 0),
|
||||
'GROWL_HOST': (str, 'Growl', ''),
|
||||
@@ -94,11 +103,14 @@ _CONFIG_DEFINITIONS = {
|
||||
'GROWL_ON_CREATED': (int, 'Growl', 0),
|
||||
'GROWL_ON_EXTDOWN': (int, 'Growl', 0),
|
||||
'GROWL_ON_INTDOWN': (int, 'Growl', 0),
|
||||
'GROWL_ON_EXTUP': (int, 'Growl', 0),
|
||||
'GROWL_ON_INTUP': (int, 'Growl', 0),
|
||||
'HOME_LIBRARY_CARDS': (str, 'General', 'library_statistics_first'),
|
||||
'HOME_STATS_LENGTH': (int, 'General', 30),
|
||||
'HOME_STATS_TYPE': (int, 'General', 0),
|
||||
'HOME_STATS_COUNT': (int, 'General', 5),
|
||||
'HOME_STATS_CARDS': (str, 'General', 'watch_statistics, top_tv, popular_tv, top_movies, popular_movies, top_music, popular_music, top_users, top_platforms, last_watched'),
|
||||
'HOME_STATS_CARDS': (str, 'General', 'watch_statistics, top_tv, popular_tv, top_movies, popular_movies, ' \
|
||||
'top_music, popular_music, last_watched, top_users, top_platforms, most_concurrent'),
|
||||
'HTTPS_CERT': (str, 'General', ''),
|
||||
'HTTPS_KEY': (str, 'General', ''),
|
||||
'HTTP_HOST': (str, 'General', '0.0.0.0'),
|
||||
@@ -121,6 +133,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'IFTTT_ON_CREATED': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_EXTDOWN': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_EXTUP': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_INTUP': (int, 'IFTTT', 0),
|
||||
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
|
||||
'LAUNCH_BROWSER': (int, 'General', 1),
|
||||
'LOG_DIR': (str, 'General', ''),
|
||||
@@ -150,6 +164,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'NMA_ON_CREATED': (int, 'NMA', 0),
|
||||
'NMA_ON_EXTDOWN': (int, 'NMA', 0),
|
||||
'NMA_ON_INTDOWN': (int, 'NMA', 0),
|
||||
'NMA_ON_EXTUP': (int, 'NMA', 0),
|
||||
'NMA_ON_INTUP': (int, 'NMA', 0),
|
||||
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
|
||||
'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0),
|
||||
'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0),
|
||||
@@ -173,6 +189,10 @@ _CONFIG_DEFINITIONS = {
|
||||
'NOTIFY_ON_EXTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is down.'),
|
||||
'NOTIFY_ON_INTDOWN_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_INTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is down.'),
|
||||
'NOTIFY_ON_EXTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_EXTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is back up.'),
|
||||
'NOTIFY_ON_INTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_INTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is back up.'),
|
||||
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'),
|
||||
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_PLAY': (int, 'OSX_Notify', 0),
|
||||
@@ -184,6 +204,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'OSX_NOTIFY_ON_CREATED': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_EXTDOWN': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_EXTUP': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_INTUP': (int, 'OSX_Notify', 0),
|
||||
'PLEX_CLIENT_HOST': (str, 'Plex', ''),
|
||||
'PLEX_ENABLED': (int, 'Plex', 0),
|
||||
'PLEX_PASSWORD': (str, 'Plex', ''),
|
||||
@@ -197,6 +219,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'PLEX_ON_CREATED': (int, 'Plex', 0),
|
||||
'PLEX_ON_EXTDOWN': (int, 'Plex', 0),
|
||||
'PLEX_ON_INTDOWN': (int, 'Plex', 0),
|
||||
'PLEX_ON_EXTUP': (int, 'Plex', 0),
|
||||
'PLEX_ON_INTUP': (int, 'Plex', 0),
|
||||
'PROWL_ENABLED': (int, 'Prowl', 0),
|
||||
'PROWL_KEYS': (str, 'Prowl', ''),
|
||||
'PROWL_PRIORITY': (int, 'Prowl', 0),
|
||||
@@ -209,6 +233,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'PROWL_ON_CREATED': (int, 'Prowl', 0),
|
||||
'PROWL_ON_EXTDOWN': (int, 'Prowl', 0),
|
||||
'PROWL_ON_INTDOWN': (int, 'Prowl', 0),
|
||||
'PROWL_ON_EXTUP': (int, 'Prowl', 0),
|
||||
'PROWL_ON_INTUP': (int, 'Prowl', 0),
|
||||
'PUSHALOT_APIKEY': (str, 'Pushalot', ''),
|
||||
'PUSHALOT_ENABLED': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0),
|
||||
@@ -220,6 +246,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'PUSHALOT_ON_CREATED': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_EXTDOWN': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_EXTUP': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_INTUP': (int, 'Pushalot', 0),
|
||||
'PUSHBULLET_APIKEY': (str, 'PushBullet', ''),
|
||||
'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''),
|
||||
'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''),
|
||||
@@ -233,6 +261,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'PUSHBULLET_ON_CREATED': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_EXTDOWN': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_EXTUP': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_INTUP': (int, 'PushBullet', 0),
|
||||
'PUSHOVER_APITOKEN': (str, 'Pushover', ''),
|
||||
'PUSHOVER_ENABLED': (int, 'Pushover', 0),
|
||||
'PUSHOVER_KEYS': (str, 'Pushover', ''),
|
||||
@@ -247,6 +277,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'PUSHOVER_ON_CREATED': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_EXTDOWN': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_EXTUP': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_INTUP': (int, 'Pushover', 0),
|
||||
'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12),
|
||||
'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1),
|
||||
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
|
||||
@@ -261,6 +293,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'TELEGRAM_ON_CREATED': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_EXTDOWN': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_EXTUP': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_INTUP': (int, 'Telegram', 0),
|
||||
'TV_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||
'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0),
|
||||
'TV_NOTIFY_ON_START': (int, 'Monitoring', 1),
|
||||
@@ -279,6 +313,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'TWITTER_ON_CREATED': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_EXTDOWN': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_INTDOWN': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_EXTUP': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_INTUP': (int, 'Twitter', 0),
|
||||
'UPDATE_DB_INTERVAL': (int, 'General', 24),
|
||||
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
|
||||
'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||
@@ -294,7 +330,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'XBMC_ON_WATCHED': (int, 'XBMC', 0),
|
||||
'XBMC_ON_CREATED': (int, 'XBMC', 0),
|
||||
'XBMC_ON_EXTDOWN': (int, 'XBMC', 0),
|
||||
'XBMC_ON_INTDOWN': (int, 'XBMC', 0)
|
||||
'XBMC_ON_INTDOWN': (int, 'XBMC', 0),
|
||||
'XBMC_ON_EXTUP': (int, 'XBMC', 0),
|
||||
'XBMC_ON_INTUP': (int, 'XBMC', 0)
|
||||
}
|
||||
# pylint:disable=R0902
|
||||
# it might be nice to refactor for fewer instance variables
|
||||
|
@@ -46,6 +46,13 @@ def get_cache_size():
|
||||
return 0
|
||||
return int(plexpy.CONFIG.CACHE_SIZEMB)
|
||||
|
||||
def dict_factory(cursor, row):
|
||||
d = {}
|
||||
for idx, col in enumerate(cursor.description):
|
||||
d[col[0]] = row[idx]
|
||||
|
||||
return d
|
||||
|
||||
|
||||
class MonitorDatabase(object):
|
||||
|
||||
@@ -58,14 +65,7 @@ class MonitorDatabase(object):
|
||||
self.connection.execute("PRAGMA journal_mode = %s" % plexpy.CONFIG.JOURNAL_MODE)
|
||||
# 64mb of cache memory, probably need to make it user configurable
|
||||
self.connection.execute("PRAGMA cache_size=-%s" % (get_cache_size() * 1024))
|
||||
self.connection.row_factory = self.dict_factory
|
||||
|
||||
def dict_factory(self, cursor, row):
|
||||
d = {}
|
||||
for idx, col in enumerate(cursor.description):
|
||||
d[col[0]] = row[idx]
|
||||
|
||||
return d
|
||||
self.connection.row_factory = dict_factory
|
||||
|
||||
def action(self, query, args=None, return_last_id=False):
|
||||
if query is None:
|
||||
@@ -111,7 +111,7 @@ class MonitorDatabase(object):
|
||||
|
||||
def select_single(self, query, args=None):
|
||||
|
||||
sql_results = self.action(query, args).fetchone()[0]
|
||||
sql_results = self.action(query, args).fetchone()
|
||||
|
||||
if sql_results is None or sql_results == "":
|
||||
return ""
|
||||
|
@@ -37,15 +37,16 @@ class DataFactory(object):
|
||||
'MIN(started) AS started',
|
||||
'MAX(stopped) AS stopped',
|
||||
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - \
|
||||
SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS duration',
|
||||
SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS duration',
|
||||
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter',
|
||||
'session_history.user_id',
|
||||
'session_history.user',
|
||||
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) as friendly_name',
|
||||
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) \
|
||||
AS friendly_name',
|
||||
'platform',
|
||||
'player',
|
||||
'ip_address',
|
||||
'session_history_metadata.media_type',
|
||||
'session_history.media_type',
|
||||
'session_history_metadata.rating_key',
|
||||
'session_history_metadata.parent_rating_key',
|
||||
'session_history_metadata.grandparent_rating_key',
|
||||
@@ -58,7 +59,8 @@ class DataFactory(object):
|
||||
'session_history_metadata.parent_thumb',
|
||||
'session_history_metadata.grandparent_thumb',
|
||||
'((CASE WHEN view_offset IS NULL THEN 0.1 ELSE view_offset * 1.0 END) / \
|
||||
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
|
||||
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 \
|
||||
ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
|
||||
'session_history_media_info.video_decision',
|
||||
'session_history_media_info.audio_decision',
|
||||
'COUNT(*) AS group_count',
|
||||
@@ -80,7 +82,7 @@ class DataFactory(object):
|
||||
['session_history.id', 'session_history_media_info.id']],
|
||||
kwargs=kwargs)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_history.")
|
||||
return {'recordsFiltered': 0,
|
||||
'recordsTotal': 0,
|
||||
'draw': 0,
|
||||
@@ -89,8 +91,13 @@ class DataFactory(object):
|
||||
|
||||
history = query['result']
|
||||
|
||||
filter_duration = 0
|
||||
total_duration = self.get_total_duration(custom_where=custom_where)
|
||||
|
||||
rows = []
|
||||
for item in history:
|
||||
filter_duration += int(item['duration'])
|
||||
|
||||
if item["media_type"] == 'episode' and item["parent_thumb"]:
|
||||
thumb = item["parent_thumb"]
|
||||
elif item["media_type"] == 'episode':
|
||||
@@ -108,9 +115,6 @@ class DataFactory(object):
|
||||
# Rename Mystery platform names
|
||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
||||
|
||||
# Sanitize player name
|
||||
player = helpers.sanitize(item["player"])
|
||||
|
||||
row = {"reference_id": item["reference_id"],
|
||||
"id": item["id"],
|
||||
"date": item["date"],
|
||||
@@ -122,7 +126,7 @@ class DataFactory(object):
|
||||
"user": item["user"],
|
||||
"friendly_name": item["friendly_name"],
|
||||
"platform": platform,
|
||||
"player": player,
|
||||
"player": item['player'],
|
||||
"ip_address": item["ip_address"],
|
||||
"media_type": item["media_type"],
|
||||
"rating_key": item["rating_key"],
|
||||
@@ -147,14 +151,17 @@ class DataFactory(object):
|
||||
dict = {'recordsFiltered': query['filteredCount'],
|
||||
'recordsTotal': query['totalCount'],
|
||||
'data': rows,
|
||||
'draw': query['draw']
|
||||
'draw': query['draw'],
|
||||
'filter_duration': helpers.human_duration(filter_duration, sig='dhm'),
|
||||
'total_duration': helpers.human_duration(total_duration, sig='dhm')
|
||||
}
|
||||
|
||||
return dict
|
||||
|
||||
def get_home_stats(self, time_range='30', stats_type=0, stats_count='5', stats_cards='', notify_watched_percent='85'):
|
||||
def get_home_stats(self, grouping=0, time_range='30', stats_type=0, stats_count='5', stats_cards='', notify_watched_percent='85'):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
||||
sort_type = 'total_plays' if stats_type == 0 else 'total_duration'
|
||||
|
||||
home_stats = []
|
||||
@@ -163,23 +170,20 @@ class DataFactory(object):
|
||||
if stat == 'top_tv':
|
||||
top_tv = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
'session_history_metadata.grandparent_title, ' \
|
||||
'COUNT(session_history_metadata.grandparent_title) as total_plays, ' \
|
||||
'SUM(case when session_history.stopped > 0 ' \
|
||||
'then (session_history.stopped - session_history.started) ' \
|
||||
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
|
||||
'else 0 end) as total_duration, ' \
|
||||
'session_history_metadata.grandparent_rating_key, ' \
|
||||
'MAX(session_history.started) as last_watch,' \
|
||||
'session_history_metadata.grandparent_thumb ' \
|
||||
'FROM session_history_metadata ' \
|
||||
'JOIN session_history on session_history_metadata.id = session_history.id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
'>= datetime("now", "-%s days", "localtime") ' \
|
||||
'AND session_history_metadata.media_type = "episode" ' \
|
||||
'GROUP BY session_history_metadata.grandparent_title ' \
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, ' \
|
||||
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \
|
||||
'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \
|
||||
' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \
|
||||
' AS total_duration ' \
|
||||
'FROM (SELECT * FROM session_history ' \
|
||||
' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
|
||||
' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
' >= datetime("now", "-%s days", "localtime") ' \
|
||||
' AND session_history.media_type = "episode" ' \
|
||||
' GROUP BY %s) AS t ' \
|
||||
'GROUP BY t.grandparent_title ' \
|
||||
'ORDER BY %s DESC ' \
|
||||
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_tv.")
|
||||
@@ -209,25 +213,21 @@ class DataFactory(object):
|
||||
elif stat == 'popular_tv':
|
||||
popular_tv = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
'session_history_metadata.grandparent_title, ' \
|
||||
'COUNT(DISTINCT session_history.user_id) as users_watched, ' \
|
||||
'session_history_metadata.grandparent_rating_key, ' \
|
||||
'MAX(session_history.started) as last_watch, ' \
|
||||
'COUNT(session_history.id) as total_plays, ' \
|
||||
'SUM(case when session_history.stopped > 0 ' \
|
||||
'then (session_history.stopped - session_history.started) ' \
|
||||
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
|
||||
'else 0 end) as total_duration, ' \
|
||||
'session_history_metadata.grandparent_thumb ' \
|
||||
'FROM session_history_metadata ' \
|
||||
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
'>= datetime("now", "-%s days", "localtime") ' \
|
||||
'AND session_history_metadata.media_type = "episode" ' \
|
||||
'GROUP BY session_history_metadata.grandparent_title ' \
|
||||
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, ' \
|
||||
'COUNT(DISTINCT t.user_id) AS users_watched, ' \
|
||||
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, ' \
|
||||
'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \
|
||||
' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \
|
||||
' AS total_duration ' \
|
||||
'FROM (SELECT * FROM session_history ' \
|
||||
' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
|
||||
' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
' >= datetime("now", "-%s days", "localtime") ' \
|
||||
' AND session_history.media_type = "episode" ' \
|
||||
' GROUP BY %s) AS t ' \
|
||||
'GROUP BY t.grandparent_title ' \
|
||||
'ORDER BY users_watched DESC, %s DESC ' \
|
||||
'LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: popular_tv.")
|
||||
@@ -255,23 +255,20 @@ class DataFactory(object):
|
||||
elif stat == 'top_movies':
|
||||
top_movies = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
'session_history_metadata.full_title, ' \
|
||||
'COUNT(session_history_metadata.full_title) as total_plays, ' \
|
||||
'SUM(case when session_history.stopped > 0 ' \
|
||||
'then (session_history.stopped - session_history.started) ' \
|
||||
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
|
||||
'else 0 end) as total_duration, ' \
|
||||
'session_history_metadata.rating_key, ' \
|
||||
'MAX(session_history.started) as last_watch,' \
|
||||
'session_history_metadata.thumb ' \
|
||||
'FROM session_history_metadata ' \
|
||||
'JOIN session_history on session_history_metadata.id = session_history.id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
'>= datetime("now", "-%s days", "localtime") ' \
|
||||
'AND session_history_metadata.media_type = "movie" ' \
|
||||
'GROUP BY session_history_metadata.full_title ' \
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, ' \
|
||||
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \
|
||||
'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \
|
||||
' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \
|
||||
' AS total_duration ' \
|
||||
'FROM (SELECT * FROM session_history ' \
|
||||
' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
|
||||
' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
' >= datetime("now", "-%s days", "localtime") ' \
|
||||
' AND session_history.media_type = "movie" ' \
|
||||
' GROUP BY %s) AS t ' \
|
||||
'GROUP BY t.full_title ' \
|
||||
'ORDER BY %s DESC ' \
|
||||
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_movies.")
|
||||
@@ -293,7 +290,6 @@ class DataFactory(object):
|
||||
'row_id': item['id']
|
||||
}
|
||||
top_movies.append(row)
|
||||
|
||||
home_stats.append({'stat_id': stat,
|
||||
'stat_type': sort_type,
|
||||
'rows': top_movies})
|
||||
@@ -301,25 +297,21 @@ class DataFactory(object):
|
||||
elif stat == 'popular_movies':
|
||||
popular_movies = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
'session_history_metadata.full_title, ' \
|
||||
'COUNT(DISTINCT session_history.user_id) as users_watched, ' \
|
||||
'session_history_metadata.rating_key, ' \
|
||||
'MAX(session_history.started) as last_watch, ' \
|
||||
'COUNT(session_history.id) as total_plays, ' \
|
||||
'SUM(case when session_history.stopped > 0 ' \
|
||||
'then (session_history.stopped - session_history.started) ' \
|
||||
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
|
||||
'else 0 end) as total_duration, ' \
|
||||
'session_history_metadata.thumb ' \
|
||||
'FROM session_history_metadata ' \
|
||||
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
'>= datetime("now", "-%s days", "localtime") ' \
|
||||
'AND session_history_metadata.media_type = "movie" ' \
|
||||
'GROUP BY session_history_metadata.full_title ' \
|
||||
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, ' \
|
||||
'COUNT(DISTINCT t.user_id) AS users_watched, ' \
|
||||
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, ' \
|
||||
'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \
|
||||
' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \
|
||||
' AS total_duration ' \
|
||||
'FROM (SELECT * FROM session_history ' \
|
||||
' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
|
||||
' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
' >= datetime("now", "-%s days", "localtime") ' \
|
||||
' AND session_history.media_type = "movie" ' \
|
||||
' GROUP BY %s) AS t ' \
|
||||
'GROUP BY t.full_title ' \
|
||||
'ORDER BY users_watched DESC, %s DESC ' \
|
||||
'LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: popular_movies.")
|
||||
@@ -347,23 +339,20 @@ class DataFactory(object):
|
||||
elif stat == 'top_music':
|
||||
top_music = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
'session_history_metadata.grandparent_title, ' \
|
||||
'COUNT(session_history_metadata.grandparent_title) as total_plays, ' \
|
||||
'SUM(case when session_history.stopped > 0 ' \
|
||||
'then (session_history.stopped - session_history.started) ' \
|
||||
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
|
||||
'else 0 end) as total_duration, ' \
|
||||
'session_history_metadata.grandparent_rating_key, ' \
|
||||
'MAX(session_history.started) as last_watch,' \
|
||||
'session_history_metadata.grandparent_thumb ' \
|
||||
'FROM session_history_metadata ' \
|
||||
'JOIN session_history on session_history_metadata.id = session_history.id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
'>= datetime("now", "-%s days", "localtime") ' \
|
||||
'AND session_history_metadata.media_type = "track" ' \
|
||||
'GROUP BY session_history_metadata.grandparent_title ' \
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, ' \
|
||||
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \
|
||||
'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \
|
||||
' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \
|
||||
' AS total_duration ' \
|
||||
'FROM (SELECT * FROM session_history ' \
|
||||
' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
|
||||
' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
' >= datetime("now", "-%s days", "localtime") ' \
|
||||
' AND session_history.media_type = "track" ' \
|
||||
' GROUP BY %s) AS t ' \
|
||||
'GROUP BY t.grandparent_title ' \
|
||||
'ORDER BY %s DESC ' \
|
||||
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_music.")
|
||||
@@ -393,25 +382,21 @@ class DataFactory(object):
|
||||
elif stat == 'popular_music':
|
||||
popular_music = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
'session_history_metadata.grandparent_title, ' \
|
||||
'COUNT(DISTINCT session_history.user_id) as users_watched, ' \
|
||||
'session_history_metadata.grandparent_rating_key, ' \
|
||||
'MAX(session_history.started) as last_watch, ' \
|
||||
'COUNT(session_history.id) as total_plays, ' \
|
||||
'SUM(case when session_history.stopped > 0 ' \
|
||||
'then (session_history.stopped - session_history.started) ' \
|
||||
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
|
||||
'else 0 end) as total_duration, ' \
|
||||
'session_history_metadata.grandparent_thumb ' \
|
||||
'FROM session_history_metadata ' \
|
||||
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
'>= datetime("now", "-%s days", "localtime") ' \
|
||||
'AND session_history_metadata.media_type = "track" ' \
|
||||
'GROUP BY session_history_metadata.grandparent_title ' \
|
||||
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, ' \
|
||||
'COUNT(DISTINCT t.user_id) AS users_watched, ' \
|
||||
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, ' \
|
||||
'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \
|
||||
' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \
|
||||
' AS total_duration ' \
|
||||
'FROM (SELECT * FROM session_history ' \
|
||||
' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
|
||||
' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
' >= datetime("now", "-%s days", "localtime") ' \
|
||||
' AND session_history.media_type = "track" ' \
|
||||
' GROUP BY %s) AS t ' \
|
||||
'GROUP BY t.grandparent_title ' \
|
||||
'ORDER BY users_watched DESC, %s DESC ' \
|
||||
'LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: popular_music.")
|
||||
@@ -439,24 +424,22 @@ class DataFactory(object):
|
||||
elif stat == 'top_users':
|
||||
top_users = []
|
||||
try:
|
||||
query = 'SELECT session_history.user, ' \
|
||||
'(case when users.friendly_name is null then users.username else ' \
|
||||
'users.friendly_name end) as friendly_name,' \
|
||||
'COUNT(session_history.id) as total_plays, ' \
|
||||
'SUM(case when session_history.stopped > 0 ' \
|
||||
'then (session_history.stopped - session_history.started) ' \
|
||||
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
|
||||
'else 0 end) as total_duration, ' \
|
||||
'MAX(session_history.started) as last_watch, ' \
|
||||
'users.custom_avatar_url as thumb, ' \
|
||||
'users.user_id ' \
|
||||
'FROM session_history ' \
|
||||
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
|
||||
'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
|
||||
'datetime("now", "-%s days", "localtime") '\
|
||||
'GROUP BY session_history.user_id ' \
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
query = 'SELECT t.user, t.user_id, t.custom_avatar_url as thumb, ' \
|
||||
'(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \
|
||||
' AS friendly_name, ' \
|
||||
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \
|
||||
'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \
|
||||
' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \
|
||||
' AS total_duration ' \
|
||||
'FROM (SELECT * FROM session_history ' \
|
||||
' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
|
||||
' LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \
|
||||
' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
' >= datetime("now", "-%s days", "localtime") ' \
|
||||
' GROUP BY %s) AS t ' \
|
||||
'GROUP BY t.user_id ' \
|
||||
'ORDER BY %s DESC ' \
|
||||
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_users.")
|
||||
@@ -493,18 +476,19 @@ class DataFactory(object):
|
||||
top_platform = []
|
||||
|
||||
try:
|
||||
query = 'SELECT session_history.platform, ' \
|
||||
'COUNT(session_history.id) as total_plays, ' \
|
||||
'SUM(case when session_history.stopped > 0 ' \
|
||||
'then (session_history.stopped - session_history.started) ' \
|
||||
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
|
||||
'else 0 end) as total_duration, ' \
|
||||
'MAX(session_history.started) as last_watch ' \
|
||||
'FROM session_history ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
'>= datetime("now", "-%s days", "localtime") ' \
|
||||
'GROUP BY session_history.platform ' \
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
query = 'SELECT t.platform, ' \
|
||||
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \
|
||||
'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \
|
||||
' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \
|
||||
' AS total_duration ' \
|
||||
'FROM (SELECT * FROM session_history ' \
|
||||
' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
|
||||
' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
' >= datetime("now", "-%s days", "localtime") ' \
|
||||
' GROUP BY %s) AS t ' \
|
||||
'GROUP BY t.platform ' \
|
||||
'ORDER BY %s DESC ' \
|
||||
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_platforms.")
|
||||
@@ -537,33 +521,26 @@ class DataFactory(object):
|
||||
elif stat == 'last_watched':
|
||||
last_watched = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
'session_history.user, ' \
|
||||
'(case when users.friendly_name is null then users.username else ' \
|
||||
'users.friendly_name end) as friendly_name,' \
|
||||
'users.user_id, ' \
|
||||
'users.custom_avatar_url as user_thumb, ' \
|
||||
'session_history_metadata.full_title, ' \
|
||||
'session_history_metadata.rating_key, ' \
|
||||
'session_history_metadata.thumb, ' \
|
||||
'session_history_metadata.grandparent_thumb, ' \
|
||||
'MAX(session_history.started) as last_watch, ' \
|
||||
'session_history.player, ' \
|
||||
'((CASE WHEN session_history.view_offset IS NULL THEN 0.1 ELSE \
|
||||
session_history.view_offset * 1.0 END) / \
|
||||
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \
|
||||
session_history_metadata.duration * 1.0 END) * 100) as percent_complete ' \
|
||||
'FROM session_history_metadata ' \
|
||||
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
|
||||
'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
'>= datetime("now", "-%s days", "localtime") ' \
|
||||
'AND (session_history_metadata.media_type = "movie" ' \
|
||||
'OR session_history_metadata.media_type = "episode") ' \
|
||||
'AND percent_complete >= %s ' \
|
||||
'GROUP BY session_history.id ' \
|
||||
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.grandparent_thumb, ' \
|
||||
't.user, t.user_id, t.custom_avatar_url as user_thumb, t.player, ' \
|
||||
'(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \
|
||||
' AS friendly_name, ' \
|
||||
'MAX(t.started) AS last_watch, ' \
|
||||
'((CASE WHEN t.view_offset IS NULL THEN 0.1 ELSE t.view_offset * 1.0 END) / ' \
|
||||
' (CASE WHEN t.duration IS NULL THEN 1.0 ELSE t.duration * 1.0 END) * 100) ' \
|
||||
' AS percent_complete ' \
|
||||
'FROM (SELECT * FROM session_history ' \
|
||||
' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
|
||||
' LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \
|
||||
' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||
' >= datetime("now", "-%s days", "localtime") ' \
|
||||
' AND (session_history.media_type = "movie" ' \
|
||||
' OR session_history_metadata.media_type = "episode") ' \
|
||||
' GROUP BY %s) AS t ' \
|
||||
'WHERE percent_complete >= %s ' \
|
||||
'GROUP BY t.id ' \
|
||||
'ORDER BY last_watch DESC ' \
|
||||
'LIMIT %s' % (time_range, notify_watched_percent, stats_count)
|
||||
'LIMIT %s' % (time_range, group_by, notify_watched_percent, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: last_watched.")
|
||||
@@ -575,9 +552,6 @@ class DataFactory(object):
|
||||
else:
|
||||
thumb = item['grandparent_thumb']
|
||||
|
||||
# Sanitize player name
|
||||
player = helpers.sanitize(item["player"])
|
||||
|
||||
row = {'row_id': item['id'],
|
||||
'user': item['user'],
|
||||
'friendly_name': item['friendly_name'],
|
||||
@@ -588,13 +562,51 @@ class DataFactory(object):
|
||||
'thumb': thumb,
|
||||
'grandparent_thumb': item['grandparent_thumb'],
|
||||
'last_watch': item['last_watch'],
|
||||
'player': player,
|
||||
'player': item['player']
|
||||
}
|
||||
last_watched.append(row)
|
||||
|
||||
home_stats.append({'stat_id': stat,
|
||||
'rows': last_watched})
|
||||
|
||||
elif stat == 'most_concurrent':
|
||||
try:
|
||||
query = 'SELECT started, stopped ' \
|
||||
'FROM session_history ' \
|
||||
'WHERE datetime(stopped, "unixepoch", "localtime") ' \
|
||||
'>= datetime("now", "-%s days", "localtime") ' % time_range
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_home_stats: most_concurrent.")
|
||||
return None
|
||||
|
||||
times = []
|
||||
for item in result:
|
||||
times.append({'time': str(item['started']) + 'B', 'count': 1})
|
||||
times.append({'time': str(item['stopped']) + 'A', 'count': -1})
|
||||
times = sorted(times, key=lambda k: k['time'])
|
||||
|
||||
count = 0
|
||||
last_count = 0
|
||||
last_start = 0
|
||||
most_concurrent = []
|
||||
|
||||
for d in times:
|
||||
if d['count'] == 1:
|
||||
count += d['count']
|
||||
if count >= last_count:
|
||||
last_start = d['time']
|
||||
else:
|
||||
if count >= last_count:
|
||||
last_count = count
|
||||
most_concurrent = [{'count': count,
|
||||
'started': last_start[:-1],
|
||||
'stopped': d['time'][:-1]}]
|
||||
count += d['count']
|
||||
|
||||
home_stats.append({'stat_id': stat,
|
||||
'rows': most_concurrent})
|
||||
|
||||
return home_stats
|
||||
|
||||
def get_stream_details(self, row_id=None):
|
||||
@@ -1052,6 +1064,29 @@ class DataFactory(object):
|
||||
ip_address = 'N/A'
|
||||
|
||||
for item in result:
|
||||
ip_address = item[0]
|
||||
ip_address = item['ip_address']
|
||||
|
||||
return ip_address
|
||||
|
||||
def get_total_duration(self, custom_where=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
# Split up custom wheres
|
||||
if custom_where:
|
||||
where = 'WHERE ' + ' AND '.join([w[0] + ' = "' + w[1] + '"' for w in custom_where])
|
||||
else:
|
||||
where = ''
|
||||
|
||||
try:
|
||||
query = 'SELECT SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - ' \
|
||||
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS total_duration ' \
|
||||
'FROM session_history %s ' % where
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query for get_total_duration.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
total_duration = item['total_duration']
|
||||
|
||||
return total_duration
|
||||
|
132
plexpy/graphs.py
132
plexpy/graphs.py
@@ -44,11 +44,11 @@ class Graphs(object):
|
||||
else:
|
||||
query = 'SELECT date(started, "unixepoch", "localtime") as date_played, ' \
|
||||
'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_count, ' \
|
||||
'SUM(case when media_type = "movie" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_count, ' \
|
||||
'SUM(case when media_type = "track" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_duration ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_count ' \
|
||||
'FROM session_history ' \
|
||||
'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") ' \
|
||||
'GROUP BY date_played ' \
|
||||
@@ -77,9 +77,9 @@ class Graphs(object):
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if date_string == item['date_played']:
|
||||
series_1_value = item['tv_duration']
|
||||
series_2_value = item['movie_duration']
|
||||
series_3_value = item['music_duration']
|
||||
series_1_value = item['tv_count']
|
||||
series_2_value = item['movie_count']
|
||||
series_3_value = item['music_count']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -138,11 +138,11 @@ class Graphs(object):
|
||||
'when 5 then "Friday" ' \
|
||||
'else "Saturday" end as dayofweek, ' \
|
||||
'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_count, ' \
|
||||
'SUM(case when media_type = "movie" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_count, ' \
|
||||
'SUM(case when media_type = "track" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_duration ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_count ' \
|
||||
'FROM session_history ' \
|
||||
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
||||
@@ -166,9 +166,9 @@ class Graphs(object):
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if day_item == item['dayofweek']:
|
||||
series_1_value = item['tv_duration']
|
||||
series_2_value = item['movie_duration']
|
||||
series_3_value = item['music_duration']
|
||||
series_1_value = item['tv_count']
|
||||
series_2_value = item['movie_count']
|
||||
series_3_value = item['music_count']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -211,11 +211,11 @@ class Graphs(object):
|
||||
else:
|
||||
query = 'select strftime("%H", datetime(started, "unixepoch", "localtime")) as hourofday, ' \
|
||||
'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_count, ' \
|
||||
'SUM(case when media_type = "movie" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_count, ' \
|
||||
'SUM(case when media_type = "track" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_duration ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_count ' \
|
||||
'FROM session_history ' \
|
||||
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
||||
@@ -241,9 +241,9 @@ class Graphs(object):
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if hour_item == item['hourofday']:
|
||||
series_1_value = item['tv_duration']
|
||||
series_2_value = item['movie_duration']
|
||||
series_3_value = item['music_duration']
|
||||
series_1_value = item['tv_count']
|
||||
series_2_value = item['movie_count']
|
||||
series_3_value = item['music_count']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -283,11 +283,11 @@ class Graphs(object):
|
||||
else:
|
||||
query = 'SELECT strftime("%Y-%m", datetime(started, "unixepoch", "localtime")) as datestring, ' \
|
||||
'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_count, ' \
|
||||
'SUM(case when media_type = "movie" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_count, ' \
|
||||
'SUM(case when media_type = "track" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_duration ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_count ' \
|
||||
'FROM session_history ' \
|
||||
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-12 months", "localtime") ' \
|
||||
'GROUP BY strftime("%Y-%m", datetime(started, "unixepoch", "localtime")) ' \
|
||||
@@ -317,9 +317,9 @@ class Graphs(object):
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if date_string == item['datestring']:
|
||||
series_1_value = item['tv_duration']
|
||||
series_2_value = item['movie_duration']
|
||||
series_3_value = item['music_duration']
|
||||
series_1_value = item['tv_count']
|
||||
series_2_value = item['movie_count']
|
||||
series_3_value = item['music_count']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -364,11 +364,11 @@ class Graphs(object):
|
||||
else:
|
||||
query = 'SELECT platform, ' \
|
||||
'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_count, ' \
|
||||
'SUM(case when media_type = "movie" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_count, ' \
|
||||
'SUM(case when media_type = "track" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_count, ' \
|
||||
'SUM(case when stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as total_duration ' \
|
||||
'FROM session_history ' \
|
||||
@@ -387,9 +387,9 @@ class Graphs(object):
|
||||
|
||||
for item in result:
|
||||
categories.append(common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']))
|
||||
series_1.append(item['tv_duration'])
|
||||
series_2.append(item['movie_duration'])
|
||||
series_3.append(item['music_duration'])
|
||||
series_1.append(item['tv_count'])
|
||||
series_2.append(item['movie_count'])
|
||||
series_3.append(item['music_count'])
|
||||
|
||||
series_1_output = {'name': 'TV',
|
||||
'data': series_1}
|
||||
@@ -430,11 +430,11 @@ class Graphs(object):
|
||||
'(case when users.friendly_name is null then users.username else ' \
|
||||
'users.friendly_name end) as friendly_name,' \
|
||||
'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_count, ' \
|
||||
'SUM(case when media_type = "movie" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as movie_count, ' \
|
||||
'SUM(case when media_type = "track" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as music_count, ' \
|
||||
'SUM(case when stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as total_duration ' \
|
||||
'FROM session_history ' \
|
||||
@@ -454,9 +454,9 @@ class Graphs(object):
|
||||
|
||||
for item in result:
|
||||
categories.append(item['friendly_name'])
|
||||
series_1.append(item['tv_duration'])
|
||||
series_2.append(item['movie_duration'])
|
||||
series_3.append(item['music_duration'])
|
||||
series_1.append(item['tv_count'])
|
||||
series_2.append(item['movie_count'])
|
||||
series_3.append(item['music_count'])
|
||||
|
||||
series_1_output = {'name': 'TV',
|
||||
'data': series_1}
|
||||
@@ -501,15 +501,15 @@ class Graphs(object):
|
||||
'SUM(case when (session_history_media_info.video_decision = "direct play" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "direct play")) ' \
|
||||
'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_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as dp_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "copy" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "copy")) ' \
|
||||
'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 ds_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as ds_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "transcode" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "transcode")) ' \
|
||||
'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 tc_duration ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tc_count ' \
|
||||
'FROM session_history ' \
|
||||
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
|
||||
@@ -541,9 +541,9 @@ class Graphs(object):
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if date_string == item['date_played']:
|
||||
series_1_value = item['dp_duration']
|
||||
series_2_value = item['ds_duration']
|
||||
series_3_value = item['tc_duration']
|
||||
series_1_value = item['dp_count']
|
||||
series_2_value = item['ds_count']
|
||||
series_3_value = item['tc_count']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -598,15 +598,15 @@ class Graphs(object):
|
||||
'SUM(case when (session_history_media_info.video_decision = "direct play" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "direct play")) ' \
|
||||
'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_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as dp_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "copy" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "copy")) ' \
|
||||
'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 ds_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as ds_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "transcode" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "transcode")) ' \
|
||||
'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 tc_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tc_count, ' \
|
||||
'SUM(case when stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as total_duration ' \
|
||||
'FROM session_history ' \
|
||||
@@ -627,9 +627,9 @@ class Graphs(object):
|
||||
|
||||
for item in result:
|
||||
categories.append(item['resolution'])
|
||||
series_1.append(item['dp_duration'])
|
||||
series_2.append(item['ds_duration'])
|
||||
series_3.append(item['tc_duration'])
|
||||
series_1.append(item['dp_count'])
|
||||
series_2.append(item['ds_count'])
|
||||
series_3.append(item['tc_count'])
|
||||
|
||||
series_1_output = {'name': 'Direct Play',
|
||||
'data': series_1}
|
||||
@@ -695,15 +695,15 @@ class Graphs(object):
|
||||
'SUM(case when (session_history_media_info.video_decision = "direct play" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "direct play")) ' \
|
||||
'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_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as dp_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "copy" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "copy")) ' \
|
||||
'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 ds_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as ds_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "transcode" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "transcode")) ' \
|
||||
'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 tc_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tc_count, ' \
|
||||
'SUM(case when stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as total_duration ' \
|
||||
'FROM session_history ' \
|
||||
@@ -724,9 +724,9 @@ class Graphs(object):
|
||||
|
||||
for item in result:
|
||||
categories.append(item['resolution'])
|
||||
series_1.append(item['dp_duration'])
|
||||
series_2.append(item['ds_duration'])
|
||||
series_3.append(item['tc_duration'])
|
||||
series_1.append(item['dp_count'])
|
||||
series_2.append(item['ds_count'])
|
||||
series_3.append(item['tc_count'])
|
||||
|
||||
series_1_output = {'name': 'Direct Play',
|
||||
'data': series_1}
|
||||
@@ -773,15 +773,15 @@ class Graphs(object):
|
||||
'SUM(case when (session_history_media_info.video_decision = "direct play" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "direct play")) ' \
|
||||
'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_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as dp_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "copy" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "copy")) ' \
|
||||
'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 ds_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as ds_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "transcode" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "transcode")) ' \
|
||||
'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 tc_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tc_count, ' \
|
||||
'SUM(case when 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 total_duration ' \
|
||||
@@ -802,9 +802,9 @@ class Graphs(object):
|
||||
|
||||
for item in result:
|
||||
categories.append(common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']))
|
||||
series_1.append(item['dp_duration'])
|
||||
series_2.append(item['ds_duration'])
|
||||
series_3.append(item['tc_duration'])
|
||||
series_1.append(item['dp_count'])
|
||||
series_2.append(item['ds_count'])
|
||||
series_3.append(item['tc_count'])
|
||||
|
||||
series_1_output = {'name': 'Direct Play',
|
||||
'data': series_1}
|
||||
@@ -853,15 +853,15 @@ class Graphs(object):
|
||||
'SUM(case when (session_history_media_info.video_decision = "direct play" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "direct play")) ' \
|
||||
'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_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as dp_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "copy" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "copy")) ' \
|
||||
'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 ds_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as ds_count, ' \
|
||||
'SUM(case when (session_history_media_info.video_decision = "transcode" ' \
|
||||
'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "transcode")) ' \
|
||||
'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 tc_duration, ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tc_count, ' \
|
||||
'SUM(case when 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 total_duration ' \
|
||||
@@ -883,9 +883,9 @@ class Graphs(object):
|
||||
|
||||
for item in result:
|
||||
categories.append(item['username'])
|
||||
series_1.append(item['dp_duration'])
|
||||
series_2.append(item['ds_duration'])
|
||||
series_3.append(item['tc_duration'])
|
||||
series_1.append(item['dp_count'])
|
||||
series_2.append(item['ds_count'])
|
||||
series_3.append(item['tc_count'])
|
||||
|
||||
series_1_output = {'name': 'Direct Play',
|
||||
'data': series_1}
|
||||
|
@@ -146,7 +146,7 @@ def now():
|
||||
now = datetime.datetime.now()
|
||||
return now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def human_duration(s):
|
||||
def human_duration(s, sig='dhms'):
|
||||
|
||||
hd = ''
|
||||
|
||||
@@ -157,20 +157,24 @@ def human_duration(s):
|
||||
s = int(((s % 84600) % 3600) % 60)
|
||||
|
||||
hd_list = []
|
||||
if d > 0:
|
||||
if sig >= 'd' and d > 0:
|
||||
d = d + 1 if sig == 'd' and h >= 12 else d
|
||||
hd_list.append(str(d) + ' days')
|
||||
if h > 0:
|
||||
|
||||
if sig >= 'dh' and h > 0:
|
||||
h = h + 1 if sig == 'dh' and m >= 30 else h
|
||||
hd_list.append(str(h) + ' hrs')
|
||||
if m > 0:
|
||||
|
||||
if sig >= 'dhm' and m > 0:
|
||||
m = m + 1 if sig == 'dhm' and s >= 30 else m
|
||||
hd_list.append(str(m) + ' mins')
|
||||
if s > 0:
|
||||
|
||||
if sig >= 'dhms' and s > 0:
|
||||
hd_list.append(str(s) + ' secs')
|
||||
|
||||
hd = ' '.join(hd_list)
|
||||
|
||||
return hd
|
||||
else:
|
||||
return hd
|
||||
return hd
|
||||
|
||||
def get_age(date):
|
||||
|
||||
|
@@ -199,6 +199,18 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
if agent['on_extup'] and notify_action == 'extup':
|
||||
# Build and send notification
|
||||
notify_strings = build_server_notify_text(state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
if agent['on_intup'] and notify_action == 'intup':
|
||||
# Build and send notification
|
||||
notify_strings = build_server_notify_text(state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
else:
|
||||
logger.debug(u"PlexPy Notifier :: Notify timeline called but incomplete data received.")
|
||||
|
||||
@@ -214,13 +226,13 @@ def get_notify_state(session):
|
||||
args=[session['session_key'], session['rating_key'], session['user']])
|
||||
notify_states = []
|
||||
for item in result:
|
||||
notify_state = {'on_play': item[0],
|
||||
'on_stop': item[1],
|
||||
'on_pause': item[2],
|
||||
'on_resume': item[3],
|
||||
'on_buffer': item[4],
|
||||
'on_watched': item[5],
|
||||
'agent_id': item[6]}
|
||||
notify_state = {'on_play': item['on_play'],
|
||||
'on_stop': item['on_stop'],
|
||||
'on_pause': item['on_pause'],
|
||||
'on_resume': item['on_resume'],
|
||||
'on_buffer': item['on_buffer'],
|
||||
'on_watched': item['on_watched'],
|
||||
'agent_id': item['agent_id']}
|
||||
notify_states.append(notify_state)
|
||||
|
||||
return notify_states
|
||||
@@ -234,8 +246,8 @@ def get_notify_state_timeline(timeline):
|
||||
args=[timeline['rating_key']])
|
||||
notify_states = []
|
||||
for item in result:
|
||||
notify_state = {'on_created': item[0],
|
||||
'agent_id': item[1]}
|
||||
notify_state = {'on_created': item['on_created'],
|
||||
'agent_id': item['agent_id']}
|
||||
notify_states.append(notify_state)
|
||||
|
||||
return notify_states
|
||||
@@ -315,13 +327,13 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
# Check for exclusion tags
|
||||
if metadata['media_type'] == 'movie':
|
||||
# Regex pattern to remove the text in the tags we don't want
|
||||
pattern = re.compile('<tv>[^>]+.</tv>|<music>[^>]+.</music>', re.IGNORECASE|re.DOTALL)
|
||||
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<music>[^>]+.</music>\n*', re.IGNORECASE|re.DOTALL)
|
||||
elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode':
|
||||
# Regex pattern to remove the text in the tags we don't want
|
||||
pattern = re.compile('<movie>[^>]+.</movie>|<music>[^>]+.</music>', re.IGNORECASE|re.DOTALL)
|
||||
pattern = re.compile('\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE|re.DOTALL)
|
||||
elif metadata['media_type'] == 'artist' or metadata['media_type'] == 'track':
|
||||
# Regex pattern to remove the text in the tags we don't want
|
||||
pattern = re.compile('<tv>[^>]+.</tv>|<movie>[^>]+.</movie>', re.IGNORECASE|re.DOTALL)
|
||||
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*', re.IGNORECASE|re.DOTALL)
|
||||
else:
|
||||
pattern = None
|
||||
|
||||
@@ -370,15 +382,31 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
duration = helpers.convert_milliseconds_to_minutes(metadata['duration'])
|
||||
|
||||
# Default values
|
||||
video_decision = ''
|
||||
audio_decision = ''
|
||||
transcode_decision = ''
|
||||
stream_duration = 0
|
||||
view_offset = 0
|
||||
user = ''
|
||||
platform = ''
|
||||
player = ''
|
||||
ip_address = 'N/A'
|
||||
stream_duration = 0
|
||||
view_offset = 0
|
||||
container = ''
|
||||
video_codec = ''
|
||||
video_bitrate = ''
|
||||
video_width = ''
|
||||
video_height = ''
|
||||
video_resolution = ''
|
||||
video_framerate = ''
|
||||
aspect_ratio = ''
|
||||
audio_codec = ''
|
||||
audio_channels = ''
|
||||
transcode_decision = ''
|
||||
video_decision = ''
|
||||
audio_decision = ''
|
||||
transcode_container = ''
|
||||
transcode_video_codec = ''
|
||||
transcode_video_width = ''
|
||||
transcode_video_height = ''
|
||||
transcode_audio_codec = ''
|
||||
transcode_audio_channels = ''
|
||||
|
||||
# Session values
|
||||
if session:
|
||||
@@ -405,9 +433,39 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
platform = session['platform']
|
||||
player = session['player']
|
||||
ip_address = session['ip_address'] if session['ip_address'] else 'N/A'
|
||||
container = session['container']
|
||||
video_codec = session['video_codec']
|
||||
video_bitrate = session['bitrate']
|
||||
video_width = session['width']
|
||||
video_height = session['height']
|
||||
video_resolution = session['video_resolution']
|
||||
video_framerate = session['video_framerate']
|
||||
aspect_ratio = session['aspect_ratio']
|
||||
audio_codec = session['audio_codec']
|
||||
audio_channels = session['audio_channels']
|
||||
transcode_container = session['transcode_container']
|
||||
transcode_video_codec = session['transcode_video_codec']
|
||||
transcode_video_width = session['transcode_width']
|
||||
transcode_video_height = session['transcode_height']
|
||||
transcode_audio_codec = session['transcode_audio_codec']
|
||||
transcode_audio_channels = session['transcode_audio_channels']
|
||||
|
||||
progress_percent = helpers.get_percent(view_offset, duration)
|
||||
|
||||
# Fix metadata params for notify recently added grandparent
|
||||
if plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
|
||||
show_name = metadata['title']
|
||||
episode_name = ''
|
||||
artist_name = metadata['title']
|
||||
album_name = ''
|
||||
track_name = ''
|
||||
else:
|
||||
show_name = metadata['grandparent_title']
|
||||
episode_name = metadata['title']
|
||||
artist_name = metadata['grandparent_title']
|
||||
album_name = metadata['parent_title']
|
||||
track_name = metadata['title']
|
||||
|
||||
available_params = {'server_name': server_name,
|
||||
'server_uptime': server_uptime,
|
||||
'user': user,
|
||||
@@ -415,19 +473,39 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
'player': player,
|
||||
'ip_address': ip_address,
|
||||
'media_type': metadata['media_type'],
|
||||
'stream_duration': stream_duration,
|
||||
'remaining_duration': duration - view_offset,
|
||||
'progress': view_offset,
|
||||
'progress_percent': progress_percent,
|
||||
'container': container,
|
||||
'video_codec': video_codec,
|
||||
'video_bitrate': video_bitrate,
|
||||
'video_width': video_width,
|
||||
'video_height': video_height,
|
||||
'video_resolution': video_resolution,
|
||||
'video_framerate': video_framerate,
|
||||
'aspect_ratio': aspect_ratio,
|
||||
'audio_codec': audio_codec,
|
||||
'audio_channels': audio_channels,
|
||||
'transcode_decision': transcode_decision,
|
||||
'video_decision': video_decision,
|
||||
'audio_decision': audio_decision,
|
||||
'transcode_container': transcode_container,
|
||||
'transcode_video_codec': transcode_video_codec,
|
||||
'transcode_video_width': transcode_video_width,
|
||||
'transcode_video_height': transcode_video_height,
|
||||
'transcode_audio_codec': transcode_audio_codec,
|
||||
'transcode_audio_channels': transcode_audio_channels,
|
||||
'title': full_title,
|
||||
'show_name': metadata['grandparent_title'],
|
||||
'episode_name': metadata['title'],
|
||||
'artist_name': metadata['grandparent_title'],
|
||||
'album_name': metadata['parent_title'],
|
||||
'track_name': metadata['title'],
|
||||
'show_name': show_name,
|
||||
'episode_name': episode_name,
|
||||
'artist_name': artist_name,
|
||||
'album_name': album_name,
|
||||
'track_name': track_name,
|
||||
'season_num': metadata['parent_index'].zfill(1),
|
||||
'season_num00': metadata['parent_index'].zfill(2),
|
||||
'episode_num': metadata['index'].zfill(1),
|
||||
'episode_num00': metadata['index'].zfill(2),
|
||||
'video_decision': video_decision,
|
||||
'audio_decision': audio_decision,
|
||||
'transcode_decision': transcode_decision,
|
||||
'year': metadata['year'],
|
||||
'studio': metadata['studio'],
|
||||
'content_rating': metadata['content_rating'],
|
||||
@@ -438,11 +516,7 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
'summary': metadata['summary'],
|
||||
'tagline': metadata['tagline'],
|
||||
'rating': metadata['rating'],
|
||||
'duration': duration,
|
||||
'stream_duration': stream_duration,
|
||||
'remaining_duration': duration - view_offset,
|
||||
'progress': view_offset,
|
||||
'progress_percent': progress_percent
|
||||
'duration': duration
|
||||
}
|
||||
|
||||
# Default subject text
|
||||
@@ -636,6 +710,10 @@ def build_server_notify_text(state=None):
|
||||
on_extdown_body = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT
|
||||
on_intdown_subject = plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT
|
||||
on_intdown_body = plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT
|
||||
on_extup_subject = plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT
|
||||
on_extup_body = plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT
|
||||
on_intup_subject = plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT
|
||||
on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT
|
||||
|
||||
available_params = {'server_name': server_name,
|
||||
'server_uptime': server_uptime}
|
||||
@@ -684,6 +762,50 @@ def build_server_notify_text(state=None):
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.")
|
||||
|
||||
return [subject_text, body_text]
|
||||
else:
|
||||
return [subject_text, body_text]
|
||||
if state == 'extup':
|
||||
# Default body text
|
||||
body_text = 'The Plex Media Server remote access is back up.'
|
||||
|
||||
if on_extup_subject and on_extup_body:
|
||||
try:
|
||||
subject_text = unicode(on_extup_subject).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.")
|
||||
|
||||
try:
|
||||
body_text = unicode(on_extup_body).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.")
|
||||
|
||||
return [subject_text, body_text]
|
||||
else:
|
||||
return [subject_text, body_text]
|
||||
elif state == 'intup':
|
||||
# Default body text
|
||||
body_text = 'The Plex Media Server is back up.'
|
||||
|
||||
if on_intup_subject and on_intup_body:
|
||||
try:
|
||||
subject_text = unicode(on_intup_subject).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.")
|
||||
|
||||
try:
|
||||
body_text = unicode(on_intup_body).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.")
|
||||
|
||||
return [subject_text, body_text]
|
||||
else:
|
||||
return [subject_text, body_text]
|
||||
|
@@ -68,7 +68,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.GROWL_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.GROWL_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.GROWL_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.GROWL_ON_INTUP
|
||||
},
|
||||
{'name': 'Prowl',
|
||||
'id': AGENT_IDS['Prowl'],
|
||||
@@ -83,7 +85,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.PROWL_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PROWL_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.PROWL_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.PROWL_ON_INTUP
|
||||
},
|
||||
{'name': 'XBMC',
|
||||
'id': AGENT_IDS['XBMC'],
|
||||
@@ -98,7 +102,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.XBMC_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.XBMC_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.XBMC_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.XBMC_ON_INTUP
|
||||
},
|
||||
{'name': 'Plex',
|
||||
'id': AGENT_IDS['Plex'],
|
||||
@@ -113,7 +119,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PLEX_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.PLEX_ON_INTUP
|
||||
},
|
||||
{'name': 'NotifyMyAndroid',
|
||||
'id': AGENT_IDS['NMA'],
|
||||
@@ -128,7 +136,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.NMA_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.NMA_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.NMA_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.NMA_ON_INTUP
|
||||
},
|
||||
{'name': 'Pushalot',
|
||||
'id': AGENT_IDS['Pushalot'],
|
||||
@@ -143,7 +153,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.PUSHALOT_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PUSHALOT_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.PUSHALOT_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP
|
||||
},
|
||||
{'name': 'Pushbullet',
|
||||
'id': AGENT_IDS['Pushbullet'],
|
||||
@@ -158,7 +170,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.PUSHBULLET_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PUSHBULLET_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.PUSHBULLET_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP
|
||||
},
|
||||
{'name': 'Pushover',
|
||||
'id': AGENT_IDS['Pushover'],
|
||||
@@ -173,7 +187,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.PUSHOVER_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PUSHOVER_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.PUSHOVER_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP
|
||||
},
|
||||
{'name': 'Boxcar2',
|
||||
'id': AGENT_IDS['Boxcar2'],
|
||||
@@ -188,7 +204,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.BOXCAR_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.BOXCAR_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.BOXCAR_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP
|
||||
},
|
||||
{'name': 'E-mail',
|
||||
'id': AGENT_IDS['Email'],
|
||||
@@ -203,7 +221,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.EMAIL_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.EMAIL_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.EMAIL_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP
|
||||
},
|
||||
{'name': 'Twitter',
|
||||
'id': AGENT_IDS['Twitter'],
|
||||
@@ -218,7 +238,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.TWITTER_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.TWITTER_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.TWITTER_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP
|
||||
},
|
||||
{'name': 'IFTTT',
|
||||
'id': AGENT_IDS['IFTTT'],
|
||||
@@ -233,7 +255,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.IFTTT_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.IFTTT_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.IFTTT_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP
|
||||
},
|
||||
{'name': 'Telegram',
|
||||
'id': AGENT_IDS['Telegram'],
|
||||
@@ -248,7 +272,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.TELEGRAM_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.TELEGRAM_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.TELEGRAM_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP
|
||||
}
|
||||
]
|
||||
|
||||
@@ -268,7 +294,9 @@ def available_notification_agents():
|
||||
'on_watched': plexpy.CONFIG.OSX_NOTIFY_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.OSX_NOTIFY_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.OSX_NOTIFY_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN
|
||||
'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN,
|
||||
'on_extup': plexpy.CONFIG.OSX_NOTIFY_ON_EXTUP,
|
||||
'on_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP
|
||||
})
|
||||
|
||||
return agents
|
||||
@@ -325,7 +353,7 @@ def get_notification_agent_config(config_id):
|
||||
return []
|
||||
|
||||
def send_notification(config_id, subject, body):
|
||||
if config_id:
|
||||
if str(config_id).isdigit():
|
||||
config_id = int(config_id)
|
||||
|
||||
if config_id == 0:
|
||||
@@ -385,9 +413,6 @@ class GROWL(object):
|
||||
self.enabled = plexpy.CONFIG.GROWL_ENABLED
|
||||
self.host = plexpy.CONFIG.GROWL_HOST
|
||||
self.password = plexpy.CONFIG.GROWL_PASSWORD
|
||||
self.on_play = plexpy.CONFIG.GROWL_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.GROWL_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.GROWL_ON_WATCHED
|
||||
|
||||
def conf(self, options):
|
||||
return cherrypy.config['config'].get('Growl', options)
|
||||
@@ -435,7 +460,7 @@ class GROWL(object):
|
||||
|
||||
# Send it, including an image
|
||||
image_file = os.path.join(str(plexpy.PROG_DIR),
|
||||
"data/images/plexpylogo.png")
|
||||
"data/interfaces/default/images/favicon.png")
|
||||
|
||||
with open(image_file, 'rb') as f:
|
||||
image = f.read()
|
||||
@@ -490,9 +515,6 @@ class PROWL(object):
|
||||
self.enabled = plexpy.CONFIG.PROWL_ENABLED
|
||||
self.keys = plexpy.CONFIG.PROWL_KEYS
|
||||
self.priority = plexpy.CONFIG.PROWL_PRIORITY
|
||||
self.on_play = plexpy.CONFIG.PROWL_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.PROWL_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.PROWL_ON_WATCHED
|
||||
|
||||
def conf(self, options):
|
||||
return cherrypy.config['config'].get('Prowl', options)
|
||||
@@ -565,9 +587,6 @@ class XBMC(object):
|
||||
self.hosts = plexpy.CONFIG.XBMC_HOST
|
||||
self.username = plexpy.CONFIG.XBMC_USERNAME
|
||||
self.password = plexpy.CONFIG.XBMC_PASSWORD
|
||||
self.on_play = plexpy.CONFIG.XBMC_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.XBMC_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.XBMC_ON_WATCHED
|
||||
|
||||
def _sendhttp(self, host, command):
|
||||
url_command = urllib.urlencode(command)
|
||||
@@ -648,9 +667,6 @@ class Plex(object):
|
||||
self.client_hosts = plexpy.CONFIG.PLEX_CLIENT_HOST
|
||||
self.username = plexpy.CONFIG.PLEX_USERNAME
|
||||
self.password = plexpy.CONFIG.PLEX_PASSWORD
|
||||
self.on_play = plexpy.CONFIG.PLEX_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.PLEX_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.PLEX_ON_WATCHED
|
||||
|
||||
def _sendhttp(self, host, command):
|
||||
|
||||
@@ -728,9 +744,6 @@ class NMA(object):
|
||||
def __init__(self):
|
||||
self.api = plexpy.CONFIG.NMA_APIKEY
|
||||
self.nma_priority = plexpy.CONFIG.NMA_PRIORITY
|
||||
self.on_play = plexpy.CONFIG.NMA_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.NMA_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.NMA_ON_WATCHED
|
||||
|
||||
def notify(self, subject=None, message=None):
|
||||
if not subject or not message:
|
||||
@@ -790,9 +803,6 @@ class PUSHBULLET(object):
|
||||
self.apikey = plexpy.CONFIG.PUSHBULLET_APIKEY
|
||||
self.deviceid = plexpy.CONFIG.PUSHBULLET_DEVICEID
|
||||
self.channel_tag = plexpy.CONFIG.PUSHBULLET_CHANNEL_TAG
|
||||
self.on_play = plexpy.CONFIG.PUSHBULLET_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.PUSHBULLET_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.PUSHBULLET_ON_WATCHED
|
||||
|
||||
def conf(self, options):
|
||||
return cherrypy.config['config'].get('PUSHBULLET', options)
|
||||
@@ -842,6 +852,31 @@ class PUSHBULLET(object):
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
def get_devices(self):
|
||||
if plexpy.CONFIG.PUSHBULLET_APIKEY:
|
||||
http_handler = HTTPSConnection("api.pushbullet.com")
|
||||
http_handler.request("GET", "/v2/devices",
|
||||
headers={'Content-type': "application/json",
|
||||
'Authorization': 'Basic %s' % base64.b64encode(plexpy.CONFIG.PUSHBULLET_APIKEY + ":")})
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
|
||||
if request_status == 200:
|
||||
data = json.loads(response.read())
|
||||
devices = data.get('devices', [])
|
||||
devices = {d['iden']: d['nickname'] for d in devices if d['active']}
|
||||
devices.update({'': ''})
|
||||
return devices
|
||||
elif request_status >= 400 and request_status < 500:
|
||||
logger.info(u"Unable to retrieve Pushbullet devices list: %s" % response.reason)
|
||||
return {'': ''}
|
||||
else:
|
||||
logger.info(u"Unable to retrieve Pushbullet devices list.")
|
||||
return {'': ''}
|
||||
|
||||
else:
|
||||
return {'': ''}
|
||||
|
||||
def return_config_options(self):
|
||||
config_option = [{'label': 'Pushbullet API Key',
|
||||
'value': self.apikey,
|
||||
@@ -849,11 +884,13 @@ class PUSHBULLET(object):
|
||||
'description': 'Your Pushbullet API key.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Device ID',
|
||||
{'label': 'Device',
|
||||
'value': self.deviceid,
|
||||
'name': 'pushbullet_deviceid',
|
||||
'description': 'A device ID (optional). If set, will override channel tag.',
|
||||
'input_type': 'text'
|
||||
'description': 'Set your Pushbullet device. If set, will override channel tag. ' \
|
||||
'Leave blank to notify on all devices.',
|
||||
'input_type': 'select',
|
||||
'select_options': self.get_devices()
|
||||
},
|
||||
{'label': 'Channel',
|
||||
'value': self.channel_tag,
|
||||
@@ -869,9 +906,6 @@ class PUSHALOT(object):
|
||||
|
||||
def __init__(self):
|
||||
self.api_key = plexpy.CONFIG.PUSHALOT_APIKEY
|
||||
self.on_play = plexpy.CONFIG.PUSHALOT_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.PUSHALOT_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.PUSHALOT_ON_WATCHED
|
||||
|
||||
def notify(self, message, event):
|
||||
if not message or not event:
|
||||
@@ -928,9 +962,6 @@ class PUSHOVER(object):
|
||||
self.keys = plexpy.CONFIG.PUSHOVER_KEYS
|
||||
self.priority = plexpy.CONFIG.PUSHOVER_PRIORITY
|
||||
self.sound = plexpy.CONFIG.PUSHOVER_SOUND
|
||||
self.on_play = plexpy.CONFIG.PUSHOVER_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.PUSHOVER_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.PUSHOVER_ON_WATCHED
|
||||
|
||||
if plexpy.CONFIG.PUSHOVER_APITOKEN:
|
||||
self.application_token = plexpy.CONFIG.PUSHOVER_APITOKEN
|
||||
@@ -1158,9 +1189,6 @@ class TwitterNotifier(object):
|
||||
class OSX_NOTIFY(object):
|
||||
|
||||
def __init__(self):
|
||||
self.on_play = plexpy.CONFIG.OSX_NOTIFY_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.OSX_NOTIFY_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.OSX_NOTIFY_ON_WATCHED
|
||||
try:
|
||||
self.objc = __import__("objc")
|
||||
self.AppKit = __import__("AppKit")
|
||||
@@ -1247,9 +1275,6 @@ class BOXCAR(object):
|
||||
self.url = 'https://new.boxcar.io/api/notifications'
|
||||
self.token = plexpy.CONFIG.BOXCAR_TOKEN
|
||||
self.sound = plexpy.CONFIG.BOXCAR_SOUND
|
||||
self.on_play = plexpy.CONFIG.BOXCAR_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.BOXCAR_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.BOXCAR_ON_WATCHED
|
||||
|
||||
def notify(self, title, message):
|
||||
if not title or not message:
|
||||
@@ -1266,6 +1291,7 @@ class BOXCAR(object):
|
||||
req = urllib2.Request(self.url)
|
||||
handle = urllib2.urlopen(req, data)
|
||||
handle.close()
|
||||
logger.info(u"Boxcar2 notifications sent.")
|
||||
return True
|
||||
|
||||
except urllib2.URLError as e:
|
||||
@@ -1322,9 +1348,7 @@ class BOXCAR(object):
|
||||
class Email(object):
|
||||
|
||||
def __init__(self):
|
||||
self.on_play = plexpy.CONFIG.EMAIL_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.EMAIL_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.EMAIL_ON_WATCHED
|
||||
pass
|
||||
|
||||
def notify(self, subject, message):
|
||||
if not subject or not message:
|
||||
@@ -1334,6 +1358,12 @@ class Email(object):
|
||||
message['Subject'] = subject
|
||||
message['From'] = email.utils.formataddr(('PlexPy', plexpy.CONFIG.EMAIL_FROM))
|
||||
message['To'] = plexpy.CONFIG.EMAIL_TO
|
||||
message['CC'] = plexpy.CONFIG.EMAIL_CC
|
||||
|
||||
recipients = [x.strip() for x in plexpy.CONFIG.EMAIL_TO.split(';')] \
|
||||
+ [x.strip() for x in plexpy.CONFIG.EMAIL_CC.split(';')] \
|
||||
+ [x.strip() for x in plexpy.CONFIG.EMAIL_BCC.split(';')]
|
||||
recipients = filter(None, recipients)
|
||||
|
||||
try:
|
||||
mailserver = smtplib.SMTP(plexpy.CONFIG.EMAIL_SMTP_SERVER, plexpy.CONFIG.EMAIL_SMTP_PORT)
|
||||
@@ -1346,7 +1376,7 @@ class Email(object):
|
||||
if plexpy.CONFIG.EMAIL_SMTP_USER:
|
||||
mailserver.login(plexpy.CONFIG.EMAIL_SMTP_USER, plexpy.CONFIG.EMAIL_SMTP_PASSWORD)
|
||||
|
||||
mailserver.sendmail(plexpy.CONFIG.EMAIL_FROM, plexpy.CONFIG.EMAIL_TO, message.as_string())
|
||||
mailserver.sendmail(plexpy.CONFIG.EMAIL_FROM, recipients, message.as_string())
|
||||
mailserver.quit()
|
||||
|
||||
logger.info(u"Email notifications sent.")
|
||||
@@ -1360,13 +1390,25 @@ class Email(object):
|
||||
config_option = [{'label': 'From',
|
||||
'value': plexpy.CONFIG.EMAIL_FROM,
|
||||
'name': 'email_from',
|
||||
'description': 'Who should the sender be.',
|
||||
'description': 'The email address of the sender.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'To',
|
||||
'value': plexpy.CONFIG.EMAIL_TO,
|
||||
'name': 'email_to',
|
||||
'description': 'Who should the recipient be.',
|
||||
'description': 'The email address(es) of the recipients, separated by semicolons (;).',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'CC',
|
||||
'value': plexpy.CONFIG.EMAIL_CC,
|
||||
'name': 'email_cc',
|
||||
'description': 'The email address(es) to CC, separated by semicolons (;).',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'BCC',
|
||||
'value': plexpy.CONFIG.EMAIL_BCC,
|
||||
'name': 'email_bcc',
|
||||
'description': 'The email address(es) to BCC, separated by semicolons (;).',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'SMTP Server',
|
||||
|
@@ -342,13 +342,13 @@ class PlexTV(object):
|
||||
|
||||
rating_key = clean_uri.rpartition('%2F')[-1]
|
||||
|
||||
sync_details = {"device_name": device_name,
|
||||
"platform": device_platform,
|
||||
"username": device_username,
|
||||
"friendly_name": device_friendly_name,
|
||||
sync_details = {"device_name": helpers.sanitize(device_name),
|
||||
"platform": helpers.sanitize(device_platform),
|
||||
"username": helpers.sanitize(device_username),
|
||||
"friendly_name": helpers.sanitize(device_friendly_name),
|
||||
"user_id": device_user_id,
|
||||
"root_title": sync_root_title,
|
||||
"title": sync_title,
|
||||
"root_title": helpers.sanitize(sync_root_title),
|
||||
"title": helpers.sanitize(sync_title),
|
||||
"metadata_type": sync_metadata_type,
|
||||
"content_type": sync_content_type,
|
||||
"rating_key": rating_key,
|
||||
|
@@ -815,7 +815,7 @@ class PmsConnect(object):
|
||||
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
|
||||
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
|
||||
'throttled': throttled,
|
||||
'transcode_progress': transcode_progress,
|
||||
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
|
||||
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
|
||||
'audio_decision': audio_decision,
|
||||
'audio_channels': audio_channels,
|
||||
@@ -937,7 +937,7 @@ class PmsConnect(object):
|
||||
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
|
||||
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
|
||||
'throttled': throttled,
|
||||
'transcode_progress': transcode_progress,
|
||||
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
|
||||
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
|
||||
'audio_decision': audio_decision,
|
||||
'audio_channels': audio_channels,
|
||||
@@ -994,7 +994,7 @@ class PmsConnect(object):
|
||||
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
|
||||
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
|
||||
'throttled': throttled,
|
||||
'transcode_progress': transcode_progress,
|
||||
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
|
||||
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
|
||||
'audio_decision': audio_decision,
|
||||
'audio_channels': audio_channels,
|
||||
@@ -1051,7 +1051,7 @@ class PmsConnect(object):
|
||||
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
|
||||
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
|
||||
'throttled': throttled,
|
||||
'transcode_progress': transcode_progress,
|
||||
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
|
||||
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
|
||||
'audio_decision': audio_decision,
|
||||
'audio_channels': audio_channels,
|
||||
@@ -1141,7 +1141,7 @@ class PmsConnect(object):
|
||||
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
|
||||
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
|
||||
'throttled': throttled,
|
||||
'transcode_progress': transcode_progress,
|
||||
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
|
||||
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
|
||||
'audio_decision': '',
|
||||
'audio_channels': '',
|
||||
@@ -1700,4 +1700,4 @@ class PmsConnect(object):
|
||||
'public_port': helpers.get_xml_attr(a, 'publicPort')
|
||||
}
|
||||
|
||||
return server_response
|
||||
return server_response
|
||||
|
@@ -89,16 +89,13 @@ class Users(object):
|
||||
# Rename Mystery platform names
|
||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
||||
|
||||
# Sanitize player name
|
||||
player = helpers.sanitize(item["player"])
|
||||
|
||||
row = {"id": item['id'],
|
||||
"plays": item['plays'],
|
||||
"last_seen": item['last_seen'],
|
||||
"friendly_name": item['friendly_name'],
|
||||
"ip_address": item['ip_address'],
|
||||
"platform": platform,
|
||||
"player": player,
|
||||
"player": item["player"],
|
||||
"last_watched": item['last_watched'],
|
||||
"thumb": thumb,
|
||||
"media_type": item['media_type'],
|
||||
@@ -183,15 +180,12 @@ class Users(object):
|
||||
# Rename Mystery platform names
|
||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
||||
|
||||
# Sanitize player name
|
||||
player = helpers.sanitize(item["player"])
|
||||
|
||||
row = {"id": item['id'],
|
||||
"last_seen": item['last_seen'],
|
||||
"ip_address": item['ip_address'],
|
||||
"play_count": item['play_count'],
|
||||
"platform": platform,
|
||||
"player": player,
|
||||
"player": item['player'],
|
||||
"last_watched": item['last_watched'],
|
||||
"thumb": thumb,
|
||||
"media_type": item['media_type'],
|
||||
@@ -325,7 +319,7 @@ class Users(object):
|
||||
query = 'select user_id FROM users WHERE username = ?'
|
||||
result = monitor_db.select_single(query, args=[user])
|
||||
if result:
|
||||
return result
|
||||
return result['user_id']
|
||||
else:
|
||||
return None
|
||||
except:
|
||||
|
@@ -1,2 +1,2 @@
|
||||
PLEXPY_VERSION = "master"
|
||||
PLEXPY_RELEASE_VERSION = "1.2.9"
|
||||
PLEXPY_RELEASE_VERSION = "1.2.16"
|
||||
|
@@ -128,13 +128,15 @@ class WebInterface(object):
|
||||
def home_stats(self, **kwargs):
|
||||
data_factory = datafactory.DataFactory()
|
||||
|
||||
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
||||
time_range = plexpy.CONFIG.HOME_STATS_LENGTH
|
||||
stats_type = plexpy.CONFIG.HOME_STATS_TYPE
|
||||
stats_count = plexpy.CONFIG.HOME_STATS_COUNT
|
||||
stats_cards = plexpy.CONFIG.HOME_STATS_CARDS.split(', ')
|
||||
notify_watched_percent = plexpy.CONFIG.NOTIFY_WATCHED_PERCENT
|
||||
|
||||
stats_data = data_factory.get_home_stats(time_range=time_range,
|
||||
stats_data = data_factory.get_home_stats(grouping=grouping,
|
||||
time_range=time_range,
|
||||
stats_type=stats_type,
|
||||
stats_count=stats_count,
|
||||
stats_cards=stats_cards,
|
||||
@@ -175,6 +177,9 @@ class WebInterface(object):
|
||||
def graphs(self):
|
||||
|
||||
config = {
|
||||
"graph_type": plexpy.CONFIG.GRAPH_TYPE,
|
||||
"graph_days": plexpy.CONFIG.GRAPH_DAYS,
|
||||
"graph_tab": plexpy.CONFIG.GRAPH_TAB,
|
||||
"music_logging_enable": plexpy.CONFIG.MUSIC_LOGGING_ENABLE
|
||||
}
|
||||
|
||||
@@ -467,6 +472,10 @@ class WebInterface(object):
|
||||
"notify_on_extdown_body_text": plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT,
|
||||
"notify_on_intdown_subject_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT,
|
||||
"notify_on_intdown_body_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT,
|
||||
"notify_on_extup_subject_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT,
|
||||
"notify_on_extup_body_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT,
|
||||
"notify_on_intup_subject_text": plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT,
|
||||
"notify_on_intup_body_text": plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT,
|
||||
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
|
||||
"home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE),
|
||||
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
|
||||
@@ -606,14 +615,14 @@ class WebInterface(object):
|
||||
custom_where.append(['session_history.grandparent_rating_key', rating_key])
|
||||
if 'start_date' in kwargs:
|
||||
start_date = kwargs.get('start_date', "")
|
||||
custom_where.append(['strftime("%Y-%m-%d", datetime(date, "unixepoch", "localtime"))', start_date])
|
||||
custom_where.append(['strftime("%Y-%m-%d", datetime(started, "unixepoch", "localtime"))', start_date])
|
||||
if 'reference_id' in kwargs:
|
||||
reference_id = kwargs.get('reference_id', "")
|
||||
custom_where.append(['session_history.reference_id', reference_id])
|
||||
if 'media_type' in kwargs:
|
||||
media_type = kwargs.get('media_type', "")
|
||||
if media_type != 'all':
|
||||
custom_where.append(['session_history_metadata.media_type', media_type])
|
||||
custom_where.append(['session_history.media_type', media_type])
|
||||
|
||||
data_factory = datafactory.DataFactory()
|
||||
history = data_factory.get_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping, watched_percent=watched_percent)
|
||||
@@ -735,8 +744,6 @@ class WebInterface(object):
|
||||
if not session['ip_address']:
|
||||
ip_address = data_factory.get_session_ip(session['session_key'])
|
||||
session['ip_address'] = ip_address
|
||||
# Sanitize player name
|
||||
session['player'] = helpers.sanitize(session['player'])
|
||||
|
||||
except:
|
||||
return serve_template(templatename="current_activity.html", data=None)
|
||||
@@ -953,6 +960,20 @@ class WebInterface(object):
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps(history)
|
||||
|
||||
@cherrypy.expose
|
||||
def set_graph_config(self, graph_type=None, graph_days=None, graph_tab=None):
|
||||
if graph_type:
|
||||
plexpy.CONFIG.__setattr__('GRAPH_TYPE', graph_type)
|
||||
plexpy.CONFIG.write()
|
||||
if graph_days:
|
||||
plexpy.CONFIG.__setattr__('GRAPH_DAYS', graph_days)
|
||||
plexpy.CONFIG.write()
|
||||
if graph_tab:
|
||||
plexpy.CONFIG.__setattr__('GRAPH_TAB', graph_tab)
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
return "Updated graphs config values."
|
||||
|
||||
@cherrypy.expose
|
||||
def get_plays_by_date(self, time_range='30', y_axis='plays', **kwargs):
|
||||
|
||||
@@ -1355,12 +1376,22 @@ class WebInterface(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def get_notification_agent_config(self, config_id, **kwargs):
|
||||
config = notifiers.get_notification_agent_config(config_id=config_id)
|
||||
if config_id.isdigit():
|
||||
config = notifiers.get_notification_agent_config(config_id=config_id)
|
||||
agents = notifiers.available_notification_agents()
|
||||
for agent in agents:
|
||||
if int(config_id) == agent['id']:
|
||||
this_agent = agent
|
||||
break
|
||||
else:
|
||||
this_agent = None
|
||||
else:
|
||||
return None
|
||||
|
||||
checkboxes = {'email_tls': checked(plexpy.CONFIG.EMAIL_TLS)}
|
||||
|
||||
return serve_template(templatename="notification_config.html", title="Notification Configuration",
|
||||
config_id=config_id, data=config, checkboxes=checkboxes)
|
||||
agent=this_agent, data=config, checkboxes=checkboxes)
|
||||
|
||||
@cherrypy.expose
|
||||
def get_notification_agent_triggers(self, config_id, **kwargs):
|
||||
|
Reference in New Issue
Block a user