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

View file

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

31
Dockerfile Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

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

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -217,7 +217,7 @@
<div id="git_update_options">
<div class="checkbox">
<label>
<input type="checkbox" id="plexpy_auto_update" name="plexpy_auto_update" value="1" ${config['plexpy_auto_update']}> Update Automatically
<input type="checkbox" id="plexpy_auto_update" name="plexpy_auto_update" value="1" ${config['plexpy_auto_update']} ${docker_setting}> Update Automatically ${docker_msg | n}
</label>
<p class="help-block">Update Tautulli automatically if an update is available.</p>
</div>
@ -265,6 +265,19 @@
</div>
<p class="help-block">Optional: The path to your git environment variable. Leave blank for default.</p>
</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
<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() });
$("#generate_api").click(function() {
$.get('generate_api_key',
@ -2531,6 +2551,7 @@ $(document).ready(function() {
for (var i in libraries_list) {
var title = libraries_list[i].section_name;
var key = libraries_list[i].section_id;
if (key === 999999) { continue; } // Don't show Live TV library
$('#sortable_home_library_cards').append(
'<li class="card card-sortable">' +
'<div class="card-handle"><i class="fa fa-bars"></i></div>' +
@ -2868,11 +2889,13 @@ $(document).ready(function() {
}
$("#install_geoip_db").click(function () {
if ($.trim($("#maxmind_license_key").val()) === "") {
$("#maxmind_license_key").focus();
var maxmind_license_key = $("#maxmind_license_key");
maxmind_license_key.val($.trim(maxmind_license_key.val()));
if (maxmind_license_key.val() === "") {
maxmind_license_key.focus();
showMsg('<i class="fa fa-exclamation-circle"></i> Maxmind License Key is required.', false, true, 5000, true);
return false;
} else if (!(saveSettings())){
} else if (!(saveSettings())) {
return false;
}
var msg = 'Are you sure you want to install the GeoLite2 database?<br /><br />' +

View file

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

View file

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

View file

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

View file

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

View file

@ -590,6 +590,7 @@ def dbcheck():
'media_index INTEGER, parent_media_index INTEGER, '
'thumb TEXT, parent_thumb TEXT, grandparent_thumb TEXT, year INTEGER, '
'parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
'originally_available_at TEXT, added_at INTEGER, guid TEXT, '
'view_offset INTEGER DEFAULT 0, duration INTEGER, video_decision TEXT, audio_decision TEXT, '
'transcode_decision TEXT, container TEXT, bitrate INTEGER, width INTEGER, height INTEGER, '
'video_codec TEXT, video_bitrate INTEGER, video_resolution TEXT, video_width INTEGER, video_height INTEGER, '
@ -609,7 +610,8 @@ def dbcheck():
'transcode_hw_decoding INTEGER, transcode_hw_encoding INTEGER, '
'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT, '
'synced_version INTEGER, synced_version_profile TEXT, '
'live INTEGER, live_uuid TEXT, secure INTEGER, relayed INTEGER, '
'live INTEGER, live_uuid TEXT, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT, '
'secure INTEGER, relayed INTEGER, '
'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, watched INTEGER DEFAULT 0, '
'write_attempts INTEGER DEFAULT 0, raw_stream_info TEXT)'
)
@ -657,7 +659,7 @@ def dbcheck():
'art TEXT, media_type TEXT, year INTEGER, originally_available_at TEXT, added_at INTEGER, updated_at INTEGER, '
'last_viewed_at INTEGER, content_rating TEXT, summary TEXT, tagline TEXT, rating TEXT, '
'duration INTEGER DEFAULT 0, guid TEXT, directors TEXT, writers TEXT, actors TEXT, genres TEXT, studio TEXT, '
'labels TEXT)'
'labels TEXT, live INTEGER DEFAULT 0, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT)'
)
# users table :: This table keeps record of the friends list
@ -1225,6 +1227,42 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN stream_video_dynamic_range TEXT'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT channel_identifier FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN channel_call_sign TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN channel_identifier TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN channel_thumb TEXT'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT originally_available_at FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN originally_available_at TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN added_at INTEGER'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT guid FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN guid TEXT'
)
# Upgrade session_history table from earlier versions
try:
c_db.execute('SELECT reference_id FROM session_history')
@ -1332,6 +1370,24 @@ def dbcheck():
'ALTER TABLE session_history_metadata ADD COLUMN original_title TEXT'
)
# Upgrade session_history_metadata table from earlier versions
try:
c_db.execute('SELECT live FROM session_history_metadata')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table session_history_metadata.")
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN live INTEGER DEFAULT 0'
)
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN channel_call_sign TEXT'
)
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN channel_identifier TEXT'
)
c_db.execute(
'ALTER TABLE session_history_metadata ADD COLUMN channel_thumb TEXT'
)
# Upgrade session_history_media_info table from earlier versions
try:
c_db.execute('SELECT transcode_decision FROM session_history_media_info')
@ -1574,6 +1630,15 @@ def dbcheck():
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN stream_video_dynamic_range TEXT '
)
result = c_db.execute('SELECT * FROM session_history_media_info '
'WHERE video_dynamic_range = "SDR" AND stream_video_dynamic_range = "HDR"').fetchone()
if result:
c_db.execute(
'UPDATE session_history_media_info SET stream_video_dynamic_range = "SDR" '
'WHERE video_dynamic_range = "SDR" AND stream_video_dynamic_range = "HDR"'
)
# Upgrade users table from earlier versions
try:
c_db.execute('SELECT do_notify FROM users')

View file

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

View file

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

View file

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

View file

@ -41,11 +41,26 @@ DEFAULT_USER_THUMB = "interfaces/default/images/gravatar-default-80x80.png"
DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png"
DEFAULT_ART = "interfaces/default/images/art.png"
DEFAULT_LIVE_TV_POSTER_THUMB = "interfaces/default/images/poster-live.png"
DEFAULT_LIVE_TV_ART = "interfaces/default/images/art-live.png"
DEFAULT_LIVE_TV_ART_FULL = "interfaces/default/images/art-live-full.png"
ONLINE_POSTER_THUMB = "https://tautulli.com/images/poster.png"
ONLINE_COVER_THUMB = "https://tautulli.com/images/cover.png"
ONLINE_ART = "https://tautulli.com/images/art.png"
LIVE_TV_SECTION_ID = 999999 # Fake section_id for Live TV library
LIVE_TV_SECTION_NAME = "Live TV" # Fake section_name for Live TV library
DEFAULT_IMAGES = {
'poster': DEFAULT_POSTER_THUMB,
'cover': DEFAULT_COVER_THUMB,
'art': DEFAULT_ART,
'poster-live': DEFAULT_LIVE_TV_POSTER_THUMB,
'art-live': DEFAULT_LIVE_TV_ART,
'art-live-full': DEFAULT_LIVE_TV_ART_FULL
}
MEDIA_TYPE_HEADERS = {
'movie': 'Movies',
'show': 'TV Shows',
@ -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': 'Synced Version', 'type': 'int', 'value': 'synced_version', 'description': 'If the stream is an synced version.', 'example': '0 or 1'},
{'name': 'Live', 'type': 'int', 'value': 'live', 'description': 'If the stream is live TV.', 'example': '0 or 1'},
{'name': 'Channel Call Sign', 'type': 'str', 'value': 'channel_call_sign', 'description': 'The Live TV channel call sign.'},
{'name': 'Channel Identifier', 'type': 'str', 'value': 'channel_identifier', 'description': 'The Live TV channel number.'},
{'name': 'Channel Thumb', 'type': 'str', 'value': 'channel_thumb', 'description': 'The URL for the Live TV channel logo.'},
{'name': 'Secure', 'type': 'int', 'value': 'secure', 'description': 'If the stream is using a secure connection.', 'example': '0 or 1'},
{'name': 'Relayed', 'type': 'int', 'value': 'relayed', 'description': 'If the stream is using Plex Relay.', 'example': '0 or 1'},
{'name': 'Stream Local', 'type': 'int', 'value': 'stream_local', 'description': 'If the stream is local.', 'example': '0 or 1'},
@ -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 Bit Depth', 'type': 'int', 'value': 'stream_video_bit_depth', 'description': 'The video bit depth of the stream.'},
{'name': 'Stream Video Chroma Subsampling', 'type': 'str', 'value': 'stream_video_chroma_subsampling', 'description': 'The video chroma subsampling of the stream.'},
{'name': 'Stream Video Color Primaries', 'type': 'srt', '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 Primaries', 'type': 'str', 'value': 'stream_video_color_primaries', 'description': 'The video color primaries of the stream.'},
{'name': 'Stream Video Color Range', 'type': 'str', 'value': 'stream_video_color_range', 'description': 'The video color range of the stream.'},
{'name': 'Stream Video Color Space', 'type': 'str', 'value': 'stream_video_color_space', 'description': 'The video color space of the stream.'},
{'name': 'Stream Video Color Transfer Function', 'type': 'str', 'value': 'stream_video_color_trc', 'description': 'The video transfer function of the stream.'},
{'name': 'Stream Video Dynamic Range', 'type': 'str', 'value': 'stream_video_dynamic_range', 'description': 'The video dynamic range of the stream.', 'example': 'HDR or SDR'},
@ -484,8 +502,8 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Video Bitrate', 'type': 'int', 'value': 'video_bitrate', 'description': 'The video bitrate of the original media.'},
{'name': 'Video Bit Depth', 'type': 'int', 'value': 'video_bit_depth', 'description': 'The video bit depth of the original media.'},
{'name': 'Video Chroma Subsampling', 'type': 'str', 'value': 'video_chroma_subsampling', 'description': 'The video chroma subsampling of the original media.'},
{'name': 'Video Color Primaries', 'type': 'srt', '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 Primaries', 'type': 'str', 'value': 'video_color_primaries', 'description': 'The video color primaries of the original media.'},
{'name': 'Video Color Range', 'type': 'str', 'value': 'video_color_range', 'description': 'The video color range of the original media.'},
{'name': 'Video Color Space', 'type': 'str', 'value': 'video_color_space', 'description': 'The video color space of the original media.'},
{'name': 'Video Color Transfer Function', 'type': 'str', 'value': 'video_color_trc', 'description': 'The video transfer function of the original media.'},
{'name': 'Video Dynamic Range', 'type': 'str', 'value': 'video_dynamic_range', 'description': 'The video dynamic range of the original media.', 'example': 'HDR or SDR'},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -547,8 +547,15 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
ap = activity_processor.ActivityProcessor()
sessions = ap.get_sessions()
stream_count = len(sessions)
user_sessions = ap.get_sessions(user_id=session.get('user_id'))
# Filter out the session_key from the database sessions for playback stopped events
# to prevent race condition between the database and notifications
if notify_action == 'on_stop':
sessions = [s for s in sessions if str(s['session_key']) != notify_params['session_key']]
user_sessions = [s for s in user_sessions if str(s['session_key']) != notify_params['session_key']]
stream_count = len(sessions)
user_stream_count = len(user_sessions)
# Generate a combined transcode decision value
@ -700,12 +707,13 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
poster_title = ''
img_service = helpers.get_img_service(include_self=True)
fallback = 'poster-live' if notify_params['live'] else 'poster'
if img_service not in (None, 'self-hosted'):
img_info = get_img_info(img=poster_thumb, rating_key=poster_key, title=poster_title, fallback='poster')
img_info = get_img_info(img=poster_thumb, rating_key=poster_key, title=poster_title, fallback=fallback)
poster_info = {'poster_title': img_info['img_title'], 'poster_url': img_info['img_url']}
notify_params.update(poster_info)
elif img_service == 'self-hosted' and plexpy.CONFIG.HTTP_BASE_URL:
img_hash = set_hash_image_info(img=poster_thumb, fallback='poster')
img_hash = set_hash_image_info(img=poster_thumb, fallback=fallback)
poster_info = {'poster_title': poster_title,
'poster_url': plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'image/' + img_hash}
notify_params.update(poster_info)
@ -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'],
'synced_version': notify_params['synced_version'],
'live': notify_params['live'],
'channel_call_sign': notify_params['channel_call_sign'],
'channel_identifier': notify_params['channel_identifier'],
'channel_thumb': notify_params['channel_thumb'],
'secure': 'unknown' if notify_params['secure'] is None else notify_params['secure'],
'relayed': notify_params['relayed'],
'stream_local': notify_params['local'],
@ -1248,14 +1259,17 @@ def get_img_info(img=None, rating_key=None, title='', width=1000, height=1500,
return img_info
if rating_key and not img:
if fallback == 'art':
if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key)
else:
img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/')
img = '/'.join(img_split[:5])
rating_key = rating_key or img_split[3]
if img.startswith('/library/metadata'):
img_split = img.split('/')
img = '/'.join(img_split[:5])
img_rating_key = img_split[3]
if rating_key != img_rating_key:
rating_key = img_rating_key
service = helpers.get_img_service()
@ -1265,7 +1279,7 @@ def get_img_info(img=None, rating_key=None, title='', width=1000, height=1500,
elif service == 'cloudinary':
if fallback == 'cover':
w, h = 1000, 1000
elif fallback == 'art':
elif fallback and fallback.startswith('art'):
w, h = 1920, 1080
else:
w, h = 1000, 1500
@ -1349,14 +1363,17 @@ def set_hash_image_info(img=None, rating_key=None, width=750, height=1000,
return fallback
if rating_key and not img:
if fallback == 'art':
if fallback and fallback.startswith('art'):
img = '/library/metadata/{}/art'.format(rating_key)
else:
img = '/library/metadata/{}/thumb'.format(rating_key)
img_split = img.split('/')
img = '/'.join(img_split[:5])
rating_key = rating_key or img_split[3]
if img.startswith('/library/metadata'):
img_split = img.split('/')
img = '/'.join(img_split[:5])
img_rating_key = img_split[3]
if rating_key != img_rating_key:
rating_key = img_rating_key
img_string = '{}.{}.{}.{}.{}.{}.{}.{}'.format(
plexpy.CONFIG.PMS_UUID, img, rating_key, width, height, opacity, background, blur, fallback)

View file

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

View file

@ -581,7 +581,8 @@ class PmsConnect(object):
return output
def get_metadata_details(self, rating_key='', sync_id='', cache_key=None, media_info=True):
def get_metadata_details(self, rating_key='', sync_id='', plex_guid='',
skip_cache=False, cache_key=None, return_cache=False, media_info=True):
"""
Return processed and validated metadata list for requested item.
@ -591,7 +592,7 @@ class PmsConnect(object):
"""
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_path = os.path.join(in_file_folder, 'metadata-sessionKey-%s.json' % cache_key)
@ -606,14 +607,18 @@ class PmsConnect(object):
if metadata:
_cache_time = metadata.pop('_cache_time', 0)
# Return cached metadata if less than METADATA_CACHE_SECONDS ago
if int(time.time()) - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS:
# Return cached metadata if less than cache_seconds ago
if return_cache or int(time.time()) - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS:
return metadata
if rating_key:
metadata_xml = self.get_metadata(str(rating_key), output_format='xml')
elif sync_id:
metadata_xml = self.get_sync_item(str(sync_id), output_format='xml')
elif plex_guid.startswith(('plex://movie', 'plex://episode')):
rating_key = plex_guid.rsplit('/', 1)[-1]
plextv_metadata = PmsConnect(url='https://metadata.provider.plex.tv', token=plexpy.CONFIG.PMS_TOKEN)
metadata_xml = plextv_metadata.get_metadata(rating_key, output_format='xml')
else:
return metadata
@ -729,7 +734,8 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'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':
@ -781,12 +787,19 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'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':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
show_details = self.get_metadata_details(parent_rating_key)
parent_guid = helpers.get_xml_attr(metadata_main, 'parentGuid')
show_details = {}
if plex_guid and parent_guid:
show_details = self.get_metadata_details(plex_guid=parent_guid)
elif not plex_guid and parent_rating_key:
show_details = self.get_metadata_details(parent_rating_key)
metadata = {'media_type': metadata_type,
'section_id': section_id,
'library_name': library_name,
@ -800,22 +813,22 @@ class PmsConnect(object):
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': show_details['studio'],
'content_rating': show_details['content_rating'],
'summary': show_details['summary'],
'studio': show_details.get('studio', ''),
'content_rating': show_details.get('content_rating', ''),
'summary': show_details.get('summary', ''),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
'duration': show_details['duration'],
'duration': show_details.get('duration', ''),
'year': helpers.get_xml_attr(metadata_main, 'year'),
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': show_details['banner'],
'banner': show_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -823,32 +836,38 @@ class PmsConnect(object):
'guid': helpers.get_xml_attr(metadata_main, 'guid'),
'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
'directors': show_details['directors'],
'writers': show_details['writers'],
'actors': show_details['actors'],
'genres': show_details['genres'],
'labels': show_details['labels'],
'collections': show_details['collections'],
'directors': show_details.get('directors', []),
'writers': show_details.get('writers', []),
'actors': show_details.get('actors', []),
'genres': show_details.get('genres', []),
'labels': show_details.get('labels', []),
'collections': show_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
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':
grandparent_rating_key = helpers.get_xml_attr(metadata_main, 'grandparentRatingKey')
show_details = self.get_metadata_details(grandparent_rating_key)
grandparent_guid = helpers.get_xml_attr(metadata_main, 'grandparentGuid')
show_details = {}
if plex_guid and grandparent_guid:
show_details = self.get_metadata_details(plex_guid=grandparent_guid)
elif not plex_guid and grandparent_rating_key:
show_details = self.get_metadata_details(grandparent_rating_key)
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
parent_media_index = helpers.get_xml_attr(metadata_main, 'parentIndex')
parent_thumb = helpers.get_xml_attr(metadata_main, 'parentThumb')
if not parent_rating_key:
if not plex_guid and not parent_rating_key:
# Try getting the parent_rating_key from the parent_thumb
if parent_thumb.startswith('/library/metadata/'):
parent_rating_key = parent_thumb.split('/')[3]
# Try getting the parent_rating_key from the grandparent's children
if not parent_rating_key:
if not parent_rating_key and grandparent_rating_key:
children_list = self.get_item_children(grandparent_rating_key)
parent_rating_key = next((c['rating_key'] for c in children_list['children_list']
if c['media_index'] == parent_media_index), '')
@ -866,7 +885,7 @@ class PmsConnect(object):
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': parent_media_index,
'studio': show_details['studio'],
'studio': show_details.get('studio', ''),
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
'summary': helpers.get_xml_attr(metadata_main, 'summary'),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
@ -881,7 +900,7 @@ class PmsConnect(object):
'parent_thumb': parent_thumb,
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': show_details['banner'],
'banner': show_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -891,13 +910,14 @@ class PmsConnect(object):
'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
'directors': directors,
'writers': writers,
'actors': show_details['actors'],
'genres': show_details['genres'],
'labels': show_details['labels'],
'collections': show_details['collections'],
'actors': show_details.get('actors', []),
'genres': show_details.get('genres', []),
'labels': show_details.get('labels', []),
'collections': show_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
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':
@ -944,12 +964,13 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'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':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
artist_details = self.get_metadata_details(parent_rating_key)
artist_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
metadata = {'media_type': metadata_type,
'section_id': section_id,
'library_name': library_name,
@ -965,7 +986,7 @@ class PmsConnect(object):
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'),
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
'summary': helpers.get_xml_attr(metadata_main, 'summary') or artist_details['summary'],
'summary': helpers.get_xml_attr(metadata_main, 'summary') or artist_details.get('summary', ''),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
@ -978,7 +999,7 @@ class PmsConnect(object):
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': artist_details['banner'],
'banner': artist_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -994,12 +1015,13 @@ class PmsConnect(object):
'collections': collections,
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
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':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
album_details = self.get_metadata_details(parent_rating_key)
album_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
track_artist = helpers.get_xml_attr(metadata_main, 'originalTitle') or \
helpers.get_xml_attr(metadata_main, 'grandparentTitle')
metadata = {'media_type': metadata_type,
@ -1025,12 +1047,12 @@ class PmsConnect(object):
'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
'duration': helpers.get_xml_attr(metadata_main, 'duration'),
'year': album_details['year'],
'year': album_details.get('year', ''),
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': album_details['banner'],
'banner': album_details.get('banner', ''),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
@ -1041,12 +1063,13 @@ class PmsConnect(object):
'directors': directors,
'writers': writers,
'actors': actors,
'genres': album_details['genres'],
'labels': album_details['labels'],
'collections': album_details['collections'],
'genres': album_details.get('genres', []),
'labels': album_details.get('labels', []),
'collections': album_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'title'),
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':
@ -1093,12 +1116,13 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'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':
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
photo_album_details = self.get_metadata_details(parent_rating_key)
photo_album_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
metadata = {'media_type': metadata_type,
'section_id': section_id,
'library_name': library_name,
@ -1138,12 +1162,13 @@ class PmsConnect(object):
'directors': directors,
'writers': writers,
'actors': actors,
'genres': photo_album_details.get('genres', ''),
'labels': photo_album_details.get('labels', ''),
'collections': photo_album_details.get('collections', ''),
'genres': photo_album_details.get('genres', []),
'labels': photo_album_details.get('labels', []),
'collections': photo_album_details.get('collections', []),
'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle') or library_name,
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':
@ -1194,7 +1219,8 @@ class PmsConnect(object):
'labels': labels,
'collections': collections,
'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':
@ -1242,17 +1268,31 @@ class PmsConnect(object):
'collections': collections,
'full_title': helpers.get_xml_attr(metadata_main, 'title'),
'extra_type': helpers.get_xml_attr(metadata_main, 'extraType'),
'sub_type': helpers.get_xml_attr(metadata_main, 'subtype')
'sub_type': helpers.get_xml_attr(metadata_main, 'subtype'),
'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
}
else:
return metadata
# Get additional metadata from metadata.provider.plex.tv
if not plex_guid and metadata['live']:
metadata['section_id'] = common.LIVE_TV_SECTION_ID
metadata['library_name'] = common.LIVE_TV_SECTION_NAME
plextv_metadata = self.get_metadata_details(plex_guid=metadata['guid'])
if plextv_metadata:
keys_to_update = ['summary', 'rating', 'thumb', 'grandparent_thumb', 'duration',
'guid', 'grandparent_guid', 'genres']
for key in keys_to_update:
metadata[key] = plextv_metadata[key]
metadata['originally_available_at'] = helpers.iso_to_YMD(plextv_metadata['originally_available_at'])
if metadata and media_info:
medias = []
media_items = metadata_main.getElementsByTagName('Media')
for media in media_items:
video_full_resolution_scan_type = None
video_full_resolution_scan_type = ''
parts = []
part_items = media.getElementsByTagName('Part')
@ -1263,8 +1303,7 @@ class PmsConnect(object):
for stream in stream_items:
if helpers.get_xml_attr(stream, 'streamType') == '1':
video_scan_type = helpers.get_xml_attr(stream, 'scanType')
if video_full_resolution_scan_type is None:
video_full_resolution_scan_type = video_scan_type
video_full_resolution_scan_type = (video_full_resolution_scan_type or video_scan_type)
streams.append({'id': helpers.get_xml_attr(stream, 'id'),
'type': helpers.get_xml_attr(stream, 'streamType'),
@ -1324,35 +1363,36 @@ class PmsConnect(object):
'selected': int(helpers.get_xml_attr(part, 'selected') == '1')
})
video_resolution = helpers.get_xml_attr(media, 'videoResolution').lower()
video_full_resolution = ''
if video_full_resolution_scan_type is not None:
video_full_resolution = common.VIDEO_RESOLUTION_OVERRIDES.get(
video_resolution, video_resolution + (video_full_resolution_scan_type[:1] or 'p')
)
video_resolution = helpers.get_xml_attr(media, 'videoResolution').lower().rstrip('ip')
video_full_resolution = common.VIDEO_RESOLUTION_OVERRIDES.get(
video_resolution, video_resolution + (video_full_resolution_scan_type[:1] or 'p')
)
audio_channels = helpers.get_xml_attr(media, 'audioChannels')
medias.append({'id': helpers.get_xml_attr(media, 'id'),
'container': helpers.get_xml_attr(media, 'container'),
'bitrate': helpers.get_xml_attr(media, 'bitrate'),
'height': helpers.get_xml_attr(media, 'height'),
'width': helpers.get_xml_attr(media, 'width'),
'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(media, 'videoCodec'),
'video_resolution': video_resolution,
'video_full_resolution': video_full_resolution,
'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(media, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(media, 'audioProfile'),
'optimized_version': int(helpers.get_xml_attr(media, 'proxyType') == '42'),
'parts': parts
})
media_info = {'id': helpers.get_xml_attr(media, 'id'),
'container': helpers.get_xml_attr(media, 'container'),
'bitrate': helpers.get_xml_attr(media, 'bitrate'),
'height': helpers.get_xml_attr(media, 'height'),
'width': helpers.get_xml_attr(media, 'width'),
'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(media, 'videoCodec'),
'video_resolution': video_resolution,
'video_full_resolution': video_full_resolution,
'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(media, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(media, 'audioProfile'),
'optimized_version': int(helpers.get_xml_attr(media, 'proxyType') == '42'),
'channel_call_sign': helpers.get_xml_attr(media, 'channelCallSign'),
'channel_identifier': helpers.get_xml_attr(media, 'channelIdentifier'),
'channel_thumb': helpers.get_xml_attr(media, 'channelThumb'),
'parts': parts
}
video_full_resolution = helpers.get_xml_attr(media, 'videoResolution').lower()
medias.append(media_info)
metadata['media_info'] = medias
@ -1474,7 +1514,7 @@ class PmsConnect(object):
return metadata_list
def get_current_activity(self):
def get_current_activity(self, skip_cache=False):
"""
Return processed and validated session list.
@ -1501,17 +1541,17 @@ class PmsConnect(object):
if a.getElementsByTagName('Track'):
session_data = a.getElementsByTagName('Track')
for session_ in session_data:
session_output = self.get_session_each(session_)
session_output = self.get_session_each(session_, skip_cache=skip_cache)
session_list.append(session_output)
if a.getElementsByTagName('Video'):
session_data = a.getElementsByTagName('Video')
for session_ in session_data:
session_output = self.get_session_each(session_)
session_output = self.get_session_each(session_, skip_cache=skip_cache)
session_list.append(session_output)
if a.getElementsByTagName('Photo'):
session_data = a.getElementsByTagName('Photo')
for session_ in session_data:
session_output = self.get_session_each(session_)
session_output = self.get_session_each(session_, skip_cache=skip_cache)
session_list.append(session_output)
session_list = sorted(session_list, key=lambda k: k['session_key'])
@ -1522,7 +1562,7 @@ class PmsConnect(object):
return output
def get_session_each(self, session=None):
def get_session_each(self, session=None, skip_cache=False):
"""
Return selected data from current sessions.
This function processes and validates session data
@ -1798,7 +1838,7 @@ class PmsConnect(object):
if helpers.cast_to_int(stream_video_width) >= 3840:
stream_video_resolution = '4k'
else:
stream_video_resolution = helpers.get_xml_attr(stream_media_info, 'videoResolution').rstrip('p').lower()
stream_video_resolution = helpers.get_xml_attr(stream_media_info, 'videoResolution').lower().rstrip('ip')
stream_audio_channels = helpers.get_xml_attr(stream_media_info, 'audioChannels')
@ -1875,13 +1915,19 @@ class PmsConnect(object):
'full_title': helpers.get_xml_attr(session, 'title'),
'container': helpers.get_xml_attr(stream_media_info, 'container') \
or helpers.get_xml_attr(stream_media_parts_info, 'container'),
'bitrate': helpers.get_xml_attr(stream_media_info, 'bitrate'),
'height': helpers.get_xml_attr(stream_media_info, 'height'),
'width': helpers.get_xml_attr(stream_media_info, 'width'),
'aspect_ratio': helpers.get_xml_attr(stream_media_info, 'aspectRatio'),
'video_codec': helpers.get_xml_attr(stream_media_info, 'videoCodec'),
'video_resolution': helpers.get_xml_attr(stream_media_info, 'videoResolution').lower(),
'video_full_resolution': helpers.get_xml_attr(stream_media_info, 'videoResolution').lower(),
'video_framerate': helpers.get_xml_attr(stream_media_info, 'videoFrameRate'),
'video_profile': helpers.get_xml_attr(stream_media_info, 'videoProfile'),
'audio_codec': helpers.get_xml_attr(stream_media_info, 'audioCodec'),
'audio_channels': audio_channels,
'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
'audio_profile': helpers.get_xml_attr(stream_media_info, 'audioProfile'),
'channel_icon': helpers.get_xml_attr(session, 'sourceIcon'),
'channel_title': helpers.get_xml_attr(session, 'sourceTitle'),
'extra_type': helpers.get_xml_attr(session, 'extraType'),
@ -1894,9 +1940,11 @@ class PmsConnect(object):
part_id = helpers.get_xml_attr(stream_media_parts_info, '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:
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
source_medias = metadata_details.pop('media_info', [])
@ -1983,15 +2031,15 @@ class PmsConnect(object):
# Override * in audio codecs
if stream_details['stream_audio_codec'] == '*':
stream_details['stream_audio_codec'] = source_audio_details['audio_codec']
stream_details['stream_audio_codec'] = source_audio_details.get('audio_codec', '')
if transcode_details['transcode_audio_codec'] == '*':
transcode_details['transcode_audio_codec'] = source_audio_details['audio_codec']
transcode_details['transcode_audio_codec'] = source_audio_details.get('audio_codec', '')
# Override * in video codecs
if stream_details['stream_video_codec'] == '*':
stream_details['stream_video_codec'] = source_video_details['video_codec']
stream_details['stream_video_codec'] = source_video_details.get('video_codec', '')
if transcode_details['transcode_video_codec'] == '*':
transcode_details['transcode_video_codec'] = source_video_details['video_codec']
transcode_details['transcode_video_codec'] = source_video_details.get('video_codec', '')
if media_type in ('movie', 'episode', 'clip'):
# Set the full resolution by combining stream_video_resolution and stream_video_scan_type
@ -1999,13 +2047,15 @@ class PmsConnect(object):
stream_details['stream_video_resolution'],
stream_details['stream_video_resolution'] + (video_details['stream_video_scan_type'][:1] or 'p'))
if helpers.cast_to_int(source_video_details['video_bit_depth']) > 8 \
and source_video_details['video_color_space'] == 'bt2020nc':
if helpers.cast_to_int(source_video_details.get('video_bit_depth')) > 8 \
and source_video_details.get('video_color_space') == 'bt2020nc':
stream_details['video_dynamic_range'] = 'HDR'
else:
stream_details['video_dynamic_range'] = 'SDR'
if 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':
stream_details['stream_video_dynamic_range'] = 'HDR'
else:
@ -2040,7 +2090,7 @@ class PmsConnect(object):
if stream_details['optimized_version']:
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
optimized_version_profile = '{} Mbps {}'.format(round(source_bitrate / 1000.0, 1),
source_media_details['video_full_resolution'])
source_media_details.get('video_full_resolution'))
else:
optimized_version_profile = ''
@ -2689,10 +2739,14 @@ class PmsConnect(object):
height = height or 1500
if img:
if refresh:
web_img = img.startswith('http')
if refresh and not web_img:
img = '{}/{}'.format(img.rstrip('/'), int(time.time()))
if clip:
if web_img:
params = {'url': '%s' % img}
elif clip:
params = {'url': '%s&%s' % (img, urllib.parse.urlencode({'X-Plex-Token': self.token}))}
else:
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.media_index',
'session_history_metadata.parent_media_index',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'session_history_media_info.transcode_decision',
'users.do_notify as do_notify',
'users.keep_history as keep_history',
@ -189,6 +193,9 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'live': item['live'],
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'transcode_decision': item['transcode_decision'],
'do_notify': helpers.checked(item['do_notify']),
'keep_history': helpers.checked(item['keep_history']),
@ -235,6 +242,10 @@ class Users(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_metadata.live',
'session_history_metadata.added_at',
'session_history_metadata.originally_available_at',
'session_history_metadata.guid',
'session_history_media_info.transcode_decision',
'session_history.user',
'session_history.user_id as custom_user_id',
@ -289,6 +300,9 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'live': item['live'],
'originally_available_at': item['originally_available_at'],
'guid': item['guid'],
'transcode_decision': item['transcode_decision'],
'friendly_name': item['friendly_name'],
'user_id': item['custom_user_id']
@ -544,11 +558,11 @@ class Users(object):
try:
if str(user_id).isdigit():
query = 'SELECT session_history.id, session_history.media_type, ' \
query = 'SELECT session_history.id, session_history.media_type, guid, ' \
'session_history.rating_key, session_history.parent_rating_key, session_history.grandparent_rating_key, ' \
'title, parent_title, grandparent_title, original_title, ' \
'thumb, parent_thumb, grandparent_thumb, media_index, parent_media_index, ' \
'year, started, user ' \
'year, originally_available_at, added_at, live, started, user ' \
'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'WHERE user_id = ? ' \
@ -563,30 +577,33 @@ class Users(object):
result = []
for row in result:
if row['media_type'] == 'episode' and row['parent_thumb']:
thumb = row['parent_thumb']
elif row['media_type'] == 'episode':
thumb = row['grandparent_thumb']
else:
thumb = row['thumb']
if row['media_type'] == 'episode' and row['parent_thumb']:
thumb = row['parent_thumb']
elif row['media_type'] == 'episode':
thumb = row['grandparent_thumb']
else:
thumb = row['thumb']
recent_output = {'row_id': row['id'],
'media_type': row['media_type'],
'rating_key': row['rating_key'],
'parent_rating_key': row['parent_rating_key'],
'grandparent_rating_key': row['grandparent_rating_key'],
'title': row['title'],
'parent_title': row['parent_title'],
'grandparent_title': row['grandparent_title'],
'original_title': row['original_title'],
'thumb': thumb,
'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'],
'year': row['year'],
'time': row['started'],
'user': row['user']
}
recently_watched.append(recent_output)
recent_output = {'row_id': row['id'],
'media_type': row['media_type'],
'rating_key': row['rating_key'],
'parent_rating_key': row['parent_rating_key'],
'grandparent_rating_key': row['grandparent_rating_key'],
'title': row['title'],
'parent_title': row['parent_title'],
'grandparent_title': row['grandparent_title'],
'original_title': row['original_title'],
'thumb': thumb,
'media_index': row['media_index'],
'parent_media_index': row['parent_media_index'],
'year': row['year'],
'originally_available_at': row['originally_available_at'],
'live': row['live'],
'guid': row['guid'],
'time': row['started'],
'user': row['user']
}
recently_watched.append(recent_output)
return recently_watched

View file

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

View file

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