Compare commits
131 Commits
v2.1.41
...
v2.2.0-bet
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f457704105 | ||
![]() |
5aa59b93b0 | ||
![]() |
47d51f92c7 | ||
![]() |
068cf17d0a | ||
![]() |
3c1b849a5d | ||
![]() |
8384ec7f15 | ||
![]() |
0e7a3962bb | ||
![]() |
3d1bb9975c | ||
![]() |
eed473be15 | ||
![]() |
e23d9bca22 | ||
![]() |
85a952a5c9 | ||
![]() |
5cc36310ba | ||
![]() |
8690d2ced5 | ||
![]() |
f572943a7b | ||
![]() |
89248ad46a | ||
![]() |
517db71916 | ||
![]() |
ad82de010d | ||
![]() |
02fd9edbbf | ||
![]() |
6c84dd7be8 | ||
![]() |
9edbe6af37 | ||
![]() |
37d09e9bad | ||
![]() |
0d0595e9a0 | ||
![]() |
17477455cb | ||
![]() |
43f594709d | ||
![]() |
8885b3e7e0 | ||
![]() |
c060638539 | ||
![]() |
2144d4d7ed | ||
![]() |
4f2e09d733 | ||
![]() |
9d13c29bf6 | ||
![]() |
60d577f95e | ||
![]() |
1dd1c6f67f | ||
![]() |
bcd0691b33 | ||
![]() |
24a2559ab5 | ||
![]() |
22a6bae4cf | ||
![]() |
2c45de1fe5 | ||
![]() |
0ab93d7a7f | ||
![]() |
c8831efb28 | ||
![]() |
0cbde5a2f5 | ||
![]() |
7de82d87f7 | ||
![]() |
751b97a39c | ||
![]() |
7b58bcc279 | ||
![]() |
820a2e688c | ||
![]() |
10a7f540ad | ||
![]() |
31a6c627af | ||
![]() |
aba4cbf9e4 | ||
![]() |
a5624e86e4 | ||
![]() |
5480d09a0b | ||
![]() |
5fad0a1d97 | ||
![]() |
4a7b5bab54 | ||
![]() |
fe0557dcc1 | ||
![]() |
e4b6d61098 | ||
![]() |
73e01ebaaf | ||
![]() |
d752d46676 | ||
![]() |
3932409fb4 | ||
![]() |
f69d4f1c42 | ||
![]() |
96699fc3b0 | ||
![]() |
97ec8f6828 | ||
![]() |
df851e67f9 | ||
![]() |
5d1bc3cf9b | ||
![]() |
81ab9b006d | ||
![]() |
1699fc09cf | ||
![]() |
f8b6a9f1e8 | ||
![]() |
ba465a0d15 | ||
![]() |
af521b4058 | ||
![]() |
27b512611f | ||
![]() |
b79f165a27 | ||
![]() |
716f76baed | ||
![]() |
18c57a4fc6 | ||
![]() |
c26483b4b8 | ||
![]() |
441e39d776 | ||
![]() |
7ecc075c7e | ||
![]() |
2e42663832 | ||
![]() |
075f9f8cbd | ||
![]() |
f3d42e7b53 | ||
![]() |
fa0b547b32 | ||
![]() |
8f19cdccfd | ||
![]() |
5c207aeee6 | ||
![]() |
d48273ef98 | ||
![]() |
a7803dcad7 | ||
![]() |
4fc9b6fdb4 | ||
![]() |
73f8f83658 | ||
![]() |
66a0f953b3 | ||
![]() |
f189eea32b | ||
![]() |
be29d879a7 | ||
![]() |
e3e906c9e5 | ||
![]() |
6fce31e1b9 | ||
![]() |
db1cb3d658 | ||
![]() |
c0d7c5ddff | ||
![]() |
d0cd2672dd | ||
![]() |
ca55900d40 | ||
![]() |
3d65ffc6d3 | ||
![]() |
f94b796c2b | ||
![]() |
66c1fd6593 | ||
![]() |
13a45facf9 | ||
![]() |
26f10b2c3d | ||
![]() |
9aea4c85b0 | ||
![]() |
ee1b0eeeff | ||
![]() |
c6cfb4a020 | ||
![]() |
91da41ff86 | ||
![]() |
a811edb236 | ||
![]() |
66de806b02 | ||
![]() |
c825176563 | ||
![]() |
7ae87fe0e7 | ||
![]() |
99f826c236 | ||
![]() |
f88d673e87 | ||
![]() |
bc2f73d686 | ||
![]() |
645c2ecef6 | ||
![]() |
4d4a8ca3b2 | ||
![]() |
a98bc45c10 | ||
![]() |
b9d5e49a71 | ||
![]() |
e90390be67 | ||
![]() |
8ef671c9cb | ||
![]() |
8829516cae | ||
![]() |
d8e8dfbd45 | ||
![]() |
0b6d9a4890 | ||
![]() |
55ffd68023 | ||
![]() |
6380de3e6c | ||
![]() |
d6220a921a | ||
![]() |
10e421b9d4 | ||
![]() |
90056bcce2 | ||
![]() |
4740d0fbf3 | ||
![]() |
13374c9ded | ||
![]() |
2a03be1905 | ||
![]() |
c8f132a750 | ||
![]() |
4d0c4bf4f4 | ||
![]() |
e321c5b197 | ||
![]() |
badbfdc4c1 | ||
![]() |
7d71086a41 | ||
![]() |
0e1764755a | ||
![]() |
c31d3ffd6c | ||
![]() |
0cba3988aa |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
.git
|
||||
.github
|
||||
.gitignore
|
||||
*.md
|
||||
!CHANGELOG*.md
|
30
.github/workflows/publishdocker-branch.yml
vendored
Normal file
30
.github/workflows/publishdocker-branch.yml
vendored
Normal 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
|
34
.github/workflows/publishdocker-release.yml
vendored
Normal file
34
.github/workflows/publishdocker-release.yml
vendored
Normal 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
|
29
.github/workflows/publishrelease-beta.yml
vendored
Normal file
29
.github/workflows/publishrelease-beta.yml
vendored
Normal 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
|
30
.github/workflows/publishrelease-master.yml
vendored
Normal file
30
.github/workflows/publishrelease-master.yml
vendored
Normal 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
120
API.md
@@ -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
|
||||
|
||||
|
64
CHANGELOG.md
64
CHANGELOG.md
@@ -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
31
Dockerfile
Normal 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
|
25
README.md
25
README.md
@@ -1,9 +1,5 @@
|
||||
# Tautulli
|
||||
|
||||
[](https://tautulli.com/discord)
|
||||
[](https://www.reddit.com/r/Tautulli/)
|
||||
[](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
|
||||
|
||||

|
||||
|
||||
## Installation and Support
|
||||
## Installation & Support
|
||||
|
||||
[](https://python.org/downloads/release/python-2717/)
|
||||
[](https://hub.docker.com/r/tautulli/tautulli)
|
||||
[](https://hub.docker.com/r/tautulli/tautulli)
|
||||
|
||||
| Status | Branch: `master` | Branch: `beta` | Branch: `nightly` |
|
||||
| --- | --- | --- | --- |
|
||||
| Release | [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/releases/latest) | [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/commits/beta) | [](https://github.com/Tautulli/Tautulli/commits/nightly) <br> [](https://github.com/Tautulli/Tautulli/commits/nightly) |
|
||||
| Docker | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=branch%3Amaster) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=branch%3Abeta) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=branch%3Anightly) |
|
||||
|
||||
[](https://github.com/Tautulli/Tautulli-Wiki/wiki)
|
||||
[](https://tautulli.com/discord)
|
||||
[](https://www.reddit.com/r/Tautulli/)
|
||||
[](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
|
||||
|
||||
[](https://github.com/Tautulli/Tautulli-Issues)
|
||||
[](https://feathub.com/Tautulli/Tautulli)
|
||||
|
||||
* Please see the [Issues Repository](https://github.com/Tautulli/Tautulli-Issues).
|
||||
|
||||
## License
|
||||
|
||||
[](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.
|
@@ -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:
|
||||
|
@@ -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) {
|
||||
|
@@ -21,7 +21,7 @@ ul.ColVis_collection li {
|
||||
|
||||
.ColVis_Button:hover,
|
||||
ul.ColVis_collection li:hover {
|
||||
color: #F9AA03;
|
||||
color: #E5A00D;
|
||||
}
|
||||
|
||||
button.ColVis_Button {
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
% 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>
|
||||
</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>
|
||||
· <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>
|
||||
|
@@ -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);
|
||||
}
|
||||
});
|
||||
|
@@ -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>
|
||||
|
@@ -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'} %>
|
||||
|
@@ -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');
|
||||
}
|
||||
});
|
||||
|
BIN
data/interfaces/default/images/art-live-full.png
Normal file
BIN
data/interfaces/default/images/art-live-full.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 786 KiB |
BIN
data/interfaces/default/images/libraries/live.png
Normal file
BIN
data/interfaces/default/images/libraries/live.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
9
data/interfaces/default/images/libraries/live.svg
Normal file
9
data/interfaces/default/images/libraries/live.svg
Normal 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 |
@@ -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');
|
||||
|
||||
|
@@ -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> </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']} · E${data['media_index']}</h3>
|
||||
% endif
|
||||
% endif
|
||||
% elif data['media_type'] in ('movie', 'show', 'artist', 'collection'):
|
||||
<h1> </h1><h1>${data['title']}</h1>
|
||||
% elif data['media_type'] == 'season':
|
||||
<h1> </h1><h1><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></h1>
|
||||
<h1> </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']} · 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',
|
||||
|
@@ -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"> ${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"> ${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
|
||||
|
@@ -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>
|
||||
|
@@ -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']}
|
||||
|
@@ -40,7 +40,6 @@ var hc_plays_by_day_options = {
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
|
@@ -23,7 +23,6 @@ var hc_plays_by_dayofweek_options = {
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
categories: [{}],
|
||||
labels: {
|
||||
|
@@ -23,7 +23,6 @@ var hc_plays_by_hourofday_options = {
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
categories: [{}],
|
||||
labels: {
|
||||
|
@@ -23,7 +23,6 @@ var hc_plays_by_month_options = {
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
labels: {
|
||||
style: {
|
||||
|
@@ -23,7 +23,6 @@ var hc_plays_by_platform_options = {
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
categories: [{}],
|
||||
labels: {
|
||||
|
@@ -23,7 +23,6 @@ var hc_plays_by_platform_by_stream_type_options = {
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
categories: [{}],
|
||||
labels: {
|
||||
|
@@ -23,7 +23,6 @@ var hc_plays_by_source_resolution_options = {
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
categories: [{}],
|
||||
labels: {
|
||||
|
@@ -23,7 +23,6 @@ var hc_plays_by_stream_resolution_options = {
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
categories: [{}],
|
||||
labels: {
|
||||
|
@@ -40,7 +40,6 @@ var hc_plays_by_stream_type_options = {
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
|
@@ -23,7 +23,6 @@ var hc_plays_by_user_options = {
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
categories: [{}],
|
||||
labels: {
|
||||
|
@@ -23,7 +23,6 @@ var hc_plays_by_user_by_stream_type_options = {
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#F9AA03', '#FFFFFF', '#FF4747'],
|
||||
xAxis: {
|
||||
categories: [{}],
|
||||
labels: {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 + ' ' + 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 + ' ' + 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'] + ' · 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 + ' ' + 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'] + ' · 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 + ' ' + 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 + ' ' + 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 + ' ' + 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>');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -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 + ' ' + 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 + ' ' + 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'] + ' · 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 + ' ' + 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'] + ' · 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 + ' ' + 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 + ' ' + 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 + ' ' + 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>');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -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 + ' ' + 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 + ' ' + 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 + ' ' + 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'] + ' · 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 + ' ' + 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 + ' ' + 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'] + ' · 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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');
|
||||
|
@@ -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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + 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 + ' ' + thumb_popover + '</div></div>');
|
||||
} else {
|
||||
$(td).html(cellData);
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 + ' ' + 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 + ' ' + 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'] + ' · 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 + ' ' + 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'] + ' · 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 + ' ' + 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 + ' ' + 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 + ' ' + 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%",
|
||||
|
@@ -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 + ' ' + 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 + ' ' + 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'] + ' · 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 + ' ' + 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'] + ' · 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 + ' ' + 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 + ' ' + 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 + ' ' + 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');
|
||||
|
@@ -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");
|
||||
|
@@ -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>
|
||||
· <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>
|
||||
· <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"> </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"> </h3>
|
||||
</div>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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"> </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"> </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"> </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>
|
||||
·
|
||||
<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"> </h3>
|
||||
</div>
|
||||
|
@@ -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();
|
||||
|
@@ -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>
|
||||
|
@@ -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()">
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
· <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>
|
||||
· <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>
|
||||
· <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"> </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:
|
||||
|
@@ -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">
|
||||
|
@@ -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> /
|
||||
<% 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>
|
||||
|
@@ -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> /
|
||||
<% 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>
|
||||
|
@@ -1,3 +1,3 @@
|
||||
from .core import where
|
||||
|
||||
__version__ = "2019.03.09"
|
||||
__version__ = "2019.11.28"
|
||||
|
@@ -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-----
|
||||
|
@@ -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')
|
||||
|
@@ -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
|
||||
|
@@ -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..."
|
||||
|
@@ -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'):
|
||||
|
@@ -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.'},
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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])
|
||||
|
255
plexpy/graphs.py
255
plexpy/graphs.py
@@ -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):
|
||||
|
@@ -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
|
||||
|
@@ -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'],
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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',
|
||||
|
@@ -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}))}
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
PLEXPY_BRANCH = "master"
|
||||
PLEXPY_RELEASE_VERSION = "v2.1.41"
|
||||
PLEXPY_BRANCH = "beta"
|
||||
PLEXPY_RELEASE_VERSION = "v2.2.0-beta"
|
||||
|
@@ -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')
|
||||
|
@@ -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'
|
||||
|
Reference in New Issue
Block a user