Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
49fb4540a2 | ||
![]() |
e2120393a2 | ||
![]() |
0b301fff3f | ||
![]() |
eeb351e991 | ||
![]() |
1095e29b4d | ||
![]() |
be058eaff7 | ||
![]() |
f409dda2ef | ||
![]() |
f409cdda8f | ||
![]() |
9cd6396c35 | ||
![]() |
ee754ea533 | ||
![]() |
36de20dd75 | ||
![]() |
a957e8eb4f | ||
![]() |
14a90d84ec | ||
![]() |
fae9bc618a | ||
![]() |
3248e6500e | ||
![]() |
c17bf79d79 | ||
![]() |
1ff1270bfa | ||
![]() |
b1a2cf33d8 | ||
![]() |
b2292e98c1 | ||
![]() |
4d156a8911 | ||
![]() |
7193b6518b |
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,5 +1,24 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.3.6 (2016-02-03)
|
||||||
|
|
||||||
|
* Fix: Regression where {duration} not reported in minutes.
|
||||||
|
* Fix: Proper daemonizing in FreeBSD and FreeNAS init scripts.
|
||||||
|
* Change: Update readme documentation.
|
||||||
|
|
||||||
|
|
||||||
|
## v1.3.5 (2016-02-02)
|
||||||
|
|
||||||
|
* Fix: Removing unique constraints from database.
|
||||||
|
* Fix: Unable to expand media info table when missing "Added At" date.
|
||||||
|
* Fix: Server verification for unpublished servers.
|
||||||
|
* Fix: Updating PMS identifier for server change.
|
||||||
|
* Add: {stream_time}, {remaining_time}, and {progress_time} to notification options.
|
||||||
|
* Add: Powershell script support. (Thanks @Hellowlol)
|
||||||
|
* Add: Method to delete duplicate libraries.
|
||||||
|
* Change: Daemonize before running start up tasks.
|
||||||
|
|
||||||
|
|
||||||
## v1.3.4 (2016-01-29)
|
## v1.3.4 (2016-01-29)
|
||||||
|
|
||||||
* Fix: Activity checker not starting with library update (history not logging).
|
* Fix: Activity checker not starting with library update (history not logging).
|
||||||
@@ -8,10 +27,10 @@
|
|||||||
* Fix: Libraries and Users lists not refreshing.
|
* Fix: Libraries and Users lists not refreshing.
|
||||||
* Fix: Server verification in settings.
|
* Fix: Server verification in settings.
|
||||||
* Fix: Empty libraries not added to database.
|
* Fix: Empty libraries not added to database.
|
||||||
* Add: Unique identifier to notification options.
|
* Add: Unique identifiers to notification options.
|
||||||
* Remove: Media type toggles for recently added notifications.
|
* Remove: Requirement of media type toggles for recently added notifications.
|
||||||
* Remove: Built in Twitter key and secret.
|
* Remove: Built in Twitter key and secret.
|
||||||
* Remove: Unnecessary quoting of script arguments.
|
* Change: Unnecessary quoting of script arguments.
|
||||||
* Change: Facebook notification instructions.
|
* Change: Facebook notification instructions.
|
||||||
|
|
||||||
|
|
||||||
|
@@ -153,12 +153,12 @@ def main():
|
|||||||
# Put the database in the DATA_DIR
|
# Put the database in the DATA_DIR
|
||||||
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, 'plexpy.db')
|
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, 'plexpy.db')
|
||||||
|
|
||||||
# Read config and start logging
|
|
||||||
plexpy.initialize(config_file)
|
|
||||||
|
|
||||||
if plexpy.DAEMON:
|
if plexpy.DAEMON:
|
||||||
plexpy.daemonize()
|
plexpy.daemonize()
|
||||||
|
|
||||||
|
# Read config and start logging
|
||||||
|
plexpy.initialize(config_file)
|
||||||
|
|
||||||
# Force the http port if neccessary
|
# Force the http port if neccessary
|
||||||
if args.port:
|
if args.port:
|
||||||
http_port = args.port
|
http_port = args.port
|
||||||
|
164
README.md
164
README.md
@@ -1,120 +1,74 @@
|
|||||||
#PlexPy
|
# PlexPy
|
||||||
|
|
||||||
[](https://gitter.im/drzoidberg33/plexpy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/drzoidberg33/plexpy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
A python based web application for monitoring, analytics and notifications for Plex Media Server (www.plex.tv).
|
A python based web application for monitoring, analytics and notifications for Plex Media Server (www.plex.tv).
|
||||||
|
|
||||||
This project is based on code from Headphones (https://github.com/rembo10/headphones) and PlexWatchWeb (https://github.com/ecleese/plexWatchWeb).
|
This project is based on code from [Headphones](https://github.com/rembo10/headphones) and [PlexWatchWeb](https://github.com/ecleese/plexWatchWeb).
|
||||||
|
|
||||||
* PlexPy forum thread: https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program
|
* PlexPy [forum thread](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* Responsive web design viewable on desktop, tablet and mobile web browsers.
|
||||||
|
* Themed to complement Plex/Web.
|
||||||
|
* Easy configuration setup (no separate web server required).
|
||||||
|
* Monitor current Plex Media Server activity.
|
||||||
|
* Fully customizable notifications for stream activity and recently added media.
|
||||||
|
* Top statistics on home page with configurable duration and measurement metric.
|
||||||
|
* Global watching history with search/filtering & dynamic column sorting.
|
||||||
|
* Full user list with general information and comparison stats.
|
||||||
|
* Individual user information including devices IP addresses.
|
||||||
|
* Complete library statistics and media file information.
|
||||||
|
* Rich analytics presented using Highcharts graphing.
|
||||||
|
* Beautiful content information pages.
|
||||||
|
* Full sync list data on all users syncing items from your library.
|
||||||
|
* And many more!!
|
||||||
|
|
||||||
###Support
|
## Installation and Support
|
||||||
-----------
|
|
||||||
* PlexPy Wiki: https://github.com/drzoidberg33/plexpy/wiki
|
|
||||||
|
|
||||||
|
* [Installation Guides](https://github.com/drzoidberg33/plexpy/wiki/Installation) shows you how to install PlexPy.
|
||||||
###Features
|
* [FAQs](https://github.com/drzoidberg33/plexpy/wiki/Frequently-Asked-Questions-(FAQ)) in the wiki can help you with common problems.
|
||||||
-----------
|
|
||||||
* Responsive web design viewable on desktop, tablet and mobile web browsers
|
|
||||||
|
|
||||||
* Themed to complement Plex/Web
|
|
||||||
|
|
||||||
* Easy configuration setup via html form
|
|
||||||
|
|
||||||
* Current Plex Media Server viewing activity including:
|
|
||||||
* number of current users
|
|
||||||
* title
|
|
||||||
* progress
|
|
||||||
* platform
|
|
||||||
* user
|
|
||||||
* state (playing, paused, buffering, etc)
|
|
||||||
* stream type (direct, transcoded)
|
|
||||||
* video type & resolution
|
|
||||||
* audio type & channel count.
|
|
||||||
|
|
||||||
* Top statistics on home page with configurable duration and measurement metric:
|
|
||||||
* Most watched TV
|
|
||||||
* Most popular TV
|
|
||||||
* Most watched Movie
|
|
||||||
* Most popular Movie
|
|
||||||
* Most active user
|
|
||||||
* Most active platform
|
|
||||||
|
|
||||||
* Recently added media and how long ago it was added
|
|
||||||
|
|
||||||
* Global watching history with search/filtering & dynamic column sorting
|
|
||||||
* date
|
|
||||||
* user
|
|
||||||
* platform
|
|
||||||
* ip address
|
|
||||||
* title
|
|
||||||
* stream information details
|
|
||||||
* start time
|
|
||||||
* paused duration length
|
|
||||||
* stop time
|
|
||||||
* duration length
|
|
||||||
* watched progress
|
|
||||||
* show/hide columns
|
|
||||||
* delete mode - allows deletion of specific history items
|
|
||||||
|
|
||||||
* Full user list with general information and comparison stats
|
|
||||||
|
|
||||||
* Individual user information
|
|
||||||
* username and gravatar (if available)
|
|
||||||
* daily, weekly, monthly, all time stats for play count and duration length
|
|
||||||
* individual platform stats for each user
|
|
||||||
* public ip address history with last seen date and geo tag location
|
|
||||||
* recently watched content
|
|
||||||
* watching history
|
|
||||||
* synced items
|
|
||||||
* assign users custom friendly names within PlexPy
|
|
||||||
* assign users custom avatar URL within PlexPy
|
|
||||||
* disable history logging per user
|
|
||||||
* disable notifications per user
|
|
||||||
* option to purge all history per user.
|
|
||||||
|
|
||||||
* Rich analytics presented using Highcharts graphing
|
|
||||||
* user-selectable time periods of 30, 90 or 365 days
|
|
||||||
* daily watch count and duration
|
|
||||||
* totals by day of week and hours of the day
|
|
||||||
* totals by top 10 platform
|
|
||||||
* totals by top 10 users
|
|
||||||
* detailed breakdown by transcode decision
|
|
||||||
* source and stream resolutions
|
|
||||||
* transcode decision counts by user and platform
|
|
||||||
* total monthly counts
|
|
||||||
|
|
||||||
* Content information pages
|
|
||||||
* movies (includes watching history)
|
|
||||||
* tv shows (includes watching history)
|
|
||||||
* tv seasons
|
|
||||||
* tv episodes (includes watching history)
|
|
||||||
|
|
||||||
* Full sync list data on all users syncing items from your library
|
|
||||||
|
|
||||||
## Installation and Notes
|
|
||||||
|
|
||||||
* [Installation page](../../wiki/Installation) shows you how to install PlexPy.
|
|
||||||
* [Usage guide](../../wiki/Usage-guide) introduces you to PlexPy.
|
|
||||||
* [Troubleshooting page](../../wiki/TroubleShooting) in the wiki can help you with common problems.
|
|
||||||
|
|
||||||
**Issues** can be reported on the GitHub issue tracker considering these rules:
|
|
||||||
|
|
||||||
1. Analyze your log, you just might find the solution yourself!
|
|
||||||
2. You read the wiki and searched existing issues, but this is not solving your problem.
|
|
||||||
3. Post the issue with a clear title, description and the HP log and use [proper markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your text (code/log in code blocks).
|
|
||||||
4. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
|
|
||||||
|
|
||||||
**Feature requests** can be reported on the GitHub issue tracker too:
|
|
||||||
|
|
||||||
1. Search for similar existing 'issues', feature requests can be recognized by the label 'Request'.
|
|
||||||
2. If a similar Request exists, post a comment (+1, or add a new idea to the existing request), otherwise you can create a new one.
|
|
||||||
|
|
||||||
If you **comply with these rules** you can [post your request/issue](http://github.com/drzoidberg33/plexpy/issues).
|
|
||||||
|
|
||||||
**Support** the project by implementing new features, solving support tickets and provide bug fixes.
|
**Support** the project by implementing new features, solving support tickets and provide bug fixes.
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
##### Many issues can simply be solved by:
|
||||||
|
|
||||||
|
- Making sure you update to the latest version.
|
||||||
|
- Turning your device off and on again.
|
||||||
|
- Analyzing your logs, you just might find the solution yourself!
|
||||||
|
- Using the **search** function to see if this issue has already been reported/solved.
|
||||||
|
- Checking the [Wiki](https://github.com/drzoidberg33/plexpy/wiki) for
|
||||||
|
[ [Installation] ](https://github.com/drzoidberg33/plexpy/wiki/Installation) and
|
||||||
|
[ [FAQs] ](https://github.com/drzoidberg33/plexpy/wiki/Frequently-Asked-Questions-(FAQ)).
|
||||||
|
- For basic questions try asking on [Gitter](https://gitter.im/drzoidberg33/plexpy) or the [Plex Forums](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program) first before opening an issue.
|
||||||
|
|
||||||
|
##### If nothing has worked:
|
||||||
|
|
||||||
|
1. Open a new issue on the GitHub [issue tracker](http://github.com/drzoidberg33/plexpy/issues).
|
||||||
|
2. Provide a clear title to easily help identify your problem.
|
||||||
|
3. Use proper [markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your post (i.e. code/log in code blocks).
|
||||||
|
4. Make sure you provide the following information:
|
||||||
|
- [ ] Branch
|
||||||
|
- [ ] Version/Commit hash
|
||||||
|
- [ ] Your operating system and python version
|
||||||
|
- [ ] What you did?
|
||||||
|
- [ ] What happened?
|
||||||
|
- [ ] What you expected?
|
||||||
|
- [ ] How can we reproduce your issue?
|
||||||
|
- [ ] What are your (relevant) settings?
|
||||||
|
- [ ] Include a link to your **FULL** (not just a few lines!) log file that has the error. Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/).
|
||||||
|
5. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
|
||||||
|
|
||||||
|
## Feature Requests
|
||||||
|
|
||||||
|
1. Search for similar existing 'issues', feature requests can be recognized by the blue `enhancement` label.
|
||||||
|
2. If a similar request exists, post a comment (+1, or add a new idea to the existing request).
|
||||||
|
3. If no similar requests exist, you can create a new one.
|
||||||
|
4. Provide a clear title to easily identify the feature request.
|
||||||
|
5. Tag your feature request with `[Feature Request]` so it can be identified easily.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
This is free software under the GPL v3 open source license. Feel free to do with it what you wish, but any modification must be open sourced. A copy of the license is included.
|
This is free software under the GPL v3 open source license. Feel free to do with it what you wish, but any modification must be open sourced. A copy of the license is included.
|
||||||
|
|
||||||
|
@@ -34,9 +34,12 @@ media_info_table_options = {
|
|||||||
"targets": [0],
|
"targets": [0],
|
||||||
"data": "added_at",
|
"data": "added_at",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData !== null && cellData !== '') {
|
if (rowData) {
|
||||||
var expand_details = '';
|
var expand_details = '';
|
||||||
var date = moment(cellData, "X").format(date_format);
|
var date = '';
|
||||||
|
if (cellData !== null && cellData !== '') {
|
||||||
|
date = moment(cellData, "X").format(date_format);
|
||||||
|
}
|
||||||
if (rowData['media_type'] === 'show') {
|
if (rowData['media_type'] === 'show') {
|
||||||
expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Seasons"><i class="fa fa-plus-circle fa-fw"></i></span>';
|
expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Seasons"><i class="fa fa-plus-circle fa-fw"></i></span>';
|
||||||
$(td).html('<div><a href="#"><div style="float: left;">' + expand_details + ' ' + date + '</div></a></div>');
|
$(td).html('<div><a href="#"><div style="float: left;">' + expand_details + ' ' + date + '</div></a></div>');
|
||||||
|
@@ -181,6 +181,10 @@ from plexpy import helpers
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('#facebookStep1').click(function () {
|
$('#facebookStep1').click(function () {
|
||||||
|
// Remove trailing '/' from Facebook redirect URI
|
||||||
|
if ($('#facebook_redirect_uri') && $('#facebook_redirect_uri').val().endsWith('/')) {
|
||||||
|
$('#facebook_redirect_uri').val($('#facebook_redirect_uri').val().slice(0, -1));
|
||||||
|
}
|
||||||
doAjaxCall('set_notification_config', $(this), 'tabs', true);
|
doAjaxCall('set_notification_config', $(this), 'tabs', true);
|
||||||
$.get('facebookStep1', function (data) { window.open(data); })
|
$.get('facebookStep1', function (data) { window.open(data); })
|
||||||
.done(function () { showMsg('<i class="fa fa-check"></i> Confirm Authorization. Check pop-up blocker if no response.', false, true, 3000); });
|
.done(function () { showMsg('<i class="fa fa-check"></i> Confirm Authorization. Check pop-up blocker if no response.', false, true, 3000); });
|
||||||
|
@@ -1093,11 +1093,11 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{datestamp}</strong></td>
|
<td><strong>{datestamp}</strong></td>
|
||||||
<td>The date the notification was triggered.</td>
|
<td>The date (in date format) the notification was triggered.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{timestamp}</strong></td>
|
<td><strong>{timestamp}</strong></td>
|
||||||
<td>The time the notification was triggered.</td>
|
<td>The time (in time format) the notification was triggered.</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -1134,14 +1134,26 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||||||
<td><strong>{stream_duration}</strong></td>
|
<td><strong>{stream_duration}</strong></td>
|
||||||
<td>The stream duration (in minutes) for the item.</td>
|
<td>The stream duration (in minutes) for the item.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{stream_time}</strong></td>
|
||||||
|
<td>The stream duration (in time format) for the item.</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{remaining_duration}</strong></td>
|
<td><strong>{remaining_duration}</strong></td>
|
||||||
<td>The remaining duration (in minutes) for the item.</td>
|
<td>The remaining duration (in minutes) for the item.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{progress}</strong></td>
|
<td><strong>{remaining_time}</strong></td>
|
||||||
|
<td>The remaining duration (in time format) for the item.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{progress_duration}</strong></td>
|
||||||
<td>The last reported offset (in minutes) for the item.</td>
|
<td>The last reported offset (in minutes) for the item.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{progress_time}</strong></td>
|
||||||
|
<td>The last reported offset (in time format) for the item.</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{progress_percent}</strong></td>
|
<td><strong>{progress_percent}</strong></td>
|
||||||
<td>The last reported progress percent for the item.</td>
|
<td>The last reported progress percent for the item.</td>
|
||||||
@@ -1222,6 +1234,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||||||
<td><strong>{transcode_audio_channels}</strong></td>
|
<td><strong>{transcode_audio_channels}</strong></td>
|
||||||
<td>The audio channels of the transcoded media.</td>
|
<td>The audio channels of the transcoded media.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{session_key}</strong></td>
|
||||||
|
<td>The unique identifier for the session.</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{user_id}</strong></td>
|
<td><strong>{user_id}</strong></td>
|
||||||
<td>The unique identifier for the user.</td>
|
<td>The unique identifier for the user.</td>
|
||||||
@@ -1569,11 +1585,11 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function verifyServer(_callback) {
|
function verifyServer(_callback) {
|
||||||
var pms_ip = $("#pms_ip").val()
|
var pms_ip = $("#pms_ip").val();
|
||||||
var pms_port = $("#pms_port").val()
|
var pms_port = $("#pms_port").val();
|
||||||
var pms_identifier = $("#pms_identifier").val()
|
var pms_identifier = $("#pms_identifier").val();
|
||||||
var pms_ssl = $("#pms_ssl").val()
|
var pms_ssl = $("#pms_ssl").is(':checked') ? 1 : 0;
|
||||||
var pms_is_remote = $("#pms_is_remote").val()
|
var pms_is_remote = $("#pms_is_remote").is(':checked') ? 1 : 0;
|
||||||
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
|
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
|
||||||
$("#pms-verify").html('<i class="fa fa-refresh fa-spin"></i>');
|
$("#pms-verify").html('<i class="fa fa-refresh fa-spin"></i>');
|
||||||
$('#pms-verify').fadeIn('fast');
|
$('#pms-verify').fadeIn('fast');
|
||||||
@@ -1582,15 +1598,16 @@ $(document).ready(function() {
|
|||||||
data : { hostname: pms_ip, port: pms_port, identifier: pms_identifier, ssl: pms_ssl, remote: pms_is_remote },
|
data : { hostname: pms_ip, port: pms_port, identifier: pms_identifier, ssl: pms_ssl, remote: pms_is_remote },
|
||||||
cache: true,
|
cache: true,
|
||||||
async: true,
|
async: true,
|
||||||
timeout: 5000,
|
timeout: 10000,
|
||||||
error: function(jqXHR, textStatus, errorThrown) {
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
$("#pms-verify").html('<i class="fa fa-close"></i>');
|
$("#pms-verify").html('<i class="fa fa-close"></i>');
|
||||||
$('#pms-verify').fadeIn('fast');
|
$('#pms-verify').fadeIn('fast');
|
||||||
$("#pms-ip-group").addClass("has-error");
|
$("#pms-ip-group").addClass("has-error");
|
||||||
},
|
},
|
||||||
success: function (xml) {
|
success: function (json) {
|
||||||
if ($(xml).find('MediaContainer').attr('machineIdentifier')) {
|
var machine_identifier = json;
|
||||||
$("#pms_identifier").val($(xml).find('MediaContainer').attr('machineIdentifier'));
|
if (machine_identifier) {
|
||||||
|
$("#pms_identifier").val(machine_identifier);
|
||||||
$("#pms-verify").html('<i class="fa fa-check"></i>');
|
$("#pms-verify").html('<i class="fa fa-check"></i>');
|
||||||
$('#pms-verify').fadeIn('fast');
|
$('#pms-verify').fadeIn('fast');
|
||||||
$("#pms-ip-group").removeClass("has-error");
|
$("#pms-ip-group").removeClass("has-error");
|
||||||
|
@@ -393,9 +393,10 @@ from plexpy import common
|
|||||||
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> This is not a Plex Server!');
|
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> This is not a Plex Server!');
|
||||||
$('#pms-verify-status').fadeIn('fast');
|
$('#pms-verify-status').fadeIn('fast');
|
||||||
},
|
},
|
||||||
success: function (xml) {
|
success: function (json) {
|
||||||
if ($(xml).find('MediaContainer').attr('machineIdentifier')) {
|
var machine_identifier = json;
|
||||||
$("#pms_identifier").val($(xml).find('MediaContainer').attr('machineIdentifier'));
|
if (machine_identifier) {
|
||||||
|
$("#pms_identifier").val(machine_identifier);
|
||||||
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!');
|
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!');
|
||||||
$('#pms-verify-status').fadeIn('fast');
|
$('#pms-verify-status').fadeIn('fast');
|
||||||
pms_verified = true;
|
pms_verified = true;
|
||||||
|
@@ -38,7 +38,7 @@ status_cmd="${name}_status"
|
|||||||
stop_cmd="${name}_stop"
|
stop_cmd="${name}_stop"
|
||||||
|
|
||||||
command="/usr/sbin/daemon"
|
command="/usr/sbin/daemon"
|
||||||
command_args="-f -p ${plexpy_pid} python ${plexpy_dir}/PlexPy.py ${plexpy_flags} --quiet --nolaunch"
|
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch"
|
||||||
|
|
||||||
# Ensure user is root when running this script.
|
# Ensure user is root when running this script.
|
||||||
if [ `id -u` != "0" ]; then
|
if [ `id -u` != "0" ]; then
|
||||||
|
@@ -38,7 +38,7 @@ status_cmd="${name}_status"
|
|||||||
stop_cmd="${name}_stop"
|
stop_cmd="${name}_stop"
|
||||||
|
|
||||||
command="/usr/sbin/daemon"
|
command="/usr/sbin/daemon"
|
||||||
command_args="-f -p ${plexpy_pid} python2 ${plexpy_dir}/PlexPy.py ${plexpy_flags} --quiet --nolaunch"
|
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch"
|
||||||
|
|
||||||
# Ensure user is root when running this script.
|
# Ensure user is root when running this script.
|
||||||
if [ `id -u` != "0" ]; then
|
if [ `id -u` != "0" ]; then
|
||||||
|
@@ -713,8 +713,8 @@ def dbcheck():
|
|||||||
|
|
||||||
# Upgrade library_sections table from earlier versions (remove UNIQUE constraint on section_id)
|
# Upgrade library_sections table from earlier versions (remove UNIQUE constraint on section_id)
|
||||||
try:
|
try:
|
||||||
result = c_db.execute('PRAGMA index_xinfo("sqlite_autoindex_library_sections_1")')
|
result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="library_sections"').fetchone()
|
||||||
if result and 'server_id' not in [row[2] for row in result]:
|
if 'section_id INTEGER UNIQUE' in result[0]:
|
||||||
logger.debug(u"Altering database. Removing unique constraint on section_id from library_sections table.")
|
logger.debug(u"Altering database. Removing unique constraint on section_id from library_sections table.")
|
||||||
c_db.execute(
|
c_db.execute(
|
||||||
'CREATE TABLE library_sections_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
'CREATE TABLE library_sections_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||||
@@ -760,8 +760,8 @@ def dbcheck():
|
|||||||
|
|
||||||
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
|
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
|
||||||
try:
|
try:
|
||||||
result = c_db.execute('PRAGMA index_xinfo("sqlite_autoindex_users_2")')
|
result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="users"').fetchone()
|
||||||
if result and 'username' in [row[2] for row in result]:
|
if 'username TEXT NOT NULL UNIQUE' in result[0]:
|
||||||
logger.debug(u"Altering database. Removing unique constraint on username from users table.")
|
logger.debug(u"Altering database. Removing unique constraint on username from users table.")
|
||||||
c_db.execute(
|
c_db.execute(
|
||||||
'CREATE TABLE users_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
'CREATE TABLE users_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||||
|
@@ -512,6 +512,7 @@ class Config(object):
|
|||||||
self.MOVIE_LOGGING_ENABLE = 0
|
self.MOVIE_LOGGING_ENABLE = 0
|
||||||
self.TV_LOGGING_ENABLE = 0
|
self.TV_LOGGING_ENABLE = 0
|
||||||
self.CONFIG_VERSION = '1'
|
self.CONFIG_VERSION = '1'
|
||||||
|
|
||||||
if self.CONFIG_VERSION == '1':
|
if self.CONFIG_VERSION == '1':
|
||||||
# Change home_stats_cards to list
|
# Change home_stats_cards to list
|
||||||
if self.HOME_STATS_CARDS:
|
if self.HOME_STATS_CARDS:
|
||||||
@@ -526,3 +527,19 @@ class Config(object):
|
|||||||
home_library_cards.remove('library_statistics')
|
home_library_cards.remove('library_statistics')
|
||||||
self.HOME_LIBRARY_CARDS = home_library_cards
|
self.HOME_LIBRARY_CARDS = home_library_cards
|
||||||
self.CONFIG_VERSION = '2'
|
self.CONFIG_VERSION = '2'
|
||||||
|
|
||||||
|
if self.CONFIG_VERSION == '2':
|
||||||
|
self.NOTIFY_ON_START_SUBJECT_TEXT = self.NOTIFY_ON_START_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_START_BODY_TEXT = self.NOTIFY_ON_START_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_STOP_SUBJECT_TEXT = self.NOTIFY_ON_STOP_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_STOP_BODY_TEXT = self.NOTIFY_ON_STOP_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_PAUSE_SUBJECT_TEXT = self.NOTIFY_ON_PAUSE_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_PAUSE_BODY_TEXT = self.NOTIFY_ON_PAUSE_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_RESUME_SUBJECT_TEXT = self.NOTIFY_ON_RESUME_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_RESUME_BODY_TEXT = self.NOTIFY_ON_RESUME_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_BUFFER_SUBJECT_TEXT = self.NOTIFY_ON_BUFFER_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_BUFFER_BODY_TEXT = self.NOTIFY_ON_BUFFER_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_WATCHED_SUBJECT_TEXT = self.NOTIFY_ON_WATCHED_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_ON_WATCHED_BODY_TEXT = self.NOTIFY_ON_WATCHED_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.NOTIFY_SCRIPTS_ARGS_TEXT = self.NOTIFY_SCRIPTS_ARGS_TEXT.replace('{progress}','{progress_duration}')
|
||||||
|
self.CONFIG_VERSION = '3'
|
||||||
|
@@ -135,6 +135,15 @@ def convert_seconds(s):
|
|||||||
|
|
||||||
return minutes
|
return minutes
|
||||||
|
|
||||||
|
def convert_seconds_to_minutes(s):
|
||||||
|
|
||||||
|
if str(s).isdigit():
|
||||||
|
minutes = round(float(s) / 60, 0)
|
||||||
|
|
||||||
|
return math.trunc(minutes)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def today():
|
def today():
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
@@ -369,14 +378,14 @@ def create_https_certificates(ssl_cert, ssl_key):
|
|||||||
def cast_to_int(s):
|
def cast_to_int(s):
|
||||||
try:
|
try:
|
||||||
return int(s)
|
return int(s)
|
||||||
except ValueError:
|
except (ValueError, TypeError):
|
||||||
return -1
|
return 0
|
||||||
|
|
||||||
def cast_to_float(s):
|
def cast_to_float(s):
|
||||||
try:
|
try:
|
||||||
return float(s)
|
return float(s)
|
||||||
except ValueError:
|
except (ValueError, TypeError):
|
||||||
return -1
|
return 0
|
||||||
|
|
||||||
def convert_xml_to_json(xml):
|
def convert_xml_to_json(xml):
|
||||||
o = xmltodict.parse(xml)
|
o = xmltodict.parse(xml)
|
||||||
|
@@ -44,7 +44,8 @@ class HTTPHandler(object):
|
|||||||
headers=None,
|
headers=None,
|
||||||
output_format='raw',
|
output_format='raw',
|
||||||
return_type=False,
|
return_type=False,
|
||||||
no_token=False):
|
no_token=False,
|
||||||
|
timeout=20):
|
||||||
|
|
||||||
valid_request_types = ['GET', 'POST', 'PUT', 'DELETE']
|
valid_request_types = ['GET', 'POST', 'PUT', 'DELETE']
|
||||||
|
|
||||||
@@ -56,12 +57,12 @@ class HTTPHandler(object):
|
|||||||
if proto.upper() == 'HTTPS':
|
if proto.upper() == 'HTTPS':
|
||||||
if not self.ssl_verify and hasattr(ssl, '_create_unverified_context'):
|
if not self.ssl_verify and hasattr(ssl, '_create_unverified_context'):
|
||||||
context = ssl._create_unverified_context()
|
context = ssl._create_unverified_context()
|
||||||
handler = HTTPSConnection(host=self.host, port=self.port, timeout=20, context=context)
|
handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout, context=context)
|
||||||
logger.warn(u"PlexPy HTTP Handler :: Unverified HTTPS request made. This connection is not secure.")
|
logger.warn(u"PlexPy HTTP Handler :: Unverified HTTPS request made. This connection is not secure.")
|
||||||
else:
|
else:
|
||||||
handler = HTTPSConnection(host=self.host, port=self.port, timeout=20)
|
handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout)
|
||||||
else:
|
else:
|
||||||
handler = HTTPConnection(host=self.host, port=self.port, timeout=20)
|
handler = HTTPConnection(host=self.host, port=self.port, timeout=timeout)
|
||||||
|
|
||||||
token_string = ''
|
token_string = ''
|
||||||
if not no_token:
|
if not no_token:
|
||||||
|
@@ -879,3 +879,21 @@ class Libraries(object):
|
|||||||
return 'Unable to delete media info table cache, section_id not valid.'
|
return 'Unable to delete media info table cache, section_id not valid.'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(u"PlexPy Libraries :: Unable to delete media info table cache: %s." % e)
|
logger.warn(u"PlexPy Libraries :: Unable to delete media info table cache: %s." % e)
|
||||||
|
|
||||||
|
def delete_duplicate_libraries(self):
|
||||||
|
from plexpy import plextv
|
||||||
|
|
||||||
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
|
# Refresh the PMS_URL to make sure the server_id is updated
|
||||||
|
plextv.get_real_pms_url()
|
||||||
|
|
||||||
|
server_id = plexpy.CONFIG.PMS_IDENTIFIER
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.debug(u"PlexPy Libraries :: Deleting libraries where server_id does not match %s." % server_id)
|
||||||
|
monitor_db.action('DELETE FROM library_sections WHERE server_id != ?', [server_id])
|
||||||
|
|
||||||
|
return 'Deleted duplicate libraries from the database.'
|
||||||
|
except Exception as e:
|
||||||
|
logger.warn(u"PlexPy Libraries :: Unable to delete duplicate libraries: %s." % e)
|
@@ -340,6 +340,11 @@ def set_notify_state(session, state, agent_info):
|
|||||||
|
|
||||||
|
|
||||||
def build_notify_text(session=None, timeline=None, state=None):
|
def build_notify_text(session=None, timeline=None, state=None):
|
||||||
|
# Get time formats
|
||||||
|
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
|
||||||
|
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
|
||||||
|
duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','').replace('a','').replace('A','')
|
||||||
|
|
||||||
# Get the server name
|
# Get the server name
|
||||||
server_name = plexpy.CONFIG.PMS_NAME
|
server_name = plexpy.CONFIG.PMS_NAME
|
||||||
|
|
||||||
@@ -349,7 +354,7 @@ def build_notify_text(session=None, timeline=None, state=None):
|
|||||||
|
|
||||||
if server_times:
|
if server_times:
|
||||||
updated_at = server_times[0]['updated_at']
|
updated_at = server_times[0]['updated_at']
|
||||||
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at)))
|
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
|
||||||
else:
|
else:
|
||||||
logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.")
|
logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.")
|
||||||
server_uptime = 'N/A'
|
server_uptime = 'N/A'
|
||||||
@@ -428,80 +433,30 @@ def build_notify_text(session=None, timeline=None, state=None):
|
|||||||
else:
|
else:
|
||||||
full_title = metadata['title']
|
full_title = metadata['title']
|
||||||
|
|
||||||
duration = helpers.convert_milliseconds_to_minutes(metadata['duration'])
|
|
||||||
|
|
||||||
# Default values
|
|
||||||
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 = ''
|
|
||||||
user_id = ''
|
|
||||||
|
|
||||||
# Session values
|
# Session values
|
||||||
if session:
|
if session is None:
|
||||||
# Generate a combined transcode decision value
|
session = {}
|
||||||
video_decision = session['video_decision'].title()
|
|
||||||
audio_decision = session['audio_decision'].title()
|
|
||||||
|
|
||||||
if session['video_decision'] == 'transcode' or session['audio_decision'] == 'transcode':
|
# Generate a combined transcode decision value
|
||||||
transcode_decision = 'Transcode'
|
if session.get('video_decision','') == 'transcode' or session.get('audio_decision','') == 'transcode':
|
||||||
elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy':
|
transcode_decision = 'Transcode'
|
||||||
transcode_decision = 'Direct Stream'
|
elif session.get('video_decision','') == 'copy' or session.get('audio_decision','') == 'copy':
|
||||||
else:
|
transcode_decision = 'Direct Stream'
|
||||||
transcode_decision = 'Direct Play'
|
else:
|
||||||
|
transcode_decision = 'Direct Play'
|
||||||
|
|
||||||
if state != 'play':
|
if state != 'play':
|
||||||
if session['paused_counter']:
|
stream_duration = helpers.convert_seconds_to_minutes(
|
||||||
stream_duration = int((time.time() - helpers.cast_to_float(session['started']) -
|
time.time() -
|
||||||
helpers.cast_to_float(session['paused_counter'])) / 60)
|
helpers.cast_to_int(session.get('started', 0)) -
|
||||||
else:
|
helpers.cast_to_int(session.get('paused_counter', 0)))
|
||||||
stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60)
|
else:
|
||||||
|
stream_duration = 0
|
||||||
view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset'])
|
|
||||||
user = session['friendly_name']
|
|
||||||
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']
|
|
||||||
user_id = session['user_id']
|
|
||||||
|
|
||||||
|
view_offset = helpers.convert_milliseconds_to_minutes(session.get('view_offset', 0))
|
||||||
|
duration = helpers.convert_milliseconds_to_minutes(metadata['duration'])
|
||||||
progress_percent = helpers.get_percent(view_offset, duration)
|
progress_percent = helpers.get_percent(view_offset, duration)
|
||||||
|
remaining_duration = duration - view_offset
|
||||||
|
|
||||||
# Fix metadata params for notify recently added grandparent
|
# Fix metadata params for notify recently added grandparent
|
||||||
if state == 'created' and plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
|
if state == 'created' and plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
|
||||||
@@ -517,41 +472,48 @@ def build_notify_text(session=None, timeline=None, state=None):
|
|||||||
album_name = metadata['parent_title']
|
album_name = metadata['parent_title']
|
||||||
track_name = metadata['title']
|
track_name = metadata['title']
|
||||||
|
|
||||||
available_params = {'server_name': server_name,
|
available_params = {# Global paramaters
|
||||||
|
'server_name': server_name,
|
||||||
'server_uptime': server_uptime,
|
'server_uptime': server_uptime,
|
||||||
'action': state,
|
'action': state.title(),
|
||||||
'datestamp': arrow.now().format(plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')),
|
'datestamp': arrow.now().format(date_format),
|
||||||
'timestamp': arrow.now().format(plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')),
|
'timestamp': arrow.now().format(time_format),
|
||||||
|
# Stream parameters
|
||||||
'streams': stream_count,
|
'streams': stream_count,
|
||||||
'user': user,
|
'user': session.get('friendly_name',''),
|
||||||
'platform': platform,
|
'platform': session.get('platform',''),
|
||||||
'player': player,
|
'player': session.get('player',''),
|
||||||
'ip_address': ip_address,
|
'ip_address': session.get('ip_address','N/A'),
|
||||||
'media_type': metadata['media_type'],
|
|
||||||
'stream_duration': stream_duration,
|
'stream_duration': stream_duration,
|
||||||
'remaining_duration': duration - view_offset,
|
'stream_time': arrow.get(stream_duration * 60).format(duration_format),
|
||||||
'progress': view_offset,
|
'remaining_duration': remaining_duration,
|
||||||
|
'remaining_time': arrow.get(remaining_duration * 60).format(duration_format),
|
||||||
|
'progress_duration': view_offset,
|
||||||
|
'progress_time': arrow.get(view_offset * 60).format(duration_format),
|
||||||
'progress_percent': progress_percent,
|
'progress_percent': progress_percent,
|
||||||
'container': container,
|
'container': session.get('container',''),
|
||||||
'video_codec': video_codec,
|
'video_codec': session.get('video_codec',''),
|
||||||
'video_bitrate': video_bitrate,
|
'video_bitrate': session.get('bitrate',''),
|
||||||
'video_width': video_width,
|
'video_width': session.get('width',''),
|
||||||
'video_height': video_height,
|
'video_height': session.get('height',''),
|
||||||
'video_resolution': video_resolution,
|
'video_resolution': session.get('video_resolution',''),
|
||||||
'video_framerate': video_framerate,
|
'video_framerate': session.get('video_framerate',''),
|
||||||
'aspect_ratio': aspect_ratio,
|
'aspect_ratio': session.get('aspect_ratio',''),
|
||||||
'audio_codec': audio_codec,
|
'audio_codec': session.get('audio_codec',''),
|
||||||
'audio_channels': audio_channels,
|
'audio_channels': session.get('audio_channels',''),
|
||||||
'transcode_decision': transcode_decision,
|
'transcode_decision': transcode_decision,
|
||||||
'video_decision': video_decision,
|
'video_decision': session.get('video_decision','').title(),
|
||||||
'audio_decision': audio_decision,
|
'audio_decision': session.get('audio_decision','').title(),
|
||||||
'transcode_container': transcode_container,
|
'transcode_container': session.get('transcode_container',''),
|
||||||
'transcode_video_codec': transcode_video_codec,
|
'transcode_video_codec': session.get('transcode_video_codec',''),
|
||||||
'transcode_video_width': transcode_video_width,
|
'transcode_video_width': session.get('transcode_width',''),
|
||||||
'transcode_video_height': transcode_video_height,
|
'transcode_video_height': session.get('transcode_height',''),
|
||||||
'transcode_audio_codec': transcode_audio_codec,
|
'transcode_audio_codec': session.get('transcode_audio_codec',''),
|
||||||
'transcode_audio_channels': transcode_audio_channels,
|
'transcode_audio_channels': session.get('transcode_audio_channels',''),
|
||||||
'user_id': user_id,
|
'session_key': session.get('session_key',''),
|
||||||
|
'user_id': session.get('user_id',''),
|
||||||
|
# Metadata parameters
|
||||||
|
'media_type': metadata['media_type'],
|
||||||
'title': full_title,
|
'title': full_title,
|
||||||
'library_name': metadata['library_name'],
|
'library_name': metadata['library_name'],
|
||||||
'show_name': show_name,
|
'show_name': show_name,
|
||||||
@@ -598,9 +560,9 @@ def build_notify_text(session=None, timeline=None, state=None):
|
|||||||
|
|
||||||
if state == 'play':
|
if state == 'play':
|
||||||
# Default body text
|
# Default body text
|
||||||
body_text = '%s (%s) is watching %s' % (session['friendly_name'],
|
body_text = '%s (%s) started playing %s' % (session['friendly_name'],
|
||||||
session['player'],
|
session['player'],
|
||||||
full_title)
|
full_title)
|
||||||
|
|
||||||
if on_start_subject and on_start_body:
|
if on_start_subject and on_start_body:
|
||||||
try:
|
try:
|
||||||
@@ -767,6 +729,10 @@ def build_notify_text(session=None, timeline=None, state=None):
|
|||||||
|
|
||||||
|
|
||||||
def build_server_notify_text(state=None):
|
def build_server_notify_text(state=None):
|
||||||
|
# Get time formats
|
||||||
|
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
|
||||||
|
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
|
||||||
|
|
||||||
# Get the server name
|
# Get the server name
|
||||||
server_name = plexpy.CONFIG.PMS_NAME
|
server_name = plexpy.CONFIG.PMS_NAME
|
||||||
|
|
||||||
@@ -776,7 +742,7 @@ def build_server_notify_text(state=None):
|
|||||||
|
|
||||||
if server_times:
|
if server_times:
|
||||||
updated_at = server_times[0]['updated_at']
|
updated_at = server_times[0]['updated_at']
|
||||||
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at)))
|
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
|
||||||
else:
|
else:
|
||||||
logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.")
|
logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.")
|
||||||
server_uptime = 'N/A'
|
server_uptime = 'N/A'
|
||||||
@@ -791,11 +757,12 @@ def build_server_notify_text(state=None):
|
|||||||
on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT
|
on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT
|
||||||
script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT
|
script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT
|
||||||
|
|
||||||
available_params = {'server_name': server_name,
|
available_params = {# Global paramaters
|
||||||
|
'server_name': server_name,
|
||||||
'server_uptime': server_uptime,
|
'server_uptime': server_uptime,
|
||||||
'action': state,
|
'action': state.title(),
|
||||||
'datestamp': arrow.now().format(plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')),
|
'datestamp': arrow.now().format(date_format),
|
||||||
'timestamp': arrow.now().format(plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz',''))}
|
'timestamp': arrow.now().format(time_format)}
|
||||||
|
|
||||||
# Default text
|
# Default text
|
||||||
subject_text = 'PlexPy (%s)' % server_name
|
subject_text = 'PlexPy (%s)' % server_name
|
||||||
|
@@ -1777,7 +1777,7 @@ class SLACK(object):
|
|||||||
class Scripts(object):
|
class Scripts(object):
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.script_exts = ('.bat', '.cmd', '.exe', '.php', '.pl', '.py', '.pyw', '.rb', '.sh')
|
self.script_exts = ('.bat', '.cmd', '.exe', '.php', '.pl', '.ps1', '.py', '.pyw', '.rb', '.sh')
|
||||||
|
|
||||||
def conf(self, options):
|
def conf(self, options):
|
||||||
return cherrypy.config['config'].get('Scripts', options)
|
return cherrypy.config['config'].get('Scripts', options)
|
||||||
@@ -1807,7 +1807,7 @@ class Scripts(object):
|
|||||||
|
|
||||||
return scripts
|
return scripts
|
||||||
|
|
||||||
def notify(self, subject='', message='', notify_action='', script_args=[], *args, **kwargs):
|
def notify(self, subject='', message='', notify_action='', script_args=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
subject(string, optional): Head text,
|
subject(string, optional): Head text,
|
||||||
@@ -1818,6 +1818,9 @@ class Scripts(object):
|
|||||||
logger.debug(u"PlexPy Notifiers :: Trying to run notify script, action: %s, arguments: %s" %
|
logger.debug(u"PlexPy Notifiers :: Trying to run notify script, action: %s, arguments: %s" %
|
||||||
(notify_action if notify_action else None, script_args if script_args else None))
|
(notify_action if notify_action else None, script_args if script_args else None))
|
||||||
|
|
||||||
|
if script_args is None:
|
||||||
|
script_args = []
|
||||||
|
|
||||||
if not plexpy.CONFIG.SCRIPTS_FOLDER:
|
if not plexpy.CONFIG.SCRIPTS_FOLDER:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1869,14 +1872,16 @@ class Scripts(object):
|
|||||||
|
|
||||||
name, ext = os.path.splitext(script)
|
name, ext = os.path.splitext(script)
|
||||||
|
|
||||||
if ext == '.py':
|
if ext == '.php':
|
||||||
prefix = 'python'
|
|
||||||
elif ext == '.pyw':
|
|
||||||
prefix = 'pythonw'
|
|
||||||
elif ext == '.php':
|
|
||||||
prefix = 'php'
|
prefix = 'php'
|
||||||
elif ext == '.pl':
|
elif ext == '.pl':
|
||||||
prefix = 'perl'
|
prefix = 'perl'
|
||||||
|
elif ext == '.ps1':
|
||||||
|
prefix = 'powershell -executionPolicy bypass -file'
|
||||||
|
elif ext == '.py':
|
||||||
|
prefix = 'python'
|
||||||
|
elif ext == '.pyw':
|
||||||
|
prefix = 'pythonw'
|
||||||
elif ext == '.rb':
|
elif ext == '.rb':
|
||||||
prefix = 'ruby'
|
prefix = 'ruby'
|
||||||
else:
|
else:
|
||||||
@@ -1886,7 +1891,7 @@ class Scripts(object):
|
|||||||
script = script.encode(plexpy.SYS_ENCODING, 'ignore')
|
script = script.encode(plexpy.SYS_ENCODING, 'ignore')
|
||||||
|
|
||||||
if prefix:
|
if prefix:
|
||||||
script = [prefix, script]
|
script = prefix.split() + [script]
|
||||||
else:
|
else:
|
||||||
script = [script]
|
script = [script]
|
||||||
|
|
||||||
|
@@ -383,7 +383,6 @@ class PlexTV(object):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
plextv_resources = self.get_plextv_resources(include_https=include_https)
|
plextv_resources = self.get_plextv_resources(include_https=include_https)
|
||||||
server_urls = []
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
xml_parse = minidom.parseString(plextv_resources)
|
xml_parse = minidom.parseString(plextv_resources)
|
||||||
@@ -400,36 +399,51 @@ class PlexTV(object):
|
|||||||
logger.warn(u"PlexPy PlexTV :: Unable to parse XML for get_server_urls: %s." % e)
|
logger.warn(u"PlexPy PlexTV :: Unable to parse XML for get_server_urls: %s." % e)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
# Function to get all connections for a device
|
||||||
|
def get_connections(device):
|
||||||
|
conn = []
|
||||||
|
connections = device.getElementsByTagName('Connection')
|
||||||
|
|
||||||
|
for c in connections:
|
||||||
|
server_details = {"protocol": helpers.get_xml_attr(c, 'protocol'),
|
||||||
|
"address": helpers.get_xml_attr(c, 'address'),
|
||||||
|
"port": helpers.get_xml_attr(c, 'port'),
|
||||||
|
"uri": helpers.get_xml_attr(c, 'uri'),
|
||||||
|
"local": helpers.get_xml_attr(c, 'local')
|
||||||
|
}
|
||||||
|
conn.append(server_details)
|
||||||
|
|
||||||
|
return conn
|
||||||
|
|
||||||
|
server_urls = []
|
||||||
|
|
||||||
|
# Try to match the device
|
||||||
for a in xml_head:
|
for a in xml_head:
|
||||||
if helpers.get_xml_attr(a, 'clientIdentifier') == server_id:
|
if helpers.get_xml_attr(a, 'clientIdentifier') == server_id:
|
||||||
connections = a.getElementsByTagName('Connection')
|
server_urls = get_connections(a)
|
||||||
for connection in connections:
|
break
|
||||||
server_details = {"protocol": helpers.get_xml_attr(connection, 'protocol'),
|
|
||||||
"address": helpers.get_xml_attr(connection, 'address'),
|
|
||||||
"port": helpers.get_xml_attr(connection, 'port'),
|
|
||||||
"uri": helpers.get_xml_attr(connection, 'uri'),
|
|
||||||
"local": helpers.get_xml_attr(connection, 'local')
|
|
||||||
}
|
|
||||||
|
|
||||||
server_urls.append(server_details)
|
# Else no device match found
|
||||||
# Else try to match the PMS_IP and PMS_PORT
|
if not server_urls:
|
||||||
else:
|
# Try to match the PMS_IP and PMS_PORT
|
||||||
connections = a.getElementsByTagName('Connection')
|
for a in xml_head:
|
||||||
for connection in connections:
|
if helpers.get_xml_attr(a, 'provides') == 'server':
|
||||||
if helpers.get_xml_attr(connection, 'address') == plexpy.CONFIG.PMS_IP and \
|
connections = a.getElementsByTagName('Connection')
|
||||||
int(helpers.get_xml_attr(connection, 'port')) == plexpy.CONFIG.PMS_PORT:
|
|
||||||
|
|
||||||
plexpy.CONFIG.PMS_IDENTIFIER = helpers.get_xml_attr(a, 'clientIdentifier')
|
for connection in connections:
|
||||||
|
if helpers.get_xml_attr(connection, 'address') == plexpy.CONFIG.PMS_IP and \
|
||||||
|
int(helpers.get_xml_attr(connection, 'port')) == plexpy.CONFIG.PMS_PORT:
|
||||||
|
|
||||||
logger.info(u"PlexPy PlexTV :: PMS identifier changed from %s to %s." % \
|
plexpy.CONFIG.PMS_IDENTIFIER = helpers.get_xml_attr(a, 'clientIdentifier')
|
||||||
(server_id, plexpy.CONFIG.PMS_IDENTIFIER))
|
plexpy.CONFIG.write()
|
||||||
|
|
||||||
server_details = {"protocol": helpers.get_xml_attr(connection, 'protocol'),
|
logger.info(u"PlexPy PlexTV :: PMS identifier changed from %s to %s." % \
|
||||||
"address": helpers.get_xml_attr(connection, 'address'),
|
(server_id, plexpy.CONFIG.PMS_IDENTIFIER))
|
||||||
"port": helpers.get_xml_attr(connection, 'port'),
|
|
||||||
"uri": helpers.get_xml_attr(connection, 'uri'),
|
server_urls = get_connections(a)
|
||||||
"local": helpers.get_xml_attr(connection, 'local')
|
break
|
||||||
}
|
|
||||||
|
if server_urls:
|
||||||
break
|
break
|
||||||
|
|
||||||
return server_urls
|
return server_urls
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
PLEXPY_VERSION = "master"
|
PLEXPY_VERSION = "master"
|
||||||
PLEXPY_RELEASE_VERSION = "1.3.4"
|
PLEXPY_RELEASE_VERSION = "1.3.6"
|
||||||
|
@@ -492,7 +492,20 @@ class WebInterface(object):
|
|||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
return json.dumps({'message': 'no data received'})
|
return json.dumps({'message': 'no data received'})
|
||||||
else:
|
else:
|
||||||
return json.dumps({'message': 'Cannot refresh library while getting file sizes.'})
|
return json.dumps({'message': 'Cannot refresh library while getting file sizes.'})
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
def delete_duplicate_libraries(self):
|
||||||
|
library_data = libraries.Libraries()
|
||||||
|
|
||||||
|
result = library_data.delete_duplicate_libraries()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
return json.dumps({'message': result})
|
||||||
|
else:
|
||||||
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
return json.dumps({'message': 'Unable to delete duplicate libraries from the database.'})
|
||||||
|
|
||||||
##### Users #####
|
##### Users #####
|
||||||
|
|
||||||
@@ -1382,7 +1395,11 @@ class WebInterface(object):
|
|||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, **kwargs):
|
def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, **kwargs):
|
||||||
if not identifier:
|
from plexpy import http_handler
|
||||||
|
|
||||||
|
# Attempt to get the pms_identifier from plex.tv if the server is published
|
||||||
|
# Works for all PMS SSL settings
|
||||||
|
if not identifier and hostname and port:
|
||||||
plex_tv = plextv.PlexTV()
|
plex_tv = plextv.PlexTV()
|
||||||
servers = plex_tv.discover()
|
servers = plex_tv.discover()
|
||||||
|
|
||||||
@@ -1391,27 +1408,28 @@ class WebInterface(object):
|
|||||||
identifier = server['clientIdentifier']
|
identifier = server['clientIdentifier']
|
||||||
break
|
break
|
||||||
|
|
||||||
if identifier and hostname and port:
|
# Fallback to checking /identity endpoint is server is unpublished
|
||||||
# Set PMS attributes to get the real PMS url
|
# Cannot set SSL settings on the PMS if unpublished so 'http' is okay
|
||||||
plexpy.CONFIG.__setattr__('PMS_IP', hostname)
|
if not identifier:
|
||||||
plexpy.CONFIG.__setattr__('PMS_PORT', port)
|
request_handler = http_handler.HTTPHandler(host=hostname,
|
||||||
plexpy.CONFIG.__setattr__('PMS_IDENTIFIER', identifier)
|
port=port,
|
||||||
plexpy.CONFIG.__setattr__('PMS_SSL', ssl)
|
token=None)
|
||||||
plexpy.CONFIG.__setattr__('PMS_IS_REMOTE', remote)
|
uri = '/identity'
|
||||||
plexpy.CONFIG.write()
|
request = request_handler.make_request(uri=uri,
|
||||||
|
proto='http',
|
||||||
|
request_type='GET',
|
||||||
|
output_format='xml',
|
||||||
|
no_token=True,
|
||||||
|
timeout=10)
|
||||||
|
if request:
|
||||||
|
xml_head = request.getElementsByTagName('MediaContainer')[0]
|
||||||
|
identifier = xml_head.getAttribute('machineIdentifier')
|
||||||
|
|
||||||
plextv.get_real_pms_url()
|
if identifier:
|
||||||
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
pms_connect = pmsconnect.PmsConnect()
|
return json.dumps(identifier)
|
||||||
request = pms_connect.get_local_server_identity()
|
|
||||||
|
|
||||||
if request:
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/xml'
|
|
||||||
return request
|
|
||||||
else:
|
|
||||||
logger.warn(u"Unable to retrieve data for get_server_id.")
|
|
||||||
return None
|
|
||||||
else:
|
else:
|
||||||
|
logger.warn('Unable to retrieve the PMS identifier.')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
|
Reference in New Issue
Block a user