Compare commits

..

39 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
67 changed files with 1948 additions and 1041 deletions

View File

@@ -1,5 +1,53 @@
# Changelog # 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) ## v2.0.1-beta (2017-12-19)
* Monitoring: * Monitoring:

View File

@@ -20,28 +20,26 @@
${next.headIncludes()} ${next.headIncludes()}
<!-- Favicons --> <!-- 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="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.0"> <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.0"> <link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.1">
<!-- ICONS --> <!-- 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="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"> <link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.1">
<meta name="theme-color" content="#1f1f1f"> <meta name="theme-color" content="#282a2d">
<!-- Apple --> <!-- Apple -->
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.0"> <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.0" color="#1f1f1f"> <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-title" content="Tautulli">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="initial-scale=1"> <meta name="viewport" content="initial-scale=1">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<!-- IE10 icon --> <!-- Microsoft -->
<meta name="application-name" content="Tautulli"> <meta name="application-name" content="Tautulli">
<meta name="msapplication-TileColor" content="#1f1f1f"> <meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.1">
<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" />
</head> </head>
<body class="content"> <body class="content">
@@ -72,7 +70,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="home"> <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> </a>
</div> </div>
<div class="collapse navbar-collapse navbar-right" id="navbar-collapse-1"> <div class="collapse navbar-collapse navbar-right" id="navbar-collapse-1">

View File

@@ -113,7 +113,7 @@ img {
box-shadow: 0 0 0 3px rgba(0,0,0,.2); box-shadow: 0 0 0 3px rgba(0,0,0,.2);
} }
.navbar-brand { .navbar-brand {
padding: 5px 5px; padding: 3px 3px;
} }
.nav > li > a { .nav > li > a {
color: #999; color: #999;
@@ -899,15 +899,15 @@ a .users-poster-face:hover {
height: 249px; height: 249px;
} }
.dashboard-activity-container:hover .dashboard-activity-progress { .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); 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: -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: -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); 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); 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: -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: -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; margin-left: 2px;
color: #999; color: #999;
} }
#children-list, #search-results-list { .children-list,
.search-results-list {
position: relative; position: relative;
z-index: 0; 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); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
z-index: -2; z-index: -2;
} }
.item-children-poster-face.season-poster { .item-children-poster-face.poster-item {
width: 150px; width: 150px;
height: 225px; height: 225px;
} }
.item-children-poster-face.episode-poster { .item-children-poster-face.episode-item {
width: 250px; width: 250px;
height: 140px; height: 140px;
} }
.item-children-poster-face.album-poster { .item-children-poster-face.cover-item {
width: 150px; width: 150px;
height: 150px; height: 150px;
} }
@@ -1915,15 +1916,13 @@ a:hover .item-children-poster {
margin-bottom: 20px; margin-bottom: 20px;
clear: both; 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; width: 150px;
} }
.item-children-instance-text-wrapper.episode-item { .item-children-instance-text-wrapper.episode-item {
width: 250px; width: 250px;
} }
.item-children-instance-text-wrapper.album-item {
width: 150px;
}
.item-children-instance-text-wrapper h3 { .item-children-instance-text-wrapper h3 {
width: 100%; width: 100%;
padding: 5px 3px 0 3px; padding: 5px 3px 0 3px;
@@ -3155,8 +3154,8 @@ pre::-webkit-scrollbar-thumb {
background-color: rgba(0,0,0,.15); background-color: rgba(0,0,0,.15);
} }
@media only screen @media only screen
and (min-device-width: 300px) and (min-device-width: 300px)
and (max-device-width: 450px) { and (max-device-width: 450px) {
.home-platforms-instance { .home-platforms-instance {
width: calc(100% - 20px); width: calc(100% - 20px);
@@ -3578,6 +3577,10 @@ a:hover .overlay-refresh-image:hover {
background-color: #10a4e8; background-color: #10a4e8;
background-image: url(../images/platforms/chromecast.svg); background-image: url(../images/platforms/chromecast.svg);
} }
.platform-default {
background-color: #e5a00d;
background-image: url(../images/platforms/default.svg);
}
.platform-dlna { .platform-dlna {
background-color: #0cb14b; background-color: #0cb14b;
background-image: url(../images/platforms/dlna.svg); background-image: url(../images/platforms/dlna.svg);
@@ -3586,6 +3589,10 @@ a:hover .overlay-refresh-image:hover {
background-color: #e67817; background-color: #e67817;
background-image: url(../images/platforms/firefox.svg); background-image: url(../images/platforms/firefox.svg);
} }
.platform-gtv {
background-color: #008bcf;
background-image: url(../images/platforms/gtv.svg);
}
.platform-ie { .platform-ie {
background-color: #00599e; background-color: #00599e;
background-image: url(../images/platforms/ie.svg); background-image: url(../images/platforms/ie.svg);
@@ -3602,6 +3609,10 @@ a:hover .overlay-refresh-image:hover {
background-color: #1793d0; background-color: #1793d0;
background-image: url(../images/platforms/linux.svg); background-image: url(../images/platforms/linux.svg);
} }
.platform-macos {
background-color: #858487;
background-image: url(../images/platforms/macos.svg);
}
.platform-msedge { .platform-msedge {
background-color: #0078d7; background-color: #0078d7;
background-image: url(../images/platforms/msedge.svg); background-image: url(../images/platforms/msedge.svg);
@@ -3622,10 +3633,6 @@ a:hover .overlay-refresh-image:hover {
background-color: #e5a00d; background-color: #e5a00d;
background-image: url(../images/platforms/plexamp.svg); background-image: url(../images/platforms/plexamp.svg);
} }
.platform-plextogether {
background-color: #151924;
background-image: url(../images/platforms/plextogether.svg);
}
.platform-roku { .platform-roku {
background-color: #6d3c97; background-color: #6d3c97;
background-image: url(../images/platforms/roku.svg); background-image: url(../images/platforms/roku.svg);
@@ -3638,6 +3645,18 @@ a:hover .overlay-refresh-image:hover {
background-color: #034ea2; background-color: #034ea2;
background-image: url(../images/platforms/samsung.svg); 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 { .platform-windows {
background-color: #2fc0f5; background-color: #2fc0f5;
background-image: url(../images/platforms/windows.svg); background-image: url(../images/platforms/windows.svg);
@@ -3646,10 +3665,6 @@ a:hover .overlay-refresh-image:hover {
background-color: #68217a; background-color: #68217a;
background-image: url(../images/platforms/windows.svg); background-image: url(../images/platforms/windows.svg);
} }
.platform-wiiu {
background-color: #03a9f4;
background-image: url(../images/platforms/wiiu.svg);
}
.platform-xbmc { .platform-xbmc {
background-color: #3b4872; background-color: #3b4872;
background-image: url(../images/platforms/xbmc.svg); background-image: url(../images/platforms/xbmc.svg);
@@ -3658,10 +3673,6 @@ a:hover .overlay-refresh-image:hover {
background-color: #107c10; background-color: #107c10;
background-image: url(../images/platforms/xbox.svg); background-image: url(../images/platforms/xbox.svg);
} }
.platform-default {
background-color: #e5a00d;
background-image: url(../images/platforms/default.svg);
}
.library-movie { .library-movie {
background-image: url(../images/libraries/movie.svg); background-image: url(../images/libraries/movie.svg);
} }
@@ -3686,4 +3697,75 @@ a:hover .overlay-refresh-image:hover {
} }
.no-image { .no-image {
background-image: none !important; 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']}"> 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-container">
<div class="dashboard-activity-background-overlay"> <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> <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: % else:
% if (data['art'] and data['art'].startswith('http')) or (data['thumb'] and data['thumb'].startswith('http')): % 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': % 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> <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 % endif
% if data['channel_stream'] == '0': % if data['channel_stream'] == 0:
% if data['media_type'] == 'movie': % if data['media_type'] == 'movie':
<a id="poster-url-${sk}" href="info?rating_key=${data['rating_key']}" title="${data['title']}"> <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> <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 % endif
</div> </div>
</li> </li>
% if data['optimized_version'] == '1': % if data['optimized_version'] == 1:
<li class="dashboard-activity-info-item"> <li class="dashboard-activity-info-item">
<div class="sub-heading">Optimized</div> <div class="sub-heading">Optimized</div>
<div class="sub-value" id="optimized_version-${sk}"> <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> </div>
</li> </li>
% endif % endif
@@ -177,7 +185,7 @@ DOCUMENTATION :: END
<div class="sub-value" id="transcode_decision-${sk}"> <div class="sub-value" id="transcode_decision-${sk}">
% if data['transcode_decision'] == 'transcode': % if data['transcode_decision'] == 'transcode':
Transcode Transcode
% if data['transcode_throttled'] == '1': % if data['transcode_throttled'] == 1:
(Throttled) (Throttled)
% else: % else:
(Speed: ${data['transcode_speed']}) (Speed: ${data['transcode_speed']})
@@ -185,7 +193,7 @@ DOCUMENTATION :: END
% elif data['transcode_decision'] == 'copy': % elif data['transcode_decision'] == 'copy':
Direct Stream Direct Stream
% else: % else:
Direct Play ${'(Synced)' if data['synced_version'] == '1' else ''} Direct Play
% endif % endif
</div> </div>
</li> </li>
@@ -207,9 +215,9 @@ DOCUMENTATION :: END
% if data.get('stream_video_decision') == 'transcode': % if data.get('stream_video_decision') == 'transcode':
<% <%
hw_d = hw_e = '' 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)' 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)' 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'])}) 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"> <li class="dashboard-activity-info-item">
<div class="sub-heading">Subtitle</div> <div class="sub-heading">Subtitle</div>
<div class="sub-value" id="subtitle_decision-${sk}"> <div class="sub-value" id="subtitle_decision-${sk}">
% if data['subtitles'] == '1': % if data['subtitles'] == 1:
% if data['stream_subtitle_decision'] == 'transcode': % if data['stream_subtitle_decision'] == 'transcode':
Transcode (${data['subtitle_codec'].upper()} &rarr; ${data['stream_subtitle_codec'].upper()}) Transcode (${data['subtitle_codec'].upper()} &rarr; ${data['stream_subtitle_codec'].upper()})
% elif data['stream_subtitle_decision'] == 'copy': % elif data['stream_subtitle_decision'] == 'copy':
@@ -250,7 +258,7 @@ DOCUMENTATION :: END
% elif data['stream_subtitle_decision'] == 'burn': % elif data['stream_subtitle_decision'] == 'burn':
Burn (${data['subtitle_codec'].upper()}) Burn (${data['subtitle_codec'].upper()})
% else: % else:
Direct Play (${data['subtitle_codec'].upper()}) Direct Play (${data['stream_subtitle_codec'].upper() if data['synced_version'] else data['subtitle_codec'].upper()})
% endif % endif
% else: % else:
None None
@@ -264,7 +272,7 @@ DOCUMENTATION :: END
<div class="sub-heading">Location</div> <div class="sub-heading">Location</div>
<div class="sub-value"> <div class="sub-value">
% if data['ip_address'] != 'N/A': % 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']}"> <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> <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> </a>
@@ -294,7 +302,7 @@ DOCUMENTATION :: END
%> %>
<span id="stream-bandwidth-${sk}">${bw}</span> <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> <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> <span id="stream-bandwidth-${sk}">None</span>
% else: % else:
<span id="stream-bandwidth-${sk}">Unknown</span> <span id="stream-bandwidth-${sk}">Unknown</span>
@@ -358,7 +366,7 @@ DOCUMENTATION :: END
% endif % endif
</div> </div>
<div class="dashboard-activity-metadata-title"> <div class="dashboard-activity-metadata-title">
% if data['channel_stream'] == '0': % if data['channel_stream'] == 0:
% if data['media_type'] == 'movie': % if data['media_type'] == 'movie':
<a href="info?rating_key=${data['rating_key']}" title="${data['title']}">${data['title']}</a> <a href="info?rating_key=${data['rating_key']}" title="${data['title']}">${data['title']}</a>
% elif data['media_type'] == 'episode': % elif data['media_type'] == 'episode':
@@ -383,7 +391,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
<div class="dashboard-activity-metadata-subtitle-container"> <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()}"> <div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="${data['media_type'].capitalize()}">
% if data['media_type'] == 'movie': % if data['media_type'] == 'movie':
<i class="fa fa-fw fa-film"></i>&nbsp; <i class="fa fa-fw fa-film"></i>&nbsp;
@@ -403,7 +411,7 @@ DOCUMENTATION :: END
</div> </div>
% endif % endif
<div class="dashboard-activity-metadata-subtitle"> <div class="dashboard-activity-metadata-subtitle">
% if data['channel_stream'] == '0': % if data['channel_stream'] == 0:
% if data['media_type'] == 'movie': % if data['media_type'] == 'movie':
<span title="${data['year']}" class="sub-heading">${data['year']}</span> <span title="${data['year']}" class="sub-heading">${data['year']}</span>
% elif data['media_type'] == 'episode': % elif data['media_type'] == 'episode':

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> <browserconfig>
<msapplication> <msapplication>
<tile> <tile>
<square70x70logo src="${http_root}images/favicon/mstile-70x70.png?v=2.0.0"/> <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.0"/> <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.0"/> <square310x310logo src="${http_root}images/favicon/mstile-310x310.png?v=2.0.1"/>
<TileColor>#1f1f1f</TileColor> <TileColor>#282a2d</TileColor>
</tile> </tile>
</msapplication> </msapplication>
</browserconfig> </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", "name": "Tautulli",
"icons": [ "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", "sizes": "192x192",
"type": "image/png" "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", "sizes": "256x256",
"type": "image/png" "type": "image/png"
} }
], ],
"theme_color": "#1f1f1f", "theme_color": "#282a2d",
"background_color": "#1f1f1f", "background_color": "#282a2d",
"display": "standalone" "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"?> <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>
<!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>

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() { function getCurrentActivity() {
activity_ready = false;
$.ajax({ $.ajax({
url: 'get_activity', url: 'get_activity',
type: 'GET', type: 'GET',
@@ -256,8 +261,13 @@
} }
if (!(current_activity)) { 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(''); $('#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 return
} }
@@ -293,7 +303,8 @@
// Create a new instance if it doesn't exist // Create a new instance if it doesn't exist
if (!(instance.length)) { if (!(instance.length)) {
getActivityInstance(session); create_instances.push(key);
getActivityInstance(key);
return; return;
} }
@@ -352,7 +363,7 @@
} else if (s.transcode_decision === 'copy') { } else if (s.transcode_decision === 'copy') {
transcode_decision = 'Direct Stream'; transcode_decision = 'Direct Stream';
} else { } else {
transcode_decision = 'Direct Play' + ((s.synced_version == 1) ? ' (Synced)' : ''); transcode_decision = 'Direct Play';
} }
$('#transcode_decision-' + key).html(transcode_decision); $('#transcode_decision-' + key).html(transcode_decision);
@@ -429,7 +440,7 @@
} else if (s.stream_subtitle_decision === 'burn') { } else if (s.stream_subtitle_decision === 'burn') {
subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')'; subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')';
} else { } 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); $('#subtitle_decision-' + key).html(subtitle_decision);
@@ -448,7 +459,8 @@
} else { } else {
$('#stream_quality-' + key).html(s.quality_profile); $('#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)) { if (s.media_type != 'photo' && parseInt(s.bandwidth)) {
var bw = parseInt(s.bandwidth); var bw = parseInt(s.bandwidth);
@@ -495,30 +507,41 @@
$('#currentActivityHeader').text(''); $('#currentActivityHeader').text('');
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">Nothing is currently being played.</div>'); $('#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({ $.ajax({
url: 'get_current_activity_instance', url: 'get_current_activity_instance',
type: 'GET', type: 'GET',
cache: false, cache: false,
async: true, async: true,
data: session, data: {
session_key: session_key
},
complete: function(xhr, status) { complete: function(xhr, status) {
$('#currentActivity').append(xhr.responseText); $('#currentActivity').append(xhr.responseText);
$('#activity-instance-' + session.session_key + ' .dashboard-activity-info-scroller').scrollbar(); $('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller').scrollbar();
$('#activity-instance-' + session.session_key + ' [data-toggle=tooltip]').tooltip({ container: 'body', placement: 'right', delay: 50 }) $('#activity-instance-' + session_key + ' [data-toggle=tooltip]').tooltip({ container: 'body', placement: 'right', delay: 50 });
$('#terminate-button-' + session.session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 }); $('#terminate-button-' + session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 });
lockScroll('#activity-instance-' + session.session_key + ' .dashboard-activity-info-scroller'); 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(); getCurrentActivity();
setInterval(function () { setInterval(function () {
getCurrentActivity(); if (!(create_instances.length) && activity_ready) {
getCurrentActivity();
}
}, 2000); }, 2000);
setInterval(function(){ setInterval(function(){

View File

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

View File

@@ -37,13 +37,42 @@ DOCUMENTATION :: END
% else: % else:
<li> <li>
% endif % 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']}"> <a href="info?rating_key=${child['rating_key']}" title="Season ${child['media_index']}">
<div class="item-children-poster"> <div class="item-children-poster">
% if child['thumb']: % if child['thumb']:
<div class="item-children-poster-face 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: % 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 % endif
<div class="item-children-card-overlay"> <div class="item-children-card-overlay">
<div class="item-children-overlay-text"> <div class="item-children-overlay-text">
@@ -59,7 +88,7 @@ DOCUMENTATION :: END
% elif data['children_type'] == 'episode': % elif data['children_type'] == 'episode':
<a href="info?rating_key=${child['rating_key']}" title="Episode ${child['media_index']}"> <a href="info?rating_key=${child['rating_key']}" title="Episode ${child['media_index']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face episode-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-card-overlay">
<div class="item-children-overlay-text"> <div class="item-children-overlay-text">
Episode ${child['media_index']} Episode ${child['media_index']}
@@ -79,13 +108,13 @@ DOCUMENTATION :: END
% elif data['children_type'] == 'album': % elif data['children_type'] == 'album':
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}"> <a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<div class="item-children-poster"> <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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
</div> </div>
</a> </a>
<div class="item-children-instance-text-wrapper album-item"> <div class="item-children-instance-text-wrapper cover-item">
<h3> <h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a> <a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
</h3> </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 != None:
% if data['results_count'] > 0: % 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']: % if 'movie' in data['results_list'] and data['results_list']['movie']:
<div class="item-children-wrapper"> <div class="item-children-wrapper">
<div class="item-children-section-title"> <div class="item-children-section-title">
@@ -64,12 +89,12 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
</div> </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 title="${child['title']}">${child['title']}</h3>
<h3 class="text-muted">${child['year']}</h3> <h3 class="text-muted">${child['year']}</h3>
</div> </div>
@@ -89,12 +114,12 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
</div> </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 title="${child['title']}">${child['title']}</h3>
<h3 class="text-muted">${child['year']}</h3> <h3 class="text-muted">${child['year']}</h3>
</div> </div>
@@ -114,12 +139,12 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
</div> </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 title="${child['parent_title']}">${child['parent_title']}</h3>
<h3 class="text-muted">S${child['media_index']}</h3> <h3 class="text-muted">S${child['media_index']}</h3>
</div> </div>
@@ -139,7 +164,7 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@@ -165,7 +190,7 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@@ -189,7 +214,7 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <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': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@@ -214,7 +239,7 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <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-card-overlay">
<div class="item-children-overlay-text"> <div class="item-children-overlay-text">
Track ${child['media_index']} Track ${child['media_index']}

View File

@@ -68,7 +68,7 @@ media_info_table_options = {
}, },
{ {
"targets": [1], "targets": [1],
"data": "title", "data": "sort_title",
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== null && cellData !== '') { if (cellData !== null && cellData !== '') {
var parent_info = ''; var parent_info = '';
@@ -77,31 +77,31 @@ media_info_table_options = {
if (rowData['media_type'] === 'movie') { if (rowData['media_type'] === 'movie') {
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; } if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + 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>'); $(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') { } else if (rowData['media_type'] === 'show') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="TV Show"><i class="fa fa-television fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="TV Show"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + 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>'); $(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') { } else if (rowData['media_type'] === 'season') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Season"><i class="fa fa-television fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Season"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + 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>'); $(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') { } else if (rowData['media_type'] === 'episode') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + 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>'); $(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') { } else if (rowData['media_type'] === 'artist') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Artist"><i class="fa fa-music fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Artist"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + 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>'); $(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') { } else if (rowData['media_type'] === 'album') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Album"><i class="fa fa-music fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Album"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + 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>'); $(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') { } else if (rowData['media_type'] === 'track') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">T' + rowData['media_index'] + ' - ' + 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>'); $(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 { } else {
$(td).html(cellData); $(td).html(cellData);

View File

@@ -8,17 +8,6 @@
<%def name="body()"> <%def name="body()">
<div class='container-fluid'> <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='table-card-header'>
<div class="header-bar"> <div class="header-bar">
<span><i class="fa fa-book"></i> All Libraries</span> <span><i class="fa fa-book"></i> All Libraries</span>
@@ -31,16 +20,10 @@
<i class="fa fa-pencil"></i> Edit mode <i class="fa fa-pencil"></i> Edit mode
</button>&nbsp </button>&nbsp
</div> </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"> <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> <button class="btn btn-dark refresh-libraries-button" id="refresh-libraries-list"><i class="fa fa-refresh"></i> Refresh libraries</button>
</div> </div>
% endif % endif
% endif
<div class="btn-group colvis-button-bar"></div> <div class="btn-group colvis-button-bar"></div>
</div> </div>
</div> </div>
@@ -197,14 +180,6 @@
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
$("#refresh-libraries-list").click(function () { $("#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({ $.ajax({
url: 'refresh_libraries_list', url: 'refresh_libraries_list',
cache: false, cache: false,

View File

@@ -71,11 +71,11 @@ DOCUMENTATION :: END
% endif % endif
</div> </div>
<div class="user-info-nav"> <div class="user-info-nav">
<ul class="user-info-nav"> <ul class="user-info-nav" role="tablist">
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li> <li class="active"><a href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
<li><a id="history-tab-btn" href="#libraryHistory" data-toggle="tab">History</a></li> <li><a id="history-tab-btn" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<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 % endif
</ul> </ul>
</div> </div>
@@ -83,7 +83,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
<div class="tab-content"> <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="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -169,7 +169,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="libraryHistory"> <div role="tabpanel" class="tab-pane" id="tabs-history">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -221,7 +221,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="libraryMediaInfo"> <div role="tabpanel" class="tab-pane" id="tabs-mediainfo">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <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/history_table.js${cache_param}"></script>
<script src="${http_root}js/tables/media_info_table.js${cache_param}"></script> <script src="${http_root}js/tables/media_info_table.js${cache_param}"></script>
<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 () { $(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 // Populate watch time stats
$.ajax({ $.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> </script>
% endif % endif

View File

@@ -44,7 +44,7 @@
<div class="row"> <div class="row">
<div class="login-container"> <div class="login-container">
<div class="login-logo"> <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>
<div class="row"> <div class="row">
<div class="col-sm-6 col-sm-offset-3"> <div class="col-sm-6 col-sm-offset-3">

View File

@@ -142,8 +142,9 @@
<input type="hidden" name="custom_conditions" id="custom_conditions" /> <input type="hidden" name="custom_conditions" id="custom_conditions" />
<div class="form-group"> <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 /> <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"> <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>). Enter the logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>).
</p> </p>
@@ -365,8 +366,10 @@
$('input[type=text]').val(function(_, value) { $('input[type=text]').val(function(_, value) {
return $.trim(value); return $.trim(value);
}); });
// Reload modal to update certain fields if (validateLogic()) {
doAjaxCall('set_notifier_config', $(this), 'tabs', true, true, saveCallback); // Reload modal to update certain fields
doAjaxCall('set_notifier_config', $(this), 'tabs', true, true, saveCallback);
}
} }
$('#delete-notifier-item').click(function () { $('#delete-notifier-item').click(function () {
@@ -466,6 +469,107 @@
}) })
% endif % 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 () { $('.notifier-text-preview').click(function () {
var action = $(this).data('action'); var action = $(this).data('action');
var subject = $('#' + action + '_subject').val(); 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> <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> <h4 class="modal-title" id="notifier-config-modal-header">Error</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body" style="text-align: center">
<center><strong> <strong>
<i class="fa fa-exclamation-circle"></i> Failed to retrieve notifier configuration. Check the <a href="logs">logs</a> for more info. <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>
<div class="modal-footer"> <div class="modal-footer">
</div> </div>

View File

@@ -18,7 +18,7 @@
</div> </div>
</div> </div>
<div class='table-card-back'> <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>
</div> </div>
</%def> </%def>

View File

@@ -357,6 +357,12 @@
</div> </div>
<p class="help-block">The base URL of the web server. Used for reverse proxies.</p> <p class="help-block">The base URL of the web server. Used for reverse proxies.</p>
</div> </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 /> <br />
<div class="checkbox"> <div class="checkbox">
<label> <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> <p class="help-block">Enable to have Tautulli check if remote access to the Plex Media Server goes down.</p>
</div> </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> <label for="pms_ip">Plex IP or Hostname</label>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
@@ -563,7 +569,7 @@
<button class="btn btn-form" type="button" id="verify_server_button">Verify Server</button> <button class="btn btn-form" type="button" id="verify_server_button">Verify Server</button>
</span> </span>
</div> </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>
<div id="pms_ip_error" class="alert alert-danger settings-alert" role="alert"></div> <div id="pms_ip_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
@@ -601,7 +607,12 @@
<label for="pms_logs_folder">Plex Web URL</label> <label for="pms_logs_folder">Plex Web URL</label>
<div class="row"> <div class="row">
<div class="col-md-6"> <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>
<div id="pms_web_url_error" class="alert alert-danger settings-alert" role="alert"></div> <div id="pms_web_url_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
@@ -827,7 +838,7 @@
<div class="checkbox"> <div class="checkbox">
<label> <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> </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> <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> </div>
@@ -848,13 +859,13 @@
<label> <label>
<input type="checkbox" name="themoviedb_lookup" id="themoviedb_lookup" value="1" ${config['themoviedb_lookup']}> Lookup TheMovieDB Links <input type="checkbox" name="themoviedb_lookup" id="themoviedb_lookup" value="1" ${config['themoviedb_lookup']}> Lookup TheMovieDB Links
</label> </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>
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="tvmaze_lookup" id="tvmaze_lookup" value="1" ${config['tvmaze_lookup']}> Lookup TVmaze Links <input type="checkbox" name="tvmaze_lookup" id="tvmaze_lookup" value="1" ${config['tvmaze_lookup']}> Lookup TVmaze Links
</label> </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> </div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p> <p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
@@ -884,12 +895,6 @@
<h3>Extra Settings</h3> <h3>Extra Settings</h3>
</div> </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"> <div class="checkbox">
<label> <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> <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>
</div> </div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-android_app"> <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. // Alert the user that their changes require a restart.
function postSaveChecks() { function postSaveChecks() {
if (serverChanged || authChanged || httpChanged || directoryChanged) { if (serverChanged || authChanged || httpChanged || directoryChanged) {
@@ -1607,11 +1625,7 @@ $(document).ready(function() {
} }
$('.save-button').click(function() { $('.save-button').click(function() {
if ($("#pms_identifier").val() == "") { preSaveChecks(function () { saveSettings() });
verifyServer(function () { saveSettings() });
} else {
saveSettings();
}
}); });
initConfigCheckbox('#api_enabled'); initConfigCheckbox('#api_enabled');
@@ -1756,43 +1770,45 @@ $(document).ready(function() {
var pms_identifier = $("#pms_identifier").val(); var pms_identifier = $("#pms_identifier").val();
var pms_ssl = $("#pms_ssl").is(':checked') ? 1 : 0; var pms_ssl = $("#pms_ssl").is(':checked') ? 1 : 0;
var pms_is_remote = $("#pms_is_remote").is(':checked') ? 1 : 0; var pms_is_remote = $("#pms_is_remote").is(':checked') ? 1 : 0;
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) { if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
$("#pms-verify").html('<i class="fa fa-refresh fa-spin"></i>'); $("#pms_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast');
$.ajax({ $.ajax({
url: 'get_server_id', 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, cache: true,
async: true, async: true,
timeout: 10000, timeout: 10000,
error: function(jqXHR, textStatus, errorThrown) { error: function (jqXHR, textStatus, errorThrown) {
$("#pms-verify").html('<i class="fa fa-close"></i>'); $("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast'); $("#pms_ip_group").addClass("has-error");
$("#pms-ip-group").addClass("has-error");
}, },
success: function (json) { success: function (json) {
var machine_identifier = json; var machine_identifier = json;
if (machine_identifier) { if (machine_identifier) {
$("#pms_identifier").val(machine_identifier); $("#pms_identifier").val(machine_identifier);
$("#pms-verify").html('<i class="fa fa-check"></i>'); $("#pms_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast'); $("#pms_ip_group").removeClass("has-error");
$("#pms-ip-group").removeClass("has-error");
if (_callback) { if (_callback) {
_callback(); _callback();
} }
} else { } else {
$("#pms-verify").html('<i class="fa fa-close"></i>'); $("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast'); $("#pms_ip_group").addClass("has-error");
$("#pms-ip-group").addClass("has-error");
showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 5000, true) showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 5000, true)
} }
} }
}); });
} else { } else {
$("#pms-verify").html('<i class="fa fa-close"></i>'); $("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast'); $("#pms_ip_group").addClass("has-error");
$("#pms-ip-group").addClass("has-error");
showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 5000, true) 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(); 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 // Plex.tv auth token fetch
$("#get-pms-auth-token").click(function() { $("#get-pms-auth-token").click(function() {
$("#pms-token-status").html('<i class="fa fa-refresh fa-spin"></i> Fetching token...'); $("#pms-token-status").html('<i class="fa fa-refresh fa-spin"></i> Fetching token...');
var pms_username = $("#pms_username").val().trim(); var pms_username = $.trim($("#pms_username").val());
var pms_password = $("#pms_password").val().trim(); var pms_password = $.trim($("#pms_password").val());
if ((pms_username !== '') && (pms_password !== '')) { if ((pms_username !== '') && (pms_password !== '')) {
$.ajax({ $.ajax({
type: 'GET', type: 'GET',

View File

@@ -38,6 +38,9 @@ DOCUMENTATION :: END
</%doc> </%doc>
% if data: % if data:
<%
import plexpy
%>
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@@ -51,86 +54,177 @@ DOCUMENTATION :: END
</h4> </h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="container-fluid"> <table class="stream-info" style="margin-top: 0;">
<div class="row"> <thead>
<div class="col-sm-12"> <tr>
<h4><strong>Stream Details</strong></h4> <th>
</div> </th>
<div class="col-sm-4"> <th class="heading">
<h5>Media</h5> Stream Details
<ul class="list-unstyled"> </th>
<li>Container: <strong>${data['transcode_container'] if data['transcode_container'] else data['container']}</strong></li> <th class="heading">
% if data['media_type'] != 'track': Source Details
<li>Resolution: <strong>${data['transcode_height'] if data['transcode_height'] else data['height']}p</strong></li> </th>
% endif </tr>
</ul> </thead>
</div> </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': % if data['media_type'] != 'track':
<div class="col-sm-4"> <tr>
<h5>Video</h5> <td>Resolution</td>
<ul class="list-unstyled"> <td>${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])}</td>
<li>Stream: <strong>${data['transcode_video_dec']}</strong></li> <td>${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])}</td>
% if data['transcode_video_dec'] != 'direct play': </tr>
<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>
% endif % endif
<div class="col-sm-4"> <tr>
<h5>Audio</h5> <td>Quality</td>
<ul class="list-unstyled"> <td>${data['quality_profile']}</td>
<li>Stream: <strong>${data['transcode_audio_dec']}</strong></li> <td>-</td>
% if data['transcode_audio_dec'] != 'direct play': </tr>
<li>Codec: <strong>${data['transcode_audio_codec']}</strong></li> % if data['optimized_version'] == 1:
<li>Channels: <strong>${data['transcode_audio_channels']}</strong></li> <tr>
% else: <td>Optimized Version</td>
<li>Codec: <strong>${data['audio_codec']}</strong></li> <td>-</td>
<li>Channels: <strong>${data['audio_channels']}</strong></li> <td>${data['optimized_version_profile']}<br>(${data['optimized_version_title']})</td>
% endif </tr>
</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>
% endif % endif
<div class="col-sm-4"> % if data['synced_version'] == 1:
<h5>Audio</h5> <tr>
<ul class="list-unstyled"> <td>Synced Version</td>
<li>Codec: <strong>${data['audio_codec']}</strong></li> <td>-</td>
<li>Channels: <strong>${data['audio_channels']}</strong></li> <td>${data['synced_version_profile']}</td>
</ul> </tr>
</div> % endif
</div> </tbody>
</div> </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>
<div class="modal-footer"> <div class="modal-footer">
</div> </div>

View File

@@ -61,19 +61,19 @@ DOCUMENTATION :: END
% endif % endif
</div> </div>
<div class="user-info-nav"> <div class="user-info-nav">
<ul class="user-info-nav"> <ul class="user-info-nav" role="tablist">
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li> <li class="active"><a href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
<li><a id="history-tab-btn" href="#userHistory" data-toggle="tab">History</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="#userSyncItems" data-toggle="tab">Synced Items</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="#userAddresses" data-toggle="tab">IP Addresses</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="#userLogins" data-toggle="tab">Tautulli Logins</a></li> <li><a id="login-tab-btn" href="#tabs-tautullilogins" role="tab" data-toggle="tab">Tautulli Logins</a></li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="tab-content"> <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="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -134,7 +134,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="userHistory"> <div role="tabpanel" class="tab-pane" id="tabs-history">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -200,7 +200,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="userSyncItems"> <div role="tabpanel" class="tab-pane" id="tabs-synceditems">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -240,7 +240,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="userAddresses"> <div role="tabpanel" class="tab-pane" id="tabs-ipaddresses">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -271,7 +271,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="userLogins"> <div role="tabpanel" class="tab-pane" id="tabs-tautullilogins">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <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.min.js"></script>
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script> <script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
% if data: % if data:
<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/moment-with-locale.js"></script>
<script src="${http_root}js/tables/history_table.js${cache_param}"></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/user_ips.js${cache_param}"></script>
<script src="${http_root}js/tables/sync_table.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 src="${http_root}js/tables/login_logs.js${cache_param}"></script>
<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 () { $(document).ready(function () {
% if str(data['user_id']).isdigit(): // Javascript to enable link to tab
var user_id = ${data['user_id']}; var hash = document.location.hash;
% else: var prefix = "tab_";
var user_id = null; if (hash) {
% endif $('.user-info-nav a[href='+hash.replace(prefix,"")+']').tab('show').trigger('show.bs.tab');
}
var username = '${data['username'].replace("'", "\\'")}'; // Change hash for page-reload
$('.user-info-nav a').on('shown.bs.tab', function (e) {
$("#edit-user-tooltip").tooltip(); window.location.hash = e.target.hash.replace("#", "#" + prefix);
});
// Populate watch time stats // Populate watch time stats
$.ajax({ $.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> </script>
% endif % endif

View File

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

View File

@@ -164,12 +164,15 @@
<!-- Required fields but hidden --> <!-- Required fields but hidden -->
<div style="display: none;"> <div style="display: none;">
<input type="checkbox" name="first_run" id="first_run" value="1" checked> <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="group_history_tables" id="group_history_tables" value="1" checked>
<input type="checkbox" name="refresh_users_on_startup" id="refresh_users_on_startup" value="1" ${config['refresh_users_on_startup']}> <input type="checkbox" name="history_table_activity" id="history_table_activity" value="1" checked>
<input type="checkbox" name="refresh_libraries_on_startup" id="refresh_libraries_on_startup" value="1" ${config['refresh_libraries_on_startup']}> <input type="checkbox" name="launch_browser" id="launch_browser" value="1" checked>
<input type="checkbox" name="check_github" id="check_github" value="1" ${config['check_github']}> <input type="checkbox" name="api_enabled" id="api_enabled" value="1" checked>
<input type="checkbox" name="log_blacklist" id="log_blacklist" value="1" ${config['log_blacklist']}> <input type="checkbox" name="refresh_users_on_startup" id="refresh_users_on_startup" value="1" checked>
<input type="checkbox" name="cache_images" id="cache_images" value="1" ${config['cache_images']}> <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="server_changed" id="server_changed" value="1" checked>
<input type="checkbox" name="first_run_complete" id="first_run_complete" 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"> <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 # Check if Tautulli has a uuid
if CONFIG.PMS_UUID == '' or not CONFIG.PMS_UUID: if CONFIG.PMS_UUID == '' or not CONFIG.PMS_UUID:
logger.debug(u"Generating UUID...")
my_uuid = generate_uuid() my_uuid = generate_uuid()
CONFIG.__setattr__('PMS_UUID', my_uuid) CONFIG.__setattr__('PMS_UUID', my_uuid)
CONFIG.write() 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 # Get the currently installed version. Returns None, 'win32' or the git
# hash. # hash.
CURRENT_VERSION, CONFIG.GIT_REMOTE, CONFIG.GIT_BRANCH = versioncheck.getVersion() CURRENT_VERSION, CONFIG.GIT_REMOTE, CONFIG.GIT_BRANCH = versioncheck.getVersion()
@@ -438,11 +446,12 @@ def dbcheck():
'stream_video_decision TEXT, stream_video_codec TEXT, stream_video_bitrate INTEGER, stream_video_width INTEGER, ' 'stream_video_decision TEXT, stream_video_codec TEXT, stream_video_bitrate INTEGER, stream_video_width INTEGER, '
'stream_video_height INTEGER, stream_video_framerate TEXT, ' 'stream_video_height INTEGER, stream_video_framerate TEXT, '
'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, ' '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_protocol TEXT, transcode_container TEXT, '
'transcode_video_codec TEXT, transcode_audio_codec TEXT, transcode_audio_channels INTEGER,' 'transcode_video_codec TEXT, transcode_audio_codec TEXT, transcode_audio_channels INTEGER,'
'transcode_width INTEGER, transcode_height 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, ' 'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, write_attempts INTEGER DEFAULT 0, '
'raw_stream_info TEXT)' 'raw_stream_info TEXT)'
) )
@@ -474,7 +483,8 @@ def dbcheck():
'stream_video_framerate TEXT, ' 'stream_video_framerate TEXT, '
'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, ' '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, ' '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 # session_history_metadata table :: This is a table which logs each session's media metadata
@@ -893,6 +903,27 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN video_height INTEGER' '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 # Upgrade session_history table from earlier versions
try: try:
c_db.execute('SELECT reference_id FROM session_history') c_db.execute('SELECT reference_id FROM session_history')
@@ -1114,6 +1145,27 @@ def dbcheck():
'UPDATE session_history_media_info SET video_resolution=REPLACE(video_resolution, "SD", "sd")' '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 # Upgrade users table from earlier versions
try: try:
c_db.execute('SELECT do_notify FROM users') c_db.execute('SELECT do_notify FROM users')
@@ -1491,5 +1543,4 @@ def shutdown(restart=False, update=False, checkout=False):
def generate_uuid(): def generate_uuid():
logger.debug(u"Generating UUID...")
return uuid.uuid4().hex return uuid.uuid4().hex

View File

@@ -484,6 +484,11 @@ def on_created(rating_key, **kwargs):
if metadata: if metadata:
notify = True 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() data_factory = datafactory.DataFactory()
if 'child_keys' not in kwargs: if 'child_keys' not in kwargs:

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ _CONFIG_DEFINITIONS = {
'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'), 'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'),
'TIME_FORMAT': (str, 'General', 'HH:mm'), 'TIME_FORMAT': (str, 'General', 'HH:mm'),
'ANON_REDIRECT': (str, 'General', 'http://www.nullrefer.com/?'), 'ANON_REDIRECT': (str, 'General', 'http://www.nullrefer.com/?'),
'API_ENABLED': (int, 'General', 0), 'API_ENABLED': (int, 'General', 1),
'API_KEY': (str, 'General', ''), 'API_KEY': (str, 'General', ''),
'API_SQL': (int, 'General', 0), 'API_SQL': (int, 'General', 0),
'BOXCAR_ENABLED': (int, 'Boxcar', 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.NOTIFY_GROUP_RECENTLY_ADDED_PARENT = self.NOTIFY_GROUP_RECENTLY_ADDED
self.MONITORING_USE_WEBSOCKET = 1 self.MONITORING_USE_WEBSOCKET = 1
self.HTTP_PROXY = 1
self.CONFIG_VERSION = 8 self.CONFIG_VERSION = 8
@@ -872,4 +871,4 @@ class Config(object):
self.TV_WATCHED_PERCENT = self.NOTIFY_WATCHED_PERCENT self.TV_WATCHED_PERCENT = self.NOTIFY_WATCHED_PERCENT
self.MUSIC_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()) user_cond = 'AND %s.user_id = %s ' % (table, session.get_session_user_id())
if row_id: if row_id:
query = 'SELECT container, bitrate, video_resolution, width, height, aspect_ratio, video_framerate, ' \ query = 'SELECT bitrate, video_resolution, ' \
'video_codec, audio_codec, audio_channels, video_decision, transcode_video_codec, transcode_height, ' \ 'optimized_version, optimized_version_profile, optimized_version_title, ' \
'transcode_width, audio_decision, transcode_audio_codec, transcode_audio_channels, transcode_container, ' \ '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 ' \ 'session_history_metadata.media_type, title, grandparent_title ' \
'FROM session_history_media_info ' \ 'FROM session_history_media_info ' \
'JOIN session_history ON session_history_media_info.id = session_history.id ' \ '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 'WHERE session_history_media_info.id = ? %s' % user_cond
result = monitor_db.select(query, args=[row_id]) result = monitor_db.select(query, args=[row_id])
elif session_key: elif session_key:
query = 'SELECT container, bitrate, video_resolution, width, height, aspect_ratio, video_framerate, ' \ query = 'SELECT bitrate, video_resolution, ' \
'video_codec, audio_codec, audio_channels, video_decision, transcode_video_codec, transcode_height, ' \ 'optimized_version, optimized_version_profile, optimized_version_title, ' \
'transcode_width, audio_decision, transcode_audio_codec, transcode_audio_channels, transcode_container, ' \ '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 ' \ 'media_type, title, grandparent_title ' \
'FROM sessions ' \ 'FROM sessions ' \
'WHERE session_key = ? %s' % user_cond 'WHERE session_key = ? %s' % user_cond
@@ -895,24 +909,42 @@ class DataFactory(object):
stream_output = {} stream_output = {}
for item in result: for item in result:
stream_output = {'container': item['container'], stream_output = {'bitrate': item['bitrate'],
'bitrate': item['bitrate'],
'video_resolution': item['video_resolution'], 'video_resolution': item['video_resolution'],
'width': item['width'], 'optimized_version': item['optimized_version'],
'height': item['height'], 'optimized_version_profile': item['optimized_version_profile'],
'aspect_ratio': item['aspect_ratio'], 'optimized_version_title': item['optimized_version_title'],
'video_framerate': item['video_framerate'], 'synced_version': item['synced_version'],
'synced_version_profile': item['synced_version_profile'],
'container': item['container'],
'video_codec': item['video_codec'], '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_codec': item['audio_codec'],
'audio_bitrate': item['audio_bitrate'],
'audio_channels': item['audio_channels'], 'audio_channels': item['audio_channels'],
'transcode_video_dec': item['video_decision'], 'subtitle_codec': item['subtitle_codec'],
'transcode_video_codec': item['transcode_video_codec'], 'stream_bitrate': item['stream_bitrate'],
'transcode_height': item['transcode_height'], 'stream_video_resolution': item['stream_video_resolution'],
'transcode_width': item['transcode_width'], 'quality_profile': item['quality_profile'],
'transcode_audio_dec': item['audio_decision'], 'stream_container_decision': item['stream_container_decision'],
'transcode_audio_codec': item['transcode_audio_codec'], 'stream_container': item['stream_container'],
'transcode_audio_channels': item['transcode_audio_channels'], 'stream_video_decision': item['stream_video_decision'],
'transcode_container': item['transcode_container'], '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'], 'media_type': item['media_type'],
'title': item['title'], 'title': item['title'],
'grandparent_title': item['grandparent_title'] 'grandparent_title': item['grandparent_title']
@@ -1072,7 +1104,7 @@ class DataFactory(object):
if str(rating_key).isdigit(): if str(rating_key).isdigit():
poster_key = rating_key poster_key = rating_key
elif metadata: elif metadata:
if metadata['media_type'] in ('movie', 'show', 'artist'): if metadata['media_type'] in ('movie', 'show', 'artist', 'collection'):
poster_key = metadata['rating_key'] poster_key = metadata['rating_key']
elif metadata['media_type'] in ('season', 'album'): elif metadata['media_type'] in ('season', 'album'):
poster_key = metadata['rating_key'] 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 # Build json data
json_data = {"draw": 1, json_data = {"draw": 1,
"columns": columns, "columns": columns,
"order": [{"column": order_column, "order": [{"column": order_column,
"dir": kwargs.pop("order_dir", "desc")}], "dir": kwargs.pop("order_dir", "desc")}],
"start": int(kwargs.pop("start", 0)), "start": int(kwargs.pop("start", 0)),
"length": int(kwargs.pop("length", 25)), "length": int(kwargs.pop("length", 25)),
"search": {"value": kwargs.pop("search", "")} "search": {"value": kwargs.pop("search", "")}
} }
return json.dumps(json_data) return json.dumps(json_data)
def humanFileSize(bytes, si=False): def humanFileSize(bytes, si=False):

View File

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

View File

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

View File

@@ -1203,14 +1203,16 @@ class DISCORD(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'discord_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
}, },
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'discord_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
}, },
@@ -1533,14 +1535,16 @@ class FACEBOOK(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'facebook_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
}, },
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'facebook_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
}, },
@@ -1872,14 +1876,16 @@ class HIPCHAT(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'hipchat_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
}, },
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'hipchat_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
}, },
@@ -2678,14 +2684,16 @@ class PUSHOVER(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'pushover_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
}, },
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'pushover_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
}, },
@@ -3038,14 +3046,16 @@ class SLACK(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'slack_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
}, },
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'slack_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', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
}, },

View File

@@ -425,7 +425,7 @@ class PlexTV(object):
return users_list 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) sync_list = self.get_plextv_sync_lists(machine_id)
user_data = users.Users() user_data = users.Users()
@@ -446,9 +446,15 @@ class PlexTV(object):
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items.") logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items.")
else: else:
for a in xml_head: for a in xml_head:
sync_id = helpers.get_xml_attr(a, 'id')
client_id = helpers.get_xml_attr(a, 'clientIdentifier') 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') sync_device = a.getElementsByTagName('Device')
for device in sync_device: for device in sync_device:
device_user_id = helpers.get_xml_attr(device, 'userID') device_user_id = helpers.get_xml_attr(device, 'userID')
try: try:
@@ -467,12 +473,23 @@ class PlexTV(object):
device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt') device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt')
# Filter by user_id # 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 continue
for synced in a.getElementsByTagName('SyncItems'): for synced in a.getElementsByTagName('SyncItems'):
sync_item = synced.getElementsByTagName('SyncItem') sync_item = synced.getElementsByTagName('SyncItem')
for item in sync_item: 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_id = helpers.get_xml_attr(item, 'id')
sync_version = helpers.get_xml_attr(item, 'version') sync_version = helpers.get_xml_attr(item, 'version')
sync_root_title = helpers.get_xml_attr(item, 'rootTitle') 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_quality = helpers.get_xml_attr(settings, 'videoQuality')
settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution') 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), sync_details = {"device_name": helpers.sanitize(device_name),
"platform": helpers.sanitize(device_platform), "platform": helpers.sanitize(device_platform),
"username": helpers.sanitize(device_username), "username": helpers.sanitize(device_username),

View File

@@ -24,6 +24,7 @@ import helpers
import http_handler import http_handler
import libraries import libraries
import logger import logger
import plextv
import session import session
import users import users
@@ -256,6 +257,23 @@ class PmsConnect(object):
return request 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=''): def get_childrens_list(self, rating_key='', output_format=''):
""" """
Return list of children in requested library item. Return list of children in requested library item.
@@ -413,7 +431,7 @@ class PmsConnect(object):
Output: array 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, request = self.request_handler.make_request(uri=uri,
proto=self.protocol, proto=self.protocol,
request_type='GET', request_type='GET',
@@ -548,6 +566,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(item, 'title'), 'title': helpers.get_xml_attr(item, 'title'),
'parent_title': helpers.get_xml_attr(item, 'parentTitle'), 'parent_title': helpers.get_xml_attr(item, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(item, 'grandparentTitle'), 'grandparent_title': helpers.get_xml_attr(item, 'grandparentTitle'),
'sort_title': helpers.get_xml_attr(item, 'titleSort'),
'media_index': helpers.get_xml_attr(item, 'index'), 'media_index': helpers.get_xml_attr(item, 'index'),
'parent_media_index': helpers.get_xml_attr(item, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(item, 'parentIndex'),
'section_id': section_id if section_id else helpers.get_xml_attr(item, 'librarySectionID'), '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'), 'title': helpers.get_xml_attr(item, 'title'),
'parent_title': helpers.get_xml_attr(item, 'parentTitle'), 'parent_title': helpers.get_xml_attr(item, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(item, 'grandparentTitle'), 'grandparent_title': helpers.get_xml_attr(item, 'grandparentTitle'),
'sort_title': helpers.get_xml_attr(item, 'titleSort'),
'media_index': helpers.get_xml_attr(item, 'index'), 'media_index': helpers.get_xml_attr(item, 'index'),
'parent_media_index': helpers.get_xml_attr(item, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(item, 'parentIndex'),
'section_id': section_id if section_id else helpers.get_xml_attr(item, 'librarySectionID'), 'section_id': section_id if section_id else helpers.get_xml_attr(item, 'librarySectionID'),
@@ -588,7 +608,7 @@ class PmsConnect(object):
return output 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. Return processed and validated metadata list for requested item.
@@ -596,12 +616,15 @@ class PmsConnect(object):
Output: array 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: try:
xml_head = metadata.getElementsByTagName('MediaContainer') xml_head = metadata.getElementsByTagName('MediaContainer')
except Exception as e: 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 {} return {}
metadata = {} metadata = {}
@@ -668,6 +691,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'), 'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -707,6 +731,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'), 'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -748,6 +773,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'), 'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': show_details['studio'], 'studio': show_details['studio'],
@@ -790,6 +816,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': 'Season %s' % helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_title': 'Season %s' % helpers.get_xml_attr(metadata_main, 'parentIndex'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': show_details['studio'], 'studio': show_details['studio'],
@@ -830,6 +857,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'), 'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -871,6 +899,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'), 'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -913,6 +942,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'), 'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -953,6 +983,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'), 'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -994,6 +1025,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'), 'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -1024,6 +1056,50 @@ class PmsConnect(object):
helpers.get_xml_attr(metadata_main, 'title')) 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': elif metadata_type == 'clip':
metadata = {'media_type': metadata_type, metadata = {'media_type': metadata_type,
'section_id': section_id, 'section_id': section_id,
@@ -1034,6 +1110,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(metadata_main, 'title'), 'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'), 'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), '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'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'),
@@ -1310,6 +1387,7 @@ class PmsConnect(object):
# Get the source media type # Get the source media type
media_type = helpers.get_xml_attr(session, 'type') media_type = helpers.get_xml_attr(session, 'type')
rating_key = helpers.get_xml_attr(session, 'ratingKey')
# Get the user details # Get the user details
user_info = session.getElementsByTagName('User')[0] user_info = session.getElementsByTagName('User')[0]
@@ -1336,7 +1414,7 @@ class PmsConnect(object):
'product_version': helpers.get_xml_attr(player_info, 'version'), 'product_version': helpers.get_xml_attr(player_info, 'version'),
'profile': helpers.get_xml_attr(player_info, 'profile'), 'profile': helpers.get_xml_attr(player_info, 'profile'),
'player': helpers.get_xml_attr(player_info, 'title') or helpers.get_xml_attr(player_info, 'product'), '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'), 'state': helpers.get_xml_attr(player_info, 'state'),
'local': helpers.get_xml_attr(player_info, 'local') 'local': helpers.get_xml_attr(player_info, 'local')
} }
@@ -1417,16 +1495,30 @@ class PmsConnect(object):
transcode_decision = 'direct play' transcode_decision = 'direct play'
# Determine if a synced version is being played # Determine if a synced version is being played
sync_id = None
if media_type not in ('photo', 'clip') and not session.getElementsByTagName('Session') \ 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': and helpers.get_xml_attr(session, 'ratingKey').isdigit() and transcode_decision == 'direct play':
synced_version = 1 plex_tv = plextv.PlexTV()
else: synced_items = plex_tv.get_synced_items(machine_id=plexpy.CONFIG.PMS_IDENTIFIER,
synced_version = 0 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 # 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_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 # Get the stream details
video_stream_info = audio_stream_info = subtitle_stream_info = None video_stream_info = audio_stream_info = subtitle_stream_info = None
@@ -1483,6 +1575,7 @@ class PmsConnect(object):
if subtitle_stream_info: if subtitle_stream_info:
subtitle_id = helpers.get_xml_attr(subtitle_stream_info, 'id') 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'), 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_container': helpers.get_xml_attr(subtitle_stream_info, 'container'),
'stream_subtitle_format': helpers.get_xml_attr(subtitle_stream_info, 'format'), '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_height': helpers.get_xml_attr(stream_media_info, 'height'),
'stream_video_width': helpers.get_xml_attr(stream_media_info, 'width'), '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_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, 'transcode_decision': transcode_decision,
'optimized_version': 1 if helpers.get_xml_attr(stream_media_info, 'proxyType') == '42' else 0, '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'), 'optimized_version_title': helpers.get_xml_attr(stream_media_info, 'title'),
'synced_version': synced_version, 'synced_version': 1 if sync_id else 0,
'indexes': 1 if indexes == 'sd' else 0, 'indexes': 1 if indexes == 'sd' else 0,
'bif_thumb': bif_thumb, '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 # Get the source media info
@@ -1560,6 +1653,7 @@ class PmsConnect(object):
'title': helpers.get_xml_attr(session, 'title'), 'title': helpers.get_xml_attr(session, 'title'),
'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'),
'sort_title': helpers.get_xml_attr(session, 'titleSort'),
'media_index': helpers.get_xml_attr(session, 'index'), 'media_index': helpers.get_xml_attr(session, 'index'),
'parent_media_index': helpers.get_xml_attr(session, 'parentIndex'), 'parent_media_index': helpers.get_xml_attr(session, 'parentIndex'),
'studio': helpers.get_xml_attr(session, 'studio'), 'studio': helpers.get_xml_attr(session, 'studio'),
@@ -1604,7 +1698,10 @@ class PmsConnect(object):
media_id = helpers.get_xml_attr(stream_media_info, 'id') media_id = helpers.get_xml_attr(stream_media_info, 'id')
part_id = helpers.get_xml_attr(stream_media_parts_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 # Get the media info, fallback to first item if match id is not found
source_medias = metadata_details.pop('media_info', []) source_medias = metadata_details.pop('media_info', [])
@@ -1669,7 +1766,22 @@ class PmsConnect(object):
quality_profile = common.VIDEO_QUALITY_PROFILES[quailtiy_bitrate] quality_profile = common.VIDEO_QUALITY_PROFILES[quailtiy_bitrate]
except ValueError: except ValueError:
quality_profile = 'Original' 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: elif media_type == 'track' and 'stream_bitrate' in stream_details:
stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate']) stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate'])
source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate')) source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
@@ -1680,11 +1792,26 @@ class PmsConnect(object):
except ValueError: except ValueError:
quality_profile = 'Original' 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': elif media_type == 'photo':
quality_profile = 'Original' quality_profile = 'Original'
synced_version_profile = ''
optimized_version_profile = ''
else: else:
quality_profile = 'Unknown' quality_profile = 'Unknown'
synced_version_profile = ''
optimized_version_profile = ''
# Entire session output (single dict for backwards compatibility) # Entire session output (single dict for backwards compatibility)
session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'),
@@ -1692,6 +1819,8 @@ class PmsConnect(object):
'view_offset': view_offset, 'view_offset': view_offset,
'progress_percent': str(helpers.get_percent(view_offset, stream_details['stream_duration'])), 'progress_percent': str(helpers.get_percent(view_offset, stream_details['stream_duration'])),
'quality_profile': quality_profile, 'quality_profile': quality_profile,
'synced_version_profile': synced_version_profile,
'optimized_version_profile': optimized_version_profile,
'user': user_details['username'], # Keep for backwards compatibility 'user': user_details['username'], # Keep for backwards compatibility
'channel_stream': channel_stream 'channel_stream': channel_stream
} }
@@ -1727,7 +1856,6 @@ class PmsConnect(object):
else: else:
return False return False
def get_item_children(self, rating_key=''): def get_item_children(self, rating_key=''):
""" """
Return processed and validated children list. Return processed and validated children list.
@@ -1768,8 +1896,11 @@ class PmsConnect(object):
for result in result_data: for result in result_data:
children_output = {'section_id': section_id, children_output = {'section_id': section_id,
'rating_key': helpers.get_xml_attr(result, 'ratingKey'), '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'), 'media_index': helpers.get_xml_attr(result, 'index'),
'title': helpers.get_xml_attr(result, 'title'), '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'), 'thumb': helpers.get_xml_attr(result, 'thumb'),
'parent_thumb': helpers.get_xml_attr(a, 'thumb'), 'parent_thumb': helpers.get_xml_attr(a, 'thumb'),
'duration': helpers.get_xml_attr(result, 'duration') 'duration': helpers.get_xml_attr(result, 'duration')
@@ -1784,6 +1915,72 @@ class PmsConnect(object):
return output 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): def get_servers_info(self):
""" """
Return the list of local servers. Return the list of local servers.
@@ -1986,16 +2183,17 @@ class PmsConnect(object):
'media_type': helpers.get_xml_attr(item, 'type'), 'media_type': helpers.get_xml_attr(item, 'type'),
'rating_key': helpers.get_xml_attr(item, 'ratingKey'), 'rating_key': helpers.get_xml_attr(item, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(item, 'parentRatingKey'), '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'), 'title': helpers.get_xml_attr(item, 'title'),
'parent_title': helpers.get_xml_attr(a, 'parentTitle'), 'parent_title': helpers.get_xml_attr(item, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(a, 'grandparentTitle'), 'grandparent_title': helpers.get_xml_attr(item, 'grandparentTitle'),
'sort_title': helpers.get_xml_attr(item, 'titleSort'),
'media_index': helpers.get_xml_attr(item, 'index'), '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'), 'year': helpers.get_xml_attr(item, 'year'),
'thumb': helpers.get_xml_attr(item, 'thumb'), 'thumb': helpers.get_xml_attr(item, 'thumb'),
'parent_thumb': helpers.get_xml_attr(a, 'thumb'), 'parent_thumb': helpers.get_xml_attr(item, 'thumb'),
'grandparent_thumb': helpers.get_xml_attr(a, 'grandparentThumb'), 'grandparent_thumb': helpers.get_xml_attr(item, 'grandparentThumb'),
'added_at': helpers.get_xml_attr(item, 'addedAt') 'added_at': helpers.get_xml_attr(item, 'addedAt')
} }
@@ -2168,7 +2366,8 @@ class PmsConnect(object):
'episode': [], 'episode': [],
'artist': [], 'artist': [],
'album': [], 'album': [],
'track': [] 'track': [],
'collection': []
} }
for a in xml_head: for a in xml_head:

View File

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

View File

@@ -175,10 +175,20 @@ def checkGithub(auto_update=False):
if plexpy.COMMITS_BEHIND > 0: if plexpy.COMMITS_BEHIND > 0:
logger.info('New version is available. You are %s commits behind' % plexpy.COMMITS_BEHIND) 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 url = 'https://api.github.com/repos/%s/plexpy/releases' % plexpy.CONFIG.GIT_USER
release = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict) 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.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: if auto_update:
logger.info('Running automatic update.') logger.info('Running automatic update.')

View File

@@ -98,9 +98,6 @@ class WebInterface(object):
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def welcome(self, **kwargs): def welcome(self, **kwargs):
config = { 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_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
"pms_ip": plexpy.CONFIG.PMS_IP, "pms_ip": plexpy.CONFIG.PMS_IP,
"pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE), "pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
@@ -108,16 +105,7 @@ class WebInterface(object):
"pms_token": plexpy.CONFIG.PMS_TOKEN, "pms_token": plexpy.CONFIG.PMS_TOKEN,
"pms_ssl": checked(plexpy.CONFIG.PMS_SSL), "pms_ssl": checked(plexpy.CONFIG.PMS_SSL),
"pms_uuid": plexpy.CONFIG.PMS_UUID, "pms_uuid": plexpy.CONFIG.PMS_UUID,
"movie_notify_enable": checked(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE), "logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL
"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)
} }
# The setup wizard just refreshes the page on submit so we must redirect to home if config set. # 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_count": plexpy.CONFIG.HOME_STATS_COUNT,
"home_stats_recently_added_count": plexpy.CONFIG.HOME_STATS_RECENTLY_ADDED_COUNT, "home_stats_recently_added_count": plexpy.CONFIG.HOME_STATS_RECENTLY_ADDED_COUNT,
"pms_name": plexpy.CONFIG.PMS_NAME, "pms_name": plexpy.CONFIG.PMS_NAME,
"pms_use_bif": plexpy.CONFIG.PMS_USE_BIF,
"update_show_changelog": plexpy.CONFIG.UPDATE_SHOW_CHANGELOG "update_show_changelog": plexpy.CONFIG.UPDATE_SHOW_CHANGELOG
} }
return serve_template(templatename="index.html", title="Home", config=config) return serve_template(templatename="index.html", title="Home", config=config)
@@ -226,18 +213,8 @@ class WebInterface(object):
@requireAuth() @requireAuth()
def get_current_activity(self, **kwargs): def get_current_activity(self, **kwargs):
try: pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN)
pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN) result = pms_connect.get_current_activity()
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)
if result: if result:
return serve_template(templatename="current_activity.html", data=result) return serve_template(templatename="current_activity.html", data=result)
@@ -247,45 +224,16 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth() @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) pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN)
result = pms_connect.get_current_activity()
@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)
if result: if result:
data = {'stream_count': result['stream_count'], session = next((s for s in result['sessions'] if s['session_key'] == session_key), None)
'direct_play': 0, return serve_template(templatename="current_activity_instance.html", session=session)
'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)
else: else:
logger.warn(u"Unable to retrieve data for get_current_activity_header.") return serve_template(templatename="current_activity_instance.html", session=None)
return serve_template(templatename="current_activity_header.html", data=None)
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@@ -389,11 +337,7 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def libraries(self, **kwargs): def libraries(self, **kwargs):
config = { return serve_template(templatename="libraries.html", title="Libraries")
"update_section_ids": plexpy.CONFIG.UPDATE_SECTION_IDS
}
return serve_template(templatename="libraries.html", title="Libraries", config=config)
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@@ -721,9 +665,13 @@ class WebInterface(object):
# Check if datatables json_data was received. # Check if datatables json_data was received.
# If not, then build the minimal amount of json data for a query # If not, then build the minimal amount of json data for a query
if not kwargs.get('json_data'): 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 # TODO: Find some one way to automatically get the columns
dt_columns = [("added_at", True, False), dt_columns = [("added_at", True, False),
("title", True, True), ("sort_title", True, True),
("container", True, True), ("container", True, True),
("bitrate", True, True), ("bitrate", True, True),
("video_codec", True, True), ("video_codec", True, True),
@@ -734,7 +682,7 @@ class WebInterface(object):
("file_size", True, False), ("file_size", True, False),
("last_played", True, False), ("last_played", True, False),
("play_count", 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': if refresh == 'true':
refresh = True refresh = True
@@ -995,19 +943,6 @@ class WebInterface(object):
else: else:
return {'message': 'no data received'} 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.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@@ -2264,7 +2199,7 @@ class WebInterface(object):
machine_id = plexpy.CONFIG.PMS_IDENTIFIER machine_id = plexpy.CONFIG.PMS_IDENTIFIER
plex_tv = plextv.PlexTV() 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: if result:
output = {"data": result} output = {"data": result}
@@ -2595,6 +2530,7 @@ class WebInterface(object):
"http_port": plexpy.CONFIG.HTTP_PORT, "http_port": plexpy.CONFIG.HTTP_PORT,
"http_password": http_password, "http_password": http_password,
"http_root": plexpy.CONFIG.HTTP_ROOT, "http_root": plexpy.CONFIG.HTTP_ROOT,
"http_proxy": checked(plexpy.CONFIG.HTTP_PROXY),
"launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER), "launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER),
"enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS), "enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS),
"https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT), "https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT),
@@ -2624,7 +2560,6 @@ class WebInterface(object):
"pms_token": plexpy.CONFIG.PMS_TOKEN, "pms_token": plexpy.CONFIG.PMS_TOKEN,
"pms_ssl": checked(plexpy.CONFIG.PMS_SSL), "pms_ssl": checked(plexpy.CONFIG.PMS_SSL),
"pms_url_manual": checked(plexpy.CONFIG.PMS_URL_MANUAL), "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_uuid": plexpy.CONFIG.PMS_UUID,
"pms_web_url": plexpy.CONFIG.PMS_WEB_URL, "pms_web_url": plexpy.CONFIG.PMS_WEB_URL,
"date_format": plexpy.CONFIG.DATE_FORMAT, "date_format": plexpy.CONFIG.DATE_FORMAT,
@@ -2682,12 +2617,12 @@ class WebInterface(object):
checked_configs = [ checked_configs = [
"launch_browser", "enable_https", "https_create_cert", "api_enabled", "freeze_db", "check_github", "launch_browser", "enable_https", "https_create_cert", "api_enabled", "freeze_db", "check_github",
"grouping_global_history", "grouping_user_history", "grouping_charts", "group_history_tables", "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", "refresh_libraries_on_startup", "refresh_users_on_startup",
"notify_consecutive", "notify_upload_posters", "notify_recently_added_upgrade", "notify_consecutive", "notify_upload_posters", "notify_recently_added_upgrade",
"notify_group_recently_added_grandparent", "notify_group_recently_added_parent", "notify_group_recently_added_grandparent", "notify_group_recently_added_parent",
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password", "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", "history_table_activity", "plexpy_auto_update",
"themoviedb_lookup", "tvmaze_lookup" "themoviedb_lookup", "tvmaze_lookup"
] ]
@@ -3539,7 +3474,7 @@ class WebInterface(object):
def generate_api_key(self, device=None, **kwargs): def generate_api_key(self, device=None, **kwargs):
apikey = '' apikey = ''
while not apikey or apikey == plexpy.CONFIG.API_KEY or mobile_app.get_mobile_device_by_token(device_token=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.info(u"New API key generated.")
logger._BLACKLIST_WORDS.add(apikey) logger._BLACKLIST_WORDS.add(apikey)
@@ -3653,7 +3588,7 @@ class WebInterface(object):
def get_item_children(self, rating_key='', **kwargs): def get_item_children(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_item_children(rating_key) result = pms_connect.get_item_children(rating_key=rating_key)
if result: if result:
return serve_template(templatename="info_children_list.html", data=result, title="Children List") 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.") logger.warn(u"Unable to retrieve data for get_item_children.")
return serve_template(templatename="info_children_list.html", data=None, title="Children List") 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.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth() @requireAuth()
@@ -4481,19 +4428,13 @@ class WebInterface(object):
result = pms_connect.get_current_activity() result = pms_connect.get_current_activity()
if result: 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: if session_key:
return next((s for s in result['sessions'] if s['session_key'] == session_key), {}) return next((s for s in result['sessions'] if s['session_key'] == session_key), {})
counts = {'stream_count_direct_play': 0, counts = {'stream_count_direct_play': 0,
'stream_count_direct_stream': 0, 'stream_count_direct_stream': 0,
'stream_count_transcode': 0} 'stream_count_transcode': 0}
for s in result['sessions']: for s in result['sessions']:
if s['transcode_decision'] == 'transcode': if s['transcode_decision'] == 'transcode':
@@ -4635,7 +4576,7 @@ class WebInterface(object):
``` ```
""" """
plex_tv = plextv.PlexTV() 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: if result:
return result return result

View File

@@ -159,28 +159,6 @@ def initialize(options):
'tools.auth.on': False, 'tools.auth.on': False,
'tools.sessions.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': { '/cache': {
'tools.staticdir.on': True, 'tools.staticdir.on': True,
'tools.staticdir.dir': plexpy.CONFIG.CACHE_DIR, 'tools.staticdir.dir': plexpy.CONFIG.CACHE_DIR,
@@ -205,7 +183,7 @@ def initialize(options):
#}, #},
'/favicon.ico': { '/favicon.ico': {
'tools.staticfile.on': True, '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.on': True,
'tools.caching.force': True, 'tools.caching.force': True,
'tools.caching.delay': 0, 'tools.caching.delay': 0,