Compare commits

...

73 Commits

Author SHA1 Message Date
JonnyWong16
5417747473 v1.4.24 2017-10-01 11:13:16 -07:00
JonnyWong16
cf6847d777 Fallback to product if player title is blank 2017-10-01 11:08:52 -07:00
JonnyWong16
464fa1f8a3 Add no forking option to startup arguments 2017-10-01 11:08:40 -07:00
JonnyWong16
665a6435ef Merge pull request #1076 from Vashypooh/master
Added support for windows service
2017-10-01 10:49:49 -07:00
JonnyWong16
2d64ba4a0e Merge pull request #1107 from Joshua1337/master
Update web 3.20.5 urls
2017-10-01 10:43:58 -07:00
JonnyWong16
006c778dca v1.4.23 2017-09-30 15:28:32 -07:00
JonnyWong16
bd636b756b Update PushBullet authorization header 2017-09-30 15:03:51 -07:00
JonnyWong16
d21b74f231 Fix regression for PlexWatch and Plexivity import 2017-09-30 15:01:10 -07:00
JonnyWong16
4354f72578 Update platform name override for Playstation 4 2017-09-30 14:53:24 -07:00
JonnyWong16
f5ca522e6c Update coin addresses 2017-09-30 14:44:14 -07:00
Joshua Dehler
8a556a50ab Fix web 3.20.5 urls 2017-09-08 20:13:45 +02:00
Joshua Dehler
532ff59dfe Fix web 3.20.5 urls 2017-09-08 20:13:07 +02:00
JonnyWong16
ec685407bb v1.4.22 2017-08-19 20:50:28 -07:00
JonnyWong16
be9a1dcf06 Temporary fix for incorrect source media info 2017-08-19 20:48:01 -07:00
JonnyWong16
1f7e8b4d9a Fix removing old config backups 2017-08-19 20:29:49 -07:00
JonnyWong16
b3da08ce74 Merge pull request #1084 from mttlmy/dev
Added platform 'Linux' to link to image linux.png
2017-08-07 16:32:12 -07:00
mttlmy
50753db4ff Added platform 'Linux' to link to image linux.png 2017-08-07 19:27:46 -04:00
Vashypooh
b3fe6145e2 Added support for windows service
Added support for running as a windows service so it does not fork the process on reboot.
2017-07-22 16:02:42 -04:00
JonnyWong16
af77e51307 v1.4.21 2017-07-01 16:45:44 -07:00
JonnyWong16
b4e8689e92 Fix qrcode javascript 2017-07-01 16:45:37 -07:00
JonnyWong16
8ec30a77ff Update donation methods 2017-07-01 16:35:12 -07:00
JonnyWong16
29c7853380 v1.4.20 2017-06-24 21:41:24 -07:00
JonnyWong16
cd417aaf44 Support custom port for Mattermost (Slack) notifications 2017-06-24 21:34:53 -07:00
JonnyWong16
428a5cc0ff Udate file sizes when refreshing media info 2017-06-24 21:34:22 -07:00
JonnyWong16
d128d7c8e6 Sort 4k properly in media info table 2017-06-24 21:17:26 -07:00
JonnyWong16
8027199bd5 Merge pull request #1021 from senepa/senepa-patch-1
Fixed math used to calculate human_duration
2017-06-24 21:07:35 -07:00
JonnyWong16
099a887cc7 Add PlexTogether platform image 2017-06-24 21:06:40 -07:00
JonnyWong16
ea41d06023 v1.4.19 2017-05-31 19:55:42 -07:00
JonnyWong16
5147baab05 Temporary fix for current activity resolution on PMS 1.7.x 2017-05-31 19:50:44 -07:00
o
1c50e615cf Fixing math used to calculate human_duration
There are 86400 seconds in a day
2017-04-22 19:27:31 -04:00
JonnyWong16
ed2d34e979 v1.4.18 2017-04-22 15:57:43 -07:00
JonnyWong16
c404016700 Merge pull request #1020 from senepa/dev
A correction and additional Arnold quotes
2017-04-22 15:44:26 -07:00
o
14b0353ba4 A correction and additional Arnold quotes
Correction:
True Lies (1994)
'Can you hurry up. My horse is getting tired.' to 'Make it quick because my horse is getting tired.'
http://www.imdb.com/title/tt0111503/quotes?item=qt0408883

Additions:
Last Action Hero (1993)
'Well, listen to this one: Rubber baby buggy bumpers!'
http://www.imdb.com/title/tt0107362/quotes?item=qt1196411

Kindergarten Cop (1990)
'Take your toy back to the carpet!'
http://www.imdb.com/title/tt0099938/quotes?item=qt0460013

Kindergarten Cop (1990)
'My name is John Kimble... And I love my car.'
http://www.imdb.com/title/tt0099938/quotes?item=qt0460001

Commando (1985)
'I eat Green Berets for breakfast.'
http://www.imdb.com/title/tt0088944/quotes?item=qt0402329

Jingle All the Way (1996)
'Put that cookie down. NOW!'
http://www.imdb.com/title/tt0116705/quotes?item=qt0266218
2017-04-22 18:40:21 -04:00
JonnyWong16
fbe136a350 Fix datatables footer text wrapping 2017-04-22 13:43:45 -07:00
JonnyWong16
4fcfea943e Add badges to readme 2017-04-22 13:35:22 -07:00
JonnyWong16
279d27d081 Update PayPal donation link 2017-04-22 13:32:36 -07:00
JonnyWong16
651125ef2c Merge pull request #999 from Hellowlol/patch-1
Add missing CONFIG for API get_apikey
2017-03-21 08:12:38 -07:00
Hellowlol
a5eb0e7faa Add missing CONFIG 2017-03-21 15:35:03 +01:00
JonnyWong16
e85cdd5609 v1.4.17 2017-03-04 15:55:45 -08:00
JonnyWong16
83f80b9288 Fix month capitalization 2017-03-04 15:49:36 -08:00
JonnyWong16
76fe771d8c Update init scripts 2017-03-04 15:42:03 -08:00
JonnyWong16
c6ad09fe8f Cleanup graphs PRs 2017-03-04 15:33:45 -08:00
JonnyWong16
a3014638c8 Merge pull request #953 from ampsonic/patch-1
Update init.ubuntu.systemd
2017-03-04 15:19:00 -08:00
JonnyWong16
8b15c63b0d Merge pull request #982 from Pbaboe/StartDayofWeek
Start day of the week changeable
2017-03-04 15:02:57 -08:00
JonnyWong16
589adf4df9 Merge pull request #980 from Pbaboe/MonthlyGraphs
Change amount of months in Graphs > Play Totals > Plays by Month
2017-03-04 15:00:24 -08:00
JonnyWong16
b07d85f233 Merge pull request #975 from demonbane/dev
Fix 404s for icon files and invalid paths
2017-03-04 14:53:23 -08:00
JonnyWong16
f0e5855a8e Update bitcoin donation link 2017-03-04 14:47:30 -08:00
JonnyWong16
5997aa5cd9 Fix Plex Web 3.0 urls 2017-03-04 14:47:22 -08:00
Peter Kums
cb2a38addc Start day of the week changeable
Option in general settings to change the start day of the week to Monday (instead of the default of Sunday). This is only relevant for the Graph : Play by day of week.

Feathub issue : http://feathub.com/JonnyWong16/plexpy/+15
2017-02-27 19:38:12 +01:00
Peter Kums
873f857c82 Store months for Play by Months graph
Store the selected amount of months in the Graphs > Play Totals > Plays by Months graph in the config.ini file.
2017-02-27 00:17:56 +01:00
Peter Kums
b9a22461c1 Change months in Plays by Month graph
Add an option on the Graphs > Play Totals > Plays by Month page to change the amount of months the graph shows data for.
2017-02-26 23:53:24 +01:00
Alex Malinovich
85a02771a7 Fix 404s for icon files and invalid paths 2017-02-21 01:44:12 -08:00
ampsonic
6a0b0327c3 Update init.ubuntu.systemd
Updated the instructions to more accurately reflect required steps. Removed outdated information and added information on creating the plexpy user. 

Only the comments of the file changed not actual configuration.
2017-01-28 16:42:50 -08:00
JonnyWong16
3742f33d08 v1.4.16 2016-11-25 18:47:22 -08:00
JonnyWong16
82ac33dd75 Fix websocket for new json response on PMS 1.3.0 2016-11-25 18:42:23 -08:00
JonnyWong16
8c7c0101cd Dynamically update stream and transcoder tooltip percent 2016-11-14 21:10:48 -08:00
JonnyWong16
4d6179dfdd Fix typo 2016-11-14 20:49:01 -08:00
JonnyWong16
ef85fba2e5 v1.4.15 2016-11-11 13:08:13 -08:00
JonnyWong16
960b601384 Remove error cmd from API 2016-11-11 12:59:35 -08:00
JonnyWong16
21d9091b43 Merge pull request #898 from Hellowlol/ap_fix
fix result type, fixup responses from the ui to api
2016-11-11 12:48:18 -08:00
Hellowlol
e881c32797 Update md add exception traceback to the browser 2016-11-05 22:24:37 +01:00
JonnyWong16
57f1af05f5 Redirect to proper HTTP root on state change 2016-11-05 12:13:53 -07:00
JonnyWong16
254f41a2cc Fix line breaks in info page summaries 2016-11-05 11:50:41 -07:00
JonnyWong16
59ce3404c9 Fix Plex.tv authentication with special characters 2016-11-05 11:48:53 -07:00
Hellowlol
11c7342299 fix result type, fixup responses from the ui to api
todo unify json responses from webserve.py
2016-11-04 00:52:01 +01:00
JonnyWong16
8b0959aa69 Fix API SQL command 2016-10-28 20:35:39 -07:00
JonnyWong16
9cd8ed12b9 Add percent to stream and transcoder progress tooltip 2016-10-28 20:34:54 -07:00
JonnyWong16
78b10d7ab5 Use HTTPS for app.plex.tv 2016-10-21 22:45:17 -07:00
JonnyWong16
a322ec2b23 Allow refreshing images if not using sessions 2016-10-16 13:08:36 -07:00
JonnyWong16
1f23654735 Merge pull request #870 from XusBadia/patch-2
Incremented table width to 100%
2016-10-13 21:52:49 -07:00
Xus Badia
e1112b95c7 Changed max-width to accommodate wider displays 2016-10-14 06:11:50 +02:00
JonnyWong16
4e043109bf Temporary fix for info pages with missing metadata
* Will be fixed properly in v2
2016-10-13 20:42:11 -07:00
Xus Badia
58e670443d Incremented table width to 100%
Table width (header and body/back) incremented to 100% but adding max-width so it isn't too wide in wide-screen monitors.
2016-10-13 11:56:02 +02:00
49 changed files with 508 additions and 353 deletions

28
API.md
View File

@@ -37,11 +37,11 @@ Get to the chopper!
### backup_config
Create a manual backup of the `config.ini` file.
Create a manual backup of the `config.ini` file.
### backup_db
Create a manual backup of the `plexpy.db` file.
Create a manual backup of the `plexpy.db` file.
### delete_all_library_history
@@ -142,6 +142,10 @@ Returns:
```
### delete_temp_sessions
Flush out all of the temporary sessions in the database.
### delete_user
Delete a user from PlexPy. Also erases all history for the user.
@@ -158,11 +162,11 @@ Returns:
### docs
Return the api docs as a dict where commands are keys, docstring are value.
Return the api docs as a dict where commands are keys, docstring are value.
### docs_md
Return the api docs formatted with markdown.
Return the api docs formatted with markdown.
### download_log
@@ -755,11 +759,11 @@ Optional parameters:
Returns:
json:
[{"loglevel": "DEBUG",
"msg": "Latest version is 2d10b0748c7fa2ee4cf59960c3d3fffc6aa9512b",
"thread": "MainThread",
[{"loglevel": "DEBUG",
"msg": "Latest version is 2d10b0748c7fa2ee4cf59960c3d3fffc6aa9512b",
"thread": "MainThread",
"time": "2016-05-08 09:36:51 "
},
},
{...},
{...}
]
@@ -1814,15 +1818,15 @@ Returns:
### refresh_libraries_list
Refresh the PlexPy libraries list.
Refresh the PlexPy libraries list.
### refresh_users_list
Refresh the PlexPy users list.
Refresh the PlexPy users list.
### restart
Restart PlexPy.
Restart PlexPy.
### search
@@ -1905,7 +1909,7 @@ Uninstalls the GeoLite2 database
### update
Check for PlexPy updates on Github.
Check for PlexPy updates on Github.
### update_metadata_details

View File

@@ -1,5 +1,81 @@
# Changelog
## v1.4.24 (2017-10-01)
* Fix: New Plex Web urls. (Thanks @Joshua1337)
* Fix: Fallback to the product name if the player title is blank.
* New: Added no forking option to startup arguments. (Thanks @Vashypooh)
## v1.4.23 (2017-09-30)
* Fix: Playstation 4 platform name.
* Fix: PlexWatch and Plexivity import.
* Fix: Pushbullet authorization header.
## v1.4.22 (2017-08-19)
* Fix: Cleaning up of old config backups.
* Fix: Temporary fix for incorrect source media info.
## v1.4.21 (2017-07-01)
* New: Updated donation methods.
## v1.4.20 (2017-06-24)
* New: Added platform image for the PlexTogether player.
* Fix: Corrected math used to calculate human duration. (Thanks @senepa)
* Fix: Sorting of 4k in media info tables.
* Fix: Update file sizes when refreshing media info tables.
* Fix: Support a custom port for Mattermost (Slack) notifications.
## v1.4.19 (2017-05-31)
* Fix: Video resolution not showing up for transcoded streams on PMS 1.7.x.
## v1.4.18 (2017-04-22)
* New: Added some new Arnold quotes. (Thanks @senepa)
* Fix: Text wrapping in datatable footers.
* Fix: API command get_apikey. (Thanks @Hellowlol)
## v1.4.17 (2017-03-04)
* New: Configurable month range for the Plays by month graph. (Thanks @Pbaboe)
* New: Option to chanage the week to start on Monday for the the Plays by day of week graph. (Thanks @Pbaboe)
* Fix: Invalid iOS icon file paths. (Thanks @demonbane)
* Fix: Plex Web 3.0 URLs on info pages and notifications.
* Fix: Update bitcoin donation link to Coinbase.
* Fix: Update init scripts. (Thanks @ampsonic)
## v1.4.16 (2016-11-25)
* Fix: Websocket for new json response on PMS 1.3.0.
* Fix: Update stream and transcoder tooltip percent.
* Fix: Typo in the edit user modal.
## v1.4.15 (2016-11-11)
* New: Add stream and transcoder progress percent to the current activity tooltip.
* Fix: Refreshing of images in the cache when authentication is disabled.
* Fix: Plex.tv authentication with special characters in the username or password.
* Fix: Line breaks in the info page summaries.
* Fix: Redirect to the proper http root when restarting.
* Fix: API result type and responses showing incorrectly. (Thanks @Hellowlol)
* Change: Use https URL for app.plex.tv.
* Change: Show API traceback errors in the browser with debugging enabled. (Thanks @Hellowlol)
* Change: Increase table width on mobile devices and max width set to 1750px. (Thanks @XusBadia)
## v1.4.14 (2016-10-12)
* Fix: History logging locking up if media is removed from Plex before PlexPy can save the session.

View File

@@ -92,7 +92,9 @@ def main():
'--nolaunch', action='store_true', help='Prevent browser from launching on startup')
parser.add_argument(
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
parser.add_argument(
'--nofork', action='store_true', help='Start PlexPy as a service, do not fork when restarting')
args = parser.parse_args()
if args.verbose:
@@ -116,6 +118,10 @@ def main():
plexpy.DAEMON = True
plexpy.QUIET = True
if args.nofork:
plexpy.NOFORK = True
logger.info("PlexPy is running as a service, it will not fork when restarted.")
if args.pidfile:
plexpy.PIDFILE = str(args.pidfile)

View File

@@ -1,15 +1,13 @@
# PlexPy
[![Join the chat at https://gitter.im/plexpy/general](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/plexpy/general?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Discord](https://img.shields.io/badge/Discord-PlexPy-738bd7.svg?style=flat-square)](https://discord.gg/36ggawe)
[![Gitter](https://img.shields.io/badge/Gitter-PlexPy-ed1965.svg?style=flat-square)](https://gitter.im/plexpy/general)
[![Plex Forums](https://img.shields.io/badge/Plex%20Forums-PlexPy-E5A00D.svg?style=flat-square)](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program)
A python based web application for monitoring, analytics and notifications for [Plex Media Server](https://plex.tv).
This project is based on code from [Headphones](https://github.com/rembo10/headphones) and [PlexWatchWeb](https://github.com/ecleese/plexWatchWeb).
* [Plex forum thread](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program)
* [Gitter chat](https://gitter.im/plexpy/general)
* [/r/Plex Discord server](https://discord.gg/011TFFWSuNFI02EKr) | [PlexPy Discord server](https://discord.gg/36ggawe)
## Features
* Responsive web design viewable on desktop, tablet and mobile web browsers.

View File

@@ -66,67 +66,67 @@
<!-- STARTUP IMAGES -->
<!-- iPad retina portrait startup image -->
<link href="${http_root}images/res/screen/ios/Default-Portrait@2x~ipad.png"
<link href="${http_root}images/res/ios/Default-Portrait@2x~ipad.png"
media="(device-width: 768px) and (device-height: 1024px)
and (-webkit-device-pixel-ratio: 2)
and (orientation: portrait)"
rel="apple-touch-startup-image">
<!-- iPad retina landscape startup image -->
<link href="${http_root}images/res/screen/ios/Default-Landscape@2x~ipad.png"
<link href="${http_root}images/res/ios/Default-Landscape@2x~ipad.png"
media="(device-width: 768px) and (device-height: 1024px)
and (-webkit-device-pixel-ratio: 2)
and (orientation: landscape)"
rel="apple-touch-startup-image">
<!-- iPad non-retina portrait startup image -->
<link href="${http_root}images/res/screen/ios/Default-Portrait~ipad.png"
<link href="${http_root}images/res/ios/Default-Portrait~ipad.png"
media="(device-width: 768px) and (device-height: 1024px)
and (-webkit-device-pixel-ratio: 1)
and (orientation: portrait)"
rel="apple-touch-startup-image">
<!-- iPad non-retina landscape startup image -->
<link href="${http_root}images/res/screen/ios/Default-Landscape~ipad.png"
<link href="${http_root}images/res/ios/Default-Landscape~ipad.png"
media="(device-width: 768px) and (device-height: 1024px)
and (-webkit-device-pixel-ratio: 1)
and (orientation: landscape)"
rel="apple-touch-startup-image">
<!-- iPhone 6 Plus portrait startup image -->
<link href="${http_root}images/res/screen/ios/Default-736h.png"
<link href="${http_root}images/res/ios/Default-736h.png"
media="(device-width: 414px) and (device-height: 736px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: portrait)"
rel="apple-touch-startup-image">
<!-- iPhone 6 Plus landscape startup image -->
<link href="${http_root}images/res/screen/ios/Default-Landscape-736h.png"
<link href="${http_root}images/res/ios/Default-Landscape-736h.png"
media="(device-width: 414px) and (device-height: 736px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: landscape)"
rel="apple-touch-startup-image">
<!-- iPhone 6 startup image -->
<link href="${http_root}images/res/screen/ios/Default-667h.png"
<link href="${http_root}images/res/ios/Default-667h.png"
media="(device-width: 375px) and (device-height: 667px)
and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image">
<!-- iPhone 5 startup image -->
<link href="${http_root}images/res/screen/ios/Default-568h@2x~iphone5.jpg"
<link href="${http_root}images/res/ios/Default-568h@2x~iphone5.jpg"
media="(device-width: 320px) and (device-height: 568px)
and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image">
<!-- iPhone < 5 retina startup image -->
<link href="${http_root}images/res/screen/ios/Default@2x~iphone.png"
<link href="${http_root}images/res/ios/Default@2x~iphone.png"
media="(device-width: 320px) and (device-height: 480px)
and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image">
<!-- iPhone < 5 non-retina startup image -->
<link href="${http_root}images/res/screen/ios/Default~iphone.png"
<link href="${http_root}images/res/ios/Default~iphone.png"
media="(device-width: 320px) and (device-height: 480px)
and (-webkit-device-pixel-ratio: 1)"
rel="apple-touch-startup-image">
@@ -223,8 +223,7 @@
<li><a href="${anon_url('https://github.com/%s/plexpy/wiki/Frequently-Asked-Questions-(FAQ)' % plexpy.CONFIG.GIT_USER)}" target="_blank"><i class="fa fa-fw fa-question-circle"></i> FAQ</a></li>
<li><a href="settings?support=true"><i class="fa fa-fw fa-comment"></i> Support</a></li>
<li role="separator" class="divider"></li>
<li><a href="${anon_url('https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DG783BMSCU3V4')}" target="_blank"><i class="fa fa-fw fa-paypal"></i> Paypal</a></li>
<li><a href="${anon_url('http://swiftpanda16.tip.me/')}" target="_blank"><i class="fa fa-fw fa-btc"></i> Bitcoin</a></li>
<li><a href="#" data-target="#donate-modal" data-toggle="modal"><i class="fa fa-fw fa-heart"></i> Donate</a></li>
<li role="separator" class="divider"></li>
% if plexpy.CONFIG.CHECK_GITHUB:
<li><a href="#" id="nav-update"><i class="fa fa-fw fa-arrow-circle-up"></i> Check for Updates</a></li>
@@ -305,6 +304,64 @@
</div>
</div>
</div>
% else:
<div id="donate-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="crypto-donate-modal">
<div class="modal-dialog" role="document">
<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">PlexPy Donation</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12" style="text-align: center;">
<h4>
<strong>Thank you for supporting PlexPy!</strong>
</h4>
<p>
Please select a donation method.
</p>
</div>
</div>
<ul id="donation_type" class="nav nav-pills" role="tablist" style="display: flex; justify-content: center; margin: 10px 0;">
<li class="active"><a href="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li>
<li><a href="#flattr-donation" role="tab" data-toggle="tab">Flattr</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoin" data-name="Bitcoin" data-address="3FdfJAyNWU15Sf11U9FTgPHuP1hPz32eEN">Bitcoin</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoincash" data-name="Bitcoin Cash" data-address="1H2atabxAQGaFAWYQEiLkXKSnK9CZZvt2n">Bitcoin Cash</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="ethereum" data-name="Ethereum" data-address="0x77ae4c2b8de1a1ccfa93553db39971da58c873d3">Ethereum</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="litecoin" data-name="Litecoin" data-address="LWpPmUqQYHBhMV83XSCsHzPmKLhJt6r57J">Litecoin</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="paypal-donation" style="text-align: center">
<p>
Click the button below to continue to PayPal.
</p>
<a href="${anon_url('https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=6XPPKTDSX9QFL&lc=US&item_name=PlexPy&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted')}" target="_blank">
<img src="images/gold-rect-paypal-34px.png" alt="PayPal">
</a>
</div>
<div role="tabpanel" class="tab-pane" id="flattr-donation" style="text-align: center">
<p>
Click the button below to continue to Flattr.
</p>
<a href="${anon_url('https://flattr.com/submit/auto?user_id=JonnyWong16&url=https://github.com/JonnyWong16/plexpy&title=PlexPy&language=en_GB&tags=github&category=software')}" target="_blank">
<img src="images/flattr-badge-large.png" alt="Flattr">
</a>
</div>
<div role="tabpanel" class="tab-pane" id="crypto-donation">
<label>QR Code</label>
<pre id="crypto_qr_code" style="text-align: center"></pre>
<label><span id="crypto_type_label"></span> Address</label>
<pre id="crypto_address" style="text-align: center"></pre>
</div>
</div>
</div>
<div class="modal-footer">
<input type="button" class="btn btn-bright" data-dismiss="modal" value="Close">
</div>
</div>
</div>
</div>
% endif
${next.headerIncludes()}
@@ -317,8 +374,10 @@ ${next.headerIncludes()}
<script src="${http_root}js/bootstrap-hover-dropdown.min.js"></script>
<script src="${http_root}js/pnotify.custom.min.js"></script>
<script src="${http_root}js/script.js"></script>
<script src="${http_root}js/jquery.qrcode.min.js"></script>
% if _session['user_group'] == 'admin' and plexpy.CONFIG.BROWSER_ENABLED:
<script src="${http_root}js/ajaxNotifications.js"></script>
% else:
% endif
<script>
% if _session['user_group'] == 'admin':
@@ -354,6 +413,17 @@ ${next.headerIncludes()}
$(this).html('<i class="fa fa-spin fa-refresh"></i> Checking');
window.location.href = "checkGithub";
});
$('#donation_type a.crypto-donation').on('shown.bs.tab', function () {
var crypto_coin = $(this).data('coin');
var crypto_name = $(this).data('name');
var crypto_address = $(this).data('address')
$('#crypto_qr_code').empty().qrcode({
text: crypto_coin + ":" + crypto_address
});
$('#crypto_type_label').html(crypto_name);
$('#crypto_address').html(crypto_address);
});
% endif
$('.dropdown-toggle').click(function (e) {

View File

@@ -2251,7 +2251,8 @@ a .home-platforms-list-cover-face:hover
margin-right: auto;
margin-top: 20px;
margin-bottom: 20px;
width: 90%;
width: 100%;
max-width: 1750px;
overflow: hidden;
}
.table-card-header {
@@ -2263,7 +2264,8 @@ a .home-platforms-list-cover-face:hover
margin-right: auto;
margin-top: 20px;
margin-bottom: -20px;
width: 90%;
width: 100%;
max-width: 1750px;
overflow: hidden;
}
.table-card-back td {
@@ -2489,6 +2491,9 @@ a .home-platforms-list-cover-face:hover
.dataTables_paginate li {
margin: 0;
}
div.dataTables_info {
white-space: normal !important;
}
.tooltip.top .tooltip-arrow {
border-top-color: #fff;
}
@@ -2831,6 +2836,14 @@ div[id^='media_info_child'] div[id^='media_info_child'] div.dataTables_scrollHea
width: 75px;
height: 34px;
}
#months-selection label {
margin-bottom: 0;
}
#graph-months {
margin: 0;
width: 75px;
height: 34px;
}
.card-sortable {
height: 36px;
padding: 0 20px 0 0;
@@ -3064,4 +3077,4 @@ a:hover .overlay-refresh-image:hover {
}
#api_key.form-control[readonly]:focus {
background-color: #fff;
}
}

View File

@@ -207,8 +207,8 @@ DOCUMENTATION :: END
% endif
<div class="dashboard-activity-progress">
<div class="dashboard-activity-progress-bar">
<div id="bufferbar-${data['session_key']}" class="bufferbar" style="width: ${data['transcode_progress']}%" data-toggle="tooltip" title="Transcoder Progress">${data['transcode_progress']}%</div>
<div id="bar-${data['session_key']}" class="bar" style="width: ${data['progress_percent']}%" data-toggle="tooltip" title="Stream Progress">${data['progress_percent']}%</div>
<div id="bufferbar-${data['session_key']}" class="bufferbar" style="width: ${data['transcode_progress']}%" data-toggle="tooltip" title="Transcoder Progress ${data['transcode_progress']}%">${data['transcode_progress']}%</div>
<div id="bar-${data['session_key']}" class="bar" style="width: ${data['progress_percent']}%" data-toggle="tooltip" title="Stream Progress ${data['progress_percent']}%">${data['progress_percent']}%</div>
</div>
</div>
</div>

View File

@@ -45,7 +45,7 @@ DOCUMENTATION :: END
<input type="text" class="form-control" id="friendly_name" name="friendly_name" value="${data['friendly_name']}" size="30">
</div>
</div>
<p class="help-block">Replace all occurances of the username with this name.</p>
<p class="help-block">Replace all occurrences of the username with this name.</p>
</div>
<div class="form-group">
<label for="profile_url">Profile Picture URL</label>

View File

@@ -42,6 +42,11 @@
<input type="number" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" /> days
</label>
</div>
<div class="btn-group" id="months-selection">
<label>
<input type="number" name="graph-months" id="graph-months" value="${config['graph_months']}" min="1" /> months
</label>
</div>
</div>
</div>
<div class='table-card-back'>
@@ -226,7 +231,7 @@
% 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>
<h4><i class="fa fa-calendar"></i> Plays by month <small>Last <span class="months">12</span> months</small></h4>
<p class="help-block">
The combined total of tv, movies, and music by month.
</p>
@@ -317,10 +322,12 @@
// Initial values for graph from config
var yaxis = "${config['graph_type']}";
var current_range = ${config['graph_days']};
var current_day_range = ${config['graph_days']};
var current_month_range = ${config['graph_months']};
var current_tab = "${'#' + config['graph_tab']}";
$('.days').html(current_range);
$('.days').html(current_day_range);
$('.months').html(current_month_range);
// Load user ids and names (for the selector)
$.ajax({
@@ -352,6 +359,7 @@
function loadGraphsTab1(time_range, yaxis) {
$('#days-selection').show();
$('#months-selection').hide();
setGraphFormat(yaxis);
@@ -442,6 +450,7 @@
function loadGraphsTab2(time_range, yaxis) {
$('#days-selection').show();
$('#months-selection').hide();
setGraphFormat(yaxis);
@@ -525,15 +534,16 @@
});
}
function loadGraphsTab3(yaxis) {
function loadGraphsTab3(time_range, yaxis) {
$('#days-selection').hide();
$('#months-selection').show();
setGraphFormat(yaxis);
$.ajax({
url: "get_plays_per_month",
type: 'get',
data: { y_axis: yaxis, user_id: selected_user_id },
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
dataType: "json",
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
@@ -547,15 +557,15 @@
}
// Set initial state
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); }
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
// Tab1 opened
$('#graph-tabs a[href="#tabs-1"]').on('shown.bs.tab', function (e) {
e.preventDefault();
current_tab = $(this).attr('href');
loadGraphsTab1(current_range, yaxis);
loadGraphsTab1(current_day_range, yaxis);
$.ajax({
url: 'set_graph_config',
data: { graph_tab: current_tab.replace('#','') },
@@ -567,7 +577,7 @@
$('#graph-tabs a[href="#tabs-2"]').on('shown.bs.tab', function (e) {
e.preventDefault();
current_tab = $(this).attr('href');
loadGraphsTab2(current_range, yaxis);
loadGraphsTab2(current_day_range, yaxis);
$.ajax({
url: 'set_graph_config',
data: { graph_tab: current_tab.replace('#','') },
@@ -579,7 +589,7 @@
$('#graph-tabs a[href="#tabs-3"]').on('shown.bs.tab', function (e) {
e.preventDefault();
current_tab = $(this).attr('href');
loadGraphsTab3(yaxis);
loadGraphsTab3(current_month_range, yaxis);
$.ajax({
url: 'set_graph_config',
data: { graph_tab: current_tab.replace('#','') },
@@ -589,17 +599,35 @@
// Date range changed
$('#graph-days').on('change', function() {
current_range = $(this).val();
if (current_range < 1) {
current_day_range = Math.round($(this).val());
$(this).val(current_day_range);
if (current_day_range < 1) {
$(this).val(7);
current_range = 7;
current_day_range = 7;
}
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
$('.days').html(current_range);
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
$('.days').html(current_day_range);
$.ajax({
url: 'set_graph_config',
data: { graph_days: current_range},
data: { graph_days: current_day_range},
async: true
});
});
// Month range changed
$('#graph-months').on('change', function() {
current_month_range = Math.round($(this).val());
$(this).val(current_month_range);
if (current_month_range < 1) {
$(this).val(12);
current_month_range = 12;
}
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
$('.months').html(current_month_range);
$.ajax({
url: 'set_graph_config',
data: { graph_months: current_month_range},
async: true
});
});
@@ -607,17 +635,17 @@
// User changed
$('#graph-user').on('change', function() {
selected_user_id = $(this).val() || null;
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); }
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
});
// Y-axis changed
$('#yaxis-selection').on('change', function() {
yaxis = $('input[name=yaxis-options]:checked', '#yaxis-selection').val();
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); }
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
$.ajax({
url: 'set_graph_config',
data: { graph_type: yaxis},

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -204,8 +204,11 @@
// update the progress bars
// percent - 3 because of 3px padding-right
$('#bufferbar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%');
$('#bar-' + key).width(parseInt(s.progress_percent) - 3 + '%').html(s.progress_percent + '%');
$('#bufferbar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%')
.attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%');
$('#bar-' + key).width(parseInt(s.progress_percent) - 3 + '%').html(s.progress_percent + '%')
.attr('data-original-title', 'Stream Progress ' + s.progress_percent + '%');
// add temporary class so we know which instances are still active
instance.addClass('updated-temp');

View File

@@ -53,6 +53,9 @@ DOCUMENTATION :: END
if re.match(pattern, codec):
return file
return codec
def br(text):
return text.replace('\n', '<br /><br />')
%>
<%inherit file="base.html"/>
@@ -112,9 +115,9 @@ DOCUMENTATION :: END
<div class="col-md-9">
<div class="summary-content-poster hidden-xs hidden-sm">
% if data['media_type'] == 'track':
<a href="http://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details/%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View in Plex Web">
<a href="https://app.plex.tv/desktop#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View in Plex Web">
% else:
<a href="http://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details/%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View in Plex Web">
<a href="https://app.plex.tv/desktop#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View in Plex Web">
% endif
% if data['media_type'] == 'episode':
<div class="summary-poster-face-episode" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=280&fallback=art);">
@@ -250,7 +253,7 @@ DOCUMENTATION :: END
</div>
% endif
<div class="summary-content-summary">
<p> ${data['summary']} </p>
<p> ${data['summary'] | br, n} </p>
</div>
</div>
</div>

View File

@@ -0,0 +1,28 @@
(function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){this.typeNumber=a;this.errorCorrectLevel=c;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[]}function q(a,c){if(void 0==a.length)throw Error(a.length+"/"+c);for(var d=0;d<a.length&&0==a[d];)d++;this.num=Array(a.length-d+c);for(var b=0;b<a.length-d;b++)this.num[b]=a[b+d]}function p(a,c){this.totalCount=a;this.dataCount=c}function t(){this.buffer=[];this.length=0}u.prototype={getLength:function(){return this.data.length},
write:function(a){for(var c=0;c<this.data.length;c++)a.put(this.data.charCodeAt(c),8)}};o.prototype={addData:function(a){this.dataList.push(new u(a));this.dataCache=null},isDark:function(a,c){if(0>a||this.moduleCount<=a||0>c||this.moduleCount<=c)throw Error(a+","+c);return this.modules[a][c]},getModuleCount:function(){return this.moduleCount},make:function(){if(1>this.typeNumber){for(var a=1,a=1;40>a;a++){for(var c=p.getRSBlocks(a,this.errorCorrectLevel),d=new t,b=0,e=0;e<c.length;e++)b+=c[e].dataCount;
for(e=0;e<this.dataList.length;e++)c=this.dataList[e],d.put(c.mode,4),d.put(c.getLength(),j.getLengthInBits(c.mode,a)),c.write(d);if(d.getLengthInBits()<=8*b)break}this.typeNumber=a}this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17;this.modules=Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=Array(this.moduleCount);for(var b=0;b<this.moduleCount;b++)this.modules[d][b]=null}this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-
7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(a,c);7<=this.typeNumber&&this.setupTypeNumber(a);null==this.dataCache&&(this.dataCache=o.createData(this.typeNumber,this.errorCorrectLevel,this.dataList));this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,c){for(var d=-1;7>=d;d++)if(!(-1>=a+d||this.moduleCount<=a+d))for(var b=-1;7>=b;b++)-1>=c+b||this.moduleCount<=c+b||(this.modules[a+d][c+b]=
0<=d&&6>=d&&(0==b||6==b)||0<=b&&6>=b&&(0==d||6==d)||2<=d&&4>=d&&2<=b&&4>=b?!0:!1)},getBestMaskPattern:function(){for(var a=0,c=0,d=0;8>d;d++){this.makeImpl(!0,d);var b=j.getLostPoint(this);if(0==d||a>b)a=b,c=d}return c},createMovieClip:function(a,c,d){a=a.createEmptyMovieClip(c,d);this.make();for(c=0;c<this.modules.length;c++)for(var d=1*c,b=0;b<this.modules[c].length;b++){var e=1*b;this.modules[c][b]&&(a.beginFill(0,100),a.moveTo(e,d),a.lineTo(e+1,d),a.lineTo(e+1,d+1),a.lineTo(e,d+1),a.endFill())}return a},
setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(a=8;a<this.moduleCount-8;a++)null==this.modules[6][a]&&(this.modules[6][a]=0==a%2)},setupPositionAdjustPattern:function(){for(var a=j.getPatternPosition(this.typeNumber),c=0;c<a.length;c++)for(var d=0;d<a.length;d++){var b=a[c],e=a[d];if(null==this.modules[b][e])for(var f=-2;2>=f;f++)for(var i=-2;2>=i;i++)this.modules[b+f][e+i]=-2==f||2==f||-2==i||2==i||0==f&&0==i?!0:!1}},setupTypeNumber:function(a){for(var c=
j.getBCHTypeNumber(this.typeNumber),d=0;18>d;d++){var b=!a&&1==(c>>d&1);this.modules[Math.floor(d/3)][d%3+this.moduleCount-8-3]=b}for(d=0;18>d;d++)b=!a&&1==(c>>d&1),this.modules[d%3+this.moduleCount-8-3][Math.floor(d/3)]=b},setupTypeInfo:function(a,c){for(var d=j.getBCHTypeInfo(this.errorCorrectLevel<<3|c),b=0;15>b;b++){var e=!a&&1==(d>>b&1);6>b?this.modules[b][8]=e:8>b?this.modules[b+1][8]=e:this.modules[this.moduleCount-15+b][8]=e}for(b=0;15>b;b++)e=!a&&1==(d>>b&1),8>b?this.modules[8][this.moduleCount-
b-1]=e:9>b?this.modules[8][15-b-1+1]=e:this.modules[8][15-b-1]=e;this.modules[this.moduleCount-8][8]=!a},mapData:function(a,c){for(var d=-1,b=this.moduleCount-1,e=7,f=0,i=this.moduleCount-1;0<i;i-=2)for(6==i&&i--;;){for(var g=0;2>g;g++)if(null==this.modules[b][i-g]){var n=!1;f<a.length&&(n=1==(a[f]>>>e&1));j.getMask(c,b,i-g)&&(n=!n);this.modules[b][i-g]=n;e--; -1==e&&(f++,e=7)}b+=d;if(0>b||this.moduleCount<=b){b-=d;d=-d;break}}}};o.PAD0=236;o.PAD1=17;o.createData=function(a,c,d){for(var c=p.getRSBlocks(a,
c),b=new t,e=0;e<d.length;e++){var f=d[e];b.put(f.mode,4);b.put(f.getLength(),j.getLengthInBits(f.mode,a));f.write(b)}for(e=a=0;e<c.length;e++)a+=c[e].dataCount;if(b.getLengthInBits()>8*a)throw Error("code length overflow. ("+b.getLengthInBits()+">"+8*a+")");for(b.getLengthInBits()+4<=8*a&&b.put(0,4);0!=b.getLengthInBits()%8;)b.putBit(!1);for(;!(b.getLengthInBits()>=8*a);){b.put(o.PAD0,8);if(b.getLengthInBits()>=8*a)break;b.put(o.PAD1,8)}return o.createBytes(b,c)};o.createBytes=function(a,c){for(var d=
0,b=0,e=0,f=Array(c.length),i=Array(c.length),g=0;g<c.length;g++){var n=c[g].dataCount,h=c[g].totalCount-n,b=Math.max(b,n),e=Math.max(e,h);f[g]=Array(n);for(var k=0;k<f[g].length;k++)f[g][k]=255&a.buffer[k+d];d+=n;k=j.getErrorCorrectPolynomial(h);n=(new q(f[g],k.getLength()-1)).mod(k);i[g]=Array(k.getLength()-1);for(k=0;k<i[g].length;k++)h=k+n.getLength()-i[g].length,i[g][k]=0<=h?n.get(h):0}for(k=g=0;k<c.length;k++)g+=c[k].totalCount;d=Array(g);for(k=n=0;k<b;k++)for(g=0;g<c.length;g++)k<f[g].length&&
(d[n++]=f[g][k]);for(k=0;k<e;k++)for(g=0;g<c.length;g++)k<i[g].length&&(d[n++]=i[g][k]);return d};s=4;for(var j={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,
78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var c=a<<10;0<=j.getBCHDigit(c)-j.getBCHDigit(j.G15);)c^=j.G15<<j.getBCHDigit(c)-j.getBCHDigit(j.G15);return(a<<10|c)^j.G15_MASK},getBCHTypeNumber:function(a){for(var c=a<<12;0<=j.getBCHDigit(c)-
j.getBCHDigit(j.G18);)c^=j.G18<<j.getBCHDigit(c)-j.getBCHDigit(j.G18);return a<<12|c},getBCHDigit:function(a){for(var c=0;0!=a;)c++,a>>>=1;return c},getPatternPosition:function(a){return j.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,c,d){switch(a){case 0:return 0==(c+d)%2;case 1:return 0==c%2;case 2:return 0==d%3;case 3:return 0==(c+d)%3;case 4:return 0==(Math.floor(c/2)+Math.floor(d/3))%2;case 5:return 0==c*d%2+c*d%3;case 6:return 0==(c*d%2+c*d%3)%2;case 7:return 0==(c*d%3+(c+d)%2)%2;default:throw Error("bad maskPattern:"+
a);}},getErrorCorrectPolynomial:function(a){for(var c=new q([1],0),d=0;d<a;d++)c=c.multiply(new q([1,l.gexp(d)],0));return c},getLengthInBits:function(a,c){if(1<=c&&10>c)switch(a){case 1:return 10;case 2:return 9;case s:return 8;case 8:return 8;default:throw Error("mode:"+a);}else if(27>c)switch(a){case 1:return 12;case 2:return 11;case s:return 16;case 8:return 10;default:throw Error("mode:"+a);}else if(41>c)switch(a){case 1:return 14;case 2:return 13;case s:return 16;case 8:return 12;default:throw Error("mode:"+
a);}else throw Error("type:"+c);},getLostPoint:function(a){for(var c=a.getModuleCount(),d=0,b=0;b<c;b++)for(var e=0;e<c;e++){for(var f=0,i=a.isDark(b,e),g=-1;1>=g;g++)if(!(0>b+g||c<=b+g))for(var h=-1;1>=h;h++)0>e+h||c<=e+h||0==g&&0==h||i==a.isDark(b+g,e+h)&&f++;5<f&&(d+=3+f-5)}for(b=0;b<c-1;b++)for(e=0;e<c-1;e++)if(f=0,a.isDark(b,e)&&f++,a.isDark(b+1,e)&&f++,a.isDark(b,e+1)&&f++,a.isDark(b+1,e+1)&&f++,0==f||4==f)d+=3;for(b=0;b<c;b++)for(e=0;e<c-6;e++)a.isDark(b,e)&&!a.isDark(b,e+1)&&a.isDark(b,e+
2)&&a.isDark(b,e+3)&&a.isDark(b,e+4)&&!a.isDark(b,e+5)&&a.isDark(b,e+6)&&(d+=40);for(e=0;e<c;e++)for(b=0;b<c-6;b++)a.isDark(b,e)&&!a.isDark(b+1,e)&&a.isDark(b+2,e)&&a.isDark(b+3,e)&&a.isDark(b+4,e)&&!a.isDark(b+5,e)&&a.isDark(b+6,e)&&(d+=40);for(e=f=0;e<c;e++)for(b=0;b<c;b++)a.isDark(b,e)&&f++;a=Math.abs(100*f/c/c-50)/5;return d+10*a}},l={glog:function(a){if(1>a)throw Error("glog("+a+")");return l.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;256<=a;)a-=255;return l.EXP_TABLE[a]},EXP_TABLE:Array(256),
LOG_TABLE:Array(256)},m=0;8>m;m++)l.EXP_TABLE[m]=1<<m;for(m=8;256>m;m++)l.EXP_TABLE[m]=l.EXP_TABLE[m-4]^l.EXP_TABLE[m-5]^l.EXP_TABLE[m-6]^l.EXP_TABLE[m-8];for(m=0;255>m;m++)l.LOG_TABLE[l.EXP_TABLE[m]]=m;q.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var c=Array(this.getLength()+a.getLength()-1),d=0;d<this.getLength();d++)for(var b=0;b<a.getLength();b++)c[d+b]^=l.gexp(l.glog(this.get(d))+l.glog(a.get(b)));return new q(c,0)},mod:function(a){if(0>
this.getLength()-a.getLength())return this;for(var c=l.glog(this.get(0))-l.glog(a.get(0)),d=Array(this.getLength()),b=0;b<this.getLength();b++)d[b]=this.get(b);for(b=0;b<a.getLength();b++)d[b]^=l.gexp(l.glog(a.get(b))+c);return(new q(d,0)).mod(a)}};p.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],
[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,
116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,
43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,
3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,
55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,
45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];p.getRSBlocks=function(a,c){var d=p.getRsBlockTable(a,c);if(void 0==d)throw Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+c);for(var b=d.length/3,e=[],f=0;f<b;f++)for(var h=d[3*f+0],g=d[3*f+1],j=d[3*f+2],l=0;l<h;l++)e.push(new p(g,j));return e};p.getRsBlockTable=function(a,c){switch(c){case 1:return p.RS_BLOCK_TABLE[4*(a-1)+0];case 0:return p.RS_BLOCK_TABLE[4*(a-1)+1];case 3:return p.RS_BLOCK_TABLE[4*
(a-1)+2];case 2:return p.RS_BLOCK_TABLE[4*(a-1)+3]}};t.prototype={get:function(a){return 1==(this.buffer[Math.floor(a/8)]>>>7-a%8&1)},put:function(a,c){for(var d=0;d<c;d++)this.putBit(1==(a>>>c-d-1&1))},getLengthInBits:function(){return this.length},putBit:function(a){var c=Math.floor(this.length/8);this.buffer.length<=c&&this.buffer.push(0);a&&(this.buffer[c]|=128>>>this.length%8);this.length++}};"string"===typeof h&&(h={text:h});h=r.extend({},{render:"canvas",width:256,height:256,typeNumber:-1,
correctLevel:2,background:"#ffffff",foreground:"#000000"},h);return this.each(function(){var a;if("canvas"==h.render){a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();var c=document.createElement("canvas");c.width=h.width;c.height=h.height;for(var d=c.getContext("2d"),b=h.width/a.getModuleCount(),e=h.height/a.getModuleCount(),f=0;f<a.getModuleCount();f++)for(var i=0;i<a.getModuleCount();i++){d.fillStyle=a.isDark(f,i)?h.foreground:h.background;var g=Math.ceil((i+1)*b)-Math.floor(i*b),
j=Math.ceil((f+1)*b)-Math.floor(f*b);d.fillRect(Math.round(i*b),Math.round(f*e),g,j)}}else{a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();c=r("<table></table>").css("width",h.width+"px").css("height",h.height+"px").css("border","0px").css("border-collapse","collapse").css("background-color",h.background);d=h.width/a.getModuleCount();b=h.height/a.getModuleCount();for(e=0;e<a.getModuleCount();e++){f=r("<tr></tr>").css("height",b+"px").appendTo(c);for(i=0;i<a.getModuleCount();i++)r("<td></td>").css("width",
d+"px").css("background-color",a.isDark(e,i)?h.foreground:h.background).appendTo(f)}}a=c;jQuery(a).appendTo(this)})}})(jQuery);

View File

@@ -256,6 +256,10 @@ function getPlatformImagePath(platformName) {
return 'images/platforms/wp.png';
} else if (platformName.indexOf("Plex Media Player") > -1) {
return 'images/platforms/pmp.png';
} else if (platformName.indexOf("PlexTogether") > -1) {
return 'images/platforms/plextogether.png';
} else if (platformName.indexOf("Linux") > -1) {
return 'images/platforms/linux.png';
} else {
return 'images/platforms/default.png';
}
@@ -465,4 +469,4 @@ function humanFileSize(bytes, si) {
++u;
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1) + '&nbsp;' + units[u];
}
}

View File

@@ -117,6 +117,12 @@
</div>
<p class="help-block">Set your preferred time format. <a href="javascript:void(0)" data-target="#dateTimeOptionsModal" data-toggle="modal">Click here</a> to see the parameter list.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="week_start_monday" name="week_start_monday" value="1" ${config['week_start_monday']}> Week Starting on Monday
</label>
<p class="help-block">Change the "<em>Play by day of week</em>" graph to start on Monday. Default is start on Sunday.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="group_history_tables" name="group_history_tables" value="1" ${config['group_history_tables']}> Group Table and Watch Statistics History

View File

@@ -47,7 +47,7 @@
// Redirect to home page after countdown.
function reloadPage() {
window.location.href = "index";
window.location.href = "${new_http_root}index";
}
</script>
</%def>

View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# PROVIDE: plexpy
# REQUIRE: sabnzbd
# REQUIRE: plexpy
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
@@ -10,7 +10,7 @@
# plexpy_enable (bool): Set to NO by default.
# Set it to YES to enable it.
# plexpy_user: The user account PlexPy daemon runs as what
# you want it to be. It uses '_sabnzbd' user by
# you want it to be. It uses 'plexpy' user by
# default. Do not sets it as empty or it will run
# as root.
# plexpy_dir: Directory where PlexPy lives.
@@ -28,7 +28,7 @@ rcvar=${name}_enable
load_rc_config ${name}
: ${plexpy_enable:="NO"}
: ${plexpy_user:="_sabnzbd"}
: ${plexpy_user:="plexpy"}
: ${plexpy_dir:="/usr/local/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}

View File

@@ -1,66 +0,0 @@
# PlexPy - Stats for Plex Media Server usage
#
# Service Unit file for systemd system manager
#
# INSTALLATION NOTES
#
# 1. Rename this file as you want, ensuring that it ends in .service
# e.g. 'plexpy.service'
#
# 2. Adjust configuration settings as required. More details in the
# "CONFIGURATION NOTES" section shown below.
#
# 3. Copy this file into your systemd service unit directory, which is
# often '/lib/systemd/system'.
#
# 4. Create any files/directories that you specified back in step #2.
# e.g. '/etc/plexpy/plexpy.ini'
# '/home/sabnzbd/.plexpy'
#
# 5. Enable boot-time autostart with the following commands:
# systemctl daemon-reload
# systemctl enable plexpy.service
#
# 6. Start now with the following command:
# systemctl start plexpy.service
#
# 7. If troubleshooting startup-errors, start by checking permissions
# and ownership on the files/directories that you created in step #4.
#
#
# CONFIGURATION NOTES
#
# - The example settings in this file assume that:
# 1. You will run PlexPy as user/group: sabnzbd.sabnzbd
# 2. You will either have PlexPy installed as a subdirectory
# under '~sabnzbd', or that you will have a symlink under
# '~/sabnzbd' pointing to your PlexPy install dir.
# 3. Your PlexPy data directory and configuration file will be
# in separate locations from your PlexPy install dir, to
# simplify updates.
#
# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive)
#
# - Adjust ExecStart= to point to:
# 1. Your PlexPy executable,
# 2. Your config file (recommended is to put it somewhere in /etc)
# 3. Your datadir (recommended is to NOT put it in your PlexPy exec dir)
#
# - Adjust User= and Group= to the user/group you want PlexPy to run as.
#
# - WantedBy= specifies which target (i.e. runlevel) to start PlexPy for.
# multi-user.target equates to runlevel 3 (multi-user text mode)
# graphical.target equates to runlevel 5 (multi-user X11 graphical mode)
[Unit]
Description=PlexPy - Stats for Plex Media Server usage
[Service]
ExecStart=/home/sabnzbd/plexpy/PlexPy.py --daemon --config /etc/plexpy/plexpy.ini --datadir /home/sabnzbd/.plexpy --nolaunch --quiet
GuessMainPID=no
Type=forking
User=sabnzbd
Group=sabnzbd
[Install]
WantedBy=multi-user.target

View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# PROVIDE: plexpy
# REQUIRE: DAEMON sabnzbd
# REQUIRE: DAEMON plexpy
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
@@ -10,7 +10,7 @@
# plexpy_enable (bool): Set to NO by default.
# Set it to YES to enable it.
# plexpy_user: The user account PlexPy daemon runs as what
# you want it to be. It uses '_sabnzbd' user by
# you want it to be. It uses 'plexpy' user by
# default. Do not sets it as empty or it will run
# as root.
# plexpy_dir: Directory where PlexPy lives.
@@ -29,7 +29,7 @@ rcvar=${name}_enable
load_rc_config ${name}
: ${plexpy_enable:="NO"}
: ${plexpy_user:="_sabnzbd"}
: ${plexpy_user:="plexpy"}
: ${plexpy_dir:="/usr/local/share/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}

View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# PROVIDE: plexpy
# REQUIRE: DAEMON sabnzbd
# REQUIRE: DAEMON plexpy
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
@@ -10,7 +10,7 @@
# plexpy_enable (bool): Set to NO by default.
# Set it to YES to enable it.
# plexpy_user: The user account PlexPy daemon runs as what
# you want it to be. It uses '_sabnzbd' user by
# you want it to be. It uses 'plexpy' user by
# default. Do not sets it as empty or it will run
# as root.
# plexpy_dir: Directory where PlexPy lives.
@@ -29,7 +29,7 @@ rcvar=${name}_enable
load_rc_config ${name}
: ${plexpy_enable:="NO"}
: ${plexpy_user:="_sabnzbd"}
: ${plexpy_user:="plexpy"}
: ${plexpy_dir:="/usr/local/share/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}

View File

@@ -1,67 +0,0 @@
# PlexPy - Stats for Plex Media Server usage
#
# Service Unit file for systemd system manager
#
# INSTALLATION NOTES
#
# 1. Rename this file as you want, ensuring that it ends in .service
# e.g. 'plexpy.service'
#
# 2. Adjust configuration settings as required. More details in the
# "CONFIGURATION NOTES" section shown below.
#
# 3. Copy this file into your systemd service unit directory, which is
# often '/lib/systemd/system'.
#
# 4. Create any files/directories that you specified back in step #2.
# e.g. '/opt/plexpy.ini'
# '/opt/plexpy'
#
# 5. Enable boot-time autostart with the following commands:
# systemctl daemon-reload
# systemctl enable plexpy.service
#
# 6. Start now with the following command:
# systemctl start plexpy.service
#
# 7. If troubleshooting startup-errors, start by checking permissions
# and ownership on the files/directories that you created in step #4.
#
#
# CONFIGURATION NOTES
#
# - The example settings in this file assume that:
# 1. You will run PlexPy as user/group: plex.users
# 2. You will either have PlexPy installed as a subdirectory
# under '/opt', or that you will have a symlink under
# '/opt' pointing to your PlexPy install dir.
# 3. Your PlexPy data directory and configuration file can be
# in separate locations from your PlexPy install dir, to
# simplify updates. However, in the example below they are in the
# PlexPy install dir.
#
# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive)
#
# - Adjust ExecStart= to point to:
# 1. Your PlexPy executable,
# 2. Your config file (recommended is to put it somewhere in /etc)
# 3. Your datadir (recommended is to NOT put it in your PlexPy exec dir)
#
# - Adjust User= and Group= to the user/group you want PlexPy to run as.
#
# - WantedBy= specifies which target (i.e. runlevel) to start PlexPy for.
# multi-user.target equates to runlevel 3 (multi-user text mode)
# graphical.target equates to runlevel 5 (multi-user X11 graphical mode)
[Unit]
Description=PlexPy - Stats for Plex Media Server usage
[Service]
ExecStart=/opt/plexpy/PlexPy.py --daemon --config /opt/plexpy/config.ini --datadir /opt/plexpy --nolaunch --quiet
GuessMainPID=no
Type=forking
User=plex
Group=users
[Install]
WantedBy=multi-user.target

View File

@@ -19,7 +19,7 @@
</dependency>
<method_context>
<method_credential user="sabnzbd" group="sabnzbd"/>
<method_credential user="plexpy" group="nogroup"/>
</method_context>
<exec_method type="method" name="start" exec="python /opt/plexpy/PlexPy.py --daemon --quiet --nolaunch" timeout_seconds="60"/>

View File

@@ -13,32 +13,20 @@
# 3. Copy this file into your systemd service unit directory, which is
# often '/lib/systemd/system'.
#
# 4. Create any files/directories that you specified back in step #2.
# e.g. '/etc/plexpy/plexpy.ini'
# '/home/sabnzbd/.plexpy'
#
# 5. Enable boot-time autostart with the following commands:
# 4. Enable boot-time autostart with the following commands:
# systemctl daemon-reload
# systemctl enable plexpy.service
#
# 6. Start now with the following command:
# 5. Start now with the following command:
# systemctl start plexpy.service
#
# 7. If troubleshooting startup-errors, start by checking permissions
# and ownership on the files/directories that you created in step #4.
#
#
# CONFIGURATION NOTES
#
# - The example settings in this file assume that:
# 1. You will run PlexPy as user/group: sabnzbd.sabnzbd
# 2. You will either have PlexPy installed as a subdirectory
# under '~sabnzbd', or that you will have a symlink under
# '~/sabnzbd' pointing to your PlexPy install dir.
# 3. Your PlexPy data directory and configuration file will be
# in separate locations from your PlexPy install dir, to
# simplify updates.
#
# - The example settings in this file assume that you will run PlexPy as user: plexpy
# - To create this user and give it ownership of the plexpy directory:
# sudo adduser --system --no-create-home plexpy
# sudo chown plexpy:nogroup -R /opt/plexpy
#
# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive)
#
# - Adjust ExecStart= to point to:
@@ -63,4 +51,4 @@ User=plexpy
Group=nogroup
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target

View File

@@ -54,6 +54,7 @@ VERBOSE = True
DAEMON = False
CREATEPID = False
PIDFILE = None
NOFORK = False
SCHED = BackgroundScheduler()
SCHED_LOCK = threading.Lock()
@@ -989,13 +990,16 @@ def shutdown(restart=False, update=False):
args += ARGS
if '--nolaunch' not in args:
args += ['--nolaunch']
logger.info('Restarting PlexPy with %s', args)
# os.execv fails with spaced names on Windows
# https://bugs.python.org/issue19066
if os.name == 'nt':
if NOFORK:
logger.info('Running as service, not forking. Exiting...')
elif os.name == 'nt':
logger.info('Restarting PlexPy with %s', args)
subprocess.Popen(args, cwd=os.getcwd())
else:
logger.info('Restarting PlexPy with %s', args)
os.execv(exe, args)
os._exit(0)

View File

@@ -220,7 +220,7 @@ class ActivityProcessor(object):
if not is_import:
logger.debug(u"PlexPy ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']), get_media_info=True)
if result and result['metadata']:
metadata = result['metadata']
else:
@@ -293,10 +293,10 @@ class ActivityProcessor(object):
'(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
args = [session['rating_key'], session['video_decision'], session['audio_decision'],
session['duration'], session['width'], session['height'], session['container'],
session['video_codec'], session['audio_codec'], session['bitrate'],
session['video_resolution'], session['video_framerate'], session['aspect_ratio'],
session['audio_channels'], session['transcode_protocol'], session['transcode_container'],
metadata['duration'], metadata['width'], metadata['height'], metadata['container'],
metadata['video_codec'], metadata['audio_codec'], metadata['bitrate'],
metadata['video_resolution'], metadata['video_framerate'], metadata['aspect_ratio'],
metadata['audio_channels'], session['transcode_protocol'], session['transcode_container'],
session['transcode_video_codec'], session['transcode_audio_codec'],
session['transcode_audio_channels'], session['transcode_width'], session['transcode_height'],
session['transcode_decision']]

View File

@@ -47,7 +47,7 @@ class API2:
self._api_cmd = None
self._api_apikey = None
self._api_callback = None # JSONP
self._api_result_type = 'failed'
self._api_result_type = 'error'
self._api_profileme = None # For profiling the api call
self._api_kwargs = None # Cleaned kwargs
@@ -64,12 +64,12 @@ class API2:
return docs
def docs_md(self):
""" Return the api docs formatted with markdown. """
""" Return the api docs formatted with markdown."""
return self._api_make_md()
def docs(self):
""" Return the api docs as a dict where commands are keys, docstring are value. """
""" Return the api docs as a dict where commands are keys, docstring are value."""
return self._api_docs()
@@ -95,7 +95,7 @@ class API2:
self._api_msg = 'Parameter cmd is required. Possible commands are: %s' % ', '.join(self._api_valid_methods)
elif 'cmd' in kwargs and kwargs.get('cmd') not in self._api_valid_methods:
self._api_msg = 'Unknown command: %s. Possible commands are: %s' % (kwargs.get('cmd', ''), ', '.join(self._api_valid_methods))
self._api_msg = 'Unknown command: %s. Possible commands are: %s' % (kwargs.get('cmd', ''), ', '.join(sorted(self._api_valid_methods)))
self._api_callback = kwargs.pop('callback', None)
self._api_apikey = kwargs.pop('apikey', None)
@@ -137,17 +137,16 @@ class API2:
Returns:
json:
[{"loglevel": "DEBUG",
"msg": "Latest version is 2d10b0748c7fa2ee4cf59960c3d3fffc6aa9512b",
"thread": "MainThread",
[{"loglevel": "DEBUG",
"msg": "Latest version is 2d10b0748c7fa2ee4cf59960c3d3fffc6aa9512b",
"thread": "MainThread",
"time": "2016-05-08 09:36:51 "
},
},
{...},
{...}
]
```
"""
logfile = os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)
templog = []
start = int(kwargs.get('start', 0))
@@ -213,7 +212,6 @@ class API2:
if order == 'desc':
templog = templog[::-1]
self.data = templog
return templog
def get_settings(self, key=''):
@@ -257,7 +255,7 @@ class API2:
config[k]['interface_list'] = interface_list
if key:
return config.get(key, None)
return config.get(key)
return config
@@ -277,81 +275,69 @@ class API2:
None
```
"""
if not plexpy.CONFIG.API_SQL or not query:
if not plexpy.CONFIG.API_SQL:
self._api_msg = 'SQL not enabled for the API.'
return
if not query:
self._api_msg = 'No SQL query provided.'
return
# allow the user to shoot them self
# in the foot but not in the head..
if not len(os.listdir(plexpy.BACKUP_DIR)):
self.backupdb()
if not len(os.listdir(plexpy.CONFIG.BACKUP_DIR)):
self.backup_db()
else:
# If the backup is less then 24 h old lets make a backup
if any([os.path.getctime(os.path.join(plexpy.BACKUP_DIR, file_)) <
(time.time() - 86400) for file_ in os.listdir(plexpy.BACKUP_DIR)]):
self.backupdb()
if any([os.path.getctime(os.path.join(plexpy.CONFIG.BACKUP_DIR, file_)) < (time.time() - 86400)
and file_.endswith('.db') for file_ in os.listdir(plexpy.CONFIG.BACKUP_DIR)]):
self.backup_db()
db = database.MonitorDatabase()
rows = db.select(query)
self.data = rows
return rows
def backup_config(self):
""" Create a manual backup of the `config.ini` file. """
""" Create a manual backup of the `config.ini` file."""
data = config.make_backup()
if data:
self.result_type = 'success'
else:
self.result_type = 'failed'
self._api_result_type = 'success' if data else 'error'
return data
def backup_db(self):
""" Create a manual backup of the `plexpy.db` file. """
""" Create a manual backup of the `plexpy.db` file."""
data = database.make_backup()
if data:
self.result_type = 'success'
else:
self.result_type = 'failed'
self._api_result_type = 'success' if data else 'error'
return data
def restart(self, **kwargs):
""" Restart PlexPy. """
""" Restart PlexPy."""
plexpy.SIGNAL = 'restart'
self.msg = 'Restarting plexpy'
self.result_type = 'success'
self._api_msg = 'Restarting plexpy'
self._api_result_type = 'success'
def update(self, **kwargs):
""" Check for PlexPy updates on Github. """
""" Check for PlexPy updates on Github."""
plexpy.SIGNAL = 'update'
self.msg = 'Updating plexpy'
self.result_type = 'success'
self._api_msg = 'Updating plexpy'
self._api_result_type = 'success'
def refresh_libraries_list(self, **kwargs):
""" Refresh the PlexPy libraries list. """
""" Refresh the PlexPy libraries list."""
data = pmsconnect.refresh_libraries()
if data:
self.result_type = 'success'
else:
self.result_type = 'failed'
self._api_result_type = 'success' if data else 'error'
return data
def refresh_users_list(self, **kwargs):
""" Refresh the PlexPy users list. """
""" Refresh the PlexPy users list."""
data = plextv.refresh_users()
if data:
self.result_type = 'success'
else:
self.result_type = 'failed'
self._api_result_type = 'success' if data else 'error'
return data
@@ -418,30 +404,30 @@ General optional parameters:
string: "apikey"
```
"""
data = None
apikey = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32]
if plexpy.CONFIG.HTTP_USERNAME and plexpy.CONFIG.HTTP_PASSWORD:
if username == plexpy.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
if username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
if plexpy.CONFIG.API_KEY:
self.data = plexpy.CONFIG.API_KEY
data = plexpy.CONFIG.API_KEY
else:
self.data = apikey
data = apikey
plexpy.CONFIG.API_KEY = apikey
plexpy.CONFIG.write()
else:
self.msg = 'Authentication is enabled, please add the correct username and password to the parameters'
self._api_msg = 'Authentication is enabled, please add the correct username and password to the parameters'
else:
if plexpy.CONFIG.API_KEY:
self.data = plexpy.CONFIG.API_KEY
data = plexpy.CONFIG.API_KEY
else:
# Make a apikey if the doesn't exist
self.data = apikey
data = apikey
plexpy.CONFIG.API_KEY = apikey
plexpy.CONFIG.write()
return self.data
return data
def _api_responds(self, result_type='success', data=None, msg=''):
def _api_responds(self, result_type='error', data=None, msg=''):
""" Formats the result to a predefined dict so we can hange it the to
the desired output by _api_out_as """
@@ -478,6 +464,7 @@ General optional parameters:
logger.info(u'PlexPy APIv2 :: ' + traceback.format_exc())
out['message'] = traceback.format_exc()
out['result'] = 'error'
elif self._api_out_type == 'xml':
cherrypy.response.headers['Content-Type'] = 'application/xml'
try:
@@ -519,13 +506,16 @@ General optional parameters:
# We allow this to fail so we get a
# traceback in the browser
if self._api_debug:
try:
result = call(**self._api_kwargs)
else:
try:
result = call(**self._api_kwargs)
except Exception as e:
logger.error(u'PlexPy APIv2 :: Failed to run %s %s %s' % (self._api_cmd, self._api_kwargs, e))
except Exception as e:
logger.error(u'PlexPy APIv2 :: Failed to run %s with %s: %s' % (self._api_cmd, self._api_kwargs, e))
if self._api_debug:
cherrypy.request.show_tracebacks = True
# Reraise the exception so the traceback hits the browser
raise
self._api_msg = 'Check the logs'
ret = None
# The api decorated function can return different result types.
@@ -556,4 +546,14 @@ General optional parameters:
else:
self._api_result_type = 'error'
# Since some of them metods use a api like response for the ui
# {result: error, message: 'Some shit happend'}
if isinstance(ret, dict):
if ret.get('message'):
self._api_msg = ret.get('message', {})
ret = {}
if ret.get('result'):
self._api_result_type = ret.get('result')
return self._api_out_as(self._api_responds(result_type=self._api_result_type, msg=self._api_msg, data=ret))

View File

@@ -39,7 +39,8 @@ DEFAULT_ART = "interfaces/default/images/art.png"
PLATFORM_NAME_OVERRIDES = {'Konvergo': 'Plex Media Player',
'Mystery 3': 'Playstation 3',
'Mystery 4': 'Playstation 4',
'Mystery 5': 'Xbox 360'
'Mystery 5': 'Xbox 360',
'WebMAF': 'Playstation 4'
}
PMS_PLATFORM_NAME_OVERRIDES = {'MacOSX': 'Mac'

View File

@@ -17,6 +17,7 @@ import arrow
import os
import re
import shutil
import time
from configobj import ConfigObj
@@ -174,6 +175,7 @@ _CONFIG_DEFINITIONS = {
'GIT_USER': (str, 'General', 'JonnyWong16'),
'GRAPH_TYPE': (str, 'General', 'plays'),
'GRAPH_DAYS': (int, 'General', 30),
'GRAPH_MONTHS': (int, 'General', 12),
'GRAPH_TAB': (str, 'General', 'tabs-1'),
'GROUP_HISTORY_TABLES': (int, 'General', 0),
'GROWL_ENABLED': (int, 'Growl', 0),
@@ -565,6 +567,7 @@ _CONFIG_DEFINITIONS = {
'UPDATE_LABELS': (int, 'General', 1),
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1),
'WEEK_START_MONDAY': (int, 'General', 0),
'XBMC_ENABLED': (int, 'XBMC', 0),
'XBMC_HOST': (str, 'XBMC', ''),
'XBMC_PASSWORD': (str, 'XBMC', ''),
@@ -608,7 +611,7 @@ def make_backup(cleanup=False, scheduler=False):
if cleanup:
now = time.time()
# Delete all scheduled backup files except from the last 5.
# Delete all scheduled backup older than BACKUP_DAYS.
for root, dirs, files in os.walk(backup_folder):
ini_files = [os.path.join(root, f) for f in files if f.endswith('.sched.ini')]
for file_ in ini_files:

View File

@@ -169,8 +169,12 @@ class Graphs(object):
logger.warn(u"PlexPy Graphs :: Unable to execute database query for get_total_plays_per_dayofweek: %s." % e)
return None
days_list = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday']
if plexpy.CONFIG.WEEK_START_MONDAY:
days_list = ['Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday', 'Sunday']
else:
days_list = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday']
categories = []
series_1 = []
@@ -291,8 +295,11 @@ class Graphs(object):
'series': [series_1_output, series_2_output, series_3_output]}
return output
def get_total_plays_per_month(self, y_axis='plays', user_id=None):
def get_total_plays_per_month(self, time_range='12', y_axis='plays', user_id=None):
import time as time
if not time_range.isdigit():
time_range = '12'
monitor_db = database.MonitorDatabase()
@@ -309,9 +316,9 @@ class Graphs(object):
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \
'FROM session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-12 months", "localtime") %s' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \
'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) ' \
'ORDER BY datestring DESC LIMIT 12' % (user_cond)
'ORDER BY datestring DESC LIMIT %s' % (time_range, user_cond, time_range)
result = monitor_db.select(query)
else:
@@ -323,9 +330,9 @@ class Graphs(object):
'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_count ' \
'FROM session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-12 months", "localtime") %s' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \
'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) ' \
'ORDER BY datestring DESC LIMIT 12' % (user_cond)
'ORDER BY datestring DESC LIMIT %s' % (time_range, user_cond, time_range)
result = monitor_db.select(query)
except Exception as e:
@@ -334,10 +341,9 @@ class Graphs(object):
# create our date range as some months may not have any data
# but we still want to display them
x = 12
base = time.localtime()
month_range = [time.localtime(
time.mktime((base.tm_year, base.tm_mon - n, 1, 0, 0, 0, 0, 0, 0))) for n in range(x)]
time.mktime((base.tm_year, base.tm_mon - n, 1, 0, 0, 0, 0, 0, 0))) for n in range(int(time_range))]
categories = []
series_1 = []

View File

@@ -204,10 +204,10 @@ def human_duration(s, sig='dhms'):
hd = ''
if str(s).isdigit() and s > 0:
d = int(s / 84600)
h = int((s % 84600) / 3600)
m = int(((s % 84600) % 3600) / 60)
s = int(((s % 84600) % 3600) % 60)
d = int(s / 86400)
h = int((s % 86400) / 3600)
m = int(((s % 86400) % 3600) / 60)
s = int(((s % 86400) % 3600) % 60)
hd_list = []
if sig >= 'd' and d > 0:

View File

@@ -382,7 +382,7 @@ class Libraries(object):
pass
# If no cache was imported, get all library children items
cached_items = {d['rating_key']: d['file_size'] for d in rows}
cached_items = {d['rating_key']: d['file_size'] for d in rows} if not refresh else {}
if refresh or not rows:
pms_connect = pmsconnect.PmsConnect()
@@ -491,6 +491,8 @@ class Libraries(object):
results = sorted(results, key=lambda k: helpers.cast_to_int(k['media_index']), reverse=reverse)
elif sort_key == 'file_size' or sort_key == 'bitrate':
results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key]), reverse=reverse)
elif sort_key == 'video_resolution':
results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key].replace('4k', '2160p').rstrip('p')), reverse=reverse)
else:
results = sorted(results, key=lambda k: k[sort_key], reverse=reverse)

View File

@@ -666,7 +666,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=
remaining_duration = duration - view_offset
# Build Plex URL
metadata['plex_url'] = 'http://app.plex.tv/web/app#!/server/{0}/details/%2Flibrary%2Fmetadata%2F{1}'.format(
metadata['plex_url'] = 'https://app.plex.tv/desktop#!/server/{0}/details?key=%2Flibrary%2Fmetadata%2F{1}'.format(
plexpy.CONFIG.PMS_IDENTIFIER, str(rating_key))
# Get media IDs from guid and build URLs

View File

@@ -1123,7 +1123,7 @@ class PUSHBULLET(object):
http_handler.request("POST",
"/v2/pushes",
headers={'Content-type': "application/json",
'Authorization': 'Basic %s' % base64.b64encode(self.apikey + ":")},
'Access-Token': self.apikey},
body=json.dumps(data))
response = http_handler.getresponse()
@@ -1155,7 +1155,7 @@ class PUSHBULLET(object):
http_handler = HTTPSConnection("api.pushbullet.com")
http_handler.request("GET", "/v2/devices",
headers={'Content-type': "application/json",
'Authorization': 'Basic %s' % base64.b64encode(self.apikey + ":")})
'Access-Token': self.apikey})
response = http_handler.getresponse()
request_status = response.status
@@ -2062,9 +2062,10 @@ class SLACK(object):
data['attachments'] = [attachment]
slackhost = urlparse(self.slack_hook).hostname
slackport = urlparse(self.slack_hook).port
slackpath = urlparse(self.slack_hook).path
http_handler = HTTPSConnection(slackhost)
http_handler = HTTPSConnection(slackhost, slackport)
http_handler.request("POST",
slackpath,
headers={'Content-type': "application/json"},

View File

@@ -414,7 +414,17 @@ def import_from_plexivity(database=None, table_name=None, import_ignore_interval
'genres': extracted_xml['genres'],
'studio': extracted_xml['studio'],
'labels': extracted_xml['labels'],
'full_title': row['full_title']
'full_title': row['full_title'],
'width': extracted_xml['width'],
'height': extracted_xml['height'],
'container': extracted_xml['container'],
'video_codec': extracted_xml['video_codec'],
'audio_codec': extracted_xml['audio_codec'],
'bitrate': extracted_xml['bitrate'],
'video_resolution': extracted_xml['video_resolution'],
'video_framerate': extracted_xml['video_framerate'],
'aspect_ratio': extracted_xml['aspect_ratio'],
'audio_channels': extracted_xml['audio_channels']
}
# On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values

View File

@@ -144,7 +144,7 @@ class PlexTV(object):
Plex.tv authentication
"""
def __init__(self, username=None, password=None, token=None):
def __init__(self, username='', password='', token=None):
self.protocol = 'HTTPS'
self.username = username
self.password = password
@@ -168,7 +168,7 @@ class PlexTV(object):
def get_plex_auth(self, output_format='raw'):
uri = '/users/sign_in.xml'
base64string = base64.encodestring('%s:%s' % (self.username, self.password)).replace('\n', '')
base64string = base64.b64encode(('%s:%s' % (self.username, self.password)).encode('utf-8'))
headers = {'Content-Type': 'application/xml; charset=utf-8',
'X-Plex-Device-Name': 'PlexPy',
'X-Plex-Product': 'PlexPy',

View File

@@ -407,7 +407,17 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
'genres': extracted_xml['genres'],
'studio': extracted_xml['studio'],
'labels': extracted_xml['labels'],
'full_title': row['full_title']
'full_title': row['full_title'],
'width': extracted_xml['width'],
'height': extracted_xml['height'],
'container': extracted_xml['container'],
'video_codec': extracted_xml['video_codec'],
'audio_codec': extracted_xml['audio_codec'],
'bitrate': extracted_xml['bitrate'],
'video_resolution': extracted_xml['video_resolution'],
'video_framerate': extracted_xml['video_framerate'],
'aspect_ratio': extracted_xml['aspect_ratio'],
'audio_channels': extracted_xml['audio_channels']
}
# On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values

View File

@@ -564,7 +564,6 @@ class PmsConnect(object):
for a in xml_head:
if a.getAttribute('size'):
if a.getAttribute('size') != '1':
metadata_list = {'metadata': None}
return metadata_list
if a.getElementsByTagName('Directory'):
@@ -882,6 +881,9 @@ class PmsConnect(object):
'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
'audio_channels': helpers.get_xml_attr(media, 'audioChannels'),
'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'),
'width': helpers.get_xml_attr(media, 'width'),
'height': helpers.get_xml_attr(media, 'height'),
'file': helpers.get_xml_attr(media.getElementsByTagName('Part')[0], 'file'),
'file_size': helpers.get_xml_attr(media.getElementsByTagName('Part')[0], 'size'),
}
@@ -1113,7 +1115,8 @@ class PmsConnect(object):
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title')
or helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'product'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'),
@@ -1187,8 +1190,8 @@ class PmsConnect(object):
transcode_audio_codec = helpers.get_xml_attr(transcode_session, 'audioCodec')
video_decision = helpers.get_xml_attr(transcode_session, 'videoDecision')
transcode_video_codec = helpers.get_xml_attr(transcode_session, 'videoCodec')
transcode_width = helpers.get_xml_attr(transcode_session, 'width')
transcode_height = helpers.get_xml_attr(transcode_session, 'height')
transcode_width = helpers.get_xml_attr(transcode_session, 'width') or width
transcode_height = helpers.get_xml_attr(transcode_session, 'height') or height
transcode_container = helpers.get_xml_attr(transcode_session, 'container')
transcode_protocol = helpers.get_xml_attr(transcode_session, 'protocol')
else:
@@ -1256,7 +1259,8 @@ class PmsConnect(object):
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title')
or helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'product'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'),
@@ -1320,7 +1324,8 @@ class PmsConnect(object):
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title')
or helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'product'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'),
@@ -1383,7 +1388,8 @@ class PmsConnect(object):
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title')
or helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'product'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'),
@@ -1444,8 +1450,8 @@ class PmsConnect(object):
transcode_speed = helpers.get_xml_attr(transcode_session, 'speed')
video_decision = helpers.get_xml_attr(transcode_session, 'videoDecision')
transcode_video_codec = helpers.get_xml_attr(transcode_session, 'videoCodec')
transcode_width = helpers.get_xml_attr(transcode_session, 'width')
transcode_height = helpers.get_xml_attr(transcode_session, 'height')
transcode_width = helpers.get_xml_attr(transcode_session, 'width') or width
transcode_height = helpers.get_xml_attr(transcode_session, 'height') or height
transcode_container = helpers.get_xml_attr(transcode_session, 'container')
transcode_protocol = helpers.get_xml_attr(transcode_session, 'protocol')
else:
@@ -1489,7 +1495,8 @@ class PmsConnect(object):
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title')
or helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'product'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'),

View File

@@ -1,2 +1,2 @@
PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.4.14"
PLEXPY_RELEASE_VERSION = "1.4.24"

View File

@@ -142,11 +142,12 @@ def process(opcode, data):
try:
info = json.loads(data)
except Exception as ex:
logger.warn(u"PlexPy WebSocket :: Error decoding message from websocket: %s" % ex)
except Exception as e:
logger.warn(u"PlexPy WebSocket :: Error decoding message from websocket: %s" % e)
logger.debug(data)
return False
info = info.get('NotificationContainer', info)
type = info.get('type')
if not type:
@@ -154,9 +155,9 @@ def process(opcode, data):
if type == 'playing':
# logger.debug('%s.playing %s' % (name, info))
try:
time_line = info.get('_children')
except:
time_line = info.get('PlaySessionStateNotification', info.get('_children'))
if not time_line:
logger.debug(u"PlexPy WebSocket :: Session found but unable to get timeline data.")
return False

View File

@@ -329,7 +329,9 @@ class WebInterface(object):
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def delete_temp_sessions(self, **kwargs):
""" Flush out all of the temporary sessions in the database."""
result = database.delete_sessions()
@@ -1745,6 +1747,7 @@ class WebInterface(object):
config = {
"graph_type": plexpy.CONFIG.GRAPH_TYPE,
"graph_days": plexpy.CONFIG.GRAPH_DAYS,
"graph_months": plexpy.CONFIG.GRAPH_MONTHS,
"graph_tab": plexpy.CONFIG.GRAPH_TAB,
"music_logging_enable": plexpy.CONFIG.MUSIC_LOGGING_ENABLE
}
@@ -1753,13 +1756,16 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth(member_of("admin"))
def set_graph_config(self, graph_type=None, graph_days=None, graph_tab=None, **kwargs):
def set_graph_config(self, graph_type=None, graph_days=None, graph_months=None, graph_tab=None, **kwargs):
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_months:
plexpy.CONFIG.__setattr__('GRAPH_MONTHS', graph_months)
plexpy.CONFIG.write()
if graph_tab:
plexpy.CONFIG.__setattr__('GRAPH_TAB', graph_tab)
plexpy.CONFIG.write()
@@ -1906,7 +1912,7 @@ class WebInterface(object):
@cherrypy.tools.json_out()
@requireAuth()
@addtoapi()
def get_plays_per_month(self, y_axis='plays', user_id=None, **kwargs):
def get_plays_per_month(self, time_range='12', y_axis='plays', user_id=None, **kwargs):
""" Get graph data by month.
```
@@ -1914,7 +1920,7 @@ class WebInterface(object):
None
Optional parameters:
time_range (str): The number of days of data to return
time_range (str): The number of months of data to return
y_axis (str): "plays" or "duration"
user_id (str): The user id to filter the data
@@ -1931,7 +1937,7 @@ class WebInterface(object):
```
"""
graph = graphs.Graphs()
result = graph.get_total_plays_per_month(y_axis=y_axis, user_id=user_id)
result = graph.get_total_plays_per_month(time_range=time_range, y_axis=y_axis, user_id=user_id)
if result:
return result
@@ -2616,7 +2622,8 @@ class WebInterface(object):
"git_token": plexpy.CONFIG.GIT_TOKEN,
"imgur_client_id": plexpy.CONFIG.IMGUR_CLIENT_ID,
"cache_images": checked(plexpy.CONFIG.CACHE_IMAGES),
"pms_version": plexpy.CONFIG.PMS_VERSION
"pms_version": plexpy.CONFIG.PMS_VERSION,
"week_start_monday": checked(plexpy.CONFIG.WEEK_START_MONDAY)
}
return serve_template(templatename="settings.html", title="Settings", config=config, kwargs=kwargs)
@@ -2630,7 +2637,7 @@ class WebInterface(object):
checked_configs = [
"launch_browser", "enable_https", "https_create_cert", "api_enabled", "freeze_db", "check_github",
"grouping_global_history", "grouping_user_history", "grouping_charts", "group_history_tables",
"pms_use_bif", "pms_ssl", "pms_is_remote", "home_stats_type",
"pms_use_bif", "pms_ssl", "pms_is_remote", "home_stats_type", "week_start_monday",
"movie_notify_enable", "tv_notify_enable", "music_notify_enable", "monitoring_use_websocket",
"refresh_libraries_on_startup", "refresh_users_on_startup",
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
@@ -3212,8 +3219,13 @@ class WebInterface(object):
quote = self.random_arnold_quotes()
plexpy.SIGNAL = signal
if plexpy.CONFIG.HTTP_ROOT:
new_http_root = '/' + plexpy.CONFIG.HTTP_ROOT.strip('/') + '/'
else:
new_http_root = '/'
return serve_template(templatename="shutdown.html", title=title,
message=message, timer=timer, quote=quote)
new_http_root=new_http_root, message=message, timer=timer, quote=quote)
@cherrypy.expose
@requireAuth(member_of("admin"))
@@ -3245,14 +3257,14 @@ class WebInterface(object):
if source == 'history':
data_factory = datafactory.DataFactory()
result = data_factory.get_metadata_details(rating_key=rating_key)
if result:
if result and result['metadata']:
metadata = result['metadata']
poster_url = data_factory.get_poster_url(metadata=metadata)
metadata['poster_url'] = poster_url
else:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=rating_key, get_media_info=True)
if result:
if result and result['metadata']:
metadata = result['metadata']
data_factory = datafactory.DataFactory()
poster_url = data_factory.get_poster_url(metadata=metadata)
@@ -3289,8 +3301,7 @@ class WebInterface(object):
refresh = False
if kwargs.get('refresh'):
mo = member_of('admin')
refresh = True if mo() else False
refresh = False if get_session_user_id() else True
kwargs['refresh'] = refresh
@@ -4301,13 +4312,18 @@ class WebInterface(object):
'Come with me if you want to live.',
'Who is your daddy and what does he do?',
'Oh, cookies! I can\'t wait to toss them.',
'Can you hurry up. My horse is getting tired.',
'Make it quick because my horse is getting tired.',
'What killed the dinosaurs? The Ice Age!',
'That\'s for sleeping with my wife!',
'Remember when I said I\'d kill you last... I lied!',
'You want to be a farmer? Here\'s a couple of acres',
'Now, this is the plan. Get your ass to Mars.',
'I just had a terrible thought... What if this is a dream?'
'I just had a terrible thought... What if this is a dream?',
'Well, listen to this one: Rubber baby buggy bumpers!',
'Take your toy back to the carpet!',
'My name is John Kimble... And I love my car.',
'I eat Green Berets for breakfast.',
'Put that cookie down! NOW!'
]
random_number = randint(0, len(quote_list) - 1)