Compare commits

...

44 Commits

Author SHA1 Message Date
JonnyWong16
e908c45cf2 v2.0.4-beta 2017-12-29 16:35:24 -08:00
JonnyWong16
c37e934f42 Check concurrent streams from the server instead of database 2017-12-29 16:29:20 -08:00
JonnyWong16
41f91956b8 Make sure current activity is loaded before refreshing 2017-12-29 16:26:50 -08:00
JonnyWong16
8b2bd5ce79 Add collections info pages 2017-12-29 15:52:08 -08:00
JonnyWong16
28e4151157 Remember tab when going back on library and user pages 2017-12-29 11:34:41 -08:00
JonnyWong16
66d45293e6 Add test Plex Web URL button 2017-12-29 10:47:31 -08:00
JonnyWong16
243eeeff67 v2.0.3-beta 2017-12-25 09:58:24 -08:00
JonnyWong16
18520e24d1 Report correct release version on beta 2017-12-25 09:48:33 -08:00
JonnyWong16
87743171b7 Fix write session history optimized_version_title column name 2017-12-25 09:07:02 -08:00
JonnyWong16
452f0747c8 Add missing stream info modal css 2017-12-25 09:02:33 -08:00
JonnyWong16
a20747044c Fix sync ID 2017-12-25 09:00:42 -08:00
JonnyWong16
e60241ca73 v2.0.2-beta 2017-12-24 13:53:49 -08:00
JonnyWong16
c69e68a4a8 Alias media info title to sort title 2017-12-24 11:29:12 -08:00
JonnyWong16
561d994328 Fix config version updgrade 2017-12-24 07:52:11 -08:00
JonnyWong16
33acf9402d Improved synced and optimized version info 2017-12-23 11:52:28 -08:00
JonnyWong16
0a42cb4135 Update stream info modal 2017-12-22 22:04:40 -08:00
JonnyWong16
19695c220b Fix some missing subtitle database columns 2017-12-22 19:55:21 -08:00
JonnyWong16
1fbc0165e6 Fix source video/audio/subtitle parameters in notifications 2017-12-22 14:22:37 -08:00
JonnyWong16
4dd05e217a Add back HTTP Proxy setting 2017-12-22 13:01:25 -08:00
JonnyWong16
b77a409414 Add sort title to metadata, sort media info tables by sort title 2017-12-22 12:40:58 -08:00
JonnyWong16
b2157df026 Default blank session value when writing session history 2017-12-22 11:47:28 -08:00
JonnyWong16
0b8d5954f9 Fix librariaes page 2017-12-22 10:30:45 -08:00
JonnyWong16
0d60c7b728 Remove unused logos 2017-12-21 15:30:24 -08:00
JonnyWong16
de58136314 Do not send recently added notification if item updated more than 24 hours ago 2017-12-21 15:22:18 -08:00
JonnyWong16
4e718056b6 Remove bif thumbnail setting 2017-12-21 11:23:27 -08:00
JonnyWong16
480913362e Fix default setup wizard settings 2017-12-21 11:23:11 -08:00
JonnyWong16
ad2f61132a Change logos to svgs 2017-12-21 10:49:14 -08:00
JonnyWong16
0e2df4ba14 Add note to enable 3rd party APIs for link lookup 2017-12-21 10:47:32 -08:00
JonnyWong16
c342273742 Retrieve session by session key 2017-12-19 22:20:24 -08:00
JonnyWong16
0cb147cd8f Cleanup safari-pinned-tab svg 2017-12-19 21:49:09 -08:00
JonnyWong16
11b2a17fb3 Fix white in logo text 2017-12-19 21:36:11 -08:00
JonnyWong16
23486ff235 Update favicons 2017-12-19 21:29:07 -08:00
JonnyWong16
a9e24bc0c5 Remove update section ids 2017-12-19 20:36:24 -08:00
JonnyWong16
3c22496ea2 Fix some platform mappings 2017-12-19 20:28:40 -08:00
JonnyWong16
b8164ca556 Validate condition logic on save 2017-12-19 17:32:37 -08:00
JonnyWong16
19565b3d0a Add message to verify server for error communicating 2017-12-19 17:27:20 -08:00
JonnyWong16
50ce17ac72 Fix missing users table cache parameter 2017-12-19 16:40:45 -08:00
JonnyWong16
e51a425389 API enabled by default 2017-12-19 11:47:59 -08:00
JonnyWong16
3df7642193 Missing save button in settings 2017-12-19 11:21:07 -08:00
JonnyWong16
38ad9c9893 v2.0.1-beta 2017-12-19 10:56:25 -08:00
JonnyWong16
fb75b9566f Temporarily disable browser notifications 2017-12-19 10:33:42 -08:00
JonnyWong16
80a6118bf9 Fix music stats background fallback 2017-12-19 09:42:11 -08:00
JonnyWong16
6b2bc0108b Fix Join apikey 2017-12-18 23:09:50 -08:00
JonnyWong16
bd07cf5a04 Missing video_height database column 2017-12-18 13:51:14 -08:00
68 changed files with 1971 additions and 1048 deletions

View File

@@ -1,5 +1,64 @@
# Changelog
## v2.0.4-beta (2017-12-29)
* Monitoring:
* Fix: Current activity cards duplicating on the homepage.
* Notifications:
* Fix: Concurrent stream notifications being sent when there is an incorrect number of streams.
* UI:
* New: Info pages for collections.
* New: Button to test Plex Web URL override.
* Fix: Library and User pages return to the correct tab when pressing back.
## v2.0.3-beta (2017-12-25)
* Monitoring:
* Fix: Missing sync ID error causing logging to crash.
* Fix: Incorrect optimized version title column name causing logging to crash.
* Notifications:
* Fix: Report correct beta version for Tautulli update notifications.
* UI:
* Fix: Missing CSS for stream info modal.
## v2.0.2-beta (2017-12-24)
* Monitoring:
* Fix: Websocket connection fails to start with existing streams when upgrading to v2.
* Fix: Long request URI for refreshing current activity on the homepage.
* Fix: Missing subtitle database columns.
* Fix: Details for synced and optimized versions reporting incorrectly.
* Notifications:
* Fix: Recently added notifications sending for previously added items. It is now limited to past 24 hours only.
* Fix: Source video/audio/subtitle parameters showing up as blank.
* Change: Validate condition logic when saving a notification agent.
* API:
* Change: API is enabled by default on new installs.
* UI:
* New: Add logo svg files. (Thanks @Fish2)
* New: Updated stream info modal.
* Change: Media info tables sort by sort title instead of title.
* Other:
* Fix: Updating library IDs message on libraries page.
* Fix: Wtched percentage settings not saving after restart.
* Remove: Video Preview Thumbnails setting no longer used.
* Change: Add back HTTP Proxy setting under the Web Interface settings tab.
* Change: "Group Table and Watch Statistics History" and "Current Activity in History Tables" enabled by default on new installs.
## v2.0.1-beta (2017-12-19)
* Monitoring:
* Fix: Missing video_height database column.
* Notifications:
* Fix: Join API key.
* Change: Temporarily disable broken browser notifications.
* UI:
* Fix: Incorrect fallback image for music watch statistics.
## v2.0.0-beta (2017-12-18)
* Monitoring:

View File

@@ -20,28 +20,26 @@
${next.headIncludes()}
<!-- Favicons -->
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.0">
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.0">
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.0">
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.1">
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.1">
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.1">
<!-- ICONS -->
<!-- Android >M39 icon -->
<!-- Android -->
<link rel="icon" type="image/png" sizes="192x192" href="${http_root}images/favicon/android-chrome-192x192.png?v=2.0.0">
<link rel="manifest" href="${http_root}json/Android-manifest.json?v=2.0.0">
<meta name="theme-color" content="#1f1f1f">
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.1">
<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.0">
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.0" color="#1f1f1f">
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.1">
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.1" color="#282a2d">
<meta name="apple-mobile-web-app-title" content="Tautulli">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="initial-scale=1">
<meta name="format-detection" content="telephone=no">
<!-- IE10 icon -->
<!-- Microsoft -->
<meta name="application-name" content="Tautulli">
<meta name="msapplication-TileColor" content="#1f1f1f">
<meta name="msapplication-TileImage" content="${http_root}images/favicon/mstile-144x144.png?v=2.0.0">
<meta name="msapplication-config" content="${http_root}xml/IEconfig.xml?v=2.0.0" />
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.1">
</head>
<body class="content">
@@ -72,7 +70,7 @@
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="home">
<img alt="Tautulli" src="${http_root}images/logo-tautulli-50.png" height="40">
<img alt="Tautulli" src="${http_root}images/logo-tautulli.svg" height="45">
</a>
</div>
<div class="collapse navbar-collapse navbar-right" id="navbar-collapse-1">
@@ -289,10 +287,6 @@ ${next.modalIncludes()}
<script src="${http_root}js/pnotify.custom.min.js"></script>
<script src="${http_root}js/script.js${cache_param}"></script>
<script src="${http_root}js/jquery.qrcode.min.js"></script>
% if _session['user_group'] == 'admin' and plexpy.CONFIG.BROWSER_ENABLED:
<script src="${http_root}js/ajaxNotifications.js"></script>
% else:
% endif
<script>
% if _session['user_group'] == 'admin':
$('#updateDismiss').click(function() {

View File

@@ -113,7 +113,7 @@ img {
box-shadow: 0 0 0 3px rgba(0,0,0,.2);
}
.navbar-brand {
padding: 5px 5px;
padding: 3px 3px;
}
.nav > li > a {
color: #999;
@@ -899,15 +899,15 @@ a .users-poster-face:hover {
height: 249px;
}
.dashboard-activity-container:hover .dashboard-activity-progress {
height: 14px;
height: 14px;
}
.dashboard-activity-container:hover .progress-bar {
.dashboard-activity-container:hover .progress-bar {
color: rgba(255, 255, 255, 1);
background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px);
background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
}
.dashboard-activity-container:hover .buffer-bar {
.dashboard-activity-container:hover .buffer-bar {
color: rgba(255, 255, 255, 1);
background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px);
background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
@@ -1808,7 +1808,8 @@ a:hover .summary-poster-face-track .summary-poster-face-overlay span {
margin-left: 2px;
color: #999;
}
#children-list, #search-results-list {
.children-list,
.search-results-list {
position: relative;
z-index: 0;
}
@@ -1874,15 +1875,15 @@ a:hover .item-children-poster {
box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
z-index: -2;
}
.item-children-poster-face.season-poster {
.item-children-poster-face.poster-item {
width: 150px;
height: 225px;
}
.item-children-poster-face.episode-poster {
.item-children-poster-face.episode-item {
width: 250px;
height: 140px;
}
.item-children-poster-face.album-poster {
.item-children-poster-face.cover-item {
width: 150px;
height: 150px;
}
@@ -1915,15 +1916,13 @@ a:hover .item-children-poster {
margin-bottom: 20px;
clear: both;
}
.item-children-instance-text-wrapper.season-item {
.item-children-instance-text-wrapper.poster-item,
.item-children-instance-text-wrapper.cover-item {
width: 150px;
}
.item-children-instance-text-wrapper.episode-item {
width: 250px;
}
.item-children-instance-text-wrapper.album-item {
width: 150px;
}
.item-children-instance-text-wrapper h3 {
width: 100%;
padding: 5px 3px 0 3px;
@@ -3155,8 +3154,8 @@ pre::-webkit-scrollbar-thumb {
background-color: rgba(0,0,0,.15);
}
@media only screen
and (min-device-width: 300px)
@media only screen
and (min-device-width: 300px)
and (max-device-width: 450px) {
.home-platforms-instance {
width: calc(100% - 20px);
@@ -3578,6 +3577,10 @@ a:hover .overlay-refresh-image:hover {
background-color: #10a4e8;
background-image: url(../images/platforms/chromecast.svg);
}
.platform-default {
background-color: #e5a00d;
background-image: url(../images/platforms/default.svg);
}
.platform-dlna {
background-color: #0cb14b;
background-image: url(../images/platforms/dlna.svg);
@@ -3586,6 +3589,10 @@ a:hover .overlay-refresh-image:hover {
background-color: #e67817;
background-image: url(../images/platforms/firefox.svg);
}
.platform-gtv {
background-color: #008bcf;
background-image: url(../images/platforms/gtv.svg);
}
.platform-ie {
background-color: #00599e;
background-image: url(../images/platforms/ie.svg);
@@ -3602,6 +3609,10 @@ a:hover .overlay-refresh-image:hover {
background-color: #1793d0;
background-image: url(../images/platforms/linux.svg);
}
.platform-macos {
background-color: #858487;
background-image: url(../images/platforms/macos.svg);
}
.platform-msedge {
background-color: #0078d7;
background-image: url(../images/platforms/msedge.svg);
@@ -3622,10 +3633,6 @@ a:hover .overlay-refresh-image:hover {
background-color: #e5a00d;
background-image: url(../images/platforms/plexamp.svg);
}
.platform-plextogether {
background-color: #151924;
background-image: url(../images/platforms/plextogether.svg);
}
.platform-roku {
background-color: #6d3c97;
background-image: url(../images/platforms/roku.svg);
@@ -3638,6 +3645,18 @@ a:hover .overlay-refresh-image:hover {
background-color: #034ea2;
background-image: url(../images/platforms/samsung.svg);
}
.platform-synclounge {
background-color: #151924;
background-image: url(../images/platforms/synclounge.svg);
}
.platform-tivo {
background-color: #00a7e1;
background-image: url(../images/platforms/tivo.svg);
}
.platform-wiiu {
background-color: #03a9f4;
background-image: url(../images/platforms/wiiu.svg);
}
.platform-windows {
background-color: #2fc0f5;
background-image: url(../images/platforms/windows.svg);
@@ -3646,10 +3665,6 @@ a:hover .overlay-refresh-image:hover {
background-color: #68217a;
background-image: url(../images/platforms/windows.svg);
}
.platform-wiiu {
background-color: #03a9f4;
background-image: url(../images/platforms/wiiu.svg);
}
.platform-xbmc {
background-color: #3b4872;
background-image: url(../images/platforms/xbmc.svg);
@@ -3658,10 +3673,6 @@ a:hover .overlay-refresh-image:hover {
background-color: #107c10;
background-image: url(../images/platforms/xbox.svg);
}
.platform-default {
background-color: #e5a00d;
background-image: url(../images/platforms/default.svg);
}
.library-movie {
background-image: url(../images/libraries/movie.svg);
}
@@ -3686,4 +3697,75 @@ a:hover .overlay-refresh-image:hover {
}
.no-image {
background-image: none !important;
}
}
#info-modal .stream-info-item {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: baseline;
width: 100%;
margin-bottom: 5px;
}
#info-modal .stream-info-item .sub-heading {
height: 100%;
width: 75px;
color: #aaa;
font-size: 10px;
text-align: right;
text-transform: uppercase;
line-height: 14px;
-webkit-flex-shrink: 0;
flex-shrink: 0;
}
#info-modal .stream-info-item .sub-value {
color: #fff;
font-weight: bold;
margin-left: 10px;
text-align: left;
line-height: 14px;
-webkit-flex-grow: 1;
flex-grow: 1;
}
.stream-info {
width: 100%;
margin-top: 10px;
background-color: #282828;
table-layout: fixed;
}
.stream-info .heading {
color: #F9AA03;
text-transform: uppercase;
font-size: 15px;
font-weight: bold !important;
}
.stream-info th:first-child {
width: 125px;
height: 30px;
color: #fff;
font-size: 12px;
text-align: right;
text-transform: uppercase;
}
.stream-info th:not(:first-child) {
text-align: center;
font-weight: normal;
}
.stream-info td {
height: 25px;
}
.stream-info td:first-child {
color: #aaa;
font-size: 10px;
text-align: right;
text-transform: uppercase;
}
.stream-info td:not(:first-child) {
text-align: center;
}
.stream-info tr:nth-child(odd) td {
background-color: rgba(255,255,255,0.035);
}
.stream-info tr:nth-child(even) td {
background-color: rgba(255,255,255,0.010);
}

View File

@@ -72,7 +72,7 @@ DOCUMENTATION :: END
data-rating_key="${data['rating_key']}" data-parent_rating_key="${data['parent_rating_key']}" data-grandparent_rating_key="${data['grandparent_rating_key']}">
<div class="dashboard-activity-container">
<div class="dashboard-activity-background-overlay">
% if data['channel_stream'] == '0':
% if data['channel_stream'] == 0:
<div id="background-${sk}" class="dashboard-activity-background" style="background-image: url(pms_image_proxy?img=${data['art']}&width=500&height=280&fallback=art&refresh=true);"></div>
% else:
% if (data['art'] and data['art'].startswith('http')) or (data['thumb'] and data['thumb'].startswith('http')):
@@ -86,7 +86,7 @@ DOCUMENTATION :: END
% if data['media_type'] == 'track':
<div id="poster-${sk}-bg" class="dashboard-activity-poster-blur" style="background-image: url(pms_image_proxy?img=${data['parent_thumb']}&width=300&height=300&fallback=cover&refresh=true);"></div>
% endif
% if data['channel_stream'] == '0':
% if data['channel_stream'] == 0:
% if data['media_type'] == 'movie':
<a id="poster-url-${sk}" href="info?rating_key=${data['rating_key']}" 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>
@@ -158,11 +158,19 @@ DOCUMENTATION :: END
% endif
</div>
</li>
% if data['optimized_version'] == '1':
% if data['optimized_version'] == 1:
<li class="dashboard-activity-info-item">
<div class="sub-heading">Optimized</div>
<div class="sub-value" id="optimized_version-${sk}">
${data['optimized_version_profile']}
${data['optimized_version_profile']} (${data['optimized_version_title']})
</div>
</li>
% endif
% if data['synced_version'] == 1:
<li class="dashboard-activity-info-item">
<div class="sub-heading">Synced</div>
<div class="sub-value" id="synced_version-${sk}">
${data['synced_version_profile']}
</div>
</li>
% endif
@@ -177,7 +185,7 @@ DOCUMENTATION :: END
<div class="sub-value" id="transcode_decision-${sk}">
% if data['transcode_decision'] == 'transcode':
Transcode
% if data['transcode_throttled'] == '1':
% if data['transcode_throttled'] == 1:
(Throttled)
% else:
(Speed: ${data['transcode_speed']})
@@ -185,7 +193,7 @@ DOCUMENTATION :: END
% elif data['transcode_decision'] == 'copy':
Direct Stream
% else:
Direct Play ${'(Synced)' if data['synced_version'] == '1' else ''}
Direct Play
% endif
</div>
</li>
@@ -207,9 +215,9 @@ DOCUMENTATION :: END
% if data.get('stream_video_decision') == 'transcode':
<%
hw_d = hw_e = ''
if data['transcode_hw_requested'] == '1' and data['transcode_hw_full_pipeline'] == '0':
if data['transcode_hw_requested'] == 1 and data['transcode_hw_full_pipeline'] == 0:
hw_d = ' (HW)'
elif data['transcode_hw_requested'] == '1' and data['transcode_hw_full_pipeline'] == '1':
elif data['transcode_hw_requested'] == 1 and data['transcode_hw_full_pipeline'] == 1:
hw_d = hw_e = ' (HW)'
%>
Transcode (${data['video_codec'].upper()}${hw_d} ${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])} &rarr; ${data['stream_video_codec'].upper()}${hw_e} ${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
@@ -242,7 +250,7 @@ DOCUMENTATION :: END
<li class="dashboard-activity-info-item">
<div class="sub-heading">Subtitle</div>
<div class="sub-value" id="subtitle_decision-${sk}">
% if data['subtitles'] == '1':
% if data['subtitles'] == 1:
% if data['stream_subtitle_decision'] == 'transcode':
Transcode (${data['subtitle_codec'].upper()} &rarr; ${data['stream_subtitle_codec'].upper()})
% elif data['stream_subtitle_decision'] == 'copy':
@@ -250,7 +258,7 @@ DOCUMENTATION :: END
% elif data['stream_subtitle_decision'] == 'burn':
Burn (${data['subtitle_codec'].upper()})
% else:
Direct Play (${data['subtitle_codec'].upper()})
Direct Play (${data['stream_subtitle_codec'].upper() if data['synced_version'] else data['subtitle_codec'].upper()})
% endif
% else:
None
@@ -264,7 +272,7 @@ DOCUMENTATION :: END
<div class="sub-heading">Location</div>
<div class="sub-value">
% if data['ip_address'] != 'N/A':
${'LAN' if data['local'] == '1' else 'WAN'}: ${data['ip_address']}
${'LAN' if data['local'] == 1 else 'WAN'}: ${data['ip_address']}
<a href="#" class="external_ip-modal" data-toggle="modal" data-target="#ip-info-modal" data-ip="${data['ip_address']}">
<span id="external_ip-${sk}" class="external-ip-tooltip" data-toggle="tooltip" title="Lookup IP" style="display: none;"><i class="fa fa-map-marker"></i></span>
</a>
@@ -294,7 +302,7 @@ DOCUMENTATION :: END
%>
<span id="stream-bandwidth-${sk}">${bw}</span>
<span id="streaming-brain-${sk}" data-toggle="tooltip" title="Streaming Brain Estimate"><i class="fa fa-info-circle"></i></span>
% elif data['synced_version'] == '1' or data['channel_stream'] == '1':
% elif data['synced_version'] == 1 or data['channel_stream'] == 1:
<span id="stream-bandwidth-${sk}">None</span>
% else:
<span id="stream-bandwidth-${sk}">Unknown</span>
@@ -358,7 +366,7 @@ DOCUMENTATION :: END
% endif
</div>
<div class="dashboard-activity-metadata-title">
% if data['channel_stream'] == '0':
% if data['channel_stream'] == 0:
% if data['media_type'] == 'movie':
<a href="info?rating_key=${data['rating_key']}" title="${data['title']}">${data['title']}</a>
% elif data['media_type'] == 'episode':
@@ -383,7 +391,7 @@ DOCUMENTATION :: END
</div>
</div>
<div class="dashboard-activity-metadata-subtitle-container">
% if data['channel_stream'] == '0':
% if data['channel_stream'] == 0:
<div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="${data['media_type'].capitalize()}">
% if data['media_type'] == 'movie':
<i class="fa fa-fw fa-film"></i>&nbsp;
@@ -403,7 +411,7 @@ DOCUMENTATION :: END
</div>
% endif
<div class="dashboard-activity-metadata-subtitle">
% if data['channel_stream'] == '0':
% if data['channel_stream'] == 0:
% if data['media_type'] == 'movie':
<span title="${data['year']}" class="sub-heading">${data['year']}</span>
% elif data['media_type'] == 'episode':

View File

@@ -86,7 +86,7 @@ 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-blur" style="background-image: url(pms_image_proxy?img=${row0['thumb']}&width=300&height=300&fallback=${type});"></div>
<div id="stats-thumb-${stat_id}-bg" class="dashboard-stats-poster-blur" style="background-image: url(pms_image_proxy?img=${row0['thumb']}&width=300&height=300&fallback=cover);"></div>
% endif
<a id="stats-thumb-url-${stat_id}" href="info?rating_key=${row0['rating_key']}" title="${row0['title']}">
<% type = 'cover' if stat_id in ('top_music', 'popular_music') else 'poster' %>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -2,10 +2,10 @@
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="${http_root}images/favicon/mstile-70x70.png?v=2.0.0"/>
<square150x150logo src="${http_root}images/favicon/mstile-150x150.png?v=2.0.0"/>
<square310x310logo src="${http_root}images/favicon/mstile-310x310.png?v=2.0.0"/>
<TileColor>#1f1f1f</TileColor>
<square70x70logo src="${http_root}images/favicon/mstile-70x70.png?v=2.0.1"/>
<square150x150logo src="${http_root}images/favicon/mstile-150x150.png?v=2.0.1"/>
<square310x310logo src="${http_root}images/favicon/mstile-310x310.png?v=2.0.1"/>
<TileColor>#282a2d</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,17 +2,17 @@
"name": "Tautulli",
"icons": [
{
"src": "${http_root}images/favicon/android-chrome-192x192.png?v=2.0.0",
"src": "${http_root}images/favicon/android-chrome-192x192.png?v=2.0.1",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "${http_root}images/favicon/android-chrome-256x256.png?v=2.0.0",
"src": "${http_root}images/favicon/android-chrome-256x256.png?v=2.0.1",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#1f1f1f",
"background_color": "#1f1f1f",
"theme_color": "#282a2d",
"background_color": "#282a2d",
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,70 +1 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="275.000000pt" height="275.000000pt" viewBox="0 0 275.000000 275.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,275.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2200 2569 c0 -4 -12 -11 -27 -17 -57 -20 -143 -130 -149 -187 -7
-82 -6 -105 6 -140 7 -22 23 -56 36 -75 13 -19 20 -37 16 -40 -5 -3 -11 -13
-15 -23 -4 -11 -12 -15 -19 -11 -7 4 -10 4 -6 -1 10 -11 -20 -65 -37 -65 -8 0
-12 -6 -9 -14 3 -7 -2 -16 -11 -20 -9 -3 -14 -10 -11 -15 4 -5 -5 -18 -19 -30
-15 -11 -26 -23 -27 -28 0 -4 -4 -7 -9 -6 -4 2 -6 -3 -4 -10 3 -6 -6 -24 -20
-39 -14 -16 -23 -28 -20 -28 4 0 0 -7 -7 -15 -30 -33 -38 -45 -38 -55 0 -5 -4
-10 -8 -10 -5 0 -17 -16 -28 -35 -10 -19 -22 -35 -26 -35 -5 0 -8 -5 -8 -10 0
-10 -8 -22 -37 -55 -7 -7 -13 -17 -13 -22 0 -5 -13 -23 -30 -41 -16 -18 -31
-36 -32 -40 -2 -4 -6 -6 -10 -5 -4 2 -4 -5 -1 -14 4 -10 2 -14 -4 -10 -6 4
-13 -3 -17 -16 -3 -12 -16 -34 -30 -48 -14 -15 -22 -29 -19 -33 3 -3 -2 -8
-11 -12 -9 -3 -16 -12 -16 -19 0 -6 -11 -24 -25 -39 -13 -14 -24 -30 -24 -33
0 -4 -5 -9 -12 -12 -6 -2 -9 1 -6 6 3 5 -2 9 -12 8 -10 0 -45 2 -79 5 -34 3
-62 2 -62 -2 0 -4 -7 -8 -15 -8 -21 0 -95 -33 -95 -43 0 -4 -5 -5 -12 -1 -7 5
-8 3 -4 -5 5 -8 0 -14 -13 -18 -11 -3 -18 -9 -15 -14 3 -5 1 -9 -4 -9 -8 0
-43 -60 -48 -82 -1 -5 -3 -10 -4 -13 -1 -3 -4 -9 -5 -15 -1 -5 -4 -13 -5 -17
-1 -5 -1 -17 0 -28 1 -16 -5 -13 -28 15 -17 19 -28 38 -25 43 2 4 1 6 -4 5
-14 -5 -114 112 -107 124 4 6 3 8 -3 5 -5 -3 -44 33 -85 81 -42 48 -90 103
-107 122 -55 61 -72 88 -61 95 34 21 50 139 30 210 -6 22 -12 48 -13 57 -1 9
-5 14 -9 12 -4 -3 -8 -2 -8 3 0 17 -70 84 -110 104 -26 14 -67 24 -110 27 -57
4 -76 1 -121 -20 -30 -14 -67 -38 -83 -54 -33 -34 -70 -84 -61 -84 3 0 -1 -10
-9 -22 -23 -37 -18 -147 8 -205 61 -130 212 -195 343 -149 38 13 41 13 59 -7
10 -12 17 -23 15 -25 -1 -2 8 -13 21 -24 13 -11 22 -25 20 -32 -2 -6 -1 -8 3
-4 7 7 35 -15 35 -27 0 -4 14 -20 30 -35 17 -15 30 -32 30 -38 0 -5 7 -13 16
-16 8 -3 12 -11 8 -17 -4 -7 -3 -9 3 -6 11 7 49 -42 45 -56 -1 -5 1 -6 6 -3 8
5 49 -35 43 -43 -1 -2 4 -8 13 -13 9 -5 13 -14 10 -20 -4 -6 -3 -8 3 -5 11 7
45 -28 36 -37 -4 -3 2 -6 12 -6 10 0 15 -3 12 -6 -4 -4 4 -16 17 -28 13 -12
31 -33 40 -47 15 -24 15 -27 0 -33 -8 -3 -21 -3 -26 1 -7 3 -8 1 -4 -5 4 -7
-2 -16 -16 -22 -13 -6 -43 -24 -68 -40 -25 -16 -56 -35 -70 -42 -14 -7 -28
-17 -31 -22 -4 -5 -12 -5 -19 -1 -9 5 -11 4 -6 -3 4 -7 3 -12 -1 -12 -5 0 -33
-16 -63 -35 -61 -40 -63 -40 -82 -22 -7 8 -24 19 -36 26 -12 7 -20 17 -16 23
4 7 3 8 -4 4 -6 -4 -31 -2 -54 5 -49 14 -128 18 -128 7 0 -5 -7 -8 -16 -8 -28
0 -80 -30 -120 -69 -43 -43 -65 -68 -64 -76 1 -27 -1 -35 -9 -31 -5 3 -8 -32
-7 -82 2 -128 46 -201 155 -256 96 -49 228 -27 309 52 17 16 34 27 37 24 4 -4
5 -2 4 3 -4 15 11 56 20 51 6 -4 11 18 14 62 0 6 4 12 8 12 4 0 5 8 3 18 -2 9
-5 36 -7 59 -4 40 -2 44 32 65 20 12 39 21 44 20 4 -1 7 3 7 9 0 6 7 8 17 5
10 -4 14 -4 11 0 -6 5 54 54 67 54 2 0 24 14 49 31 25 17 51 27 57 23 8 -4 9
-3 5 5 -4 6 0 14 11 18 10 3 30 14 44 24 14 10 29 16 33 12 3 -3 6 -1 6 5 0 7
7 12 15 12 8 0 15 -7 15 -15 0 -8 -5 -15 -12 -15 -6 0 -9 -2 -6 -5 3 -3 0 -24
-6 -47 -7 -23 -10 -59 -6 -83 4 -23 6 -52 5 -63 -1 -12 2 -19 6 -17 3 3 13
-10 20 -28 19 -47 95 -113 152 -131 123 -40 255 4 319 106 22 36 45 44 60 23
5 -6 8 -5 8 2 0 9 7 10 24 3 13 -5 44 -11 67 -15 24 -3 76 -12 114 -19 39 -8
86 -17 106 -20 33 -6 38 -11 44 -43 12 -67 50 -124 113 -176 12 -9 26 -17 30
-17 4 0 19 -6 34 -14 15 -8 56 -14 95 -14 245 0 365 295 190 468 -31 31 -64
53 -92 61 -24 7 -42 15 -40 18 2 4 -20 5 -48 4 -29 -1 -52 -2 -52 -3 0 -1 -10
-3 -23 -6 -61 -11 -182 -105 -182 -141 0 -9 -8 -12 -22 -8 -26 5 -184 33 -238
41 -19 3 -42 8 -50 11 -15 6 -35 10 -70 12 -14 1 -20 8 -20 26 0 29 -39 115
-60 132 -12 10 -13 15 -2 30 24 34 61 117 56 125 -3 4 0 8 6 8 6 0 9 4 6 8 -3
5 -6 34 -7 65 -1 31 -6 57 -11 57 -4 0 -8 7 -8 16 0 9 -9 29 -21 45 -17 24
-18 33 -9 44 7 8 16 12 21 9 5 -3 6 2 3 10 -3 9 -1 16 5 16 6 0 10 6 8 13 -1
6 4 11 11 9 8 -1 11 1 8 6 -7 12 39 74 48 65 4 -4 4 0 0 10 -3 10 -1 17 5 17
6 0 11 7 11 15 0 8 7 15 15 15 8 0 14 3 13 8 -3 10 110 180 199 297 5 6 26 36
47 67 21 32 43 55 47 52 5 -3 6 1 4 8 -3 8 3 23 13 34 11 11 30 36 43 56 13
19 40 58 59 86 l35 52 45 -7 c25 -3 50 -6 55 -6 78 5 153 38 199 87 68 72 96
198 63 279 -9 20 -14 37 -11 37 5 0 -43 55 -76 86 -14 13 -28 24 -33 24 -4 0
-5 5 -1 12 4 7 3 8 -4 4 -6 -4 -24 0 -40 8 -28 15 -192 19 -192 5z"/>
<path d="M2046 2132 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4
-9 -8z"/>
</g>
</svg>
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"><path d="M5695 6555 c-135 -34 -244 -94 -342 -189 -40 -39 -73 -76 -73 -83 0 -7 -4 -13 -10 -13 -14 0 -87 -156 -106 -225 -22 -83 -26 -234 -8 -320 17 -79 86 -230 133 -288 l30 -39 -48 -71 c-39 -57 -159 -228 -251 -357 -69 -97 -398 -564 -416 -590 -13 -19 -60 -87 -105 -150 -45 -63 -107 -151 -138 -195 -30 -44 -59 -84 -63 -90 -7 -9 -251 -354 -346 -490 -92 -131 -173 -245 -175 -245 -1 0 -34 9 -72 21 -130 38 -325 31 -454 -18 -168 -63 -313 -196 -385 -354 -39 -87 -65 -183 -68 -256 0 -24 -3 -43 -4 -43 -2 0 -43 46 -91 102 -49 57 -100 117 -115 133 -14 17 -128 149 -253 295 -125 146 -251 292 -279 324 -56 65 -77 89 -108 126 -58 68 -152 178 -172 200 -12 14 -50 57 -83 96 l-61 71 27 44 c58 93 91 217 92 342 2 161 -38 294 -125 412 -133 181 -316 279 -542 292 -470 27 -833 -434 -699 -887 74 -251 275 -437 530 -490 132 -28 334 -6 421 45 l42 24 173 -197 c96 -108 186 -210 200 -227 15 -16 163 -187 330 -380 458 -529 491 -567 526 -605 18 -19 31 -35 30 -36 -6 -5 -265 -161 -277 -167 -8 -4 -34 -20 -58 -35 -194 -124 -634 -382 -651 -382 -12 0 -46 20 -75 44 -60 49 -180 112 -242 127 -21 5 -48 12 -59 15 -11 4 -65 9 -121 11 -81 4 -117 1 -182 -15 -261 -66 -462 -270 -528 -537 -10 -40 -11 -217 -2 -258 5 -23 11 -51 14 -61 29 -145 147 -312 284 -403 123 -82 224 -114 370 -118 83 -3 124 2 240 29 36 9 133 57 187 94 60 41 111 91 153 152 14 19 28 37 32 40 19 15 71 140 89 217 17 73 20 107 16 198 -4 61 -7 121 -9 134 -3 28 -46 0 482 321 179 108 379 228 444 265 104 59 120 65 133 52 13 -13 12 -22 -10 -78 -49 -123 -58 -165 -62 -262 -7 -149 25 -286 89 -383 47 -72 91 -128 125 -158 19 -17 39 -36 45 -42 27 -25 136 -94 150 -94 8 0 17 -4 20 -9 3 -5 16 -11 28 -14 13 -3 50 -12 83 -21 74 -19 278 -15 345 7 198 65 358 196 435 358 16 34 20 36 49 28 17 -4 49 -10 71 -14 22 -3 99 -16 170 -30 72 -13 144 -26 160 -29 28 -5 101 -18 170 -31 17 -3 80 -14 140 -25 61 -11 124 -22 140 -25 17 -4 49 -9 72 -12 40 -5 42 -7 48 -47 14 -98 29 -147 73 -235 36 -75 61 -110 121 -171 154 -154 280 -210 480 -213 134 -2 180 5 273 40 212 83 371 262 427 481 24 93 25 255 2 342 -64 241 -245 428 -481 501 -62 18 -97 23 -200 22 -107 0 -136 -4 -205 -26 -44 -15 -109 -43 -145 -64 -83 -48 -208 -171 -250 -245 -17 -32 -35 -60 -38 -61 -4 -2 -46 4 -93 13 -48 10 -104 20 -125 23 -22 3 -46 8 -54 11 -8 3 -33 7 -55 10 -38 5 -58 9 -122 21 -16 3 -53 10 -83 15 -30 6 -66 12 -79 15 -13 2 -103 19 -200 36 -169 30 -207 42 -196 60 10 16 -28 155 -62 224 -19 39 -54 96 -78 127 l-45 58 40 52 c96 125 143 266 143 433 1 164 -27 263 -108 391 -19 30 -35 57 -35 61 0 3 31 49 69 102 57 81 450 638 625 889 28 40 62 88 76 107 14 18 194 274 400 568 291 414 379 534 393 531 10 -2 27 -6 37 -9 78 -25 240 -29 338 -9 433 87 677 573 489 974 -93 200 -255 332 -478 389 -87 22 -227 25 -304 6z"/></g></svg>

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="900px"
height="300px" viewBox="-41.335 -35.197 900 300" enable-background="new -41.335 -35.197 900 300" xml:space="preserve">
<g id="Layer_1">
<g id="Logo">
<image overflow="visible" opacity="0.15" enable-background="new " width="259" height="193" xlink:href="13AD46AC.png" transform="matrix(0.9547 0 0 0.9547 -17.8804 54.1814)">
</image>
<image overflow="visible" opacity="0.15" enable-background="new " width="259" height="258" xlink:href="13AD46AD.png" transform="matrix(0.9547 0 0 0.9584 -17.8804 -8.8303)">
</image>
</g>
</g>
<g id="Layer_2">
<g>
<path fill="none" d="M349.343,165.396c7.493,0,14.836,0,22.407,0c-3.766-12.354-7.495-24.585-11.333-37.175
C356.665,140.819,353.017,153.064,349.343,165.396z"/>
<path fill="#FFFFFF" d="M103.83,174.477c-7.6,0-14.441-3.232-19.236-8.394l-4.595,2.845l1.406,1.593
c-2.429,3.979-3.83,8.656-3.83,13.66c0,14.5,11.755,26.255,26.255,26.255c10.38,0,19.351-6.024,23.612-14.767l37.675,5.471
c0.22,14.31,11.881,25.845,26.244,25.845c14.5,0,26.254-11.755,26.254-26.254c0-14.5-11.754-26.254-26.254-26.254
c-10.438,0-19.453,6.092-23.685,14.915l-37.598-5.46c-0.064-6.86-2.759-13.089-7.125-17.73
C118.165,171.293,111.371,174.477,103.83,174.477z"/>
<path fill="#FFFFFF" d="M77.574,148.22c0-0.054,0.004-0.107,0.004-0.161l-41.163-46.637c3.078-4.301,4.895-9.564,4.895-15.257
c0-14.5-11.754-26.254-26.254-26.254c-14.5,0-26.254,11.754-26.254,26.254S0.556,112.42,15.056,112.42
c4.464,0,8.666-1.117,12.347-3.083l44.545,50.47l6.701-4.148C77.953,153.299,77.574,150.804,77.574,148.22z"/>
<path fill="#E5A00D" d="M191.361,49.885c14.5,0,26.254-11.754,26.254-26.254c0-14.499-11.754-26.254-26.254-26.254
s-26.254,11.755-26.254,26.254c0,7.145,2.858,13.619,7.488,18.353l-58.186,82.207c-3.237-1.427-6.814-2.226-10.58-2.226
c-14.446,0-26.165,11.669-26.252,26.095c0,0.054-0.004,0.107-0.004,0.161c0,2.584,0.379,5.078,1.075,7.438l-6.701,4.148
l-37.506,23.223c-4.801-5.255-11.708-8.552-19.386-8.552c-14.5,0-26.254,11.754-26.254,26.254
c0,14.499,11.754,26.254,26.254,26.254c14.5,0,26.254-11.755,26.254-26.254c0-2.506-0.358-4.926-1.014-7.221l39.703-24.582
l4.595-2.845c4.794,5.161,11.636,8.394,19.236,8.394c7.542,0,14.335-3.185,19.124-8.275c4.419-4.699,7.132-11.021,7.132-17.981
c0-6.396-2.29-12.257-6.092-16.812l58.736-82.983C185.434,49.367,188.336,49.885,191.361,49.885z"/>
<path fill="#FFFFFF" d="M384.772,151.627c-4.399-12.521-8.814-25.034-13.223-37.553c-7.552,0-15.106,0-22.657,0.002
c-0.14,0.612-0.221,1.246-0.428,1.836c-8.502,24.335-17.017,48.666-25.526,72.996c-1.451,4.146-2.887,8.298-4.363,12.536
c6.686,0,13.198,0,19.792,0c2.095-6.874,4.172-13.694,6.248-20.514c10.593,0,21.056,0,31.594,0
c2.102,6.893,4.177,13.707,6.245,20.489c6.66,0,13.147,0,19.773,0c-0.229-0.676-0.409-1.213-0.597-1.747
C396.014,183.656,390.4,167.638,384.772,151.627z M349.343,165.396c3.674-12.332,7.322-24.577,11.074-37.175
c3.837,12.59,7.567,24.82,11.333,37.175C364.179,165.396,356.836,165.396,349.343,165.396z"/>
<path fill="#FFFFFF" d="M319.072,114.459c-21.916,0-43.668,0-65.421,0c0,5.112,0,10.227,0,15.341c7.836,0,15.672,0,23.691,0
c0,24.046,0,47.786,0,71.621c6.164,0,12.144,0,18.361,0c0-23.902,0-47.698,0-71.682c7.948,0,15.66,0,23.369,0
C319.072,124.549,319.072,119.549,319.072,114.459z"/>
<path fill="#FFFFFF" d="M480.615,114.554c-6.176,0-12.154,0-18.348,0c0,0.843,0,1.593,0,2.341c0,16.42,0.021,32.838-0.021,49.258
c-0.006,2.674-0.119,5.373-0.494,8.017c-0.997,7.07-4.634,11.153-11.06,12.587c-3.731,0.83-7.491,0.754-11.238,0
c-5.745-1.157-9.344-4.668-10.58-10.297c-0.678-3.088-0.958-6.318-0.976-9.488c-0.093-16.737-0.044-33.477-0.044-50.216
c0-0.733,0-1.467,0-2.149c-6.296,0-12.276,0-18.33,0c0,0.591,0,1.036,0,1.481c0,18.209-0.012,36.416,0.018,54.625
c0.003,1.908,0.127,3.835,0.396,5.726c1.632,11.409,7.683,19.563,18.562,23.516c11.146,4.052,22.489,3.83,33.593-0.392
c7.936-3.017,13.483-8.627,16.483-16.618c1.474-3.93,2.044-8.024,2.042-12.21c-0.006-18.146-0.002-36.29-0.002-54.436
C480.615,115.744,480.615,115.187,480.615,114.554z"/>
<path fill="#FFFFFF" d="M636.532,114.554c-6.16,0-12.139,0-18.348,0c0,0.925,0,1.621,0,2.319c0,16.482,0.021,32.967-0.021,49.449
c-0.006,2.609-0.125,5.244-0.49,7.825c-1.003,7.126-4.74,11.291-11.226,12.654c-3.674,0.771-7.373,0.702-11.055-0.038
c-5.741-1.155-9.354-4.651-10.596-10.281c-0.682-3.086-0.964-6.317-0.979-9.487c-0.093-16.738-0.043-33.477-0.043-50.216
c0-0.739,0-1.477,0-2.18c-6.279,0-12.26,0-18.332,0c0,0.582,0,1.025,0,1.467c0.002,18.272-0.011,36.544,0.02,54.816
c0.002,1.846,0.132,3.707,0.391,5.533c1.626,11.411,7.669,19.567,18.546,23.533c11.212,4.09,22.621,3.836,33.771-0.445
c7.851-3.018,13.345-8.613,16.315-16.541c1.521-4.052,2.06-8.275,2.056-12.592c-0.018-17.953-0.008-35.905-0.008-53.858
C636.532,115.892,636.532,115.269,636.532,114.554z"/>
<path fill="#FFFFFF" d="M490.405,129.793c7.813,0,15.523,0,23.479,0c0,23.979,0,47.771,0,71.649c6.172,0,12.101,0,18.425,0
c0-23.949,0-47.739,0-71.729c7.947,0,15.655,0,23.378,0c0-5.158,0-10.115,0-15.116c-21.82,0-43.531,0-65.282,0
C490.405,119.691,490.405,124.648,490.405,129.793z"/>
<path fill="#FFFFFF" d="M673.407,114.58c-6.199,0-12.176,0-18.193,0c0,29.021,0,57.92,0,86.857c17.832,0,35.528,0,53.335,0
c0-5.191,0-10.146,0-15.306c-11.72,0-23.322,0-35.142,0C673.407,162.143,673.407,138.362,673.407,114.58z"/>
<path fill="#FFFFFF" d="M738.668,114.593c-6.197,0-12.172,0-18.216,0c0,28.985,0,57.833,0,86.831c17.905,0,35.6,0,53.371,0
c0-5.106,0-10.107,0-15.316c-11.729,0-23.335,0-35.155,0C738.668,162.119,738.668,138.339,738.668,114.593z"/>
<path fill="#FFFFFF" d="M786.183,201.464c6.083,0,12.02,0,18.095,0c0-29.09,0-57.987,0-86.879c-6.12,0-12.097,0-18.095,0
C786.183,143.612,786.183,172.513,786.183,201.464z"/>
<ellipse fill="#E5A00D" cx="795.229" cy="91.416" rx="12.458" ry="12.44"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="298.961px" height="300px" viewBox="0 0 298.961 300" enable-background="new 0 0 298.961 300" xml:space="preserve">
<image overflow="visible" opacity="0.15" enable-background="new " width="259" height="193" xlink:href="93124B54.png" transform="matrix(1 0 0 1 22.6465 89.25)">
</image>
<path fill="#FFFFFF" d="M241.811,215.25c-10.935,0-20.376,6.382-24.809,15.623l-39.383-5.715
c-0.139-15.068-12.392-27.242-27.493-27.242c-5.385,0-10.405,1.554-14.646,4.229l-55.963-63.414
c3.224-4.505,5.127-10.02,5.127-15.981c0-15.188-12.312-27.5-27.5-27.5s-27.5,12.312-27.5,27.5s12.312,27.5,27.5,27.5
c4.677,0,9.079-1.171,12.934-3.23l56.56,64.089c-2.544,4.168-4.012,9.065-4.012,14.307c0,15.188,12.312,27.5,27.5,27.5
c10.872,0,20.269-6.311,24.731-15.467l39.464,5.727c0.229,14.99,12.443,27.074,27.489,27.074c15.188,0,27.5-12.313,27.5-27.5
S256.998,215.25,241.811,215.25z"/>
<image overflow="visible" opacity="0.15" enable-background="new " width="259" height="259" xlink:href="93124B55.png" transform="matrix(1 0 0 1 22.6465 23.25)">
</image>
<path fill="#E5A00D" d="M241.811,29.75c-15.188,0-27.5,12.312-27.5,27.5c0,7.48,2.99,14.258,7.835,19.216l-60.943,86.113
c-3.389-1.493-7.134-2.329-11.076-2.329c-15.188,0-27.5,12.313-27.5,27.5c0,2.704,0.397,5.314,1.125,7.783l-46.306,28.668
c-5.028-5.5-12.261-8.951-20.3-8.951c-15.188,0-27.5,12.313-27.5,27.5s12.312,27.5,27.5,27.5s27.5-12.313,27.5-27.5
c0-2.627-0.376-5.165-1.064-7.571l46.396-28.723c5.022,5.407,12.189,8.794,20.15,8.794c15.188,0,27.5-12.313,27.5-27.5
c0-6.701-2.399-12.84-6.382-17.611l61.515-86.92c2.836,0.988,5.88,1.532,9.052,1.532c15.188,0,27.5-12.312,27.5-27.5
S256.998,29.75,241.811,29.75z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="953.503px" height="211.614px" viewBox="0 0 953.503 211.614" enable-background="new 0 0 953.503 211.614"
xml:space="preserve">
<g>
<path fill="none" d="M-7.087,418.282c12.933,0,25.607,0,38.673,0c-6.5-21.354-12.936-42.492-19.56-64.253
C5.548,375.804-0.747,396.967-7.087,418.282z"/>
<path fill="#FFFFFF" d="M54.064,394.482c-7.594-21.64-15.215-43.269-22.824-64.903c-13.035,0-26.072,0-39.106,0.002
c-0.241,1.06-0.381,2.153-0.737,3.174c-14.675,42.06-29.37,84.111-44.058,126.164c-2.503,7.166-4.984,14.34-7.53,21.666
c11.54,0,22.78,0,34.162,0c3.614-11.881,7.2-23.67,10.784-35.455c18.283,0,36.34,0,54.529,0
c3.627,11.914,7.212,23.691,10.781,35.412c11.493,0,22.693,0,34.128,0c-0.397-1.166-0.705-2.094-1.029-3.02
C73.466,449.84,63.776,422.157,54.064,394.482z M-7.087,418.282c6.34-21.314,12.635-42.478,19.113-64.253
c6.624,21.761,13.06,42.899,19.56,64.253C18.52,418.282,5.846,418.282-7.087,418.282z"/>
<path fill="#FFFFFF" d="M-59.334,330.243c-37.826,0-75.371,0-112.916,0c0,8.836,0,17.674,0.001,26.514c13.525,0,27.05,0,40.889,0
c0,41.561,0,82.594,0,123.787c10.64,0,20.961,0,31.692,0c0-41.311,0-82.439,0-123.892c13.718,0,27.028,0,40.334,0
C-59.334,347.68-59.334,339.04-59.334,330.243z"/>
<path fill="#FFFFFF" d="M219.484,330.409c-10.657,0-20.976,0-31.667,0c0,1.457,0,2.752,0,4.045c0,28.379,0.037,56.758-0.037,85.135
c-0.012,4.623-0.206,9.287-0.851,13.857c-1.723,12.219-7.998,19.277-19.089,21.752c-6.441,1.436-12.932,1.305-19.396,0.002
c-9.917-2-16.129-8.066-18.262-17.797c-1.171-5.336-1.655-10.92-1.685-16.398c-0.158-28.928-0.074-57.86-0.074-86.791
c0-1.268,0-2.535,0-3.717c-10.869,0-21.188,0-31.639,0c0,1.023,0,1.793,0,2.562c0,31.47-0.018,62.94,0.032,94.411
c0.005,3.299,0.217,6.629,0.683,9.896c2.816,19.719,13.26,33.813,32.036,40.645c19.239,7.002,38.813,6.619,57.978-0.678
c13.699-5.215,23.273-14.91,28.45-28.723c2.545-6.791,3.528-13.869,3.526-21.104c-0.011-31.363-0.005-62.723-0.005-94.082
C219.484,332.463,219.484,331.502,219.484,330.409z"/>
<path fill="#FFFFFF" d="M488.595,333.793c0-1.073,0-2.149,0-3.385c-10.631,0-20.952,0-31.668,0c0,1.598,0,2.803,0,4.008
c0,28.488,0.036,56.979-0.037,85.467c-0.011,4.51-0.215,9.064-0.844,13.525c-1.734,12.314-8.183,19.514-19.379,21.869
c-6.339,1.332-12.722,1.215-19.076-0.064c-9.912-1.996-16.146-8.039-18.291-17.77c-1.174-5.334-1.662-10.92-1.69-16.398
c-0.159-28.928-0.075-57.86-0.075-86.791c0-1.277,0-2.553,0-3.769c-10.835,0-21.158,0-31.641,0c0,1.009,0,1.772,0,2.537
c0.003,31.581-0.016,63.161,0.034,94.743c0.005,3.189,0.229,6.406,0.676,9.563c2.806,19.723,13.238,33.82,32.007,40.674
c19.354,7.068,39.046,6.631,58.289-0.77c13.549-5.215,23.032-14.887,28.161-28.588c2.623-7.006,3.555-14.305,3.549-21.764
C488.578,395.852,488.595,364.823,488.595,333.793z"/>
<path fill="#FFFFFF" d="M236.383,356.745c13.484,0,26.794,0,40.523,0c0,41.445,0,82.566,0,123.836c10.655,0,20.887,0,31.803,0
c0-41.391,0-82.51-0.001-123.974c13.718,0,27.021,0,40.351,0c0-8.916,0-17.481,0-26.125c-37.661,0-75.134,0-112.676,0
C236.383,339.288,236.383,347.853,236.383,356.745z"/>
<path fill="#FFFFFF" d="M552.24,330.454c-10.699,0-21.014,0-31.4,0c0,50.158,0,100.104,0,150.119c30.779,0,61.322,0,92.055,0
c0-8.973,0-17.535,0-26.453c-20.227,0-40.254,0-60.654,0C552.24,412.659,552.24,371.556,552.24,330.454z"/>
<path fill="#FFFFFF" d="M664.878,330.476c-10.697,0-21.01,0-31.441,0c0,50.094,0,99.954,0,150.073c30.904,0,61.445,0,92.119,0
c0-8.826,0-17.469,0-26.473c-20.246,0-40.273,0-60.678,0C664.878,412.618,664.878,371.515,664.878,330.476z"/>
<path fill="#FFFFFF" d="M746.886,480.62c10.502,0,20.748,0,31.232,0c0-50.277,0-100.226,0-150.158c-10.563,0-20.879,0-31.232,0
C746.886,380.631,746.886,430.583,746.886,480.62z"/>
<circle fill="#E5A00D" cx="762.501" cy="290.417" r="21.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -235,7 +235,12 @@
}
};
var create_instances = [];
var activity_ready = true;
function getCurrentActivity() {
activity_ready = false;
$.ajax({
url: 'get_activity',
type: 'GET',
@@ -256,8 +261,13 @@
}
if (!(current_activity)) {
% if _session['user_group'] == 'admin':
var msg_settings = ' Verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.';
% else:
var msg_settings = ''
% endif
$('#currentActivityHeader').text('');
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.</div>');
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.' + msg_settings + '</div>');
return
}
@@ -293,7 +303,8 @@
// Create a new instance if it doesn't exist
if (!(instance.length)) {
getActivityInstance(session);
create_instances.push(key);
getActivityInstance(key);
return;
}
@@ -352,7 +363,7 @@
} else if (s.transcode_decision === 'copy') {
transcode_decision = 'Direct Stream';
} else {
transcode_decision = 'Direct Play' + ((s.synced_version == 1) ? ' (Synced)' : '');
transcode_decision = 'Direct Play';
}
$('#transcode_decision-' + key).html(transcode_decision);
@@ -429,7 +440,7 @@
} else if (s.stream_subtitle_decision === 'burn') {
subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')';
} else {
subtitle_decision = 'Direct Play (' + s.subtitle_codec.toUpperCase() + ')';
subtitle_decision = 'Direct Play (' + ((s.synced_version == '1') ? s.stream_subtitle_codec.toUpperCase() : s.subtitle_codec.toUpperCase()) + ')';
}
}
$('#subtitle_decision-' + key).html(subtitle_decision);
@@ -448,7 +459,8 @@
} else {
$('#stream_quality-' + key).html(s.quality_profile);
}
$('#optimized_version-' + key).html(s.optimized_version_profile);
$('#optimized_version-' + key).html(s.optimized_version_profile + ' (' + s.optimized_version_title + ')');
$('#synced_quality_profile-' + key).html(s.synced_quality_profile);
if (s.media_type != 'photo' && parseInt(s.bandwidth)) {
var bw = parseInt(s.bandwidth);
@@ -495,30 +507,41 @@
$('#currentActivityHeader').text('');
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">Nothing is currently being played.</div>');
}
activity_ready = true;
}
});
}
function getActivityInstance(session) {
function getActivityInstance(session_key) {
$.ajax({
url: 'get_current_activity_instance',
type: 'GET',
cache: false,
async: true,
data: session,
data: {
session_key: session_key
},
complete: function(xhr, status) {
$('#currentActivity').append(xhr.responseText);
$('#activity-instance-' + session.session_key + ' .dashboard-activity-info-scroller').scrollbar();
$('#activity-instance-' + session.session_key + ' [data-toggle=tooltip]').tooltip({ container: 'body', placement: 'right', delay: 50 })
$('#terminate-button-' + session.session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 });
lockScroll('#activity-instance-' + session.session_key + ' .dashboard-activity-info-scroller');
$('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller').scrollbar();
$('#activity-instance-' + session_key + ' [data-toggle=tooltip]').tooltip({ container: 'body', placement: 'right', delay: 50 });
$('#terminate-button-' + session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 });
lockScroll('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller');
var index = create_instances.indexOf(session_key);
if (index > -1) {
create_instances.splice(index, 1);
}
}
});
}
getCurrentActivity();
setInterval(function () {
getCurrentActivity();
if (!(create_instances.length) && activity_ready) {
getCurrentActivity();
}
}, 2000);
setInterval(function(){

View File

@@ -80,7 +80,7 @@ DOCUMENTATION :: END
<div class="col-md-12">
<div class="summary-navbar-list">
<ul class="list-unstyled breadcrumb">
% if data['media_type'] == 'movie':
% if data['media_type'] in ('movie', 'collection'):
<li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="active">${data['title']}</li>
% elif data['media_type'] == 'show':
@@ -116,9 +116,9 @@ DOCUMENTATION :: END
<div class="col-md-9">
<div class="summary-content-poster hidden-xs hidden-sm">
% if data['media_type'] == 'track':
<a href="${config['pms_web_url'] or 'https://app.plex.tv/desktop'}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View in Plex Web">
<a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View in Plex Web">
% else:
<a href="${config['pms_web_url'] or 'https://app.plex.tv/desktop'}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View in Plex Web">
<a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View in Plex Web">
% endif
% if data['media_type'] == 'episode':
<div class="summary-poster-face-episode" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=280&fallback=art);">
@@ -151,7 +151,7 @@ DOCUMENTATION :: END
</a>
</div>
<div class="summary-content-title">
% if data['media_type'] in ('movie', 'show', 'artist'):
% if 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>
@@ -175,7 +175,7 @@ DOCUMENTATION :: END
<div class="col-md-9">
% if data['media_type'] == 'movie':
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 305px;">
% elif data['media_type'] == 'show' or data['media_type'] == 'season':
% elif data['media_type'] in ('show', 'season', 'collection'):
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 270px;">
% elif data['media_type'] == 'episode':
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 70px;">
@@ -235,6 +235,8 @@ DOCUMENTATION :: END
Aired <strong> <span id="airdate">${data['originally_available_at']}</span></strong>
% elif data['media_type'] == 'album' or data['media_type'] == 'track':
Released <strong> ${data['year']}</strong>
% elif data['media_type'] == 'collection':
Year <strong> ${data['min_year']} - ${data['max_year']}</strong>
% endif
</div>
<div class="summary-content-details-tag">
@@ -308,51 +310,65 @@ DOCUMENTATION :: END
</div>
% if data['media_type'] == 'show':
<div class="col-md-12">
<div class='table-card-header'>
<div class="table-card-header">
<div class="header-bar">
<span>Season List for <strong>${data['title']}</strong></span>
</div>
</div>
<div class='table-card-back'>
<div id="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading season list...</div>
<div class="table-card-back">
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading season list...</div>
</div>
</div>
% elif data['media_type'] == 'season':
<div class="col-md-12">
<div class='table-card-header'>
<div class="table-card-header">
<div class="header-bar">
<span>Episode List for <strong>${data['title']}</strong></span>
</div>
</div>
<div class='table-card-back'>
<div id="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading episode list...</div>
<div class="table-card-back">
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading episode list...</div>
</div>
</div>
% elif data['media_type'] == 'artist':
<div class="col-md-12">
<div class='table-card-header'>
<div class="table-card-header">
<div class="header-bar">
<span>Album List for <strong>${data['title']}</strong></span>
</div>
</div>
<div class='table-card-back'>
<div id="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading album list...</div>
<div class="table-card-back">
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading album list...</div>
</div>
</div>
% elif data['media_type'] == 'album':
<div class="col-md-12">
<div class='table-card-header'>
<div class="table-card-header">
<div class="header-bar">
<span>Track List for <strong>${data['title']}</strong></span>
</div>
</div>
<div class='table-card-back'>
<div id="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading track list...</div>
<div class="table-card-back">
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading track list...</div>
</div>
</div>
% endif
% elif data['media_type'] == 'collection':
<div class="col-md-12">
<div class='table-card-header'>
<div class="table-card-header">
<div class="header-bar">
<span>Movies in <strong>${data['title']}</strong> collection</span>
</div>
</div>
<div class="table-card-back">
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading movies list...</div>
</div>
</div>
<div id="collection-related-list-container" style="display: none;">
</div>
% endif
% if data['media_type'] != 'collection':
<div class="col-md-12">
<div class="table-card-header">
<div class="header-bar">
<span>Watch History for <strong>${data['title']}</strong></span>
</div>
@@ -420,6 +436,7 @@ DOCUMENTATION :: END
</table>
</div>
</div>
% endif
</div>
</div>
</div>
@@ -428,7 +445,7 @@ DOCUMENTATION :: END
</%def>
<%def name="modalIncludes()">
<div id="info-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="info-modal">
<div class="modal fade" id="info-modal" tabindex="-1" role="dialog" aria-labelledby="info-modal">
</div>
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
</div>
@@ -562,6 +579,7 @@ DOCUMENTATION :: END
}
</script>
% endif
% if data['media_type'] != 'collection':
<script>
$(document).ready(function () {
get_history();
@@ -638,7 +656,8 @@ DOCUMENTATION :: END
});
});
</script>
% if data['media_type'] in ('show', 'season', 'artist', 'album'):
% endif
% if data['media_type'] in ('show', 'season', 'artist', 'album', 'collection'):
<script>
$.ajax({
url: 'get_item_children',
@@ -646,7 +665,24 @@ DOCUMENTATION :: END
async: true,
data: { rating_key : "${data['rating_key']}" },
complete: function(xhr, status) {
$("#children-list").html(xhr.responseText); }
$("#children-list").html(xhr.responseText);
}
});
</script>
% endif
% if data['media_type'] == 'collection':
<script>
$.ajax({
url: 'get_item_children_related',
type: 'GET',
async: true,
data: {
rating_key : "${data['rating_key']}",
title: "${data['title']}"
},
complete: function(xhr, status) {
$("#collection-related-list-container").html(xhr.responseText).show();
}
});
</script>
% endif

View File

@@ -37,13 +37,42 @@ DOCUMENTATION :: END
% else:
<li>
% endif
%if data['children_type'] == 'season':
% if data['children_type'] == 'movie':
<a href="info?rating_key=${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>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
</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>
</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']}">
<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>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
</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>
</h3>
</div>
% elif data['children_type'] == 'season':
<a href="info?rating_key=${child['rating_key']}" title="Season ${child['media_index']}">
<div class="item-children-poster">
% if child['thumb']:
<div class="item-children-poster-face season-poster" 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(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);">
% else:
<div class="item-children-poster-face season-poster" 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(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=450&fallback=poster);">
% endif
<div class="item-children-card-overlay">
<div class="item-children-overlay-text">
@@ -59,7 +88,7 @@ DOCUMENTATION :: END
% elif data['children_type'] == 'episode':
<a href="info?rating_key=${child['rating_key']}" title="Episode ${child['media_index']}">
<div class="item-children-poster">
<div class="item-children-poster-face episode-poster" 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(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);">
<div class="item-children-card-overlay">
<div class="item-children-overlay-text">
Episode ${child['media_index']}
@@ -79,13 +108,13 @@ DOCUMENTATION :: END
% elif data['children_type'] == 'album':
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<div class="item-children-poster">
<div class="item-children-poster-face album-poster" 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(pms_image_proxy?img=${child['thumb']}&width=300&height=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
</div>
</a>
<div class="item-children-instance-text-wrapper album-item">
<div class="item-children-instance-text-wrapper cover-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
</h3>

View File

@@ -0,0 +1,99 @@
<%doc>
USAGE DOCUMENTATION :: PLEASE LEAVE THIS AT THE TOP OF THIS FILE
For Mako templating syntax documentation please visit: http://docs.makotemplates.org/en/latest/
Filename: info_collection_list.html
Version: 0.1
Variable names: data [list]
data :: Usable parameters
== Global keys ==
children_type Returns the type of children in the array.
children_count Returns the number of episodes in the array.
children_list Returns an array of episodes.
data['children_list'] :: Usable paramaters
== Global keys ==
rating_key Returns the unique identifier for the media item.
media_index Returns the episode number.
title Returns the name of the episode.
thumb Returns the location of the item's thumbnail. Use with pms_image_proxy.
parent_thumb Returns the location of the item's parent thumbnail. Use with pms_image_proxy.
DOCUMENTATION :: END
</%doc>
% if data != None:
<%
types = ('movie', 'show', 'artist', 'album')
headers = {'movie': 'Movies',
'show': 'TV Shows',
'season': 'Seasons',
'episode': 'Episodes',
'artist': 'Artists',
'album': 'Albums',
'track': 'Tracks',
}
%>
% for media_type in types:
% if data['results_list'][media_type]:
<div class="col-md-12">
<div class="table-card-header">
<div class="header-bar">
<span>${headers[media_type]} in <strong>${title}</strong> collection</span>
</div>
</div>
<div class="table-card-back">
<div id="children-list" class="children-list">
<div class="item-children-wrapper">
<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']}">
<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>
% 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>
% endif
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
</a>
% 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>
</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>
</h3>
<h3>
<a href="info?rating_key=${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>
</h3>
<h3 class="text-muted">${child['year']}</h3>
</div>
% endif
</li>
% endfor
</ul>
</div>
</div>
</div>
</div>
% endif
% endfor
% endif

View File

@@ -54,6 +54,31 @@ DOCUMENTATION :: END
% if data != None:
% if data['results_count'] > 0:
% if 'collection' in data['results_list'] and data['results_list']['collection']:
<div class="item-children-wrapper">
<div class="item-children-section-title">
<h4>Collections</h4>
</div>
<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']}">
<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>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
<div class="item-children-instance-text-wrapper poster-item">
<h3 title="${child['title']}">${child['title']}</h3>
<h3 class="text-muted">${child['min_year']} - ${child['max_year']}</h3>
</div>
</a>
</li>
% endfor
</ul>
</div>
% endif
% if 'movie' in data['results_list'] and data['results_list']['movie']:
<div class="item-children-wrapper">
<div class="item-children-section-title">
@@ -64,12 +89,12 @@ DOCUMENTATION :: END
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face season-poster" 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(pms_image_proxy?img=${child['thumb']}&width=300&height=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
</div>
<div class="item-children-instance-text-wrapper season-item">
<div class="item-children-instance-text-wrapper poster-item">
<h3 title="${child['title']}">${child['title']}</h3>
<h3 class="text-muted">${child['year']}</h3>
</div>
@@ -89,12 +114,12 @@ DOCUMENTATION :: END
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face season-poster" 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(pms_image_proxy?img=${child['thumb']}&width=300&height=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
</div>
<div class="item-children-instance-text-wrapper season-item">
<div class="item-children-instance-text-wrapper poster-item">
<h3 title="${child['title']}">${child['title']}</h3>
<h3 class="text-muted">${child['year']}</h3>
</div>
@@ -114,12 +139,12 @@ DOCUMENTATION :: END
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face season-poster" 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(pms_image_proxy?img=${child['thumb']}&width=300&height=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
</div>
<div class="item-children-instance-text-wrapper season-item">
<div class="item-children-instance-text-wrapper poster-item">
<h3 title="${child['parent_title']}">${child['parent_title']}</h3>
<h3 class="text-muted">S${child['media_index']}</h3>
</div>
@@ -139,7 +164,7 @@ DOCUMENTATION :: END
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face episode-poster" 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(pms_image_proxy?img=${child['thumb']}&width=500&height=250&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
@@ -165,7 +190,7 @@ DOCUMENTATION :: END
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face album-poster" 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(pms_image_proxy?img=${child['thumb']}&width=300&height=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
@@ -189,7 +214,7 @@ DOCUMENTATION :: END
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face album-poster" 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(pms_image_proxy?img=${child['thumb']}&width=300&height=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
@@ -214,7 +239,7 @@ DOCUMENTATION :: END
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face album-poster" 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(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=300&fallback=cover);">
<div class="item-children-card-overlay">
<div class="item-children-overlay-text">
Track ${child['media_index']}

View File

@@ -68,7 +68,7 @@ media_info_table_options = {
},
{
"targets": [1],
"data": "title",
"data": "sort_title",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== null && cellData !== '') {
var parent_info = '';
@@ -77,31 +77,31 @@ 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">' + cellData + parent_info + '</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>');
} 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">' + cellData + '</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>');
} 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">' + cellData + '</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>');
} 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'] + ' - ' + cellData + '</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>');
} 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">' + cellData + '</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>');
} 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">' + cellData + '</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>');
} 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'] + ' - ' + cellData + '</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>');
} else {
$(td).html(cellData);

View File

@@ -8,17 +8,6 @@
<%def name="body()">
<div class='container-fluid'>
% if config['update_section_ids'] == 1:
<div id="update_section_ids_message" style="text-align: center; margin-top: 20px;">
<i class="fa fa-exclamation-triangle"></i> Tautulli needs to update the Library IDs in your databse. Click the "<strong>Refresh libraries</strong>" button below to begin the update.
</div>
% elif config['update_section_ids'] == -1:
<div id="update_section_ids_message" style="text-align: center; margin-top: 20px;">
<i class="fa fa-refresh fa-spin"></i> Tautulli is updating library IDs in the database. This could take a few minutes to hours depending on the size of your database.
<br />
You may leave this page and come back later.
</div>
% endif
<div class='table-card-header'>
<div class="header-bar">
<span><i class="fa fa-book"></i> All Libraries</span>
@@ -31,16 +20,10 @@
<i class="fa fa-pencil"></i> Edit mode
</button>&nbsp
</div>
% if config['update_section_ids'] == -1:
<div class="btn-group">
<button class="btn btn-dark refresh-libraries-button" id="refresh-libraries-list" disabled><i class="fa fa-refresh"></i> Refresh libraries</button>
</div>
% else:
<div class="btn-group">
<button class="btn btn-dark refresh-libraries-button" id="refresh-libraries-list"><i class="fa fa-refresh"></i> Refresh libraries</button>
</div>
% endif
% endif
<div class="btn-group colvis-button-bar"></div>
</div>
</div>
@@ -197,14 +180,6 @@
% if _session['user_group'] == 'admin':
$("#refresh-libraries-list").click(function () {
if ("${config['update_section_ids']}" == "1") {
$('#update_section_ids_message').html(
'<i class="fa fa-refresh fa-spin"></i> Tautulli is updating library IDs in the database. This could take a few minutes to hours depending on the size of your database.' +
'<br />' +
'You may leave this page and come back later.');
$(this).prop('disabled', true);
}
$.ajax({
url: 'refresh_libraries_list',
cache: false,

View File

@@ -71,11 +71,11 @@ DOCUMENTATION :: END
% endif
</div>
<div class="user-info-nav">
<ul class="user-info-nav">
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li>
<li><a id="history-tab-btn" href="#libraryHistory" data-toggle="tab">History</a></li>
<ul class="user-info-nav" role="tablist">
<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':
<li><a id="media-info-tab-btn" href="#libraryMediaInfo" data-toggle="tab">Media Info</a></li>
<li><a id="media-info-tab-btn" href="#tabs-mediainfo" role="tab" data-toggle="tab">Media Info</a></li>
% endif
</ul>
</div>
@@ -83,7 +83,7 @@ DOCUMENTATION :: END
</div>
</div>
<div class="tab-content">
<div class="tab-pane active" id="profile">
<div role="tabpanel" class="tab-pane active" id="tabs-profile">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@@ -169,7 +169,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
<div class="tab-pane" id="libraryHistory">
<div role="tabpanel" class="tab-pane" id="tabs-history">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@@ -221,7 +221,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
<div class="tab-pane" id="libraryMediaInfo">
<div role="tabpanel" class="tab-pane" id="tabs-mediainfo">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@@ -366,8 +366,253 @@ DOCUMENTATION :: END
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
<script src="${http_root}js/tables/media_info_table.js${cache_param}"></script>
<script>
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
});
function loadHistoryTable() {
// Build watch history table
history_table_options.ajax = {
url: 'get_history',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
section_id: section_id,
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
};
}
}
history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
}
$('a[href="#tabs-history"]').on('shown.bs.tab', function() {
loadHistoryTable();
});
% if _session['user_group'] == 'admin':
function loadMediaInfoTable() {
// Build media info table
media_info_table_options.ajax = {
url: 'get_library_media_info',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
section_id: section_id,
refresh: refresh_table
};
}
}
media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options);
var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
$(colvis.button()).appendTo('#button-bar-media-info');
clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table);
}
$('a[href="#tabs-mediainfo"]').on('shown.bs.tab', function() {
loadMediaInfoTable();
});
$("#refresh-media-info-table").click(function () {
media_info_child_table = {};
refresh_table = true;
refresh_child_tables = true;
media_info_table.draw();
refresh_table = false;
});
$("#edit-library-tooltip").tooltip();
// Load edit library modal
$("#toggle-edit-library-modal").click(function() {
$("#edit-library-tooltip").tooltip('hide');
$.ajax({
url: 'edit_library_dialog',
data: { section_id: section_id },
cache: false,
async: true,
complete: function(xhr, status) {
$("#edit-library-modal").html(xhr.responseText);
}
});
});
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal-delete').modal();
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
history_to_delete.forEach(function(row, idx) {
$.ajax({
url: 'delete_history_rows',
type: 'POST',
data: { row_id: row },
async: true,
success: function (data) {
var msg = "History deleted";
showMsg(msg, false, true, 2000);
}
});
});
history_table.draw();
});
}
$('.delete-control').each(function () {
$(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200);
});
} else {
history_to_delete = [];
$('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden');
});
}
});
% endif
$("#refresh-history-list").click(function () {
history_table.draw();
});
function recentlyWatched() {
// Populate recently watched
$.ajax({
url: 'library_recently_watched',
async: true,
data: {
section_id: section_id,
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
function recentlyAdded() {
// Populate recently added
$.ajax({
url: 'library_recently_added',
async: true,
data: {
section_id: section_id,
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-added").html(xhr.responseText);
highlightAddedScrollerButton();
}
});
}
recentlyWatched();
recentlyAdded();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
function highlightAddedScrollerButton() {
var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-added").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function() {
highlightWatchedScrollerButton();
highlightAddedScrollerButton();
});
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
var leftTotalWatched = 0;
$(".paginate-watched").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#library-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalWatched }, 250);
if (leftTotalWatched == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotalWatched == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
var leftTotalAdded = 0;
$(".paginate-added").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("#library-recently-added").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalAdded }, 250);
if (leftTotalAdded == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotalAdded == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
$(document).ready(function () {
$("#edit-library-tooltip").tooltip();
// Javascript to enable link to tab
var hash = document.location.hash;
var prefix = "tab_";
if (hash) {
$('.user-info-nav a[href='+hash.replace(prefix,"")+']').tab('show').trigger('show.bs.tab');
}
// Change hash for page-reload
$('.user-info-nav a').on('shown.bs.tab', function (e) {
window.location.hash = e.target.hash.replace("#", "#" + prefix);
});
// Populate watch time stats
$.ajax({
@@ -389,237 +634,6 @@ DOCUMENTATION :: END
}
});
function loadHistoryTable() {
// Build watch history table
history_table_options.ajax = {
url: 'get_history',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
section_id: section_id,
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
};
}
}
history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
}
$( "#history-tab-btn" ).one( "click", function() {
loadHistoryTable();
});
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
});
% if _session['user_group'] == 'admin':
function loadMediaInfoTable() {
// Build media info table
media_info_table_options.ajax = {
url: 'get_library_media_info',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
section_id: section_id,
refresh: refresh_table
};
}
}
media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options);
var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
$(colvis.button()).appendTo('#button-bar-media-info');
clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table);
}
$( "#media-info-tab-btn" ).one( "click", function() {
loadMediaInfoTable();
});
$("#refresh-media-info-table").click(function () {
media_info_child_table = {};
refresh_table = true;
refresh_child_tables = true;
media_info_table.draw();
refresh_table = false;
});
// Load edit library modal
$("#toggle-edit-library-modal").click(function() {
$("#edit-library-tooltip").tooltip('hide');
$.ajax({
url: 'edit_library_dialog',
data: { section_id: section_id },
cache: false,
async: true,
complete: function(xhr, status) {
$("#edit-library-modal").html(xhr.responseText);
}
});
});
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal-delete').modal();
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
history_to_delete.forEach(function(row, idx) {
$.ajax({
url: 'delete_history_rows',
type: 'POST',
data: { row_id: row },
async: true,
success: function (data) {
var msg = "History deleted";
showMsg(msg, false, true, 2000);
}
});
});
history_table.draw();
});
}
$('.delete-control').each(function () {
$(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200);
});
} else {
history_to_delete = [];
$('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden');
});
}
});
% endif
$("#refresh-history-list").click(function () {
history_table.draw();
});
function recentlyWatched() {
// Populate recently watched
$.ajax({
url: 'library_recently_watched',
async: true,
data: {
section_id: section_id,
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
function recentlyAdded() {
// Populate recently added
$.ajax({
url: 'library_recently_added',
async: true,
data: {
section_id: section_id,
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-added").html(xhr.responseText);
highlightAddedScrollerButton();
}
});
}
recentlyWatched();
recentlyAdded();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
function highlightAddedScrollerButton() {
var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-added").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function() {
highlightWatchedScrollerButton();
highlightAddedScrollerButton();
});
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
var leftTotalWatched = 0;
$(".paginate-watched").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#library-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalWatched }, 250);
if (leftTotalWatched == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotalWatched == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
var leftTotalAdded = 0;
$(".paginate-added").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("#library-recently-added").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalAdded }, 250);
if (leftTotalAdded == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotalAdded == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
});
</script>
% endif

View File

@@ -44,7 +44,7 @@
<div class="row">
<div class="login-container">
<div class="login-logo">
<img alt="Tautulli" src="${http_root}images/logo-tautulli.png">
<img alt="Tautulli" src="${http_root}images/logo-tautulli.svg" height="100">
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-3">

View File

@@ -142,8 +142,9 @@
<input type="hidden" name="custom_conditions" id="custom_conditions" />
<div class="form-group">
<label for="custom_condition_logic">Condition Logic</label>
<label for="custom_conditions_logic">Condition Logic</label>
<input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" required />
<div id="custom_conditions_logic_error" class="alert alert-danger" role="alert" style="padding-top: 5px; padding-bottom: 5px; margin: 0; display: none;"><i class="fa fa-exclamation-triangle" style="color: #a94442;"></i> <span></span></div>
<p class="help-block">
Enter the logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>).
</p>
@@ -365,8 +366,10 @@
$('input[type=text]').val(function(_, value) {
return $.trim(value);
});
// Reload modal to update certain fields
doAjaxCall('set_notifier_config', $(this), 'tabs', true, true, saveCallback);
if (validateLogic()) {
// Reload modal to update certain fields
doAjaxCall('set_notifier_config', $(this), 'tabs', true, true, saveCallback);
}
}
$('#delete-notifier-item').click(function () {
@@ -466,6 +469,107 @@
})
% endif
function validateLogic() {
const valid_tokens = /(\(|\)|and|or)/g;
const conditions_pattern = /{\d+}/g;
var custom_conditions = $.parseJSON($('#custom_conditions').val());
var custom_conditions_logic = $('#custom_conditions_logic').val();
var num_cond = custom_conditions.length;
var tokens = $.map(custom_conditions_logic.toLowerCase().split(valid_tokens), $.trim).filter(String);
var stack = [[]];
var temp;
var cond_next = true;
var bool_next = false;
var open_bracket_next = true;
var close_bracket_next = false;
var nest_and = 0;
var nest_nest_and = 0;
try {
$.each(tokens, function(i, x) {
if (open_bracket_next && x === '(') {
stack[stack.length-1].push([]);
temp = stack[stack.length-1];
stack.push(temp[temp.length-1]);
cond_next = true;
bool_next = false;
open_bracket_next = true;
close_bracket_next = false;
if (nest_and) {
nest_nest_and += 1
}
} else if (close_bracket_next && x === ')') {
stack.pop();
if (stack.length === 0) {
throw 'opening bracket is missing';
}
cond_next = false;
bool_next = true;
open_bracket_next = false;
close_bracket_next = true;
if (nest_and > 0 && nest_nest_and > 0 && nest_and === nest_nest_and) {
stack.pop();
nest_and -= 1;
nest_nest_and -= 1;
}
} else if (cond_next && x.match(conditions_pattern)) {
if (isNaN(x.slice(1, -1))) {
throw 'invalid condition logic'
} else {
var num = parseInt(x.slice(1, -1));
}
if (!(0 < num && num <= num_cond)) {
throw 'invalid condition number in condition logic'
}
stack[stack.length-1].push(num);
cond_next = false;
bool_next = true;
open_bracket_next = false;
close_bracket_next = true;
if (nest_and > nest_nest_and) {
stack.pop();
nest_and -= 1;
}
} else if (bool_next && x === 'and' && i < tokens.length-1) {
stack[stack.length-1].push([]);
temp = stack[stack.length-1];
stack.push(temp[temp.length-1]);
temp = stack[stack.length-2];
stack[stack.length-1].push(temp.splice(0, temp.length-2) + temp.splice(temp.length-2, temp.length-1));
stack[stack.length-1].push(x);
cond_next = true;
bool_next = false;
open_bracket_next = true;
close_bracket_next = false;
nest_and += 1;
} else if (bool_next && x === 'or' && i < tokens.length-1) {
stack[stack.length-1].push(x);
cond_next = true;
bool_next = false;
open_bracket_next = true;
close_bracket_next = false;
} else {
throw 'invalid condition logic';
}
});
if (stack.length > 1) {
throw 'closing bracket is missing';
}
$('#custom_conditions_logic_error').hide();
return true;
} catch (e) {
$('#custom_conditions_logic_error span').text(e);
$('#custom_conditions_logic_error').show();
showMsg('<i class="fa fa-times"></i> Failed to save notifier. Invalid condition logic.', false, true, 5000, true);
}
}
$('.notifier-text-preview').click(function () {
var action = $(this).data('action');
var subject = $('#' + action + '_subject').val();
@@ -564,10 +668,10 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title" id="notifier-config-modal-header">Error</h4>
</div>
<div class="modal-body">
<center><strong>
<div class="modal-body" style="text-align: center">
<strong>
<i class="fa fa-exclamation-circle"></i> Failed to retrieve notifier configuration. Check the <a href="logs">logs</a> for more info.
</strong></center>
</strong>
</div>
<div class="modal-footer">
</div>

View File

@@ -18,7 +18,7 @@
</div>
</div>
<div class='table-card-back'>
<div id="search-results-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading search results...</div>
<div id="search-results-list" class="search-results-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading search results...</div>
</div>
</div>
</%def>

View File

@@ -357,6 +357,12 @@
</div>
<p class="help-block">The base URL of the web server. Used for reverse proxies.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" class="http-settings" name="http_proxy" id="http_proxy" value="1" ${config['http_proxy']}> Enable HTTP Proxy
</label>
<p class="help-block">Respect the X-Forwarded-Proto header. Used for reverse proxies with SSL.</p>
</div>
<br />
<div class="checkbox">
<label>
@@ -553,7 +559,7 @@
<p class="help-block">Enable to have Tautulli check if remote access to the Plex Media Server goes down.</p>
</div>
<div class="form-group has-feedback" id="pms-ip-group">
<div class="form-group has-feedback" id="pms_ip_group">
<label for="pms_ip">Plex IP or Hostname</label>
<div class="row">
<div class="col-md-6">
@@ -563,7 +569,7 @@
<button class="btn btn-form" type="button" id="verify_server_button">Verify Server</button>
</span>
</div>
<span class="form-control-feedback" id="pms-verify" aria-hidden="true" style="display: none; right: 110px;"></span>
<span class="form-control-feedback" id="pms_verify" aria-hidden="true" style="display: none; right: 110px;"></span>
</div>
<div id="pms_ip_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
@@ -601,7 +607,12 @@
<label for="pms_logs_folder">Plex Web URL</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="pms_web_url" name="pms_web_url" value="${config['pms_web_url']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^https?:\/\/.+\/web\/index\.html$|^https:\/\/app.plex.tv\/desktop$" data-parsley-errors-container="#pms_web_url_error" data-parsley-error-message="Invalid Plex Web URL." required>
<div class="input-group">
<input type="text" class="form-control" id="pms_web_url" name="pms_web_url" value="${config['pms_web_url']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^https?:\/\/\S+$|^https:\/\/app.plex.tv\/desktop$" data-parsley-errors-container="#pms_web_url_error" data-parsley-error-message="Invalid Plex Web URL.">
<span class="input-group-btn">
<button class="btn btn-form" type="button" id="test_pms_web_button">Test URL</button>
</span>
</div>
</div>
<div id="pms_web_url_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
@@ -827,7 +838,7 @@
<div class="checkbox">
<label>
<input type="checkbox" name="notify_upload_posters" id="notify_upload_posters" value="1" ${config['notify_upload_posters']}> Enable Posters in Notifications
<input type="checkbox" name="notify_upload_posters" id="notify_upload_posters" value="1" ${config['notify_upload_posters']}> Upload Posters to Imgur for Notifications
</label>
<p class="help-block">Enable to upload Plex posters to Imgur for notifications. Disable if posters are not being used to save bandwidth.</p>
</div>
@@ -848,13 +859,13 @@
<label>
<input type="checkbox" name="themoviedb_lookup" id="themoviedb_lookup" value="1" ${config['themoviedb_lookup']}> Lookup TheMovieDB Links
</label>
<p class="help-block">Enable to lookup links to TheMovieDB for movies and TV shows when available.</p>
<p class="help-block">Enable to lookup links to TheMovieDB (and IMDb if needed) for movies and TV shows when available.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="tvmaze_lookup" id="tvmaze_lookup" value="1" ${config['tvmaze_lookup']}> Lookup TVmaze Links
</label>
<p class="help-block">Enable to lookup links to TVmaze for TV shows when available.</p>
<p class="help-block">Enable to lookup links to TVmaze (and IMDb if needed) for TV shows when available.</p>
</div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
@@ -884,12 +895,6 @@
<h3>Extra Settings</h3>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="pms_use_bif" name="pms_use_bif" value="1" ${config['pms_use_bif']}> Use Video Preview Thumbnails (BIF)
</label>
<p class="help-block">If you have media indexing enabled on your server, use these on the activity pane.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="get_file_sizes" name="get_file_sizes" value="1" ${config['get_file_sizes']}> Calculate Total File Sizes <span style="color: #eb8600; padding-left: 10px;">[experimental]</span>
@@ -1061,6 +1066,8 @@
</div>
</div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div>
<div role="tabpanel" class="tab-pane" id="tabs-android_app">
@@ -1578,6 +1585,17 @@ $(document).ready(function() {
}
}
function preSaveChecks(_callback) {
if ($("#pms_identifier").val() == "") {
verifyServer();
}
verifyPMSWebURL();
if (_callback) {
_callback();
}
}
// Alert the user that their changes require a restart.
function postSaveChecks() {
if (serverChanged || authChanged || httpChanged || directoryChanged) {
@@ -1607,11 +1625,7 @@ $(document).ready(function() {
}
$('.save-button').click(function() {
if ($("#pms_identifier").val() == "") {
verifyServer(function () { saveSettings() });
} else {
saveSettings();
}
preSaveChecks(function () { saveSettings() });
});
initConfigCheckbox('#api_enabled');
@@ -1756,43 +1770,45 @@ $(document).ready(function() {
var pms_identifier = $("#pms_identifier").val();
var pms_ssl = $("#pms_ssl").is(':checked') ? 1 : 0;
var pms_is_remote = $("#pms_is_remote").is(':checked') ? 1 : 0;
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
$("#pms-verify").html('<i class="fa fa-refresh fa-spin"></i>');
$('#pms-verify').fadeIn('fast');
$("#pms_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
$.ajax({
url: 'get_server_id',
data : { hostname: pms_ip, port: pms_port, identifier: pms_identifier, ssl: pms_ssl, remote: pms_is_remote },
data: {
hostname: pms_ip,
port: pms_port,
identifier: pms_identifier,
ssl: pms_ssl,
remote: pms_is_remote
},
cache: true,
async: true,
timeout: 10000,
error: function(jqXHR, textStatus, errorThrown) {
$("#pms-verify").html('<i class="fa fa-close"></i>');
$('#pms-verify').fadeIn('fast');
$("#pms-ip-group").addClass("has-error");
error: function (jqXHR, textStatus, errorThrown) {
$("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
$("#pms_ip_group").addClass("has-error");
},
success: function (json) {
var machine_identifier = json;
if (machine_identifier) {
$("#pms_identifier").val(machine_identifier);
$("#pms-verify").html('<i class="fa fa-check"></i>');
$('#pms-verify').fadeIn('fast');
$("#pms-ip-group").removeClass("has-error");
$("#pms_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
$("#pms_ip_group").removeClass("has-error");
if (_callback) {
_callback();
}
} else {
$("#pms-verify").html('<i class="fa fa-close"></i>');
$('#pms-verify').fadeIn('fast');
$("#pms-ip-group").addClass("has-error");
$("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
$("#pms_ip_group").addClass("has-error");
showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 5000, true)
}
}
});
} else {
$("#pms-verify").html('<i class="fa fa-close"></i>');
$('#pms-verify').fadeIn('fast');
$("#pms-ip-group").addClass("has-error");
$("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
$("#pms_ip_group").addClass("has-error");
showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 5000, true)
}
}
@@ -1802,11 +1818,21 @@ $(document).ready(function() {
verifyServer();
});
function verifyPMSWebURL() {
var pms_web_url = $.trim($("#pms_web_url").val());
$("#pms_web_url").val(pms_web_url || 'https://app.plex.tv/desktop');
}
$('#test_pms_web_button').on('click', function(){
var pms_web_url = $.trim($("#pms_web_url").val());
window.open(pms_web_url, '_blank');
});
// Plex.tv auth token fetch
$("#get-pms-auth-token").click(function() {
$("#pms-token-status").html('<i class="fa fa-refresh fa-spin"></i> Fetching token...');
var pms_username = $("#pms_username").val().trim();
var pms_password = $("#pms_password").val().trim();
var pms_username = $.trim($("#pms_username").val());
var pms_password = $.trim($("#pms_password").val());
if ((pms_username !== '') && (pms_password !== '')) {
$.ajax({
type: 'GET',

View File

@@ -38,6 +38,9 @@ DOCUMENTATION :: END
</%doc>
% if data:
<%
import plexpy
%>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -51,86 +54,177 @@ DOCUMENTATION :: END
</h4>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<h4><strong>Stream Details</strong></h4>
</div>
<div class="col-sm-4">
<h5>Media</h5>
<ul class="list-unstyled">
<li>Container: <strong>${data['transcode_container'] if data['transcode_container'] else data['container']}</strong></li>
% if data['media_type'] != 'track':
<li>Resolution: <strong>${data['transcode_height'] if data['transcode_height'] else data['height']}p</strong></li>
% endif
</ul>
</div>
<table class="stream-info" style="margin-top: 0;">
<thead>
<tr>
<th>
</th>
<th class="heading">
Stream Details
</th>
<th class="heading">
Source Details
</th>
</tr>
</thead>
</table>
<table class="stream-info">
<thead>
<tr>
<th>
Media
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bitrate</td>
<td>${data['stream_bitrate']} kbps</td>
<td>${data['bitrate']} kbps</td>
</tr>
% if data['media_type'] != 'track':
<div class="col-sm-4">
<h5>Video</h5>
<ul class="list-unstyled">
<li>Stream: <strong>${data['transcode_video_dec']}</strong></li>
% if data['transcode_video_dec'] != 'direct play':
<li>Width: <strong>${data['transcode_width']}</strong></li>
<li>Height: <strong>${data['transcode_height']}</strong></li>
<li>Codec: <strong>${data['transcode_video_codec']}</strong></li>
% else:
<li>Width: <strong>${data['width']}</strong></li>
<li>Height: <strong>${data['height']}</strong></li>
<li>Codec: <strong>${data['video_codec']}</strong></li>
% endif
</ul>
</div>
<tr>
<td>Resolution</td>
<td>${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])}</td>
<td>${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])}</td>
</tr>
% endif
<div class="col-sm-4">
<h5>Audio</h5>
<ul class="list-unstyled">
<li>Stream: <strong>${data['transcode_audio_dec']}</strong></li>
% if data['transcode_audio_dec'] != 'direct play':
<li>Codec: <strong>${data['transcode_audio_codec']}</strong></li>
<li>Channels: <strong>${data['transcode_audio_channels']}</strong></li>
% else:
<li>Codec: <strong>${data['audio_codec']}</strong></li>
<li>Channels: <strong>${data['audio_channels']}</strong></li>
% endif
</ul>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h4><strong>Source Details</strong></h4>
</div>
<div class="col-sm-4">
<h5>Media</h5>
<ul class="list-unstyled">
<li>Container: <strong>${data['container']}</strong></li>
% if data['media_type'] != 'track':
<li>Resolution: <strong>${data['video_resolution'] + 'p' if data['video_resolution'] != 'sd' else data['video_resolution']}</strong></li>
% endif
<li>Bitrate: <strong>${data['bitrate']} kbps</strong></li>
</ul>
</div>
% if data['media_type'] != 'track':
<div class="col-sm-4">
<h5>Video</h5>
<ul class="list-unstyled">
<li>Width: <strong>${data['width']}</strong></li>
<li>Height: <strong>${data['height']}</strong></li>
<li>Codec: <strong>${data['video_codec']}</strong></li>
<li>Aspect Ratio: <strong>${data['aspect_ratio']}</strong></li>
<li>Frame Rate: <strong>${data['video_framerate']}</strong></li>
</ul>
</div>
<tr>
<td>Quality</td>
<td>${data['quality_profile']}</td>
<td>-</td>
</tr>
% if data['optimized_version'] == 1:
<tr>
<td>Optimized Version</td>
<td>-</td>
<td>${data['optimized_version_profile']}<br>(${data['optimized_version_title']})</td>
</tr>
% endif
<div class="col-sm-4">
<h5>Audio</h5>
<ul class="list-unstyled">
<li>Codec: <strong>${data['audio_codec']}</strong></li>
<li>Channels: <strong>${data['audio_channels']}</strong></li>
</ul>
</div>
</div>
</div>
% if data['synced_version'] == 1:
<tr>
<td>Synced Version</td>
<td>-</td>
<td>${data['synced_version_profile']}</td>
</tr>
% endif
</tbody>
</table>
<table class="stream-info">
<thead>
<tr>
<th>
Container
</th>
<th>
${data['stream_container_decision']}
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Container</td>
<td>${data['stream_container']}</td>
<td>${data['container']}</td>
</tr>
</tbody>
</table>
% if data['media_type'] != 'track':
<table class="stream-info">
<thead>
<tr>
<th>
Video
</th>
<th>
${data['stream_video_decision']}
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Codec</td>
<td>${data['stream_video_codec']}</td>
<td>${data['video_codec']}</td>
</tr>
<tr>
<td>Bitrate</td>
<td>${data['stream_video_bitrate']} kbps</td>
<td>${data['video_bitrate']} kbps</td>
</tr>
<tr>
<td>Width</td>
<td>${data['stream_video_width']}</td>
<td>${data['video_width']}</td>
</tr>
<tr>
<td>Height</td>
<td>${data['stream_video_height']}</td>
<td>${data['video_height']}</td>
</tr>
<tr>
<td>Framerate</td>
<td>${data['stream_video_framerate']}</td>
<td>${data['video_framerate']}</td>
</tr>
<tr>
<td>Aspect Ratio</td>
<td>-</td>
<td>${data['aspect_ratio']}</td>
</tr>
</tbody>
</table>
% endif
<table class="stream-info">
<thead>
<tr>
<th>
Audio
</th>
<th>
${data['stream_audio_decision']}
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Codec</td>
<td>${data['stream_audio_codec']}</td>
<td>${data['audio_codec']}</td>
</tr>
<tr>
<td>Bitrate</td>
<td>${data['stream_audio_bitrate']} kbps</td>
<td>${data['audio_bitrate']} kbps</td>
</tr>
<tr>
<td>Channels</td>
<td>${data['stream_audio_channels']}</td>
<td>${data['audio_channels']}</td>
</tr>
</tbody>
</table>
% if data['subtitles'] == 1:
<table class="stream-info">
<thead>
<tr>
<th>
Subtitles
</th>
<th>
${data['stream_subtitle_decision']}
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Codec</td>
<td>${data['stream_subtitle_codec']}</td>
<td>${data['subtitle_codec']}</td>
</tr>
</tbody>
</table>
% endif
</div>
<div class="modal-footer">
</div>

View File

@@ -61,19 +61,19 @@ DOCUMENTATION :: END
% endif
</div>
<div class="user-info-nav">
<ul class="user-info-nav">
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li>
<li><a id="history-tab-btn" href="#userHistory" data-toggle="tab">History</a></li>
<li><a id="sync-tab-btn" href="#userSyncItems" data-toggle="tab">Synced Items</a></li>
<li><a id="ip-tab-btn" href="#userAddresses" data-toggle="tab">IP Addresses</a></li>
<li><a id="login-tab-btn" href="#userLogins" data-toggle="tab">Tautulli Logins</a></li>
<ul class="user-info-nav" role="tablist">
<li class="active"><a href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
<li><a id="history-tab-btn" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
<li><a id="sync-tab-btn" href="#tabs-synceditems" role="tab" data-toggle="tab">Synced Items</a></li>
<li><a id="ip-tab-btn" href="#tabs-ipaddresses" role="tab" data-toggle="tab">IP Addresses</a></li>
<li><a id="login-tab-btn" href="#tabs-tautullilogins" role="tab" data-toggle="tab">Tautulli Logins</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="tab-content">
<div class="tab-pane active" id="profile">
<div role="tabpanel" class="tab-pane active" id="tabs-profile">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@@ -134,7 +134,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
<div class="tab-pane" id="userHistory">
<div role="tabpanel" class="tab-pane" id="tabs-history">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@@ -200,7 +200,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
<div class="tab-pane" id="userSyncItems">
<div role="tabpanel" class="tab-pane" id="tabs-synceditems">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@@ -240,7 +240,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
<div class="tab-pane" id="userAddresses">
<div role="tabpanel" class="tab-pane" id="tabs-ipaddresses">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@@ -271,7 +271,7 @@ DOCUMENTATION :: END
</div>
</div>
</div>
<div class="tab-pane" id="userLogins">
<div role="tabpanel" class="tab-pane" id="tabs-tautullilogins">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@@ -369,23 +369,246 @@ DOCUMENTATION :: END
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
% if data:
<script>
% if str(data['user_id']).isdigit():
var user_id = ${data['user_id']};
% else:
var user_id = null;
% endif
var username = '${data['username'].replace("'", "\\'")}';
</script>
<script src="${http_root}js/moment-with-locale.js"></script>
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
<script src="${http_root}js/tables/user_ips.js${cache_param}"></script>
<script src="${http_root}js/tables/sync_table.js${cache_param}"></script>
<script src="${http_root}js/tables/login_logs.js${cache_param}"></script>
<script>
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
});
$('a[href="#tabs-profile"]').on('shown.bs.tab', function() {
var media_type = null;
loadHistoryTable(media_type);
});
function loadHistoryTable(media_type) {
// Build watch history table
history_table_options.ajax = {
url: 'get_history',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
user_id: user_id,
media_type: media_type
};
}
}
history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
history_table.column(2).visible(false);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
$('#media_type-selection').on('change', function () {
$('#media_type-selection > label').removeClass('active');
selected_filter = $('input[name=media_type-filter]:checked', '#media_type-selection');
$(selected_filter).closest('label').addClass('active');
media_type = $(selected_filter).val();
history_table.draw();
});
}
$('a[href="#tabs-history"]').on('shown.bs.tab', function() {
var media_type = null;
loadHistoryTable(media_type);
});
$('a[href="#tabs-synceditems"]').on('shown.bs.tab', function() {
// Build user sync table
sync_table_options.ajax = {
url: 'get_sync',
data: function(d) {
d.user_id = user_id;
}
}
sync_table = $('#sync_table-UID-${data["user_id"]}').DataTable(sync_table_options);
sync_table.column(1).visible(false);
var colvis_sync = new $.fn.dataTable.ColVis( sync_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_sync.button() ).appendTo('#button-bar-sync');
clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
});
$('a[href="#tabs-ipaddresses"]').on('shown.bs.tab', function() {
// Build user IP table
user_ip_table_options.ajax = {
url: 'get_user_ips',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
user_id: user_id
};
}
}
user_ip_table = $('#user_ip_table-UID-${data["user_id"]}').DataTable(user_ip_table_options);
clearSearchButton('user_ip_table-UID-${data["user_id"]}', user_ip_table);
});
$('a[href="#tabs-tautullilogins"]').on('shown.bs.tab', function() {
// Build user login table
login_log_table_options.ajax = {
url: 'get_user_logins',
data: function(d) {
d.user_id = user_id;
}
}
login_log_table = $('#login_log_table-UID-${data["user_id"]}').DataTable(login_log_table_options);
login_log_table.columns([1, 2]).visible(false);
var colvis_login = new $.fn.dataTable.ColVis( login_log_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_login.button() ).appendTo('#button-bar-login');
clearSearchButton('login_log_table-UID-${data["user_id"]}', login_log_table);
});
% if _session['user_group'] == 'admin':
$("#edit-user-tooltip").tooltip();
// Load edit user modal
$("#toggle-edit-user-modal").click(function() {
$("#edit-user-tooltip").tooltip('hide');
$.ajax({
url: 'edit_user_dialog',
data: { user_id: user_id },
cache: false,
async: true,
complete: function(xhr, status) {
$("#edit-user-modal").html(xhr.responseText);
}
});
});
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal-delete').modal();
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
history_to_delete.forEach(function(row, idx) {
$.ajax({
url: 'delete_history_rows',
type: 'POST',
data: { row_id: row },
async: true,
success: function (data) {
var msg = "History deleted";
showMsg(msg, false, true, 2000);
}
});
});
history_table.draw();
});
}
$('.delete-control').each(function () {
$(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200);
});
} else {
history_to_delete = [];
$('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden');
});
}
});
% endif
$("#refresh-history-list").click(function () {
history_table.draw();
});
function recentlyWatched() {
// Populate recently watched
$.ajax({
url: 'get_user_recently_watched',
async: true,
data: {
user_id: user_id,
limit: 50
},
complete: function(xhr, status) {
$("#user-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
recentlyWatched();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#user-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
$(window).resize(function() {
highlightWatchedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#user-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
$(document).ready(function () {
% if str(data['user_id']).isdigit():
var user_id = ${data['user_id']};
% else:
var user_id = null;
% endif
// Javascript to enable link to tab
var hash = document.location.hash;
var prefix = "tab_";
if (hash) {
$('.user-info-nav a[href='+hash.replace(prefix,"")+']').tab('show').trigger('show.bs.tab');
}
var username = '${data['username'].replace("'", "\\'")}';
$("#edit-user-tooltip").tooltip();
// Change hash for page-reload
$('.user-info-nav a').on('shown.bs.tab', function (e) {
window.location.hash = e.target.hash.replace("#", "#" + prefix);
});
// Populate watch time stats
$.ajax({
@@ -407,210 +630,6 @@ DOCUMENTATION :: END
}
});
function loadHistoryTable(media_type) {
// Build watch history table
history_table_options.ajax = {
url: 'get_history',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
user_id: user_id,
media_type: media_type
};
}
}
history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
history_table.column(2).visible(false);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
$('#media_type-selection').on('change', function () {
$('#media_type-selection > label').removeClass('active');
selected_filter = $('input[name=media_type-filter]:checked', '#media_type-selection');
$(selected_filter).closest('label').addClass('active');
media_type = $(selected_filter).val();
history_table.draw();
});
}
$( "#history-tab-btn" ).one( "click", function() {
var media_type = null;
loadHistoryTable(media_type);
});
$( "#ip-tab-btn" ).one( "click", function() {
// Build user IP table
user_ip_table_options.ajax = {
url: 'get_user_ips',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
user_id: user_id
};
}
}
user_ip_table = $('#user_ip_table-UID-${data["user_id"]}').DataTable(user_ip_table_options);
clearSearchButton('user_ip_table-UID-${data["user_id"]}', user_ip_table);
});
$( "#sync-tab-btn" ).one( "click", function() {
// Build user sync table
sync_table_options.ajax = {
url: 'get_sync',
data: function(d) {
d.user_id = user_id;
}
}
sync_table = $('#sync_table-UID-${data["user_id"]}').DataTable(sync_table_options);
sync_table.column(1).visible(false);
var colvis_sync = new $.fn.dataTable.ColVis( sync_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_sync.button() ).appendTo('#button-bar-sync');
clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
});
$( "#login-tab-btn" ).one( "click", function() {
// Build user login table
login_log_table_options.ajax = {
url: 'get_user_logins',
data: function(d) {
d.user_id = user_id;
}
}
login_log_table = $('#login_log_table-UID-${data["user_id"]}').DataTable(login_log_table_options);
login_log_table.columns([1, 2]).visible(false);
var colvis_login = new $.fn.dataTable.ColVis( login_log_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_login.button() ).appendTo('#button-bar-login');
clearSearchButton('login_log_table-UID-${data["user_id"]}', login_log_table);
});
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
});
% if _session['user_group'] == 'admin':
// Load edit user modal
$("#toggle-edit-user-modal").click(function() {
$("#edit-user-tooltip").tooltip('hide');
$.ajax({
url: 'edit_user_dialog',
data: { user_id: user_id },
cache: false,
async: true,
complete: function(xhr, status) {
$("#edit-user-modal").html(xhr.responseText);
}
});
});
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal-delete').modal();
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
history_to_delete.forEach(function(row, idx) {
$.ajax({
url: 'delete_history_rows',
type: 'POST',
data: { row_id: row },
async: true,
success: function (data) {
var msg = "History deleted";
showMsg(msg, false, true, 2000);
}
});
});
history_table.draw();
});
}
$('.delete-control').each(function () {
$(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200);
});
} else {
history_to_delete = [];
$('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden');
});
}
});
% endif
$("#refresh-history-list").click(function () {
history_table.draw();
});
function recentlyWatched() {
// Populate recently watched
$.ajax({
url: 'get_user_recently_watched',
async: true,
data: {
user_id: user_id,
limit: 50
},
complete: function(xhr, status) {
$("#user-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
recentlyWatched();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#user-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
$(window).resize(function() {
highlightWatchedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#user-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
});
</script>
% endif

View File

@@ -83,7 +83,7 @@
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
<script src="${http_root}js/moment-with-locale.js"></script>
<script src="${http_root}js/tables/users.js"></script>
<script src="${http_root}js/tables/users.js${cache_param}"></script>
<script>
$(document).ready(function () {
users_list_table_options.ajax = {

View File

@@ -164,12 +164,15 @@
<!-- Required fields but hidden -->
<div style="display: none;">
<input type="checkbox" name="first_run" id="first_run" value="1" checked>
<input type="checkbox" name="launch_browser" id="launch_browser" value="1" ${config['launch_browser']}>
<input type="checkbox" name="refresh_users_on_startup" id="refresh_users_on_startup" value="1" ${config['refresh_users_on_startup']}>
<input type="checkbox" name="refresh_libraries_on_startup" id="refresh_libraries_on_startup" value="1" ${config['refresh_libraries_on_startup']}>
<input type="checkbox" name="check_github" id="check_github" value="1" ${config['check_github']}>
<input type="checkbox" name="log_blacklist" id="log_blacklist" value="1" ${config['log_blacklist']}>
<input type="checkbox" name="cache_images" id="cache_images" value="1" ${config['cache_images']}>
<input type="checkbox" name="group_history_tables" id="group_history_tables" value="1" checked>
<input type="checkbox" name="history_table_activity" id="history_table_activity" value="1" checked>
<input type="checkbox" name="launch_browser" id="launch_browser" value="1" checked>
<input type="checkbox" name="api_enabled" id="api_enabled" value="1" checked>
<input type="checkbox" name="refresh_users_on_startup" id="refresh_users_on_startup" value="1" checked>
<input type="checkbox" name="refresh_libraries_on_startup" id="refresh_libraries_on_startup" value="1" checked>
<input type="checkbox" name="check_github" id="check_github" value="1" checked>
<input type="checkbox" name="log_blacklist" id="log_blacklist" value="1" checked>
<input type="checkbox" name="cache_images" id="cache_images" value="1" checked>
<input type="checkbox" name="server_changed" id="server_changed" value="1" checked>
<input type="checkbox" name="first_run_complete" id="first_run_complete" value="1" checked>
<input type="text" name="home_stats_cards" id="home_stats_cards" value="first_run_wizard">

View File

@@ -172,10 +172,18 @@ def initialize(config_file):
# Check if Tautulli has a uuid
if CONFIG.PMS_UUID == '' or not CONFIG.PMS_UUID:
logger.debug(u"Generating UUID...")
my_uuid = generate_uuid()
CONFIG.__setattr__('PMS_UUID', my_uuid)
CONFIG.write()
# Check if Tautulli has an API key
if CONFIG.API_KEY == '':
logger.debug(u"Generating API key...")
api_key = generate_uuid()
CONFIG.__setattr__('API_KEY', api_key)
CONFIG.write()
# Get the currently installed version. Returns None, 'win32' or the git
# hash.
CURRENT_VERSION, CONFIG.GIT_REMOTE, CONFIG.GIT_BRANCH = versioncheck.getVersion()
@@ -430,7 +438,7 @@ def dbcheck():
'parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
'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_codec TEXT, video_bitrate INTEGER, video_resolution TEXT, video_width INTEGER, video_height INTEGER, '
'video_framerate TEXT, aspect_ratio TEXT, '
'audio_codec TEXT, audio_bitrate INTEGER, audio_channels INTEGER, subtitle_codec TEXT, '
'stream_bitrate INTEGER, stream_video_resolution TEXT, quality_profile TEXT, '
@@ -438,11 +446,12 @@ def dbcheck():
'stream_video_decision TEXT, stream_video_codec TEXT, stream_video_bitrate INTEGER, stream_video_width INTEGER, '
'stream_video_height INTEGER, stream_video_framerate TEXT, '
'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, '
'stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, '
'subtitles INTEGER, stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, '
'transcode_protocol TEXT, transcode_container TEXT, '
'transcode_video_codec TEXT, transcode_audio_codec TEXT, transcode_audio_channels INTEGER,'
'transcode_width INTEGER, transcode_height INTEGER, '
'optimized_version INTEGER, optimized_version_profile TEXT, synced_version INTEGER, '
'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT, '
'synced_version INTEGER, synced_version_profile TEXT, '
'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, write_attempts INTEGER DEFAULT 0, '
'raw_stream_info TEXT)'
)
@@ -474,7 +483,8 @@ def dbcheck():
'stream_video_framerate TEXT, '
'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, '
'stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, stream_subtitle_container TEXT, stream_subtitle_forced INTEGER, '
'subtitles INTEGER, synced_version INTEGER, optimized_version INTEGER, optimized_version_profile TEXT)'
'subtitles INTEGER, subtitle_codec TEXT, synced_version INTEGER, synced_version_profile TEXT, '
'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT)'
)
# session_history_metadata table :: This is a table which logs each session's media metadata
@@ -884,6 +894,36 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN raw_stream_info TEXT'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT video_height FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN video_height INTEGER'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT subtitles FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN subtitles INTEGER'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT synced_version_profile FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN synced_version_profile TEXT'
)
c_db.execute(
'ALTER TABLE sessions ADD COLUMN optimized_version_title TEXT'
)
# Upgrade session_history table from earlier versions
try:
c_db.execute('SELECT reference_id FROM session_history')
@@ -1105,6 +1145,27 @@ def dbcheck():
'UPDATE session_history_media_info SET video_resolution=REPLACE(video_resolution, "SD", "sd")'
)
# Upgrade session_history_media_info table from earlier versions
try:
c_db.execute('SELECT subtitle_codec FROM session_history_media_info')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table session_history_media_info.")
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN subtitle_codec TEXT '
)
# Upgrade session_history_media_info table from earlier versions
try:
c_db.execute('SELECT synced_version_profile FROM session_history_media_info')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table session_history_media_info.")
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN synced_version_profile TEXT '
)
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN optimized_version_title TEXT '
)
# Upgrade users table from earlier versions
try:
c_db.execute('SELECT do_notify FROM users')
@@ -1482,5 +1543,4 @@ def shutdown(restart=False, update=False, checkout=False):
def generate_uuid():
logger.debug(u"Generating UUID...")
return uuid.uuid4().hex

View File

@@ -484,6 +484,11 @@ def on_created(rating_key, **kwargs):
if metadata:
notify = True
now = int(time.time())
if helpers.cast_to_int(metadata['updated_at']) < now - 86400: # Updated more than 24 hours ago
logger.debug(u"Tautulli TimelineHandler :: Library item %s updated more than 24 hours ago. Not notifying." % str(rating_key))
notify = False
data_factory = datafactory.DataFactory()
if 'child_keys' not in kwargs:

View File

@@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
from collections import defaultdict
import json
import threading
import time
@@ -89,9 +90,11 @@ class ActivityProcessor(object):
'transcode_audio_channels': session.get('transcode_audio_channels', ''),
'transcode_width': session.get('stream_video_width', ''),
'transcode_height': session.get('stream_video_height', ''),
'synced_version': session.get('synced_version', ''),
'synced_version_profile': session.get('synced_version_profile', ''),
'optimized_version': session.get('optimized_version', ''),
'optimized_version_profile': session.get('optimized_version_profile', ''),
'synced_version': session.get('synced_version', ''),
'optimized_version_title': session.get('optimized_version_title', ''),
'stream_bitrate': session.get('stream_bitrate', ''),
'stream_video_resolution': session.get('stream_video_resolution', ''),
'quality_profile': session.get('quality_profile', ''),
@@ -109,6 +112,7 @@ class ActivityProcessor(object):
'stream_audio_channels': session.get('stream_audio_channels', ''),
'stream_subtitle_decision': session.get('stream_subtitle_decision', ''),
'stream_subtitle_codec': session.get('stream_subtitle_codec', ''),
'subtitles': session.get('subtitles', ''),
'raw_stream_info': json.dumps(session),
'stopped': int(time.time())
}
@@ -153,9 +157,11 @@ class ActivityProcessor(object):
logging_enabled = False
# Reload json from raw stream info
if 'raw_stream_info' in session:
if session.get('raw_stream_info'):
session.update(json.loads(session['raw_stream_info']))
session = defaultdict(str, session)
if is_import:
if str(session['stopped']).isdigit():
stopped = int(session['stopped'])
@@ -353,8 +359,10 @@ class ActivityProcessor(object):
'stream_subtitle_forced': session['stream_subtitle_forced'],
'subtitles': session['subtitles'],
'synced_version': session['synced_version'],
'synced_version_profile': session['synced_version_profile'],
'optimized_version': session['optimized_version'],
'optimized_version_profile': session['optimized_version_profile']
'optimized_version_profile': session['optimized_version_profile'],
'optimized_version_title': session['optimized_version_title']
}
# logger.debug(u"Tautulli ActivityProcessor :: Writing session_history_media_info transaction...")

View File

@@ -68,11 +68,12 @@ PLATFORM_NAMES = {'android': 'android',
'safari': 'safari',
'samsung': 'samsung',
'synclounge': 'synclounge',
'tivo': 'tivo',
'tvos': 'atv',
'vizio': 'opera',
'wiiu': 'wiiu',
'windows': 'windows',
'windows phone': 'wp',
'wiiu': 'wiiu',
'xbmc': 'xbmc',
'xbox': 'xbox'
}

View File

@@ -66,7 +66,7 @@ _CONFIG_DEFINITIONS = {
'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'),
'TIME_FORMAT': (str, 'General', 'HH:mm'),
'ANON_REDIRECT': (str, 'General', 'http://www.nullrefer.com/?'),
'API_ENABLED': (int, 'General', 0),
'API_ENABLED': (int, 'General', 1),
'API_KEY': (str, 'General', ''),
'API_SQL': (int, 'General', 0),
'BOXCAR_ENABLED': (int, 'Boxcar', 0),
@@ -863,7 +863,6 @@ class Config(object):
self.NOTIFY_GROUP_RECENTLY_ADDED_PARENT = self.NOTIFY_GROUP_RECENTLY_ADDED
self.MONITORING_USE_WEBSOCKET = 1
self.HTTP_PROXY = 1
self.CONFIG_VERSION = 8
@@ -872,4 +871,4 @@ class Config(object):
self.TV_WATCHED_PERCENT = self.NOTIFY_WATCHED_PERCENT
self.MUSIC_WATCHED_PERCENT = self.NOTIFY_WATCHED_PERCENT
self.CONFIG_VERSION == 9
self.CONFIG_VERSION = 9

View File

@@ -872,9 +872,16 @@ class DataFactory(object):
user_cond = 'AND %s.user_id = %s ' % (table, session.get_session_user_id())
if row_id:
query = 'SELECT container, bitrate, video_resolution, width, height, aspect_ratio, video_framerate, ' \
'video_codec, audio_codec, audio_channels, video_decision, transcode_video_codec, transcode_height, ' \
'transcode_width, audio_decision, transcode_audio_codec, transcode_audio_channels, transcode_container, ' \
query = 'SELECT bitrate, video_resolution, ' \
'optimized_version, optimized_version_profile, optimized_version_title, ' \
'synced_version, synced_version_profile, ' \
'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \
'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \
'stream_bitrate, stream_video_resolution, quality_profile, stream_container_decision, stream_container, ' \
'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \
'stream_video_framerate, ' \
'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \
'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \
'session_history_metadata.media_type, title, grandparent_title ' \
'FROM session_history_media_info ' \
'JOIN session_history ON session_history_media_info.id = session_history.id ' \
@@ -882,9 +889,16 @@ class DataFactory(object):
'WHERE session_history_media_info.id = ? %s' % user_cond
result = monitor_db.select(query, args=[row_id])
elif session_key:
query = 'SELECT container, bitrate, video_resolution, width, height, aspect_ratio, video_framerate, ' \
'video_codec, audio_codec, audio_channels, video_decision, transcode_video_codec, transcode_height, ' \
'transcode_width, audio_decision, transcode_audio_codec, transcode_audio_channels, transcode_container, ' \
query = 'SELECT bitrate, video_resolution, ' \
'optimized_version, optimized_version_profile, optimized_version_title, ' \
'synced_version, synced_version_profile, ' \
'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \
'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \
'stream_bitrate, stream_video_resolution, quality_profile, stream_container_decision, stream_container, ' \
'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \
'stream_video_framerate, ' \
'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \
'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \
'media_type, title, grandparent_title ' \
'FROM sessions ' \
'WHERE session_key = ? %s' % user_cond
@@ -895,24 +909,42 @@ class DataFactory(object):
stream_output = {}
for item in result:
stream_output = {'container': item['container'],
'bitrate': item['bitrate'],
stream_output = {'bitrate': item['bitrate'],
'video_resolution': item['video_resolution'],
'width': item['width'],
'height': item['height'],
'aspect_ratio': item['aspect_ratio'],
'video_framerate': item['video_framerate'],
'optimized_version': item['optimized_version'],
'optimized_version_profile': item['optimized_version_profile'],
'optimized_version_title': item['optimized_version_title'],
'synced_version': item['synced_version'],
'synced_version_profile': item['synced_version_profile'],
'container': item['container'],
'video_codec': item['video_codec'],
'video_bitrate': item['video_bitrate'],
'video_width': item['video_width'],
'video_height': item['video_height'],
'video_framerate': item['video_framerate'],
'aspect_ratio': item['aspect_ratio'],
'audio_codec': item['audio_codec'],
'audio_bitrate': item['audio_bitrate'],
'audio_channels': item['audio_channels'],
'transcode_video_dec': item['video_decision'],
'transcode_video_codec': item['transcode_video_codec'],
'transcode_height': item['transcode_height'],
'transcode_width': item['transcode_width'],
'transcode_audio_dec': item['audio_decision'],
'transcode_audio_codec': item['transcode_audio_codec'],
'transcode_audio_channels': item['transcode_audio_channels'],
'transcode_container': item['transcode_container'],
'subtitle_codec': item['subtitle_codec'],
'stream_bitrate': item['stream_bitrate'],
'stream_video_resolution': item['stream_video_resolution'],
'quality_profile': item['quality_profile'],
'stream_container_decision': item['stream_container_decision'],
'stream_container': item['stream_container'],
'stream_video_decision': item['stream_video_decision'],
'stream_video_codec': item['stream_video_codec'],
'stream_video_bitrate': item['stream_video_bitrate'],
'stream_video_width': item['stream_video_width'],
'stream_video_height': item['stream_video_height'],
'stream_video_framerate': item['stream_video_framerate'],
'stream_audio_decision': item['stream_audio_decision'],
'stream_audio_codec': item['stream_audio_codec'],
'stream_audio_bitrate': item['stream_audio_bitrate'],
'stream_audio_channels': item['stream_audio_channels'],
'subtitles': item['subtitles'],
'stream_subtitle_decision': item['stream_subtitle_decision'],
'stream_subtitle_codec': item['stream_subtitle_codec'],
'media_type': item['media_type'],
'title': item['title'],
'grandparent_title': item['grandparent_title']
@@ -1072,7 +1104,7 @@ class DataFactory(object):
if str(rating_key).isdigit():
poster_key = rating_key
elif metadata:
if metadata['media_type'] in ('movie', 'show', 'artist'):
if metadata['media_type'] in ('movie', 'show', 'artist', 'collection'):
poster_key = metadata['rating_key']
elif metadata['media_type'] in ('season', 'album'):
poster_key = metadata['rating_key']

View File

@@ -768,13 +768,13 @@ def build_datatables_json(kwargs, dt_columns, default_sort_col=None):
# Build json data
json_data = {"draw": 1,
"columns": columns,
"order": [{"column": order_column,
"columns": columns,
"order": [{"column": order_column,
"dir": kwargs.pop("order_dir", "desc")}],
"start": int(kwargs.pop("start", 0)),
"length": int(kwargs.pop("length", 25)),
"search": {"value": kwargs.pop("search", "")}
}
"start": int(kwargs.pop("start", 0)),
"length": int(kwargs.pop("length", 25)),
"search": {"value": kwargs.pop("search", "")}
}
return json.dumps(json_data)
def humanFileSize(bytes, si=False):

View File

@@ -416,6 +416,7 @@ class Libraries(object):
'parent_rating_key': item['parent_rating_key'],
'grandparent_rating_key': item['grandparent_rating_key'],
'title': item['title'],
'sort_title': item['sort_title'] or item['title'],
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
@@ -483,12 +484,12 @@ class Libraries(object):
filtered_count = len(results)
# Sort results
results = sorted(results, key=lambda k: k['title'])
results = sorted(results, key=lambda k: k['sort_title'])
sort_order = json_data['order']
for order in reversed(sort_order):
sort_key = json_data['columns'][int(order['column'])]['data']
reverse = True if order['dir'] == 'desc' else False
if rating_key and sort_key == 'title':
if rating_key and sort_key == 'sort_title':
results = sorted(results, key=lambda k: helpers.cast_to_int(k['media_index']), reverse=reverse)
elif sort_key == 'file_size' or sort_key == 'bitrate':
results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key]), reverse=reverse)

View File

@@ -16,6 +16,7 @@
import arrow
import bleach
from collections import Counter
from itertools import groupby
import json
from operator import itemgetter
@@ -145,10 +146,17 @@ def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
return False
if notify_action == 'on_concurrent':
ap = activity_processor.ActivityProcessor()
user_sessions = ap.get_sessions(user_id=stream_data['user_id'],
ip_address=plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP)
return len(user_sessions) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_current_activity()
user_sessions = []
if result:
user_sessions = [s for s in result['sessions'] if s['user_id'] == stream_data['user_id']]
if plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP:
return len(Counter(s['ip_address'] for s in user_sessions)) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD
else:
return len(user_sessions) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD
elif notify_action == 'on_newdevice':
data_factory = datafactory.DataFactory()
@@ -453,17 +461,20 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
if 'media_info' in metadata and len(metadata['media_info']) > 0:
media_info = metadata['media_info'][0]
if 'parts' in media_info and len(media_info['parts']) > 0:
media_part_info = media_info['parts'][0]
media_part_info = media_info.pop('parts')[0]
stream_video = stream_audio = stream_subtitle = False
if 'streams' in media_part_info:
for stream in media_part_info['streams']:
for stream in media_part_info.pop('streams'):
if not stream_video and stream['type'] == '1':
media_part_info.update(stream)
stream_video = True
if not stream_audio and stream['type'] == '2':
media_part_info.update(stream)
stream_audio = True
if not stream_subtitle and stream['type'] == '3':
media_part_info.update(stream)
stream_subtitle = True
child_metadata = grandchild_metadata = []
for key in kwargs.pop('child_keys', []):
@@ -501,7 +512,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
# Build Plex URL
metadata['plex_url'] = '{web_url}#!/server/{pms_identifier}/details?key=%2Flibrary%2Fmetadata%2F{rating_key}'.format(
web_url=plexpy.CONFIG.PMS_WEB_URL or 'https://app.plex.tv/desktop',
web_url=plexpy.CONFIG.PMS_WEB_URL,
pms_identifier=plexpy.CONFIG.PMS_IDENTIFIER,
rating_key=rating_key)
@@ -766,32 +777,32 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'container': session.get('container', media_info.get('container','')),
'bitrate': session.get('bitrate', media_info.get('bitrate','')),
'aspect_ratio': session.get('aspect_ratio', media_info.get('aspect_ratio','')),
'video_codec': session.get('video_codec', media_info.get('video_codec','')),
'video_codec_level': session.get('video_codec_level', media_info.get('video_codec_level','')),
'video_bitrate': session.get('video_bitrate', media_info.get('video_bitrate','')),
'video_bit_depth': session.get('video_bit_depth', media_info.get('video_bit_depth','')),
'video_codec': session.get('video_codec', media_part_info.get('video_codec','')),
'video_codec_level': session.get('video_codec_level', media_part_info.get('video_codec_level','')),
'video_bitrate': session.get('video_bitrate', media_part_info.get('video_bitrate','')),
'video_bit_depth': session.get('video_bit_depth', media_part_info.get('video_bit_depth','')),
'video_framerate': session.get('video_framerate', media_info.get('video_framerate','')),
'video_ref_frames': session.get('video_ref_frames', media_info.get('video_ref_frames','')),
'video_ref_frames': session.get('video_ref_frames', media_part_info.get('video_ref_frames','')),
'video_resolution': session.get('video_resolution', media_info.get('video_resolution','')),
'video_height': session.get('height', media_info.get('height','')),
'video_width': session.get('width', media_info.get('width','')),
'video_language': session.get('video_language', media_info.get('video_language','')),
'video_language_code': session.get('video_language_code', media_info.get('video_language_code','')),
'audio_bitrate': session.get('audio_bitrate', media_info.get('audio_bitrate','')),
'audio_bitrate_mode': session.get('audio_bitrate_mode', media_info.get('audio_bitrate_mode','')),
'audio_codec': session.get('audio_codec', media_info.get('audio_codec','')),
'audio_channels': session.get('audio_channels', media_info.get('audio_channels','')),
'audio_channel_layout': session.get('audio_channel_layout', media_info.get('audio_channel_layout','')),
'audio_sample_rate': session.get('audio_sample_rate', media_info.get('audio_sample_rate','')),
'audio_language': session.get('audio_language', media_info.get('audio_language','')),
'audio_language_code': session.get('audio_language_code', media_info.get('audio_language_code','')),
'subtitle_codec': session.get('subtitle_codec', media_info.get('subtitle_codec','')),
'subtitle_container': session.get('subtitle_container', media_info.get('subtitle_container','')),
'subtitle_format': session.get('subtitle_format', media_info.get('subtitle_format','')),
'subtitle_forced': session.get('subtitle_forced', media_info.get('subtitle_forced','')),
'subtitle_location': session.get('subtitle_location', media_info.get('subtitle_location','')),
'subtitle_language': session.get('subtitle_language', media_info.get('subtitle_language','')),
'subtitle_language_code': session.get('subtitle_language_code', media_info.get('subtitle_language_code','')),
'video_language': session.get('video_language', media_part_info.get('video_language','')),
'video_language_code': session.get('video_language_code', media_part_info.get('video_language_code','')),
'audio_bitrate': session.get('audio_bitrate', media_part_info.get('audio_bitrate','')),
'audio_bitrate_mode': session.get('audio_bitrate_mode', media_part_info.get('audio_bitrate_mode','')),
'audio_codec': session.get('audio_codec', media_part_info.get('audio_codec','')),
'audio_channels': session.get('audio_channels', media_part_info.get('audio_channels','')),
'audio_channel_layout': session.get('audio_channel_layout', media_part_info.get('audio_channel_layout','')),
'audio_sample_rate': session.get('audio_sample_rate', media_part_info.get('audio_sample_rate','')),
'audio_language': session.get('audio_language', media_part_info.get('audio_language','')),
'audio_language_code': session.get('audio_language_code', media_part_info.get('audio_language_code','')),
'subtitle_codec': session.get('subtitle_codec', media_part_info.get('subtitle_codec','')),
'subtitle_container': session.get('subtitle_container', media_part_info.get('subtitle_container','')),
'subtitle_format': session.get('subtitle_format', media_part_info.get('subtitle_format','')),
'subtitle_forced': session.get('subtitle_forced', media_part_info.get('subtitle_forced','')),
'subtitle_location': session.get('subtitle_location', media_part_info.get('subtitle_location','')),
'subtitle_language': session.get('subtitle_language', media_part_info.get('subtitle_language','')),
'subtitle_language_code': session.get('subtitle_language_code', media_part_info.get('subtitle_language_code','')),
'file': media_part_info.get('file',''),
'file_size': helpers.humanFileSize(media_part_info.get('file_size','')),
'indexes': media_part_info.get('indexes',''),

View File

@@ -1203,14 +1203,16 @@ class DISCORD(Notifier):
{'label': 'Movie Link Source',
'value': self.config['movie_provider'],
'name': 'discord_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers()
},
{'label': 'TV Show Link Source',
'value': self.config['tv_provider'],
'name': 'discord_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers()
},
@@ -1533,14 +1535,16 @@ class FACEBOOK(Notifier):
{'label': 'Movie Link Source',
'value': self.config['movie_provider'],
'name': 'facebook_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers()
},
{'label': 'TV Show Link Source',
'value': self.config['tv_provider'],
'name': 'facebook_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers()
},
@@ -1872,14 +1876,16 @@ class HIPCHAT(Notifier):
{'label': 'Movie Link Source',
'value': self.config['movie_provider'],
'name': 'hipchat_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers()
},
{'label': 'TV Show Link Source',
'value': self.config['tv_provider'],
'name': 'hipchat_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers()
},
@@ -1982,7 +1988,7 @@ class JOIN(Notifier):
def get_devices(self):
if self.config['api_key']:
params = {'api_key': self.config['api_key']}
params = {'apikey': self.config['api_key']}
r = requests.get('https://joinjoaomgcd.appspot.com/_ah/api/registration/v1/listDevices', params=params)
@@ -2678,14 +2684,16 @@ class PUSHOVER(Notifier):
{'label': 'Movie Link Source',
'value': self.config['movie_provider'],
'name': 'pushover_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers()
},
{'label': 'TV Show Link Source',
'value': self.config['tv_provider'],
'name': 'pushover_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers()
},
@@ -3038,14 +3046,16 @@ class SLACK(Notifier):
{'label': 'Movie Link Source',
'value': self.config['movie_provider'],
'name': 'slack_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers()
},
{'label': 'TV Show Link Source',
'value': self.config['tv_provider'],
'name': 'slack_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br> \
3rd party API lookup may need to be enabled under the notification settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers()
},

View File

@@ -425,7 +425,7 @@ class PlexTV(object):
return users_list
def get_synced_items(self, machine_id=None, user_id=None):
def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None, rating_key_filter=None):
sync_list = self.get_plextv_sync_lists(machine_id)
user_data = users.Users()
@@ -446,9 +446,15 @@ class PlexTV(object):
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items.")
else:
for a in xml_head:
sync_id = helpers.get_xml_attr(a, 'id')
client_id = helpers.get_xml_attr(a, 'clientIdentifier')
# Filter by client_id
if client_id_filter and client_id_filter != client_id:
continue
sync_id = helpers.get_xml_attr(a, 'id')
sync_device = a.getElementsByTagName('Device')
for device in sync_device:
device_user_id = helpers.get_xml_attr(device, 'userID')
try:
@@ -467,12 +473,23 @@ class PlexTV(object):
device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt')
# Filter by user_id
if user_id and user_id != device_user_id:
if user_id_filter and user_id_filter != device_user_id:
continue
for synced in a.getElementsByTagName('SyncItems'):
sync_item = synced.getElementsByTagName('SyncItem')
for item in sync_item:
for location in item.getElementsByTagName('Location'):
clean_uri = helpers.get_xml_attr(location, 'uri').split('%2F')
rating_key = next((clean_uri[(idx + 1) % len(clean_uri)]
for idx, item in enumerate(clean_uri) if item == 'metadata'), None)
# Filter by rating_key
if rating_key_filter and rating_key_filter != rating_key:
continue
sync_id = helpers.get_xml_attr(item, 'id')
sync_version = helpers.get_xml_attr(item, 'version')
sync_root_title = helpers.get_xml_attr(item, 'rootTitle')
@@ -501,12 +518,6 @@ class PlexTV(object):
settings_video_quality = helpers.get_xml_attr(settings, 'videoQuality')
settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution')
for location in item.getElementsByTagName('Location'):
clean_uri = helpers.get_xml_attr(location, 'uri').split('%2F')
rating_key = next((clean_uri[(idx + 1) % len(clean_uri)]
for idx, item in enumerate(clean_uri) if item == 'metadata'), None)
sync_details = {"device_name": helpers.sanitize(device_name),
"platform": helpers.sanitize(device_platform),
"username": helpers.sanitize(device_username),

View File

@@ -24,6 +24,7 @@ import helpers
import http_handler
import libraries
import logger
import plextv
import session
import users
@@ -256,6 +257,23 @@ class PmsConnect(object):
return request
def get_children_list_related(self, rating_key='', output_format=''):
"""
Return list of related children in requested collection item.
Parameters required: rating_key { ratingKey of parent }
Optional parameters: output_format { dict, json }
Output: array
"""
uri = '/hubs/metadata/' + rating_key + '/related'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
def get_childrens_list(self, rating_key='', output_format=''):
"""
Return list of children in requested library item.
@@ -413,7 +431,7 @@ class PmsConnect(object):
Output: array
"""
uri = '/hubs/search?query=' + urllib.quote(query.encode('utf8')) + '&limit=' + limit
uri = '/hubs/search?query=' + urllib.quote(query.encode('utf8')) + '&limit=' + limit + '&includeCollections=1'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
@@ -548,6 +566,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(item, 'title'),
'parent_title': helpers.get_xml_attr(item, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(item, 'grandparentTitle'),
'sort_title': helpers.get_xml_attr(item, 'titleSort'),
'media_index': helpers.get_xml_attr(item, 'index'),
'parent_media_index': helpers.get_xml_attr(item, 'parentIndex'),
'section_id': section_id if section_id else helpers.get_xml_attr(item, 'librarySectionID'),
@@ -571,6 +590,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(item, 'title'),
'parent_title': helpers.get_xml_attr(item, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(item, 'grandparentTitle'),
'sort_title': helpers.get_xml_attr(item, 'titleSort'),
'media_index': helpers.get_xml_attr(item, 'index'),
'parent_media_index': helpers.get_xml_attr(item, 'parentIndex'),
'section_id': section_id if section_id else helpers.get_xml_attr(item, 'librarySectionID'),
@@ -588,7 +608,7 @@ class PmsConnect(object):
return output
def get_metadata_details(self, rating_key=''):
def get_metadata_details(self, rating_key='', sync_id=''):
"""
Return processed and validated metadata list for requested item.
@@ -596,12 +616,15 @@ class PmsConnect(object):
Output: array
"""
metadata = self.get_metadata(str(rating_key), output_format='xml')
if rating_key:
metadata = self.get_metadata(str(rating_key), output_format='xml')
elif sync_id:
metadata = self.get_sync_item(str(sync_id), output_format='xml')
try:
xml_head = metadata.getElementsByTagName('MediaContainer')
except Exception as e:
logger.warn(u"Tautulli Pmsconnect :: Unable to parse XML for get_metadata: %s." % e)
logger.warn(u"Tautulli Pmsconnect :: Unable to parse XML for get_metadata_details: %s." % e)
return {}
metadata = {}
@@ -668,6 +691,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -707,6 +731,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -748,6 +773,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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'],
@@ -790,6 +816,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': 'Season %s' % helpers.get_xml_attr(metadata_main, 'parentIndex'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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'],
@@ -830,6 +857,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -871,6 +899,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -913,6 +942,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -953,6 +983,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -994,6 +1025,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -1024,6 +1056,50 @@ class PmsConnect(object):
helpers.get_xml_attr(metadata_main, 'title'))
}
elif metadata_type == 'collection':
metadata = {'media_type': metadata_type,
'sub_media_type': helpers.get_xml_attr(metadata_main, 'subtype'),
'section_id': section_id,
'library_name': library_name,
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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': helpers.get_xml_attr(metadata_main, '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'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
'duration': helpers.get_xml_attr(metadata_main, 'duration'),
'year': helpers.get_xml_attr(metadata_main, 'year'),
'min_year': helpers.get_xml_attr(metadata_main, 'minYear'),
'max_year': helpers.get_xml_attr(metadata_main, 'maxYear'),
'thumb': helpers.get_xml_attr(metadata_main, 'thumb').split('?')[0],
'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': helpers.get_xml_attr(metadata_main, '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'),
'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
'guid': helpers.get_xml_attr(metadata_main, 'guid'),
'child_count': helpers.get_xml_attr(metadata_main, 'childCount'),
'directors': directors,
'writers': writers,
'actors': actors,
'genres': genres,
'labels': labels,
'full_title': helpers.get_xml_attr(metadata_main, 'title')
}
elif metadata_type == 'clip':
metadata = {'media_type': metadata_type,
'section_id': section_id,
@@ -1034,6 +1110,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'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': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -1310,6 +1387,7 @@ class PmsConnect(object):
# Get the source media type
media_type = helpers.get_xml_attr(session, 'type')
rating_key = helpers.get_xml_attr(session, 'ratingKey')
# Get the user details
user_info = session.getElementsByTagName('User')[0]
@@ -1336,7 +1414,7 @@ class PmsConnect(object):
'product_version': helpers.get_xml_attr(player_info, 'version'),
'profile': helpers.get_xml_attr(player_info, 'profile'),
'player': helpers.get_xml_attr(player_info, 'title') or helpers.get_xml_attr(player_info, 'product'),
'machine_id': helpers.get_xml_attr(player_info, 'machineIdentifier').rstrip('_Video').rstrip('_Track'),
'machine_id': helpers.get_xml_attr(player_info, 'machineIdentifier'),
'state': helpers.get_xml_attr(player_info, 'state'),
'local': helpers.get_xml_attr(player_info, 'local')
}
@@ -1417,16 +1495,30 @@ class PmsConnect(object):
transcode_decision = 'direct play'
# Determine if a synced version is being played
sync_id = None
if media_type not in ('photo', 'clip') and not session.getElementsByTagName('Session') \
and helpers.get_xml_attr(session, 'ratingKey').isdigit() and transcode_decision == 'direct play':
synced_version = 1
else:
synced_version = 0
plex_tv = plextv.PlexTV()
synced_items = plex_tv.get_synced_items(machine_id=plexpy.CONFIG.PMS_IDENTIFIER,
client_id_filter=player_details['machine_id'],
rating_key_filter=rating_key)
if synced_items:
sync_id = synced_items[0]['sync_id']
synced_xml = self.get_sync_item(sync_id=sync_id, output_format='xml')
synced_xml_head = synced_xml.getElementsByTagName('MediaContainer')
if synced_xml_head[0].getElementsByTagName('Track'):
synced_session_data = synced_xml_head[0].getElementsByTagName('Track')[0]
elif synced_xml_head[0].getElementsByTagName('Video'):
synced_session_data = synced_xml_head[0].getElementsByTagName('Video')[0]
# Figure out which version is being played
media_info_all = session.getElementsByTagName('Media')
if sync_id:
media_info_all = synced_session_data.getElementsByTagName('Media')
else:
media_info_all = session.getElementsByTagName('Media')
stream_media_info = next((m for m in media_info_all if helpers.get_xml_attr(m, 'selected') == '1'), media_info_all[0])
stream_media_parts_info = stream_media_info.getElementsByTagName('Part')[0]
part_info_all = stream_media_info.getElementsByTagName('Part')
stream_media_parts_info = next((p for p in part_info_all if helpers.get_xml_attr(p, 'selected') == '1'), part_info_all[0])
# Get the stream details
video_stream_info = audio_stream_info = subtitle_stream_info = None
@@ -1483,6 +1575,7 @@ class PmsConnect(object):
if subtitle_stream_info:
subtitle_id = helpers.get_xml_attr(subtitle_stream_info, 'id')
subtitle_selected = helpers.get_xml_attr(subtitle_stream_info, 'selected')
subtitle_details = {'stream_subtitle_codec': helpers.get_xml_attr(subtitle_stream_info, 'codec'),
'stream_subtitle_container': helpers.get_xml_attr(subtitle_stream_info, 'container'),
'stream_subtitle_format': helpers.get_xml_attr(subtitle_stream_info, 'format'),
@@ -1532,14 +1625,14 @@ class PmsConnect(object):
'stream_video_height': helpers.get_xml_attr(stream_media_info, 'height'),
'stream_video_width': helpers.get_xml_attr(stream_media_info, 'width'),
'stream_duration': helpers.get_xml_attr(stream_media_info, 'duration') or helpers.get_xml_attr(session, 'duration'),
'stream_container_decision': helpers.get_xml_attr(stream_media_parts_info, 'decision').replace('directplay', 'direct play'),
'stream_container_decision': 'direct play' if sync_id else helpers.get_xml_attr(stream_media_parts_info, 'decision').replace('directplay', 'direct play'),
'transcode_decision': transcode_decision,
'optimized_version': 1 if helpers.get_xml_attr(stream_media_info, 'proxyType') == '42' else 0,
'optimized_version_profile': helpers.get_xml_attr(stream_media_info, 'title'),
'synced_version': synced_version,
'optimized_version_title': helpers.get_xml_attr(stream_media_info, 'title'),
'synced_version': 1 if sync_id else 0,
'indexes': 1 if indexes == 'sd' else 0,
'bif_thumb': bif_thumb,
'subtitles': 1 if subtitle_id else 0
'subtitles': 1 if subtitle_id and subtitle_selected else 0
}
# Get the source media info
@@ -1560,6 +1653,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(session, 'title'),
'parent_title': helpers.get_xml_attr(session, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'),
'sort_title': helpers.get_xml_attr(session, 'titleSort'),
'media_index': helpers.get_xml_attr(session, 'index'),
'parent_media_index': helpers.get_xml_attr(session, 'parentIndex'),
'studio': helpers.get_xml_attr(session, 'studio'),
@@ -1604,7 +1698,10 @@ class PmsConnect(object):
media_id = helpers.get_xml_attr(stream_media_info, 'id')
part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
metadata_details = self.get_metadata_details(rating_key=helpers.get_xml_attr(session, 'ratingKey'))
if sync_id:
metadata_details = self.get_metadata_details(sync_id=sync_id)
else:
metadata_details = self.get_metadata_details(rating_key=rating_key)
# Get the media info, fallback to first item if match id is not found
source_medias = metadata_details.pop('media_info', [])
@@ -1669,7 +1766,22 @@ class PmsConnect(object):
quality_profile = common.VIDEO_QUALITY_PROFILES[quailtiy_bitrate]
except ValueError:
quality_profile = 'Original'
if sync_id:
try:
synced_bitrate = min(b for b in common.VIDEO_QUALITY_PROFILES if source_bitrate <= b)
synced_version_profile = common.VIDEO_QUALITY_PROFILES[synced_bitrate]
except ValueError:
synced_version_profile = 'Original'
else:
synced_version_profile = ''
if stream_details['optimized_version']:
optimized_version_profile = '{} Mbps {}'.format(round(source_bitrate / 1000.0, 1),
plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(source_media_details['video_resolution'], source_media_details['video_resolution']))
else:
optimized_version_profile = ''
elif media_type == 'track' and 'stream_bitrate' in stream_details:
stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate'])
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
@@ -1680,11 +1792,26 @@ class PmsConnect(object):
except ValueError:
quality_profile = 'Original'
if sync_id:
try:
synced_bitrate = min(b for b in common.AUDIO_QUALITY_PROFILES if source_bitrate <= b)
synced_version_profile = common.AUDIO_QUALITY_PROFILES[synced_bitrate]
except ValueError:
synced_version_profile = 'Original'
else:
synced_version_profile = ''
optimized_version_profile = ''
elif media_type == 'photo':
quality_profile = 'Original'
synced_version_profile = ''
optimized_version_profile = ''
else:
quality_profile = 'Unknown'
synced_version_profile = ''
optimized_version_profile = ''
# Entire session output (single dict for backwards compatibility)
session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'),
@@ -1692,6 +1819,8 @@ class PmsConnect(object):
'view_offset': view_offset,
'progress_percent': str(helpers.get_percent(view_offset, stream_details['stream_duration'])),
'quality_profile': quality_profile,
'synced_version_profile': synced_version_profile,
'optimized_version_profile': optimized_version_profile,
'user': user_details['username'], # Keep for backwards compatibility
'channel_stream': channel_stream
}
@@ -1727,7 +1856,6 @@ class PmsConnect(object):
else:
return False
def get_item_children(self, rating_key=''):
"""
Return processed and validated children list.
@@ -1768,8 +1896,11 @@ class PmsConnect(object):
for result in result_data:
children_output = {'section_id': section_id,
'rating_key': helpers.get_xml_attr(result, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(result, 'parentRatingKey'),
'media_index': helpers.get_xml_attr(result, 'index'),
'title': helpers.get_xml_attr(result, 'title'),
'parent_title': helpers.get_xml_attr(result, 'parentTitle'),
'year': helpers.get_xml_attr(result, 'year'),
'thumb': helpers.get_xml_attr(result, 'thumb'),
'parent_thumb': helpers.get_xml_attr(a, 'thumb'),
'duration': helpers.get_xml_attr(result, 'duration')
@@ -1784,6 +1915,72 @@ class PmsConnect(object):
return output
def get_item_children_related(self, rating_key=''):
"""
Return processed and validated children list.
Output: array
"""
children_data = self.get_children_list_related(rating_key, output_format='xml')
try:
xml_head = children_data.getElementsByTagName('MediaContainer')
except Exception as e:
logger.warn(u"Tautulli Pmsconnect :: Unable to parse XML for get_item_children_related: %s." % e)
return []
children_results_list = {'movie': [],
'show': [],
'season': [],
'episode': [],
'artist': [],
'album': [],
'track': [],
}
for a in xml_head:
section_id = helpers.get_xml_attr(a, 'librarySectionID')
hubs = a.getElementsByTagName('Hub')
for h in hubs:
size = helpers.get_xml_attr(h, 'size')
media_type = helpers.get_xml_attr(h, 'type')
title = helpers.get_xml_attr(h, 'title')
hub_identifier = helpers.get_xml_attr(h, 'hubIdentifier')
if size == '0' or not hub_identifier.startswith('collection.related') or \
media_type not in children_results_list.keys():
continue
result_data = []
if h.getElementsByTagName('Video'):
result_data = h.getElementsByTagName('Video')
if h.getElementsByTagName('Directory'):
result_data = h.getElementsByTagName('Directory')
if h.getElementsByTagName('Track'):
result_data = h.getElementsByTagName('Track')
for result in result_data:
children_output = {'section_id': section_id,
'rating_key': helpers.get_xml_attr(result, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(result, 'parentRatingKey'),
'media_index': helpers.get_xml_attr(result, 'index'),
'title': helpers.get_xml_attr(result, 'title'),
'parent_title': helpers.get_xml_attr(result, 'parentTitle'),
'year': helpers.get_xml_attr(result, 'year'),
'thumb': helpers.get_xml_attr(result, 'thumb'),
'parent_thumb': helpers.get_xml_attr(a, 'thumb'),
'duration': helpers.get_xml_attr(result, 'duration')
}
children_results_list[media_type].append(children_output)
output = {'results_count': sum(len(s) for s in children_results_list.items()),
'results_list': children_results_list,
}
return output
def get_servers_info(self):
"""
Return the list of local servers.
@@ -1986,16 +2183,17 @@ class PmsConnect(object):
'media_type': helpers.get_xml_attr(item, 'type'),
'rating_key': helpers.get_xml_attr(item, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(item, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(a, 'grandparentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(item, 'grandparentRatingKey'),
'title': helpers.get_xml_attr(item, 'title'),
'parent_title': helpers.get_xml_attr(a, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(a, 'grandparentTitle'),
'parent_title': helpers.get_xml_attr(item, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(item, 'grandparentTitle'),
'sort_title': helpers.get_xml_attr(item, 'titleSort'),
'media_index': helpers.get_xml_attr(item, 'index'),
'parent_media_index': helpers.get_xml_attr(a, 'parentIndex'),
'parent_media_index': helpers.get_xml_attr(item, 'parentIndex'),
'year': helpers.get_xml_attr(item, 'year'),
'thumb': helpers.get_xml_attr(item, 'thumb'),
'parent_thumb': helpers.get_xml_attr(a, 'thumb'),
'grandparent_thumb': helpers.get_xml_attr(a, 'grandparentThumb'),
'parent_thumb': helpers.get_xml_attr(item, 'thumb'),
'grandparent_thumb': helpers.get_xml_attr(item, 'grandparentThumb'),
'added_at': helpers.get_xml_attr(item, 'addedAt')
}
@@ -2168,7 +2366,8 @@ class PmsConnect(object):
'episode': [],
'artist': [],
'album': [],
'track': []
'track': [],
'collection': []
}
for a in xml_head:

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.0.0-beta"
PLEXPY_RELEASE_VERSION = "v2.0.4-beta"

View File

@@ -175,10 +175,20 @@ def checkGithub(auto_update=False):
if plexpy.COMMITS_BEHIND > 0:
logger.info('New version is available. You are %s commits behind' % plexpy.COMMITS_BEHIND)
url = 'https://api.github.com/repos/%s/plexpy/releases/latest' % plexpy.CONFIG.GIT_USER
release = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict)
url = 'https://api.github.com/repos/%s/plexpy/releases' % plexpy.CONFIG.GIT_USER
releases = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict)
if plexpy.CONFIG.GIT_BRANCH == 'master':
release = next((r for r in releases if not r['prerelease']), releases[0])
elif plexpy.CONFIG.GIT_BRANCH == 'beta':
release = next((r for r in releases if r['prerelease'] and '-beta' in r['tag_name']), releases[0])
elif plexpy.CONFIG.GIT_BRANCH == 'nightly':
release = next((r for r in releases if r['prerelease'] and '-nightly' in r['tag_name']), releases[0])
else:
release = releases[0]
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_plexpyupdate', 'plexpy_download_info': release,
'plexpy_update_commit': plexpy.LATEST_VERSION, 'plexpy_update_behind': plexpy.COMMITS_BEHIND})
'plexpy_update_commit': plexpy.LATEST_VERSION, 'plexpy_update_behind': plexpy.COMMITS_BEHIND})
if auto_update:
logger.info('Running automatic update.')

View File

@@ -98,9 +98,6 @@ class WebInterface(object):
@requireAuth(member_of("admin"))
def welcome(self, **kwargs):
config = {
"launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER),
"refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
"refresh_libraries_on_startup": checked(plexpy.CONFIG.REFRESH_LIBRARIES_ON_STARTUP),
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
"pms_ip": plexpy.CONFIG.PMS_IP,
"pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
@@ -108,16 +105,7 @@ class WebInterface(object):
"pms_token": plexpy.CONFIG.PMS_TOKEN,
"pms_ssl": checked(plexpy.CONFIG.PMS_SSL),
"pms_uuid": plexpy.CONFIG.PMS_UUID,
"movie_notify_enable": checked(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE),
"tv_notify_enable": checked(plexpy.CONFIG.TV_NOTIFY_ENABLE),
"music_notify_enable": checked(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE),
"movie_logging_enable": checked(plexpy.CONFIG.MOVIE_LOGGING_ENABLE),
"tv_logging_enable": checked(plexpy.CONFIG.TV_LOGGING_ENABLE),
"music_logging_enable": checked(plexpy.CONFIG.MUSIC_LOGGING_ENABLE),
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
"check_github": checked(plexpy.CONFIG.CHECK_GITHUB),
"log_blacklist": checked(plexpy.CONFIG.LOG_BLACKLIST),
"cache_images": checked(plexpy.CONFIG.CACHE_IMAGES)
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL
}
# The setup wizard just refreshes the page on submit so we must redirect to home if config set.
@@ -182,7 +170,6 @@ class WebInterface(object):
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"home_stats_recently_added_count": plexpy.CONFIG.HOME_STATS_RECENTLY_ADDED_COUNT,
"pms_name": plexpy.CONFIG.PMS_NAME,
"pms_use_bif": plexpy.CONFIG.PMS_USE_BIF,
"update_show_changelog": plexpy.CONFIG.UPDATE_SHOW_CHANGELOG
}
return serve_template(templatename="index.html", title="Home", config=config)
@@ -226,18 +213,8 @@ class WebInterface(object):
@requireAuth()
def get_current_activity(self, **kwargs):
try:
pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN)
result = pms_connect.get_current_activity()
data_factory = datafactory.DataFactory()
for session in result['sessions']:
if not session['ip_address']:
ip_address = data_factory.get_session_ip(session['session_key'])
session['ip_address'] = ip_address
except:
return serve_template(templatename="current_activity.html", data=None)
pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN)
result = pms_connect.get_current_activity()
if result:
return serve_template(templatename="current_activity.html", data=result)
@@ -247,45 +224,16 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth()
def get_current_activity_instance(self, **kwargs):
def get_current_activity_instance(self, session_key=None, **kwargs):
return serve_template(templatename="current_activity_instance.html", session=kwargs)
@cherrypy.expose
@requireAuth()
def get_current_activity_header(self, **kwargs):
try:
pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN)
result = pms_connect.get_current_activity()
except:
return serve_template(templatename="current_activity_header.html", data=None)
pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN)
result = pms_connect.get_current_activity()
if result:
data = {'stream_count': result['stream_count'],
'direct_play': 0,
'direct_stream': 0,
'transcode': 0}
for s in result['sessions']:
if s['media_type'] == 'track':
if s['audio_decision'] == 'transcode':
data['transcode'] += 1
elif s['audio_decision'] == 'copy':
data['direct_stream'] += 1
else:
data['direct_play'] += 1
else:
if s['video_decision'] == 'transcode' or s['audio_decision'] == 'transcode':
data['transcode'] += 1
elif s['video_decision'] == 'copy' or s['audio_decision'] == 'copy':
data['direct_stream'] += 1
else:
data['direct_play'] += 1
return serve_template(templatename="current_activity_header.html", data=data)
session = next((s for s in result['sessions'] if s['session_key'] == session_key), None)
return serve_template(templatename="current_activity_instance.html", session=session)
else:
logger.warn(u"Unable to retrieve data for get_current_activity_header.")
return serve_template(templatename="current_activity_header.html", data=None)
return serve_template(templatename="current_activity_instance.html", session=None)
@cherrypy.expose
@cherrypy.tools.json_out()
@@ -389,11 +337,7 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth()
def libraries(self, **kwargs):
config = {
"update_section_ids": plexpy.CONFIG.UPDATE_SECTION_IDS
}
return serve_template(templatename="libraries.html", title="Libraries", config=config)
return serve_template(templatename="libraries.html", title="Libraries")
@cherrypy.expose
@cherrypy.tools.json_out()
@@ -721,9 +665,13 @@ class WebInterface(object):
# Check if datatables json_data was received.
# If not, then build the minimal amount of json data for a query
if not kwargs.get('json_data'):
# Alias 'title' to 'sort_title'
if kwargs.get('order_column') == 'title':
kwargs['order_column'] == 'sort_title'
# TODO: Find some one way to automatically get the columns
dt_columns = [("added_at", True, False),
("title", True, True),
("sort_title", True, True),
("container", True, True),
("bitrate", True, True),
("video_codec", True, True),
@@ -734,7 +682,7 @@ class WebInterface(object):
("file_size", True, False),
("last_played", True, False),
("play_count", True, False)]
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "title")
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "sort_title")
if refresh == 'true':
refresh = True
@@ -995,19 +943,6 @@ class WebInterface(object):
else:
return {'message': 'no data received'}
@cherrypy.expose
@requireAuth(member_of("admin"))
def update_section_ids(self, **kwargs):
logger.debug(u"Manual database section_id update called.")
result = libraries.update_section_ids()
if result:
return "Updated all section_id's in database."
else:
return "Unable to update section_id's in database. See logs for details."
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@@ -2264,7 +2199,7 @@ class WebInterface(object):
machine_id = plexpy.CONFIG.PMS_IDENTIFIER
plex_tv = plextv.PlexTV()
result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id)
result = plex_tv.get_synced_items(machine_id=machine_id, user_id_filter=user_id)
if result:
output = {"data": result}
@@ -2595,6 +2530,7 @@ class WebInterface(object):
"http_port": plexpy.CONFIG.HTTP_PORT,
"http_password": http_password,
"http_root": plexpy.CONFIG.HTTP_ROOT,
"http_proxy": checked(plexpy.CONFIG.HTTP_PROXY),
"launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER),
"enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS),
"https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT),
@@ -2624,7 +2560,6 @@ class WebInterface(object):
"pms_token": plexpy.CONFIG.PMS_TOKEN,
"pms_ssl": checked(plexpy.CONFIG.PMS_SSL),
"pms_url_manual": checked(plexpy.CONFIG.PMS_URL_MANUAL),
"pms_use_bif": checked(plexpy.CONFIG.PMS_USE_BIF),
"pms_uuid": plexpy.CONFIG.PMS_UUID,
"pms_web_url": plexpy.CONFIG.PMS_WEB_URL,
"date_format": plexpy.CONFIG.DATE_FORMAT,
@@ -2682,12 +2617,12 @@ class WebInterface(object):
checked_configs = [
"launch_browser", "enable_https", "https_create_cert", "api_enabled", "freeze_db", "check_github",
"grouping_global_history", "grouping_user_history", "grouping_charts", "group_history_tables",
"pms_use_bif", "pms_ssl", "pms_is_remote", "pms_url_manual", "home_stats_type", "week_start_monday",
"pms_ssl", "pms_is_remote", "pms_url_manual", "week_start_monday",
"refresh_libraries_on_startup", "refresh_users_on_startup",
"notify_consecutive", "notify_upload_posters", "notify_recently_added_upgrade",
"notify_group_recently_added_grandparent", "notify_group_recently_added_parent",
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password",
"allow_guest_access", "cache_images", "http_basic_auth", "notify_concurrent_by_ip",
"allow_guest_access", "cache_images", "http_proxy", "http_basic_auth", "notify_concurrent_by_ip",
"history_table_activity", "plexpy_auto_update",
"themoviedb_lookup", "tvmaze_lookup"
]
@@ -3539,7 +3474,7 @@ class WebInterface(object):
def generate_api_key(self, device=None, **kwargs):
apikey = ''
while not apikey or apikey == plexpy.CONFIG.API_KEY or mobile_app.get_mobile_device_by_token(device_token=apikey):
apikey = uuid.uuid4().hex
apikey = plexpy.generate_uuid()
logger.info(u"New API key generated.")
logger._BLACKLIST_WORDS.add(apikey)
@@ -3653,7 +3588,7 @@ class WebInterface(object):
def get_item_children(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_item_children(rating_key)
result = pms_connect.get_item_children(rating_key=rating_key)
if result:
return serve_template(templatename="info_children_list.html", data=result, title="Children List")
@@ -3661,6 +3596,18 @@ class WebInterface(object):
logger.warn(u"Unable to retrieve data for get_item_children.")
return serve_template(templatename="info_children_list.html", data=None, title="Children List")
@cherrypy.expose
@requireAuth()
def get_item_children_related(self, rating_key='', title='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_item_children_related(rating_key=rating_key)
if result:
return serve_template(templatename="info_collection_list.html", data=result, title=title)
else:
return serve_template(templatename="info_collection_list.html", data=None, title=title)
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth()
@@ -4481,19 +4428,13 @@ class WebInterface(object):
result = pms_connect.get_current_activity()
if result:
data_factory = datafactory.DataFactory()
for session in result['sessions']:
if not session['ip_address']:
ip_address = data_factory.get_session_ip(session['session_key'])
session['ip_address'] = ip_address
if session_key:
return next((s for s in result['sessions'] if s['session_key'] == session_key), {})
counts = {'stream_count_direct_play': 0,
'stream_count_direct_stream': 0,
'stream_count_transcode': 0}
'stream_count_direct_stream': 0,
'stream_count_transcode': 0}
for s in result['sessions']:
if s['transcode_decision'] == 'transcode':
@@ -4635,7 +4576,7 @@ class WebInterface(object):
```
"""
plex_tv = plextv.PlexTV()
result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id)
result = plex_tv.get_synced_items(machine_id=machine_id, user_id_filter=user_id)
if result:
return result

View File

@@ -159,28 +159,6 @@ def initialize(options):
'tools.auth.on': False,
'tools.sessions.on': False
},
'/json': {
'tools.staticdir.on': True,
'tools.staticdir.dir': "interfaces/default/json",
'tools.caching.on': True,
'tools.caching.force': True,
'tools.caching.delay': 0,
'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False,
'tools.sessions.on': False
},
'/xml': {
'tools.staticdir.on': True,
'tools.staticdir.dir': "interfaces/default/xml",
'tools.caching.on': True,
'tools.caching.force': True,
'tools.caching.delay': 0,
'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False,
'tools.sessions.on': False
},
'/cache': {
'tools.staticdir.on': True,
'tools.staticdir.dir': plexpy.CONFIG.CACHE_DIR,
@@ -205,7 +183,7 @@ def initialize(options):
#},
'/favicon.ico': {
'tools.staticfile.on': True,
'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon.ico')),
'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon/favicon.ico')),
'tools.caching.on': True,
'tools.caching.force': True,
'tools.caching.delay': 0,