Compare commits

...

131 Commits

Author SHA1 Message Date
JonnyWong16
f457704105 v2.2.0-beta 2020-02-27 14:53:29 -08:00
JonnyWong16
5aa59b93b0 Update Docker build workflows 2020-02-27 14:53:29 -08:00
JonnyWong16
47d51f92c7 Hide Live TV library from Library Statistics setting 2020-02-25 18:00:02 -08:00
JonnyWong16
068cf17d0a Only show graph series if the library type is present 2020-02-25 16:11:43 -08:00
JonnyWong16
3c1b849a5d Notify Discord on Docker build status 2020-02-25 11:26:27 -08:00
JonnyWong16
8384ec7f15 Add release workflows 2020-02-24 21:08:55 -08:00
JonnyWong16
0e7a3962bb Remove print 2020-02-24 16:20:31 -08:00
JonnyWong16
3d1bb9975c Fix interlaced video media flag on info page 2020-02-24 16:20:01 -08:00
JonnyWong16
eed473be15 Update README.md 2020-02-24 13:42:27 -08:00
JonnyWong16
e23d9bca22 Update README.md 2020-02-24 13:30:58 -08:00
JonnyWong16
85a952a5c9 Merge pull request #1364 from pkoenig10/manifest
Add crossorigin use-credentials attribute to manifest tag
2020-02-24 13:09:10 -08:00
JonnyWong16
5cc36310ba Change repo in workflow files 2020-02-24 12:18:23 -08:00
TheMeanCanEHdian
8690d2ced5 Disable updates when using Docker container 2020-02-24 10:26:52 -08:00
TheMeanCanEHdian
f572943a7b Add items needed to create Docker image 2020-02-24 10:26:50 -08:00
JonnyWong16
89248ad46a Fix typo 2020-02-23 09:35:52 -08:00
JonnyWong16
517db71916 Update API docs 2020-02-22 23:49:48 -08:00
JonnyWong16
ad82de010d Always have channel info in metadata response 2020-02-22 23:15:45 -08:00
JonnyWong16
02fd9edbbf Add value == 1 to helper.bool_true 2020-02-22 22:49:34 -08:00
JonnyWong16
6c84dd7be8 Clean up get_history filter parameters 2020-02-22 22:46:23 -08:00
JonnyWong16
9edbe6af37 Add helper to cast API parameter true to bool 2020-02-22 22:33:49 -08:00
JonnyWong16
37d09e9bad Check Plex guid before retrieving Live TV cloud metadata 2020-02-22 22:09:39 -08:00
JonnyWong16
0d0595e9a0 Add Live TV library icon 2020-02-22 17:21:22 -08:00
JonnyWong16
17477455cb Fix history table modal header for Live TV 2020-02-22 17:20:29 -08:00
JonnyWong16
43f594709d Don't include Live TV with Movies / TV Shows history filter 2020-02-22 17:09:31 -08:00
JonnyWong16
8885b3e7e0 Align library / user name 2020-02-22 16:53:41 -08:00
JonnyWong16
c060638539 Add Live TV to graphs 2020-02-22 16:31:35 -08:00
JonnyWong16
2144d4d7ed Change colours to Plex colours 2020-02-22 14:52:25 -08:00
JonnyWong16
4f2e09d733 Add link to Live TV library on info page 2020-02-22 14:09:04 -08:00
JonnyWong16
9d13c29bf6 Revert "Fix retrieving metadata for info page for Live TV without section_id"
This reverts commit 6fce31e1b9.
2020-02-22 14:08:49 -08:00
JonnyWong16
60d577f95e Revert "Add setting to enable Live TV logging"
This reverts commit 5d1bc3cf9b.
2020-02-22 14:02:34 -08:00
JonnyWong16
1dd1c6f67f Add fake Live TV library 2020-02-22 14:02:03 -08:00
JonnyWong16
bcd0691b33 Fix Live TV poster fallback for notifications 2020-02-22 13:11:27 -08:00
JonnyWong16
24a2559ab5 Fix channel popover on info page 2020-02-22 13:10:59 -08:00
JonnyWong16
22a6bae4cf Add new Live TV art fallback for info page 2020-02-22 12:42:40 -08:00
JonnyWong16
2c45de1fe5 Add Live TV channel notification parameters 2020-02-22 12:12:45 -08:00
JonnyWong16
0ab93d7a7f Fix Live TV roll over metadata caching 2020-02-22 11:39:27 -08:00
JonnyWong16
c8831efb28 Update activity card on Live TV rollover 2020-02-20 20:51:05 -08:00
JonnyWong16
0cbde5a2f5 Fix ambiguous column name in Live TV session grouping 2020-02-20 19:10:12 -08:00
JonnyWong16
7de82d87f7 Update history grouping for Live TV only if the user's last session has a matching guid 2020-02-20 18:59:12 -08:00
JonnyWong16
751b97a39c Stop Live TV session when guid changes 2020-02-20 18:58:36 -08:00
JonnyWong16
7b58bcc279 Check for metadata returned from metadata.provider.plex.tv 2020-02-20 13:18:47 -08:00
JonnyWong16
820a2e688c Add channel icon popover to info page 2020-02-20 12:30:54 -08:00
JonnyWong16
10a7f540ad Fix filtering history using guid 2020-02-20 12:10:51 -08:00
JonnyWong16
31a6c627af Fix incorrect source=history in info page URL for active sessions on history table 2020-02-20 11:30:17 -08:00
JonnyWong16
aba4cbf9e4 Write guid to session table 2020-02-20 11:20:29 -08:00
JonnyWong16
a5624e86e4 Replace single quotes in page query params 2020-02-20 11:09:33 -08:00
JonnyWong16
5480d09a0b Add library and user page helper functions 2020-02-20 10:13:27 -08:00
JonnyWong16
5fad0a1d97 Refactor page helper functions 2020-02-20 09:00:10 -08:00
JonnyWong16
4a7b5bab54 Update all info page links with helper function 2020-02-20 00:01:30 -08:00
JonnyWong16
fe0557dcc1 Add helper function for info page 2020-02-20 00:00:04 -08:00
JonnyWong16
e4b6d61098 Add option to retrieve history using guid 2020-02-19 23:57:55 -08:00
JonnyWong16
73e01ebaaf Use pms_image_proxy helper function for datatables 2020-02-19 21:19:44 -08:00
JonnyWong16
d752d46676 Update pms_image_proxy with helper function and encode URL params 2020-02-19 19:57:56 -08:00
JonnyWong16
3932409fb4 Fix info page redirect with invalid rating key 2020-02-18 15:55:27 -08:00
JonnyWong16
f69d4f1c42 Fix typo in activity bandwidth header 2020-02-18 15:30:02 -08:00
JonnyWong16
96699fc3b0 Remove debug logging 2020-02-18 08:52:19 -08:00
JonnyWong16
97ec8f6828 Add some debug logging 2020-02-18 08:43:48 -08:00
JonnyWong16
df851e67f9 Get additional Live TV metadata from metadata.provider.plex.tv 2020-02-17 20:52:16 -08:00
JonnyWong16
5d1bc3cf9b Add setting to enable Live TV logging 2020-02-17 17:53:13 -08:00
JonnyWong16
81ab9b006d Add Live TV history filter button 2020-02-17 17:41:28 -08:00
JonnyWong16
1699fc09cf Get history using title and year for Live TV info page 2020-02-17 17:32:52 -08:00
JonnyWong16
f8b6a9f1e8 Fix showing Gbps bandwidth on activity card 2020-02-17 17:08:12 -08:00
JonnyWong16
ba465a0d15 Move live key to main metadata dict 2020-02-17 16:11:26 -08:00
JonnyWong16
af521b4058 Fix air date for active sessions on history table 2020-02-17 15:59:02 -08:00
JonnyWong16
27b512611f Change channel popover background and enlarge 2020-02-17 15:57:15 -08:00
JonnyWong16
b79f165a27 More fix for poster/background not showing for Live TV watch statistics 2020-02-17 15:53:56 -08:00
JonnyWong16
716f76baed Fix poster/background not showing for Live TV watch statistics 2020-02-17 15:23:36 -08:00
JonnyWong16
18c57a4fc6 Fix recently watched on user page 2020-02-17 15:04:38 -08:00
JonnyWong16
c26483b4b8 Add channel thumb popover back onto activity card 2020-02-17 13:44:03 -08:00
JonnyWong16
441e39d776 Add air date to tables for non-episodic Live TV 2020-02-17 13:37:58 -08:00
JonnyWong16
7ecc075c7e Correct rating_key link for watch statistics 2020-02-17 12:44:47 -08:00
JonnyWong16
2e42663832 Fix watch statistic links for Live TV 2020-02-17 12:42:00 -08:00
JonnyWong16
075f9f8cbd Fallback to Live TV art/poster for watch statistics 2020-02-17 12:06:24 -08:00
JonnyWong16
f3d42e7b53 Show Gbps bandwidth 2020-02-17 12:00:00 -08:00
JonnyWong16
fa0b547b32 Fix library recenly watched missing live key 2020-02-17 10:51:44 -08:00
JonnyWong16
8f19cdccfd Add rating_key to all uses of pms_image_proxy 2020-02-17 10:51:18 -08:00
JonnyWong16
5c207aeee6 Fix Live TV shown on user page and user tables 2020-02-16 21:04:33 -08:00
JonnyWong16
d48273ef98 Fix "Live TV" in activity card subtitle for episodic tv 2020-02-16 20:43:48 -08:00
JonnyWong16
a7803dcad7 Fix Live TV air date on info page 2020-02-16 20:36:53 -08:00
JonnyWong16
4fc9b6fdb4 More Live TV fixes for info page 2020-02-16 20:13:27 -08:00
JonnyWong16
73f8f83658 Fallback pms_image_proxy when no image imput received 2020-02-16 20:06:35 -08:00
JonnyWong16
66a0f953b3 Fix missing channel info for Live TV history on info page 2020-02-16 19:56:27 -08:00
JonnyWong16
f189eea32b Use Live TV added_at date to fake air date 2020-02-16 19:53:51 -08:00
JonnyWong16
be29d879a7 Fix missing title on info page for Live TV episodes 2020-02-16 19:39:43 -08:00
JonnyWong16
e3e906c9e5 Remove click through link to Plex Web on info page for Live TV 2020-02-16 19:37:58 -08:00
JonnyWong16
6fce31e1b9 Fix retrieving metadata for info page for Live TV without section_id 2020-02-16 19:33:40 -08:00
JonnyWong16
db1cb3d658 Forgot saving channel info to sessions db table 2020-02-16 19:07:08 -08:00
JonnyWong16
c0d7c5ddff Disable Recently Added Notification button on info page for Live TV 2020-02-16 19:05:41 -08:00
JonnyWong16
d0cd2672dd Fix Live TV artwork on activity card 2020-02-16 19:02:29 -08:00
JonnyWong16
ca55900d40 Update info page for Live TV 2020-02-16 16:23:58 -08:00
JonnyWong16
3d65ffc6d3 Update font-awesome icon for Live TV 2020-02-16 16:23:42 -08:00
JonnyWong16
f94b796c2b Fix saving channel info to database 2020-02-16 16:06:43 -08:00
JonnyWong16
66c1fd6593 Save Live TV sessions to the database 2020-02-16 14:54:48 -08:00
JonnyWong16
13a45facf9 Update pms_image_proxy to work with web URLs 2020-02-15 20:14:50 -08:00
JonnyWong16
26f10b2c3d Add poster to activity card for Live TV movies 2020-02-15 14:36:51 -08:00
JonnyWong16
9aea4c85b0 Remove test parameters from Live TV 2020-02-15 14:28:19 -08:00
JonnyWong16
ee1b0eeeff Add Live TV metadata to activity card 2020-02-15 13:29:25 -08:00
JonnyWong16
c6cfb4a020 More Live TV fixes 2020-02-15 11:44:11 -08:00
JonnyWong16
91da41ff86 Fix Live TV on PMS 1.18.7.2415 crashing Tautulli activity 2020-02-15 10:48:01 -08:00
JonnyWong16
a811edb236 Fix race condition for notification stream count when stopping playback 2020-02-15 10:03:11 -08:00
Patrick Koenig
66de806b02 Add crossorigin use-credentials attribute to manifest tag 2020-02-14 08:11:07 -08:00
JonnyWong16
c825176563 v2.1.44 2020-02-05 21:41:31 -08:00
JonnyWong16
7ae87fe0e7 Fix SDR source video being identified as HDR stream video 2020-02-05 21:37:33 -08:00
JonnyWong16
99f826c236 Fix video color notification condition types 2020-02-05 20:24:43 -08:00
JonnyWong16
f88d673e87 Remove capitalization for platform on history tables 2020-02-04 13:48:29 -08:00
JonnyWong16
bc2f73d686 v2.1.43 2020-02-03 18:27:41 -08:00
JonnyWong16
645c2ecef6 Use Authorization header for GitHub API 2020-02-03 18:11:56 -08:00
JonnyWong16
4d4a8ca3b2 Trim MaxMind License Key when saving 2020-01-28 10:38:21 -08:00
JonnyWong16
a98bc45c10 Fix HDR for direct stream video 2020-01-24 20:14:48 -08:00
JonnyWong16
b9d5e49a71 Make verbose logging an advanced config option (Fixes Tautulli/Tautulli-Issues#205)
* Verbose logging is enabled by default
* Make toggleVerbose stick
2020-01-20 20:49:37 -08:00
JonnyWong16
e90390be67 Fix plural episodes and albums in newsletter templates (Fixes Tautulli/Tautulli-Issues#203) 2020-01-20 20:08:45 -08:00
JonnyWong16
8ef671c9cb Fix Telegram notification character limit (Closes #1361) 2020-01-20 19:56:38 -08:00
JonnyWong16
8829516cae Fix season 0 number not showing in tables 2020-01-20 19:23:16 -08:00
JonnyWong16
d8e8dfbd45 Revert "Match session user using user_id instead of username"
This reverts commit 6380de3e6c.
2020-01-19 16:43:34 -08:00
JonnyWong16
0b6d9a4890 Add email address log filter 2020-01-19 16:03:10 -08:00
JonnyWong16
55ffd68023 Refactor log filter 2020-01-19 16:02:56 -08:00
JonnyWong16
6380de3e6c Match session user using user_id instead of username 2020-01-19 16:00:36 -08:00
JonnyWong16
d6220a921a Log dynamic range to history and show in stream info modal 2020-01-18 11:46:43 -08:00
JonnyWong16
10e421b9d4 Add color and dynamic range to notification parameters 2020-01-15 12:19:43 -08:00
JonnyWong16
90056bcce2 Indicate HDR content on activity cards 2020-01-15 11:38:50 -08:00
JonnyWong16
4740d0fbf3 Change webhook request data if not Content-Type header is not application/json 2020-01-14 17:36:25 -08:00
JonnyWong16
13374c9ded Fix Windows and macOS platform capitalization 2020-01-14 17:35:35 -08:00
JonnyWong16
2a03be1905 Fix typo in CHANGELOG.md 2020-01-04 17:49:27 -08:00
JonnyWong16
c8f132a750 v2.1.42 2020-01-04 17:46:03 -08:00
JonnyWong16
4d0c4bf4f4 Update GeoLite2 database help text to leave blank for default 2020-01-04 12:03:44 -08:00
JonnyWong16
e321c5b197 Fix GeoLite2 update log message to correct number of days from setting 2020-01-04 11:50:48 -08:00
JonnyWong16
badbfdc4c1 Disable uninstall GeoLite2 database button when not installed 2020-01-04 10:52:11 -08:00
JonnyWong16
7d71086a41 Verify MaxMind license key and GeoLite2 database path before allowing install 2020-01-02 21:28:11 -08:00
JonnyWong16
0e1764755a Change save setting callbacks 2020-01-02 21:25:15 -08:00
JonnyWong16
c31d3ffd6c Download from MaxMind using urllib3 using certifi 2020-01-01 10:10:52 -08:00
JonnyWong16
0cba3988aa Update certifi to 2019.11.28 2020-01-01 10:10:05 -08:00
81 changed files with 2437 additions and 971 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
.git
.github
.gitignore
*.md
!CHANGELOG*.md

View File

@@ -0,0 +1,30 @@
name: Publish Docker Branch
on:
push:
branches: [master, beta, nightly]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@master
- name: Get Branch
run: echo ::set-env name=BRANCH::${GITHUB_REF#refs/heads/}
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@master
env:
VERSION: ${{ github.sha }}
with:
name: tautulli/tautulli
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
dockerfile: Dockerfile
buildargs: VERSION, BRANCH
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
job: ${{ github.workflow }}
nofail: true

View File

@@ -0,0 +1,34 @@
name: Publish Docker Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@master
- name: Get Branch
run: echo ::set-env name=BRANCH::${GITHUB_REF#refs/heads/}
- name: Get Release Version
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF/refs\/tags\//}
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@master
env:
VERSION: ${{ github.sha }}
with:
name: tautulli/tautulli
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
dockerfile: Dockerfile
buildargs: VERSION, BRANCH
tags: ${{ env.RELEASE_VERSION }}
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
job: ${{ github.workflow }}
nofail: true

View File

@@ -0,0 +1,29 @@
name: Create Pre-Release
on:
push:
tags:
- 'v*-beta'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@master
- name: Get Release Version
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF/refs\/tags\//}
- name: Get Changelog
run: echo ::set-env name=CHANGELOG::"$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md | sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )"
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.RELEASE_VERSION }}
release_name: Tautulli ${{ env.RELEASE_VERSION }}
body: |
## Changelog
##${{ env.CHANGELOG }}
draft: false
prerelease: true

View File

@@ -0,0 +1,30 @@
name: Create Release
on:
push:
tags:
- 'v*'
- '!v*-beta'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@master
- name: Get Release Version
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF/refs\/tags\//}
- name: Get Changelog
run: echo ::set-env name=CHANGELOG::"$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md | sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )"
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.RELEASE_VERSION }}
release_name: Tautulli ${{ env.RELEASE_VERSION }}
body: |
## Changelog
##${{ env.CHANGELOG }}
draft: false
prerelease: false

120
API.md
View File

@@ -395,7 +395,11 @@ Returns:
"banner": "/library/metadata/1219/banner/1503306930",
"bif_thumb": "/library/parts/274169/indexes/sd/1000",
"bitrate": "10617",
"channel_call_sign": "",
"channel_identifier": "",
"channel_stream": 0,
"channel_thumb": "",
"children_count": "",
"collections": [],
"container": "mkv",
"content_rating": "TV-MA",
@@ -427,13 +431,15 @@ Returns:
"ip_address": "10.10.10.1",
"ip_address_public": "64.123.23.111",
"is_admin": 1,
"is_allow_sync": null,
"is_allow_sync": 1,
"is_home_user": 1,
"is_restricted": 0,
"keep_history": 1,
"labels": [],
"last_viewed_at": "1462165717",
"library_name": "TV Shows",
"live": 0,
"live_uuid": "",
"local": "1",
"location": "lan",
"machine_id": "lmd93nkn12k29j2lnm",
@@ -442,8 +448,8 @@ Returns:
"optimized_version": 0,
"optimized_version_profile": "",
"optimized_version_title": "",
"originally_available_at": "2016-04-24",
"original_title": "",
"originally_available_at": "2016-04-24",
"parent_guid": "com.plexapp.agents.thetvdb://121361/6?lang=en",
"parent_media_index": "6",
"parent_rating_key": "153036",
@@ -463,6 +469,7 @@ Returns:
"rating_key": "153037",
"relay": 0,
"section_id": "2",
"secure": 1,
"session_id": "helf15l3rxgw01xxe0jf3l3d",
"session_key": "27",
"shared_libraries": [
@@ -501,15 +508,21 @@ Returns:
"stream_subtitle_location": "",
"stream_video_bit_depth": "8",
"stream_video_bitrate": "10233",
"stream_video_chroma_subsampling": "4:2:0",
"stream_video_codec": "h264",
"stream_video_codec_level": "41",
"stream_video_color_primaries": "",
"stream_video_color_range": "tv",
"stream_video_color_space": "bt709",
"stream_video_color_trc": "",
"stream_video_decision": "direct play",
"stream_video_dynamic_range": "SDR",
"stream_video_framerate": "24p",
"stream_video_full_resolution": "1080p",
"stream_video_height": "1078",
"stream_video_language": "",
"stream_video_language_code": "",
"stream_video_ref_frames": "4",
"stream_video_full_resolution": "1080p",
"stream_video_resolution": "1080",
"stream_video_scan_type": "progressive",
"stream_video_width": "1920",
@@ -559,9 +572,15 @@ Returns:
"username": "LordCommanderSnow",
"video_bit_depth": "8",
"video_bitrate": "10233",
"video_chroma_subsampling": "4:2:0",
"video_codec": "h264",
"video_codec_level": "41",
"video_color_primaries": "",
"video_color_range": "tv",
"video_color_space": "bt709",
"video_color_trc": ",
"video_decision": "direct play",
"video_dynamic_range": "SDR",
"video_frame_rate": "23.976",
"video_framerate": "24p",
"video_full_resolution": "1080p",
@@ -671,8 +690,9 @@ Optional parameters:
grandparent_rating_key (int): 351
start_date (str): "YYYY-MM-DD"
section_id (int): 2
media_type (str): "movie", "episode", "track"
media_type (str): "movie", "episode", "track", "live"
transcode_decision (str): "direct play", "copy", "transcode",
guid (str): Plex guid for an item, e.g. "com.plexapp.agents.thetvdb://121361/6/1"
order_column (str): "date", "friendly_name", "ip_address", "platform", "player",
"full_title", "started", "paused_counter", "stopped", "duration"
order_dir (str): "desc" or "asc"
@@ -697,10 +717,13 @@ Returns:
"original_title": "",
"group_count": 1,
"group_ids": "1124",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1124,
"ip_address": "xxx.xxx.xxx.xxx",
"live": 0,
"media_index": 17,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 7,
"parent_rating_key": 544,
"parent_title": "",
@@ -758,8 +781,10 @@ Returns:
[{"content_rating": "TV-MA",
"friendly_name": "",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"labels": [],
"last_play": 1462380698,
"live": 0,
"media_type": "episode",
"platform": "",
"platform_type": "",
@@ -860,15 +885,18 @@ Returns:
"do_notify": "Checked",
"do_notify_created": "Checked",
"duration": 1578037,
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1128,
"keep_history": "Checked",
"labels": [],
"last_accessed": 1462693216,
"last_played": "Game of Thrones - The Red Woman",
"library_art": "/:/resources/show-fanart.jpg",
"library_thumb": "",
"library_thumb": "/:/resources/show.png",
"live": 0,
"media_index": 1,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_count": 240,
"parent_media_index": 6,
"parent_title": "",
@@ -958,6 +986,7 @@ Returns:
"rating_key": "1219",
"section_id": 2,
"section_type": "show",
"sort_title": "Game of Thrones",
"thumb": "/library/metadata/1219/thumb/1436265995",
"title": "Game of Thrones",
"video_codec": "",
@@ -1124,6 +1153,7 @@ Returns:
"labels": [],
"last_viewed_at": "1462165717",
"library_name": "TV Shows",
"live": 0,
"media_index": "1",
"media_info": [
{
@@ -1133,6 +1163,9 @@ Returns:
"audio_codec": "ac3",
"audio_profile": "",
"bitrate": "10617",
"channel_call_sign": "",
"channel_identifier": "",
"channel_thumb": "",
"container": "mkv",
"height": "1078",
"id": "257925",
@@ -1151,6 +1184,10 @@ Returns:
"video_bitrate": "10233",
"video_codec": "h264",
"video_codec_level": "41",
"video_color_primaries": "",
"video_color_range": "tv",
"video_color_space": "bt709",
"video_color_trc": "",
"video_frame_rate": "23.976",
"video_height": "1078",
"video_language": "",
@@ -1210,7 +1247,7 @@ Returns:
"rating_image": "rottentomatoes://image.rating.ripe",
"rating_key": "153037",
"section_id": "2",
"sort_title": "Game of Thrones",
"sort_title": "Red Woman",
"studio": "HBO",
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
"tagline": "",
@@ -1506,7 +1543,8 @@ Returns:
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -1532,7 +1570,8 @@ Returns:
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -1558,7 +1597,8 @@ Returns:
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -1662,7 +1702,8 @@ Returns:
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -1688,7 +1729,8 @@ Returns:
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -1714,7 +1756,8 @@ Returns:
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -1802,22 +1845,59 @@ Optional parameters:
Returns:
json:
{"recently_added":
[{"added_at": "1461572396",
[{"actors": [
"Kit Harington",
"Emilia Clarke",
"Isaac Hempstead-Wright",
"Maisie Williams",
"Liam Cunningham",
],
"added_at": "1461572396",
"art": "/library/metadata/1219/art/1462175063",
"audience_rating": "8",
"audience_rating_image": "rottentomatoes://image.rating.upright",
"banner": "/library/metadata/1219/banner/1462175063",
"directors": [
"Jeremy Podeswa"
],
"duration": "2998290",
"full_title": "Game of Thrones - The Red Woman",
"genres": [
"Adventure",
"Drama",
"Fantasy"
],
"grandparent_rating_key": "1219",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"grandparent_title": "Game of Thrones",
"library_name": "",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"labels": [],
"last_viewed_at": "1462165717",
"library_name": "TV Shows",
"media_index": "1",
"media_type": "episode",
"original_title": "",
"originally_available_at": "2016-04-24",
"parent_media_index": "6",
"parent_rating_key": "153036",
"parent_thumb": "/library/metadata/153036/thumb/1462175062",
"parent_title": "",
"rating": "7.8",
"rating_image": "rottentomatoes://image.rating.ripe",
"rating_key": "153037",
"section_id": "2",
"sort_title": "Red Woman",
"studio": "HBO",
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
"tagline": "",
"thumb": "/library/metadata/153037/thumb/1462175060",
"title": "The Red Woman",
"user_rating": "9.0",
"updated_at": "1462175060",
"writers": [
"David Benioff",
"D. B. Weiss"
],
"year": "2016"
},
{...},
@@ -1999,6 +2079,7 @@ Returns:
"stream_video_bitrate": 527,
"stream_video_codec": "h264",
"stream_video_decision": "transcode",
"stream_video_dynamic_range": "SDR",
"stream_video_framerate": "24p",
"stream_video_height": 306,
"stream_video_resolution": "SD",
@@ -2013,6 +2094,7 @@ Returns:
"video_bitrate": 2500,
"video_codec": "h264",
"video_decision": "transcode",
"video_dynamic_range": "SDR",
"video_framerate": "24p",
"video_height": 816,
"video_resolution": "1080",
@@ -2166,12 +2248,15 @@ Returns:
"recordsFiltered": 10,
"data":
[{"friendly_name": "Jon Snow",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1121,
"ip_address": "xxx.xxx.xxx.xxx",
"last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869,
"live": 0,
"media_index": 1,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 6,
"parent_title": "",
"platform": "Chrome",
@@ -2371,13 +2456,16 @@ Returns:
"do_notify": "Checked",
"duration": 2998290,
"friendly_name": "Jon Snow",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1121,
"ip_address": "xxx.xxx.xxx.xxx",
"keep_history": "Checked",
"last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869,
"live": 0,
"media_index": 1,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 6,
"parent_title": "",
"platform": "Chrome",
@@ -2516,10 +2604,10 @@ Optional parameters:
width (str): 300
height (str): 450
opacity (str): 25
background (str): 282828
background (str): Hex color, e.g. 282828
blur (str): 3
img_format (str): png
fallback (str): "poster", "cover", "art"
fallback (str): "poster", "cover", "art", "poster-live", "art-live", "art-live-full"
refresh (bool): True or False whether to refresh the image cache
return_hash (bool): True or False to return the self-hosted image hash instead of the image

View File

@@ -1,5 +1,69 @@
# Changelog
## v2.2.0-beta (2020-02-27)
* Important Note!
* All Live TV changes requires Plex Media Server 1.18.7 or higher.
* Monitoring:
* New: Added Live TV metadata and posters to the activity cards.
* Change: Show bandwidth in Gbps when greater than 1000 Mbps.
* History:
* New: Added history logging for Live TV sessions.
* New: Added a fake "Live TV" library to collect Live TV history.
* Note: This library will show up the first time that Live TV is played.
* New: Added the ability to filter history by Live TV.
* Graphs:
* New: Added Live TV series to the "Plays by Period" and "Play Totals" graphs.
* Change: Media type series on the graphs are only shown if the corresponding library type is present.
* Notifications:
* Fix: Race condition causing stream count to be incorrect for playback stop notifications.
* New: Added Live TV channel notification parameters.
* API:
* New: Added ability to filter history using a "live" media type and by guid for the get_history API command.
* Other:
* Change: Add crossorigin use-credentials attribute to manifest tags. (Thanks @pkoenig10)
* Change: Disable automatic updates for Docker containers. Updates are now handled by updating the Docker container.
* Note: If you are using an old Docker container created before v2.2.0, then you may need to completely remove and recreate the container to update for the first time.
* Note: Use the ":latest" Docker tag for the newest stable release, or the ":beta" or ":nightly" tags to access the beta or nightly branches.
## v2.1.44 (2020-02-05)
* Monitoring:
* Fix: SDR source video being identified as HDR stream video.
* Notifications:
* Fix: Unable to select condition operator for video color parameters.
* UI:
* Fix: Capitalization for platforms on history tables.
## v2.1.43 (2020-02-03)
* Monitoring:
* New: Added HDR indicator on activity card.
* New: Added dynamic range to history steam info modal.
* Notifications:
* Fix: Webhook notification body sent as incorrect data type when Content-Type header is overridden.
* Fix: Telegram notification character limit incorrect for unicode characters.
* New: Added color and dynamic range notification parameters.
* Newsletters:
* Fix: Episodes and Albums plural spelling on recently added newsletter section headers.
* UI:
* Fix: Windows and macOS platform capitalization.
* Fix: Season number 0 not shown for episodes on history tables.
* Other:
* Change: Mask email addresses in logs.
* Change: Update deprecated GitHub access token URL parameter to Authorization header.
## v2.1.42 (2020-01-04)
* Other:
* Fix: SSL certificate error when installing GeoLite2 database.
* Change: Verify MaxMind license key and GeoLite2 database path before installing.
* Change: Disable GeoLite2 database uninstall button when it is not installed.
## v2.1.41 (2019-12-30)
* Other:

31
Dockerfile Normal file
View File

@@ -0,0 +1,31 @@
FROM python:2.7.17-slim
LABEL maintainer="TheMeanCanEHdian"
ARG VERSION
ARG BRANCH
ENV TAUTULLI_DOCKER=True
ENV TZ=UTC
WORKDIR /app
RUN \
apt-get -q -y update --no-install-recommends && \
apt-get install -q -y --no-install-recommends \
curl && \
rm -rf /var/lib/apt/lists/* && \
pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir --upgrade \
pycryptodomex \
pyopenssl && \
echo ${VERSION} > /app/version.txt && \
echo ${BRANCH} > /app/branch.txt
COPY . /app
CMD [ "python", "Tautulli.py", "--datadir", "/config" ]
VOLUME /config /plex_logs
EXPOSE 8181
HEALTHCHECK --start-period=90s CMD curl -ILfSs http://localhost:8181/status > /dev/null || curl -ILfkSs https://localhost:8181/status > /dev/null || exit 1

View File

@@ -1,9 +1,5 @@
# Tautulli
[![Discord](https://img.shields.io/badge/Discord-Tautulli-7289DA.svg?style=flat-square)](https://tautulli.com/discord)
[![Reddit](https://img.shields.io/badge/Reddit-Tautulli-FF5700.svg?style=flat-square)](https://www.reddit.com/r/Tautulli/)
[![Plex Forums](https://img.shields.io/badge/Plex%20Forums-Tautulli-E5A00D.svg?style=flat-square)](https://forums.plex.tv/t/tautulli-monitor-your-plex-media-server/225242)
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).
@@ -31,7 +27,21 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
![Tautulli Homepage](https://tautulli.com/images/screenshots/activity-compressed.jpg?v=2)
## Installation and Support
## Installation & Support
[![Python](https://img.shields.io/badge/python-v2.7.17-blue?style=flat-square)](https://python.org/downloads/release/python-2717/)
[![Docker Pulls](https://img.shields.io/docker/pulls/tautulli/tautulli?style=flat-square)](https://hub.docker.com/r/tautulli/tautulli)
[![Docker Stars](https://img.shields.io/docker/stars/tautulli/tautulli?style=flat-square)](https://hub.docker.com/r/tautulli/tautulli)
| Status | Branch: `master` | Branch: `beta` | Branch: `nightly` |
| --- | --- | --- | --- |
| Release | [![Release@master](https://img.shields.io/github/v/release/Tautulli/Tautulli?style=flat-square)](https://github.com/Tautulli/Tautulli/releases/latest) <br> [![Release Date@master](https://img.shields.io/github/release-date/Tautulli/Tautulli?style=flat-square&color=blue)](https://github.com/Tautulli/Tautulli/releases/latest) | [![Release@beta](https://img.shields.io/github/v/release/Tautulli/Tautulli?include_prereleases&style=flat-square)](https://github.com/Tautulli/Tautulli/releases) <br> [![Commits@nightly](https://img.shields.io/github/commits-since/Tautulli/Tautulli/latest/beta?style=flat-square&color=blue)](https://github.com/Tautulli/Tautulli/commits/beta) | [![Last Commits@nightly](https://img.shields.io/github/last-commit/Tautulli/Tautulli/nightly?style=flat-square&color=blue)](https://github.com/Tautulli/Tautulli/commits/nightly) <br> [![Commits@nightly](https://img.shields.io/github/commits-since/Tautulli/Tautulli/latest/nightly?style=flat-square&color=blue)](https://github.com/Tautulli/Tautulli/commits/nightly) |
| Docker | [![Docker@master](https://img.shields.io/badge/tautulli-tautulli:latest-blue?style=flat-square)](https://hub.docker.com/r/tautulli/tautulli) <br> [![Docker Build@master](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker%20Branch/master?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=branch%3Amaster) | [![Docker@beta](https://img.shields.io/badge/tautulli-tautulli:beta-blue?style=flat-square)](https://hub.docker.com/r/tautulli/tautulli) <br> [![Docker Build@beta](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker%20Branch/beta?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=branch%3Abeta) | [![Docker@nightly](https://img.shields.io/badge/tautulli-tautulli:nightly-blue?style=flat-square)](https://hub.docker.com/r/tautulli/tautulli) <br> [![Docker Build@nightly](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker%20Branch/nightly?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=branch%3Anightly) |
[![Wiki](https://img.shields.io/badge/github-wiki-black?style=flat-square)](https://github.com/Tautulli/Tautulli-Wiki/wiki)
[![Discord](https://img.shields.io/discord/183396325142822912?label=discord&style=flat-square&color=7289DA)](https://tautulli.com/discord)
[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/tautulli?label=reddit&style=flat-square&color=FF5700)](https://www.reddit.com/r/Tautulli/)
[![Plex Forums](https://img.shields.io/badge/plex%20forums-discussion-E5A00D?style=flat-square)](https://forums.plex.tv/t/tautulli-monitor-your-plex-media-server/225242)
* Read the [Installation Guides](https://github.com/Tautulli/Tautulli-Wiki/wiki/Installation) for instructions to install Tautulli.
* The [Frequently Asked Questions](https://github.com/Tautulli/Tautulli-Wiki/wiki/Frequently-Asked-Questions) in the wiki can help you with common problems.
@@ -39,10 +49,15 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
## Issues & Feature Requests
[![Issues](https://img.shields.io/badge/github-issues-red?style=flat-square)](https://github.com/Tautulli/Tautulli-Issues)
[![Feathub](https://img.shields.io/badge/feathub-requests-lightgrey?style=flat-square)](https://feathub.com/Tautulli/Tautulli)
* Please see the [Issues Repository](https://github.com/Tautulli/Tautulli-Issues).
## License
[![License](https://img.shields.io/github/license/Tautulli/Tautulli?style=flat-square)](https://github.com/Tautulli/Tautulli/blob/master/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 software includes Highsoft software libraries which you may freely distribute for non-commercial use. Commerical users must licence this software, for more information visit https://shop.highsoft.com/faq/non-commercial#non-commercial-redistribution.

View File

@@ -36,7 +36,7 @@ import time
import tzlocal
import plexpy
from plexpy import config, database, logger, webstart
from plexpy import config, database, helpers, logger, webstart
# Register signals, such as CTRL + C
@@ -106,8 +106,8 @@ def main():
plexpy.QUIET = True
# Do an intial setup of the logger.
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
verbose=plexpy.VERBOSE)
# Require verbose for pre-initilization to see critical errors
logger.initLogger(console=not plexpy.QUIET, log_dir=False, verbose=True)
try:
plexpy.SYS_TIMEZONE = tzlocal.get_localzone()
@@ -117,7 +117,7 @@ def main():
plexpy.SYS_UTC_OFFSET = datetime.datetime.now(plexpy.SYS_TIMEZONE).strftime('%z')
if os.getenv('TAUTULLI_DOCKER', False) == 'True':
if helpers.bool_true(os.getenv('TAUTULLI_DOCKER', False)):
plexpy.DOCKER = True
if args.dev:

View File

@@ -28,7 +28,7 @@
<!-- ICONS -->
<!-- Android -->
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5">
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5" crossorigin="use-credentials">
<meta name="theme-color" content="#282a2d">
<!-- Apple -->
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">
@@ -43,23 +43,23 @@
<div class="container">
<div id="ajaxMsg" class="ajaxMsg"></div>
% if _session['user_group'] == 'admin':
% if plexpy.CONFIG.CHECK_GITHUB and plexpy.UPDATE_AVAILABLE is None:
% if plexpy.CONFIG.CHECK_GITHUB and plexpy.UPDATE_AVAILABLE is not False:
<div id="updatebar" style="display: none;">
% if plexpy.UPDATE_AVAILABLE is None:
You are running an unknown version of Tautulli.<br />
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
</div>
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.UPDATE_AVAILABLE == 'release':
<div id="updatebar" style="display: none;">
% elif plexpy.UPDATE_AVAILABLE == 'release':
A <a href="${anon_url('https://github.com/%s/%s/releases/tag/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.LATEST_RELEASE))}" target="_blank">
new release (${plexpy.LATEST_RELEASE})</a> of Tautulli is available!<br />
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
</div>
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.UPDATE_AVAILABLE == 'commit':
<div id="updatebar" style="display: none;">
% elif plexpy.UPDATE_AVAILABLE == 'commit':
A <a href="${anon_url('https://github.com/%s/%s/compare/%s...%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CURRENT_VERSION, plexpy.LATEST_VERSION))}" target="_blank">
newer version</a> of Tautulli is available!<br />
You are ${plexpy.COMMITS_BEHIND} commit${'s' if plexpy.COMMITS_BEHIND > 1 else ''} behind.<br />
% endif
% if plexpy.DOCKER:
Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>
% else:
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
% endif
</div>
% else:
<div id="updatebar" style="display: none;"></div>
@@ -318,21 +318,23 @@ ${next.modalIncludes()}
complete: function (xhr, status) {
var result = $.parseJSON(xhr.responseText);
var msg = '';
if (result.update === null) {
msg = 'You are running an unknown version of Tautulli.<br />' +
'<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>';
$('#updatebar').html(msg).fadeIn();
} else if (result.update === true && result.release === true) {
msg = 'A <a href="' + result.release_url + '" target="_blank">new release (' + result.latest_release + ')</a> of Tautulli is available!<br />' +
'<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>';
$('#updatebar').html(msg).fadeIn();
} else if (result.update === true && result.release === false) {
msg = 'A <a href="' + result.compare_url + '" target="_blank">newer version</a> of Tautulli is available!<br />' +
'You are '+ result.commits_behind + ' commit' + (result.commits_behind > 1 ? 's' : '') + ' behind.<br />' +
'<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>';
$('#updatebar').html(msg).fadeIn();
} else if (result.update === false) {
if (result.update === false) {
showMsg('<i class="fa fa-check"></i> ' + result.message, false, true, 2000);
} else {
if (result.update === null) {
msg = 'You are running an unknown version of Tautulli.<br />';
} else if (result.update === true && result.release === true) {
msg = 'A <a href="' + result.release_url + '" target="_blank">new release (' + result.latest_release + ')</a> of Tautulli is available!<br />';
} else if (result.update === true && result.release === false) {
msg = 'A <a href="' + result.compare_url + '" target="_blank">newer version</a> of Tautulli is available!<br />' +
'You are '+ result.commits_behind + ' commit' + (result.commits_behind > 1 ? 's' : '') + ' behind.<br />';
}
if (result.docker) {
msg += 'Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>';
} else {
msg += '<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>';
}
$('#updatebar').html(msg).fadeIn();
}
if (_callback) {

View File

@@ -21,7 +21,7 @@ ul.ColVis_collection li {
.ColVis_Button:hover,
ul.ColVis_collection li:hover {
color: #F9AA03;
color: #E5A00D;
}
button.ColVis_Button {

View File

@@ -101,7 +101,7 @@ table.display tr:hover a {
text-decoration:none;
}
table.display td:hover a {
color: #F9AA03;
color: #E5A00D;
}
table.display thead tr:hover {
background-color: #212121;

View File

@@ -523,7 +523,7 @@ fieldset[disabled] .btn-bright.active {
color: #eee;
}
.modal-body i {
color: #F9AA03;
color: #E5A00D;
}
.modal-body i.fa {
color: #fff;
@@ -534,7 +534,7 @@ fieldset[disabled] .btn-bright.active {
}
.modal-body strong,
.modal-body strong i.fa {
color: #F9AA03;
color: #E5A00D;
}
.modal-footer {
padding: 15px 20px;
@@ -673,7 +673,7 @@ textarea.form-control:focus {
color: #fff;
}
.form-control-feedback {
color: #F9AA03;
color: #E5A00D;
margin: 5px 40px 5px 0;
}
.form-control[disabled],
@@ -2177,7 +2177,7 @@ li.advanced-setting {
font-size: 24px;
color: #fff;
padding-top: 27px;
padding-left: 110px;
padding-left: 105px;
}
.user-info-nav {
margin-top: 15px;
@@ -3134,6 +3134,37 @@ div.dataTables_info {
-moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
}
.channel-thumbnail-popover {
z-index: 2000;
padding: 0;
border: 0;
}
.channel-thumbnail-popover.popover.left {
margin-left: -15px;
}
.channel-thumbnail-popover.popover.right {
margin-left: 15px;
}
.channel-thumbnail-popover .popover-content {
color: #000;
padding: 0;
}
.channel-thumbnail {
background-color: #868b8b;
background-position: center;
background-size: cover;
background-origin: content-box;
background-repeat: no-repeat;
height: 50px;
width: 50px;
padding: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.channel-thumbnail-popover .arrow:after {
border-right-color: #868b8b !important;
}
.edit-user-toggles,
.edit-library-toggles {
padding-right: 10px;
@@ -3983,6 +4014,9 @@ a:hover .overlay-refresh-image:hover {
.library-video {
background-image: url(../images/libraries/video.svg);
}
.library-live {
background-image: url(../images/libraries/live.svg);
}
.stats-most_concurrent {
background-image: url(../images/icons/most-concurrent-streams.svg);
}
@@ -4033,7 +4067,7 @@ a:hover .overlay-refresh-image:hover {
table-layout: fixed;
}
.stream-info .heading {
color: #F9AA03;
color: #E5A00D;
text-transform: uppercase;
font-size: 15px;
font-weight: bold !important;

View File

@@ -62,8 +62,7 @@ DOCUMENTATION :: END
% if session is not None:
<%
from collections import defaultdict
from urllib import quote
from plexpy import helpers
from plexpy.helpers import cast_to_int, page
from plexpy.common import VIDEO_RESOLUTION_OVERRIDES, AUDIO_CODEC_OVERRIDES, EXTRA_TYPES
import plexpy
%>
@@ -71,62 +70,67 @@ DOCUMENTATION :: END
data = defaultdict(lambda: 'Unknown', **session)
sk = data['session_key']
href = 'info?rating_key={}'.format(data['rating_key']) if data['rating_key'] else '#'
parent_href = 'info?rating_key={}'.format(data['parent_rating_key']) if data['parent_rating_key'] else '#'
grandparent_href = 'info?rating_key={}'.format(data['grandparent_rating_key']) if data['grandparent_rating_key'] else '#'
user_href = 'user?user_id={}'.format(data['user_id']) if data['user_id'] else '#'
href = page('info', data['rating_key'])
parent_href = page('info', data['parent_rating_key'])
grandparent_href = page('info', data['grandparent_rating_key'])
user_href = page('user', data['user_id']) if data['user_id'] else '#'
%>
<div class="dashboard-activity-instance" id="activity-instance-${sk}" data-key="${sk}" data-id="${data['session_id']}"
data-rating_key="${data['rating_key']}" data-parent_rating_key="${data['parent_rating_key']}" data-grandparent_rating_key="${data['grandparent_rating_key']}">
data-rating_key="${data['rating_key']}" data-parent_rating_key="${data['parent_rating_key']}" data-grandparent_rating_key="${data['grandparent_rating_key']}"
data-guid="${data['guid']}">
<div class="dashboard-activity-container">
<%
if data['live'] == 1:
background_url = 'images/art-live.png'
if data['live']:
background_url = page('pms_image_proxy', data['art'], data['rating_key'], 500, 280, 40, '282828', 3, fallback='art-live', refresh=True)
elif data['channel_stream'] == 0:
background_url = 'pms_image_proxy?img=' + data['art'] + '&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art&refresh=true'
background_url = page('pms_image_proxy', data['art'], data['rating_key'], 500, 280, 40, '282828', 3, fallback='art', refresh=True)
else:
if (data['art'] and data['art'].startswith('http')) or (data['thumb'] and data['thumb'].startswith('http')):
background_url = data['art']
else:
background_url = 'pms_image_proxy?img=' + quote(data['art'] or data['thumb']) + '&width=500&height=280&fallback=art&refresh=true&clip=true'
background_url = page('pms_image_proxy', data['art'] or data['thumb'], data['rating_key'], 500, 280, 40, '282828', 3, fallback='art', refresh=True, clip=True)
%>
<div id="background-${sk}" class="dashboard-activity-background" style="background-image: url(${background_url});">
<div class="dashboard-activity-poster-container hidden-xs">
% if data['media_type'] == 'track':
<div id="poster-${sk}-bg" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['parent_thumb']}&width=300&height=300&opacity=60&background=282828&blur=3&fallback=cover&refresh=true);"></div>
<div id="poster-${sk}-bg" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['parent_thumb'], data['parent_rating_key'], 300, 300, 60, '282828', 3, fallback='cover', refresh=True)});"></div>
% endif
% if data['live'] == 1:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(images/poster-live.png);"></div>
% if data['live']:
% if data['media_type'] == 'movie':
<a id="poster-url-${sk}" href="${href}" title="${data['title']}">
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['thumb'], data['rating_key'], 300, 450, fallback='poster-live', refresh=True)});"></div>
</a>
% elif data['media_type'] == 'episode':
<a id="poster-url-${sk}" href="${href}" title="${data['grandparent_title']}">
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['grandparent_thumb'], data['rating_key'], 300, 450, fallback='poster-live', refresh=True)});"></div>
</a>
% else:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['grandparent_thumb'] or data['thumb'], data['rating_key'], 300, 450, fallback='poster-live', refresh=True)});"></div>
% endif
% elif data['channel_stream'] == 0:
% if data['media_type'] == 'movie':
<a id="poster-url-${sk}" href="${href}" title="${data['title']}">
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['thumb'], data['rating_key'], 300, 450, fallback='poster', refresh=True)});"></div>
</a>
% elif data['media_type'] == 'episode':
<a id="poster-url-${sk}" href="${grandparent_href}" title="${data['grandparent_title']}">
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['grandparent_thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['grandparent_thumb'], data['grandparent_rating_key'], 300, 450, fallback='poster', refresh=True)});"></div>
</a>
% elif data['media_type'] == 'track':
<a id="poster-url-${sk}" href="${parent_href}" title="${data['parent_title']}">
<div id="poster-${sk}" class="dashboard-activity-cover" style="background-image: url(pms_image_proxy?img=${data['parent_thumb']}&width=300&height=300&fallback=cover&refresh=true);"></div>
<div id="poster-${sk}" class="dashboard-activity-cover" style="background-image: url(${page('pms_image_proxy', data['parent_thumb'], data['parent_rating_key'], 300, 300, fallback='cover', refresh=True)});"></div>
</a>
% elif data['media_type'] in ('photo', 'clip'):
% if data['extra_type']:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['art'].replace('/art', '/thumb') or data['thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['art'].replace('/art', '/thumb') or data['thumb'], data['rating_key'], 300, 450, fallback='poster', refresh=True)});"></div>
% elif data['parent_thumb']:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['parent_thumb'], data['parent_rating_key'], 300, 450, fallback='poster', refresh=True)});"></div>
% else:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['parent_thumb'] or data['thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['thumb'], data['rating_key'], 300, 450, fallback='poster', refresh=True)});"></div>
% endif
% else:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(images/poster.png);"></div>
% endif
% else:
% if data['channel_icon'].startswith('http'):
<div id="poster-${sk}" class="dashboard-activity-poster-blur" style="background-image: url(${data['channel_icon']});"></div>
<div id="poster-${sk}" class="dashboard-activity-cover" style="background-image: url(${data['channel_icon']});"></div>
% else:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['channel_icon']}&width=300&height=300&opacity=60&background=282828&blur=3&fallback=cover&refresh=true);"></div>
<div id="poster-${sk}" class="dashboard-activity-cover" style="background-image: url(pms_image_proxy?img=${data['channel_icon']}&width=300&height=300&fallback=cover&refresh=true);"></div>
% endif
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['channel_icon'], data['rating_key'], 300, 300, 60, '282828', 3, fallback='cover', refresh=True)});"></div>
<div id="poster-${sk}" class="dashboard-activity-cover" style="background-image: url(${page('pms_image_proxy', data['channel_icon'], data['rating_key'], 300, 300, fallback='cover', refresh=True)});"></div>
% endif
</div>
<div class="dashboard-activity-info-icon">
@@ -160,7 +164,7 @@ DOCUMENTATION :: END
<div class="sub-value platform-right" id="stream_quality-${sk}">
% if data['media_type'] != 'photo' and data['quality_profile'] != 'Unknown':
<%
br = helpers.cast_to_int(data['stream_bitrate']) or ''
br = cast_to_int(data['stream_bitrate']) or ''
if br:
if br > 1000:
br = '(' + str(round(br / 1000.0, 1)) + ' Mbps)'
@@ -226,17 +230,24 @@ DOCUMENTATION :: END
<li class="dashboard-activity-info-item">
<div class="sub-heading">Video</div>
<div class="sub-value" id="video_decision-${sk}">
% if data['media_type'] in ('movie', 'episode', 'clip'):
% if data['media_type'] in ('movie', 'episode', 'clip') and data['stream_video_decision']:
<%
if data['video_dynamic_range'] == 'HDR':
video_dynamic_range = ' ' + data['video_dynamic_range']
stream_video_dynamic_range = ' ' + data['stream_video_dynamic_range']
else:
video_dynamic_range = stream_video_dynamic_range = ''
%>
% if data['stream_video_decision'] == 'transcode':
<%
hw_d = ' (HW)' if data['transcode_hw_decoding'] else ''
hw_e = ' (HW)' if data['transcode_hw_encoding'] else ''
%>
Transcode (${data['video_codec'].upper()}${hw_d} ${data['video_full_resolution']} <i class="fa fa-long-arrow-right"></i> ${data['stream_video_codec'].upper()}${hw_e} ${data['stream_video_full_resolution']})
Transcode (${data['video_codec'].upper()}${hw_d} ${data['video_full_resolution']}${video_dynamic_range} <i class="fa fa-long-arrow-right"></i> ${data['stream_video_codec'].upper()}${hw_e} ${data['stream_video_full_resolution']}${stream_video_dynamic_range})
% elif data['stream_video_decision'] == 'copy':
Direct Stream (${data['stream_video_codec'].upper()} ${data['stream_video_full_resolution']})
Direct Stream (${data['stream_video_codec'].upper()} ${data['stream_video_full_resolution']}${stream_video_dynamic_range})
% else:
Direct Play (${data['stream_video_codec'].upper()} ${data['stream_video_full_resolution']})
Direct Play (${data['stream_video_codec'].upper()} ${data['stream_video_full_resolution']}${stream_video_dynamic_range})
% endif
% elif data['media_type'] == 'photo':
Direct Play (${data['width']}x${data['height']})
@@ -248,12 +259,14 @@ DOCUMENTATION :: END
<li class="dashboard-activity-info-item">
<div class="sub-heading">Audio</div>
<div class="sub-value" id="audio_decision-${sk}">
% if data['stream_audio_decision'] == 'transcode':
Transcode (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} <i class="fa fa-long-arrow-right"></i> ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% elif data['stream_audio_decision'] == 'copy':
Direct Stream (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% else:
Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% if data['stream_audio_decision']:
% if data['stream_audio_decision'] == 'transcode':
Transcode (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} <i class="fa fa-long-arrow-right"></i> ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% elif data['stream_audio_decision'] == 'copy':
Direct Stream (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% else:
Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% endif
% endif
</div>
</li>
@@ -317,8 +330,10 @@ DOCUMENTATION :: END
<div class="sub-value time-right">
% if data['media_type'] != 'photo' and data['bandwidth'] != 'Unknown':
<%
bw = helpers.cast_to_int(data['bandwidth'])
if bw > 1000:
bw = cast_to_int(data['bandwidth'])
if bw > 1000000:
bw = str(round(bw / 1000000.0, 1)) + ' Gbps'
elif bw > 1000:
bw = str(round(bw / 1000.0, 1)) + ' Mbps'
else:
bw = str(bw) + ' kbps'
@@ -337,8 +352,8 @@ DOCUMENTATION :: END
</div>
% if data['media_type'] != 'photo':
<div class="dashboard-activity-info-time">
% if data['live'] == 1:
<br />Live
% if data['live']:
<br /><span class="thumb-tooltip" data-toggle="popover" data-img="${data['channel_thumb']}" data-height="40" data-width="40">${data['channel_call_sign']} ${data['channel_identifier']}</span>
% elif data['view_offset']:
ETA:
<span id="stream-eta-${sk}">
@@ -367,8 +382,8 @@ DOCUMENTATION :: END
</div>
<div class="dashboard-activity-progress">
<div class="dashboard-activity-progress-bar">
% if data['live'] == 1:
<div id="progress-bar-${sk}" class="progress-bar" style="width: 100%" data-toggle="tooltip" title="Stream Progress Live">Live</div>
% if data['live']:
<div id="progress-bar-${sk}" class="progress-bar" style="width: 100%" data-state="live" data-toggle="tooltip" title="Stream Progress Live">Live</div>
% else:
<div id="buffer-bar-${sk}" class="buffer-bar" style="width: ${data['transcode_progress']}%" data-toggle="tooltip" title="Transcoder Progress ${data['transcode_progress']}%">${data['transcode_progress']}%</div>
<div id="progress-bar-${sk}" class="progress-bar" style="width: ${data['progress_percent']}%" data-last_view_offset="${data['view_offset']}" data-view_offset="${data['view_offset']}" data-stream_duration="${data['stream_duration']}" data-state="${data['state']}" data-toggle="tooltip" title="Stream Progress ${data['progress_percent']}%">${data['progress_percent']}%</div>
@@ -391,7 +406,16 @@ DOCUMENTATION :: END
% endif
</div>
<div class="dashboard-activity-metadata-title">
% if data['channel_stream'] == 0:
% if data['live']:
% if data['media_type'] == 'movie':
<a href="${href}" title="${data['title']}">${data['title']}</a>
% elif data['media_type'] == 'episode':
<a href="${href}" title="${data['grandparent_title']}">${data['grandparent_title']}</a>
- <a href="${href}" title="${data['title']}">${data['title']}</a>
% else:
<span title="${data['title']}">${data['title']}</span>
% endif
% elif data['channel_stream'] == 0:
% if data['media_type'] == 'movie':
<a href="${href}" title="${data['title']}">${data['title']}</a>
% elif data['media_type'] == 'episode':
@@ -416,9 +440,9 @@ DOCUMENTATION :: END
</div>
</div>
<div class="dashboard-activity-metadata-subtitle-container">
% if data['live'] == 1:
<div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="Plex Live TV">
<i class="fa fa-fw fa-television"></i>&nbsp;
% if data['live']:
<div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="Live TV">
<i class="fa fa-fw fa-broadcast-tower"></i>&nbsp;
</div>
% elif data['channel_stream'] == 0:
<div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="${data['media_type'].capitalize()}">
@@ -440,8 +464,19 @@ DOCUMENTATION :: END
</div>
% endif
<div class="dashboard-activity-metadata-subtitle">
% if data['live'] == 1:
<span title="Plex Live TV" class="sub-heading">Plex Live TV</span>
% if data['live']:
% if data['media_type'] == 'movie':
<span title="${data['year']}" class="sub-heading">${data['year']}</span>
% elif data['media_type'] == 'episode':
% if data['media_index']:
<a href="${href}" title="Season ${data['parent_media_index']}" class="sub-heading">S${data['parent_media_index']}</a>
&middot; <a href="${href}" title="Episode ${data['media_index']}" class="sub-heading">E${data['media_index']}</a>
% else:
<a href="${href}" title="${data['originally_available_at']}" class="sub-heading">${data['originally_available_at']}</a>
% endif
% else:
<span title="Live TV" class="sub-heading">Live TV</span>
% endif
% elif data['channel_stream'] == 0:
% if data['media_type'] == 'movie':
<span title="${data['year']}" class="sub-heading">${data['year']}</span>

View File

@@ -252,6 +252,7 @@
case "TV": media_type = 'episode'; break;
case "Movies": media_type = 'movie'; break;
case "Music": media_type = 'track'; break;
case "Live TV": media_type = 'live'; break;
case "Direct Play": transcode_decision = 'direct play'; break;
case "Direct Stream": transcode_decision = 'copy'; break;
case "Transcode": transcode_decision = 'transcode'; break;
@@ -304,6 +305,23 @@
setLocalStorage(chart_key, JSON.stringify(chart_visibility));
}
function getGraphColors(data_series) {
var colors = {
'TV': '#E5A00D',
'Movies': '#FFFFFF',
'Music': '#F06464',
'Live TV': '#19A0D7',
'Direct Play': '#E5A00D',
'Direct Stream': '#FFFFFF',
'Transcode': '#F06464'
};
var series_colors = [];
$.each(data_series, function(index, series) {
series_colors.push(colors[series.name]);
});
return series_colors;
}
</script>
<script src="${http_root}js/graphs/plays_by_day.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_dayofweek.js${cache_param}"></script>
@@ -390,6 +408,7 @@
hc_plays_by_day_options.yAxis.min = 0;
hc_plays_by_day_options.xAxis.categories = dateArray;
hc_plays_by_day_options.series = getGraphVisibility(hc_plays_by_day_options.chart.renderTo, data.series);
hc_plays_by_day_options.colors = getGraphColors(data.series);
var hc_plays_by_day = new Highcharts.Chart(hc_plays_by_day_options);
}
});
@@ -403,6 +422,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_dayofweek_options.xAxis.categories = data.categories;
hc_plays_by_dayofweek_options.series = getGraphVisibility(hc_plays_by_dayofweek_options.chart.renderTo, data.series);
hc_plays_by_dayofweek_options.colors = getGraphColors(data.series);
var hc_plays_by_dayofweek = new Highcharts.Chart(hc_plays_by_dayofweek_options);
}
});
@@ -416,6 +436,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_hourofday_options.xAxis.categories = data.categories;
hc_plays_by_hourofday_options.series = getGraphVisibility(hc_plays_by_hourofday_options.chart.renderTo, data.series);
hc_plays_by_hourofday_options.colors = getGraphColors(data.series);
var hc_plays_by_hourofday = new Highcharts.Chart(hc_plays_by_hourofday_options);
}
});
@@ -429,6 +450,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_platform_options.xAxis.categories = data.categories;
hc_plays_by_platform_options.series = getGraphVisibility(hc_plays_by_platform_options.chart.renderTo, data.series);
hc_plays_by_platform_options.colors = getGraphColors(data.series);
var hc_plays_by_platform = new Highcharts.Chart(hc_plays_by_platform_options);
}
});
@@ -442,6 +464,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_user_options.xAxis.categories = data.categories;
hc_plays_by_user_options.series = getGraphVisibility(hc_plays_by_user_options.chart.renderTo, data.series);
hc_plays_by_user_options.colors = getGraphColors(data.series);
var hc_plays_by_user = new Highcharts.Chart(hc_plays_by_user_options);
}
});
@@ -478,6 +501,7 @@
hc_plays_by_stream_type_options.yAxis.min = 0;
hc_plays_by_stream_type_options.xAxis.categories = dateArray;
hc_plays_by_stream_type_options.series = getGraphVisibility(hc_plays_by_stream_type_options.chart.renderTo, data.series);
hc_plays_by_stream_type_options.colors = getGraphColors(data.series);
var hc_plays_by_stream_type = new Highcharts.Chart(hc_plays_by_stream_type_options);
}
});
@@ -491,6 +515,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_source_resolution_options.xAxis.categories = data.categories;
hc_plays_by_source_resolution_options.series = getGraphVisibility(hc_plays_by_source_resolution_options.chart.renderTo, data.series);
hc_plays_by_source_resolution_options.colors = getGraphColors(data.series);
var hc_plays_by_source_resolution = new Highcharts.Chart(hc_plays_by_source_resolution_options);
}
});
@@ -504,6 +529,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_stream_resolution_options.xAxis.categories = data.categories;
hc_plays_by_stream_resolution_options.series = getGraphVisibility(hc_plays_by_stream_resolution_options.chart.renderTo, data.series);
hc_plays_by_stream_resolution_options.colors = getGraphColors(data.series);
var hc_plays_by_stream_resolution = new Highcharts.Chart(hc_plays_by_stream_resolution_options);
}
});
@@ -517,6 +543,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_platform_by_stream_type_options.xAxis.categories = data.categories;
hc_plays_by_platform_by_stream_type_options.series = getGraphVisibility(hc_plays_by_platform_by_stream_type_options.chart.renderTo, data.series);
hc_plays_by_platform_by_stream_type_options.colors = getGraphColors(data.series);
var hc_plays_by_platform_by_stream_type = new Highcharts.Chart(hc_plays_by_platform_by_stream_type_options);
}
});
@@ -530,6 +557,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_user_by_stream_type_options.xAxis.categories = data.categories;
hc_plays_by_user_by_stream_type_options.series = getGraphVisibility(hc_plays_by_user_by_stream_type_options.chart.renderTo, data.series);
hc_plays_by_user_by_stream_type_options.colors = getGraphColors(data.series);
var hc_plays_by_user_by_stream_type = new Highcharts.Chart(hc_plays_by_user_by_stream_type_options);
}
});
@@ -553,6 +581,7 @@
hc_plays_by_month_options.yAxis.min = 0;
hc_plays_by_month_options.xAxis.categories = data.categories;
hc_plays_by_month_options.series = getGraphVisibility(hc_plays_by_month_options.chart.renderTo, data.series);
hc_plays_by_month_options.colors = getGraphColors(data.series);
var hc_plays_by_month = new Highcharts.Chart(hc_plays_by_month_options);
}
});

View File

@@ -44,6 +44,9 @@
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-track" value="track" autocomplete="off"> Music
</label>
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-live" value="live" autocomplete="off"> Live TV
</label>
</div>
<div class="btn-group">
<button class="btn btn-dark refresh-history-button" id="refresh-history-list"><i class="fa fa-refresh"></i> Refresh history</button>

View File

@@ -6,7 +6,7 @@
<h4 class="modal-title" id="myModalLabel">
<strong><span id="modal_header_ip_address">
% if data.get('media_type'):
<% h = {'episode': 'TV Show', 'track': 'Music'} %>
<% h = {'episode': 'TV Show', 'track': 'Music', 'live': 'Live TV'} %>
<i class="fa fa-history"></i> ${h.get(data['media_type'], data['media_type'].title())} History for <span id="date-header">${data['start_date']}</span>
% elif data.get('transcode_decision'):
<% h = {'copy': 'Direct Stream'} %>

View File

@@ -53,11 +53,11 @@ DOCUMENTATION :: END
</%doc>
<%!
from plexpy import helpers
from plexpy.helpers import cast_to_int, page
# Human readable duration
def hd(seconds):
m, s = divmod(helpers.cast_to_int(seconds), 60)
m, s = divmod(cast_to_int(seconds), 60)
h, m = divmod(m, 60)
return str(h).zfill(1) + ':' + str(m).zfill(2)
%>
@@ -72,11 +72,8 @@ DOCUMENTATION :: END
<div class="dashboard-stats-instance" id="stats-instance-${stat_id}" data-stat_id="${stat_id}">
<div class="dashboard-stats-container">
% if stat_id in ('top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'last_watched'):
% if row0['art']:
<div id="stats-background-${stat_id}" class="dashboard-stats-background" style="background-image: url(pms_image_proxy?img=${row0['art']}&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art);">
% else:
<div id="stats-background-${stat_id}" class="dashboard-stats-background" style="background-image: url(images/art.png);">
% endif
<% fallback = 'art-live' if row0['live'] else 'art' %>
<div id="stats-background-${stat_id}" class="dashboard-stats-background" style="background-image: url(${page('pms_image_proxy', row0['art'], row0['rating_key'], 500, 280, 40, '282828', 3, fallback=fallback)});">
% elif stat_id == 'top_platforms':
<div id="stats-background-${stat_id}" class="dashboard-stats-background platform-${row0['platform_name']}-rgba no-image">
% else:
@@ -85,20 +82,28 @@ DOCUMENTATION :: END
% if stat_id in ('top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'last_watched'):
<div class="dashboard-stats-poster-container hidden-xs">
% if stat_id in ('top_music', 'popular_music'):
<div id="stats-thumb-${stat_id}-bg" class="dashboard-stats-poster" style="background-image: url(pms_image_proxy?img=${row0['thumb']}&width=300&height=300&opacity=60&background=282828&blur=3&fallback=cover);"></div>
<div id="stats-thumb-${stat_id}-bg" class="dashboard-stats-poster" style="background-image: url(${page('pms_image_proxy', row0['thumb'], row0['rating_key'], 300, 300, 60, '282828', 3, fallback='cover')});"></div>
% endif
<% height, type = ('300', 'cover') if stat_id in ('top_music', 'popular_music') else ('450', 'poster') %>
<% href = 'info?rating_key={}'.format(row0['rating_key']) if row0['rating_key'] else '#' %>
<%
height, fallback = ('450', 'poster')
if stat_id in ('top_music', 'popular_music'):
height, fallback = ('300', 'cover')
elif row0['live']:
height, fallback = ('450', 'poster-live')
href = '#'
if row0['rating_key']:
if row0['live']:
href = page('info', row0['rating_key'], row0['guid'], history=True, live=row0['live'])
else:
href = page('info', row0['rating_key'])
%>
<a id="stats-thumb-url-${stat_id}" href="${href}" title="${row0['title']}">
% if row0['thumb']:
<div id="stats-thumb-${stat_id}" class="dashboard-stats-${type}" style="background-image: url(pms_image_proxy?img=${row0['thumb']}&width=300&height=${height}&fallback=${type});"></div>
% else:
<div id="stats-thumb-${stat_id}" class="dashboard-stats-${type}" style="background-image: url(images/${type}.png);"></div>
% endif
<div id="stats-thumb-${stat_id}" class="dashboard-stats-${fallback.split('-')[0]}" style="background-image: url(${page('pms_image_proxy', row0['thumb'], row0['rating_key'], 300, height, fallback=fallback)});"></div>
</a>
</div>
% elif stat_id == 'top_users':
<% user_href = 'user?user_id={}'.format(row0['user_id']) if row0['user_id'] else '#' %>
<% user_href = page('user', row0['user_id']) if row0['user_id'] else '#' %>
<a id="stats-thumb-url-${stat_id}" href="${user_href}" title="${row0['friendly_name']}" class="hidden-xs">
<div id="stats-thumb-${stat_id}" class="dashboard-stats-circle" style="background-image: url(${row0['user_thumb'] or 'images/gravatar-default.png'})"></div>
</a>
@@ -126,19 +131,27 @@ DOCUMENTATION :: END
<div class="dashboard-stats-info scoller-content">
<ul class="list-unstyled dashboard-stats-info-list">
% for row in top_stat['rows']:
<li class="dashboard-stats-info-item ${'expanded' if loop.index == 0 else ''}" data-stat_id="${stat_id}" data-rating_key="${row.get('rating_key')}" data-title="${row.get('title')}"
<li class="dashboard-stats-info-item ${'expanded' if loop.index == 0 else ''}" data-stat_id="${stat_id}"
data-rating_key="${row.get('rating_key')}" data-guid="${row.get('guid')}" data-title="${row.get('title')}"
data-art="${row.get('art')}" data-thumb="${row.get('thumb')}" data-platform="${row.get('platform_name')}"
data-user_id="${row.get('user_id')}" data-friendly_name="${row.get('friendly_name')}" data-user_thumb="${row.get('user_thumb')}"
data-last_watch="${row.get('last_watch')}" data-started="${row.get('started')}">
data-last_watch="${row.get('last_watch')}" data-started="${row.get('started')}" data-live="${row.get('live')}">
<div class="sub-list">${loop.index + 1}</div>
<div class="sub-value">
% if stat_id in ('top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'last_watched'):
<% href = 'info?rating_key={}'.format(row['rating_key']) if row['rating_key'] else '#' %>
<%
href = '#'
if row['rating_key']:
if row['live']:
href = page('info', row['rating_key'], row['guid'], history=True, live=row['live'])
else:
href = page('info', row['rating_key'])
%>
<a href="${href}" title="${row['title']}">
${row['title']}
</a>
% elif stat_id == 'top_users':
<% user_href = 'user?user_id={}'.format(row['user_id']) if row['user_id'] else '#' %>
<% user_href = page('user', row['user_id']) if row['user_id'] else '#' %>
<a href="${user_href}" title="${row['friendly_name']}">
${row['friendly_name']}
</a>
@@ -171,7 +184,7 @@ DOCUMENTATION :: END
% endif
% endfor
<script>
$('.dashboard-stats-instance .dashboard-stats-info-scroller').scrollbar()
$('.dashboard-stats-instance .dashboard-stats-info-scroller').scrollbar();
function changeImages(elem) {
var stat_id = $(elem).data('stat_id');
@@ -180,20 +193,25 @@ DOCUMENTATION :: END
var user_id = $(elem).data('user_id');
var user_thumb = $(elem).data('user_thumb');
var rating_key = $(elem).data('rating_key');
var [height, fallback] = ($.inArray(stat_id, ['top_music', 'popular_music']) > -1) ? [300, 'cover'] : [450, 'poster'];
var href;
var guid = $(elem).data('guid');
var live = $(elem).data('live');
var [height, fallback_poster, fallback_art] = [450, 'poster', 'art'];
if ($.inArray(stat_id, ['top_music', 'popular_music']) > -1) {
[height, fallback_poster, fallback_art] = [300, 'cover', 'art'];
} else if (live) {
[height, fallback_poster, fallback_art] = [450, 'poster-live', 'art-live'];
}
var href = '#';
if (stat_id == 'most_concurrent') {
if (stat_id === 'most_concurrent') {
return
} else if (stat_id == 'top_users') {
} else if (stat_id === 'top_users') {
$('#stats-thumb-' + stat_id).css('background-image', 'url(' + (user_thumb || 'images/gravatar-default.png') + ')');
if (user_id) {
href = 'user?user_id=' + user_id;
} else {
href = '#';
href = page('user', user_id);
}
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('friendly_name'));
} else if (stat_id == 'top_platforms') {
} else if (stat_id === 'top_platforms') {
$('#stats-thumb-' + stat_id).removeClass(function (index, className) {
return (className.match (/(^|\s)platform-\S+/g) || []).join(' ');
}).addClass('platform-' + $(elem).data('platform'));
@@ -202,42 +220,35 @@ DOCUMENTATION :: END
}).addClass('platform-' + $(elem).data('platform') + '-rgba');
} else {
if (rating_key) {
href = 'info?rating_key=' + rating_key;
} else {
href = '#';
if (live) {
href = page('info', rating_key, guid, true, live);
} else {
href = page('info', rating_key);
}
}
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('title'));
if (art) {
$('#stats-background-' + stat_id).css('background-image', 'url(pms_image_proxy?img=' + art + '&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art)');
} else {
$('#stats-background-' + stat_id).css('background-image', 'url(images/art.png)');
}
if (thumb) {
$('#stats-thumb-' + stat_id).css('background-image', 'url(pms_image_proxy?img=' + thumb + '&width=300&height=' + height + '&fallback=' + fallback + ')');
$('#stats-thumb-' + stat_id + '-bg').css('background-image', 'url(pms_image_proxy?img=' + thumb + '&width=300&height=' + height + '&opacity=60&background=282828&blur=3&fallback=' + fallback + ')');
} else {
$('#stats-thumb-' + stat_id).css('background-image', 'url(images/' + fallback + '.png)');
$('#stats-thumb-' + stat_id + '-bg').css('background-image', 'url(images/' + fallback + '.png)');
}
$('#stats-background-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', art, rating_key, 500, 280, 40, '282828', 3, fallback_art) + ')');
$('#stats-thumb-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', thumb, rating_key, 300, height, null, null, null, fallback_poster) + ')');
$('#stats-thumb-' + stat_id + '-bg').css('background-image', 'url(' + page('pms_image_proxy', thumb, rating_key, 300, height, 60, '282828', 3, fallback_poster) + ')');
}
}
$('.dashboard-stats-info-item').mouseenter(function () {
changeImages(this)
if ($(this).data('stat_id') == 'last_watched') {
changeImages(this);
if ($(this).data('stat_id') === 'last_watched') {
var friendly_name = $(this).data('friendly_name');
var last_watch = moment($(this).data('last_watch'), 'X').format(date_format);
$('#last-watched-header-info').html(friendly_name);
} else if ($(this).data('stat_id') == 'most_concurrent') {
} else if ($(this).data('stat_id') === 'most_concurrent') {
var started = moment($(this).data('started'), 'X').format(date_format + ' ' + time_format);
$('#most-concurrent-header-info').html(started);
}
});
$('.dashboard-stats-instance').mouseleave(function () {
changeImages($(this).find('.dashboard-stats-info-item').first())
if ($(this).data('stat_id') == 'last_watched') {
changeImages($(this).find('.dashboard-stats-info-item').first());
if ($(this).data('stat_id') === 'last_watched') {
$('#last-watched-header-info').text($(this).find('.dashboard-stats-info-item').first().data('friendly_name'));
} else if ($(this).data('stat_id') == 'most_concurrent') {
} else if ($(this).data('stat_id') === 'most_concurrent') {
$('#most-concurrent-header-info').text('streams');
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,9 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>live</title>
<path fill="#fff" d="M9.636 10.115c-0.829 0.544-1.243 0.816-2.072 1.361-2.331-3.547-2.331-6.195 0-9.749 0.829 0.546 1.244 0.819 2.072 1.361-1.68 2.557-1.68 4.464 0 7.027z"></path>
<path fill="#fff" d="M4.374 11.662c-0.828 0.542-1.243 0.815-2.072 1.359-3.069-4.676-3.069-8.159 0-12.838 0.829 0.546 1.244 0.817 2.072 1.362-2.418 3.684-2.418 6.426 0 10.117z"></path>
<path fill="#fff" d="M22.365 10.115c0.826 0.544 1.242 0.816 2.070 1.361 2.334-3.547 2.334-6.195 0-9.749-0.828 0.546-1.244 0.819-2.070 1.361 1.677 2.557 1.677 4.464 0 7.027z"></path>
<path fill="#fff" d="M27.627 11.662c0.827 0.542 1.243 0.815 2.070 1.359 3.070-4.676 3.070-8.159 0-12.838-0.827 0.546-1.243 0.817-2.070 1.362 2.419 3.684 2.419 6.426 0 10.117z"></path>
<path fill="#fff" d="M25.211 31.982l2.611-0.95-8.172-22.45c0.32-0.589 0.502-1.263 0.502-1.979 0-2.293-1.859-4.152-4.152-4.152s-4.151 1.858-4.151 4.152c0 0.672 0.16 1.305 0.443 1.868l-8.212 22.561 2.612 0.95 1.952-5.362h14.616l1.951 5.362zM17.396 10.513l3.945 10.834-7.903-7.9 1.080-2.966c0.46 0.176 0.96 0.272 1.481 0.272 0.49 0.001 0.961-0.084 1.397-0.24zM12.39 16.329l7.51 7.512h-10.245l2.735-7.512z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -334,13 +334,13 @@
streams_header = streams_header.replace(/, $/, '') + ')';
$('#currentActivityHeader-streams').text(streams_header);
var bandwidth_header = ((total_bw > 1000) ? ((total_bw / 1000).toFixed(1) + ' Mbps') : (total_bw + ' kbps'));
var bandwidth_header = ((total_bw > 1000000) ? ((total_bw / 1000000).toFixed(1) + ' Gbps') : ((total_bw > 1000) ? ((total_bw / 1000).toFixed(1) + ' Mbps') : (total_bw + ' kbps')));
var lan_wan_bandwidth_header = '';
if (lan_bw) {
lan_wan_bandwidth_header += 'LAN: ' + ((lan_bw > 1000) ? ((lan_bw / 1000).toFixed(1) + ' Mbps') : (lan_bw + ' kbps')) + ', ';
lan_wan_bandwidth_header += 'LAN: ' + ((lan_bw > 1000000) ? ((lan_bw / 1000000).toFixed(1) + ' Gbps') : ((lan_bw > 1000) ? ((lan_bw / 1000).toFixed(1) + ' Mbps') : (lan_bw + ' kbps'))) + ', ';
}
if (wan_bw) {
lan_wan_bandwidth_header += 'WAN: ' + ((wan_bw > 1000) ? ((wan_bw / 1000).toFixed(1) + ' Mbps') : (wan_bw + ' kbps')) + ', ';
lan_wan_bandwidth_header += 'WAN: ' + ((wan_bw > 1000000) ? ((wan_bw / 1000000).toFixed(1) + ' Gbps') : ((wan_bw > 1000) ? ((wan_bw / 1000).toFixed(1) + ' Mbps') : (wan_bw + ' kbps'))) + ', ';
}
if (lan_wan_bandwidth_header) {
bandwidth_header += ' (' + lan_wan_bandwidth_header.replace(/, $/, '') + ')';
@@ -356,8 +356,10 @@
var instance = $('#activity-instance-' + key);
// Create a new instance if it doesn't exist or recreate the entire instance
// if the rating key changed (for movies or episodes) with the same session key
if (!(instance.length) || (s.media_type !== 'track' && s.rating_key !== instance.data('rating_key').toString())) {
// if the rating key changed (for movies or episodes) of guid changed (for live tv) with the same session key
if (!(instance.length) ||
(s.media_type !== 'track' && s.rating_key !== instance.data('rating_key').toString()) ||
(s.live === 1 && s.guid !== instance.data('guid'))) {
create_instances.push(key);
getActivityInstance(key);
return;
@@ -384,32 +386,32 @@
if (s.media_type === 'track') {
// Update if artist changed
if (s.grandparent_rating_key !== instance.data('grandparent_rating_key').toString()) {
$('#background-' + key).css('background-image', 'url(pms_image_proxy?img=' + s.art + '&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art&refresh=true)');
$('#background-' + key).css('background-image', 'url(' + page('pms_image_proxy', s.art, s.rating_key, 500, 280, 40, '282828', 3, 'art', true) + ')');
$('#metadata-grandparent_title-' + key)
.attr('href', 'info?rating_key=' + s.grandparent_rating_key)
.attr('href', page('info', s.grandparent_rating_key))
.attr('title', s.original_title || s.grandparent_title)
.text(s.original_title || s.grandparent_title);
}
// Update cover if album changed
if (s.parent_rating_key !== instance.data('parent_rating_key').toString()) {
$('#poster-' + key).css('background-image', 'url(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&fallback=poster&refresh=true)');
$('#poster-' + key + '-bg').css('background-image', 'url(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&opacity=60&background=282828&blur=3&fallback=poster&refresh=true)');
$('#poster-' + key).css('background-image', 'url(' + page('pms_image_proxy', s.parent_thumb, s.parent_rating_key, 300, 300, null, null, null, 'poster', true) + ')');
$('#poster-' + key + '-bg').css('background-image', 'url(' + page('pms_image_proxy', s.parent_thumb, s.parent_rating_key, 300, 300, 60, '282828', 3, 'poster', true) + ')');
$('#poster-url-' + key)
.attr('href', 'info?rating_key=' + s.parent_rating_key)
.attr('href', page('info', s.parent_rating_key))
.attr('title', s.parent_title);
$('#metadata-parent_title-' + key)
.attr('href', 'info?rating_key=' + s.parent_rating_key)
.attr('href', page('info', s.parent_rating_key))
.attr('title', s.parent_title)
.text(s.parent_title);
}
// Update cover if track changed
if (s.rating_key !== instance.data('rating_key').toString()) {
$('#metadata-grandparent_title-' + key)
.attr('href', 'info?rating_key=' + s.grandparent_rating_key)
.attr('href', page('info', s.grandparent_rating_key))
.attr('title', s.original_title || s.grandparent_title)
.text(s.original_title || s.grandparent_title);
$('#metadata-title-' + key)
.attr('href', 'info?rating_key=' + s.rating_key)
.attr('href', page('info', s.rating_key))
.attr('title', s.title)
.text(s.title);
}
@@ -437,6 +439,8 @@
var video_decision = '';
if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.stream_video_decision) {
var v_bd = (s.video_dynamic_range === 'HDR') ? ' ' + s.video_dynamic_range : '';
var sv_bd = (s.video_dynamic_range === 'HDR') ? ' ' + s.stream_video_dynamic_range : '';
var v_res= '';
switch (s.video_resolution.toLowerCase()) {
case 'sd':
@@ -462,11 +466,11 @@
if (s.stream_video_decision === 'transcode') {
var hw_d = (s.transcode_hw_decoding === 1) ? ' (HW)' : '';
var hw_e = (s.transcode_hw_encoding === 1) ? ' (HW)' : '';
video_decision = 'Transcode (' + s.video_codec.toUpperCase() + hw_d + ' ' + v_res + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_video_codec.toUpperCase() + hw_e + ' ' + sv_res + ')';
video_decision = 'Transcode (' + s.video_codec.toUpperCase() + hw_d + ' ' + v_res + v_bd + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_video_codec.toUpperCase() + hw_e + ' ' + sv_res + sv_bd + ')';
} else if (s.stream_video_decision === 'copy') {
video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + sv_bd + ')';
} else {
video_decision = 'Direct Play (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
video_decision = 'Direct Play (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + sv_bd + ')';
}
} else if (s.media_type === 'photo') {
video_decision = 'Direct Play (' + s.width + 'x' + s.height + ')';
@@ -522,7 +526,9 @@
if (s.media_type !== 'photo' && s.bandwidth !== 'Unknown') {
var bw = parseInt(s.bandwidth) || 0;
if (bw > 1000) {
if (bw > 1000000) {
bw = (bw / 1000000).toFixed(1) + ' Gbps';
} else if (bw > 1000) {
bw = (bw / 1000).toFixed(1) + ' Mbps';
} else {
bw = bw + ' kbps'
@@ -541,10 +547,12 @@
// Update the progress bars, percent - 3 because of 3px padding-right
$('#buffer-bar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%')
.attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%');
var progress_bar = $('#progress-bar-' + key);
progress_bar.data('state', s.state);
if (progress_bar.data('last_view_offset') !== s.view_offset) {
progress_bar.data('last_view_offset', s.view_offset).data('view_offset', s.view_offset);
if (s.live !== 1) {
var progress_bar = $('#progress-bar-' + key);
progress_bar.data('state', s.state);
if (progress_bar.data('last_view_offset') !== s.view_offset) {
progress_bar.data('last_view_offset', s.view_offset).data('view_offset', s.view_offset);
}
}
// Add temporary class so we know which instances are still active
@@ -557,6 +565,7 @@
$(instance).removeClass('updated-temp');
} else {
$(instance).find('[data-toggle=tooltip]').tooltip('destroy');
$(instance).find('[data-toggle=popover]').popover('destroy');
$(instance).remove();
}
});
@@ -591,6 +600,17 @@
$('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller').scrollbar();
$('#activity-instance-' + session_key + ' [data-toggle=tooltip]').tooltip({ container: 'body', placement: 'right', delay: 50 });
$('#activity-instance-' + session_key + ' [data-toggle=popover]').popover({
html: true,
container: 'body',
trigger: 'hover',
placement: 'right',
delay: 50,
template: '<div class="popover channel-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>',
content: function () {
return '<div class="channel-thumbnail" style="background-image: url(' + $(this).data('img') + ');" />';
}
});
$('#terminate-button-' + session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 });
lockScroll('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller');

View File

@@ -36,10 +36,12 @@ DOCUMENTATION :: END
</%doc>
<%!
from collections import defaultdict
import re
from plexpy import notifiers
from plexpy.common import MEDIA_TYPE_HEADERS, MEDIA_FLAGS_AUDIO, MEDIA_FLAGS_VIDEO
from plexpy.helpers import page
# Get audio codec file
def af(codec):
@@ -48,13 +50,20 @@ DOCUMENTATION :: END
return file_type
return codec
# Get audio codec file
# Get video codec file
def vf(codec):
for pattern, file_type in MEDIA_FLAGS_VIDEO.iteritems():
if re.match(pattern, codec):
return file_type
return codec
# Get video resolution file
def vr(resolution):
if resolution in ('1080i', '576i', '480i'):
return resolution
else:
return resolution.lower().rstrip('ip')
def br(text):
return text.replace('\n', '<br /><br />')
%>
@@ -68,11 +77,15 @@ DOCUMENTATION :: END
</%def>
<%def name="body()">
% if data:
<% media_info = data['media_info'][0] if data['media_info'] else {} %>
% if metadata:
<%
data = defaultdict(lambda: None, **metadata)
media_info = defaultdict(lambda: None, **(data['media_info'][0] if data['media_info'] else {}))
%>
<div class="container-fluid">
<div class="row">
<div class="art-face" style="background-image:url(pms_image_proxy?img=${data['art']}&width=1920&height=1080)"></div>
<% fallback = 'art-live-full' if data['live'] else None %>
<div class="art-face" style="background-image:url(${page('pms_image_proxy', data['art'], data['rating_key'], 1920, 1080, fallback=fallback)})"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image info-art" title="Refresh background image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -81,44 +94,60 @@ DOCUMENTATION :: END
<div class="col-md-12">
<div class="summary-navbar-list">
<ul class="list-unstyled breadcrumb">
% if data['media_type'] in ('movie', 'collection'):
<li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
% if data['live']:
<li><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
% if data['media_type'] == 'movie':
<li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'episode':
<li class="hidden-xs hidden-sm">${data['grandparent_title']}</li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
% if data['media_index']:
<li>Season ${data['parent_media_index']}</li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">Episode ${data['media_index']} - ${data['title']}</li>
% else:
<li class="active metadata-xml">${data['title']}</li>
% endif
% endif
% elif data['media_type'] in ('movie', 'collection'):
<li><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'show':
<li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'season':
<li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="hidden-xs hidden-sm"><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></li>
<li><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">Season ${data['media_index']}</li>
% elif data['media_type'] == 'episode':
<li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="hidden-xs hidden-sm"><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="hidden-xs hidden-sm"><a href="info?rating_key=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li>
<li class="hidden-xs hidden-sm"><a href="${page('info', data['grandparent_rating_key'])}">${data['grandparent_title']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li><a href="info?rating_key=${data['parent_rating_key']}">Season ${data['parent_media_index']}</a></li>
<li><a href="${page('info', data['parent_rating_key'])}">Season ${data['parent_media_index']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">Episode ${data['media_index']} - ${data['title']}</li>
% elif data['media_type'] == 'artist':
<li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'album':
<li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="hidden-xs hidden-sm"><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></li>
<li><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'track':
<li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="hidden-xs hidden-sm"><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="hidden-xs hidden-sm"><a href="info?rating_key=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li>
<li class="hidden-xs hidden-sm"><a href="${page('info', data['grandparent_rating_key'])}">${data['grandparent_title']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></li>
<li><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">Track ${data['media_index']} - ${data['title']}</li>
% endif
@@ -131,11 +160,18 @@ DOCUMENTATION :: END
<div class="summary-content-poster hidden-xs hidden-sm">
% if data['media_type'] == 'track':
<a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View on Plex Web">
% else:
% elif not data['live']:
<a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View on Plex Web">
% endif
% if data['live']:
<div class="summary-poster-face" style="background-image: url(${page('pms_image_proxy', data['grandparent_thumb'] or data['thumb'], data['rating_key'], 300, 450, fallback='poster-live')});">
<div class="summary-poster-face-overlay">
<span></span>
</div>
</div>
% else:
% 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);">
<div class="summary-poster-face-episode" style="background-image: url(${page('pms_image_proxy', data['thumb'], data['rating_key'], 500, 280, fallback='art')});">
<div class="summary-poster-face-overlay">
<span></span>
</div>
@@ -144,7 +180,7 @@ DOCUMENTATION :: END
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
% elif data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
<div class="summary-poster-face-track" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=500&fallback=cover);">
<div class="summary-poster-face-track" style="background-image: url(${page('pms_image_proxy', data['thumb'], data['rating_key'], 500, 500, fallback='cover')});">
<div class="summary-poster-face-overlay">
<span></span>
</div>
@@ -153,7 +189,7 @@ DOCUMENTATION :: END
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
% else:
<div class="summary-poster-face" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster);">
<div class="summary-poster-face" style="background-image: url(${page('pms_image_proxy', data['thumb'], data['rating_key'], 300, 450, fallback='poster')});">
<div class="summary-poster-face-overlay">
<span></span>
</div>
@@ -162,24 +198,37 @@ DOCUMENTATION :: END
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
% endif
% endif
% if not data['live']:
</a>
% endif
</div>
<div class="summary-content-title">
% if data['media_type'] in ('movie', 'show', 'artist', 'collection'):
% if data['live']:
% if data['media_type'] == 'movie':
<h1>&nbsp;</h1><h1>${data['title']}</h1>
% elif data['media_type'] == 'episode':
<h1>${data['grandparent_title']}</h1>
<h2>${data['title']}</h2>
% if data['media_index']:
<h3 class="hidden-xs">S${data['parent_media_index']} &middot; E${data['media_index']}</h3>
% endif
% endif
% elif data['media_type'] in ('movie', 'show', 'artist', 'collection'):
<h1>&nbsp;</h1><h1>${data['title']}</h1>
% elif data['media_type'] == 'season':
<h1>&nbsp;</h1><h1><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></h1>
<h1>&nbsp;</h1><h1><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></h1>
<h3 class="hidden-xs">S${data['media_index']}</h3>
% elif data['media_type'] == 'episode':
<h1><a href="info?rating_key=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></h1>
<h1><a href="${page('info', data['grandparent_rating_key'])}">${data['grandparent_title']}</a></h1>
<h2>${data['title']}</h2>
<h3 class="hidden-xs">S${data['parent_media_index']} &middot; E${data['media_index']}</h3>
% elif data['media_type'] == 'album':
<h1><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></h1>
<h1><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></h1>
<h2>${data['title']}</h2>
% elif data['media_type'] == 'track':
<h1><a href="info?rating_key=${data['grandparent_rating_key']}">${data['original_title'] or data['grandparent_title']}</a></h1>
<h2><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a> - ${data['title']}</h2>
<h1><a href="${page('info', data['grandparent_rating_key'])}">${data['original_title'] or data['grandparent_title']}</a></h1>
<h2><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a> - ${data['title']}</h2>
<h3 class="hidden-xs">T${data['media_index']}</h3>
% endif
</div>
@@ -187,7 +236,7 @@ DOCUMENTATION :: END
</div>
<div class="summary-content-wrapper">
<div class="col-md-9">
% if data['media_type'] == 'movie':
% if data['media_type'] == 'movie' or data['live']:
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 305px;">
% elif data['media_type'] in ('show', 'season', 'collection'):
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 270px;">
@@ -206,7 +255,7 @@ DOCUMENTATION :: END
<img class="summary-content-media-flag" title="${media_info['video_codec']}" src="${http_root}images/media_flags/video_codec/${media_info['video_codec'] | vf}.png" />
% endif
% if data['media_type'] != 'track' and media_info['video_resolution']:
<img class="summary-content-media-flag" title="${media_info['video_resolution']}" src="${http_root}images/media_flags/video_resolution/${media_info['video_resolution']}.png" />
<img class="summary-content-media-flag" title="${media_info['video_resolution']}" src="${http_root}images/media_flags/video_resolution/${media_info['video_full_resolution'] | vr}.png" />
% endif
% if media_info['audio_codec']:
<img class="summary-content-media-flag" title="${media_info['audio_codec']}" src="${http_root}images/media_flags/audio_codec/${media_info['audio_codec'] | af}.png" />
@@ -251,6 +300,8 @@ DOCUMENTATION :: END
Released <strong> ${data['year']}</strong>
% elif data['media_type'] == 'collection':
Year <strong> ${data['min_year']} - ${data['max_year']}</strong>
% elif data['year']:
Year <strong> ${data['year']}</strong>
% endif
</div>
<div class="summary-content-details-tag">
@@ -263,6 +314,11 @@ DOCUMENTATION :: END
Rated <strong> ${data['content_rating']} </strong>
% endif
</div>
<div class="summary-content-details-tag" id="channel-icon">
% if media_info['channel_identifier']:
Channel <strong> <span class="thumb-tooltip" data-toggle="popover" data-img="${media_info['channel_thumb']}" data-height="40" data-width="40">${media_info['channel_call_sign']} ${media_info['channel_identifier']}</span> </strong>
% endif
</div>
</div>
% if data['tagline']:
<div class="summary-content-summary">
@@ -415,7 +471,7 @@ DOCUMENTATION :: END
</div>
% endif
% if data.get('poster_url'):
<div class="btn-group">
<div class="btn-group" id="hosted-poster">
% if data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
<span class="hosted-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="80" data-width="80" style="display: inline-flex;">
% else:
@@ -429,6 +485,7 @@ DOCUMENTATION :: END
</span>
</div>
% endif
% if not data['live']:
<div class="btn-group">
<button class="btn btn-dark" data-toggle="modal" aria-pressed="false" autocomplete="off" id="send-recently-added-notification"
data-id="${data['rating_key']}">
@@ -436,6 +493,7 @@ DOCUMENTATION :: END
</button>
</div>
% endif
% endif
<div class="btn-group">
<button class="btn btn-dark refresh-history-button" id="refresh-history-list"><i class="fa fa-refresh"></i> Refresh history</button>
</div>
@@ -474,6 +532,10 @@ DOCUMENTATION :: END
</%def>
<%def name="modalIncludes()">
% if metadata:
<%
data = defaultdict(None, **metadata)
%>
<div class="modal fade" id="info-modal" tabindex="-1" role="dialog" aria-labelledby="info-modal">
</div>
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
@@ -549,6 +611,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
% endif
</%def>
<%def name="javascriptIncludes()">
@@ -558,9 +621,28 @@ DOCUMENTATION :: END
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
<script src="${http_root}js/moment-with-locale.js"></script>
% if data:
% if metadata:
<%
data = defaultdict(None, **metadata)
%>
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
% if data['media_type'] in ('show', 'artist'):
% if data['live']:
<script>
function get_history() {
history_table_options.ajax = {
url: 'get_history',
type: 'POST',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
guid: "${data['guid']}",
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
};
}
}
}
</script>
% elif data['media_type'] in ('show', 'artist'):
<script>
function get_history() {
history_table_options.ajax = {
@@ -724,10 +806,22 @@ DOCUMENTATION :: END
$("#airdate").html(moment($("#airdate").text()).format('MMM DD, YYYY'));
$("#runtime").html(millisecondsToMinutes($("#runtime").text(), true));
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
$('#channel-icon').popover({
selector: '[data-toggle=popover]',
html: true,
container: 'body',
trigger: 'hover',
placement: 'right',
template: '<div class="popover channel-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>',
content: function () {
return '<div class="channel-thumbnail" style="background-image: url(' + $(this).data('img') + ');" />';
}
});
</script>
% if data.get('poster_url'):
<script>
$('.hosted-poster-tooltip').popover({
$('#hosted-poster').popover({
selector: '[data-toggle=popover]',
html: true,
container: 'body',
trigger: 'hover',

View File

@@ -27,6 +27,9 @@ DOCUMENTATION :: END
</%doc>
% if data != None:
<%
from plexpy.helpers import page
%>
% if data['children_count'] > 0:
<div class="item-children-wrapper">
<ul class="item-children-instance list-unstyled">
@@ -38,9 +41,9 @@ DOCUMENTATION :: END
<li>
% endif
% if data['children_type'] == 'movie':
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -48,14 +51,14 @@ DOCUMENTATION :: END
</a>
<div class="item-children-instance-text-wrapper poster-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
<a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
</h3>
<h3 class="text-muted">${child['year']}</h3>
</div>
% elif data['children_type'] == 'show':
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -63,16 +66,16 @@ DOCUMENTATION :: END
</a>
<div class="item-children-instance-text-wrapper poster-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
<a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
</h3>
</div>
% elif data['children_type'] == 'season':
<a href="info?rating_key=${child['rating_key']}" title="Season ${child['media_index']}">
<a href="${page('info', child['rating_key'])}" title="Season ${child['media_index']}">
<div class="item-children-poster">
% if child['thumb']:
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);">
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});">
% else:
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=450&fallback=poster);">
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['parent_thumb'], child['parent_rating_key'], 300, 450, fallback='poster')});">
% endif
<div class="item-children-card-overlay">
<div class="item-children-overlay-text">
@@ -86,9 +89,9 @@ DOCUMENTATION :: END
</div>
</a>
% elif data['children_type'] == 'episode':
<a href="info?rating_key=${child['rating_key']}" title="Episode ${child['media_index']}">
<a href="${page('info', child['rating_key'])}" title="Episode ${child['media_index']}">
<div class="item-children-poster">
<div class="item-children-poster-face episode-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);">
<div class="item-children-poster-face episode-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 500, 280, fallback='art')});">
<div class="item-children-card-overlay">
<div class="item-children-overlay-text">
Episode ${child['media_index'] or child['originally_available_at']}
@@ -102,13 +105,13 @@ DOCUMENTATION :: END
</a>
<div class="item-children-instance-text-wrapper episode-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
<a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
</h3>
</div>
% elif data['children_type'] == 'album':
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
<div class="item-children-poster">
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -116,14 +119,14 @@ DOCUMENTATION :: END
</a>
<div class="item-children-instance-text-wrapper cover-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
<a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
</h3>
</div>
% elif data['children_type'] == 'track':
% if loop.index % 2 == 0:
<div class="item-children-list-item-even">
<span class="item-children-list-item-index">&nbsp;${child['media_index']}</span>
<span class="item-children-list-item-title"><a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
<span class="item-children-list-item-title"><a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
% if child['original_title']:
<span class="text-muted"> - ${child['original_title']}</span>
% endif
@@ -135,7 +138,7 @@ DOCUMENTATION :: END
% else:
<div class="item-children-list-item-odd">
<span class="item-children-list-item-index">&nbsp;${child['media_index']}</span>
<span class="item-children-list-item-title"><a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
<span class="item-children-list-item-title"><a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
% if child['original_title']:
<span class="text-muted"> - ${child['original_title']}</span>
% endif

View File

@@ -29,6 +29,7 @@ DOCUMENTATION :: END
% if data != None:
<%
from plexpy.common import MEDIA_TYPE_HEADERS
from plexpy.helpers import page
types = ('movie', 'show', 'artist', 'album')
%>
% for media_type in types:
@@ -45,12 +46,12 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list'][media_type]:
<li>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
<div class="item-children-poster">
% if media_type in ('artist', 'album'):
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')});"></div>
% else:
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% endif
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
@@ -60,22 +61,22 @@ DOCUMENTATION :: END
% if media_type == 'artist':
<div class="item-children-instance-text-wrapper cover-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
<a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
</h3>
</div>
% elif media_type == 'album':
<div class="item-children-instance-text-wrapper cover-item">
<h3>
<a href="info?rating_key=${child['parent_rating_key']}" title="${child['parent_title']}">${child['parent_title']}</a>
<a href="${page('info', child['parent_rating_key'])}" title="${child['parent_title']}">${child['parent_title']}</a>
</h3>
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
<a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
</h3>
</div>
% else:
<div class="item-children-instance-text-wrapper poster-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
<a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
</h3>
<h3 class="text-muted">${child['year']}</h3>
</div>

View File

@@ -53,6 +53,9 @@ DOCUMENTATION :: END
</%doc>
% if data != None:
<%
from plexpy.helpers import page
%>
% if data['results_count'] > 0:
% if 'collection' in data['results_list'] and data['results_list']['collection']:
<div class="item-children-wrapper">
@@ -62,9 +65,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['collection']:
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -87,9 +90,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['movie']:
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -112,9 +115,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['show']:
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -137,9 +140,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['season']:
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -162,9 +165,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['episode']:
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face episode-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);"></div>
<div class="item-children-poster-face episode-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 500, 280, fallback='art')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -188,9 +191,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['artist']:
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -212,9 +215,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['album']:
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')});"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -237,9 +240,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['track']:
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=300&fallback=cover);">
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['parent_thumb'], child['parent_rating_key'], 300, 300, fallback='cover')});">
<div class="item-children-card-overlay">
<div class="item-children-overlay-text">
Track ${child['media_index']}

View File

@@ -40,7 +40,6 @@ var hc_plays_by_day_options = {
}
}
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
type: 'datetime',
labels: {

View File

@@ -23,7 +23,6 @@ var hc_plays_by_dayofweek_options = {
credits: {
enabled: false
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
categories: [{}],
labels: {

View File

@@ -23,7 +23,6 @@ var hc_plays_by_hourofday_options = {
credits: {
enabled: false
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
categories: [{}],
labels: {

View File

@@ -23,7 +23,6 @@ var hc_plays_by_month_options = {
credits: {
enabled: false
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
labels: {
style: {

View File

@@ -23,7 +23,6 @@ var hc_plays_by_platform_options = {
credits: {
enabled: false
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
categories: [{}],
labels: {

View File

@@ -23,7 +23,6 @@ var hc_plays_by_platform_by_stream_type_options = {
credits: {
enabled: false
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
categories: [{}],
labels: {

View File

@@ -23,7 +23,6 @@ var hc_plays_by_source_resolution_options = {
credits: {
enabled: false
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
categories: [{}],
labels: {

View File

@@ -23,7 +23,6 @@ var hc_plays_by_stream_resolution_options = {
credits: {
enabled: false
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
categories: [{}],
labels: {

View File

@@ -40,7 +40,6 @@ var hc_plays_by_stream_type_options = {
}
}
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
type: 'datetime',
labels: {

View File

@@ -23,7 +23,6 @@ var hc_plays_by_user_options = {
credits: {
enabled: false
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
categories: [{}],
labels: {

View File

@@ -23,7 +23,6 @@ var hc_plays_by_user_by_stream_type_options = {
credits: {
enabled: false
},
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
xAxis: {
categories: [{}],
labels: {

View File

@@ -717,3 +717,69 @@ function encodeData(data) {
return [key, data[key]].map(encodeURIComponent).join("=");
}).join("&");
}
function page(endpoint, ...args) {
let endpoints = {
'pms_image_proxy': pms_image_proxy,
'info': info_page,
'library': library_page,
'user': user_page
};
var params = {};
if (endpoint in endpoints) {
params = endpoints[endpoint](...args);
}
return endpoint + '?' + $.param(params).replace(/'/g, '%27');
}
function pms_image_proxy(img, rating_key, width, height, opacity, background, blur, fallback, refresh, clip, img_format) {
var params = {};
if (img != null) { params.img = img; }
if (rating_key != null) { params.rating_key = rating_key; }
if (width != null) { params.width = width; }
if (height != null) { params.height = height; }
if (opacity != null) { params.opacity = opacity; }
if (background != null) { params.background = background; }
if (blur != null) { params.blur = blur; }
if (fallback != null) { params.fallback = fallback; }
if (refresh != null) { params.refresh = true; }
if (clip != null) { params.clip = true; }
if (img_format != null) { params.img_format = img_format; }
return params;
}
function info_page(rating_key, guid, history, live) {
var params = {};
if (live && history) {
params.guid = guid;
} else {
params.rating_key = rating_key;
}
if (history) { params.source = 'history'; }
return params;
}
function library_page(section_id) {
var params = {};
if (section_id != null) { params.section_id = section_id; }
return params;
}
function user_page(user_id, user) {
var params = {};
if (user_id != null) { params.user_id = user_id; }
if (user != null) { params.user = user; }
return params;
}

View File

@@ -81,9 +81,9 @@ history_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
if (rowData['user_id']) {
$(td).html('<a href="user?user_id=' + rowData['user_id'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>');
} else {
$(td).html('<a href="user?user=' + rowData['user'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', null, rowData['user']) + '">' + cellData + '</a>');
}
} else {
$(td).html(cellData);
@@ -115,7 +115,7 @@ history_table_options = {
"data": "platform",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(capitalizeFirstLetter(cellData));
$(td).html(cellData);
}
},
"width": "10%",
@@ -156,29 +156,37 @@ history_table_options = {
"data": "full_title",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var icon = '';
var icon_title = '';
var parent_info = '';
var media_type = '';
var thumb_popover = '';
var source = (rowData['state'] === null) ? 'source=history&' : '';
var fallback = (rowData['live']) ? 'poster-live' : 'poster';
var history = (rowData['state'] === null);
if (rowData['media_type'] === 'movie') {
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-film';
icon_title = (rowData['live']) ? 'Live TV' : 'Movie';
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?' + source + 'rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], history, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?' + source + 'rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-television';
icon_title = (rowData['live']) ? 'Live TV' : 'Episode';
if (!isNaN(parseInt(rowData['parent_media_index'])) && !isNaN(parseInt(rowData['media_index']))) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
else if (rowData['live'] && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], history, rowData['live']) + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'track') {
if (rowData['parent_title']) { parent_info = ' (' + rowData['parent_title'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?' + source + 'rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], history, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'clip') {
$(td).html(cellData);
} else {
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('info', rowData['rating_key']) + '">' + cellData + '</a>');
}
}
},

View File

@@ -63,9 +63,9 @@ history_table_modal_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
if (rowData['user_id']) {
$(td).html('<a href="user?user_id=' + rowData['user_id'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>');
} else {
$(td).html('<a href="user?user=' + rowData['user'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', null, rowData['user']) + '">' + cellData + '</a>');
}
} else {
$(td).html(cellData);
@@ -98,26 +98,34 @@ history_table_modal_options = {
"data":"full_title",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var icon = '';
var icon_title = '';
var parent_info = '';
var media_type = '';
var thumb_popover = '';
var fallback = (rowData['live']) ? 'poster-live' : 'poster';
if (rowData['media_type'] === 'movie') {
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-film';
icon_title = (rowData['live']) ? 'Live TV' : 'Movie';
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-television';
icon_title = (rowData['live']) ? 'Live TV' : 'Episode';
if (!isNaN(parseInt(rowData['parent_media_index'])) && !isNaN(parseInt(rowData['media_index']))) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
else if (rowData['live'] && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'track') {
if (rowData['parent_title']) { parent_info = ' (' + rowData['parent_title'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else {
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('info', rowData['rating_key']) + '">' + cellData + '</a>');
}
}
},

View File

@@ -137,45 +137,34 @@ libraries_list_table_options = {
"data":"last_played",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== null && cellData !== '') {
var icon = '';
var icon_title = '';
var parent_info = '';
var media_type = '';
var thumb_popover = '';
var fallback = (rowData['live']) ? 'poster-live' : 'poster';
if (rowData['media_type'] === 'movie') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
if (rowData['rating_key']) {
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else {
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="images/poster.png" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></div>');
}
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-film';
icon_title = (rowData['live']) ? 'Live TV' : 'Movie';
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
if (rowData['rating_key']) {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else {
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="images/poster.png" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></div>');
}
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-television';
icon_title = (rowData['live']) ? 'Live TV' : 'Episode';
if (!isNaN(parseInt(rowData['parent_media_index'])) && !isNaN(parseInt(rowData['media_index']))) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
else if (rowData['live'] && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'track') {
if (rowData['parent_title']) { parent_info = ' (' + rowData['parent_title'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
if (rowData['rating_key']) {
if (rowData['parent_title']) { parent_info = ' (' + rowData['parent_title'] + ')'; }
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else {
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="images/cover.png" data-height="80" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></div>');
}
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type']) {
if (rowData['rating_key']) {
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
} else {
$(td).html(cellData);
}
$(td).html('<a href="' + page('info', rowData['rating_key']) + '">' + cellData + '</a>');
}
} else {
$(td).html('n/a');

View File

@@ -78,43 +78,43 @@ media_info_table_options = {
if (rowData['media_type'] === 'movie') {
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, 'poster') + '" data-height="120" data-width="80">' + rowData['title'] + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'show') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="TV Show"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, 'poster') + '" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'season') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Season"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, 'poster') + '" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 500, 280, null, null, null, 'art') + '" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left; padding-left: 30px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'artist') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Artist"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'album') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Album"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'track') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">T' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">T' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left; padding-left: 30px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'photo_album') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Photo Album"><i class="fa fa-camera fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, 'poster') + '" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></div>');
} else if (rowData['media_type'] === 'photo') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Photo"><i class="fa fa-picture-o fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, 'poster') + '" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></div>');
} else if (rowData['media_type'] === 'clip') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Video"><i class="fa fa-video-camera fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">' + rowData['title'] + '</span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 500, 280, null, null, null, 'art') + '" data-height="80" data-width="140">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></div>');
} else {
$(td).html(cellData);

View File

@@ -51,9 +51,9 @@ sync_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
if (rowData['user_id']) {
$(td).html('<a href="user?user_id=' + rowData['user_id'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', rowData['user_id']) + '>' + cellData + '</a>');
} else {
$(td).html('<a href="user?user=' + rowData['user'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', null, rowData['user']) + '">' + cellData + '</a>');
}
} else {
$(td).html(cellData);
@@ -67,7 +67,7 @@ sync_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
if (rowData['rating_key']) {
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('info', rowData['rating_key']) + '">' + cellData + '</a>');
} else {
$(td).html(cellData);
}

View File

@@ -82,29 +82,37 @@ user_ip_table_options = {
"data": "last_played",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var icon = '';
var icon_title = '';
var parent_info = '';
var media_type = '';
var thumb_popover = '';
var fallback = (rowData['live']) ? 'poster-live' : 'poster';
if (rowData['media_type'] === 'movie') {
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-film';
icon_title = (rowData['live']) ? 'Live TV' : 'Movie';
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-television';
icon_title = (rowData['live']) ? 'Live TV' : 'Episode';
if (!isNaN(parseInt(rowData['parent_media_index'])) && !isNaN(parseInt(rowData['media_index']))) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
else if (rowData['live'] && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'track') {
if (rowData['parent_title']) { parent_info = ' (' + rowData['parent_title'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type']) {
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
} else {
$(td).html('n/a');
$(td).html('<a href="' + page('info', rowData['rating_key']) + '">' + cellData + '</a>');
}
} else {
$(td).html('n/a');
}
},
"width": "30%",

View File

@@ -60,9 +60,9 @@ users_list_table_options = {
"data": "user_thumb",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData === '') {
$(td).html('<a href="user?user_id=' + rowData['user_id'] + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);"></div></a>');
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);"></div></a>');
} else {
$(td).html('<a href="user?user_id=' + rowData['user_id'] + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');"></div></a>');
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');"></div></a>');
}
},
"orderable": false,
@@ -76,7 +76,7 @@ users_list_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== null && cellData !== '') {
$(td).html('<div class="edit-user-name" data-id="' + rowData['user_id'] + '">' +
'<a href="user?user_id=' + rowData['user_id'] + '">' + cellData + '</a>' +
'<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>' +
'<input type="text" class="hidden" value="' + cellData + '">' +
'</div>');
} else {
@@ -157,26 +157,34 @@ users_list_table_options = {
"data":"last_played",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== null && cellData !== '') {
var icon = '';
var icon_title = '';
var parent_info = '';
var media_type = '';
var thumb_popover = '';
var fallback = (rowData['live']) ? 'poster-live' : 'poster';
if (rowData['media_type'] === 'movie') {
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-film';
icon_title = (rowData['live']) ? 'Live TV' : 'Movie';
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-television';
icon_title = (rowData['live']) ? 'Live TV' : 'Episode';
if (!isNaN(parseInt(rowData['parent_media_index'])) && !isNaN(parseInt(rowData['media_index']))) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
else if (rowData['live'] && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'track') {
if (rowData['parent_title']) { parent_info = ' (' + rowData['parent_title'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type']) {
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
$(td).html('<a href="' + page('info', rowData['rating_key']) + '">' + cellData + '</a>');
}
} else {
$(td).html('n/a');

View File

@@ -35,10 +35,14 @@ DOCUMENTATION :: END
<%def name="body()">
% if data:
<%
from plexpy.common import LIVE_TV_SECTION_ID
from plexpy.helpers import page
%>
<div class="container-fluid">
<div class="row">
% if data['library_art']:
<div class="art-face" style="background-image:url(pms_image_proxy?img=${data['library_art']}&width=1920&height=1080)"></div>
<div class="art-face" style="background-image:url(${page('pms_image_proxy', data['library_art'], None, 1920, 1080)})"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image info-art" title="Refresh background image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
@@ -57,7 +61,7 @@ DOCUMENTATION :: END
<div class="col-md-12">
<div class="table-card-back">
<div class="user-info-wrapper">
% if data['library_thumb'][:4] == 'http' or data['library_thumb'][:10] == 'interfaces':
% if data['library_thumb'][:4] == 'http':
<div class="library-info-poster-face" style="background-image: url(${data['library_thumb']});"></div>
% else:
<div class="library-info-poster-face svg-icon library-${data['section_type']}"></div>
@@ -75,8 +79,10 @@ DOCUMENTATION :: END
<li class="active"><a href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
<li><a id="history-tab-btn" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
% if _session['user_group'] == 'admin':
% if data['section_id'] != LIVE_TV_SECTION_ID:
<li><a id="media-info-tab-btn" href="#tabs-mediainfo" role="tab" data-toggle="tab">Media Info</a></li>
% endif
% endif
</ul>
</div>
</div>
@@ -143,6 +149,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
% if data['section_id'] != LIVE_TV_SECTION_ID:
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@@ -168,6 +175,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
% endif
</div>
<div role="tabpanel" class="tab-pane" id="tabs-history">
<div class="container-fluid">
@@ -348,6 +356,7 @@ DOCUMENTATION :: END
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
% if data:
<% from plexpy.common import LIVE_TV_SECTION_ID %>
<script>
% if str(data['section_id']).isdigit():
var section_id = ${data['section_id']};
@@ -526,7 +535,9 @@ DOCUMENTATION :: END
}
recentlyWatched();
% if data['section_id'] != LIVE_TV_SECTION_ID:
recentlyAdded();
% endif
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");

View File

@@ -31,6 +31,9 @@ DOCUMENTATION :: END
</%doc>
% if data:
<%
from plexpy.helpers import page
%>
<div class="dashboard-recent-media-row">
<div id="recently-added-row-scroller" style="left: 0;">
<ul class="dashboard-recent-media list-unstyled">
@@ -38,19 +41,19 @@ DOCUMENTATION :: END
<li>
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
% if item['media_type'] == 'movie':
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">
<a href="${page('info', item['rating_key'])}" title="${item['title']}">
% elif item['media_type'] == 'episode':
<a href="info?rating_key=${item['rating_key']}" title="${item['grandparent_title']}">
<a href="${page('info', item['rating_key'])}" title="${item['grandparent_title']}">
% endif
<div class="dashboard-recent-media-poster">
% if item['media_type'] == 'episode':
% if item['parent_thumb']:
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['parent_thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-poster-face" style="background-image: url(${page('pms_image_proxy', item['parent_thumb'], item['parent_rating_key'], 300, 450, fallback='poster')});">
% else:
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['grandparent_thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-poster-face" style="background-image: url(${page('pms_image_proxy', item['grandparent_thumb'], item['grandparent_rating_key'], 300, 450, fallback='poster')});">
% endif
% elif item['media_type'] == 'movie':
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-poster-face" style="background-image: url(${page('pms_image_proxy', item['thumb'], item['rating_key'], 300, 450, fallback='poster')});">
% endif
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
@@ -68,27 +71,27 @@ DOCUMENTATION :: END
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'episode':
<h3>
<a href="info?rating_key=${item['grandparent_rating_key']}" title="${item['grandparent_title']}">${item['grandparent_title']}</a>
<a href="${page('info', item['grandparent_rating_key'])}" title="${item['grandparent_title']}">${item['grandparent_title']}</a>
</h3>
<h3 class="text-muted">
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">
<a href="info?rating_key=${item['parent_rating_key']}" title="Season ${item['parent_media_index']}">S${item['parent_media_index']}</a>
&middot; <a href="info?rating_key=${item['rating_key']}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
<a href="${page('info', item['parent_rating_key'])}" title="Season ${item['parent_media_index']}">S${item['parent_media_index']}</a>
&middot; <a href="${page('info', item['rating_key'])}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
</h3>
% elif item['media_type'] == 'movie':
<h3>
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">${item['year']}</h3>
<h3 class="text-muted">&nbsp;</h3>
% endif
</div>
% elif item['media_type'] == 'album':
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">
<a href="${page('info', item['rating_key'])}" title="${item['title']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-cover-face" style="background-image: url(${page('pms_image_proxy', item['thumb'], item['rating_key'], 300, 300, fallback='cover')});">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
@@ -100,10 +103,10 @@ DOCUMENTATION :: END
</div>
<div class="dashboard-recent-media-metacontainer">
<h3>
<a href="info?rating_key=${item['parent_rating_key']}" title="${item['parent_title']}">${item['parent_title']}</a>
<a href="${page('info', item['parent_rating_key'])}" title="${item['parent_title']}">${item['parent_title']}</a>
</h3>
<h3 class="text-muted">
<a class="text-muted" href="info?rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a class="text-muted" href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">&nbsp;</h3>
</div>

View File

@@ -25,6 +25,8 @@ DOCUMENTATION :: END
% if data:
<%
from plexpy.helpers import page
types = ('movie', 'show', 'artist', 'photo')
headers = {'movie': ('Movie Libraries', ('Movies', '', '')),
'show': ('TV Show Libraries', ('Shows', 'Seasons', 'Episodes')),
@@ -35,7 +37,7 @@ DOCUMENTATION :: END
% if section_type in data:
<div class="dashboard-stats-instance" id="library-stats-instance-${section_type}" data-section_type="${section_type}">
<div class="dashboard-stats-container">
<div id="library-stats-background-${section_type}" class="dashboard-stats-background" style="background-image: url(pms_image_proxy?img=/:/resources/${section_type}-fanart.jpg&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art);">
<div id="library-stats-background-${section_type}" class="dashboard-stats-background" style="background-image: url(${page('pms_image_proxy', '/:/resources/' + section_type + '-fanart.jpg', None, 500, 280, 40, '282828', 3, fallback='art')});">
<div id="library-stats-thumb-${section_type}" class="dashboard-stats-flat svg-icon library-${section_type} hidden-xs"></div>
<div class="dashboard-stats-info-container">
<div id="library-stats-title-${section_type}" class="dashboard-stats-info-title">
@@ -49,7 +51,7 @@ DOCUMENTATION :: END
<li class="dashboard-stats-info-item ${'expanded' if loop.index == 0 else ''}">
<div class="sub-list">${loop.index + 1}</div>
<div class="sub-value">
<a href="library?section_id=${section['section_id']}" title="${section['section_name']}">
<a href="${page('library', section['section_id'])}" title="${section['section_name']}">
${section['section_name']}
</a>
</div>

View File

@@ -19,16 +19,17 @@ DOCUMENTATION :: END
</%doc>
% if data:
<% from plexpy.helpers import page %>
% for a in data:
<ul class="list-unstyled">
<div class="user-player-instance">
<li>
% if a['user_id']:
<a href="user?user_id=${a['user_id']}" title="${a['friendly_name']}">
<a href="${page('user', a['user_id'])}" title="${a['friendly_name']}">
<div class="library-user-instance-box" style="background-image: url(${a['user_thumb']});"></div>
</a>
<div class=" user-player-instance-name">
<a href="user?user_id=${a['user_id']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
<a href="${page('user', a['user_id'])}" title="${a['friendly_name']}">${a['friendly_name']}</a>
</div>
% else:
<div class="library-user-instance-box" style="background-image: url(${a['user_thumb']});"></div>

View File

@@ -24,7 +24,7 @@
<!-- ICONS -->
<!-- Android -->
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5">
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5" crossorigin="use-credentials>
<meta name="theme-color" content="#282a2d">
<!-- Apple -->
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">
@@ -183,4 +183,4 @@
}
</script>
</body>
</html>
</html>

View File

@@ -31,6 +31,9 @@ DOCUMENTATION :: END
</%doc>
% if data != None:
<%
from plexpy.helpers import page
%>
% if data:
<div class="dashboard-recent-media-row">
<div id="recently-added-row-scroller" style="left: 0;">
@@ -39,9 +42,9 @@ DOCUMENTATION :: END
<div class="dashboard-recent-media-instance">
<li data-type="${item['media_type']}">
% if item['media_type'] == 'movie':
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">
<a href="${page('info', item['rating_key'])}" title="${item['title']}">
<div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-poster-face" style="background-image: url(${page('pms_image_proxy', item['thumb'], item['rating_key'], 300, 450, fallback='poster')});">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
@@ -57,15 +60,15 @@ DOCUMENTATION :: END
</a>
<div class="dashboard-recent-media-metacontainer">
<h3>
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">${item['year']}</h3>
<h3 class="text-muted">&nbsp;</h3>
</div>
% elif item['media_type'] == 'show':
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">
<a href="${page('info', item['rating_key'])}" title="${item['title']}">
<div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-poster-face" style="background-image: url(${page('pms_image_proxy', item['thumb'], item['rating_key'], 300, 450, fallback='poster')});">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
@@ -81,7 +84,7 @@ DOCUMENTATION :: END
</a>
<div class="dashboard-recent-media-metacontainer">
<h3>
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">
${item['child_count']} Seasons
@@ -89,9 +92,13 @@ DOCUMENTATION :: END
<h3 class="text-muted">&nbsp;</h3>
</div>
% elif item['media_type'] == 'season':
<a href="info?rating_key=${item['rating_key']}" title="${item['parent_title']}">
<a href="${page('info', item['rating_key'])}" title="${item['parent_title']}">
<div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb'] or item['parent_thumb']}&width=300&height=450&fallback=poster);">
% if item['thumb']:
<div class="dashboard-recent-media-poster-face" style="background-image: url(${page('pms_image_proxy', item['thumb'], item['rating_key'], 300, 450, fallback='poster')});">
% else:
<div class="dashboard-recent-media-poster-face" style="background-image: url(${page('pms_image_proxy', item['parent_thumb'], item['parent_rating_key'], 300, 450, fallback='poster')});">
% endif
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
@@ -107,17 +114,17 @@ DOCUMENTATION :: END
</a>
<div class="dashboard-recent-media-metacontainer">
<h3>
<a href="info?rating_key=${item['parent_rating_key']}" title="${item['parent_title']}">${item['parent_title']}</a>
<a href="${page('info', item['parent_rating_key'])}" title="${item['parent_title']}">${item['parent_title']}</a>
</h3>
<h3 class="text-muted">
<a class="text-muted" href="info?rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a class="text-muted" href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">&nbsp;</h3>
</div>
% elif item['media_type'] == 'episode':
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">
<a href="${page('info', item['rating_key'])}" title="${item['title']}">
<div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['grandparent_thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-poster-face" style="background-image: url(${page('pms_image_proxy', item['grandparent_thumb'], item['grandparent_rating_key'], 300, 450, fallback='poster')});">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
@@ -133,21 +140,21 @@ DOCUMENTATION :: END
</a>
<div class="dashboard-recent-media-metacontainer">
<h3>
<a href="info?rating_key=${item['grandparent_rating_key']}" title="${item['grandparent_title']}">${item['grandparent_title']}</a>
<a href="${page('info', item['grandparent_rating_key'])}" title="${item['grandparent_title']}">${item['grandparent_title']}</a>
</h3>
<h3 class="text-muted">
<a href="info?rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">
<a href="info?rating_key=${item['parent_rating_key']}" title="Season ${item['parent_media_index']}">S${item['parent_media_index']}</a>
<a href="${page('info', item['parent_rating_key'])}" title="Season ${item['parent_media_index']}">S${item['parent_media_index']}</a>
&middot;
<a href="info?rating_key=${item['rating_key']}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
<a href="${page('info', item['rating_key'])}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
</h3>
</div>
% elif item['media_type'] == 'album':
<a href="info?rating_key=${item['rating_key']}" title="${item['parent_title']}">
<a href="${page('info', item['rating_key'])}" title="${item['parent_title']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-cover-face" style="background-image: url(${page('pms_image_proxy', item['thumb'], item['rating_key'], 300, 300, fallback='cover')});">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
@@ -163,10 +170,10 @@ DOCUMENTATION :: END
</a>
<div class="dashboard-recent-media-metacontainer">
<h3>
<a href="info?rating_key=${item['parent_rating_key']}" title="${item['parent_title']}">${item['parent_title']}</a>
<a href="${page('info', item['parent_rating_key'])}" title="${item['parent_title']}">${item['parent_title']}</a>
</h3>
<h3 class="text-muted">
<a class="text-muted" href="info?rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a class="text-muted" href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">&nbsp;</h3>
</div>

View File

@@ -217,7 +217,7 @@
<div id="git_update_options">
<div class="checkbox">
<label>
<input type="checkbox" id="plexpy_auto_update" name="plexpy_auto_update" value="1" ${config['plexpy_auto_update']}> Update Automatically
<input type="checkbox" id="plexpy_auto_update" name="plexpy_auto_update" value="1" ${config['plexpy_auto_update']} ${docker_setting}> Update Automatically ${docker_msg | n}
</label>
<p class="help-block">Update Tautulli automatically if an update is available.</p>
</div>
@@ -1242,7 +1242,7 @@
</div>
</div>
<p class="help-block">
Enter and save your MaxMind License Key to install the GeoLite2 database.
Enter your MaxMind License Key to install the GeoLite2 database.
</p>
</div>
<div class="form-group">
@@ -1250,16 +1250,17 @@
<div class="row">
<div class="col-md-9">
<div class="input-group">
<input type="text" class="form-control" id="geoip_db" name="geoip_db" value="${config['geoip_db']}" ${docker_setting}>
<input type="text" class="form-control" id="geoip_db" name="geoip_db" value="${config['geoip_db']}" ${docker_setting} data-parsley-trigger="change" data-parsley-pattern=".+\.mmdb$" data-parsley-errors-container="#geoip_db_error" data-parsley-error-message="Must end with '.mmdb'">
<span class="input-group-btn">
<button class="btn btn-form" type="button" id="install_geoip_db" disabled>${'Update' if config["geoip_db_installed"] else 'Install'}</button>
<button class="btn btn-form" type="button" id="uninstall_geoip_db">Uninstall</button>
<button class="btn btn-form" type="button" id="install_geoip_db">${'Update' if config["geoip_db_installed"] else 'Install'}</button>
<button class="btn btn-form" type="button" id="uninstall_geoip_db" ${'disabled' if not config['geoip_db_installed'] else ''}>Uninstall</button>
</span>
</div>
</div>
<div id="geoip_db_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">
GeoLite2 Database last updated <strong><span id="geoip_db_updated">never</span></strong>.
Leave blank to install in the default location. GeoLite2 database last updated <strong><span id="geoip_db_updated">never</span></strong>.
</p>
</div>
<div class="form-group advanced-setting">
@@ -1979,12 +1980,10 @@ $(document).ready(function() {
}
function preSaveChecks(_callback) {
if (serverChanged) {
verifyServer();
}
verifyPMSWebURL();
if (_callback) {
if (serverChanged) {
verifyServer(_callback);
} else if (typeof _callback === "function") {
_callback();
}
}
@@ -2001,7 +2000,6 @@ $(document).ready(function() {
getNewslettersTable();
getMobileDevicesTable();
loadUpdateDistros();
enableGeoLiteInstallButton();
settingsChanged = false;
}
@@ -2010,12 +2008,13 @@ $(document).ready(function() {
settingsChanged = true;
});
function saveSettings() {
function saveSettings(showMsg, _callback) {
if (configForm.parsley().validate()) {
doAjaxCall('configUpdate', $(this), 'tabs', true, true, postSaveChecks);
return false;
doAjaxCall('configUpdate', $(this), 'tabs', true, showMsg, _callback);
return true;
} else {
showMsg('<i class="fa fa-exclamation-circle"></i> Please verify your settings.', false, true, 5000, true)
showMsg('<i class="fa fa-exclamation-circle"></i> Please verify your settings.', false, true, 5000, true);
return false;
}
}
@@ -2029,7 +2028,7 @@ $(document).ready(function() {
}
$('.save-button').click(function() {
preSaveChecks(function () { saveSettings() });
preSaveChecks(function () { saveSettings(true, postSaveChecks) });
});
initConfigCheckbox('#api_enabled');
@@ -2338,6 +2337,7 @@ $(document).ready(function() {
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
$("#pms_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
showMsg('Verifying Plex server...', true, true, 10000, false);
$.ajax({
url: 'get_server_id',
data: {
@@ -2375,10 +2375,11 @@ $(document).ready(function() {
} else {
$("#pms_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
$("#pms_ip_group").removeClass("has-error");
showMsg('<i class="fa fa-check"></i> Server verified.', false, true, 5000);
serverChanged = false;
}
if (_callback) {
if (typeof _callback === "function") {
_callback();
}
} else {
@@ -2530,6 +2531,7 @@ $(document).ready(function() {
for (var i in libraries_list) {
var title = libraries_list[i].section_name;
var key = libraries_list[i].section_id;
if (key === 999999) { continue; } // Don't show Live TV library
$('#sortable_home_library_cards').append(
'<li class="card card-sortable">' +
'<div class="card-handle"><i class="fa fa-bars"></i></div>' +
@@ -2862,20 +2864,24 @@ $(document).ready(function() {
gotoSetting('3rd_party_apis', 'geoip_db')
}
function enableGeoLiteInstallButton() {
$('#install_geoip_db').prop('disabled', !(Boolean($("#maxmind_license_key").val())));
}
enableGeoLiteInstallButton();
if ("${config['geoip_db_installed']}" > "0") {
$("#geoip_db_updated").text(moment("${config['geoip_db_installed']}", "X").fromNow());
}
$("#install_geoip_db").click(function () {
var maxmind_license_key = $("#maxmind_license_key");
maxmind_license_key.val($.trim(maxmind_license_key.val()));
if (maxmind_license_key.val() === "") {
maxmind_license_key.focus();
showMsg('<i class="fa fa-exclamation-circle"></i> Maxmind License Key is required.', false, true, 5000, true);
return false;
} else if (!(saveSettings())) {
return false;
}
var msg = 'Are you sure you want to install the GeoLite2 database?<br /><br />' +
'The database is used to lookup IP address geolocation info.<br />' +
'The database will be downloaded from <a href="${anon_url("https://dev.maxmind.com/geoip/geoip2/geolite2/")}" target="_blank">MaxMind</a>, <br />' +
'and requires <strong>100MB</strong> of free space to install in your Tautulli directory.<br />';
'and requires <strong>100MB</strong> of free space to install.<br />';
var url = 'install_geoip_db';
if ($(this).text() === 'Update') {
url += '?update=true';
@@ -2883,6 +2889,7 @@ $(document).ready(function() {
confirmAjaxCall(url, msg, null, 'Installing GeoLite2 database.', function (result) {
if (result.result === "success") {
$('#install_geoip_db').text('Update');
$('#uninstall_geoip_db').prop('disabled', false);
$('#geoip_db_updated').text(moment(result.updated, "X").fromNow());
}
getSchedulerTable();
@@ -2896,6 +2903,7 @@ $(document).ready(function() {
confirmAjaxCall(url, msg, null, 'Uninstalling GeoLite2 database.', function (result) {
if (result.result === "success") {
$('#install_geoip_db').text('Install');
$('#uninstall_geoip_db').prop('disabled', true);
$('#geoip_db_updated').text('never');
}
getSchedulerTable();

View File

@@ -178,6 +178,11 @@ DOCUMENTATION :: END
<td>${data['stream_video_framerate']}</td>
<td>${data['video_framerate']}</td>
</tr>
<tr>
<td>Dynamic Range</td>
<td>${data['stream_video_dynamic_range']}</td>
<td>${data['video_dynamic_range']}</td>
</tr>
<tr>
<td>Aspect Ratio</td>
<td>-</td>

View File

@@ -127,6 +127,7 @@ DOCUMENTATION :: END
</%def>
<%def name="modalIncludes()">
% if query:
<div class="modal fade" id="confirm-modal-update" tabindex="-1" role="dialog" aria-labelledby="confirm-modal-update">
<div class="modal-dialog">
<div class="modal-content">
@@ -169,6 +170,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
% endif
</%def>
<%def name="javascriptIncludes()">

View File

@@ -168,6 +168,9 @@ DOCUMENTATION :: END
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-track" value="track" autocomplete="off"> Music
</label>
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-live" value="live" autocomplete="off"> Live TV
</label>
</div>
<div class="btn-group">
<button class="btn btn-dark refresh-history-button" id="refresh-history-list"><i class="fa fa-refresh"></i> Refresh history</button>

View File

@@ -27,6 +27,9 @@ DOCUMENTATION :: END
</%doc>
% if data:
<%
from plexpy.helpers import page
%>
<div class="dashboard-recent-media-row">
<div id="recently-watched-row-scroller" style="left: 0;">
<ul class="dashboard-recent-media list-unstyled">
@@ -35,12 +38,12 @@ DOCUMENTATION :: END
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
% if item['rating_key']:
% if item['media_type'] == 'movie':
<a href="info?source=history&rating_key=${item['rating_key']}" title="${item['title']}">
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['title']}">
% elif item['media_type'] == 'episode':
<a href="info?source=history&rating_key=${item['rating_key']}" title="${item['grandparent_title']}">
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['grandparent_title']}">
% endif
<div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-poster-face" style="background-image: url(${page('pms_image_proxy', item['thumb'], item['rating_key'], 300, 450, fallback='poster')});">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
<script>
@@ -56,19 +59,38 @@ DOCUMENTATION :: END
</a>
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'episode':
% if item['live']:
<h3>
<a href="info?rating_key=${item['grandparent_rating_key']}" title="${item['grandparent_title']}">${item['grandparent_title']}</a>
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['grandparent_title']}">${item['grandparent_title']}</a>
</h3>
<h3 class="text-muted" title="${item['title']}">
<a href="info?source=history&rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['title']}">${item['title']}</a>
</h3>
% if item['media_index']:
<h3 class="text-muted">
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="Season ${item['parent_media_index']}">S${item['parent_media_index']}</a>
&middot; <a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
</h3>
% else:
<h3 class="text-muted">
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['originally_available_at']}">${item['originally_available_at']}</a>
</h3>
% endif
% else:
<h3>
<a href="${page('info', item['grandparent_rating_key'])}" title="${item['grandparent_title']}">${item['grandparent_title']}</a>
</h3>
<h3 class="text-muted" title="${item['title']}">
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">
<a href="info?rating_key=${item['parent_rating_key']}" title="Season ${item['parent_media_index']}">S${item['parent_media_index']}</a>
&middot; <a href="info?source=history&rating_key=${item['rating_key']}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
<a href="${page('info', item['parent_rating_key'])}" title="Season ${item['parent_media_index']}">S${item['parent_media_index']}</a>
&middot; <a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
</h3>
% endif
% elif item['media_type'] == 'movie':
<h3 title="${item['title']}">
<a href="info?source=history&rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">${item['year']}</h3>
<h3 class="text-muted">&nbsp;</h3>
@@ -94,9 +116,9 @@ DOCUMENTATION :: END
% endif
% elif item['media_type'] == 'track':
% if item['rating_key']:
<a href="info?source=history&rating_key=${item['rating_key']}" title="${item['parent_title']}">
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['parent_title']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-cover-face" style="background-image: url(${page('pms_image_proxy', item['thumb'], item['rating_key'], 300, 300, fallback='cover')});">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
<script>
@@ -109,13 +131,13 @@ DOCUMENTATION :: END
</a>
<div class="dashboard-recent-media-metacontainer">
<h3 title="${item['original_title'] or item['grandparent_title']}">
<a href="info?rating_key=${item['grandparent_rating_key']}" title="${item['original_title'] or item['grandparent_title']}">${item['original_title'] or item['grandparent_title']}</a>
<a href="${page('info', item['grandparent_rating_key'])}" title="${item['original_title'] or item['grandparent_title']}">${item['original_title'] or item['grandparent_title']}</a>
</h3>
<h3 class="text-muted" title="${item['title']}">
<a href="info?source=history&rating_key=${item['rating_key']}" title="${item['title']}">${item['title']}</a>
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">
<a href="info?rating_key=${item['parent_rating_key']}" title="${item['parent_title']}">${item['parent_title']}</a>
<a href="${page('info', item['parent_rating_key'])}" title="${item['parent_title']}">${item['parent_title']}</a>
</h3>
</div>
% else:

View File

@@ -27,7 +27,7 @@
<!-- ICONS -->
<!-- Android -->
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5">
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5" crossorigin="use-credentials">
<meta name="theme-color" content="#282a2d">
<!-- Apple -->
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">

View File

@@ -694,7 +694,7 @@
<div class="sub-header-count" style="margin-left: auto;margin-right: auto;font-size: 30px;text-align: center;">
<span class="count" style="color: #E5A00D;">${len(recently_added['show'])}</span> <span class="count-units" style="color: #aaaaaa;font-size: 20px;text-transform: uppercase;">show${'s' if len(recently_added['show']) > 1 else ''}</span>&nbsp;/&nbsp;
<% total_episodes = sum(season['episode_count'] for show in recently_added['show'] for season in show['season']) %>
<span class="count" style="color: #E5A00D;">${total_episodes}</span> <span class="count-units" style="color: #aaaaaa;font-size: 20px;text-transform: uppercase;">episode${'s' if total > 1 else ''}</span>
<span class="count" style="color: #E5A00D;">${total_episodes}</span> <span class="count-units" style="color: #aaaaaa;font-size: 20px;text-transform: uppercase;">episode${'s' if total_episodes > 1 else ''}</span>
</div>
</td>
</tr>
@@ -847,7 +847,7 @@
<div class="sub-header-count" style="margin-left: auto;margin-right: auto;font-size: 30px;text-align: center;">
<span class="count" style="color: #E5A00D;">${len(recently_added['artist'])}</span> <span class="count-units" style="color: #aaaaaa;font-size: 20px;text-transform: uppercase;">artist${'s' if len(recently_added['artist']) > 1 else ''}</span> /
<% total_albums = sum(artist['album_count'] for artist in recently_added['artist']) %>
<span class="count" style="color: #E5A00D;">${total_albums}</span> <span class="count-units" style="color: #aaaaaa;font-size: 20px;text-transform: uppercase;">album${'s' if total > 1 else ''}</span>
<span class="count" style="color: #E5A00D;">${total_albums}</span> <span class="count-units" style="color: #aaaaaa;font-size: 20px;text-transform: uppercase;">album${'s' if total_albums > 1 else ''}</span>
</div>
</td>
</tr>

View File

@@ -694,7 +694,7 @@
<div class="sub-header-count">
<span class="count">${len(recently_added['show'])}</span> <span class="count-units">show${'s' if len(recently_added['show']) > 1 else ''}</span>&nbsp;/&nbsp;
<% total_episodes = sum(season['episode_count'] for show in recently_added['show'] for season in show['season']) %>
<span class="count">${total_episodes}</span> <span class="count-units">episode${'s' if total > 1 else ''}</span>
<span class="count">${total_episodes}</span> <span class="count-units">episode${'s' if total_episodes > 1 else ''}</span>
</div>
</td>
</tr>
@@ -847,7 +847,7 @@
<div class="sub-header-count">
<span class="count">${len(recently_added['artist'])}</span> <span class="count-units">artist${'s' if len(recently_added['artist']) > 1 else ''}</span> /
<% total_albums = sum(artist['album_count'] for artist in recently_added['artist']) %>
<span class="count">${total_albums}</span> <span class="count-units">album${'s' if total > 1 else ''}</span>
<span class="count">${total_albums}</span> <span class="count-units">album${'s' if total_albums > 1 else ''}</span>
</div>
</td>
</tr>

View File

@@ -1,3 +1,3 @@
from .core import where
__version__ = "2019.03.09"
__version__ = "2019.11.28"

View File

@@ -771,36 +771,6 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
# Issuer: CN=Class 2 Primary CA O=Certplus
# Subject: CN=Class 2 Primary CA O=Certplus
# Label: "Certplus Class 2 Primary CA"
# Serial: 177770208045934040241468760488327595043
# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b
# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb
# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb
-----BEGIN CERTIFICATE-----
MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
l7+ijrRU
-----END CERTIFICATE-----
# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co.
# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co.
# Label: "DST Root CA X3"
@@ -1219,36 +1189,6 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----
# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center
# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center
# Label: "Deutsche Telekom Root CA 2"
# Serial: 38
# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08
# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf
# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3
-----BEGIN CERTIFICATE-----
MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
Cm26OWMohpLzGITY+9HPBVZkVw==
-----END CERTIFICATE-----
# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc
# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc
# Label: "Cybertrust Global Root"
@@ -3453,46 +3393,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----
# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903
# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903
# Label: "Certinomis - Root CA"
# Serial: 1
# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f
# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8
# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58
-----BEGIN CERTIFICATE-----
MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET
MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb
BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz
MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx
FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g
Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2
fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl
LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV
WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF
TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb
5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc
CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri
wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ
wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG
m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4
F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng
WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0
2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF
AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/
0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw
F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS
g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj
qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN
h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/
ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V
btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj
Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ
8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW
gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE=
-----END CERTIFICATE-----
# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed
# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed
# Label: "OISTE WISeKey Global Root GB CA"
@@ -4656,3 +4556,47 @@ L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa
LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG
mpv0
-----END CERTIFICATE-----
# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only
# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only
# Label: "Entrust Root Certification Authority - G4"
# Serial: 289383649854506086828220374796556676440
# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88
# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01
# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88
-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw
gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL
Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg
MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw
BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0
MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1
c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ
bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg
Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ
2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E
T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j
5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM
C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T
DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX
wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A
2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm
nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8
dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl
N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj
c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS
5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS
Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr
hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/
B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI
AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw
H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+
b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk
2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol
IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk
5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY
n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw==
-----END CERTIFICATE-----

View File

@@ -65,7 +65,7 @@ SYS_LANGUAGE = None
SYS_ENCODING = None
QUIET = False
VERBOSE = True
VERBOSE = False
DAEMON = False
CREATEPID = False
PIDFILE = None
@@ -122,6 +122,7 @@ def initialize(config_file):
global CONFIG
global CONFIG_FILE
global VERBOSE
global _INITIALIZED
global CURRENT_VERSION
global LATEST_VERSION
@@ -152,6 +153,8 @@ def initialize(config_file):
if not log_writable and not QUIET:
sys.stderr.write("Unable to create the log directory. Logging to screen only.\n")
VERBOSE = VERBOSE or bool(CONFIG.VERBOSE_LOGS)
# Start the logger, disable console if needed
logger.initLogger(console=not QUIET, log_dir=CONFIG.LOG_DIR if log_writable else None,
verbose=VERBOSE)
@@ -582,15 +585,18 @@ def dbcheck():
'media_index INTEGER, parent_media_index INTEGER, '
'thumb TEXT, parent_thumb TEXT, grandparent_thumb TEXT, year INTEGER, '
'parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
'originally_available_at TEXT, added_at INTEGER, guid TEXT, '
'view_offset INTEGER DEFAULT 0, duration INTEGER, video_decision TEXT, audio_decision TEXT, '
'transcode_decision TEXT, container TEXT, bitrate INTEGER, width INTEGER, height INTEGER, '
'video_codec TEXT, video_bitrate INTEGER, video_resolution TEXT, video_width INTEGER, video_height INTEGER, '
'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, aspect_ratio TEXT, '
'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, '
'video_dynamic_range TEXT, aspect_ratio TEXT, '
'audio_codec TEXT, audio_bitrate INTEGER, audio_channels INTEGER, subtitle_codec TEXT, '
'stream_bitrate INTEGER, stream_video_resolution TEXT, quality_profile TEXT, '
'stream_container_decision TEXT, stream_container TEXT, '
'stream_video_decision TEXT, stream_video_codec TEXT, stream_video_bitrate INTEGER, stream_video_width INTEGER, '
'stream_video_height INTEGER, stream_video_framerate TEXT, stream_video_scan_type TEXT, stream_video_full_resolution TEXT, '
'stream_video_dynamic_range TEXT, '
'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, '
'subtitles INTEGER, stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, '
'transcode_protocol TEXT, transcode_container TEXT, '
@@ -599,7 +605,8 @@ def dbcheck():
'transcode_hw_decoding INTEGER, transcode_hw_encoding INTEGER, '
'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT, '
'synced_version INTEGER, synced_version_profile TEXT, '
'live INTEGER, live_uuid TEXT, secure INTEGER, relayed INTEGER, '
'live INTEGER, live_uuid TEXT, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT, '
'secure INTEGER, relayed INTEGER, '
'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, watched INTEGER DEFAULT 0, '
'write_attempts INTEGER DEFAULT 0, raw_stream_info TEXT)'
)
@@ -620,7 +627,7 @@ def dbcheck():
'video_decision TEXT, audio_decision TEXT, transcode_decision TEXT, duration INTEGER DEFAULT 0, '
'container TEXT, bitrate INTEGER, width INTEGER, height INTEGER, video_bitrate INTEGER, video_bit_depth INTEGER, '
'video_codec TEXT, video_codec_level TEXT, video_width INTEGER, video_height INTEGER, video_resolution TEXT, '
'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, aspect_ratio TEXT, '
'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, video_dynamic_range TEXT, aspect_ratio TEXT, '
'audio_bitrate INTEGER, audio_codec TEXT, audio_channels INTEGER, transcode_protocol TEXT, '
'transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, '
'transcode_audio_channels INTEGER, transcode_width INTEGER, transcode_height INTEGER, '
@@ -630,7 +637,7 @@ def dbcheck():
'stream_container TEXT, stream_container_decision TEXT, stream_bitrate INTEGER, '
'stream_video_decision TEXT, stream_video_bitrate INTEGER, stream_video_codec TEXT, stream_video_codec_level TEXT, '
'stream_video_bit_depth INTEGER, stream_video_height INTEGER, stream_video_width INTEGER, stream_video_resolution TEXT, '
'stream_video_framerate TEXT, stream_video_scan_type TEXT, stream_video_full_resolution TEXT, '
'stream_video_framerate TEXT, stream_video_scan_type TEXT, stream_video_full_resolution TEXT, stream_video_dynamic_range TEXT, '
'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, '
'stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, stream_subtitle_container TEXT, stream_subtitle_forced INTEGER, '
'subtitles INTEGER, subtitle_codec TEXT, synced_version INTEGER, synced_version_profile TEXT, '
@@ -647,7 +654,7 @@ def dbcheck():
'art TEXT, media_type TEXT, year INTEGER, originally_available_at TEXT, added_at INTEGER, updated_at INTEGER, '
'last_viewed_at INTEGER, content_rating TEXT, summary TEXT, tagline TEXT, rating TEXT, '
'duration INTEGER DEFAULT 0, guid TEXT, directors TEXT, writers TEXT, actors TEXT, genres TEXT, studio TEXT, '
'labels TEXT)'
'labels TEXT, live INTEGER DEFAULT 0, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT)'
)
# users table :: This table keeps record of the friends list
@@ -1203,6 +1210,54 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN stream_video_full_resolution TEXT'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT video_dynamic_range FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN video_dynamic_range TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN stream_video_dynamic_range TEXT'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT channel_identifier FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN channel_call_sign TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN channel_identifier TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN channel_thumb TEXT'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT originally_available_at FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN originally_available_at TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN added_at INTEGER'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT guid FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN guid TEXT'
)
# Upgrade session_history table from earlier versions
try:
c_db.execute('SELECT reference_id FROM session_history')
@@ -1310,6 +1365,24 @@ def dbcheck():
'ALTER TABLE session_history_metadata ADD COLUMN original_title TEXT'
)
# Upgrade session_history_metadata table from earlier versions
try:
c_db.execute('SELECT live FROM session_history_metadata')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table session_history_metadata.")
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN live INTEGER DEFAULT 0'
)
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN channel_call_sign TEXT'
)
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN channel_identifier TEXT'
)
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN channel_thumb TEXT'
)
# Upgrade session_history_media_info table from earlier versions
try:
c_db.execute('SELECT transcode_decision FROM session_history_media_info')
@@ -1541,6 +1614,26 @@ def dbcheck():
'ELSE stream_video_resolution || "p" END)'
)
# Upgrade session_history_media_info table from earlier versions
try:
c_db.execute('SELECT video_dynamic_range FROM session_history_media_info')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table session_history_media_info.")
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN video_dynamic_range TEXT '
)
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN stream_video_dynamic_range TEXT '
)
result = c_db.execute('SELECT * FROM session_history_media_info '
'WHERE video_dynamic_range = "SDR" AND stream_video_dynamic_range = "HDR"').fetchone()
if result:
c_db.execute(
'UPDATE session_history_media_info SET stream_video_dynamic_range = "SDR" '
'WHERE video_dynamic_range = "SDR" AND stream_video_dynamic_range = "HDR"'
)
# Upgrade users table from earlier versions
try:
c_db.execute('SELECT do_notify FROM users')

View File

@@ -58,9 +58,19 @@ class ActivityHandler(object):
return None
def get_live_session(self):
def get_metadata(self, skip_cache=False):
cache_key = None if skip_cache else self.get_session_key()
pms_connect = pmsconnect.PmsConnect()
session_list = pms_connect.get_current_activity()
metadata = pms_connect.get_metadata_details(rating_key=self.get_rating_key(), cache_key=cache_key)
if metadata:
return metadata
return None
def get_live_session(self, skip_cache=False):
pms_connect = pmsconnect.PmsConnect()
session_list = pms_connect.get_current_activity(skip_cache=skip_cache)
if session_list:
for session in session_list['sessions']:
@@ -94,7 +104,7 @@ class ActivityHandler(object):
def on_start(self):
if self.is_valid_session():
session = self.get_live_session()
session = self.get_live_session(skip_cache=True)
if not session:
return
@@ -269,11 +279,20 @@ class ActivityHandler(object):
last_transcode_key = db_session['transcode_key'].split('/')[-1]
last_paused = db_session['last_paused']
last_rating_key_websocket = db_session['rating_key_websocket']
last_guid = db_session['guid']
this_guid = last_guid
# Check guid for live TV metadata every 60 seconds
if db_session['live'] and int(time.time()) - db_session['stopped'] > 60:
metadata = self.get_metadata(skip_cache=True)
if metadata:
this_guid = metadata['guid']
# Make sure the same item is being played
if this_rating_key == last_rating_key \
or this_rating_key == last_rating_key_websocket \
or this_live_uuid == last_live_uuid:
if (this_rating_key == last_rating_key
or this_rating_key == last_rating_key_websocket
or this_live_uuid == last_live_uuid) \
and this_guid == last_guid:
# Update the session state and viewOffset
if this_state == 'playing':
# Update the session in our temp session table

View File

@@ -61,6 +61,9 @@ class ActivityProcessor(object):
'platform': session.get('platform', ''),
'parent_rating_key': session.get('parent_rating_key', ''),
'grandparent_rating_key': session.get('grandparent_rating_key', ''),
'originally_available_at': session.get('originally_available_at', ''),
'added_at': session.get('added_at', ''),
'guid': session.get('guid', ''),
'view_offset': session.get('view_offset', ''),
'duration': session.get('duration', ''),
'video_decision': session.get('video_decision', ''),
@@ -78,6 +81,7 @@ class ActivityProcessor(object):
'video_framerate': session.get('video_framerate', ''),
'video_scan_type': session.get('video_scan_type', ''),
'video_full_resolution': session.get('video_full_resolution', ''),
'video_dynamic_range': session.get('video_dynamic_range', ''),
'aspect_ratio': session.get('aspect_ratio', ''),
'audio_codec': session.get('audio_codec', ''),
'audio_bitrate': session.get('audio_bitrate', ''),
@@ -110,6 +114,7 @@ class ActivityProcessor(object):
'stream_video_framerate': session.get('stream_video_framerate', ''),
'stream_video_scan_type': session.get('stream_video_scan_type', ''),
'stream_video_full_resolution': session.get('stream_video_full_resolution', ''),
'stream_video_dynamic_range': session.get('stream_video_dynamic_range', ''),
'stream_audio_decision': session.get('stream_audio_decision', ''),
'stream_audio_codec': session.get('stream_audio_codec', ''),
'stream_audio_bitrate': session.get('stream_audio_bitrate', ''),
@@ -123,6 +128,9 @@ class ActivityProcessor(object):
'relayed': session.get('relayed', 0),
'rating_key_websocket': session.get('rating_key_websocket', ''),
'raw_stream_info': json.dumps(session),
'channel_call_sign': session.get('channel_call_sign', ''),
'channel_identifier': session.get('channel_identifier', ''),
'channel_thumb': session.get('channel_thumb', ''),
'stopped': int(time.time())
}
@@ -141,6 +149,10 @@ class ActivityProcessor(object):
timestamp = {'started': started}
self.db.upsert('sessions', timestamp, keys)
# Add Live TV library if it hasn't been added
if values['live']:
libraries.add_live_tv_library()
return True
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0):
@@ -233,7 +245,12 @@ class ActivityProcessor(object):
if not is_import:
logger.debug(u"Tautulli ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
metadata = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
if session['live']:
metadata = pms_connect.get_metadata_details(rating_key=str(session['rating_key']),
cache_key=session['session_key'],
return_cache=True)
else:
metadata = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
if not metadata:
return False
else:
@@ -277,38 +294,57 @@ class ActivityProcessor(object):
# % session['session_key'])
self.db.upsert(table_name='session_history', key_dict=keys, value_dict=values)
# Check if we should group the session, select the last two rows from the user
query = 'SELECT id, rating_key, view_offset, user_id, reference_id FROM session_history ' \
'WHERE user_id = ? AND rating_key = ? ORDER BY id DESC LIMIT 2 '
args = [session['user_id'], session['rating_key']]
result = self.db.select(query=query, args=args)
new_session = prev_session = None
prev_progress_percent = media_watched_percent = 0
# Get the last insert row id
last_id = self.db.last_insert_id()
new_session = prev_session = None
prev_progress_percent = media_watched_percent = 0
if len(result) > 1:
new_session = {'id': result[0]['id'],
'rating_key': result[0]['rating_key'],
'view_offset': result[0]['view_offset'],
'user_id': result[0]['user_id'],
'reference_id': result[0]['reference_id']}
if session['live']:
# Check if we should group the session, select the last guid from the user
query = 'SELECT session_history.id, session_history_metadata.guid, session_history.reference_id ' \
'FROM session_history ' \
'JOIN session_history_metadata ON session_history.id == session_history_metadata.id ' \
'WHERE session_history.user_id = ? ORDER BY session_history.id DESC LIMIT 1 '
prev_session = {'id': result[1]['id'],
'rating_key': result[1]['rating_key'],
'view_offset': result[1]['view_offset'],
'user_id': result[1]['user_id'],
'reference_id': result[1]['reference_id']}
args = [session['user_id']]
watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT,
'episode': plexpy.CONFIG.TV_WATCHED_PERCENT,
'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT
}
prev_progress_percent = helpers.get_percent(prev_session['view_offset'], session['duration'])
media_watched_percent = watched_percent.get(session['media_type'], 0)
result = self.db.select(query=query, args=args)
if len(result) > 0:
new_session = {'id': last_id,
'guid': metadata['guid'],
'reference_id': last_id}
prev_session = {'id': result[0]['id'],
'guid': result[0]['guid'],
'reference_id': result[0]['reference_id']}
else:
# Check if we should group the session, select the last two rows from the user
query = 'SELECT id, rating_key, view_offset, reference_id FROM session_history ' \
'WHERE user_id = ? AND rating_key = ? ORDER BY id DESC LIMIT 2 '
args = [session['user_id'], session['rating_key']]
result = self.db.select(query=query, args=args)
if len(result) > 1:
new_session = {'id': result[0]['id'],
'rating_key': result[0]['rating_key'],
'view_offset': result[0]['view_offset'],
'reference_id': result[0]['reference_id']}
prev_session = {'id': result[1]['id'],
'rating_key': result[1]['rating_key'],
'view_offset': result[1]['view_offset'],
'reference_id': result[1]['reference_id']}
watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT,
'episode': plexpy.CONFIG.TV_WATCHED_PERCENT,
'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT
}
prev_progress_percent = helpers.get_percent(prev_session['view_offset'], session['duration'])
media_watched_percent = watched_percent.get(session['media_type'], 0)
query = 'UPDATE session_history SET reference_id = ? WHERE id = ? '
@@ -319,7 +355,8 @@ class ActivityProcessor(object):
if prev_session is None and new_session is None:
args = [last_id, last_id]
elif prev_progress_percent < media_watched_percent and \
prev_session['view_offset'] <= new_session['view_offset']:
prev_session['view_offset'] <= new_session['view_offset'] or \
session['live'] and prev_session['guid'] == new_session['guid']:
args = [prev_session['reference_id'], new_session['id']]
else:
args = [new_session['id'], new_session['id']]
@@ -353,6 +390,7 @@ class ActivityProcessor(object):
'video_framerate': session['video_framerate'],
'video_scan_type': session['video_scan_type'],
'video_full_resolution': session['video_full_resolution'],
'video_dynamic_range': session['video_dynamic_range'],
'aspect_ratio': session['aspect_ratio'],
'audio_codec': session['audio_codec'],
'audio_bitrate': session['audio_bitrate'],
@@ -387,6 +425,7 @@ class ActivityProcessor(object):
'stream_video_framerate': session['stream_video_framerate'],
'stream_video_scan_type': session['stream_video_scan_type'],
'stream_video_full_resolution': session['stream_video_full_resolution'],
'stream_video_dynamic_range': session['stream_video_dynamic_range'],
'stream_audio_decision': session['stream_audio_decision'],
'stream_audio_codec': session['stream_audio_codec'],
'stream_audio_bitrate': session['stream_audio_bitrate'],
@@ -449,7 +488,11 @@ class ActivityProcessor(object):
'actors': actors,
'genres': genres,
'studio': metadata['studio'],
'labels': labels
'labels': labels,
'live': session['live'],
'channel_call_sign': media_info.get('channel_call_sign', ''),
'channel_identifier': media_info.get('channel_identifier', ''),
'channel_thumb': media_info.get('channel_thumb', '')
}
# logger.debug(u"Tautulli ActivityProcessor :: Writing sessionKey %s session_history_metadata transaction..."

View File

@@ -116,7 +116,7 @@ class API2:
# Allow override for the api.
self._api_out_type = kwargs.pop('out_type', 'json')
if 'app' in kwargs and kwargs.pop('app') == 'true':
if 'app' in kwargs and helpers.bool_true(kwargs.pop('app')):
self._api_app = True
if plexpy.CONFIG.API_ENABLED and not self._api_msg or self._api_cmd in ('get_apikey', 'docs', 'docs_md'):

View File

@@ -34,11 +34,26 @@ DEFAULT_USER_THUMB = "interfaces/default/images/gravatar-default-80x80.png"
DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png"
DEFAULT_ART = "interfaces/default/images/art.png"
DEFAULT_LIVE_TV_POSTER_THUMB = "interfaces/default/images/poster-live.png"
DEFAULT_LIVE_TV_ART = "interfaces/default/images/art-live.png"
DEFAULT_LIVE_TV_ART_FULL = "interfaces/default/images/art-live-full.png"
ONLINE_POSTER_THUMB = "https://tautulli.com/images/poster.png"
ONLINE_COVER_THUMB = "https://tautulli.com/images/cover.png"
ONLINE_ART = "https://tautulli.com/images/art.png"
LIVE_TV_SECTION_ID = 999999 # Fake section_id for Live TV library
LIVE_TV_SECTION_NAME = "Live TV" # Fake section_name for Live TV library
DEFAULT_IMAGES = {
'poster': DEFAULT_POSTER_THUMB,
'cover': DEFAULT_COVER_THUMB,
'art': DEFAULT_ART,
'poster-live': DEFAULT_LIVE_TV_POSTER_THUMB,
'art-live': DEFAULT_LIVE_TV_ART,
'art-live-full': DEFAULT_LIVE_TV_ART_FULL
}
MEDIA_TYPE_HEADERS = {
'movie': 'Movies',
'show': 'TV Shows',
@@ -54,7 +69,9 @@ PLATFORM_NAME_OVERRIDES = {
'Mystery 3': 'Playstation 3',
'Mystery 4': 'Playstation 4',
'Mystery 5': 'Xbox 360',
'WebMAF': 'Playstation 4'
'WebMAF': 'Playstation 4',
'windows': 'Windows',
'osx': 'macOS'
}
PMS_PLATFORM_NAME_OVERRIDES = {
@@ -349,6 +366,9 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Optimized Version Profile', 'type': 'str', 'value': 'optimized_version_profile', 'description': 'The optimized version profile of the stream.'},
{'name': 'Synced Version', 'type': 'int', 'value': 'synced_version', 'description': 'If the stream is an synced version.', 'example': '0 or 1'},
{'name': 'Live', 'type': 'int', 'value': 'live', 'description': 'If the stream is live TV.', 'example': '0 or 1'},
{'name': 'Channel Call Sign', 'type': 'str', 'value': 'channel_call_sign', 'description': 'The Live TV channel call sign.'},
{'name': 'Channel Identifier', 'type': 'str', 'value': 'channel_identifier', 'description': 'The Live TV channel number.'},
{'name': 'Channel Thumb', 'type': 'str', 'value': 'channel_thumb', 'description': 'The URL for the Live TV channel logo.'},
{'name': 'Secure', 'type': 'int', 'value': 'secure', 'description': 'If the stream is using a secure connection.', 'example': '0 or 1'},
{'name': 'Relayed', 'type': 'int', 'value': 'relayed', 'description': 'If the stream is using Plex Relay.', 'example': '0 or 1'},
{'name': 'Stream Local', 'type': 'int', 'value': 'stream_local', 'description': 'If the stream is local.', 'example': '0 or 1'},
@@ -361,6 +381,12 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Stream Video Codec Level', 'type': 'int', 'value': 'stream_video_codec_level', 'description': 'The video codec level of the stream.'},
{'name': 'Stream Video Bitrate', 'type': 'int', 'value': 'stream_video_bitrate', 'description': 'The video bitrate (in kbps) of the stream.'},
{'name': 'Stream Video Bit Depth', 'type': 'int', 'value': 'stream_video_bit_depth', 'description': 'The video bit depth of the stream.'},
{'name': 'Stream Video Chroma Subsampling', 'type': 'str', 'value': 'stream_video_chroma_subsampling', 'description': 'The video chroma subsampling of the stream.'},
{'name': 'Stream Video Color Primaries', 'type': 'str', 'value': 'stream_video_color_primaries', 'description': 'The video color primaries of the stream.'},
{'name': 'Stream Video Color Range', 'type': 'str', 'value': 'stream_video_color_range', 'description': 'The video color range of the stream.'},
{'name': 'Stream Video Color Space', 'type': 'str', 'value': 'stream_video_color_space', 'description': 'The video color space of the stream.'},
{'name': 'Stream Video Color Transfer Function', 'type': 'str', 'value': 'stream_video_color_trc', 'description': 'The video transfer function of the stream.'},
{'name': 'Stream Video Dynamic Range', 'type': 'str', 'value': 'stream_video_dynamic_range', 'description': 'The video dynamic range of the stream.', 'example': 'HDR or SDR'},
{'name': 'Stream Video Framerate', 'type': 'str', 'value': 'stream_video_framerate', 'description': 'The video framerate of the stream.'},
{'name': 'Stream Video Full Resolution', 'type': 'str', 'value': 'stream_video_full_resolution', 'description': 'The video resolution of the stream with scan type.'},
{'name': 'Stream Video Ref Frames', 'type': 'int', 'value': 'stream_video_ref_frames', 'description': 'The video reference frames of the stream.'},
@@ -468,6 +494,12 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Video Codec Level', 'type': 'int', 'value': 'video_codec_level', 'description': 'The video codec level of the original media.'},
{'name': 'Video Bitrate', 'type': 'int', 'value': 'video_bitrate', 'description': 'The video bitrate of the original media.'},
{'name': 'Video Bit Depth', 'type': 'int', 'value': 'video_bit_depth', 'description': 'The video bit depth of the original media.'},
{'name': 'Video Chroma Subsampling', 'type': 'str', 'value': 'video_chroma_subsampling', 'description': 'The video chroma subsampling of the original media.'},
{'name': 'Video Color Primaries', 'type': 'str', 'value': 'video_color_primaries', 'description': 'The video color primaries of the original media.'},
{'name': 'Video Color Range', 'type': 'str', 'value': 'video_color_range', 'description': 'The video color range of the original media.'},
{'name': 'Video Color Space', 'type': 'str', 'value': 'video_color_space', 'description': 'The video color space of the original media.'},
{'name': 'Video Color Transfer Function', 'type': 'str', 'value': 'video_color_trc', 'description': 'The video transfer function of the original media.'},
{'name': 'Video Dynamic Range', 'type': 'str', 'value': 'video_dynamic_range', 'description': 'The video dynamic range of the original media.', 'example': 'HDR or SDR'},
{'name': 'Video Framerate', 'type': 'str', 'value': 'video_framerate', 'description': 'The video framerate of the original media.'},
{'name': 'Video Full Resolution', 'type': 'str', 'value': 'video_full_resolution', 'description': 'The video resolution of the original media with scan type.'},
{'name': 'Video Ref Frames', 'type': 'int', 'value': 'video_ref_frames', 'description': 'The video reference frames of the original media.'},

View File

@@ -68,6 +68,7 @@ _CONFIG_DEFINITIONS = {
'PMS_UPDATE_CHECK_INTERVAL': (int, 'Advanced', 24),
'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'),
'TIME_FORMAT': (str, 'General', 'HH:mm'),
'ADD_LIVE_TV_LIBRARY': (int, 'Advanced', 1),
'ANON_REDIRECT': (str, 'General', 'http://www.nullrefer.com/?'),
'API_ENABLED': (int, 'General', 1),
'API_KEY': (str, 'General', ''),
@@ -603,6 +604,7 @@ _CONFIG_DEFINITIONS = {
'UPDATE_LABELS': (int, 'General', 1),
'UPDATE_LIBRARIES_DB_NOTIFY': (int, 'General', 1),
'UPDATE_NOTIFIERS_DB': (int, 'General', 1),
'VERBOSE_LOGS': (int, 'Advanced', 1),
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1),
'WEBSOCKET_MONITOR_PING_PONG': (int, 'Advanced', 0),
@@ -932,3 +934,9 @@ class Config(object):
self.GEOIP_DB = os.path.join(plexpy.DATA_DIR, 'GeoLite2-City.mmdb')
self.CONFIG_VERSION = 14
if self.CONFIG_VERSION == 14:
if plexpy.DOCKER:
self.PLEXPY_AUTO_UPDATE = 0
self.CONFIG_VERSION == 15

View File

@@ -94,6 +94,10 @@ class DataFactory(object):
'session_history_metadata.thumb',
'session_history_metadata.parent_thumb',
'session_history_metadata.grandparent_thumb',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \
(CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \
THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
@@ -142,6 +146,10 @@ class DataFactory(object):
'thumb',
'parent_thumb',
'grandparent_thumb',
'live',
'added_at',
'originally_available_at',
'guid',
'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \
(CASE WHEN (duration IS NULL OR duration = "") \
THEN 1.0 ELSE duration * 1.0 END) * 100) AS percent_complete',
@@ -206,6 +214,9 @@ class DataFactory(object):
else:
thumb = item['thumb']
if item['live']:
item['percent_complete'] = 100
if item['percent_complete'] >= watched_percent[item['media_type']]:
watched_status = 1
elif item['percent_complete'] >= watched_percent[item['media_type']]/2:
@@ -230,6 +241,7 @@ class DataFactory(object):
'product': item['product'],
'player': item['player'],
'ip_address': item['ip_address'],
'live': item['live'],
'media_type': item['media_type'],
'rating_key': item['rating_key'],
'parent_rating_key': item['parent_rating_key'],
@@ -243,6 +255,8 @@ class DataFactory(object):
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'thumb': thumb,
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'transcode_decision': item['transcode_decision'],
'percent_complete': int(round(item['percent_complete'])),
'watched_status': watched_status,
@@ -286,7 +300,7 @@ class DataFactory(object):
top_movies = []
try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@@ -323,6 +337,8 @@ class DataFactory(object):
'friendly_name': '',
'platform': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
top_movies.append(row)
@@ -336,7 +352,7 @@ class DataFactory(object):
popular_movies = []
try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'COUNT(DISTINCT t.user_id) AS users_watched, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@@ -372,6 +388,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
popular_movies.append(row)
@@ -384,7 +402,7 @@ class DataFactory(object):
top_tv = []
try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, ' \
't.rating_key, t.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@@ -408,7 +426,7 @@ class DataFactory(object):
'total_plays': item['total_plays'],
'total_duration': item['total_duration'],
'users_watched': '',
'rating_key': item['grandparent_rating_key'],
'rating_key': item['rating_key'] if item['live'] else item['grandparent_rating_key'],
'last_play': item['last_watch'],
'grandparent_thumb': item['grandparent_thumb'],
'thumb': item['grandparent_thumb'],
@@ -420,6 +438,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
top_tv.append(row)
@@ -433,7 +453,7 @@ class DataFactory(object):
popular_tv = []
try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, ' \
't.rating_key, t.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'COUNT(DISTINCT t.user_id) AS users_watched, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@@ -456,7 +476,7 @@ class DataFactory(object):
for item in result:
row = {'title': item['grandparent_title'],
'users_watched': item['users_watched'],
'rating_key': item['grandparent_rating_key'],
'rating_key': item['rating_key'] if item['live'] else item['grandparent_rating_key'],
'last_play': item['last_watch'],
'total_plays': item['total_plays'],
'grandparent_thumb': item['grandparent_thumb'],
@@ -469,6 +489,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
popular_tv.append(row)
@@ -482,7 +504,7 @@ class DataFactory(object):
try:
query = 'SELECT t.id, t.grandparent_title, t.original_title, ' \
't.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@@ -518,6 +540,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
top_music.append(row)
@@ -532,7 +556,7 @@ class DataFactory(object):
try:
query = 'SELECT t.id, t.grandparent_title, t.original_title, ' \
't.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, ' \
't.art, t.media_type, t.content_rating, t.labels, t.started, t.live, t.guid, ' \
'COUNT(DISTINCT t.user_id) AS users_watched, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@@ -568,6 +592,8 @@ class DataFactory(object):
'user': '',
'friendly_name': '',
'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id']
}
popular_music.append(row)
@@ -684,7 +710,7 @@ class DataFactory(object):
try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.grandparent_thumb, ' \
't.user, t.user_id, t.custom_avatar_url as user_thumb, t.player, t.section_id, ' \
't.art, t.media_type, t.content_rating, t.labels, ' \
't.art, t.media_type, t.content_rating, t.labels, t.live, t.guid, ' \
'(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \
' AS friendly_name, ' \
'MAX(t.started) AS last_watch, ' \
@@ -730,6 +756,8 @@ class DataFactory(object):
'content_rating': item['content_rating'],
'labels': item['labels'].split(';') if item['labels'] else (),
'last_watch': item['last_watch'],
'live': item['live'],
'guid': item['guid'],
'player': item['player']
}
last_watched.append(row)
@@ -878,11 +906,12 @@ class DataFactory(object):
query = 'SELECT bitrate, video_full_resolution, ' \
'optimized_version, optimized_version_profile, optimized_version_title, ' \
'synced_version, synced_version_profile, ' \
'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \
'container, video_codec, video_bitrate, video_width, video_height, video_framerate, ' \
'video_dynamic_range, aspect_ratio, ' \
'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \
'stream_bitrate, stream_video_full_resolution, quality_profile, stream_container_decision, stream_container, ' \
'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \
'stream_video_framerate, ' \
'stream_video_framerate, stream_video_dynamic_range, ' \
'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \
'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \
'transcode_hw_decoding, transcode_hw_encoding, ' \
@@ -899,11 +928,12 @@ class DataFactory(object):
query = 'SELECT bitrate, video_full_resolution, ' \
'optimized_version, optimized_version_profile, optimized_version_title, ' \
'synced_version, synced_version_profile, ' \
'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \
'container, video_codec, video_bitrate, video_width, video_height, video_framerate, ' \
'video_dynamic_range, aspect_ratio, ' \
'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \
'stream_bitrate, stream_video_full_resolution, quality_profile, stream_container_decision, stream_container, ' \
'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \
'stream_video_framerate, ' \
'stream_video_framerate, stream_video_dynamic_range, ' \
'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \
'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \
'transcode_hw_decoding, transcode_hw_encoding, ' \
@@ -950,6 +980,7 @@ class DataFactory(object):
'video_width': item['video_width'],
'video_height': item['video_height'],
'video_framerate': item['video_framerate'],
'video_dynamic_range': item['video_dynamic_range'],
'aspect_ratio': item['aspect_ratio'],
'audio_codec': item['audio_codec'],
'audio_bitrate': item['audio_bitrate'],
@@ -966,6 +997,7 @@ class DataFactory(object):
'stream_video_width': item['stream_video_width'],
'stream_video_height': item['stream_video_height'],
'stream_video_framerate': item['stream_video_framerate'],
'stream_video_dynamic_range': item['stream_video_dynamic_range'],
'stream_audio_decision': item['stream_audio_decision'],
'stream_audio_codec': item['stream_audio_codec'],
'stream_audio_bitrate': item['stream_audio_bitrate'],
@@ -988,10 +1020,17 @@ class DataFactory(object):
stream_output = {k: v or '' for k, v in stream_output.iteritems()}
return stream_output
def get_metadata_details(self, rating_key):
def get_metadata_details(self, rating_key='', guid=''):
monitor_db = database.MonitorDatabase()
if rating_key:
if rating_key or guid:
if guid:
where = 'session_history_metadata.guid LIKE ?'
args = [guid.split('?')[0] + '%'] # SQLite LIKE wildcard
else:
where = 'session_history_metadata.rating_key = ?'
args = [rating_key]
query = 'SELECT session_history_metadata.id, ' \
'session_history_metadata.rating_key, session_history_metadata.parent_rating_key, ' \
'session_history_metadata.grandparent_rating_key, session_history_metadata.title, ' \
@@ -1011,15 +1050,18 @@ class DataFactory(object):
'session_history_metadata.labels, ' \
'session_history_media_info.container, session_history_media_info.bitrate, ' \
'session_history_media_info.video_codec, session_history_media_info.video_resolution, ' \
'session_history_media_info.video_full_resolution, ' \
'session_history_media_info.video_framerate, session_history_media_info.audio_codec, ' \
'session_history_media_info.audio_channels ' \
'session_history_media_info.audio_channels, session_history_metadata.live, ' \
'session_history_metadata.channel_call_sign, session_history_metadata.channel_identifier, ' \
'session_history_metadata.channel_thumb ' \
'FROM session_history_metadata ' \
'JOIN library_sections ON session_history_metadata.section_id = library_sections.section_id ' \
'JOIN session_history_media_info ON session_history_metadata.id = session_history_media_info.id ' \
'WHERE session_history_metadata.rating_key = ? ' \
'WHERE %s ' \
'ORDER BY session_history_metadata.id DESC ' \
'LIMIT 1'
result = monitor_db.select(query=query, args=[rating_key])
'LIMIT 1' % where
result = monitor_db.select(query=query, args=args)
else:
result = []
@@ -1036,9 +1078,13 @@ class DataFactory(object):
'bitrate': item['bitrate'],
'video_codec': item['video_codec'],
'video_resolution': item['video_resolution'],
'video_full_resolution': item['video_full_resolution'],
'video_framerate': item['video_framerate'],
'audio_codec': item['audio_codec'],
'audio_channels': item['audio_channels']
'audio_channels': item['audio_channels'],
'channel_call_sign': item['channel_call_sign'],
'channel_identifier': item['channel_identifier'],
'channel_thumb': item['channel_thumb']
}]
metadata = {'media_type': item['media_type'],
@@ -1052,6 +1098,7 @@ class DataFactory(object):
'media_index': item['media_index'],
'studio': item['studio'],
'title': item['title'],
'full_title': item['full_title'],
'content_rating': item['content_rating'],
'summary': item['summary'],
'tagline': item['tagline'],
@@ -1074,6 +1121,7 @@ class DataFactory(object):
'labels': labels,
'library_name': item['section_name'],
'section_id': item['section_id'],
'live': item['live'],
'media_info': media_info
}
metadata_list.append(metadata)

View File

@@ -142,6 +142,9 @@ class DataTables(object):
for w_ in w[1]:
if w_ == None:
c_where += w[0] + ' IS NULL OR '
elif str(w_).startswith('LIKE '):
c_where += w[0] + ' LIKE ? OR '
args.append(w_[5:])
else:
c_where += w[0] + ' = ? OR '
args.append(w_)
@@ -149,6 +152,9 @@ class DataTables(object):
else:
if w[1] == None:
c_where += w[0] + ' IS NULL AND '
elif str(w[1]).startswith('LIKE '):
c_where += w[0] + ' LIKE ? AND '
args.append(w[1][5:])
else:
c_where += w[0] + ' = ? AND '
args.append(w[1])

View File

@@ -18,6 +18,7 @@ import datetime
import plexpy
import common
import database
import libraries
import logger
import session
@@ -47,10 +48,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT date(started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'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 ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY date(started, "unixepoch", "localtime"), %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
@@ -60,13 +63,17 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT date(started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count ' \
'FROM session_history ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY date_played ' \
'ORDER BY started ASC' % (time_range, user_cond)
@@ -85,6 +92,7 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for date_item in sorted(date_list):
date_string = date_item.strftime('%Y-%m-%d')
@@ -92,20 +100,24 @@ class Graphs(object):
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
for item in result:
if date_string == item['date_played']:
series_1_value = item['tv_count']
series_2_value = item['movie_count']
series_3_value = item['music_count']
series_4_value = item['live_count']
break
else:
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
series_1.append(series_1_value)
series_2.append(series_2_value)
series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV',
'data': series_1}
@@ -113,9 +125,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_per_dayofweek(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@@ -146,10 +170,12 @@ class Graphs(object):
'WHEN 4 THEN "Thursday" ' \
'WHEN 5 THEN "Friday" ' \
'ELSE "Saturday" END) AS dayofweek, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'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 ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY strftime("%%w", datetime(started, "unixepoch", "localtime")), %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
@@ -167,13 +193,17 @@ class Graphs(object):
'WHEN 4 THEN "Thursday" ' \
'WHEN 5 THEN "Friday" ' \
'ELSE "Saturday" END) AS dayofweek, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count ' \
'FROM session_history ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY dayofweek ' \
'ORDER BY daynumber' % (time_range, user_cond)
@@ -194,26 +224,31 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for day_item in days_list:
categories.append(day_item)
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
for item in result:
if day_item == item['dayofweek']:
series_1_value = item['tv_count']
series_2_value = item['movie_count']
series_3_value = item['music_count']
series_4_value = item['live_count']
break
else:
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
series_1.append(series_1_value)
series_2.append(series_2_value)
series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV',
'data': series_1}
@@ -221,9 +256,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_per_hourofday(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@@ -246,10 +293,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT strftime("%%H", datetime(started, "unixepoch", "localtime")) AS hourofday, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'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 ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY strftime("%%H", datetime(started, "unixepoch", "localtime")) , %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
@@ -259,13 +308,17 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT strftime("%%H", datetime(started, "unixepoch", "localtime")) AS hourofday, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count ' \
'FROM session_history ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY hourofday ' \
'ORDER BY hourofday' % (time_range, user_cond)
@@ -275,35 +328,40 @@ class Graphs(object):
logger.warn(u"Tautulli Graphs :: Unable to execute database query for get_total_plays_per_hourofday: %s." % e)
return None
hours_list = ['00','01','02','03','04','05',
'06','07','08','09','10','11',
'12','13','14','15','16','17',
'18','19','20','21','22','23']
hours_list = ['00', '01', '02', '03', '04', '05',
'06', '07', '08', '09', '10', '11',
'12', '13', '14', '15', '16', '17',
'18', '19', '20', '21', '22', '23']
categories = []
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for hour_item in hours_list:
categories.append(hour_item)
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
for item in result:
if hour_item == item['hourofday']:
series_1_value = item['tv_count']
series_2_value = item['movie_count']
series_3_value = item['music_count']
series_4_value = item['live_count']
break
else:
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
series_1.append(series_1_value)
series_2.append(series_2_value)
series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV',
'data': series_1}
@@ -311,14 +369,24 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_per_month(self, time_range='12', y_axis='plays', user_id=None, grouping=None):
import time as time
if not time_range.isdigit():
time_range = '12'
@@ -338,10 +406,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) AS datestring, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'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 ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")), %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \
@@ -351,13 +421,17 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) AS datestring, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count ' \
'FROM session_history ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'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 %s' % (time_range, user_cond, time_range)
@@ -383,6 +457,7 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for dt in sorted(month_range):
date_string = dt.strftime('%Y-%m')
@@ -390,20 +465,24 @@ class Graphs(object):
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
for item in result:
if date_string == item['datestring']:
series_1_value = item['tv_count']
series_2_value = item['movie_count']
series_3_value = item['music_count']
series_4_value = item['live_count']
break
else:
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
series_1.append(series_1_value)
series_2.append(series_2_value)
series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV',
'data': series_1}
@@ -411,9 +490,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_by_top_10_platforms(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@@ -436,11 +527,15 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT platform, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'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, ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count, ' \
'COUNT(id) AS total_count ' \
'FROM (SELECT * FROM session_history GROUP BY %s) AS session_history ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY %s) ' \
'AS session_history ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY platform ' \
'ORDER BY total_count DESC ' \
@@ -449,15 +544,19 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT platform, ' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS total_duration ' \
'FROM session_history ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY platform ' \
'ORDER BY total_duration DESC ' \
@@ -472,12 +571,14 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for item in result:
categories.append(common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']))
series_1.append(item['tv_count'])
series_2.append(item['movie_count'])
series_3.append(item['music_count'])
series_4.append(item['live_count'])
series_1_output = {'name': 'TV',
'data': series_1}
@@ -485,9 +586,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_by_top_10_users(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@@ -513,11 +626,15 @@ class Graphs(object):
'users.user_id, users.username, ' \
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \
' THEN users.username ELSE users.friendly_name END) AS friendly_name,' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'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, ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM (SELECT * FROM session_history GROUP BY %s) AS session_history ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY %s) ' \
'AS session_history ' \
'JOIN users ON session_history.user_id = users.user_id ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY session_history.user_id ' \
@@ -530,15 +647,19 @@ class Graphs(object):
'users.user_id, users.username, ' \
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \
' THEN users.username ELSE users.friendly_name END) AS friendly_name,' \
'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "episode" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "movie" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" AND stopped > 0 THEN (stopped - started) ' \
'SUM(CASE WHEN media_type = "track" AND live = 0 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'SUM(CASE WHEN live = 1 AND stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS live_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS total_duration ' \
'FROM session_history ' \
'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id) ' \
'AS session_history ' \
'JOIN users ON session_history.user_id = users.user_id ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY session_history.user_id ' \
@@ -554,6 +675,7 @@ class Graphs(object):
series_1 = []
series_2 = []
series_3 = []
series_4 = []
session_user_id = session.get_session_user_id()
@@ -565,6 +687,7 @@ class Graphs(object):
series_1.append(item['tv_count'])
series_2.append(item['movie_count'])
series_3.append(item['music_count'])
series_4.append(item['live_count'])
series_1_output = {'name': 'TV',
'data': series_1}
@@ -572,9 +695,21 @@ class Graphs(object):
'data': series_2}
series_3_output = {'name': 'Music',
'data': series_3}
series_4_output = {'name': 'Live TV',
'data': series_4}
series_output = []
if libraries.has_library_type('show'):
series_output.append(series_1_output)
if libraries.has_library_type('movie'):
series_output.append(series_2_output)
if libraries.has_library_type('artist'):
series_output.append(series_3_output)
if libraries.has_library_type('live'):
series_output.append(series_4_output)
output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]}
'series': series_output}
return output
def get_total_plays_per_stream_type(self, time_range='30', y_axis='plays', user_id=None, grouping=None):

View File

@@ -13,7 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
import arrow
import base64
import certifi
import cloudinary
from cloudinary.api import delete_resources_by_tag
from cloudinary.uploader import upload
@@ -21,7 +23,6 @@ from cloudinary.utils import cloudinary_url
import datetime
from functools import wraps
import geoip2.database, geoip2.errors
import gzip
import hashlib
import imghdr
from itertools import izip_longest
@@ -34,12 +35,14 @@ from operator import itemgetter
import os
import re
import shlex
import shutil
import socket
import sys
import tarfile
import time
import unicodedata
import urllib, urllib2
import urllib
import urllib3
from xml.dom import minidom
import xmltodict
@@ -220,6 +223,22 @@ def utc_now_iso():
return utcnow.isoformat()
def timestamp_to_YMD(timestamp):
return timestamp_to_datetime(timestamp).strftime("%Y-%m-%d")
def timestamp_to_datetime(timestamp):
return datetime.datetime.fromtimestamp(cast_to_int(str(timestamp)))
def iso_to_YMD(iso):
return iso_to_datetime(iso).strftime("%Y-%m-%d")
def iso_to_datetime(iso):
return arrow.get(iso).datetime
def human_duration(s, sig='dhms'):
hd = ''
@@ -590,7 +609,8 @@ def update_geoip_db():
now = int(time.time())
if now - plexpy.CONFIG.GEOIP_DB_INSTALLED >= plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS * 24 * 60 * 60:
return install_geoip_db(update=True)
logger.info(u"Tautulli Helpers :: GeoLite2 database already updated within the last 30 days.")
logger.info(u"Tautulli Helpers :: GeoLite2 database already updated within the last %s days."
% plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS)
def install_geoip_db(update=False):
@@ -608,15 +628,21 @@ def install_geoip_db(update=False):
geolite2_db = maxmind_db + '.mmdb'
geolite2_db_path = plexpy.CONFIG.GEOIP_DB or os.path.join(plexpy.DATA_DIR, geolite2_db)
# Check path ends with .mmdb
if os.path.splitext(geolite2_db_path)[1] != os.path.splitext(geolite2_db)[1]:
geolite2_db_path = os.path.join(geolite2_db_path, geolite2_db)
temp_gz = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_gz)
temp_md5 = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_md5)
# Retrieve the GeoLite2 gzip file
logger.debug(u"Tautulli Helpers :: Downloading GeoLite2 gzip file from MaxMind...")
try:
maxmind = urllib.URLopener()
maxmind.retrieve(geolite2_db_url, temp_gz)
maxmind.retrieve(geolite2_md5_url, temp_md5)
maxmind = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())
with maxmind.request('GET', geolite2_db_url, preload_content=False) as r_db, open(temp_gz, 'wb') as f_db:
shutil.copyfileobj(r_db, f_db)
with maxmind.request('GET', geolite2_md5_url, preload_content=False) as r_md5, open(temp_md5, 'wb') as f_md5:
shutil.copyfileobj(r_md5, f_md5)
except Exception as e:
logger.error(u"Tautulli Helpers :: Failed to download GeoLite2 gzip file from MaxMind: %s" % e)
return False
@@ -1232,3 +1258,92 @@ def mask_config_passwords(config):
config[cfg] = ' '
return config
def bool_true(value):
if value is True or value == 1:
return True
elif isinstance(value, basestring) and value.lower() in ('1', 'true', 't', 'yes', 'y', 'on'):
return True
return False
def page(endpoint, *args, **kwargs):
endpoints = {
'pms_image_proxy': pms_image_proxy,
'info': info_page,
'library': library_page,
'user': user_page
}
params = {}
if endpoint in endpoints:
params = endpoints[endpoint](*args, **kwargs)
return endpoint + '?' + urllib.urlencode(params)
def pms_image_proxy(img=None, rating_key=None, width=None, height=None,
opacity=None, background=None, blur=None, img_format=None,
fallback=None, refresh=None, clip=None):
params = {}
if img is not None:
params['img'] = img
if rating_key is not None:
params['rating_key'] = rating_key
if width is not None:
params['width'] = width
if height is not None:
params['height'] = height
if opacity is not None:
params['opacity'] = opacity
if background is not None:
params['background'] = background
if blur is not None:
params['blur'] = blur
if img_format is not None:
params['img_format'] = img_format
if fallback is not None:
params['fallback'] = fallback
if refresh is not None:
params['refresh'] = 'true'
if clip is not None:
params['clip'] = 'true'
return params
def info_page(rating_key=None, guid=None, history=None, live=None):
params = {}
if live and history:
params['guid'] = guid
else:
params['rating_key'] = rating_key
if history:
params['source'] = 'history'
return params
def library_page(section_id=None):
params = {}
if section_id is not None:
params['section_id'] = section_id
return params
def user_page(user_id=None, user=None):
params = {}
if user_id is not None:
params['user_id'] = user_id
if user is not None:
params['user'] = user
return params

View File

@@ -88,6 +88,37 @@ def refresh_libraries():
return False
def add_live_tv_library():
if not plexpy.CONFIG.ADD_LIVE_TV_LIBRARY:
return
logger.info(u"Tautulli Libraries :: Adding Live TV library to the database.")
monitor_db = database.MonitorDatabase()
section_keys = {'server_id': plexpy.CONFIG.PMS_IDENTIFIER,
'section_id': common.LIVE_TV_SECTION_ID}
section_values = {'server_id': plexpy.CONFIG.PMS_IDENTIFIER,
'section_id': common.LIVE_TV_SECTION_ID,
'section_name': common.LIVE_TV_SECTION_NAME,
'section_type': 'live'
}
result = monitor_db.upsert('library_sections', key_dict=section_keys, value_dict=section_values)
if result == 'insert':
plexpy.CONFIG.__setattr__('ADD_LIVE_TV_LIBRARY', 0)
plexpy.CONFIG.write()
def has_library_type(section_type):
monitor_db = database.MonitorDatabase()
query = 'SELECT * FROM library_sections WHERE section_type = ? AND deleted_section = 0'
args = [section_type]
result = monitor_db.select_single(query=query, args=args)
return bool(result)
def update_section_ids():
plexpy.CONFIG.UPDATE_SECTION_IDS = -1
@@ -285,6 +316,10 @@ class Libraries(object):
'session_history_metadata.parent_media_index',
'session_history_metadata.content_rating',
'session_history_metadata.labels',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'library_sections.do_notify',
'library_sections.do_notify_created',
'library_sections.keep_history'
@@ -348,6 +383,9 @@ class Libraries(object):
'parent_media_index': item['parent_media_index'],
'content_rating': item['content_rating'],
'labels': item['labels'].split(';') if item['labels'] else (),
'live': item['live'],
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'do_notify': helpers.checked(item['do_notify']),
'do_notify_created': helpers.checked(item['do_notify_created']),
'keep_history': helpers.checked(item['keep_history'])
@@ -699,7 +737,7 @@ class Libraries(object):
'deleted_section': 0
}
if not section_id or helpers.cast_to_int(section_id) <= 0:
if not section_id:
return default_return
def get_library_details(section_id=section_id):
@@ -879,11 +917,11 @@ class Libraries(object):
try:
if str(section_id).isdigit():
query = 'SELECT session_history.id, session_history.media_type, ' \
query = 'SELECT session_history.id, session_history.media_type, guid, ' \
'session_history.rating_key, session_history.parent_rating_key, session_history.grandparent_rating_key, ' \
'title, parent_title, grandparent_title, original_title, ' \
'thumb, parent_thumb, grandparent_thumb, media_index, parent_media_index, ' \
'year, started, user, content_rating, labels, section_id ' \
'year, originally_available_at, added_at, live, started, user, content_rating, labels, section_id ' \
'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'WHERE section_id = ? ' \
@@ -917,6 +955,9 @@ class Libraries(object):
'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'],
'year': row['year'],
'originally_available_at': row['originally_available_at'],
'live': row['live'],
'guid': row['guid'],
'time': row['started'],
'user': row['user'],
'section_id': row['section_id'],

View File

@@ -80,7 +80,7 @@ class BlacklistFilter(logging.Filter):
Log filter for blacklisted tokens and passwords
"""
def __init__(self):
pass
super(BlacklistFilter, self).__init__()
def filter(self, record):
if not plexpy.CONFIG.LOG_BLACKLIST:
@@ -98,30 +98,29 @@ class BlacklistFilter(logging.Filter):
return True
class PublicIPFilter(logging.Filter):
class RegexFilter(logging.Filter):
"""
Log filter for public IP addresses
Base class for regex log filter
"""
def __init__(self):
pass
super(RegexFilter, self).__init__()
self.regex = re.compile(r'')
def filter(self, record):
if not plexpy.CONFIG.LOG_BLACKLIST:
return True
try:
# Currently only checking for ipv4 addresses
ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})', record.msg)
for ip in ipv4:
if helpers.is_public_ip(ip):
record.msg = record.msg.replace(ip, ip.partition('.')[0] + '.***.***.***')
matches = self.regex.findall(record.msg)
for match in matches:
record.msg = self.replace(record.msg, match)
args = []
for arg in record.args:
ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})', arg) if isinstance(arg, basestring) else []
for ip in ipv4:
if helpers.is_public_ip(ip):
arg = arg.replace(ip, ip.partition('.')[0] + '.***.***.***')
matches = self.regex.findall(arg) if isinstance(arg, basestring) else []
for match in matches:
arg = self.replace(arg, match)
args.append(arg)
record.args = tuple(args)
except:
@@ -129,31 +128,53 @@ class PublicIPFilter(logging.Filter):
return True
def replace(self, text, match):
return text
class PlexTokenFilter(logging.Filter):
class PublicIPFilter(RegexFilter):
"""
Log filter for public IP addresses
"""
def __init__(self):
super(PublicIPFilter, self).__init__()
# Currently only checking for ipv4 addresses
self.regex = re.compile(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})')
def replace(self, text, ip):
if helpers.is_public_ip(ip):
return text.replace(ip, ip.partition('.')[0] + '.***.***.***')
return text
class EmailFilter(RegexFilter):
"""
Log filter for email addresses
"""
def __init__(self):
super(EmailFilter, self).__init__()
self.regex = re.compile(r'([a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@'
r'(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)',
re.IGNORECASE)
def replace(self, text, email):
email_parts = email.partition('@')
return text.replace(email, email_parts[0][:2] + 8 * '*' + email_parts[1] + 8 * '*')
class PlexTokenFilter(RegexFilter):
"""
Log filter for X-Plex-Token
"""
def __init__(self):
pass
super(PlexTokenFilter, self).__init__()
def filter(self, record):
try:
tokens = re.findall(r'X-Plex-Token(?:=|%3D)([a-zA-Z0-9]+)', record.msg)
for token in tokens:
record.msg = record.msg.replace(token, 8 * '*' + token[-2:])
self.regex = re.compile(r'X-Plex-Token(?:=|%3D)([a-zA-Z0-9]+)')
args = []
for arg in record.args:
tokens = re.findall(r'X-Plex-Token(?:=|%3D)([a-zA-Z0-9]+)', arg) if isinstance(arg, basestring) else []
for token in tokens:
arg = arg.replace(token, 8 * '*' + token[-2:])
args.append(arg)
record.args = tuple(args)
except:
pass
return True
def replace(self, text, token):
return text.replace(token, 8 * '*' + token[-2:])
@contextlib.contextmanager
@@ -294,6 +315,7 @@ def initLogger(console=False, log_dir=False, verbose=False):
for handler in logger.handlers + logger_api.handlers + logger_plex_websocket.handlers:
handler.addFilter(BlacklistFilter())
handler.addFilter(PublicIPFilter())
handler.addFilter(EmailFilter())
handler.addFilter(PlexTokenFilter())
# Install exception hooks

View File

@@ -536,8 +536,15 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
ap = activity_processor.ActivityProcessor()
sessions = ap.get_sessions()
stream_count = len(sessions)
user_sessions = ap.get_sessions(user_id=session.get('user_id'))
# Filter out the session_key from the database sessions for playback stopped events
# to prevent race condition between the database and notifications
if notify_action == 'on_stop':
sessions = [s for s in sessions if str(s['session_key']) != notify_params['session_key']]
user_sessions = [s for s in user_sessions if str(s['session_key']) != notify_params['session_key']]
stream_count = len(sessions)
user_stream_count = len(user_sessions)
# Generate a combined transcode decision value
@@ -689,12 +696,13 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
poster_title = ''
img_service = helpers.get_img_service(include_self=True)
fallback = 'poster-live' if notify_params['live'] else 'poster'
if img_service not in (None, 'self-hosted'):
img_info = get_img_info(img=poster_thumb, rating_key=poster_key, title=poster_title, fallback='poster')
img_info = get_img_info(img=poster_thumb, rating_key=poster_key, title=poster_title, fallback=fallback)
poster_info = {'poster_title': img_info['img_title'], 'poster_url': img_info['img_url']}
notify_params.update(poster_info)
elif img_service == 'self-hosted' and plexpy.CONFIG.HTTP_BASE_URL:
img_hash = set_hash_image_info(img=poster_thumb, fallback='poster')
img_hash = set_hash_image_info(img=poster_thumb, fallback=fallback)
poster_info = {'poster_title': poster_title,
'poster_url': plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'image/' + img_hash}
notify_params.update(poster_info)
@@ -818,6 +826,9 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'optimized_version_profile': notify_params['optimized_version_profile'],
'synced_version': notify_params['synced_version'],
'live': notify_params['live'],
'channel_call_sign': notify_params['channel_call_sign'],
'channel_identifier': notify_params['channel_identifier'],
'channel_thumb': notify_params['channel_thumb'],
'secure': 'unknown' if notify_params['secure'] is None else notify_params['secure'],
'relayed': notify_params['relayed'],
'stream_local': notify_params['local'],
@@ -830,6 +841,12 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'stream_video_codec_level': notify_params['stream_video_codec_level'],
'stream_video_bitrate': notify_params['stream_video_bitrate'],
'stream_video_bit_depth': notify_params['stream_video_bit_depth'],
'stream_video_chroma_subsampling': notify_params['stream_video_chroma_subsampling'],
'stream_video_color_primaries': notify_params['stream_video_color_primaries'],
'stream_video_color_range': notify_params['stream_video_color_range'],
'stream_video_color_space': notify_params['stream_video_color_space'],
'stream_video_color_trc': notify_params['stream_video_color_trc'],
'stream_video_dynamic_range': notify_params['stream_video_dynamic_range'],
'stream_video_framerate': notify_params['stream_video_framerate'],
'stream_video_full_resolution': notify_params['stream_video_full_resolution'],
'stream_video_ref_frames': notify_params['stream_video_ref_frames'],
@@ -940,6 +957,12 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'video_codec_level': notify_params['video_codec_level'],
'video_bitrate': notify_params['video_bitrate'],
'video_bit_depth': notify_params['video_bit_depth'],
'video_chroma_subsampling': notify_params['video_chroma_subsampling'],
'video_color_primaries': notify_params['video_color_primaries'],
'video_color_range': notify_params['video_color_range'],
'video_color_space': notify_params['video_color_space'],
'video_color_trc': notify_params['video_color_trc'],
'video_dynamic_range': notify_params['video_dynamic_range'],
'video_framerate': notify_params['video_framerate'],
'video_full_resolution': notify_params['video_full_resolution'],
'video_ref_frames': notify_params['video_ref_frames'],
@@ -1227,14 +1250,17 @@ def get_img_info(img=None, rating_key=None, title='', width=1000, height=1500,
return img_info
if rating_key and not img:
if fallback == 'art':
if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key)
else:
img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/')
img = '/'.join(img_split[:5])
rating_key = rating_key or img_split[3]
if img.startswith('/library/metadata'):
img_split = img.split('/')
img = '/'.join(img_split[:5])
img_rating_key = img_split[3]
if rating_key != img_rating_key:
rating_key = img_rating_key
service = helpers.get_img_service()
@@ -1244,7 +1270,7 @@ def get_img_info(img=None, rating_key=None, title='', width=1000, height=1500,
elif service == 'cloudinary':
if fallback == 'cover':
w, h = 1000, 1000
elif fallback == 'art':
elif fallback and fallback.startswith('art'):
w, h = 1920, 1080
else:
w, h = 1000, 1500
@@ -1328,14 +1354,17 @@ def set_hash_image_info(img=None, rating_key=None, width=750, height=1000,
return fallback
if rating_key and not img:
if fallback == 'art':
if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key)
else:
img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/')
img = '/'.join(img_split[:5])
rating_key = rating_key or img_split[3]
if img.startswith('/library/metadata'):
img_split = img.split('/')
img = '/'.join(img_split[:5])
img_rating_key = img_split[3]
if rating_key != img_rating_key:
rating_key = img_rating_key
img_string = '{}.{}.{}.{}.{}.{}.{}.{}'.format(
plexpy.CONFIG.PMS_UUID, img, rating_key, width, height, opacity, background, blur, fallback)

View File

@@ -3416,9 +3416,9 @@ class TELEGRAM(Notifier):
data = {'chat_id': self.config['chat_id']}
if self.config['incl_subject']:
text = subject.encode('utf-8') + '\r\n' + body.encode('utf-8')
text = subject + '\r\n' + body
else:
text = body.encode('utf-8')
text = body
if self.config['html_support']:
data['parse_mode'] = 'HTML'
@@ -3442,7 +3442,7 @@ class TELEGRAM(Notifier):
if len(text) > 1024:
data['disable_notification'] = True
else:
data['caption'] = text
data['caption'] = text.encode('utf-8')
r = self.make_request('https://api.telegram.org/bot{}/sendPhoto'.format(self.config['bot_token']),
data=data, files=files)
@@ -3450,7 +3450,7 @@ class TELEGRAM(Notifier):
if not data.pop('disable_notification', None):
return r
data['text'] = text
data['text'] = (text[:4093] + (text[4093:] and '...')).encode('utf-8')
if self.config['disable_web_preview']:
data['disable_web_page_preview'] = True
@@ -3636,7 +3636,12 @@ class WEBHOOK(Notifier):
if webhook_headers:
headers.update(webhook_headers)
return self.make_request(self.config['hook'], method=self.config['method'], headers=headers, json=webhook_body)
if headers['Content-Type'] == 'application/json':
data = {'json': webhook_body}
else:
data = {'data': webhook_body}
return self.make_request(self.config['hook'], method=self.config['method'], headers=headers, **data)
def _return_config_options(self):
config_option = [{'label': 'Webhook URL',

View File

@@ -571,7 +571,8 @@ class PmsConnect(object):
return output
def get_metadata_details(self, rating_key='', sync_id='', cache_key=None, media_info=True):
def get_metadata_details(self, rating_key='', sync_id='', plex_guid='',
cache_key=None, return_cache=False, media_info=True):
"""
Return processed and validated metadata list for requested item.
@@ -596,14 +597,18 @@ class PmsConnect(object):
if metadata:
_cache_time = metadata.pop('_cache_time', 0)
# Return cached metadata if less than METADATA_CACHE_SECONDS ago
if int(time.time()) - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS:
# Return cached metadata if less than cache_seconds ago
if return_cache or int(time.time()) - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS:
return metadata
if rating_key:
metadata_xml = self.get_metadata(str(rating_key), output_format='xml')
elif sync_id:
metadata_xml = self.get_sync_item(str(sync_id), output_format='xml')
elif plex_guid.startswith(('plex://movie', 'plex://episode')):
rating_key = plex_guid.rsplit('/', 1)[-1]
plextv_metadata = PmsConnect(url='https://metadata.provider.plex.tv', token=plexpy.CONFIG.PMS_TOKEN)
metadata_xml = plextv_metadata.get_metadata(rating_key, output_format='xml')
else:
return metadata
@@ -719,7 +724,8 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'show':
@@ -771,12 +777,19 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'season':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
show_details = self.get_metadata_details(parent_rating_key)
parent_guid = helpers.get_xml_attr(metadata_main, 'parentGuid')
show_details = {}
if plex_guid and parent_guid:
show_details = self.get_metadata_details(plex_guid=parent_guid)
elif not plex_guid and parent_rating_key:
show_details = self.get_metadata_details(parent_rating_key)
metadata = {'media_type': metadata_type,
'section_id': section_id,
'library_name': library_name,
@@ -790,22 +803,22 @@ class PmsConnect(object):
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': show_details['studio'],
'content_rating': show_details['content_rating'],
'summary': show_details['summary'],
'studio': show_details.get('studio', ''),
'content_rating': show_details.get('content_rating', ''),
'summary': show_details.get('summary', ''),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
'duration': show_details['duration'],
'duration': show_details.get('duration', ''),
'year': helpers.get_xml_attr(metadata_main, 'year'),
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': show_details['banner'],
'banner': show_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@@ -813,32 +826,38 @@ class PmsConnect(object):
'guid': helpers.get_xml_attr(metadata_main, 'guid'),
'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
'directors': show_details['directors'],
'writers': show_details['writers'],
'actors': show_details['actors'],
'genres': show_details['genres'],
'labels': show_details['labels'],
'collections': show_details['collections'],
'directors': show_details.get('directors', []),
'writers': show_details.get('writers', []),
'actors': show_details.get('actors', []),
'genres': show_details.get('genres', []),
'labels': show_details.get('labels', []),
'collections': show_details.get('collections', []),
'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'episode':
grandparent_rating_key = helpers.get_xml_attr(metadata_main, 'grandparentRatingKey')
show_details = self.get_metadata_details(grandparent_rating_key)
grandparent_guid = helpers.get_xml_attr(metadata_main, 'grandparentGuid')
show_details = {}
if plex_guid and grandparent_guid:
show_details = self.get_metadata_details(plex_guid=grandparent_guid)
elif not plex_guid and grandparent_rating_key:
show_details = self.get_metadata_details(grandparent_rating_key)
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
parent_media_index = helpers.get_xml_attr(metadata_main, 'parentIndex')
parent_thumb = helpers.get_xml_attr(metadata_main, 'parentThumb')
if not parent_rating_key:
if not plex_guid and not parent_rating_key:
# Try getting the parent_rating_key from the parent_thumb
if parent_thumb.startswith('/library/metadata/'):
parent_rating_key = parent_thumb.split('/')[3]
# Try getting the parent_rating_key from the grandparent's children
if not parent_rating_key:
if not parent_rating_key and grandparent_rating_key:
children_list = self.get_item_children(grandparent_rating_key)
parent_rating_key = next((c['rating_key'] for c in children_list['children_list']
if c['media_index'] == parent_media_index), '')
@@ -856,7 +875,7 @@ class PmsConnect(object):
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': parent_media_index,
'studio': show_details['studio'],
'studio': show_details.get('studio', ''),
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
'summary': helpers.get_xml_attr(metadata_main, 'summary'),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
@@ -871,7 +890,7 @@ class PmsConnect(object):
'parent_thumb': parent_thumb,
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': show_details['banner'],
'banner': show_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@@ -881,13 +900,14 @@ class PmsConnect(object):
'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
'directors': directors,
'writers': writers,
'actors': show_details['actors'],
'genres': show_details['genres'],
'labels': show_details['labels'],
'collections': show_details['collections'],
'actors': show_details.get('actors', []),
'genres': show_details.get('genres', []),
'labels': show_details.get('labels', []),
'collections': show_details.get('collections', []),
'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'artist':
@@ -934,12 +954,13 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'album':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
artist_details = self.get_metadata_details(parent_rating_key)
artist_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
metadata = {'media_type': metadata_type,
'section_id': section_id,
'library_name': library_name,
@@ -955,7 +976,7 @@ class PmsConnect(object):
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'),
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
'summary': helpers.get_xml_attr(metadata_main, 'summary') or artist_details['summary'],
'summary': helpers.get_xml_attr(metadata_main, 'summary') or artist_details.get('summary', ''),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
@@ -968,7 +989,7 @@ class PmsConnect(object):
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': artist_details['banner'],
'banner': artist_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@@ -984,12 +1005,13 @@ class PmsConnect(object):
'collections': collections,
'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'track':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
album_details = self.get_metadata_details(parent_rating_key)
album_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
track_artist = helpers.get_xml_attr(metadata_main, 'originalTitle') or \
helpers.get_xml_attr(metadata_main, 'grandparentTitle')
metadata = {'media_type': metadata_type,
@@ -1015,12 +1037,12 @@ class PmsConnect(object):
'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
'duration': helpers.get_xml_attr(metadata_main, 'duration'),
'year': album_details['year'],
'year': album_details.get('year', ''),
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': album_details['banner'],
'banner': album_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@@ -1031,12 +1053,13 @@ class PmsConnect(object):
'directors': directors,
'writers': writers,
'actors': actors,
'genres': album_details['genres'],
'labels': album_details['labels'],
'collections': album_details['collections'],
'genres': album_details.get('genres', []),
'labels': album_details.get('labels', []),
'collections': album_details.get('collections', []),
'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'title'),
track_artist),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'photo_album':
@@ -1083,12 +1106,13 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'photo':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
photo_album_details = self.get_metadata_details(parent_rating_key)
photo_album_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
metadata = {'media_type': metadata_type,
'section_id': section_id,
'library_name': library_name,
@@ -1128,12 +1152,13 @@ class PmsConnect(object):
'directors': directors,
'writers': writers,
'actors': actors,
'genres': photo_album_details.get('genres', ''),
'labels': photo_album_details.get('labels', ''),
'collections': photo_album_details.get('collections', ''),
'genres': photo_album_details.get('genres', []),
'labels': photo_album_details.get('labels', []),
'collections': photo_album_details.get('collections', []),
'full_title': u'{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle') or library_name,
helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'collection':
@@ -1184,7 +1209,8 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount')
'children_count': helpers.get_xml_attr(metadata_main, 'leafCount'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
elif metadata_type == 'clip':
@@ -1232,17 +1258,31 @@ class PmsConnect(object):
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'extra_type': helpers.get_xml_attr(metadata_main, 'extraType'),
'sub_type': helpers.get_xml_attr(metadata_main, 'subtype')
'sub_type': helpers.get_xml_attr(metadata_main, 'subtype'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
else:
return metadata
# Get additional metadata from metadata.provider.plex.tv
if not plex_guid and metadata['live']:
metadata['section_id'] = common.LIVE_TV_SECTION_ID
metadata['library_name'] = common.LIVE_TV_SECTION_NAME
plextv_metadata = self.get_metadata_details(plex_guid=metadata['guid'])
if plextv_metadata:
keys_to_update = ['summary', 'rating', 'thumb', 'grandparent_thumb', 'duration',
'guid', 'grandparent_guid', 'genres']
for key in keys_to_update:
metadata[key] = plextv_metadata[key]
metadata['originally_available_at'] = helpers.iso_to_YMD(plextv_metadata['originally_available_at'])
if metadata and media_info:
medias = []
media_items = metadata_main.getElementsByTagName('Media')
for media in media_items:
video_full_resolution_scan_type = None
video_full_resolution_scan_type = ''
parts = []
part_items = media.getElementsByTagName('Part')
@@ -1253,8 +1293,7 @@ class PmsConnect(object):
for stream in stream_items:
if helpers.get_xml_attr(stream, 'streamType') == '1':
video_scan_type = helpers.get_xml_attr(stream, 'scanType')
if video_full_resolution_scan_type is None:
video_full_resolution_scan_type = video_scan_type
video_full_resolution_scan_type = (video_full_resolution_scan_type or video_scan_type)
streams.append({'id': helpers.get_xml_attr(stream, 'id'),
'type': helpers.get_xml_attr(stream, 'streamType'),
@@ -1262,6 +1301,11 @@ class PmsConnect(object):
'video_codec_level': helpers.get_xml_attr(stream, 'level'),
'video_bitrate': helpers.get_xml_attr(stream, 'bitrate'),
'video_bit_depth': helpers.get_xml_attr(stream, 'bitDepth'),
'video_chroma_subsampling': helpers.get_xml_attr(stream, 'chromaSubsampling'),
'video_color_primaries': helpers.get_xml_attr(stream, 'colorPrimaries'),
'video_color_range': helpers.get_xml_attr(stream, 'colorRange'),
'video_color_space': helpers.get_xml_attr(stream, 'colorSpace'),
'video_color_trc': helpers.get_xml_attr(stream, 'colorTrc'),
'video_frame_rate': helpers.get_xml_attr(stream, 'frameRate'),
'video_ref_frames': helpers.get_xml_attr(stream, 'refFrames'),
'video_height': helpers.get_xml_attr(stream, 'height'),
@@ -1309,35 +1353,36 @@ class PmsConnect(object):
'selected': int(helpers.get_xml_attr(part, 'selected') == '1')
})
video_resolution = helpers.get_xml_attr(media, 'videoResolution').lower()
video_full_resolution = ''
if video_full_resolution_scan_type is not None:
video_full_resolution = common.VIDEO_RESOLUTION_OVERRIDES.get(
video_resolution, video_resolution + (video_full_resolution_scan_type[:1] or 'p')
)
video_resolution = helpers.get_xml_attr(media, 'videoResolution').lower().rstrip('ip')
video_full_resolution = common.VIDEO_RESOLUTION_OVERRIDES.get(
video_resolution, video_resolution + (video_full_resolution_scan_type[:1] or 'p')
)
audio_channels = helpers.get_xml_attr(media, 'audioChannels')
medias.append({'id': helpers.get_xml_attr(media, 'id'),
'container': helpers.get_xml_attr(media, 'container'),
'bitrate': helpers.get_xml_attr(media, 'bitrate'),
'height': helpers.get_xml_attr(media, 'height'),
'width': helpers.get_xml_attr(media, 'width'),
'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(media, 'videoCodec'),
'video_resolution': video_resolution,
'video_full_resolution': video_full_resolution,
'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(media, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(media, 'audioProfile'),
'optimized_version': int(helpers.get_xml_attr(media, 'proxyType') == '42'),
'parts': parts
})
media_info = {'id': helpers.get_xml_attr(media, 'id'),
'container': helpers.get_xml_attr(media, 'container'),
'bitrate': helpers.get_xml_attr(media, 'bitrate'),
'height': helpers.get_xml_attr(media, 'height'),
'width': helpers.get_xml_attr(media, 'width'),
'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(media, 'videoCodec'),
'video_resolution': video_resolution,
'video_full_resolution': video_full_resolution,
'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(media, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(media, 'audioProfile'),
'optimized_version': int(helpers.get_xml_attr(media, 'proxyType') == '42'),
'channel_call_sign': helpers.get_xml_attr(media, 'channelCallSign'),
'channel_identifier': helpers.get_xml_attr(media, 'channelIdentifier'),
'channel_thumb': helpers.get_xml_attr(media, 'channelThumb'),
'parts': parts
}
video_full_resolution = helpers.get_xml_attr(media, 'videoResolution').lower()
medias.append(media_info)
metadata['media_info'] = medias
@@ -1459,7 +1504,7 @@ class PmsConnect(object):
return metadata_list
def get_current_activity(self):
def get_current_activity(self, skip_cache=False):
"""
Return processed and validated session list.
@@ -1486,17 +1531,17 @@ class PmsConnect(object):
if a.getElementsByTagName('Track'):
session_data = a.getElementsByTagName('Track')
for session_ in session_data:
session_output = self.get_session_each(session_)
session_output = self.get_session_each(session_, skip_cache=skip_cache)
session_list.append(session_output)
if a.getElementsByTagName('Video'):
session_data = a.getElementsByTagName('Video')
for session_ in session_data:
session_output = self.get_session_each(session_)
session_output = self.get_session_each(session_, skip_cache=skip_cache)
session_list.append(session_output)
if a.getElementsByTagName('Photo'):
session_data = a.getElementsByTagName('Photo')
for session_ in session_data:
session_output = self.get_session_each(session_)
session_output = self.get_session_each(session_, skip_cache=skip_cache)
session_list.append(session_output)
session_list = sorted(session_list, key=lambda k: k['session_key'])
@@ -1507,7 +1552,7 @@ class PmsConnect(object):
return output
def get_session_each(self, session=None):
def get_session_each(self, session=None, skip_cache=False):
"""
Return selected data from current sessions.
This function processes and validates session data
@@ -1698,6 +1743,11 @@ class PmsConnect(object):
video_id = helpers.get_xml_attr(video_stream_info, 'id')
video_details = {'stream_video_bitrate': helpers.get_xml_attr(video_stream_info, 'bitrate'),
'stream_video_bit_depth': helpers.get_xml_attr(video_stream_info, 'bitDepth'),
'stream_video_chroma_subsampling': helpers.get_xml_attr(video_stream_info, 'chromaSubsampling'),
'stream_video_color_primaries': helpers.get_xml_attr(video_stream_info, 'colorPrimaries'),
'stream_video_color_range': helpers.get_xml_attr(video_stream_info, 'colorRange'),
'stream_video_color_space': helpers.get_xml_attr(video_stream_info, 'colorSpace'),
'stream_video_color_trc': helpers.get_xml_attr(video_stream_info, 'colorTrc'),
'stream_video_codec_level': helpers.get_xml_attr(video_stream_info, 'level'),
'stream_video_ref_frames': helpers.get_xml_attr(video_stream_info, 'refFrames'),
'stream_video_language': helpers.get_xml_attr(video_stream_info, 'language'),
@@ -1708,6 +1758,11 @@ class PmsConnect(object):
else:
video_details = {'stream_video_bitrate': '',
'stream_video_bit_depth': '',
'stream_video_chroma_subsampling': '',
'stream_video_color_primaries': '',
'stream_video_color_range': '',
'stream_video_color_space': '',
'stream_video_color_trc': '',
'stream_video_codec_level': '',
'stream_video_ref_frames': '',
'stream_video_language': '',
@@ -1773,7 +1828,7 @@ class PmsConnect(object):
if helpers.cast_to_int(stream_video_width) >= 3840:
stream_video_resolution = '4k'
else:
stream_video_resolution = helpers.get_xml_attr(stream_media_info, 'videoResolution').rstrip('p').lower()
stream_video_resolution = helpers.get_xml_attr(stream_media_info, 'videoResolution').lower().rstrip('ip')
stream_audio_channels = helpers.get_xml_attr(stream_media_info, 'audioChannels')
@@ -1850,13 +1905,19 @@ class PmsConnect(object):
'full_title': helpers.get_xml_attr(session, 'title'),
'container': helpers.get_xml_attr(stream_media_info, 'container') \
or helpers.get_xml_attr(stream_media_parts_info, 'container'),
'bitrate': helpers.get_xml_attr(stream_media_info, 'bitrate'),
'height': helpers.get_xml_attr(stream_media_info, 'height'),
'width': helpers.get_xml_attr(stream_media_info, 'width'),
'aspect_ratio': helpers.get_xml_attr(stream_media_info, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(stream_media_info, 'videoCodec'),
'video_resolution': helpers.get_xml_attr(stream_media_info, 'videoResolution').lower(),
'video_full_resolution': helpers.get_xml_attr(stream_media_info, 'videoResolution').lower(),
'video_framerate': helpers.get_xml_attr(stream_media_info, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(stream_media_info, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(stream_media_info, 'audioCodec'),
'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(stream_media_info, 'audioProfile'),
'channel_icon': helpers.get_xml_attr(session, 'sourceIcon'),
'channel_title': helpers.get_xml_attr(session, 'sourceTitle'),
'extra_type': helpers.get_xml_attr(session, 'extraType'),
@@ -1868,10 +1929,12 @@ class PmsConnect(object):
media_id = helpers.get_xml_attr(stream_media_info, 'id')
part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
cache_key = None if skip_cache else session_key
if sync_id:
metadata_details = self.get_metadata_details(rating_key=rating_key, sync_id=sync_id, cache_key=session_key)
metadata_details = self.get_metadata_details(rating_key=rating_key, sync_id=sync_id, cache_key=cache_key)
else:
metadata_details = self.get_metadata_details(rating_key=rating_key, cache_key=session_key)
metadata_details = self.get_metadata_details(rating_key=rating_key, cache_key=cache_key)
# Get the media info, fallback to first item if match id is not found
source_medias = metadata_details.pop('media_info', [])
@@ -1886,6 +1949,11 @@ class PmsConnect(object):
'video_codec_level': '',
'video_bitrate': '',
'video_bit_depth': '',
'video_chroma_subsampling': '',
'video_color_primaries': '',
'video_color_range': '',
'video_color_space': '',
'video_color_trc': '',
'video_frame_rate': '',
'video_ref_frames': '',
'video_height': '',
@@ -1953,15 +2021,15 @@ class PmsConnect(object):
# Override * in audio codecs
if stream_details['stream_audio_codec'] == '*':
stream_details['stream_audio_codec'] = source_audio_details['audio_codec']
stream_details['stream_audio_codec'] = source_audio_details.get('audio_codec', '')
if transcode_details['transcode_audio_codec'] == '*':
transcode_details['transcode_audio_codec'] = source_audio_details['audio_codec']
transcode_details['transcode_audio_codec'] = source_audio_details.get('audio_codec', '')
# Override * in video codecs
if stream_details['stream_video_codec'] == '*':
stream_details['stream_video_codec'] = source_video_details['video_codec']
stream_details['stream_video_codec'] = source_video_details.get('video_codec', '')
if transcode_details['transcode_video_codec'] == '*':
transcode_details['transcode_video_codec'] = source_video_details['video_codec']
transcode_details['transcode_video_codec'] = source_video_details.get('video_codec', '')
if media_type in ('movie', 'episode', 'clip'):
# Set the full resolution by combining stream_video_resolution and stream_video_scan_type
@@ -1969,6 +2037,23 @@ class PmsConnect(object):
stream_details['stream_video_resolution'],
stream_details['stream_video_resolution'] + (video_details['stream_video_scan_type'][:1] or 'p'))
if helpers.cast_to_int(source_video_details.get('video_bit_depth')) > 8 \
and source_video_details.get('video_color_space') == 'bt2020nc':
stream_details['video_dynamic_range'] = 'HDR'
else:
stream_details['video_dynamic_range'] = 'SDR'
if stream_details['video_dynamic_range'] == 'HDR' \
and video_details['stream_video_decision'] != 'transcode' \
or helpers.cast_to_int(video_details['stream_video_bit_depth']) > 8 \
and video_details['stream_video_color_space'] == 'bt2020nc':
stream_details['stream_video_dynamic_range'] = 'HDR'
else:
stream_details['stream_video_dynamic_range'] = 'SDR'
else:
stream_details['video_dynamic_range'] = ''
stream_details['stream_video_dynamic_range'] = ''
# Get the quality profile
if media_type in ('movie', 'episode', 'clip') and 'stream_bitrate' in stream_details:
if sync_id:
@@ -1995,7 +2080,7 @@ class PmsConnect(object):
if stream_details['optimized_version']:
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
optimized_version_profile = '{} Mbps {}'.format(round(source_bitrate / 1000.0, 1),
source_media_details['video_full_resolution'])
source_media_details.get('video_full_resolution'))
else:
optimized_version_profile = ''
@@ -2644,10 +2729,14 @@ class PmsConnect(object):
height = height or 1500
if img:
if refresh:
web_img = img.startswith('http')
if refresh and not web_img:
img = '{}/{}'.format(img.rstrip('/'), int(time.time()))
if clip:
if web_img:
params = {'url': '%s' % img}
elif clip:
params = {'url': '%s&%s' % (img, urllib.urlencode({'X-Plex-Token': self.token}))}
else:
params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urllib.urlencode({'X-Plex-Token': self.token}))}

View File

@@ -116,6 +116,10 @@ class Users(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'session_history_media_info.transcode_decision',
'users.do_notify as do_notify',
'users.keep_history as keep_history',
@@ -179,6 +183,9 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'live': item['live'],
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'transcode_decision': item['transcode_decision'],
'do_notify': helpers.checked(item['do_notify']),
'keep_history': helpers.checked(item['keep_history']),
@@ -225,6 +232,10 @@ class Users(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'session_history_media_info.transcode_decision',
'session_history.user',
'session_history.user_id as custom_user_id',
@@ -279,6 +290,9 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'live': item['live'],
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'transcode_decision': item['transcode_decision'],
'friendly_name': item['friendly_name'],
'user_id': item['custom_user_id']
@@ -534,11 +548,11 @@ class Users(object):
try:
if str(user_id).isdigit():
query = 'SELECT session_history.id, session_history.media_type, ' \
query = 'SELECT session_history.id, session_history.media_type, guid, ' \
'session_history.rating_key, session_history.parent_rating_key, session_history.grandparent_rating_key, ' \
'title, parent_title, grandparent_title, original_title, ' \
'thumb, parent_thumb, grandparent_thumb, media_index, parent_media_index, ' \
'year, started, user ' \
'year, originally_available_at, added_at, live, started, user ' \
'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'WHERE user_id = ? ' \
@@ -553,30 +567,33 @@ class Users(object):
result = []
for row in result:
if row['media_type'] == 'episode' and row['parent_thumb']:
thumb = row['parent_thumb']
elif row['media_type'] == 'episode':
thumb = row['grandparent_thumb']
else:
thumb = row['thumb']
if row['media_type'] == 'episode' and row['parent_thumb']:
thumb = row['parent_thumb']
elif row['media_type'] == 'episode':
thumb = row['grandparent_thumb']
else:
thumb = row['thumb']
recent_output = {'row_id': row['id'],
'media_type': row['media_type'],
'rating_key': row['rating_key'],
'parent_rating_key': row['parent_rating_key'],
'grandparent_rating_key': row['grandparent_rating_key'],
'title': row['title'],
'parent_title': row['parent_title'],
'grandparent_title': row['grandparent_title'],
'original_title': row['original_title'],
'thumb': thumb,
'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'],
'year': row['year'],
'time': row['started'],
'user': row['user']
}
recently_watched.append(recent_output)
recent_output = {'row_id': row['id'],
'media_type': row['media_type'],
'rating_key': row['rating_key'],
'parent_rating_key': row['parent_rating_key'],
'grandparent_rating_key': row['grandparent_rating_key'],
'title': row['title'],
'parent_title': row['parent_title'],
'grandparent_title': row['grandparent_title'],
'original_title': row['original_title'],
'thumb': thumb,
'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'],
'year': row['year'],
'originally_available_at': row['originally_available_at'],
'live': row['live'],
'guid': row['guid'],
'time': row['started'],
'user': row['user']
}
recently_watched.append(recent_output)
return recently_watched

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.1.41"
PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.2.0-beta"

View File

@@ -115,20 +115,24 @@ def getVersion():
else:
plexpy.INSTALL_TYPE = 'source'
plexpy.INSTALL_TYPE = 'docker' if plexpy.DOCKER else 'source'
version_file = os.path.join(plexpy.PROG_DIR, 'version.txt')
branch_file = os.path.join(plexpy.PROG_DIR, 'branch.txt')
if not os.path.isfile(version_file):
return None, 'origin', common.BRANCH
with open(version_file, 'r') as f:
current_version = f.read().strip(' \n\r')
if current_version:
return current_version, 'origin', common.BRANCH
if os.path.isfile(version_file):
with open(version_file, 'r') as f:
current_version = f.read().strip(' \n\r')
else:
return None, 'origin', common.BRANCH
current_version = None
if os.path.isfile(branch_file):
with open(branch_file, 'r') as f:
current_branch = f.read().strip(' \n\r')
else:
current_branch = common.BRANCH
return current_version, 'origin', current_branch
def check_update(auto_update=False, notify=False):
@@ -158,13 +162,17 @@ def check_update(auto_update=False, notify=False):
def check_github(auto_update=False, notify=False):
plexpy.COMMITS_BEHIND = 0
if plexpy.CONFIG.GIT_TOKEN:
headers = {'Authorization': 'token {}'.format(plexpy.CONFIG.GIT_TOKEN)}
else:
headers = {}
# Get the latest version available from github
logger.info('Retrieving latest version information from GitHub')
url = 'https://api.github.com/repos/%s/%s/commits/%s' % (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.CONFIG.GIT_BRANCH)
if plexpy.CONFIG.GIT_TOKEN: url = url + '?access_token=%s' % plexpy.CONFIG.GIT_TOKEN
version = request.request_json(url, timeout=20, validator=lambda x: type(x) == dict)
version = request.request_json(url, headers=headers, timeout=20, validator=lambda x: type(x) == dict)
if version is None:
logger.warn('Could not get the latest version from GitHub. Are you running a local development version?')
@@ -187,8 +195,8 @@ def check_github(auto_update=False, notify=False):
plexpy.CONFIG.GIT_REPO,
plexpy.LATEST_VERSION,
plexpy.CURRENT_VERSION)
if plexpy.CONFIG.GIT_TOKEN: url = url + '?access_token=%s' % plexpy.CONFIG.GIT_TOKEN
commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict)
commits = request.request_json(url, headers=headers, timeout=20, whitelist_status_code=404,
validator=lambda x: type(x) == dict)
if commits is None:
logger.warn('Could not get commits behind from GitHub.')
@@ -228,7 +236,7 @@ def check_github(auto_update=False, notify=False):
'plexpy_update_commit': plexpy.LATEST_VERSION,
'plexpy_update_behind': plexpy.COMMITS_BEHIND})
if auto_update:
if auto_update and not plexpy.DOCKER:
logger.info('Running automatic update.')
plexpy.shutdown(restart=True, update=True)
@@ -258,6 +266,9 @@ def update():
logger.error('Unable to update from git: ' + line)
logger.info('Output: ' + str(output))
elif plexpy.INSTALL_TYPE == 'docker':
return
else:
tar_download_url = 'https://github.com/{}/{}/tarball/{}'.format(plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_BRANCH)
update_dir = os.path.join(plexpy.PROG_DIR, 'update')

View File

@@ -277,7 +277,7 @@ class WebInterface(object):
def return_plex_xml_url(self, endpoint='', plextv=False, **kwargs):
kwargs['X-Plex-Token'] = plexpy.CONFIG.PMS_TOKEN
if plextv == 'true':
if helpers.bool_true(plextv):
base_url = 'https://plex.tv'
else:
if plexpy.CONFIG.PMS_URL_OVERRIDE:
@@ -382,15 +382,18 @@ class WebInterface(object):
"do_notify": "Checked",
"do_notify_created": "Checked",
"duration": 1578037,
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1128,
"keep_history": "Checked",
"labels": [],
"last_accessed": 1462693216,
"last_played": "Game of Thrones - The Red Woman",
"library_art": "/:/resources/show-fanart.jpg",
"library_thumb": "",
"library_thumb": "/:/resources/show.png",
"live": 0,
"media_index": 1,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_count": 240,
"parent_media_index": 6,
"parent_title": "",
@@ -666,6 +669,7 @@ class WebInterface(object):
"rating_key": "1219",
"section_id": 2,
"section_type": "show",
"sort_title": "Game of Thrones",
"thumb": "/library/metadata/1219/thumb/1436265995",
"title": "Game of Thrones",
"video_codec": "",
@@ -685,7 +689,7 @@ class WebInterface(object):
# Alias 'title' to 'sort_title'
if kwargs.get('order_column') == 'title':
kwargs['order_column'] = 'sort_title'
# TODO: Find some one way to automatically get the columns
dt_columns = [("added_at", True, False),
("sort_title", True, True),
@@ -701,7 +705,7 @@ class WebInterface(object):
("play_count", True, False)]
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "sort_title")
if refresh == 'true':
if helpers.bool_true(refresh):
refresh = True
else:
refresh = False
@@ -1044,13 +1048,16 @@ class WebInterface(object):
"do_notify": "Checked",
"duration": 2998290,
"friendly_name": "Jon Snow",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1121,
"ip_address": "xxx.xxx.xxx.xxx",
"keep_history": "Checked",
"last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869,
"live": 0,
"media_index": 1,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 6,
"parent_title": "",
"platform": "Chrome",
@@ -1256,12 +1263,15 @@ class WebInterface(object):
"recordsFiltered": 10,
"data":
[{"friendly_name": "Jon Snow",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1121,
"ip_address": "xxx.xxx.xxx.xxx",
"last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869,
"live": 0,
"media_index": 1,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 6,
"parent_title": "",
"platform": "Chrome",
@@ -1596,8 +1606,9 @@ class WebInterface(object):
grandparent_rating_key (int): 351
start_date (str): "YYYY-MM-DD"
section_id (int): 2
media_type (str): "movie", "episode", "track"
media_type (str): "movie", "episode", "track", "live"
transcode_decision (str): "direct play", "copy", "transcode",
guid (str): Plex guid for an item, e.g. "com.plexapp.agents.thetvdb://121361/6/1"
order_column (str): "date", "friendly_name", "ip_address", "platform", "player",
"full_title", "started", "paused_counter", "stopped", "duration"
order_dir (str): "desc" or "asc"
@@ -1622,10 +1633,13 @@ class WebInterface(object):
"original_title": "",
"group_count": 1,
"group_ids": "1124",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1124,
"ip_address": "xxx.xxx.xxx.xxx",
"live": 0,
"media_index": 17,
"media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 7,
"parent_rating_key": 544,
"parent_title": "",
@@ -1683,31 +1697,37 @@ class WebInterface(object):
elif user:
custom_where.append(['session_history.user', user])
if 'rating_key' in kwargs:
rating_key = kwargs.get('rating_key', "")
rating_key = kwargs.get('rating_key', '')
custom_where.append(['session_history.rating_key', rating_key])
if 'parent_rating_key' in kwargs:
rating_key = kwargs.get('parent_rating_key', "")
rating_key = kwargs.get('parent_rating_key', '')
custom_where.append(['session_history.parent_rating_key', rating_key])
if 'grandparent_rating_key' in kwargs:
rating_key = kwargs.get('grandparent_rating_key', "")
rating_key = kwargs.get('grandparent_rating_key', '')
custom_where.append(['session_history.grandparent_rating_key', rating_key])
if 'start_date' in kwargs:
start_date = kwargs.get('start_date', "")
start_date = kwargs.get('start_date', '')
custom_where.append(['strftime("%Y-%m-%d", datetime(started, "unixepoch", "localtime"))', start_date])
if 'reference_id' in kwargs:
reference_id = kwargs.get('reference_id', "")
reference_id = kwargs.get('reference_id', '')
custom_where.append(['session_history.reference_id', reference_id])
if 'section_id' in kwargs:
section_id = kwargs.get('section_id', "")
section_id = kwargs.get('section_id', '')
custom_where.append(['session_history_metadata.section_id', section_id])
if 'media_type' in kwargs:
media_type = kwargs.get('media_type', "")
if media_type != 'all':
media_type = kwargs.get('media_type', '')
if media_type not in ('all', 'live'):
custom_where.append(['session_history.media_type', media_type])
custom_where.append(['session_history_metadata.live', '0'])
elif media_type == 'live':
custom_where.append(['session_history_metadata.live', '1'])
if 'transcode_decision' in kwargs:
transcode_decision = kwargs.get('transcode_decision', "")
transcode_decision = kwargs.get('transcode_decision', '')
if transcode_decision:
custom_where.append(['session_history_media_info.transcode_decision', transcode_decision])
if 'guid' in kwargs:
guid = kwargs.get('guid', '').split('?')[0]
custom_where.append(['session_history_metadata.guid', 'LIKE ' + guid + '%']) # SQLite LIKE wildcard
data_factory = datafactory.DataFactory()
history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping)
@@ -1768,6 +1788,7 @@ class WebInterface(object):
"stream_video_bitrate": 527,
"stream_video_codec": "h264",
"stream_video_decision": "transcode",
"stream_video_dynamic_range": "SDR",
"stream_video_framerate": "24p",
"stream_video_height": 306,
"stream_video_resolution": "SD",
@@ -1782,6 +1803,7 @@ class WebInterface(object):
"video_bitrate": 2500,
"video_codec": "h264",
"video_decision": "transcode",
"video_dynamic_range": "SDR",
"video_framerate": "24p",
"video_height": 816,
"video_resolution": "1080",
@@ -1877,7 +1899,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -1916,7 +1939,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -1955,7 +1979,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -1994,7 +2019,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -2033,7 +2059,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -2072,7 +2099,8 @@ class WebInterface(object):
"series":
[{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]},
{"name": "Music", "data": [...]}
{"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
]
}
```
@@ -2667,8 +2695,11 @@ class WebInterface(object):
@requireAuth(member_of("admin"))
def toggleVerbose(self, **kwargs):
plexpy.VERBOSE = not plexpy.VERBOSE
logger.initLogger(console=not plexpy.QUIET,
log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE)
plexpy.CONFIG.__setattr__('VERBOSE_LOGS', plexpy.VERBOSE)
plexpy.CONFIG.write()
logger.initLogger(console=not plexpy.QUIET, log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE)
logger.info(u"Verbose toggled, set to %s", plexpy.VERBOSE)
logger.debug(u"If you read this message, debug logging is available")
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "logs")
@@ -3062,7 +3093,7 @@ class WebInterface(object):
def install_geoip_db(self, update=False, **kwargs):
""" Downloads and installs the GeoLite2 database """
update = True if update == 'true' else False
update = helpers.bool_true(update)
result = helpers.install_geoip_db(update=update)
@@ -3310,7 +3341,7 @@ class WebInterface(object):
'type': param['type'],
'value': param['value']
}
for category in common.NOTIFICATION_PARAMETERS
for category in common.NOTIFICATION_PARAMETERS
for param in category['parameters']]
return parameters
@@ -3468,7 +3499,7 @@ class WebInterface(object):
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def verify_mobile_device(self, device_token='', cancel=False, **kwargs):
if cancel == 'true':
if helpers.bool_true(cancel):
mobile_app.TEMP_DEVICE_TOKEN = None
return {'result': 'error', 'message': 'Device registration cancelled.'}
@@ -3633,7 +3664,7 @@ class WebInterface(object):
if not username and not password:
return None
force = True if force == 'true' else False
force = helpers.bool_true(force)
plex_tv = plextv.PlexTV(username=username, password=password)
token = plex_tv.get_plexpy_pms_token(force=force)
@@ -3696,7 +3727,7 @@ class WebInterface(object):
result = {'identifier': identifier}
if identifier:
if get_url == 'true':
if helpers.bool_true(get_url):
server = self.get_server_resources(pms_ip=hostname,
pms_port=port,
pms_ssl=ssl,
@@ -3706,7 +3737,7 @@ class WebInterface(object):
result['url'] = server['pms_url']
result['ws'] = None
if test_websocket == 'true':
if helpers.bool_true(test_websocket):
# Quick test websocket connection
ws_url = result['url'].replace('http', 'ws', 1) + '/:/websockets/notifications'
header = ['X-Plex-Token: %s' % plexpy.CONFIG.PMS_TOKEN]
@@ -3760,7 +3791,7 @@ class WebInterface(object):
logger.info(u"New API key generated.")
logger._BLACKLIST_WORDS.add(apikey)
if device == 'true':
if helpers.bool_true(device):
mobile_app.TEMP_DEVICE_TOKEN = apikey
return apikey
@@ -3790,46 +3821,51 @@ class WebInterface(object):
versioncheck.check_update()
if plexpy.UPDATE_AVAILABLE is None:
return {'result': 'error',
'update': None,
'message': 'You are running an unknown version of Tautulli.'
}
update = {'result': 'error',
'update': None,
'message': 'You are running an unknown version of Tautulli.'
}
elif plexpy.UPDATE_AVAILABLE == 'release':
return {'result': 'success',
'update': True,
'release': True,
'message': 'A new release (%s) of Tautulli is available.' % plexpy.LATEST_RELEASE,
'current_release': plexpy.common.RELEASE,
'latest_release': plexpy.LATEST_RELEASE,
'release_url': helpers.anon_url(
'https://github.com/%s/%s/releases/tag/%s'
% (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.LATEST_RELEASE))
}
update = {'result': 'success',
'update': True,
'release': True,
'message': 'A new release (%s) of Tautulli is available.' % plexpy.LATEST_RELEASE,
'current_release': plexpy.common.RELEASE,
'latest_release': plexpy.LATEST_RELEASE,
'release_url': helpers.anon_url(
'https://github.com/%s/%s/releases/tag/%s'
% (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.LATEST_RELEASE))
}
elif plexpy.UPDATE_AVAILABLE == 'commit':
return {'result': 'success',
'update': True,
'release': False,
'message': 'A newer version of Tautulli is available.',
'current_version': plexpy.CURRENT_VERSION,
'latest_version': plexpy.LATEST_VERSION,
'commits_behind': plexpy.COMMITS_BEHIND,
'compare_url': helpers.anon_url(
'https://github.com/%s/%s/compare/%s...%s'
% (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.CURRENT_VERSION,
plexpy.LATEST_VERSION))
update = {'result': 'success',
'update': True,
'release': False,
'message': 'A newer version of Tautulli is available.',
'current_version': plexpy.CURRENT_VERSION,
'latest_version': plexpy.LATEST_VERSION,
'commits_behind': plexpy.COMMITS_BEHIND,
'compare_url': helpers.anon_url(
'https://github.com/%s/%s/compare/%s...%s'
% (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.CURRENT_VERSION,
plexpy.LATEST_VERSION))
}
else:
return {'result': 'success',
'update': False,
'message': 'Tautulli is up to date.'
}
update = {'result': 'success',
'update': False,
'message': 'Tautulli is up to date.'
}
if plexpy.DOCKER:
update['docker'] = plexpy.DOCKER
return update
@cherrypy.expose
@requireAuth(member_of("admin"))
@@ -3859,6 +3895,9 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth(member_of("admin"))
def update(self, **kwargs):
if plexpy.DOCKER:
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
# Show changelog after updating
plexpy.CONFIG.__setattr__('UPDATE_SHOW_CHANGELOG', 1)
plexpy.CONFIG.write()
@@ -3870,7 +3909,7 @@ class WebInterface(object):
if git_branch == plexpy.CONFIG.GIT_BRANCH:
logger.error(u"Already on the %s branch" % git_branch)
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
# Set the new git remote and branch
plexpy.CONFIG.__setattr__('GIT_REMOTE', git_remote)
plexpy.CONFIG.__setattr__('GIT_BRANCH', git_branch)
@@ -3880,15 +3919,15 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth(member_of("admin"))
def get_changelog(self, latest_only=False, since_prev_release=False, update_shown=False, **kwargs):
latest_only = (latest_only == 'true')
since_prev_release = (since_prev_release == 'true')
latest_only = helpers.bool_true(latest_only)
since_prev_release = helpers.bool_true(since_prev_release)
if since_prev_release and plexpy.PREV_RELEASE == common.RELEASE:
latest_only = True
since_prev_release = False
# Set update changelog shown status
if update_shown == 'true':
if helpers.bool_true(update_shown):
plexpy.CONFIG.__setattr__('UPDATE_SHOW_CHANGELOG', 0)
plexpy.CONFIG.write()
@@ -3898,7 +3937,7 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth()
def info(self, rating_key=None, source=None, query=None, **kwargs):
def info(self, rating_key=None, guid=None, source=None, **kwargs):
if rating_key and not str(rating_key).isdigit():
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
@@ -3911,7 +3950,7 @@ class WebInterface(object):
if source == 'history':
data_factory = datafactory.DataFactory()
metadata = data_factory.get_metadata_details(rating_key=rating_key)
metadata = data_factory.get_metadata_details(rating_key=rating_key, guid=guid)
if metadata:
poster_info = data_factory.get_poster_info(metadata=metadata)
metadata.update(poster_info)
@@ -3931,12 +3970,12 @@ class WebInterface(object):
if metadata['section_id'] and not allow_session_library(metadata['section_id']):
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
return serve_template(templatename="info.html", data=metadata, title="Info", config=config, source=source)
return serve_template(templatename="info.html", metadata=metadata, title="Info", config=config, source=source)
else:
if get_session_user_id():
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
else:
return self.update_metadata(rating_key, query)
return self.update_metadata(rating_key)
@cherrypy.expose
@requireAuth()
@@ -4032,10 +4071,10 @@ class WebInterface(object):
width (str): 300
height (str): 450
opacity (str): 25
background (str): 282828
background (str): Hex color, e.g. 282828
blur (str): 3
img_format (str): png
fallback (str): "poster", "cover", "art"
fallback (str): "poster", "cover", "art", "poster-live", "art-live", "art-live-full"
refresh (bool): True or False whether to refresh the image cache
return_hash (bool): True or False to return the self-hosted image hash instead of the image
@@ -4044,20 +4083,27 @@ class WebInterface(object):
```
"""
if not img and not rating_key:
if fallback in common.DEFAULT_IMAGES:
fbi = common.DEFAULT_IMAGES[fallback]
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
return serve_file(path=fp, content_type='image/png')
logger.warn('No image input received.')
return
return_hash = (kwargs.get('return_hash') == 'true')
return_hash = helpers.bool_true(kwargs.get('return_hash'))
if rating_key and not img:
if fallback == 'art':
if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key)
else:
img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/')
img = '/'.join(img_split[:5])
rating_key = rating_key or img_split[3]
if img.startswith('/library/metadata'):
img_split = img.split('/')
img = '/'.join(img_split[:5])
img_rating_key = img_split[3]
if rating_key != img_rating_key:
rating_key = img_rating_key
img_hash = notification_handler.set_hash_image_info(
img=img, rating_key=rating_key, width=width, height=height,
@@ -4074,7 +4120,7 @@ class WebInterface(object):
if not os.path.exists(c_dir):
os.mkdir(c_dir)
clip = True if clip == 'true' else False
clip = helpers.bool_true(clip)
try:
if not plexpy.CONFIG.CACHE_IMAGES or refresh or 'indexes' in img:
@@ -4108,15 +4154,8 @@ class WebInterface(object):
except Exception as e:
logger.warn(u'Failed to get image %s, falling back to %s.' % (img, fallback))
fbi = None
if fallback == 'poster':
fbi = common.DEFAULT_POSTER_THUMB
elif fallback == 'cover':
fbi = common.DEFAULT_COVER_THUMB
elif fallback == 'art':
fbi = common.DEFAULT_ART
if fbi:
if fallback in common.DEFAULT_IMAGES:
fbi = common.DEFAULT_IMAGES[fallback]
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
return serve_file(path=fp, content_type='image/png')
@@ -4134,14 +4173,8 @@ class WebInterface(object):
img_hash = args[0].split('.')[0]
if img_hash in ('poster', 'cover', 'art'):
if img_hash == 'poster':
fbi = common.DEFAULT_POSTER_THUMB
elif img_hash == 'cover':
fbi = common.DEFAULT_COVER_THUMB
elif img_hash == 'art':
fbi = common.DEFAULT_ART
if img_hash in common.DEFAULT_IMAGES:
fbi = common.DEFAULT_IMAGES[img_hash]
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
return serve_file(path=fp, content_type='image/png')
@@ -4290,7 +4323,7 @@ class WebInterface(object):
```
"""
delete_all = (delete_all == 'true')
delete_all = helpers.bool_true(delete_all)
data_factory = datafactory.DataFactory()
result = data_factory.delete_img_info(rating_key=rating_key, service=service, delete_all=delete_all)
@@ -4403,7 +4436,7 @@ class WebInterface(object):
@requireAuth(member_of("admin"))
def update_metadata(self, rating_key=None, query=None, update=False, **kwargs):
query_string = query
update = True if update == 'True' else False
update = helpers.bool_true(update)
data_factory = datafactory.DataFactory()
query = data_factory.get_search_query(rating_key=rating_key)
@@ -4576,6 +4609,7 @@ class WebInterface(object):
"labels": [],
"last_viewed_at": "1462165717",
"library_name": "TV Shows",
"live": 0,
"media_index": "1",
"media_info": [
{
@@ -4585,6 +4619,9 @@ class WebInterface(object):
"audio_codec": "ac3",
"audio_profile": "",
"bitrate": "10617",
"channel_call_sign": "",
"channel_identifier": "",
"channel_thumb": "",
"container": "mkv",
"height": "1078",
"id": "257925",
@@ -4603,6 +4640,10 @@ class WebInterface(object):
"video_bitrate": "10233",
"video_codec": "h264",
"video_codec_level": "41",
"video_color_primaries": "",
"video_color_range": "tv",
"video_color_space": "bt709",
"video_color_trc": "",
"video_frame_rate": "23.976",
"video_height": "1078",
"video_language": "",
@@ -4662,7 +4703,7 @@ class WebInterface(object):
"rating_image": "rottentomatoes://image.rating.ripe",
"rating_key": "153037",
"section_id": "2",
"sort_title": "Game of Thrones",
"sort_title": "Red Woman",
"studio": "HBO",
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
"tagline": "",
@@ -4705,22 +4746,59 @@ class WebInterface(object):
Returns:
json:
{"recently_added":
[{"added_at": "1461572396",
[{"actors": [
"Kit Harington",
"Emilia Clarke",
"Isaac Hempstead-Wright",
"Maisie Williams",
"Liam Cunningham",
],
"added_at": "1461572396",
"art": "/library/metadata/1219/art/1462175063",
"audience_rating": "8",
"audience_rating_image": "rottentomatoes://image.rating.upright",
"banner": "/library/metadata/1219/banner/1462175063",
"directors": [
"Jeremy Podeswa"
],
"duration": "2998290",
"full_title": "Game of Thrones - The Red Woman",
"genres": [
"Adventure",
"Drama",
"Fantasy"
],
"grandparent_rating_key": "1219",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"grandparent_title": "Game of Thrones",
"library_name": "",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"labels": [],
"last_viewed_at": "1462165717",
"library_name": "TV Shows",
"media_index": "1",
"media_type": "episode",
"original_title": "",
"originally_available_at": "2016-04-24",
"parent_media_index": "6",
"parent_rating_key": "153036",
"parent_thumb": "/library/metadata/153036/thumb/1462175062",
"parent_title": "",
"rating": "7.8",
"rating_image": "rottentomatoes://image.rating.ripe",
"rating_key": "153037",
"section_id": "2",
"sort_title": "Red Woman",
"studio": "HBO",
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
"tagline": "",
"thumb": "/library/metadata/153037/thumb/1462175060",
"title": "The Red Woman",
"user_rating": "9.0",
"updated_at": "1462175060",
"writers": [
"David Benioff",
"D. B. Weiss"
],
"year": "2016"
},
{...},
@@ -4943,7 +5021,11 @@ class WebInterface(object):
"banner": "/library/metadata/1219/banner/1503306930",
"bif_thumb": "/library/parts/274169/indexes/sd/1000",
"bitrate": "10617",
"channel_call_sign": "",
"channel_identifier": "",
"channel_stream": 0,
"channel_thumb": "",
"children_count": "",
"collections": [],
"container": "mkv",
"content_rating": "TV-MA",
@@ -4975,13 +5057,15 @@ class WebInterface(object):
"ip_address": "10.10.10.1",
"ip_address_public": "64.123.23.111",
"is_admin": 1,
"is_allow_sync": null,
"is_allow_sync": 1,
"is_home_user": 1,
"is_restricted": 0,
"keep_history": 1,
"labels": [],
"last_viewed_at": "1462165717",
"library_name": "TV Shows",
"live": 0,
"live_uuid": "",
"local": "1",
"location": "lan",
"machine_id": "lmd93nkn12k29j2lnm",
@@ -4990,8 +5074,8 @@ class WebInterface(object):
"optimized_version": 0,
"optimized_version_profile": "",
"optimized_version_title": "",
"originally_available_at": "2016-04-24",
"original_title": "",
"originally_available_at": "2016-04-24",
"parent_guid": "com.plexapp.agents.thetvdb://121361/6?lang=en",
"parent_media_index": "6",
"parent_rating_key": "153036",
@@ -5011,6 +5095,7 @@ class WebInterface(object):
"rating_key": "153037",
"relay": 0,
"section_id": "2",
"secure": 1,
"session_id": "helf15l3rxgw01xxe0jf3l3d",
"session_key": "27",
"shared_libraries": [
@@ -5049,15 +5134,21 @@ class WebInterface(object):
"stream_subtitle_location": "",
"stream_video_bit_depth": "8",
"stream_video_bitrate": "10233",
"stream_video_chroma_subsampling": "4:2:0",
"stream_video_codec": "h264",
"stream_video_codec_level": "41",
"stream_video_color_primaries": "",
"stream_video_color_range": "tv",
"stream_video_color_space": "bt709",
"stream_video_color_trc": "",
"stream_video_decision": "direct play",
"stream_video_dynamic_range": "SDR",
"stream_video_framerate": "24p",
"stream_video_full_resolution": "1080p",
"stream_video_height": "1078",
"stream_video_language": "",
"stream_video_language_code": "",
"stream_video_ref_frames": "4",
"stream_video_full_resolution": "1080p",
"stream_video_resolution": "1080",
"stream_video_scan_type": "progressive",
"stream_video_width": "1920",
@@ -5107,9 +5198,15 @@ class WebInterface(object):
"username": "LordCommanderSnow",
"video_bit_depth": "8",
"video_bitrate": "10233",
"video_chroma_subsampling": "4:2:0",
"video_codec": "h264",
"video_codec_level": "41",
"video_color_primaries": "",
"video_color_range": "tv",
"video_color_space": "bt709",
"video_color_trc": ",
"video_decision": "direct play",
"video_dynamic_range": "SDR",
"video_frame_rate": "23.976",
"video_framerate": "24p",
"video_full_resolution": "1080p",
@@ -5363,8 +5460,10 @@ class WebInterface(object):
[{"content_rating": "TV-MA",
"friendly_name": "",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"labels": [],
"last_play": 1462380698,
"live": 0,
"media_type": "episode",
"platform": "",
"platform_type": "",
@@ -5870,8 +5969,8 @@ class WebInterface(object):
subject=newsletter['subject'],
body=newsletter['body'],
message=newsletter['message'])
preview = (preview == 'true')
raw = (raw == 'true')
preview = helpers.bool_true(preview)
raw = helpers.bool_true(raw)
if raw:
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'