Merge branch 'nightly' into python3

# Conflicts:
#	data/interfaces/default/current_activity_instance.html
#	plexpy/activity_handler.py
#	plexpy/graphs.py
#	plexpy/helpers.py
#	plexpy/pmsconnect.py
#	plexpy/version.py
#	plexpy/webserve.py
This commit is contained in:
JonnyWong16 2020-02-29 15:26:33 -08:00
commit 8d5bc88fd9
75 changed files with 2227 additions and 794 deletions

5
.dockerignore Normal file
View file

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

View file

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

View file

@ -0,0 +1,32 @@
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\/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.BRANCH }}
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
job: ${{ github.workflow }}
nofail: true

View file

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

View file

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

120
API.md
View file

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

View file

@ -1,5 +1,61 @@
# Changelog # 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) ## v2.1.42 (2020-01-04)
* Other: * Other:

31
Dockerfile Normal file
View file

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

View file

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

View file

@ -34,7 +34,7 @@ import time
import tzlocal import tzlocal
import plexpy import plexpy
from plexpy import config, database, logger, webstart from plexpy import config, database, helpers, logger, webstart
# Register signals, such as CTRL + C # Register signals, such as CTRL + C
@ -115,7 +115,7 @@ def main():
plexpy.SYS_UTC_OFFSET = datetime.datetime.now(plexpy.SYS_TIMEZONE).strftime('%z') 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 plexpy.DOCKER = True
if args.dev: if args.dev:

View file

@ -28,7 +28,7 @@
<!-- ICONS --> <!-- ICONS -->
<!-- Android --> <!-- 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"> <meta name="theme-color" content="#282a2d">
<!-- Apple --> <!-- Apple -->
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5"> <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 class="container">
<div id="ajaxMsg" class="ajaxMsg"></div> <div id="ajaxMsg" class="ajaxMsg"></div>
% if _session['user_group'] == 'admin': % 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;"> <div id="updatebar" style="display: none;">
% if plexpy.UPDATE_AVAILABLE is None:
You are running an unknown version of Tautulli.<br /> You are running an unknown version of Tautulli.<br />
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a> % elif plexpy.UPDATE_AVAILABLE == 'release':
</div>
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.UPDATE_AVAILABLE == 'release':
<div id="updatebar" style="display: none;">
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"> 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 /> new release (${plexpy.LATEST_RELEASE})</a> of Tautulli is available!<br />
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a> % elif plexpy.UPDATE_AVAILABLE == 'commit':
</div>
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.UPDATE_AVAILABLE == 'commit':
<div id="updatebar" style="display: none;">
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"> 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 /> newer version</a> of Tautulli is available!<br />
You are ${plexpy.COMMITS_BEHIND} commit${'s' if plexpy.COMMITS_BEHIND > 1 else ''} behind.<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> <a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
% endif
</div> </div>
% else: % else:
<div id="updatebar" style="display: none;"></div> <div id="updatebar" style="display: none;"></div>
@ -318,21 +318,23 @@ ${next.modalIncludes()}
complete: function (xhr, status) { complete: function (xhr, status) {
var result = $.parseJSON(xhr.responseText); var result = $.parseJSON(xhr.responseText);
var msg = ''; var msg = '';
if (result.update === null) { if (result.update === false) {
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) {
showMsg('<i class="fa fa-check"></i> ' + result.message, false, true, 2000); 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) { if (_callback) {

View file

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

View file

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

View file

@ -523,7 +523,7 @@ fieldset[disabled] .btn-bright.active {
color: #eee; color: #eee;
} }
.modal-body i { .modal-body i {
color: #F9AA03; color: #E5A00D;
} }
.modal-body i.fa { .modal-body i.fa {
color: #fff; color: #fff;
@ -534,7 +534,7 @@ fieldset[disabled] .btn-bright.active {
} }
.modal-body strong, .modal-body strong,
.modal-body strong i.fa { .modal-body strong i.fa {
color: #F9AA03; color: #E5A00D;
} }
.modal-footer { .modal-footer {
padding: 15px 20px; padding: 15px 20px;
@ -673,7 +673,7 @@ textarea.form-control:focus {
color: #fff; color: #fff;
} }
.form-control-feedback { .form-control-feedback {
color: #F9AA03; color: #E5A00D;
margin: 5px 40px 5px 0; margin: 5px 40px 5px 0;
} }
.form-control[disabled], .form-control[disabled],
@ -2177,7 +2177,7 @@ li.advanced-setting {
font-size: 24px; font-size: 24px;
color: #fff; color: #fff;
padding-top: 27px; padding-top: 27px;
padding-left: 110px; padding-left: 105px;
} }
.user-info-nav { .user-info-nav {
margin-top: 15px; 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); -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); 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-user-toggles,
.edit-library-toggles { .edit-library-toggles {
padding-right: 10px; padding-right: 10px;
@ -3983,6 +4014,9 @@ a:hover .overlay-refresh-image:hover {
.library-video { .library-video {
background-image: url(../images/libraries/video.svg); background-image: url(../images/libraries/video.svg);
} }
.library-live {
background-image: url(../images/libraries/live.svg);
}
.stats-most_concurrent { .stats-most_concurrent {
background-image: url(../images/icons/most-concurrent-streams.svg); background-image: url(../images/icons/most-concurrent-streams.svg);
} }
@ -4033,7 +4067,7 @@ a:hover .overlay-refresh-image:hover {
table-layout: fixed; table-layout: fixed;
} }
.stream-info .heading { .stream-info .heading {
color: #F9AA03; color: #E5A00D;
text-transform: uppercase; text-transform: uppercase;
font-size: 15px; font-size: 15px;
font-weight: bold !important; font-weight: bold !important;

View file

@ -62,8 +62,7 @@ DOCUMENTATION :: END
% if session is not None: % if session is not None:
<% <%
from collections import defaultdict from collections import defaultdict
from six.moves.urllib.parse import quote from plexpy.helpers import cast_to_int, page
from plexpy import helpers
from plexpy.common import VIDEO_RESOLUTION_OVERRIDES, AUDIO_CODEC_OVERRIDES, EXTRA_TYPES from plexpy.common import VIDEO_RESOLUTION_OVERRIDES, AUDIO_CODEC_OVERRIDES, EXTRA_TYPES
import plexpy import plexpy
%> %>
@ -71,62 +70,67 @@ DOCUMENTATION :: END
data = defaultdict(lambda: 'Unknown', **session) data = defaultdict(lambda: 'Unknown', **session)
sk = data['session_key'] sk = data['session_key']
href = 'info?rating_key={}'.format(data['rating_key']) if data['rating_key'] else '#' href = page('info', data['rating_key'])
parent_href = 'info?rating_key={}'.format(data['parent_rating_key']) if data['parent_rating_key'] else '#' parent_href = page('info', data['parent_rating_key'])
grandparent_href = 'info?rating_key={}'.format(data['grandparent_rating_key']) if data['grandparent_rating_key'] else '#' grandparent_href = page('info', data['grandparent_rating_key'])
user_href = 'user?user_id={}'.format(data['user_id']) if data['user_id'] else '#' 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']}" <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"> <div class="dashboard-activity-container">
<% <%
if data['live'] == 1: if data['live']:
background_url = 'images/art-live.png' 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: 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: else:
if (data['art'] and data['art'].startswith('http')) or (data['thumb'] and data['thumb'].startswith('http')): 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)
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'
%> %>
<div id="background-${sk}" class="dashboard-activity-background" style="background-image: url(${background_url});"> <div id="background-${sk}" class="dashboard-activity-background" style="background-image: url(${background_url});">
<div class="dashboard-activity-poster-container hidden-xs"> <div class="dashboard-activity-poster-container hidden-xs">
% if data['media_type'] == 'track': % 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 % endif
% if data['live'] == 1: % if data['live']:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(images/poster-live.png);"></div> % 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: % elif data['channel_stream'] == 0:
% if data['media_type'] == 'movie': % if data['media_type'] == 'movie':
<a id="poster-url-${sk}" href="${href}" title="${data['title']}"> <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> </a>
% elif data['media_type'] == 'episode': % elif data['media_type'] == 'episode':
<a id="poster-url-${sk}" href="${grandparent_href}" title="${data['grandparent_title']}"> <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> </a>
% elif data['media_type'] == 'track': % elif data['media_type'] == 'track':
<a id="poster-url-${sk}" href="${parent_href}" title="${data['parent_title']}"> <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> </a>
% elif data['media_type'] in ('photo', 'clip'): % elif data['media_type'] in ('photo', 'clip'):
% if data['extra_type']: % 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: % 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 % endif
% else: % else:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(images/poster.png);"></div> <div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(images/poster.png);"></div>
% endif % endif
% else: % else:
% if data['channel_icon'].startswith('http'): <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-poster-blur" style="background-image: url(${data['channel_icon']});"></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>
<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
% endif % endif
</div> </div>
<div class="dashboard-activity-info-icon"> <div class="dashboard-activity-info-icon">
@ -160,7 +164,7 @@ DOCUMENTATION :: END
<div class="sub-value platform-right" id="stream_quality-${sk}"> <div class="sub-value platform-right" id="stream_quality-${sk}">
% if data['media_type'] != 'photo' and data['quality_profile'] != 'Unknown': % 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:
if br > 1000: if br > 1000:
br = '(' + str(round(br / 1000.0, 1)) + ' Mbps)' br = '(' + str(round(br / 1000.0, 1)) + ' Mbps)'
@ -326,8 +330,10 @@ DOCUMENTATION :: END
<div class="sub-value time-right"> <div class="sub-value time-right">
% if data['media_type'] != 'photo' and data['bandwidth'] != 'Unknown': % if data['media_type'] != 'photo' and data['bandwidth'] != 'Unknown':
<% <%
bw = helpers.cast_to_int(data['bandwidth']) bw = cast_to_int(data['bandwidth'])
if bw > 1000: if bw > 1000000:
bw = str(round(bw / 1000000.0, 1)) + ' Gbps'
elif bw > 1000:
bw = str(round(bw / 1000.0, 1)) + ' Mbps' bw = str(round(bw / 1000.0, 1)) + ' Mbps'
else: else:
bw = str(bw) + ' kbps' bw = str(bw) + ' kbps'
@ -346,8 +352,8 @@ DOCUMENTATION :: END
</div> </div>
% if data['media_type'] != 'photo': % if data['media_type'] != 'photo':
<div class="dashboard-activity-info-time"> <div class="dashboard-activity-info-time">
% if data['live'] == 1: % if data['live']:
<br />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']: % elif data['view_offset']:
ETA: ETA:
<span id="stream-eta-${sk}"> <span id="stream-eta-${sk}">
@ -376,8 +382,8 @@ DOCUMENTATION :: END
</div> </div>
<div class="dashboard-activity-progress"> <div class="dashboard-activity-progress">
<div class="dashboard-activity-progress-bar"> <div class="dashboard-activity-progress-bar">
% if data['live'] == 1: % if data['live']:
<div id="progress-bar-${sk}" class="progress-bar" style="width: 100%" data-toggle="tooltip" title="Stream Progress Live">Live</div> <div id="progress-bar-${sk}" class="progress-bar" style="width: 100%" data-state="live" data-toggle="tooltip" title="Stream Progress Live">Live</div>
% else: % 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="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> <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>
@ -400,7 +406,16 @@ DOCUMENTATION :: END
% endif % endif
</div> </div>
<div class="dashboard-activity-metadata-title"> <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': % if data['media_type'] == 'movie':
<a href="${href}" title="${data['title']}">${data['title']}</a> <a href="${href}" title="${data['title']}">${data['title']}</a>
% elif data['media_type'] == 'episode': % elif data['media_type'] == 'episode':
@ -425,9 +440,9 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
<div class="dashboard-activity-metadata-subtitle-container"> <div class="dashboard-activity-metadata-subtitle-container">
% if data['live'] == 1: % if data['live']:
<div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="Plex Live TV"> <div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="Live TV">
<i class="fa fa-fw fa-television"></i>&nbsp; <i class="fa fa-fw fa-broadcast-tower"></i>&nbsp;
</div> </div>
% elif data['channel_stream'] == 0: % elif data['channel_stream'] == 0:
<div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="${data['media_type'].capitalize()}"> <div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="${data['media_type'].capitalize()}">
@ -449,8 +464,19 @@ DOCUMENTATION :: END
</div> </div>
% endif % endif
<div class="dashboard-activity-metadata-subtitle"> <div class="dashboard-activity-metadata-subtitle">
% if data['live'] == 1: % if data['live']:
<span title="Plex Live TV" class="sub-heading">Plex Live TV</span> % if data['media_type'] == 'movie':
<span title="${data['year']}" class="sub-heading">${data['year']}</span>
% elif data['media_type'] == 'episode':
% if data['media_index']:
<a href="${href}" title="Season ${data['parent_media_index']}" class="sub-heading">S${data['parent_media_index']}</a>
&middot; <a href="${href}" title="Episode ${data['media_index']}" class="sub-heading">E${data['media_index']}</a>
% else:
<a href="${href}" title="${data['originally_available_at']}" class="sub-heading">${data['originally_available_at']}</a>
% endif
% else:
<span title="Live TV" class="sub-heading">Live TV</span>
% endif
% elif data['channel_stream'] == 0: % elif data['channel_stream'] == 0:
% if data['media_type'] == 'movie': % if data['media_type'] == 'movie':
<span title="${data['year']}" class="sub-heading">${data['year']}</span> <span title="${data['year']}" class="sub-heading">${data['year']}</span>

View file

@ -252,6 +252,7 @@
case "TV": media_type = 'episode'; break; case "TV": media_type = 'episode'; break;
case "Movies": media_type = 'movie'; break; case "Movies": media_type = 'movie'; break;
case "Music": media_type = 'track'; break; case "Music": media_type = 'track'; break;
case "Live TV": media_type = 'live'; break;
case "Direct Play": transcode_decision = 'direct play'; break; case "Direct Play": transcode_decision = 'direct play'; break;
case "Direct Stream": transcode_decision = 'copy'; break; case "Direct Stream": transcode_decision = 'copy'; break;
case "Transcode": transcode_decision = 'transcode'; break; case "Transcode": transcode_decision = 'transcode'; break;
@ -304,6 +305,23 @@
setLocalStorage(chart_key, JSON.stringify(chart_visibility)); 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>
<script src="${http_root}js/graphs/plays_by_day.js${cache_param}"></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> <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.yAxis.min = 0;
hc_plays_by_day_options.xAxis.categories = dateArray; 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.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); var hc_plays_by_day = new Highcharts.Chart(hc_plays_by_day_options);
} }
}); });
@ -403,6 +422,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); } if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_dayofweek_options.xAxis.categories = data.categories; 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.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); var hc_plays_by_dayofweek = new Highcharts.Chart(hc_plays_by_dayofweek_options);
} }
}); });
@ -416,6 +436,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); } if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_hourofday_options.xAxis.categories = data.categories; 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.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); var hc_plays_by_hourofday = new Highcharts.Chart(hc_plays_by_hourofday_options);
} }
}); });
@ -429,6 +450,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); } if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_platform_options.xAxis.categories = data.categories; 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.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); var hc_plays_by_platform = new Highcharts.Chart(hc_plays_by_platform_options);
} }
}); });
@ -442,6 +464,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); } if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_user_options.xAxis.categories = data.categories; 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.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); 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.yAxis.min = 0;
hc_plays_by_stream_type_options.xAxis.categories = dateArray; 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.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); var hc_plays_by_stream_type = new Highcharts.Chart(hc_plays_by_stream_type_options);
} }
}); });
@ -491,6 +515,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); } if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_source_resolution_options.xAxis.categories = data.categories; 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.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); var hc_plays_by_source_resolution = new Highcharts.Chart(hc_plays_by_source_resolution_options);
} }
}); });
@ -504,6 +529,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); } if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_stream_resolution_options.xAxis.categories = data.categories; 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.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); var hc_plays_by_stream_resolution = new Highcharts.Chart(hc_plays_by_stream_resolution_options);
} }
}); });
@ -517,6 +543,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); } 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.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.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); 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); } 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.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.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); 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.yAxis.min = 0;
hc_plays_by_month_options.xAxis.categories = data.categories; 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.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); var hc_plays_by_month = new Highcharts.Chart(hc_plays_by_month_options);
} }
}); });

View file

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

View file

@ -6,7 +6,7 @@
<h4 class="modal-title" id="myModalLabel"> <h4 class="modal-title" id="myModalLabel">
<strong><span id="modal_header_ip_address"> <strong><span id="modal_header_ip_address">
% if data.get('media_type'): % 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> <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'): % elif data.get('transcode_decision'):
<% h = {'copy': 'Direct Stream'} %> <% h = {'copy': 'Direct Stream'} %>

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

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

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -334,13 +334,13 @@
streams_header = streams_header.replace(/, $/, '') + ')'; streams_header = streams_header.replace(/, $/, '') + ')';
$('#currentActivityHeader-streams').text(streams_header); $('#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 = ''; var lan_wan_bandwidth_header = '';
if (lan_bw) { 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) { 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) { if (lan_wan_bandwidth_header) {
bandwidth_header += ' (' + lan_wan_bandwidth_header.replace(/, $/, '') + ')'; bandwidth_header += ' (' + lan_wan_bandwidth_header.replace(/, $/, '') + ')';
@ -356,8 +356,10 @@
var instance = $('#activity-instance-' + key); var instance = $('#activity-instance-' + key);
// Create a new instance if it doesn't exist or recreate the entire instance // 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 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())) { 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); create_instances.push(key);
getActivityInstance(key); getActivityInstance(key);
return; return;
@ -384,32 +386,32 @@
if (s.media_type === 'track') { if (s.media_type === 'track') {
// Update if artist changed // Update if artist changed
if (s.grandparent_rating_key !== instance.data('grandparent_rating_key').toString()) { 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) $('#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) .attr('title', s.original_title || s.grandparent_title)
.text(s.original_title || s.grandparent_title); .text(s.original_title || s.grandparent_title);
} }
// Update cover if album changed // Update cover if album changed
if (s.parent_rating_key !== instance.data('parent_rating_key').toString()) { 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).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(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&opacity=60&background=282828&blur=3&fallback=poster&refresh=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) $('#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); .attr('title', s.parent_title);
$('#metadata-parent_title-' + key) $('#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) .attr('title', s.parent_title)
.text(s.parent_title); .text(s.parent_title);
} }
// Update cover if track changed // Update cover if track changed
if (s.rating_key !== instance.data('rating_key').toString()) { if (s.rating_key !== instance.data('rating_key').toString()) {
$('#metadata-grandparent_title-' + key) $('#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) .attr('title', s.original_title || s.grandparent_title)
.text(s.original_title || s.grandparent_title); .text(s.original_title || s.grandparent_title);
$('#metadata-title-' + key) $('#metadata-title-' + key)
.attr('href', 'info?rating_key=' + s.rating_key) .attr('href', page('info', s.rating_key))
.attr('title', s.title) .attr('title', s.title)
.text(s.title); .text(s.title);
} }
@ -524,7 +526,9 @@
if (s.media_type !== 'photo' && s.bandwidth !== 'Unknown') { if (s.media_type !== 'photo' && s.bandwidth !== 'Unknown') {
var bw = parseInt(s.bandwidth) || 0; 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'; bw = (bw / 1000).toFixed(1) + ' Mbps';
} else { } else {
bw = bw + ' kbps' bw = bw + ' kbps'
@ -543,10 +547,12 @@
// Update the progress bars, percent - 3 because of 3px padding-right // Update the progress bars, percent - 3 because of 3px padding-right
$('#buffer-bar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%') $('#buffer-bar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%')
.attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%'); .attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%');
var progress_bar = $('#progress-bar-' + key); if (s.live !== 1) {
progress_bar.data('state', s.state); var progress_bar = $('#progress-bar-' + key);
if (progress_bar.data('last_view_offset') !== s.view_offset) { progress_bar.data('state', s.state);
progress_bar.data('last_view_offset', s.view_offset).data('view_offset', s.view_offset); 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 // Add temporary class so we know which instances are still active
@ -559,6 +565,7 @@
$(instance).removeClass('updated-temp'); $(instance).removeClass('updated-temp');
} else { } else {
$(instance).find('[data-toggle=tooltip]').tooltip('destroy'); $(instance).find('[data-toggle=tooltip]').tooltip('destroy');
$(instance).find('[data-toggle=popover]').popover('destroy');
$(instance).remove(); $(instance).remove();
} }
}); });
@ -593,6 +600,17 @@
$('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller').scrollbar(); $('#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=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 }); $('#terminate-button-' + session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 });
lockScroll('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller'); lockScroll('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller');

View file

@ -36,10 +36,12 @@ DOCUMENTATION :: END
</%doc> </%doc>
<%! <%!
from collections import defaultdict
import re import re
from plexpy import notifiers from plexpy import notifiers
from plexpy.common import MEDIA_TYPE_HEADERS, MEDIA_FLAGS_AUDIO, MEDIA_FLAGS_VIDEO from plexpy.common import MEDIA_TYPE_HEADERS, MEDIA_FLAGS_AUDIO, MEDIA_FLAGS_VIDEO
from plexpy.helpers import page
# Get audio codec file # Get audio codec file
def af(codec): def af(codec):
@ -48,13 +50,20 @@ DOCUMENTATION :: END
return file_type return file_type
return codec return codec
# Get audio codec file # Get video codec file
def vf(codec): def vf(codec):
for pattern, file_type in MEDIA_FLAGS_VIDEO.items(): for pattern, file_type in MEDIA_FLAGS_VIDEO.items():
if re.match(pattern, codec): if re.match(pattern, codec):
return file_type return file_type
return codec 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): def br(text):
return text.replace('\n', '<br /><br />') return text.replace('\n', '<br /><br />')
%> %>
@ -68,11 +77,15 @@ DOCUMENTATION :: END
</%def> </%def>
<%def name="body()"> <%def name="body()">
% if data: % if metadata:
<% media_info = data['media_info'][0] if data['media_info'] else {} %> <%
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="container-fluid">
<div class="row"> <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': % 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> <span class="overlay-refresh-image info-art" title="Refresh background image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@ -81,44 +94,60 @@ DOCUMENTATION :: END
<div class="col-md-12"> <div class="col-md-12">
<div class="summary-navbar-list"> <div class="summary-navbar-list">
<ul class="list-unstyled breadcrumb"> <ul class="list-unstyled breadcrumb">
% if data['media_type'] in ('movie', 'collection'): % if data['live']:
<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>
% 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> <span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">${data['title']}</li> <li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'show': % 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> <span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">${data['title']}</li> <li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'season': % 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> <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> <span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">Season ${data['media_index']}</li> <li class="active metadata-xml">Season ${data['media_index']}</li>
% elif data['media_type'] == 'episode': % 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> <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> <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> <span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">Episode ${data['media_index']} - ${data['title']}</li> <li class="active metadata-xml">Episode ${data['media_index']} - ${data['title']}</li>
% elif data['media_type'] == 'artist': % 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> <span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">${data['title']}</li> <li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'album': % 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> <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> <span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">${data['title']}</li> <li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'track': % 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> <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> <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> <span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">Track ${data['media_index']} - ${data['title']}</li> <li class="active metadata-xml">Track ${data['media_index']} - ${data['title']}</li>
% endif % endif
@ -131,11 +160,18 @@ DOCUMENTATION :: END
<div class="summary-content-poster hidden-xs hidden-sm"> <div class="summary-content-poster hidden-xs hidden-sm">
% if data['media_type'] == 'track': % 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"> <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"> <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 % 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': % 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"> <div class="summary-poster-face-overlay">
<span></span> <span></span>
</div> </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> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
% elif data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track': % 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"> <div class="summary-poster-face-overlay">
<span></span> <span></span>
</div> </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> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
% else: % 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"> <div class="summary-poster-face-overlay">
<span></span> <span></span>
</div> </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> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
% endif % endif
% endif
% if not data['live']:
</a> </a>
% endif
</div> </div>
<div class="summary-content-title"> <div class="summary-content-title">
% if data['media_type'] in ('movie', 'show', 'artist', 'collection'): % if data['live']:
% if data['media_type'] == 'movie':
<h1>&nbsp;</h1><h1>${data['title']}</h1>
% elif data['media_type'] == 'episode':
<h1>${data['grandparent_title']}</h1>
<h2>${data['title']}</h2>
% if data['media_index']:
<h3 class="hidden-xs">S${data['parent_media_index']} &middot; E${data['media_index']}</h3>
% endif
% endif
% elif data['media_type'] in ('movie', 'show', 'artist', 'collection'):
<h1>&nbsp;</h1><h1>${data['title']}</h1> <h1>&nbsp;</h1><h1>${data['title']}</h1>
% elif data['media_type'] == 'season': % elif data['media_type'] == 'season':
<h1>&nbsp;</h1><h1><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></h1> <h1>&nbsp;</h1><h1><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></h1>
<h3 class="hidden-xs">S${data['media_index']}</h3> <h3 class="hidden-xs">S${data['media_index']}</h3>
% elif data['media_type'] == 'episode': % 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> <h2>${data['title']}</h2>
<h3 class="hidden-xs">S${data['parent_media_index']} &middot; E${data['media_index']}</h3> <h3 class="hidden-xs">S${data['parent_media_index']} &middot; E${data['media_index']}</h3>
% elif data['media_type'] == 'album': % 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> <h2>${data['title']}</h2>
% elif data['media_type'] == 'track': % elif data['media_type'] == 'track':
<h1><a href="info?rating_key=${data['grandparent_rating_key']}">${data['original_title'] or data['grandparent_title']}</a></h1> <h1><a href="${page('info', 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> <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> <h3 class="hidden-xs">T${data['media_index']}</h3>
% endif % endif
</div> </div>
@ -187,7 +236,7 @@ DOCUMENTATION :: END
</div> </div>
<div class="summary-content-wrapper"> <div class="summary-content-wrapper">
<div class="col-md-9"> <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;"> <div class="summary-content-padding hidden-xs hidden-sm" style="height: 305px;">
% elif data['media_type'] in ('show', 'season', 'collection'): % elif data['media_type'] in ('show', 'season', 'collection'):
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 270px;"> <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" /> <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 % endif
% if data['media_type'] != 'track' and media_info['video_resolution']: % 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 % endif
% if media_info['audio_codec']: % 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" /> <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> Released <strong> ${data['year']}</strong>
% elif data['media_type'] == 'collection': % elif data['media_type'] == 'collection':
Year <strong> ${data['min_year']} - ${data['max_year']}</strong> Year <strong> ${data['min_year']} - ${data['max_year']}</strong>
% elif data['year']:
Year <strong> ${data['year']}</strong>
% endif % endif
</div> </div>
<div class="summary-content-details-tag"> <div class="summary-content-details-tag">
@ -263,6 +314,11 @@ DOCUMENTATION :: END
Rated <strong> ${data['content_rating']} </strong> Rated <strong> ${data['content_rating']} </strong>
% endif % endif
</div> </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> </div>
% if data['tagline']: % if data['tagline']:
<div class="summary-content-summary"> <div class="summary-content-summary">
@ -415,7 +471,7 @@ DOCUMENTATION :: END
</div> </div>
% endif % endif
% if data.get('poster_url'): % 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': % 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;"> <span class="hosted-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="80" data-width="80" style="display: inline-flex;">
% else: % else:
@ -429,6 +485,7 @@ DOCUMENTATION :: END
</span> </span>
</div> </div>
% endif % endif
% if not data['live']:
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-dark" data-toggle="modal" aria-pressed="false" autocomplete="off" id="send-recently-added-notification" <button class="btn btn-dark" data-toggle="modal" aria-pressed="false" autocomplete="off" id="send-recently-added-notification"
data-id="${data['rating_key']}"> data-id="${data['rating_key']}">
@ -436,6 +493,7 @@ DOCUMENTATION :: END
</button> </button>
</div> </div>
% endif % endif
% endif
<div class="btn-group"> <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> <button class="btn btn-dark refresh-history-button" id="refresh-history-list"><i class="fa fa-refresh"></i> Refresh history</button>
</div> </div>
@ -474,6 +532,10 @@ DOCUMENTATION :: END
</%def> </%def>
<%def name="modalIncludes()"> <%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 class="modal fade" id="info-modal" tabindex="-1" role="dialog" aria-labelledby="info-modal">
</div> </div>
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal"> <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> </div>
</div> </div>
% endif
</%def> </%def>
<%def name="javascriptIncludes()"> <%def name="javascriptIncludes()">
@ -558,9 +621,28 @@ DOCUMENTATION :: END
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script> <script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
<script src="${http_root}js/moment-with-locale.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> <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> <script>
function get_history() { function get_history() {
history_table_options.ajax = { history_table_options.ajax = {
@ -724,10 +806,22 @@ DOCUMENTATION :: END
$("#airdate").html(moment($("#airdate").text()).format('MMM DD, YYYY')); $("#airdate").html(moment($("#airdate").text()).format('MMM DD, YYYY'));
$("#runtime").html(millisecondsToMinutes($("#runtime").text(), true)); $("#runtime").html(millisecondsToMinutes($("#runtime").text(), true));
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 }); $('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> </script>
% if data.get('poster_url'): % if data.get('poster_url'):
<script> <script>
$('.hosted-poster-tooltip').popover({ $('#hosted-poster').popover({
selector: '[data-toggle=popover]',
html: true, html: true,
container: 'body', container: 'body',
trigger: 'hover', trigger: 'hover',

View file

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

View file

@ -29,6 +29,7 @@ DOCUMENTATION :: END
% if data != None: % if data != None:
<% <%
from plexpy.common import MEDIA_TYPE_HEADERS from plexpy.common import MEDIA_TYPE_HEADERS
from plexpy.helpers import page
types = ('movie', 'show', 'artist', 'album') types = ('movie', 'show', 'artist', 'album')
%> %>
% for media_type in types: % for media_type in types:
@ -45,12 +46,12 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled"> <ul class="item-children-instance list-unstyled">
% for child in data['results_list'][media_type]: % for child in data['results_list'][media_type]:
<li> <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"> <div class="item-children-poster">
% if media_type in ('artist', 'album'): % 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: % 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 % endif
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <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': % if media_type == 'artist':
<div class="item-children-instance-text-wrapper cover-item"> <div class="item-children-instance-text-wrapper cover-item">
<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> </h3>
</div> </div>
% elif media_type == 'album': % elif media_type == 'album':
<div class="item-children-instance-text-wrapper cover-item"> <div class="item-children-instance-text-wrapper cover-item">
<h3> <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>
<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> </h3>
</div> </div>
% else: % else:
<div class="item-children-instance-text-wrapper poster-item"> <div class="item-children-instance-text-wrapper poster-item">
<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> </h3>
<h3 class="text-muted">${child['year']}</h3> <h3 class="text-muted">${child['year']}</h3>
</div> </div>

View file

@ -53,6 +53,9 @@ DOCUMENTATION :: END
</%doc> </%doc>
% if data != None: % if data != None:
<%
from plexpy.helpers import page
%>
% if data['results_count'] > 0: % if data['results_count'] > 0:
% if 'collection' in data['results_list'] and data['results_list']['collection']: % if 'collection' in data['results_list'] and data['results_list']['collection']:
<div class="item-children-wrapper"> <div class="item-children-wrapper">
@ -62,9 +65,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled"> <ul class="item-children-instance list-unstyled">
% for child in data['results_list']['collection']: % for child in data['results_list']['collection']:
<li> <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">
<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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@ -87,9 +90,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled"> <ul class="item-children-instance list-unstyled">
% for child in data['results_list']['movie']: % for child in data['results_list']['movie']:
<li> <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">
<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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@ -112,9 +115,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled"> <ul class="item-children-instance list-unstyled">
% for child in data['results_list']['show']: % for child in data['results_list']['show']:
<li> <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">
<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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@ -137,9 +140,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled"> <ul class="item-children-instance list-unstyled">
% for child in data['results_list']['season']: % for child in data['results_list']['season']:
<li> <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">
<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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@ -162,9 +165,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled"> <ul class="item-children-instance list-unstyled">
% for child in data['results_list']['episode']: % for child in data['results_list']['episode']:
<li> <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">
<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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@ -188,9 +191,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled"> <ul class="item-children-instance list-unstyled">
% for child in data['results_list']['artist']: % for child in data['results_list']['artist']:
<li> <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">
<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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@ -212,9 +215,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled"> <ul class="item-children-instance list-unstyled">
% for child in data['results_list']['album']: % for child in data['results_list']['album']:
<li> <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">
<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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@ -237,9 +240,9 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled"> <ul class="item-children-instance list-unstyled">
% for child in data['results_list']['track']: % for child in data['results_list']['track']:
<li> <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">
<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-card-overlay">
<div class="item-children-overlay-text"> <div class="item-children-overlay-text">
Track ${child['media_index']} Track ${child['media_index']}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -31,6 +31,9 @@ DOCUMENTATION :: END
</%doc> </%doc>
% if data: % if data:
<%
from plexpy.helpers import page
%>
<div class="dashboard-recent-media-row"> <div class="dashboard-recent-media-row">
<div id="recently-added-row-scroller" style="left: 0;"> <div id="recently-added-row-scroller" style="left: 0;">
<ul class="dashboard-recent-media list-unstyled"> <ul class="dashboard-recent-media list-unstyled">
@ -38,19 +41,19 @@ DOCUMENTATION :: END
<li> <li>
% if item['media_type'] == 'episode' or item['media_type'] == 'movie': % if item['media_type'] == 'episode' or item['media_type'] == 'movie':
% if 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': % 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 % endif
<div class="dashboard-recent-media-poster"> <div class="dashboard-recent-media-poster">
% if item['media_type'] == 'episode': % if item['media_type'] == 'episode':
% if item['parent_thumb']: % 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: % 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 % endif
% elif item['media_type'] == 'movie': % 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 % endif
<div class="dashboard-recent-media-overlay"> <div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}"> <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"> <div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'episode': % if item['media_type'] == 'episode':
<h3> <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>
<h3 class="text-muted"> <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>
<h3 class="text-muted"> <h3 class="text-muted">
<a href="info?rating_key=${item['parent_rating_key']}" title="Season ${item['parent_media_index']}">S${item['parent_media_index']}</a> <a href="${page('info', item['parent_rating_key'])}" title="Season ${item['parent_media_index']}">S${item['parent_media_index']}</a>
&middot; <a href="info?rating_key=${item['rating_key']}" title="Episode ${item['media_index']}">E${item['media_index']}</a> &middot; <a href="${page('info', item['rating_key'])}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
</h3> </h3>
% elif item['media_type'] == 'movie': % elif item['media_type'] == 'movie':
<h3> <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>
<h3 class="text-muted">${item['year']}</h3> <h3 class="text-muted">${item['year']}</h3>
<h3 class="text-muted">&nbsp;</h3> <h3 class="text-muted">&nbsp;</h3>
% endif % endif
</div> </div>
% elif item['media_type'] == 'album': % 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">
<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">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}"> <div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script> <script>
@ -100,10 +103,10 @@ DOCUMENTATION :: END
</div> </div>
<div class="dashboard-recent-media-metacontainer"> <div class="dashboard-recent-media-metacontainer">
<h3> <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>
<h3 class="text-muted"> <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>
<h3 class="text-muted">&nbsp;</h3> <h3 class="text-muted">&nbsp;</h3>
</div> </div>

View file

@ -25,6 +25,8 @@ DOCUMENTATION :: END
% if data: % if data:
<% <%
from plexpy.helpers import page
types = ('movie', 'show', 'artist', 'photo') types = ('movie', 'show', 'artist', 'photo')
headers = {'movie': ('Movie Libraries', ('Movies', '', '')), headers = {'movie': ('Movie Libraries', ('Movies', '', '')),
'show': ('TV Show Libraries', ('Shows', 'Seasons', 'Episodes')), 'show': ('TV Show Libraries', ('Shows', 'Seasons', 'Episodes')),
@ -35,7 +37,7 @@ DOCUMENTATION :: END
% if section_type in data: % 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-instance" id="library-stats-instance-${section_type}" data-section_type="${section_type}">
<div class="dashboard-stats-container"> <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 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 class="dashboard-stats-info-container">
<div id="library-stats-title-${section_type}" class="dashboard-stats-info-title"> <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 ''}"> <li class="dashboard-stats-info-item ${'expanded' if loop.index == 0 else ''}">
<div class="sub-list">${loop.index + 1}</div> <div class="sub-list">${loop.index + 1}</div>
<div class="sub-value"> <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']} ${section['section_name']}
</a> </a>
</div> </div>

View file

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

View file

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

View file

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

View file

@ -217,7 +217,7 @@
<div id="git_update_options"> <div id="git_update_options">
<div class="checkbox"> <div class="checkbox">
<label> <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> </label>
<p class="help-block">Update Tautulli automatically if an update is available.</p> <p class="help-block">Update Tautulli automatically if an update is available.</p>
</div> </div>
@ -265,6 +265,19 @@
</div> </div>
<p class="help-block">Optional: The path to your git environment variable. Leave blank for default.</p> <p class="help-block">Optional: The path to your git environment variable. Leave blank for default.</p>
</div> </div>
<div class="form-group advanced-setting">
<label>Repair Git Install</label>
<p class="help-block">
Attempt to fix updating by resetting your Tautulli installation to the latest "${config['git_remote']}/${config['git_branch']}" branch.
</p>
<div class="row">
<div class="col-md-4">
<div class="btn-group">
<button class="btn btn-form" type="button" id="reset_git_install">Reset</button>
</div>
</div>
</div>
</div>
% endif % endif
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p> <p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
@ -2154,6 +2167,13 @@ $(document).ready(function() {
} }
}); });
$("#reset_git_install").click(function () {
var msg = 'Are you sure you want to reset your Tautulli installtion?';
var url = 'reset_git_install';
confirmAjaxCall(url, msg);
});
$('#api_key').click(function(){ $('#api_key').select() }); $('#api_key').click(function(){ $('#api_key').select() });
$("#generate_api").click(function() { $("#generate_api").click(function() {
$.get('generate_api_key', $.get('generate_api_key',
@ -2531,6 +2551,7 @@ $(document).ready(function() {
for (var i in libraries_list) { for (var i in libraries_list) {
var title = libraries_list[i].section_name; var title = libraries_list[i].section_name;
var key = libraries_list[i].section_id; var key = libraries_list[i].section_id;
if (key === 999999) { continue; } // Don't show Live TV library
$('#sortable_home_library_cards').append( $('#sortable_home_library_cards').append(
'<li class="card card-sortable">' + '<li class="card card-sortable">' +
'<div class="card-handle"><i class="fa fa-bars"></i></div>' + '<div class="card-handle"><i class="fa fa-bars"></i></div>' +
@ -2868,11 +2889,13 @@ $(document).ready(function() {
} }
$("#install_geoip_db").click(function () { $("#install_geoip_db").click(function () {
if ($.trim($("#maxmind_license_key").val()) === "") { var maxmind_license_key = $("#maxmind_license_key");
$("#maxmind_license_key").focus(); 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); showMsg('<i class="fa fa-exclamation-circle"></i> Maxmind License Key is required.', false, true, 5000, true);
return false; return false;
} else if (!(saveSettings())){ } else if (!(saveSettings())) {
return false; return false;
} }
var msg = 'Are you sure you want to install the GeoLite2 database?<br /><br />' + var msg = 'Are you sure you want to install the GeoLite2 database?<br /><br />' +

View file

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

View file

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

View file

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

View file

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

View file

@ -590,6 +590,7 @@ def dbcheck():
'media_index INTEGER, parent_media_index INTEGER, ' 'media_index INTEGER, parent_media_index INTEGER, '
'thumb TEXT, parent_thumb TEXT, grandparent_thumb TEXT, year INTEGER, ' 'thumb TEXT, parent_thumb TEXT, grandparent_thumb TEXT, year INTEGER, '
'parent_rating_key INTEGER, grandparent_rating_key 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, ' 'view_offset INTEGER DEFAULT 0, duration INTEGER, video_decision TEXT, audio_decision TEXT, '
'transcode_decision TEXT, container TEXT, bitrate INTEGER, width INTEGER, height INTEGER, ' '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_codec TEXT, video_bitrate INTEGER, video_resolution TEXT, video_width INTEGER, video_height INTEGER, '
@ -609,7 +610,8 @@ def dbcheck():
'transcode_hw_decoding INTEGER, transcode_hw_encoding INTEGER, ' 'transcode_hw_decoding INTEGER, transcode_hw_encoding INTEGER, '
'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT, ' 'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT, '
'synced_version INTEGER, synced_version_profile 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, ' '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)' 'write_attempts INTEGER DEFAULT 0, raw_stream_info TEXT)'
) )
@ -657,7 +659,7 @@ def dbcheck():
'art TEXT, media_type TEXT, year INTEGER, originally_available_at TEXT, added_at INTEGER, updated_at INTEGER, ' '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, ' '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, ' '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 # users table :: This table keeps record of the friends list
@ -1225,6 +1227,42 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN stream_video_dynamic_range TEXT' '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 # Upgrade session_history table from earlier versions
try: try:
c_db.execute('SELECT reference_id FROM session_history') c_db.execute('SELECT reference_id FROM session_history')
@ -1332,6 +1370,24 @@ def dbcheck():
'ALTER TABLE session_history_metadata ADD COLUMN original_title TEXT' '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 # Upgrade session_history_media_info table from earlier versions
try: try:
c_db.execute('SELECT transcode_decision FROM session_history_media_info') c_db.execute('SELECT transcode_decision FROM session_history_media_info')
@ -1574,6 +1630,15 @@ def dbcheck():
c_db.execute( c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN stream_video_dynamic_range TEXT ' '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 # Upgrade users table from earlier versions
try: try:
c_db.execute('SELECT do_notify FROM users') c_db.execute('SELECT do_notify FROM users')

View file

@ -63,9 +63,19 @@ class ActivityHandler(object):
return None 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() 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: if session_list:
for session in session_list['sessions']: for session in session_list['sessions']:
@ -99,7 +109,7 @@ class ActivityHandler(object):
def on_start(self): def on_start(self):
if self.is_valid_session(): if self.is_valid_session():
session = self.get_live_session() session = self.get_live_session(skip_cache=True)
if not session: if not session:
return return
@ -112,9 +122,9 @@ class ActivityHandler(object):
if not session: if not session:
return return
logger.debug("Tautulli ActivityHandler :: Session %s started by user %s (%s) with ratingKey %s (%s)." logger.debug("Tautulli ActivityHandler :: Session %s started by user %s (%s) with ratingKey %s (%s)%s."
% (str(session['session_key']), str(session['user_id']), session['username'], % (str(session['session_key']), str(session['user_id']), session['username'],
str(session['rating_key']), session['full_title'])) str(session['rating_key']), session['full_title'], '[Live TV]' if session['live'] else ''))
plexpy.NOTIFY_QUEUE.put({'stream_data': session.copy(), 'notify_action': 'on_play'}) plexpy.NOTIFY_QUEUE.put({'stream_data': session.copy(), 'notify_action': 'on_play'})
@ -274,11 +284,20 @@ class ActivityHandler(object):
last_transcode_key = db_session['transcode_key'].split('/')[-1] last_transcode_key = db_session['transcode_key'].split('/')[-1]
last_paused = db_session['last_paused'] last_paused = db_session['last_paused']
last_rating_key_websocket = db_session['rating_key_websocket'] 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 # Make sure the same item is being played
if this_rating_key == last_rating_key \ if (this_rating_key == last_rating_key
or this_rating_key == last_rating_key_websocket \ or this_rating_key == last_rating_key_websocket
or this_live_uuid == last_live_uuid: or this_live_uuid == last_live_uuid) \
and this_guid == last_guid:
# Update the session state and viewOffset # Update the session state and viewOffset
if this_state == 'playing': if this_state == 'playing':
# Update the session in our temp session table # Update the session in our temp session table

View file

@ -66,6 +66,9 @@ class ActivityProcessor(object):
'platform': session.get('platform', ''), 'platform': session.get('platform', ''),
'parent_rating_key': session.get('parent_rating_key', ''), 'parent_rating_key': session.get('parent_rating_key', ''),
'grandparent_rating_key': session.get('grandparent_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', ''), 'view_offset': session.get('view_offset', ''),
'duration': session.get('duration', ''), 'duration': session.get('duration', ''),
'video_decision': session.get('video_decision', ''), 'video_decision': session.get('video_decision', ''),
@ -130,6 +133,9 @@ class ActivityProcessor(object):
'relayed': session.get('relayed', 0), 'relayed': session.get('relayed', 0),
'rating_key_websocket': session.get('rating_key_websocket', ''), 'rating_key_websocket': session.get('rating_key_websocket', ''),
'raw_stream_info': json.dumps(session), '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()) 'stopped': int(time.time())
} }
@ -148,6 +154,10 @@ class ActivityProcessor(object):
timestamp = {'started': started} timestamp = {'started': started}
self.db.upsert('sessions', timestamp, keys) 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 return True
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0): def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0):
@ -240,7 +250,12 @@ class ActivityProcessor(object):
if not is_import: if not is_import:
logger.debug("Tautulli ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key']) logger.debug("Tautulli ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect() 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: if not metadata:
return False return False
else: else:
@ -284,38 +299,57 @@ class ActivityProcessor(object):
# % session['session_key']) # % session['session_key'])
self.db.upsert(table_name='session_history', key_dict=keys, value_dict=values) 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 # Get the last insert row id
last_id = self.db.last_insert_id() last_id = self.db.last_insert_id()
new_session = prev_session = None
prev_progress_percent = media_watched_percent = 0
if len(result) > 1: if session['live']:
new_session = {'id': result[0]['id'], # Check if we should group the session, select the last guid from the user
'rating_key': result[0]['rating_key'], query = 'SELECT session_history.id, session_history_metadata.guid, session_history.reference_id ' \
'view_offset': result[0]['view_offset'], 'FROM session_history ' \
'user_id': result[0]['user_id'], 'JOIN session_history_metadata ON session_history.id == session_history_metadata.id ' \
'reference_id': result[0]['reference_id']} 'WHERE session_history.user_id = ? ORDER BY session_history.id DESC LIMIT 1 '
prev_session = {'id': result[1]['id'], args = [session['user_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']}
watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT, result = self.db.select(query=query, args=args)
'episode': plexpy.CONFIG.TV_WATCHED_PERCENT,
'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT if len(result) > 0:
} new_session = {'id': last_id,
prev_progress_percent = helpers.get_percent(prev_session['view_offset'], session['duration']) 'guid': metadata['guid'],
media_watched_percent = watched_percent.get(session['media_type'], 0) '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 = ? ' query = 'UPDATE session_history SET reference_id = ? WHERE id = ? '
@ -326,7 +360,8 @@ class ActivityProcessor(object):
if prev_session is None and new_session is None: if prev_session is None and new_session is None:
args = [last_id, last_id] args = [last_id, last_id]
elif prev_progress_percent < media_watched_percent and \ 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']] args = [prev_session['reference_id'], new_session['id']]
else: else:
args = [new_session['id'], new_session['id']] args = [new_session['id'], new_session['id']]
@ -458,7 +493,11 @@ class ActivityProcessor(object):
'actors': actors, 'actors': actors,
'genres': genres, 'genres': genres,
'studio': metadata['studio'], '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("Tautulli ActivityProcessor :: Writing sessionKey %s session_history_metadata transaction..." # logger.debug("Tautulli ActivityProcessor :: Writing sessionKey %s session_history_metadata transaction..."

View file

@ -120,7 +120,7 @@ class API2(object):
# Allow override for the api. # Allow override for the api.
self._api_out_type = kwargs.pop('out_type', 'json') 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 self._api_app = True
if plexpy.CONFIG.API_ENABLED and not self._api_msg or self._api_cmd in ('get_apikey', 'docs', 'docs_md'): if plexpy.CONFIG.API_ENABLED and not self._api_msg or self._api_cmd in ('get_apikey', 'docs', 'docs_md'):

View file

@ -41,11 +41,26 @@ DEFAULT_USER_THUMB = "interfaces/default/images/gravatar-default-80x80.png"
DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png" DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png" DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png"
DEFAULT_ART = "interfaces/default/images/art.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_POSTER_THUMB = "https://tautulli.com/images/poster.png"
ONLINE_COVER_THUMB = "https://tautulli.com/images/cover.png" ONLINE_COVER_THUMB = "https://tautulli.com/images/cover.png"
ONLINE_ART = "https://tautulli.com/images/art.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 = { MEDIA_TYPE_HEADERS = {
'movie': 'Movies', 'movie': 'Movies',
'show': 'TV Shows', 'show': 'TV Shows',
@ -358,6 +373,9 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Optimized Version Profile', 'type': 'str', 'value': 'optimized_version_profile', 'description': 'The optimized version profile of the stream.'}, {'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': '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': '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': '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': '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'}, {'name': 'Stream Local', 'type': 'int', 'value': 'stream_local', 'description': 'If the stream is local.', 'example': '0 or 1'},
@ -371,8 +389,8 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Stream Video Bitrate', 'type': 'int', 'value': 'stream_video_bitrate', 'description': 'The video bitrate (in kbps) 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 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 Chroma Subsampling', 'type': 'str', 'value': 'stream_video_chroma_subsampling', 'description': 'The video chroma subsampling of the stream.'},
{'name': 'Stream Video Color Primaries', 'type': 'srt', 'value': 'stream_video_color_primaries', 'description': 'The video color primaries 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': 'srt', 'value': 'stream_video_color_range', 'description': 'The video color range 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 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 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 Dynamic Range', 'type': 'str', 'value': 'stream_video_dynamic_range', 'description': 'The video dynamic range of the stream.', 'example': 'HDR or SDR'},
@ -484,8 +502,8 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Video Bitrate', 'type': 'int', 'value': 'video_bitrate', 'description': 'The video bitrate 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 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 Chroma Subsampling', 'type': 'str', 'value': 'video_chroma_subsampling', 'description': 'The video chroma subsampling of the original media.'},
{'name': 'Video Color Primaries', 'type': 'srt', 'value': 'video_color_primaries', 'description': 'The video color primaries 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': 'srt', 'value': 'video_color_range', 'description': 'The video color range 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 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 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 Dynamic Range', 'type': 'str', 'value': 'video_dynamic_range', 'description': 'The video dynamic range of the original media.', 'example': 'HDR or SDR'},

View file

@ -73,6 +73,7 @@ _CONFIG_DEFINITIONS = {
'PMS_UPDATE_CHECK_INTERVAL': (int, 'Advanced', 24), 'PMS_UPDATE_CHECK_INTERVAL': (int, 'Advanced', 24),
'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'), 'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'),
'TIME_FORMAT': (str, 'General', 'HH:mm'), 'TIME_FORMAT': (str, 'General', 'HH:mm'),
'ADD_LIVE_TV_LIBRARY': (int, 'Advanced', 1),
'ANON_REDIRECT': (str, 'General', 'http://www.nullrefer.com/?'), 'ANON_REDIRECT': (str, 'General', 'http://www.nullrefer.com/?'),
'API_ENABLED': (int, 'General', 1), 'API_ENABLED': (int, 'General', 1),
'API_KEY': (str, 'General', ''), 'API_KEY': (str, 'General', ''),
@ -944,3 +945,9 @@ class Config(object):
self.GEOIP_DB = os.path.join(plexpy.DATA_DIR, 'GeoLite2-City.mmdb') self.GEOIP_DB = os.path.join(plexpy.DATA_DIR, 'GeoLite2-City.mmdb')
self.CONFIG_VERSION = 14 self.CONFIG_VERSION = 14
if self.CONFIG_VERSION == 14:
if plexpy.DOCKER:
self.PLEXPY_AUTO_UPDATE = 0
self.CONFIG_VERSION == 15

View file

@ -104,6 +104,10 @@ class DataFactory(object):
'session_history_metadata.thumb', 'session_history_metadata.thumb',
'session_history_metadata.parent_thumb', 'session_history_metadata.parent_thumb',
'session_history_metadata.grandparent_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) / \ '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 = "") \ (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', THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
@ -152,6 +156,10 @@ class DataFactory(object):
'thumb', 'thumb',
'parent_thumb', 'parent_thumb',
'grandparent_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) / \ '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 = "") \ (CASE WHEN (duration IS NULL OR duration = "") \
THEN 1.0 ELSE duration * 1.0 END) * 100) AS percent_complete', THEN 1.0 ELSE duration * 1.0 END) * 100) AS percent_complete',
@ -216,6 +224,9 @@ class DataFactory(object):
else: else:
thumb = item['thumb'] thumb = item['thumb']
if item['live']:
item['percent_complete'] = 100
if item['percent_complete'] >= watched_percent[item['media_type']]: if item['percent_complete'] >= watched_percent[item['media_type']]:
watched_status = 1 watched_status = 1
elif item['percent_complete'] >= old_div(watched_percent[item['media_type']],2): elif item['percent_complete'] >= old_div(watched_percent[item['media_type']],2):
@ -240,6 +251,7 @@ class DataFactory(object):
'product': item['product'], 'product': item['product'],
'player': item['player'], 'player': item['player'],
'ip_address': item['ip_address'], 'ip_address': item['ip_address'],
'live': item['live'],
'media_type': item['media_type'], 'media_type': item['media_type'],
'rating_key': item['rating_key'], 'rating_key': item['rating_key'],
'parent_rating_key': item['parent_rating_key'], 'parent_rating_key': item['parent_rating_key'],
@ -253,6 +265,8 @@ class DataFactory(object):
'media_index': item['media_index'], 'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'], 'parent_media_index': item['parent_media_index'],
'thumb': thumb, 'thumb': thumb,
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'transcode_decision': item['transcode_decision'], 'transcode_decision': item['transcode_decision'],
'percent_complete': int(round(item['percent_complete'])), 'percent_complete': int(round(item['percent_complete'])),
'watched_status': watched_status, 'watched_status': watched_status,
@ -296,7 +310,7 @@ class DataFactory(object):
top_movies = [] top_movies = []
try: try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \ 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 ' \ '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) - ' \ '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) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@ -333,6 +347,8 @@ class DataFactory(object):
'friendly_name': '', 'friendly_name': '',
'platform': '', 'platform': '',
'platform': '', 'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id'] 'row_id': item['id']
} }
top_movies.append(row) top_movies.append(row)
@ -346,7 +362,7 @@ class DataFactory(object):
popular_movies = [] popular_movies = []
try: try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \ 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, ' \ '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 ' \ '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) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@ -382,6 +398,8 @@ class DataFactory(object):
'user': '', 'user': '',
'friendly_name': '', 'friendly_name': '',
'platform': '', 'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id'] 'row_id': item['id']
} }
popular_movies.append(row) popular_movies.append(row)
@ -394,7 +412,7 @@ class DataFactory(object):
top_tv = [] top_tv = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ 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 ' \ '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) - ' \ '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) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@ -418,7 +436,7 @@ class DataFactory(object):
'total_plays': item['total_plays'], 'total_plays': item['total_plays'],
'total_duration': item['total_duration'], 'total_duration': item['total_duration'],
'users_watched': '', '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'], 'last_play': item['last_watch'],
'grandparent_thumb': item['grandparent_thumb'], 'grandparent_thumb': item['grandparent_thumb'],
'thumb': item['grandparent_thumb'], 'thumb': item['grandparent_thumb'],
@ -430,6 +448,8 @@ class DataFactory(object):
'user': '', 'user': '',
'friendly_name': '', 'friendly_name': '',
'platform': '', 'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id'] 'row_id': item['id']
} }
top_tv.append(row) top_tv.append(row)
@ -443,7 +463,7 @@ class DataFactory(object):
popular_tv = [] popular_tv = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ 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, ' \ '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 ' \ '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) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@ -466,7 +486,7 @@ class DataFactory(object):
for item in result: for item in result:
row = {'title': item['grandparent_title'], row = {'title': item['grandparent_title'],
'users_watched': item['users_watched'], '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'], 'last_play': item['last_watch'],
'total_plays': item['total_plays'], 'total_plays': item['total_plays'],
'grandparent_thumb': item['grandparent_thumb'], 'grandparent_thumb': item['grandparent_thumb'],
@ -479,6 +499,8 @@ class DataFactory(object):
'user': '', 'user': '',
'friendly_name': '', 'friendly_name': '',
'platform': '', 'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id'] 'row_id': item['id']
} }
popular_tv.append(row) popular_tv.append(row)
@ -492,7 +514,7 @@ class DataFactory(object):
try: try:
query = 'SELECT t.id, t.grandparent_title, t.original_title, ' \ query = 'SELECT t.id, t.grandparent_title, t.original_title, ' \
't.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ '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 ' \ '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) - ' \ '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) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@ -528,6 +550,8 @@ class DataFactory(object):
'user': '', 'user': '',
'friendly_name': '', 'friendly_name': '',
'platform': '', 'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id'] 'row_id': item['id']
} }
top_music.append(row) top_music.append(row)
@ -542,7 +566,7 @@ class DataFactory(object):
try: try:
query = 'SELECT t.id, t.grandparent_title, t.original_title, ' \ query = 'SELECT t.id, t.grandparent_title, t.original_title, ' \
't.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ '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, ' \ '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 ' \ '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) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@ -578,6 +602,8 @@ class DataFactory(object):
'user': '', 'user': '',
'friendly_name': '', 'friendly_name': '',
'platform': '', 'platform': '',
'live': item['live'],
'guid': item['guid'],
'row_id': item['id'] 'row_id': item['id']
} }
popular_music.append(row) popular_music.append(row)
@ -694,7 +720,7 @@ class DataFactory(object):
try: try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.grandparent_thumb, ' \ 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.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) ' \ '(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \
' AS friendly_name, ' \ ' AS friendly_name, ' \
'MAX(t.started) AS last_watch, ' \ 'MAX(t.started) AS last_watch, ' \
@ -740,6 +766,8 @@ class DataFactory(object):
'content_rating': item['content_rating'], 'content_rating': item['content_rating'],
'labels': item['labels'].split(';') if item['labels'] else (), 'labels': item['labels'].split(';') if item['labels'] else (),
'last_watch': item['last_watch'], 'last_watch': item['last_watch'],
'live': item['live'],
'guid': item['guid'],
'player': item['player'] 'player': item['player']
} }
last_watched.append(row) last_watched.append(row)
@ -1002,10 +1030,17 @@ class DataFactory(object):
stream_output = {k: v or '' for k, v in list(stream_output.items())} stream_output = {k: v or '' for k, v in list(stream_output.items())}
return stream_output return stream_output
def get_metadata_details(self, rating_key): def get_metadata_details(self, rating_key='', guid=''):
monitor_db = database.MonitorDatabase() 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, ' \ query = 'SELECT session_history_metadata.id, ' \
'session_history_metadata.rating_key, session_history_metadata.parent_rating_key, ' \ 'session_history_metadata.rating_key, session_history_metadata.parent_rating_key, ' \
'session_history_metadata.grandparent_rating_key, session_history_metadata.title, ' \ 'session_history_metadata.grandparent_rating_key, session_history_metadata.title, ' \
@ -1025,15 +1060,18 @@ class DataFactory(object):
'session_history_metadata.labels, ' \ 'session_history_metadata.labels, ' \
'session_history_media_info.container, session_history_media_info.bitrate, ' \ '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_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.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 ' \ 'FROM session_history_metadata ' \
'JOIN library_sections ON session_history_metadata.section_id = library_sections.section_id ' \ '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 ' \ '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 ' \ 'ORDER BY session_history_metadata.id DESC ' \
'LIMIT 1' 'LIMIT 1' % where
result = monitor_db.select(query=query, args=[rating_key]) result = monitor_db.select(query=query, args=args)
else: else:
result = [] result = []
@ -1050,9 +1088,13 @@ class DataFactory(object):
'bitrate': item['bitrate'], 'bitrate': item['bitrate'],
'video_codec': item['video_codec'], 'video_codec': item['video_codec'],
'video_resolution': item['video_resolution'], 'video_resolution': item['video_resolution'],
'video_full_resolution': item['video_full_resolution'],
'video_framerate': item['video_framerate'], 'video_framerate': item['video_framerate'],
'audio_codec': item['audio_codec'], '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'], metadata = {'media_type': item['media_type'],
@ -1066,6 +1108,7 @@ class DataFactory(object):
'media_index': item['media_index'], 'media_index': item['media_index'],
'studio': item['studio'], 'studio': item['studio'],
'title': item['title'], 'title': item['title'],
'full_title': item['full_title'],
'content_rating': item['content_rating'], 'content_rating': item['content_rating'],
'summary': item['summary'], 'summary': item['summary'],
'tagline': item['tagline'], 'tagline': item['tagline'],
@ -1088,6 +1131,7 @@ class DataFactory(object):
'labels': labels, 'labels': labels,
'library_name': item['section_name'], 'library_name': item['section_name'],
'section_id': item['section_id'], 'section_id': item['section_id'],
'live': item['live'],
'media_info': media_info 'media_info': media_info
} }
metadata_list.append(metadata) metadata_list.append(metadata)

View file

@ -146,6 +146,9 @@ class DataTables(object):
for w_ in w[1]: for w_ in w[1]:
if w_ == None: if w_ == None:
c_where += w[0] + ' IS NULL OR ' c_where += w[0] + ' IS NULL OR '
elif str(w_).startswith('LIKE '):
c_where += w[0] + ' LIKE ? OR '
args.append(w_[5:])
else: else:
c_where += w[0] + ' = ? OR ' c_where += w[0] + ' = ? OR '
args.append(w_) args.append(w_)
@ -153,6 +156,9 @@ class DataTables(object):
else: else:
if w[1] == None: if w[1] == None:
c_where += w[0] + ' IS NULL AND ' 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: else:
c_where += w[0] + ' = ? AND ' c_where += w[0] + ' = ? AND '
args.append(w[1]) args.append(w[1])

View file

@ -27,6 +27,7 @@ import plexpy
from plexpy import common from plexpy import common
from plexpy import database from plexpy import database
from plexpy import logger from plexpy import logger
from plexpy import libraries
from plexpy import session from plexpy import session
@ -55,10 +56,12 @@ class Graphs(object):
try: try:
if y_axis == 'plays': if y_axis == 'plays':
query = 'SELECT date(started, "unixepoch", "localtime") AS date_played, ' \ 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 = "episode" AND live = 0 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 = "movie" AND live = 0 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 = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \ 'FROM (SELECT * FROM session_history ' \
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
'GROUP BY date(started, "unixepoch", "localtime"), %s) ' \ 'GROUP BY date(started, "unixepoch", "localtime"), %s) ' \
'AS session_history ' \ 'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
@ -68,13 +71,17 @@ class Graphs(object):
result = monitor_db.select(query) result = monitor_db.select(query)
else: else:
query = 'SELECT date(started, "unixepoch", "localtime") AS date_played, ' \ 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, ' \ ' - (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, ' \ ' - (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 ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'FROM session_history ' \ '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' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY date_played ' \ 'GROUP BY date_played ' \
'ORDER BY started ASC' % (time_range, user_cond) 'ORDER BY started ASC' % (time_range, user_cond)
@ -93,6 +100,7 @@ class Graphs(object):
series_1 = [] series_1 = []
series_2 = [] series_2 = []
series_3 = [] series_3 = []
series_4 = []
for date_item in sorted(date_list): for date_item in sorted(date_list):
date_string = date_item.strftime('%Y-%m-%d') date_string = date_item.strftime('%Y-%m-%d')
@ -100,20 +108,24 @@ class Graphs(object):
series_1_value = 0 series_1_value = 0
series_2_value = 0 series_2_value = 0
series_3_value = 0 series_3_value = 0
series_4_value = 0
for item in result: for item in result:
if date_string == item['date_played']: if date_string == item['date_played']:
series_1_value = item['tv_count'] series_1_value = item['tv_count']
series_2_value = item['movie_count'] series_2_value = item['movie_count']
series_3_value = item['music_count'] series_3_value = item['music_count']
series_4_value = item['live_count']
break break
else: else:
series_1_value = 0 series_1_value = 0
series_2_value = 0 series_2_value = 0
series_3_value = 0 series_3_value = 0
series_4_value = 0
series_1.append(series_1_value) series_1.append(series_1_value)
series_2.append(series_2_value) series_2.append(series_2_value)
series_3.append(series_3_value) series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV', series_1_output = {'name': 'TV',
'data': series_1} 'data': series_1}
@ -121,9 +133,21 @@ class Graphs(object):
'data': series_2} 'data': series_2}
series_3_output = {'name': 'Music', series_3_output = {'name': 'Music',
'data': series_3} '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, output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]} 'series': series_output}
return output return output
def get_total_plays_per_dayofweek(self, time_range='30', y_axis='plays', user_id=None, grouping=None): def get_total_plays_per_dayofweek(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@ -154,10 +178,12 @@ class Graphs(object):
'WHEN 4 THEN "Thursday" ' \ 'WHEN 4 THEN "Thursday" ' \
'WHEN 5 THEN "Friday" ' \ 'WHEN 5 THEN "Friday" ' \
'ELSE "Saturday" END) AS dayofweek, ' \ 'ELSE "Saturday" END) AS dayofweek, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \ 'SUM(CASE WHEN media_type = "episode" AND live = 0 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 = "movie" AND live = 0 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 = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \ '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) ' \ 'GROUP BY strftime("%%w", datetime(started, "unixepoch", "localtime")), %s) ' \
'AS session_history ' \ 'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
@ -175,13 +201,17 @@ class Graphs(object):
'WHEN 4 THEN "Thursday" ' \ 'WHEN 4 THEN "Thursday" ' \
'WHEN 5 THEN "Friday" ' \ 'WHEN 5 THEN "Friday" ' \
'ELSE "Saturday" END) AS dayofweek, ' \ '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, ' \ ' - (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, ' \ ' - (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 ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'FROM session_history ' \ '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' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY dayofweek ' \ 'GROUP BY dayofweek ' \
'ORDER BY daynumber' % (time_range, user_cond) 'ORDER BY daynumber' % (time_range, user_cond)
@ -202,26 +232,31 @@ class Graphs(object):
series_1 = [] series_1 = []
series_2 = [] series_2 = []
series_3 = [] series_3 = []
series_4 = []
for day_item in days_list: for day_item in days_list:
categories.append(day_item) categories.append(day_item)
series_1_value = 0 series_1_value = 0
series_2_value = 0 series_2_value = 0
series_3_value = 0 series_3_value = 0
series_4_value = 0
for item in result: for item in result:
if day_item == item['dayofweek']: if day_item == item['dayofweek']:
series_1_value = item['tv_count'] series_1_value = item['tv_count']
series_2_value = item['movie_count'] series_2_value = item['movie_count']
series_3_value = item['music_count'] series_3_value = item['music_count']
series_4_value = item['live_count']
break break
else: else:
series_1_value = 0 series_1_value = 0
series_2_value = 0 series_2_value = 0
series_3_value = 0 series_3_value = 0
series_4_value = 0
series_1.append(series_1_value) series_1.append(series_1_value)
series_2.append(series_2_value) series_2.append(series_2_value)
series_3.append(series_3_value) series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV', series_1_output = {'name': 'TV',
'data': series_1} 'data': series_1}
@ -229,9 +264,21 @@ class Graphs(object):
'data': series_2} 'data': series_2}
series_3_output = {'name': 'Music', series_3_output = {'name': 'Music',
'data': series_3} '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, output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]} 'series': series_output}
return output return output
def get_total_plays_per_hourofday(self, time_range='30', y_axis='plays', user_id=None, grouping=None): def get_total_plays_per_hourofday(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@ -254,10 +301,12 @@ class Graphs(object):
try: try:
if y_axis == 'plays': if y_axis == 'plays':
query = 'SELECT strftime("%%H", datetime(started, "unixepoch", "localtime")) AS hourofday, ' \ 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 = "episode" AND live = 0 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 = "movie" AND live = 0 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 = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \ '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) ' \ 'GROUP BY strftime("%%H", datetime(started, "unixepoch", "localtime")) , %s) ' \
'AS session_history ' \ 'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
@ -267,13 +316,17 @@ class Graphs(object):
result = monitor_db.select(query) result = monitor_db.select(query)
else: else:
query = 'SELECT strftime("%%H", datetime(started, "unixepoch", "localtime")) AS hourofday, ' \ 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, ' \ ' - (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, ' \ ' - (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 ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'FROM session_history ' \ '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' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY hourofday ' \ 'GROUP BY hourofday ' \
'ORDER BY hourofday' % (time_range, user_cond) 'ORDER BY hourofday' % (time_range, user_cond)
@ -283,35 +336,40 @@ class Graphs(object):
logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_plays_per_hourofday: %s." % e) logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_plays_per_hourofday: %s." % e)
return None return None
hours_list = ['00','01','02','03','04','05', hours_list = ['00', '01', '02', '03', '04', '05',
'06','07','08','09','10','11', '06', '07', '08', '09', '10', '11',
'12','13','14','15','16','17', '12', '13', '14', '15', '16', '17',
'18','19','20','21','22','23'] '18', '19', '20', '21', '22', '23']
categories = [] categories = []
series_1 = [] series_1 = []
series_2 = [] series_2 = []
series_3 = [] series_3 = []
series_4 = []
for hour_item in hours_list: for hour_item in hours_list:
categories.append(hour_item) categories.append(hour_item)
series_1_value = 0 series_1_value = 0
series_2_value = 0 series_2_value = 0
series_3_value = 0 series_3_value = 0
series_4_value = 0
for item in result: for item in result:
if hour_item == item['hourofday']: if hour_item == item['hourofday']:
series_1_value = item['tv_count'] series_1_value = item['tv_count']
series_2_value = item['movie_count'] series_2_value = item['movie_count']
series_3_value = item['music_count'] series_3_value = item['music_count']
series_4_value = item['live_count']
break break
else: else:
series_1_value = 0 series_1_value = 0
series_2_value = 0 series_2_value = 0
series_3_value = 0 series_3_value = 0
series_4_value = 0
series_1.append(series_1_value) series_1.append(series_1_value)
series_2.append(series_2_value) series_2.append(series_2_value)
series_3.append(series_3_value) series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV', series_1_output = {'name': 'TV',
'data': series_1} 'data': series_1}
@ -319,14 +377,24 @@ class Graphs(object):
'data': series_2} 'data': series_2}
series_3_output = {'name': 'Music', series_3_output = {'name': 'Music',
'data': series_3} '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, output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]} 'series': series_output}
return output return output
def get_total_plays_per_month(self, time_range='12', y_axis='plays', user_id=None, grouping=None): 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(): if not time_range.isdigit():
time_range = '12' time_range = '12'
@ -346,10 +414,12 @@ class Graphs(object):
try: try:
if y_axis == 'plays': if y_axis == 'plays':
query = 'SELECT strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) AS datestring, ' \ 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 = "episode" AND live = 0 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 = "movie" AND live = 0 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 = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count ' \
'FROM (SELECT * FROM session_history ' \ '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) ' \ 'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")), %s) ' \
'AS session_history ' \ 'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \
@ -359,13 +429,17 @@ class Graphs(object):
result = monitor_db.select(query) result = monitor_db.select(query)
else: else:
query = 'SELECT strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) AS datestring, ' \ 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, ' \ ' - (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, ' \ ' - (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 ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS music_count, ' \
'FROM session_history ' \ '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' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \
'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) ' \ 'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) ' \
'ORDER BY datestring DESC LIMIT %s' % (time_range, user_cond, time_range) 'ORDER BY datestring DESC LIMIT %s' % (time_range, user_cond, time_range)
@ -391,6 +465,7 @@ class Graphs(object):
series_1 = [] series_1 = []
series_2 = [] series_2 = []
series_3 = [] series_3 = []
series_4 = []
for dt in sorted(month_range): for dt in sorted(month_range):
date_string = dt.strftime('%Y-%m') date_string = dt.strftime('%Y-%m')
@ -398,20 +473,24 @@ class Graphs(object):
series_1_value = 0 series_1_value = 0
series_2_value = 0 series_2_value = 0
series_3_value = 0 series_3_value = 0
series_4_value = 0
for item in result: for item in result:
if date_string == item['datestring']: if date_string == item['datestring']:
series_1_value = item['tv_count'] series_1_value = item['tv_count']
series_2_value = item['movie_count'] series_2_value = item['movie_count']
series_3_value = item['music_count'] series_3_value = item['music_count']
series_4_value = item['live_count']
break break
else: else:
series_1_value = 0 series_1_value = 0
series_2_value = 0 series_2_value = 0
series_3_value = 0 series_3_value = 0
series_4_value = 0
series_1.append(series_1_value) series_1.append(series_1_value)
series_2.append(series_2_value) series_2.append(series_2_value)
series_3.append(series_3_value) series_3.append(series_3_value)
series_4.append(series_4_value)
series_1_output = {'name': 'TV', series_1_output = {'name': 'TV',
'data': series_1} 'data': series_1}
@ -419,9 +498,21 @@ class Graphs(object):
'data': series_2} 'data': series_2}
series_3_output = {'name': 'Music', series_3_output = {'name': 'Music',
'data': series_3} '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, output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]} 'series': series_output}
return output return output
def get_total_plays_by_top_10_platforms(self, time_range='30', y_axis='plays', user_id=None, grouping=None): def get_total_plays_by_top_10_platforms(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@ -444,11 +535,15 @@ class Graphs(object):
try: try:
if y_axis == 'plays': if y_axis == 'plays':
query = 'SELECT platform, ' \ query = 'SELECT platform, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \ 'SUM(CASE WHEN media_type = "episode" AND live = 0 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 = "movie" AND live = 0 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 = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count, ' \
'COUNT(id) AS total_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' \ 'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY platform ' \ 'GROUP BY platform ' \
'ORDER BY total_count DESC ' \ 'ORDER BY total_count DESC ' \
@ -457,15 +552,19 @@ class Graphs(object):
result = monitor_db.select(query) result = monitor_db.select(query)
else: else:
query = 'SELECT platform, ' \ 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, ' \ ' - (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, ' \ ' - (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, ' \ ' - (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) ' \ '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 ' \ ' - (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' \ 'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY platform ' \ 'GROUP BY platform ' \
'ORDER BY total_duration DESC ' \ 'ORDER BY total_duration DESC ' \
@ -480,12 +579,14 @@ class Graphs(object):
series_1 = [] series_1 = []
series_2 = [] series_2 = []
series_3 = [] series_3 = []
series_4 = []
for item in result: for item in result:
categories.append(common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])) categories.append(common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']))
series_1.append(item['tv_count']) series_1.append(item['tv_count'])
series_2.append(item['movie_count']) series_2.append(item['movie_count'])
series_3.append(item['music_count']) series_3.append(item['music_count'])
series_4.append(item['live_count'])
series_1_output = {'name': 'TV', series_1_output = {'name': 'TV',
'data': series_1} 'data': series_1}
@ -493,9 +594,21 @@ class Graphs(object):
'data': series_2} 'data': series_2}
series_3_output = {'name': 'Music', series_3_output = {'name': 'Music',
'data': series_3} '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, output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]} 'series': series_output}
return output return output
def get_total_plays_by_top_10_users(self, time_range='30', y_axis='plays', user_id=None, grouping=None): def get_total_plays_by_top_10_users(self, time_range='30', y_axis='plays', user_id=None, grouping=None):
@ -521,11 +634,15 @@ class Graphs(object):
'users.user_id, users.username, ' \ 'users.user_id, users.username, ' \
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \ '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \
' THEN users.username ELSE users.friendly_name END) AS 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 = "episode" AND live = 0 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 = "movie" AND live = 0 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 = "track" AND live = 0 THEN 1 ELSE 0 END) AS music_count, ' \
'SUM(live) AS live_count, ' \
'COUNT(session_history.id) AS total_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 ' \ 'JOIN users ON session_history.user_id = users.user_id ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \ 'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY session_history.user_id ' \ 'GROUP BY session_history.user_id ' \
@ -538,15 +655,19 @@ class Graphs(object):
'users.user_id, users.username, ' \ 'users.user_id, users.username, ' \
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \ '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \
' THEN users.username ELSE users.friendly_name END) AS 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, ' \ ' - (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, ' \ ' - (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, ' \ ' - (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) ' \ '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 ' \ ' - (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 ' \ 'JOIN users ON session_history.user_id = users.user_id ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \ 'WHERE (datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime")) %s' \
'GROUP BY session_history.user_id ' \ 'GROUP BY session_history.user_id ' \
@ -562,6 +683,7 @@ class Graphs(object):
series_1 = [] series_1 = []
series_2 = [] series_2 = []
series_3 = [] series_3 = []
series_4 = []
session_user_id = session.get_session_user_id() session_user_id = session.get_session_user_id()
@ -573,6 +695,7 @@ class Graphs(object):
series_1.append(item['tv_count']) series_1.append(item['tv_count'])
series_2.append(item['movie_count']) series_2.append(item['movie_count'])
series_3.append(item['music_count']) series_3.append(item['music_count'])
series_4.append(item['live_count'])
series_1_output = {'name': 'TV', series_1_output = {'name': 'TV',
'data': series_1} 'data': series_1}
@ -580,9 +703,21 @@ class Graphs(object):
'data': series_2} 'data': series_2}
series_3_output = {'name': 'Music', series_3_output = {'name': 'Music',
'data': series_3} '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, output = {'categories': categories,
'series': [series_1_output, series_2_output, series_3_output]} 'series': series_output}
return output return output
def get_total_plays_per_stream_type(self, time_range='30', y_axis='plays', user_id=None, grouping=None): def get_total_plays_per_stream_type(self, time_range='30', y_axis='plays', user_id=None, grouping=None):

View file

@ -26,6 +26,7 @@ from builtins import str
from past.builtins import basestring from past.builtins import basestring
from past.utils import old_div from past.utils import old_div
import arrow
import base64 import base64
import certifi import certifi
import cloudinary import cloudinary
@ -56,6 +57,7 @@ import sys
import tarfile import tarfile
import time import time
import unicodedata import unicodedata
import urllib
import urllib3 import urllib3
from xml.dom import minidom from xml.dom import minidom
import xmltodict import xmltodict
@ -237,6 +239,22 @@ def utc_now_iso():
return utcnow.isoformat() 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'): def human_duration(s, sig='dhms'):
hd = '' hd = ''
@ -1256,3 +1274,92 @@ def mask_config_passwords(config):
config[cfg] = ' ' config[cfg] = ' '
return config return config
def bool_true(value):
if value is True or value == 1:
return True
elif isinstance(value, basestring) and value.lower() in ('1', 'true', 't', 'yes', 'y', 'on'):
return True
return False
def page(endpoint, *args, **kwargs):
endpoints = {
'pms_image_proxy': pms_image_proxy,
'info': info_page,
'library': library_page,
'user': user_page
}
params = {}
if endpoint in endpoints:
params = endpoints[endpoint](*args, **kwargs)
return endpoint + '?' + urllib.urlencode(params)
def pms_image_proxy(img=None, rating_key=None, width=None, height=None,
opacity=None, background=None, blur=None, img_format=None,
fallback=None, refresh=None, clip=None):
params = {}
if img is not None:
params['img'] = img
if rating_key is not None:
params['rating_key'] = rating_key
if width is not None:
params['width'] = width
if height is not None:
params['height'] = height
if opacity is not None:
params['opacity'] = opacity
if background is not None:
params['background'] = background
if blur is not None:
params['blur'] = blur
if img_format is not None:
params['img_format'] = img_format
if fallback is not None:
params['fallback'] = fallback
if refresh is not None:
params['refresh'] = 'true'
if clip is not None:
params['clip'] = 'true'
return params
def info_page(rating_key=None, guid=None, history=None, live=None):
params = {}
if live and history:
params['guid'] = guid
else:
params['rating_key'] = rating_key
if history:
params['source'] = 'history'
return params
def library_page(section_id=None):
params = {}
if section_id is not None:
params['section_id'] = section_id
return params
def user_page(user_id=None, user=None):
params = {}
if user_id is not None:
params['user_id'] = user_id
if user is not None:
params['user'] = user
return params

View file

@ -96,6 +96,37 @@ def refresh_libraries():
return False 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(): def update_section_ids():
plexpy.CONFIG.UPDATE_SECTION_IDS = -1 plexpy.CONFIG.UPDATE_SECTION_IDS = -1
@ -293,6 +324,10 @@ class Libraries(object):
'session_history_metadata.parent_media_index', 'session_history_metadata.parent_media_index',
'session_history_metadata.content_rating', 'session_history_metadata.content_rating',
'session_history_metadata.labels', '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',
'library_sections.do_notify_created', 'library_sections.do_notify_created',
'library_sections.keep_history' 'library_sections.keep_history'
@ -356,6 +391,9 @@ class Libraries(object):
'parent_media_index': item['parent_media_index'], 'parent_media_index': item['parent_media_index'],
'content_rating': item['content_rating'], 'content_rating': item['content_rating'],
'labels': item['labels'].split(';') if item['labels'] else (), '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': helpers.checked(item['do_notify']),
'do_notify_created': helpers.checked(item['do_notify_created']), 'do_notify_created': helpers.checked(item['do_notify_created']),
'keep_history': helpers.checked(item['keep_history']) 'keep_history': helpers.checked(item['keep_history'])
@ -707,7 +745,7 @@ class Libraries(object):
'deleted_section': 0 'deleted_section': 0
} }
if not section_id or helpers.cast_to_int(section_id) <= 0: if not section_id:
return default_return return default_return
def get_library_details(section_id=section_id): def get_library_details(section_id=section_id):
@ -887,11 +925,11 @@ class Libraries(object):
try: try:
if str(section_id).isdigit(): 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, ' \ 'session_history.rating_key, session_history.parent_rating_key, session_history.grandparent_rating_key, ' \
'title, parent_title, grandparent_title, original_title, ' \ 'title, parent_title, grandparent_title, original_title, ' \
'thumb, parent_thumb, grandparent_thumb, media_index, parent_media_index, ' \ '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 ' \ 'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \ 'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'WHERE section_id = ? ' \ 'WHERE section_id = ? ' \
@ -925,6 +963,9 @@ class Libraries(object):
'media_index': row['media_index'], 'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'], 'parent_media_index': row['parent_media_index'],
'year': row['year'], 'year': row['year'],
'originally_available_at': row['originally_available_at'],
'live': row['live'],
'guid': row['guid'],
'time': row['started'], 'time': row['started'],
'user': row['user'], 'user': row['user'],
'section_id': row['section_id'], 'section_id': row['section_id'],

View file

@ -547,8 +547,15 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
ap = activity_processor.ActivityProcessor() ap = activity_processor.ActivityProcessor()
sessions = ap.get_sessions() sessions = ap.get_sessions()
stream_count = len(sessions)
user_sessions = ap.get_sessions(user_id=session.get('user_id')) 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) user_stream_count = len(user_sessions)
# Generate a combined transcode decision value # Generate a combined transcode decision value
@ -700,12 +707,13 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
poster_title = '' poster_title = ''
img_service = helpers.get_img_service(include_self=True) 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'): 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']} poster_info = {'poster_title': img_info['img_title'], 'poster_url': img_info['img_url']}
notify_params.update(poster_info) notify_params.update(poster_info)
elif img_service == 'self-hosted' and plexpy.CONFIG.HTTP_BASE_URL: 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_info = {'poster_title': poster_title,
'poster_url': plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'image/' + img_hash} 'poster_url': plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'image/' + img_hash}
notify_params.update(poster_info) notify_params.update(poster_info)
@ -829,6 +837,9 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'optimized_version_profile': notify_params['optimized_version_profile'], 'optimized_version_profile': notify_params['optimized_version_profile'],
'synced_version': notify_params['synced_version'], 'synced_version': notify_params['synced_version'],
'live': notify_params['live'], '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'], 'secure': 'unknown' if notify_params['secure'] is None else notify_params['secure'],
'relayed': notify_params['relayed'], 'relayed': notify_params['relayed'],
'stream_local': notify_params['local'], 'stream_local': notify_params['local'],
@ -1248,14 +1259,17 @@ def get_img_info(img=None, rating_key=None, title='', width=1000, height=1500,
return img_info return img_info
if rating_key and not img: if rating_key and not img:
if fallback == 'art': if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key) img = '/library/metadata/{}/art'.format(rating_key)
else: else:
img = '/library/metadata/{}/thumb'.format(rating_key) img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/') if img.startswith('/library/metadata'):
img = '/'.join(img_split[:5]) img_split = img.split('/')
rating_key = rating_key or img_split[3] 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() service = helpers.get_img_service()
@ -1265,7 +1279,7 @@ def get_img_info(img=None, rating_key=None, title='', width=1000, height=1500,
elif service == 'cloudinary': elif service == 'cloudinary':
if fallback == 'cover': if fallback == 'cover':
w, h = 1000, 1000 w, h = 1000, 1000
elif fallback == 'art': elif fallback and fallback.startswith('art'):
w, h = 1920, 1080 w, h = 1920, 1080
else: else:
w, h = 1000, 1500 w, h = 1000, 1500
@ -1349,14 +1363,17 @@ def set_hash_image_info(img=None, rating_key=None, width=750, height=1000,
return fallback return fallback
if rating_key and not img: if rating_key and not img:
if fallback == 'art': if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key) img = '/library/metadata/{}/art'.format(rating_key)
else: else:
img = '/library/metadata/{}/thumb'.format(rating_key) img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/') if img.startswith('/library/metadata'):
img = '/'.join(img_split[:5]) img_split = img.split('/')
rating_key = rating_key or img_split[3] 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( img_string = '{}.{}.{}.{}.{}.{}.{}.{}'.format(
plexpy.CONFIG.PMS_UUID, img, rating_key, width, height, opacity, background, blur, fallback) plexpy.CONFIG.PMS_UUID, img, rating_key, width, height, opacity, background, blur, fallback)

View file

@ -3533,7 +3533,7 @@ class WEBHOOK(Notifier):
""" """
NAME = 'Webhook' NAME = 'Webhook'
_DEFAULT_CONFIG = {'hook': '', _DEFAULT_CONFIG = {'hook': '',
'method': '' 'method': 'POST'
} }
def agent_notify(self, subject='', body='', action='', **kwargs): def agent_notify(self, subject='', body='', action='', **kwargs):
@ -3579,8 +3579,7 @@ class WEBHOOK(Notifier):
'name': 'webhook_method', 'name': 'webhook_method',
'description': 'The Webhook HTTP request method.', 'description': 'The Webhook HTTP request method.',
'input_type': 'select', 'input_type': 'select',
'select_options': {'': '', 'select_options': {'GET': 'GET',
'GET': 'GET',
'POST': 'POST', 'POST': 'POST',
'PUT': 'PUT', 'PUT': 'PUT',
'DELETE': 'DELETE'} 'DELETE': 'DELETE'}

View file

@ -581,7 +581,8 @@ class PmsConnect(object):
return output 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='',
skip_cache=False, cache_key=None, return_cache=False, media_info=True):
""" """
Return processed and validated metadata list for requested item. Return processed and validated metadata list for requested item.
@ -591,7 +592,7 @@ class PmsConnect(object):
""" """
metadata = {} metadata = {}
if cache_key: if not skip_cache and cache_key:
in_file_folder = os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata') in_file_folder = os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata')
in_file_path = os.path.join(in_file_folder, 'metadata-sessionKey-%s.json' % cache_key) in_file_path = os.path.join(in_file_folder, 'metadata-sessionKey-%s.json' % cache_key)
@ -606,14 +607,18 @@ class PmsConnect(object):
if metadata: if metadata:
_cache_time = metadata.pop('_cache_time', 0) _cache_time = metadata.pop('_cache_time', 0)
# Return cached metadata if less than METADATA_CACHE_SECONDS ago # Return cached metadata if less than cache_seconds ago
if int(time.time()) - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS: if return_cache or int(time.time()) - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS:
return metadata return metadata
if rating_key: if rating_key:
metadata_xml = self.get_metadata(str(rating_key), output_format='xml') metadata_xml = self.get_metadata(str(rating_key), output_format='xml')
elif sync_id: elif sync_id:
metadata_xml = self.get_sync_item(str(sync_id), output_format='xml') 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: else:
return metadata return metadata
@ -729,7 +734,8 @@ class PmsConnect(object):
'labels': labels, 'labels': labels,
'collections': collections, 'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'), 'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'show': elif metadata_type == 'show':
@ -781,12 +787,19 @@ class PmsConnect(object):
'labels': labels, 'labels': labels,
'collections': collections, 'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'), 'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'season': elif metadata_type == 'season':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey') 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, metadata = {'media_type': metadata_type,
'section_id': section_id, 'section_id': section_id,
'library_name': library_name, 'library_name': library_name,
@ -800,22 +813,22 @@ class PmsConnect(object):
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': show_details['studio'], 'studio': show_details.get('studio', ''),
'content_rating': show_details['content_rating'], 'content_rating': show_details.get('content_rating', ''),
'summary': show_details['summary'], 'summary': show_details.get('summary', ''),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'), 'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'), 'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'), 'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'), 'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'), 'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'), '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'), 'year': helpers.get_xml_attr(metadata_main, 'year'),
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'), 'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'), 'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'), 'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'), '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'), 'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'), 'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'), 'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -823,32 +836,38 @@ class PmsConnect(object):
'guid': helpers.get_xml_attr(metadata_main, 'guid'), 'guid': helpers.get_xml_attr(metadata_main, 'guid'),
'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'), 'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'), 'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
'directors': show_details['directors'], 'directors': show_details.get('directors', []),
'writers': show_details['writers'], 'writers': show_details.get('writers', []),
'actors': show_details['actors'], 'actors': show_details.get('actors', []),
'genres': show_details['genres'], 'genres': show_details.get('genres', []),
'labels': show_details['labels'], 'labels': show_details.get('labels', []),
'collections': show_details['collections'], 'collections': show_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'), 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
helpers.get_xml_attr(metadata_main, 'title')), helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'episode': elif metadata_type == 'episode':
grandparent_rating_key = helpers.get_xml_attr(metadata_main, 'grandparentRatingKey') 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_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
parent_media_index = helpers.get_xml_attr(metadata_main, 'parentIndex') parent_media_index = helpers.get_xml_attr(metadata_main, 'parentIndex')
parent_thumb = helpers.get_xml_attr(metadata_main, 'parentThumb') 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 # Try getting the parent_rating_key from the parent_thumb
if parent_thumb.startswith('/library/metadata/'): if parent_thumb.startswith('/library/metadata/'):
parent_rating_key = parent_thumb.split('/')[3] parent_rating_key = parent_thumb.split('/')[3]
# Try getting the parent_rating_key from the grandparent's children # 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) children_list = self.get_item_children(grandparent_rating_key)
parent_rating_key = next((c['rating_key'] for c in children_list['children_list'] parent_rating_key = next((c['rating_key'] for c in children_list['children_list']
if c['media_index'] == parent_media_index), '') if c['media_index'] == parent_media_index), '')
@ -866,7 +885,7 @@ class PmsConnect(object):
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': parent_media_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'), 'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
'summary': helpers.get_xml_attr(metadata_main, 'summary'), 'summary': helpers.get_xml_attr(metadata_main, 'summary'),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'), 'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
@ -881,7 +900,7 @@ class PmsConnect(object):
'parent_thumb': parent_thumb, 'parent_thumb': parent_thumb,
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'), 'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'), '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'), 'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'), 'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'), 'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -891,13 +910,14 @@ class PmsConnect(object):
'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'), 'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
'directors': directors, 'directors': directors,
'writers': writers, 'writers': writers,
'actors': show_details['actors'], 'actors': show_details.get('actors', []),
'genres': show_details['genres'], 'genres': show_details.get('genres', []),
'labels': show_details['labels'], 'labels': show_details.get('labels', []),
'collections': show_details['collections'], 'collections': show_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
helpers.get_xml_attr(metadata_main, 'title')), helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'artist': elif metadata_type == 'artist':
@ -944,12 +964,13 @@ class PmsConnect(object):
'labels': labels, 'labels': labels,
'collections': collections, 'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'), 'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'album': elif metadata_type == 'album':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey') 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, metadata = {'media_type': metadata_type,
'section_id': section_id, 'section_id': section_id,
'library_name': library_name, 'library_name': library_name,
@ -965,7 +986,7 @@ class PmsConnect(object):
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'),
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'), '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'), 'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'), 'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'), 'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
@ -978,7 +999,7 @@ class PmsConnect(object):
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'), 'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'), 'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'), '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'), 'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'), 'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'), 'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -994,12 +1015,13 @@ class PmsConnect(object):
'collections': collections, 'collections': collections,
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'), 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
helpers.get_xml_attr(metadata_main, 'title')), helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'track': elif metadata_type == 'track':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey') 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 \ track_artist = helpers.get_xml_attr(metadata_main, 'originalTitle') or \
helpers.get_xml_attr(metadata_main, 'grandparentTitle') helpers.get_xml_attr(metadata_main, 'grandparentTitle')
metadata = {'media_type': metadata_type, metadata = {'media_type': metadata_type,
@ -1025,12 +1047,12 @@ class PmsConnect(object):
'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'), 'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'), 'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
'duration': helpers.get_xml_attr(metadata_main, 'duration'), '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'), 'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'), 'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'), 'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'), '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'), 'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'), 'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'), 'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -1041,12 +1063,13 @@ class PmsConnect(object):
'directors': directors, 'directors': directors,
'writers': writers, 'writers': writers,
'actors': actors, 'actors': actors,
'genres': album_details['genres'], 'genres': album_details.get('genres', []),
'labels': album_details['labels'], 'labels': album_details.get('labels', []),
'collections': album_details['collections'], 'collections': album_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'title'), 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'title'),
track_artist), track_artist),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'photo_album': elif metadata_type == 'photo_album':
@ -1093,12 +1116,13 @@ class PmsConnect(object):
'labels': labels, 'labels': labels,
'collections': collections, 'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'), 'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'photo': elif metadata_type == 'photo':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey') 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, metadata = {'media_type': metadata_type,
'section_id': section_id, 'section_id': section_id,
'library_name': library_name, 'library_name': library_name,
@ -1138,12 +1162,13 @@ class PmsConnect(object):
'directors': directors, 'directors': directors,
'writers': writers, 'writers': writers,
'actors': actors, 'actors': actors,
'genres': photo_album_details.get('genres', ''), 'genres': photo_album_details.get('genres', []),
'labels': photo_album_details.get('labels', ''), 'labels': photo_album_details.get('labels', []),
'collections': photo_album_details.get('collections', ''), 'collections': photo_album_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle') or library_name, 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle') or library_name,
helpers.get_xml_attr(metadata_main, 'title')), helpers.get_xml_attr(metadata_main, 'title')),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'collection': elif metadata_type == 'collection':
@ -1194,7 +1219,8 @@ class PmsConnect(object):
'labels': labels, 'labels': labels,
'collections': collections, 'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'), 'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')) 'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
} }
elif metadata_type == 'clip': elif metadata_type == 'clip':
@ -1242,17 +1268,31 @@ class PmsConnect(object):
'collections': collections, 'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'), 'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'extra_type': helpers.get_xml_attr(metadata_main, 'extraType'), '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: else:
return metadata 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: if metadata and media_info:
medias = [] medias = []
media_items = metadata_main.getElementsByTagName('Media') media_items = metadata_main.getElementsByTagName('Media')
for media in media_items: for media in media_items:
video_full_resolution_scan_type = None video_full_resolution_scan_type = ''
parts = [] parts = []
part_items = media.getElementsByTagName('Part') part_items = media.getElementsByTagName('Part')
@ -1263,8 +1303,7 @@ class PmsConnect(object):
for stream in stream_items: for stream in stream_items:
if helpers.get_xml_attr(stream, 'streamType') == '1': if helpers.get_xml_attr(stream, 'streamType') == '1':
video_scan_type = helpers.get_xml_attr(stream, 'scanType') video_scan_type = helpers.get_xml_attr(stream, 'scanType')
if video_full_resolution_scan_type is None: video_full_resolution_scan_type = (video_full_resolution_scan_type or video_scan_type)
video_full_resolution_scan_type = video_scan_type
streams.append({'id': helpers.get_xml_attr(stream, 'id'), streams.append({'id': helpers.get_xml_attr(stream, 'id'),
'type': helpers.get_xml_attr(stream, 'streamType'), 'type': helpers.get_xml_attr(stream, 'streamType'),
@ -1324,35 +1363,36 @@ class PmsConnect(object):
'selected': int(helpers.get_xml_attr(part, 'selected') == '1') 'selected': int(helpers.get_xml_attr(part, 'selected') == '1')
}) })
video_resolution = helpers.get_xml_attr(media, 'videoResolution').lower() video_resolution = helpers.get_xml_attr(media, 'videoResolution').lower().rstrip('ip')
video_full_resolution = '' video_full_resolution = common.VIDEO_RESOLUTION_OVERRIDES.get(
if video_full_resolution_scan_type is not None: video_resolution, video_resolution + (video_full_resolution_scan_type[:1] or 'p')
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') audio_channels = helpers.get_xml_attr(media, 'audioChannels')
medias.append({'id': helpers.get_xml_attr(media, 'id'), media_info = {'id': helpers.get_xml_attr(media, 'id'),
'container': helpers.get_xml_attr(media, 'container'), 'container': helpers.get_xml_attr(media, 'container'),
'bitrate': helpers.get_xml_attr(media, 'bitrate'), 'bitrate': helpers.get_xml_attr(media, 'bitrate'),
'height': helpers.get_xml_attr(media, 'height'), 'height': helpers.get_xml_attr(media, 'height'),
'width': helpers.get_xml_attr(media, 'width'), 'width': helpers.get_xml_attr(media, 'width'),
'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'), 'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(media, 'videoCodec'), 'video_codec': helpers.get_xml_attr(media, 'videoCodec'),
'video_resolution': video_resolution, 'video_resolution': video_resolution,
'video_full_resolution': video_full_resolution, 'video_full_resolution': video_full_resolution,
'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'), 'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(media, 'videoProfile'), 'video_profile': helpers.get_xml_attr(media, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(media, 'audioCodec'), 'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
'audio_channels': audio_channels, 'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels), 'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(media, 'audioProfile'), 'audio_profile': helpers.get_xml_attr(media, 'audioProfile'),
'optimized_version': int(helpers.get_xml_attr(media, 'proxyType') == '42'), 'optimized_version': int(helpers.get_xml_attr(media, 'proxyType') == '42'),
'parts': parts '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 metadata['media_info'] = medias
@ -1474,7 +1514,7 @@ class PmsConnect(object):
return metadata_list return metadata_list
def get_current_activity(self): def get_current_activity(self, skip_cache=False):
""" """
Return processed and validated session list. Return processed and validated session list.
@ -1501,17 +1541,17 @@ class PmsConnect(object):
if a.getElementsByTagName('Track'): if a.getElementsByTagName('Track'):
session_data = a.getElementsByTagName('Track') session_data = a.getElementsByTagName('Track')
for session_ in session_data: 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.append(session_output)
if a.getElementsByTagName('Video'): if a.getElementsByTagName('Video'):
session_data = a.getElementsByTagName('Video') session_data = a.getElementsByTagName('Video')
for session_ in session_data: 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.append(session_output)
if a.getElementsByTagName('Photo'): if a.getElementsByTagName('Photo'):
session_data = a.getElementsByTagName('Photo') session_data = a.getElementsByTagName('Photo')
for session_ in session_data: 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.append(session_output)
session_list = sorted(session_list, key=lambda k: k['session_key']) session_list = sorted(session_list, key=lambda k: k['session_key'])
@ -1522,7 +1562,7 @@ class PmsConnect(object):
return output 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. Return selected data from current sessions.
This function processes and validates session data This function processes and validates session data
@ -1798,7 +1838,7 @@ class PmsConnect(object):
if helpers.cast_to_int(stream_video_width) >= 3840: if helpers.cast_to_int(stream_video_width) >= 3840:
stream_video_resolution = '4k' stream_video_resolution = '4k'
else: 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') stream_audio_channels = helpers.get_xml_attr(stream_media_info, 'audioChannels')
@ -1875,13 +1915,19 @@ class PmsConnect(object):
'full_title': helpers.get_xml_attr(session, 'title'), 'full_title': helpers.get_xml_attr(session, 'title'),
'container': helpers.get_xml_attr(stream_media_info, 'container') \ 'container': helpers.get_xml_attr(stream_media_info, 'container') \
or helpers.get_xml_attr(stream_media_parts_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'), 'height': helpers.get_xml_attr(stream_media_info, 'height'),
'width': helpers.get_xml_attr(stream_media_info, 'width'), '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_codec': helpers.get_xml_attr(stream_media_info, 'videoCodec'),
'video_resolution': helpers.get_xml_attr(stream_media_info, 'videoResolution').lower(), '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_codec': helpers.get_xml_attr(stream_media_info, 'audioCodec'),
'audio_channels': audio_channels, 'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(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_icon': helpers.get_xml_attr(session, 'sourceIcon'),
'channel_title': helpers.get_xml_attr(session, 'sourceTitle'), 'channel_title': helpers.get_xml_attr(session, 'sourceTitle'),
'extra_type': helpers.get_xml_attr(session, 'extraType'), 'extra_type': helpers.get_xml_attr(session, 'extraType'),
@ -1894,9 +1940,11 @@ class PmsConnect(object):
part_id = helpers.get_xml_attr(stream_media_parts_info, 'id') part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
if sync_id: 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,
skip_cache=skip_cache, cache_key=session_key)
else: 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,
skip_cache=skip_cache, cache_key=session_key)
# Get the media info, fallback to first item if match id is not found # Get the media info, fallback to first item if match id is not found
source_medias = metadata_details.pop('media_info', []) source_medias = metadata_details.pop('media_info', [])
@ -1983,15 +2031,15 @@ class PmsConnect(object):
# Override * in audio codecs # Override * in audio codecs
if stream_details['stream_audio_codec'] == '*': 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'] == '*': 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 # Override * in video codecs
if stream_details['stream_video_codec'] == '*': 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'] == '*': 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'): if media_type in ('movie', 'episode', 'clip'):
# Set the full resolution by combining stream_video_resolution and stream_video_scan_type # Set the full resolution by combining stream_video_resolution and stream_video_scan_type
@ -1999,13 +2047,15 @@ class PmsConnect(object):
stream_details['stream_video_resolution'], stream_details['stream_video_resolution'],
stream_details['stream_video_resolution'] + (video_details['stream_video_scan_type'][:1] or 'p')) stream_details['stream_video_resolution'] + (video_details['stream_video_scan_type'][:1] or 'p'))
if helpers.cast_to_int(source_video_details['video_bit_depth']) > 8 \ if helpers.cast_to_int(source_video_details.get('video_bit_depth')) > 8 \
and source_video_details['video_color_space'] == 'bt2020nc': and source_video_details.get('video_color_space') == 'bt2020nc':
stream_details['video_dynamic_range'] = 'HDR' stream_details['video_dynamic_range'] = 'HDR'
else: else:
stream_details['video_dynamic_range'] = 'SDR' stream_details['video_dynamic_range'] = 'SDR'
if helpers.cast_to_int(video_details['stream_video_bit_depth']) > 8 \ 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': and video_details['stream_video_color_space'] == 'bt2020nc':
stream_details['stream_video_dynamic_range'] = 'HDR' stream_details['stream_video_dynamic_range'] = 'HDR'
else: else:
@ -2040,7 +2090,7 @@ class PmsConnect(object):
if stream_details['optimized_version']: if stream_details['optimized_version']:
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate')) source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
optimized_version_profile = '{} Mbps {}'.format(round(source_bitrate / 1000.0, 1), 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: else:
optimized_version_profile = '' optimized_version_profile = ''
@ -2689,10 +2739,14 @@ class PmsConnect(object):
height = height or 1500 height = height or 1500
if img: if img:
if refresh: web_img = img.startswith('http')
if refresh and not web_img:
img = '{}/{}'.format(img.rstrip('/'), int(time.time())) img = '{}/{}'.format(img.rstrip('/'), int(time.time()))
if clip: if web_img:
params = {'url': '%s' % img}
elif clip:
params = {'url': '%s&%s' % (img, urllib.parse.urlencode({'X-Plex-Token': self.token}))} params = {'url': '%s&%s' % (img, urllib.parse.urlencode({'X-Plex-Token': self.token}))}
else: else:
params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urllib.parse.urlencode({'X-Plex-Token': self.token}))} params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urllib.parse.urlencode({'X-Plex-Token': self.token}))}

View file

@ -126,6 +126,10 @@ class Users(object):
'session_history_metadata.year', 'session_history_metadata.year',
'session_history_metadata.media_index', 'session_history_metadata.media_index',
'session_history_metadata.parent_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_media_info.transcode_decision',
'users.do_notify as do_notify', 'users.do_notify as do_notify',
'users.keep_history as keep_history', 'users.keep_history as keep_history',
@ -189,6 +193,9 @@ class Users(object):
'year': item['year'], 'year': item['year'],
'media_index': item['media_index'], 'media_index': item['media_index'],
'parent_media_index': item['parent_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'], 'transcode_decision': item['transcode_decision'],
'do_notify': helpers.checked(item['do_notify']), 'do_notify': helpers.checked(item['do_notify']),
'keep_history': helpers.checked(item['keep_history']), 'keep_history': helpers.checked(item['keep_history']),
@ -235,6 +242,10 @@ class Users(object):
'session_history_metadata.year', 'session_history_metadata.year',
'session_history_metadata.media_index', 'session_history_metadata.media_index',
'session_history_metadata.parent_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_media_info.transcode_decision',
'session_history.user', 'session_history.user',
'session_history.user_id as custom_user_id', 'session_history.user_id as custom_user_id',
@ -289,6 +300,9 @@ class Users(object):
'year': item['year'], 'year': item['year'],
'media_index': item['media_index'], 'media_index': item['media_index'],
'parent_media_index': item['parent_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'], 'transcode_decision': item['transcode_decision'],
'friendly_name': item['friendly_name'], 'friendly_name': item['friendly_name'],
'user_id': item['custom_user_id'] 'user_id': item['custom_user_id']
@ -544,11 +558,11 @@ class Users(object):
try: try:
if str(user_id).isdigit(): 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, ' \ 'session_history.rating_key, session_history.parent_rating_key, session_history.grandparent_rating_key, ' \
'title, parent_title, grandparent_title, original_title, ' \ 'title, parent_title, grandparent_title, original_title, ' \
'thumb, parent_thumb, grandparent_thumb, media_index, parent_media_index, ' \ '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 ' \ 'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \ 'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'WHERE user_id = ? ' \ 'WHERE user_id = ? ' \
@ -563,30 +577,33 @@ class Users(object):
result = [] result = []
for row in result: for row in result:
if row['media_type'] == 'episode' and row['parent_thumb']: if row['media_type'] == 'episode' and row['parent_thumb']:
thumb = row['parent_thumb'] thumb = row['parent_thumb']
elif row['media_type'] == 'episode': elif row['media_type'] == 'episode':
thumb = row['grandparent_thumb'] thumb = row['grandparent_thumb']
else: else:
thumb = row['thumb'] thumb = row['thumb']
recent_output = {'row_id': row['id'], recent_output = {'row_id': row['id'],
'media_type': row['media_type'], 'media_type': row['media_type'],
'rating_key': row['rating_key'], 'rating_key': row['rating_key'],
'parent_rating_key': row['parent_rating_key'], 'parent_rating_key': row['parent_rating_key'],
'grandparent_rating_key': row['grandparent_rating_key'], 'grandparent_rating_key': row['grandparent_rating_key'],
'title': row['title'], 'title': row['title'],
'parent_title': row['parent_title'], 'parent_title': row['parent_title'],
'grandparent_title': row['grandparent_title'], 'grandparent_title': row['grandparent_title'],
'original_title': row['original_title'], 'original_title': row['original_title'],
'thumb': thumb, 'thumb': thumb,
'media_index': row['media_index'], 'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'], 'parent_media_index': row['parent_media_index'],
'year': row['year'], 'year': row['year'],
'time': row['started'], 'originally_available_at': row['originally_available_at'],
'user': row['user'] 'live': row['live'],
} 'guid': row['guid'],
recently_watched.append(recent_output) 'time': row['started'],
'user': row['user']
}
recently_watched.append(recent_output)
return recently_watched return recently_watched

View file

@ -1,3 +1,21 @@
from __future__ import unicode_literals # -*- coding: utf-8 -*-
PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.1.42" # This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Tautulli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.2.0-beta"

View file

@ -124,20 +124,24 @@ def getVersion():
else: else:
plexpy.INSTALL_TYPE = 'source' plexpy.INSTALL_TYPE = 'docker' if plexpy.DOCKER else 'source'
version_file = os.path.join(plexpy.PROG_DIR, 'version.txt') 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): if os.path.isfile(version_file):
return None, 'origin', common.BRANCH with open(version_file, 'r') as f:
current_version = f.read().strip(' \n\r')
with open(version_file, 'r') as f:
current_version = f.read().strip(' \n\r')
if current_version:
return current_version, 'origin', common.BRANCH
else: 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): def check_update(auto_update=False, notify=False):
@ -167,13 +171,17 @@ def check_update(auto_update=False, notify=False):
def check_github(auto_update=False, notify=False): def check_github(auto_update=False, notify=False):
plexpy.COMMITS_BEHIND = 0 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 # Get the latest version available from github
logger.info('Retrieving latest version information from GitHub') logger.info('Retrieving latest version information from GitHub')
url = 'https://api.github.com/repos/%s/%s/commits/%s' % (plexpy.CONFIG.GIT_USER, url = 'https://api.github.com/repos/%s/%s/commits/%s' % (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_REPO,
plexpy.CONFIG.GIT_BRANCH) plexpy.CONFIG.GIT_BRANCH)
if plexpy.CONFIG.GIT_TOKEN: url = url + '?access_token=%s' % plexpy.CONFIG.GIT_TOKEN version = request.request_json(url, headers=headers, timeout=20, validator=lambda x: type(x) == dict)
version = request.request_json(url, timeout=20, validator=lambda x: type(x) == dict)
if version is None: if version is None:
logger.warn('Could not get the latest version from GitHub. Are you running a local development version?') logger.warn('Could not get the latest version from GitHub. Are you running a local development version?')
@ -196,8 +204,8 @@ def check_github(auto_update=False, notify=False):
plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_REPO,
plexpy.LATEST_VERSION, plexpy.LATEST_VERSION,
plexpy.CURRENT_VERSION) plexpy.CURRENT_VERSION)
if plexpy.CONFIG.GIT_TOKEN: url = url + '?access_token=%s' % plexpy.CONFIG.GIT_TOKEN commits = request.request_json(url, headers=headers, timeout=20, whitelist_status_code=404,
commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict) validator=lambda x: type(x) == dict)
if commits is None: if commits is None:
logger.warn('Could not get commits behind from GitHub.') logger.warn('Could not get commits behind from GitHub.')
@ -237,7 +245,7 @@ def check_github(auto_update=False, notify=False):
'plexpy_update_commit': plexpy.LATEST_VERSION, 'plexpy_update_commit': plexpy.LATEST_VERSION,
'plexpy_update_behind': plexpy.COMMITS_BEHIND}) 'plexpy_update_behind': plexpy.COMMITS_BEHIND})
if auto_update: if auto_update and not plexpy.DOCKER:
logger.info('Running automatic update.') logger.info('Running automatic update.')
plexpy.shutdown(restart=True, update=True) plexpy.shutdown(restart=True, update=True)
@ -252,23 +260,26 @@ def update():
logger.info('Windows .exe updating not supported yet.') logger.info('Windows .exe updating not supported yet.')
elif plexpy.INSTALL_TYPE == 'git': elif plexpy.INSTALL_TYPE == 'git':
output, err = runGit('pull ' + plexpy.CONFIG.GIT_REMOTE + ' ' + plexpy.CONFIG.GIT_BRANCH) output, err = runGit('pull {} {} --ff-only'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
if not output: if not output:
logger.error('Unable to download latest version') logger.error('Unable to download latest version')
return return
for line in output.split('\n'): for line in output.split('\n'):
if 'Already up-to-date.' in line or 'Already up to date.' in line:
if 'Already up-to-date.' in line:
logger.info('No update available, not updating') logger.info('No update available, not updating')
logger.info('Output: ' + str(output))
elif line.endswith(('Aborting', 'Aborting.')): elif line.endswith(('Aborting', 'Aborting.')):
logger.error('Unable to update from git: ' + line) logger.error('Unable to update from git: ' + line)
logger.info('Output: ' + str(output))
elif plexpy.INSTALL_TYPE == 'docker':
return
else: else:
tar_download_url = 'https://github.com/{}/{}/tarball/{}'.format(plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_BRANCH) 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') update_dir = os.path.join(plexpy.PROG_DIR, 'update')
version_path = os.path.join(plexpy.PROG_DIR, 'version.txt') version_path = os.path.join(plexpy.PROG_DIR, 'version.txt')
@ -326,6 +337,34 @@ def update():
return return
def reset():
if plexpy.INSTALL_TYPE == 'git':
logger.info('Attempting to reset git install to "%s/%s"' % (plexpy.CONFIG.GIT_REMOTE, plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('remote set-url {} https://github.com/{}/{}.git'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO))
output, err = runGit('fetch {}'.format(plexpy.CONFIG.GIT_REMOTE))
output, err = runGit('checkout {}'.format(plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('branch -u {}/{}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('reset --hard {}/{}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('pull {} {}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
if not output:
logger.error('Unable to reset Tautulli installation.')
return False
for line in output.split('\n'):
if 'Already up-to-date.' in line or 'Already up to date.' in line:
logger.info('Tautulli installation reset successfully.')
return True
elif line.endswith(('Aborting', 'Aborting.')):
logger.error('Unable to reset Tautulli installation: ' + line)
return False
def checkout_git_branch(): def checkout_git_branch():
if plexpy.INSTALL_TYPE == 'git': if plexpy.INSTALL_TYPE == 'git':
output, err = runGit('fetch %s' % plexpy.CONFIG.GIT_REMOTE) output, err = runGit('fetch %s' % plexpy.CONFIG.GIT_REMOTE)
@ -338,9 +377,10 @@ def checkout_git_branch():
for line in output.split('\n'): for line in output.split('\n'):
if line.endswith(('Aborting', 'Aborting.')): if line.endswith(('Aborting', 'Aborting.')):
logger.error('Unable to checkout from git: ' + line) logger.error('Unable to checkout from git: ' + line)
logger.info('Output: ' + str(output)) return
output, err = runGit('pull %s %s' % (plexpy.CONFIG.GIT_REMOTE, plexpy.CONFIG.GIT_BRANCH)) output, err = runGit('pull {} {}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
def read_changelog(latest_only=False, since_prev_release=False): def read_changelog(latest_only=False, since_prev_release=False):

View file

@ -288,7 +288,7 @@ class WebInterface(object):
def return_plex_xml_url(self, endpoint='', plextv=False, **kwargs): def return_plex_xml_url(self, endpoint='', plextv=False, **kwargs):
kwargs['X-Plex-Token'] = plexpy.CONFIG.PMS_TOKEN kwargs['X-Plex-Token'] = plexpy.CONFIG.PMS_TOKEN
if plextv == 'true': if helpers.bool_true(plextv):
base_url = 'https://plex.tv' base_url = 'https://plex.tv'
else: else:
if plexpy.CONFIG.PMS_URL_OVERRIDE: if plexpy.CONFIG.PMS_URL_OVERRIDE:
@ -393,15 +393,18 @@ class WebInterface(object):
"do_notify": "Checked", "do_notify": "Checked",
"do_notify_created": "Checked", "do_notify_created": "Checked",
"duration": 1578037, "duration": 1578037,
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1128, "id": 1128,
"keep_history": "Checked", "keep_history": "Checked",
"labels": [], "labels": [],
"last_accessed": 1462693216, "last_accessed": 1462693216,
"last_played": "Game of Thrones - The Red Woman", "last_played": "Game of Thrones - The Red Woman",
"library_art": "/:/resources/show-fanart.jpg", "library_art": "/:/resources/show-fanart.jpg",
"library_thumb": "", "library_thumb": "/:/resources/show.png",
"live": 0,
"media_index": 1, "media_index": 1,
"media_type": "episode", "media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_count": 240, "parent_count": 240,
"parent_media_index": 6, "parent_media_index": 6,
"parent_title": "", "parent_title": "",
@ -677,6 +680,7 @@ class WebInterface(object):
"rating_key": "1219", "rating_key": "1219",
"section_id": 2, "section_id": 2,
"section_type": "show", "section_type": "show",
"sort_title": "Game of Thrones",
"thumb": "/library/metadata/1219/thumb/1436265995", "thumb": "/library/metadata/1219/thumb/1436265995",
"title": "Game of Thrones", "title": "Game of Thrones",
"video_codec": "", "video_codec": "",
@ -712,7 +716,7 @@ class WebInterface(object):
("play_count", True, False)] ("play_count", True, False)]
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "sort_title") kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "sort_title")
if refresh == 'true': if helpers.bool_true(refresh):
refresh = True refresh = True
else: else:
refresh = False refresh = False
@ -1055,13 +1059,16 @@ class WebInterface(object):
"do_notify": "Checked", "do_notify": "Checked",
"duration": 2998290, "duration": 2998290,
"friendly_name": "Jon Snow", "friendly_name": "Jon Snow",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1121, "id": 1121,
"ip_address": "xxx.xxx.xxx.xxx", "ip_address": "xxx.xxx.xxx.xxx",
"keep_history": "Checked", "keep_history": "Checked",
"last_played": "Game of Thrones - The Red Woman", "last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869, "last_seen": 1462591869,
"live": 0,
"media_index": 1, "media_index": 1,
"media_type": "episode", "media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 6, "parent_media_index": 6,
"parent_title": "", "parent_title": "",
"platform": "Chrome", "platform": "Chrome",
@ -1267,12 +1274,15 @@ class WebInterface(object):
"recordsFiltered": 10, "recordsFiltered": 10,
"data": "data":
[{"friendly_name": "Jon Snow", [{"friendly_name": "Jon Snow",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1121, "id": 1121,
"ip_address": "xxx.xxx.xxx.xxx", "ip_address": "xxx.xxx.xxx.xxx",
"last_played": "Game of Thrones - The Red Woman", "last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869, "last_seen": 1462591869,
"live": 0,
"media_index": 1, "media_index": 1,
"media_type": "episode", "media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 6, "parent_media_index": 6,
"parent_title": "", "parent_title": "",
"platform": "Chrome", "platform": "Chrome",
@ -1607,8 +1617,9 @@ class WebInterface(object):
grandparent_rating_key (int): 351 grandparent_rating_key (int): 351
start_date (str): "YYYY-MM-DD" start_date (str): "YYYY-MM-DD"
section_id (int): 2 section_id (int): 2
media_type (str): "movie", "episode", "track" media_type (str): "movie", "episode", "track", "live"
transcode_decision (str): "direct play", "copy", "transcode", 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", order_column (str): "date", "friendly_name", "ip_address", "platform", "player",
"full_title", "started", "paused_counter", "stopped", "duration" "full_title", "started", "paused_counter", "stopped", "duration"
order_dir (str): "desc" or "asc" order_dir (str): "desc" or "asc"
@ -1633,10 +1644,13 @@ class WebInterface(object):
"original_title": "", "original_title": "",
"group_count": 1, "group_count": 1,
"group_ids": "1124", "group_ids": "1124",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"id": 1124, "id": 1124,
"ip_address": "xxx.xxx.xxx.xxx", "ip_address": "xxx.xxx.xxx.xxx",
"live": 0,
"media_index": 17, "media_index": 17,
"media_type": "episode", "media_type": "episode",
"originally_available_at": "2016-04-24",
"parent_media_index": 7, "parent_media_index": 7,
"parent_rating_key": 544, "parent_rating_key": 544,
"parent_title": "", "parent_title": "",
@ -1694,31 +1708,37 @@ class WebInterface(object):
elif user: elif user:
custom_where.append(['session_history.user', user]) custom_where.append(['session_history.user', user])
if 'rating_key' in kwargs: 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]) custom_where.append(['session_history.rating_key', rating_key])
if 'parent_rating_key' in kwargs: 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]) custom_where.append(['session_history.parent_rating_key', rating_key])
if 'grandparent_rating_key' in kwargs: 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]) custom_where.append(['session_history.grandparent_rating_key', rating_key])
if 'start_date' in kwargs: 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]) custom_where.append(['strftime("%Y-%m-%d", datetime(started, "unixepoch", "localtime"))', start_date])
if 'reference_id' in kwargs: 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]) custom_where.append(['session_history.reference_id', reference_id])
if 'section_id' in kwargs: 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]) custom_where.append(['session_history_metadata.section_id', section_id])
if 'media_type' in kwargs: if 'media_type' in kwargs:
media_type = kwargs.get('media_type', "") media_type = kwargs.get('media_type', '')
if media_type != 'all': if media_type not in ('all', 'live'):
custom_where.append(['session_history.media_type', media_type]) 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: if 'transcode_decision' in kwargs:
transcode_decision = kwargs.get('transcode_decision', "") transcode_decision = kwargs.get('transcode_decision', '')
if transcode_decision: if transcode_decision:
custom_where.append(['session_history_media_info.transcode_decision', 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() data_factory = datafactory.DataFactory()
history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping) history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping)
@ -1779,6 +1799,7 @@ class WebInterface(object):
"stream_video_bitrate": 527, "stream_video_bitrate": 527,
"stream_video_codec": "h264", "stream_video_codec": "h264",
"stream_video_decision": "transcode", "stream_video_decision": "transcode",
"stream_video_dynamic_range": "SDR",
"stream_video_framerate": "24p", "stream_video_framerate": "24p",
"stream_video_height": 306, "stream_video_height": 306,
"stream_video_resolution": "SD", "stream_video_resolution": "SD",
@ -1793,6 +1814,7 @@ class WebInterface(object):
"video_bitrate": 2500, "video_bitrate": 2500,
"video_codec": "h264", "video_codec": "h264",
"video_decision": "transcode", "video_decision": "transcode",
"video_dynamic_range": "SDR",
"video_framerate": "24p", "video_framerate": "24p",
"video_height": 816, "video_height": 816,
"video_resolution": "1080", "video_resolution": "1080",
@ -1888,7 +1910,8 @@ class WebInterface(object):
"series": "series":
[{"name": "Movies", "data": [...]} [{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]}, {"name": "TV", "data": [...]},
{"name": "Music", "data": [...]} {"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
] ]
} }
``` ```
@ -1927,7 +1950,8 @@ class WebInterface(object):
"series": "series":
[{"name": "Movies", "data": [...]} [{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]}, {"name": "TV", "data": [...]},
{"name": "Music", "data": [...]} {"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
] ]
} }
``` ```
@ -1966,7 +1990,8 @@ class WebInterface(object):
"series": "series":
[{"name": "Movies", "data": [...]} [{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]}, {"name": "TV", "data": [...]},
{"name": "Music", "data": [...]} {"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
] ]
} }
``` ```
@ -2005,7 +2030,8 @@ class WebInterface(object):
"series": "series":
[{"name": "Movies", "data": [...]} [{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]}, {"name": "TV", "data": [...]},
{"name": "Music", "data": [...]} {"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
] ]
} }
``` ```
@ -2044,7 +2070,8 @@ class WebInterface(object):
"series": "series":
[{"name": "Movies", "data": [...]} [{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]}, {"name": "TV", "data": [...]},
{"name": "Music", "data": [...]} {"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
] ]
} }
``` ```
@ -2083,7 +2110,8 @@ class WebInterface(object):
"series": "series":
[{"name": "Movies", "data": [...]} [{"name": "Movies", "data": [...]}
{"name": "TV", "data": [...]}, {"name": "TV", "data": [...]},
{"name": "Music", "data": [...]} {"name": "Music", "data": [...]},
{"name": "Live TV", "data": [...]}
] ]
} }
``` ```
@ -3076,7 +3104,7 @@ class WebInterface(object):
def install_geoip_db(self, update=False, **kwargs): def install_geoip_db(self, update=False, **kwargs):
""" Downloads and installs the GeoLite2 database """ """ 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) result = helpers.install_geoip_db(update=update)
@ -3482,7 +3510,7 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def verify_mobile_device(self, device_token='', cancel=False, **kwargs): def verify_mobile_device(self, device_token='', cancel=False, **kwargs):
if cancel == 'true': if helpers.bool_true(cancel):
mobile_app.TEMP_DEVICE_TOKEN = None mobile_app.TEMP_DEVICE_TOKEN = None
return {'result': 'error', 'message': 'Device registration cancelled.'} return {'result': 'error', 'message': 'Device registration cancelled.'}
@ -3647,7 +3675,7 @@ class WebInterface(object):
if not username and not password: if not username and not password:
return None return None
force = True if force == 'true' else False force = helpers.bool_true(force)
plex_tv = plextv.PlexTV(username=username, password=password) plex_tv = plextv.PlexTV(username=username, password=password)
token = plex_tv.get_plexpy_pms_token(force=force) token = plex_tv.get_plexpy_pms_token(force=force)
@ -3710,7 +3738,7 @@ class WebInterface(object):
result = {'identifier': identifier} result = {'identifier': identifier}
if identifier: if identifier:
if get_url == 'true': if helpers.bool_true(get_url):
server = self.get_server_resources(pms_ip=hostname, server = self.get_server_resources(pms_ip=hostname,
pms_port=port, pms_port=port,
pms_ssl=ssl, pms_ssl=ssl,
@ -3720,7 +3748,7 @@ class WebInterface(object):
result['url'] = server['pms_url'] result['url'] = server['pms_url']
result['ws'] = None result['ws'] = None
if test_websocket == 'true': if helpers.bool_true(test_websocket):
# Quick test websocket connection # Quick test websocket connection
ws_url = result['url'].replace('http', 'ws', 1) + '/:/websockets/notifications' ws_url = result['url'].replace('http', 'ws', 1) + '/:/websockets/notifications'
header = ['X-Plex-Token: %s' % plexpy.CONFIG.PMS_TOKEN] header = ['X-Plex-Token: %s' % plexpy.CONFIG.PMS_TOKEN]
@ -3774,7 +3802,7 @@ class WebInterface(object):
logger.info("New API key generated.") logger.info("New API key generated.")
logger._BLACKLIST_WORDS.add(apikey) logger._BLACKLIST_WORDS.add(apikey)
if device == 'true': if helpers.bool_true(device):
mobile_app.TEMP_DEVICE_TOKEN = apikey mobile_app.TEMP_DEVICE_TOKEN = apikey
return apikey return apikey
@ -3804,46 +3832,51 @@ class WebInterface(object):
versioncheck.check_update() versioncheck.check_update()
if plexpy.UPDATE_AVAILABLE is None: if plexpy.UPDATE_AVAILABLE is None:
return {'result': 'error', update = {'result': 'error',
'update': None, 'update': None,
'message': 'You are running an unknown version of Tautulli.' 'message': 'You are running an unknown version of Tautulli.'
} }
elif plexpy.UPDATE_AVAILABLE == 'release': elif plexpy.UPDATE_AVAILABLE == 'release':
return {'result': 'success', update = {'result': 'success',
'update': True, 'update': True,
'release': True, 'release': True,
'message': 'A new release (%s) of Tautulli is available.' % plexpy.LATEST_RELEASE, 'message': 'A new release (%s) of Tautulli is available.' % plexpy.LATEST_RELEASE,
'current_release': plexpy.common.RELEASE, 'current_release': plexpy.common.RELEASE,
'latest_release': plexpy.LATEST_RELEASE, 'latest_release': plexpy.LATEST_RELEASE,
'release_url': helpers.anon_url( 'release_url': helpers.anon_url(
'https://github.com/%s/%s/releases/tag/%s' 'https://github.com/%s/%s/releases/tag/%s'
% (plexpy.CONFIG.GIT_USER, % (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_REPO,
plexpy.LATEST_RELEASE)) plexpy.LATEST_RELEASE))
} }
elif plexpy.UPDATE_AVAILABLE == 'commit': elif plexpy.UPDATE_AVAILABLE == 'commit':
return {'result': 'success', update = {'result': 'success',
'update': True, 'update': True,
'release': False, 'release': False,
'message': 'A newer version of Tautulli is available.', 'message': 'A newer version of Tautulli is available.',
'current_version': plexpy.CURRENT_VERSION, 'current_version': plexpy.CURRENT_VERSION,
'latest_version': plexpy.LATEST_VERSION, 'latest_version': plexpy.LATEST_VERSION,
'commits_behind': plexpy.COMMITS_BEHIND, 'commits_behind': plexpy.COMMITS_BEHIND,
'compare_url': helpers.anon_url( 'compare_url': helpers.anon_url(
'https://github.com/%s/%s/compare/%s...%s' 'https://github.com/%s/%s/compare/%s...%s'
% (plexpy.CONFIG.GIT_USER, % (plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_REPO,
plexpy.CURRENT_VERSION, plexpy.CURRENT_VERSION,
plexpy.LATEST_VERSION)) plexpy.LATEST_VERSION))
} }
else: else:
return {'result': 'success', update = {'result': 'success',
'update': False, 'update': False,
'message': 'Tautulli is up to date.' 'message': 'Tautulli is up to date.'
} }
if plexpy.DOCKER:
update['docker'] = plexpy.DOCKER
return update
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@ -3873,6 +3906,9 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def update(self, **kwargs): def update(self, **kwargs):
if plexpy.DOCKER:
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
# Show changelog after updating # Show changelog after updating
plexpy.CONFIG.__setattr__('UPDATE_SHOW_CHANGELOG', 1) plexpy.CONFIG.__setattr__('UPDATE_SHOW_CHANGELOG', 1)
plexpy.CONFIG.write() plexpy.CONFIG.write()
@ -3891,18 +3927,30 @@ class WebInterface(object):
plexpy.CONFIG.write() plexpy.CONFIG.write()
return self.do_state_change('checkout', 'Switching Git Branches', 120) return self.do_state_change('checkout', 'Switching Git Branches', 120)
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def reset_git_install(self, **kwargs):
result = versioncheck.reset()
if result:
return {'result': 'success', 'message': 'Tautulli installation reset.'}
else:
return {'result': 'error', 'message': 'Reset installation failed.'}
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def get_changelog(self, latest_only=False, since_prev_release=False, update_shown=False, **kwargs): def get_changelog(self, latest_only=False, since_prev_release=False, update_shown=False, **kwargs):
latest_only = (latest_only == 'true') latest_only = helpers.bool_true(latest_only)
since_prev_release = (since_prev_release == 'true') since_prev_release = helpers.bool_true(since_prev_release)
if since_prev_release and plexpy.PREV_RELEASE == common.RELEASE: if since_prev_release and plexpy.PREV_RELEASE == common.RELEASE:
latest_only = True latest_only = True
since_prev_release = False since_prev_release = False
# Set update changelog shown status # Set update changelog shown status
if update_shown == 'true': if helpers.bool_true(update_shown):
plexpy.CONFIG.__setattr__('UPDATE_SHOW_CHANGELOG', 0) plexpy.CONFIG.__setattr__('UPDATE_SHOW_CHANGELOG', 0)
plexpy.CONFIG.write() plexpy.CONFIG.write()
@ -3912,7 +3960,7 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth() @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(): if rating_key and not str(rating_key).isdigit():
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
@ -3925,7 +3973,7 @@ class WebInterface(object):
if source == 'history': if source == 'history':
data_factory = datafactory.DataFactory() 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: if metadata:
poster_info = data_factory.get_poster_info(metadata=metadata) poster_info = data_factory.get_poster_info(metadata=metadata)
metadata.update(poster_info) metadata.update(poster_info)
@ -3945,12 +3993,12 @@ class WebInterface(object):
if metadata['section_id'] and not allow_session_library(metadata['section_id']): if metadata['section_id'] and not allow_session_library(metadata['section_id']):
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) 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: else:
if get_session_user_id(): if get_session_user_id():
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
else: else:
return self.update_metadata(rating_key, query) return self.update_metadata(rating_key)
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
@ -4046,10 +4094,10 @@ class WebInterface(object):
width (str): 300 width (str): 300
height (str): 450 height (str): 450
opacity (str): 25 opacity (str): 25
background (str): 282828 background (str): Hex color, e.g. 282828
blur (str): 3 blur (str): 3
img_format (str): png 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 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 return_hash (bool): True or False to return the self-hosted image hash instead of the image
@ -4058,20 +4106,27 @@ class WebInterface(object):
``` ```
""" """
if not img and not rating_key: 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.') logger.warn('No image input received.')
return return
return_hash = (kwargs.get('return_hash') == 'true') return_hash = helpers.bool_true(kwargs.get('return_hash'))
if rating_key and not img: if rating_key and not img:
if fallback == 'art': if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key) img = '/library/metadata/{}/art'.format(rating_key)
else: else:
img = '/library/metadata/{}/thumb'.format(rating_key) img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/') if img.startswith('/library/metadata'):
img = '/'.join(img_split[:5]) img_split = img.split('/')
rating_key = rating_key or img_split[3] 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_hash = notification_handler.set_hash_image_info(
img=img, rating_key=rating_key, width=width, height=height, img=img, rating_key=rating_key, width=width, height=height,
@ -4088,7 +4143,7 @@ class WebInterface(object):
if not os.path.exists(c_dir): if not os.path.exists(c_dir):
os.mkdir(c_dir) os.mkdir(c_dir)
clip = True if clip == 'true' else False clip = helpers.bool_true(clip)
try: try:
if not plexpy.CONFIG.CACHE_IMAGES or refresh or 'indexes' in img: if not plexpy.CONFIG.CACHE_IMAGES or refresh or 'indexes' in img:
@ -4121,16 +4176,9 @@ class WebInterface(object):
raise Exception('PMS image request failed') raise Exception('PMS image request failed')
except Exception as e: except Exception as e:
logger.warn('Failed to get image %s, falling back to %s.' % (img, fallback)) logger.warn(u'Failed to get image %s, falling back to %s.' % (img, fallback))
fbi = None if fallback in common.DEFAULT_IMAGES:
if fallback == 'poster': fbi = common.DEFAULT_IMAGES[fallback]
fbi = common.DEFAULT_POSTER_THUMB
elif fallback == 'cover':
fbi = common.DEFAULT_COVER_THUMB
elif fallback == 'art':
fbi = common.DEFAULT_ART
if fbi:
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi) fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
return serve_file(path=fp, content_type='image/png') return serve_file(path=fp, content_type='image/png')
@ -4148,14 +4196,8 @@ class WebInterface(object):
img_hash = args[0].split('.')[0] img_hash = args[0].split('.')[0]
if img_hash in ('poster', 'cover', 'art'): if img_hash in common.DEFAULT_IMAGES:
if img_hash == 'poster': fbi = common.DEFAULT_IMAGES[img_hash]
fbi = common.DEFAULT_POSTER_THUMB
elif img_hash == 'cover':
fbi = common.DEFAULT_COVER_THUMB
elif img_hash == 'art':
fbi = common.DEFAULT_ART
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi) fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
return serve_file(path=fp, content_type='image/png') return serve_file(path=fp, content_type='image/png')
@ -4304,7 +4346,7 @@ class WebInterface(object):
``` ```
""" """
delete_all = (delete_all == 'true') delete_all = helpers.bool_true(delete_all)
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
result = data_factory.delete_img_info(rating_key=rating_key, service=service, delete_all=delete_all) result = data_factory.delete_img_info(rating_key=rating_key, service=service, delete_all=delete_all)
@ -4417,7 +4459,7 @@ class WebInterface(object):
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def update_metadata(self, rating_key=None, query=None, update=False, **kwargs): def update_metadata(self, rating_key=None, query=None, update=False, **kwargs):
query_string = query query_string = query
update = True if update == 'True' else False update = helpers.bool_true(update)
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
query = data_factory.get_search_query(rating_key=rating_key) query = data_factory.get_search_query(rating_key=rating_key)
@ -4590,6 +4632,7 @@ class WebInterface(object):
"labels": [], "labels": [],
"last_viewed_at": "1462165717", "last_viewed_at": "1462165717",
"library_name": "TV Shows", "library_name": "TV Shows",
"live": 0,
"media_index": "1", "media_index": "1",
"media_info": [ "media_info": [
{ {
@ -4599,6 +4642,9 @@ class WebInterface(object):
"audio_codec": "ac3", "audio_codec": "ac3",
"audio_profile": "", "audio_profile": "",
"bitrate": "10617", "bitrate": "10617",
"channel_call_sign": "",
"channel_identifier": "",
"channel_thumb": "",
"container": "mkv", "container": "mkv",
"height": "1078", "height": "1078",
"id": "257925", "id": "257925",
@ -4617,6 +4663,10 @@ class WebInterface(object):
"video_bitrate": "10233", "video_bitrate": "10233",
"video_codec": "h264", "video_codec": "h264",
"video_codec_level": "41", "video_codec_level": "41",
"video_color_primaries": "",
"video_color_range": "tv",
"video_color_space": "bt709",
"video_color_trc": "",
"video_frame_rate": "23.976", "video_frame_rate": "23.976",
"video_height": "1078", "video_height": "1078",
"video_language": "", "video_language": "",
@ -4676,7 +4726,7 @@ class WebInterface(object):
"rating_image": "rottentomatoes://image.rating.ripe", "rating_image": "rottentomatoes://image.rating.ripe",
"rating_key": "153037", "rating_key": "153037",
"section_id": "2", "section_id": "2",
"sort_title": "Game of Thrones", "sort_title": "Red Woman",
"studio": "HBO", "studio": "HBO",
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.", "summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
"tagline": "", "tagline": "",
@ -4719,22 +4769,59 @@ class WebInterface(object):
Returns: Returns:
json: json:
{"recently_added": {"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_rating_key": "1219",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"grandparent_title": "Game of Thrones", "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_index": "1",
"media_type": "episode", "media_type": "episode",
"original_title": "", "original_title": "",
"originally_available_at": "2016-04-24",
"parent_media_index": "6", "parent_media_index": "6",
"parent_rating_key": "153036", "parent_rating_key": "153036",
"parent_thumb": "/library/metadata/153036/thumb/1462175062", "parent_thumb": "/library/metadata/153036/thumb/1462175062",
"parent_title": "", "parent_title": "",
"rating": "7.8",
"rating_image": "rottentomatoes://image.rating.ripe",
"rating_key": "153037", "rating_key": "153037",
"section_id": "2", "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", "thumb": "/library/metadata/153037/thumb/1462175060",
"title": "The Red Woman", "title": "The Red Woman",
"user_rating": "9.0",
"updated_at": "1462175060",
"writers": [
"David Benioff",
"D. B. Weiss"
],
"year": "2016" "year": "2016"
}, },
{...}, {...},
@ -4957,7 +5044,11 @@ class WebInterface(object):
"banner": "/library/metadata/1219/banner/1503306930", "banner": "/library/metadata/1219/banner/1503306930",
"bif_thumb": "/library/parts/274169/indexes/sd/1000", "bif_thumb": "/library/parts/274169/indexes/sd/1000",
"bitrate": "10617", "bitrate": "10617",
"channel_call_sign": "",
"channel_identifier": "",
"channel_stream": 0, "channel_stream": 0,
"channel_thumb": "",
"children_count": "",
"collections": [], "collections": [],
"container": "mkv", "container": "mkv",
"content_rating": "TV-MA", "content_rating": "TV-MA",
@ -4989,13 +5080,15 @@ class WebInterface(object):
"ip_address": "10.10.10.1", "ip_address": "10.10.10.1",
"ip_address_public": "64.123.23.111", "ip_address_public": "64.123.23.111",
"is_admin": 1, "is_admin": 1,
"is_allow_sync": null, "is_allow_sync": 1,
"is_home_user": 1, "is_home_user": 1,
"is_restricted": 0, "is_restricted": 0,
"keep_history": 1, "keep_history": 1,
"labels": [], "labels": [],
"last_viewed_at": "1462165717", "last_viewed_at": "1462165717",
"library_name": "TV Shows", "library_name": "TV Shows",
"live": 0,
"live_uuid": "",
"local": "1", "local": "1",
"location": "lan", "location": "lan",
"machine_id": "lmd93nkn12k29j2lnm", "machine_id": "lmd93nkn12k29j2lnm",
@ -5004,8 +5097,8 @@ class WebInterface(object):
"optimized_version": 0, "optimized_version": 0,
"optimized_version_profile": "", "optimized_version_profile": "",
"optimized_version_title": "", "optimized_version_title": "",
"originally_available_at": "2016-04-24",
"original_title": "", "original_title": "",
"originally_available_at": "2016-04-24",
"parent_guid": "com.plexapp.agents.thetvdb://121361/6?lang=en", "parent_guid": "com.plexapp.agents.thetvdb://121361/6?lang=en",
"parent_media_index": "6", "parent_media_index": "6",
"parent_rating_key": "153036", "parent_rating_key": "153036",
@ -5025,6 +5118,7 @@ class WebInterface(object):
"rating_key": "153037", "rating_key": "153037",
"relay": 0, "relay": 0,
"section_id": "2", "section_id": "2",
"secure": 1,
"session_id": "helf15l3rxgw01xxe0jf3l3d", "session_id": "helf15l3rxgw01xxe0jf3l3d",
"session_key": "27", "session_key": "27",
"shared_libraries": [ "shared_libraries": [
@ -5063,15 +5157,21 @@ class WebInterface(object):
"stream_subtitle_location": "", "stream_subtitle_location": "",
"stream_video_bit_depth": "8", "stream_video_bit_depth": "8",
"stream_video_bitrate": "10233", "stream_video_bitrate": "10233",
"stream_video_chroma_subsampling": "4:2:0",
"stream_video_codec": "h264", "stream_video_codec": "h264",
"stream_video_codec_level": "41", "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_decision": "direct play",
"stream_video_dynamic_range": "SDR",
"stream_video_framerate": "24p", "stream_video_framerate": "24p",
"stream_video_full_resolution": "1080p",
"stream_video_height": "1078", "stream_video_height": "1078",
"stream_video_language": "", "stream_video_language": "",
"stream_video_language_code": "", "stream_video_language_code": "",
"stream_video_ref_frames": "4", "stream_video_ref_frames": "4",
"stream_video_full_resolution": "1080p",
"stream_video_resolution": "1080", "stream_video_resolution": "1080",
"stream_video_scan_type": "progressive", "stream_video_scan_type": "progressive",
"stream_video_width": "1920", "stream_video_width": "1920",
@ -5121,9 +5221,15 @@ class WebInterface(object):
"username": "LordCommanderSnow", "username": "LordCommanderSnow",
"video_bit_depth": "8", "video_bit_depth": "8",
"video_bitrate": "10233", "video_bitrate": "10233",
"video_chroma_subsampling": "4:2:0",
"video_codec": "h264", "video_codec": "h264",
"video_codec_level": "41", "video_codec_level": "41",
"video_color_primaries": "",
"video_color_range": "tv",
"video_color_space": "bt709",
"video_color_trc": ",
"video_decision": "direct play", "video_decision": "direct play",
"video_dynamic_range": "SDR",
"video_frame_rate": "23.976", "video_frame_rate": "23.976",
"video_framerate": "24p", "video_framerate": "24p",
"video_full_resolution": "1080p", "video_full_resolution": "1080p",
@ -5377,8 +5483,10 @@ class WebInterface(object):
[{"content_rating": "TV-MA", [{"content_rating": "TV-MA",
"friendly_name": "", "friendly_name": "",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"labels": [], "labels": [],
"last_play": 1462380698, "last_play": 1462380698,
"live": 0,
"media_type": "episode", "media_type": "episode",
"platform": "", "platform": "",
"platform_type": "", "platform_type": "",
@ -5884,8 +5992,8 @@ class WebInterface(object):
subject=newsletter['subject'], subject=newsletter['subject'],
body=newsletter['body'], body=newsletter['body'],
message=newsletter['message']) message=newsletter['message'])
preview = (preview == 'true') preview = helpers.bool_true(preview)
raw = (raw == 'true') raw = helpers.bool_true(raw)
if raw: if raw:
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8' cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'