Added restart and shutdown links in settings.

No longer exposing passwords in html forms.
Removed some old headphones js.
Minor styling adjustments.
Current activity on home screen now works.
Some history table fixes and additions.
Info screen for video items now works.
This commit is contained in:
Tim 2015-06-16 17:35:52 +02:00
parent 7dbaf46408
commit 04b290173c
16 changed files with 1163 additions and 257 deletions

View file

@ -5,14 +5,7 @@
%> %>
<%def name="headerIncludes()"> <%def name="headerIncludes()">
<!--
<div id="subhead_container">
<div id="subhead_menu">
<a id="menu_link_shutdown" href="shutdown"><i class="fa fa-power-off"></i> Shut Down</a>
<a id="menu_link_shutdown" href="restart"><i class="fa fa-power-off"></i> Restart</a>
</div>
</div>
-->
</%def> </%def>
<%def name="body()"> <%def name="body()">
@ -22,7 +15,15 @@
<div class="span12"> <div class="span12">
<div class="wellheader-bg"> <div class="wellheader-bg">
<div class="dashboard-wellheader-no-chevron"> <div class="dashboard-wellheader-no-chevron">
<h2><i class="fa fa-cog"></i> Settings</h2> <div class="row-fluid">
<div class="span9"><h2><i class="fa fa-cog"></i> Settings</h2></div>
<div class="span3">
<div class="pull-right">
<h5><a id="menu_link_shutdown" href="shutdown"><i class="fa fa-power-off"></i> Shut Down</a>
&nbsp<a id="menu_link_restart" href="restart"><i class="fa fa-refresh"></i> Restart</a></h5>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -898,7 +899,6 @@
} }
}); });
initActions();
initConfigCheckbox("#api_enabled"); initConfigCheckbox("#api_enabled");
initConfigCheckbox("#enable_https"); initConfigCheckbox("#enable_https");

View file

@ -6650,7 +6650,7 @@ button.close {
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
width: 82%; width: 80%;
margin-right: 5px; margin-right: 5px;
float: left; float: left;
} }
@ -6665,7 +6665,7 @@ button.close {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
font-size: 14px; font-size: 14px;
width: 82%; width: 80%;
margin-right: 5px; margin-right: 5px;
color: #fff; color: #fff;
float: left; float: left;

View file

@ -0,0 +1,99 @@
% if activity is not None:
% if activity['stream_count'] != '0':
% for a in activity['sessions']:
<div class="instance" id="instance-${a['sessionKey']}">
<div class="poster">
% if a['type'] == 'track-to-do':
<div class="art-music-face" style="background-image:url(pms_image_proxy?img=${a['thumb']}&width=300&height=300)"></div>
% elif a['type'] == 'movie':
<div class="dashboard-activity-poster-face">
<img src="pms_image_proxy?img=${a['art']}&width=300&height=169"/> <!-- media artwork -->
</div>
% else:
<div class="dashboard-activity-poster-face">
<img src="pms_image_proxy?img=${a['thumb']}&width=300&height=169"/> <!-- media artwork -->
</div>
% endif
<div class='dashboard-activity-metadata-wrapper'>
<div class='dashboard-activity-instance-overlay'>
<div class='dashboard-activity-metadata-progress-minutes'>
<div class='progress progress-warning'>
<div class="bar" style="width: ${a['progressPercent']}%">${a['progressPercent']}%</div>
</div>
</div>
<div class="dashboard-activity-metadata-platform" id="platform-${a['sessionKey']}">
<!-- <img src="interfaces/default/images/platforms/roku.png"> platform image -->
</div>
<div class="dashboard-activity-metadata-user">
${a['user']} is ${a['state']}
</div>
<div class="dashboard-activity-metadata-title">
% if a['type'] == 'episode':
<a href="info?rating_key=${a['ratingKey']}">${a['grandparentTitle']} - ${a['title']}</a>
% elif a['type'] == 'movie':
<a href="info?rating_key=${a['ratingKey']}">${a['title']}</a>
% elif a['type'] == 'track':
${a['artist']} - ${a['track']}
% else:
${a['grandparentTitle']} - ${a['title']}
% endif
</div>
</div>
<div id="stream-${a['sessionKey']}" class="collapse out">
<div class='dashboard-activity-info-details-overlay'>
<div class='dashboard-activity-info-details-content'>
% if a['type'] == 'track':
Artist: <strong>${a['artist']}</strong>
<br>
Album: <strong>${a['album']}</strong>
<br>
% endif
% if a['state'] == 'playing':
State: <strong>Playing</strong>
% elif a['state'] == 'paused':
State: <strong>Paused</strong>
% elif a['state'] == 'buffering':
State: <strong>Buffering</strong>
% endif
<br>
% if a['type'] == 'track':
% if a['audioDecision'] == 'direct play':
Stream: <strong>Direct Play</strong>
% else:
Stream: <strong>Transcoding</strong>
% endif
<br/>
Audio: <strong>${a['audioCodec']} (${a['audioChannels']}ch)</strong>
% elif a['type'] == 'episode' or a['type'] == 'movie':
% if a['videoDecision'] == 'direct play':
Stream: <strong>Direct Play</strong>
% else:
Stream: <strong>Transcoding</strong>
% endif
<br/>
Video: <strong>${a['videoDecision']} (${a['videoCodec']}) (${a['width']}x${a['height']})</strong>
<br/>
Audio: <strong>${a['audioDecision']} (${a['audioCodec']}) (${a['audioChannels']}ch)</strong>
% endif
<br>
</div>
</div>
</div>
</div>
</div>
<div class="dashboard-activity-button-info">
<button type="button" class="btn btn-warning" data-toggle="collapse" data-target="#stream-${a['sessionKey']}">
<i class='icon-info-sign icon-white'></i>
</button>
</div>
</div>
<script>
$("#platform-${a['sessionKey']}").html("<img src='" + getPlatformImagePath('${a['player']}') + "'>");
</script>
% endfor
% else:
<div class="muted">Nothing is currently being watched.</div><br>
% endif
% else:
<div class="muted">There was an error communicating with your Plex Server. Please check your settings.</div><br>
% endif

View file

@ -0,0 +1,9 @@
% if activity != None:
% if activity == '0':
<h3>Activity</h3>
% else:
<h3>Activity <strong>${activity}</strong> stream(s)</h3>
% endif
% else:
<h3>Activity</h3>
% endif

View file

@ -38,6 +38,7 @@
<th align='left' id="stopped"><i class='fa fa-sort'></i> Stopped</th> <th align='left' id="stopped"><i class='fa fa-sort'></i> Stopped</th>
<th align='left' id="duration"><i class='fa fa-sort'></i> Duration</th> <th align='left' id="duration"><i class='fa fa-sort'></i> Duration</th>
<th align='left' id="percent_complete"> Completed</th> <th align='left' id="percent_complete"> Completed</th>
<th align='left' id="rating_key"> RatingKey</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -158,11 +159,21 @@
}, },
{ {
"targets": [4], "targets": [4],
"data":"ip_address" "data":"ip_address",
"createdCell": function (td, cellData, rowData, row, col) {
if ((cellData == '') || (cellData == '0')) {
$(td).html('n/a');
}
}
}, },
{ {
"targets": [5], "targets": [5],
"data":"title" "data":"title",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
}
}
}, },
{ {
"targets": [6], "targets": [6],
@ -203,6 +214,11 @@
return '<span class="badge">100%</span>'; return '<span class="badge">100%</span>';
} }
} }
},
{
"targets": [11],
"data":"rating_key",
"visible": false
} }
], ],
"drawCallback": function (settings) { "drawCallback": function (settings) {

View file

@ -3,16 +3,61 @@
from plexpy import helpers from plexpy import helpers
%> %>
<%def name="body()">
<div class="container">
</div>
</%def>
<%def name="headIncludes()"> <%def name="headIncludes()">
</%def> </%def>
<%def name="body()">
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<div class="wellbg">
<div class="wellheader">
<div class="dashboard-wellheader">
<div id="currentActivityHeader">
<h3>Activity</h3>
</div>
</div>
</div>
<div id="currentActivity">
<div class="muted">Checking for activity...</div><br>
</div>
</div>
</div>
</div>
</div>
</%def>
<%def name="javascriptIncludes()"> <%def name="javascriptIncludes()">
<script>
function currentActivity() {
$.ajax({
url: 'get_current_activity',
cache: false,
async: true,
complete: function(xhr, status) {
$("#currentActivity").html(xhr.responseText);
}
});
}
currentActivity();
setInterval(currentActivity, 15000);
function currentActivityHeader() {
$.ajax({
url: 'get_current_activity_header',
cache: false,
async: true,
complete: function(xhr, status) {
$("#currentActivityHeader").html(xhr.responseText);
}
});
}
currentActivityHeader();
setInterval(currentActivityHeader, 15000);
</script>
</%def> </%def>

View file

@ -0,0 +1,162 @@
<%inherit file="base.html"/>
<%!
from plexpy import helpers
%>
<%def name="headIncludes()">
</%def>
<%def name="body()">
% if metadata:
<div class="clear"></div>
<div class="container-fluid">
<!-- Some span12 if parameter -->
<div class="row-fluid">
<div class="span12">
<!-- end span12 if -->
<!-- if art -->
<div class="art-face" style="background-image:url(pms_image_proxy?img=${metadata['art']}&width=1920&height=1080)">
<!-- if not art
<div class="art-face">
-->
<div class="summary-wrapper">
<div class="summary-overlay">
<div class="row-fluid">
<div class="span9">
<div class="summary-content-poster hidden-phone hidden-tablet">
% if metadata['type'] == 'episode':
<img src="pms_image_proxy?img=${metadata['parentThumb']}&width=256&height=352">
% else:
<img src="pms_image_proxy?img=${metadata['thumb']}&width=256&height=352">
% endif
</div>
<div class="summary-content">
<div class="summary-content-title">
% if metadata['type'] == 'movie':
<h1>${metadata['title']} (${metadata['year']})</h1>
% elif metadata['type'] == 'season':
<h1>${metadata['parentTitle']} (${metadata['title']})</h1>
% elif metadata['type'] == 'episode':
<h1>${metadata['grandparentTitle']} (Season ${metadata['parentIndex']}, Episode ${metadata['index']}) "${metadata['title']}"</h1>
% else:
<h1>${metadata['title']}</h1>
% endif
</div>
% if metadata['type'] == 'movie':
<div id="stars" class="rateit hidden-phone hidden-tablet" data-rateit-value="" data-rateit-ispreset="true" data-rateit-readonly="true"></div>
% endif
<div class="summary-content-details-wrapper">
<div class="summary-content-director">
% if metadata['type'] == 'episode' or metadata['type'] == 'movie':
% if metadata['directors']:
Directed by <strong> ${metadata['directors'][0]}
% else:
Directed by <strong> unknown
% endif
% elif metadata['type'] == 'show':
Studio <strong> ${metadata['studio']}
% endif
</div>
<div class="summary-content-duration">
% if metadata['type'] == 'episode' or metadata['type'] == 'movie' or metadata['type'] == 'show':
Runtime <strong> ${metadata['duration']} mins</strong>
% endif
</div>
<div class="summary-content-content-rating">
% if metadata['type'] == 'episode' or metadata['type'] == 'movie' or metadata['type'] == 'show':
Rated <strong> ${metadata['contentRating']} </strong>
% endif
</div>
</div>
<div class="summary-content-summary">
% if metadata['type'] == 'episode' or metadata['type'] == 'movie' or metadata['type'] == 'show':
<p> ${metadata['summary']} </p>
% endif
</div>
</div>
</div>
% if metadata['type'] == 'episode':
<div class="span3">
<div class="summary-content-people-wrapper hidden-phone hidden-tablet">
<div class="summary-content-writers">
<h6><strong>Written by</strong></h6>
<ul>
% for writer in metadata['writers']:
% if loop.index < 5:
<li>
${writer}
</li>
% endif
% endfor
</ul>
</div>
</div>
</div>
% elif metadata['type'] == 'movie' or metadata['type'] == 'show':
<div class="span3">
<div class="summary-content-people-wrapper hidden-phone hidden-tablet">
<div class="summary-content-actors">
<h6><strong>Genres</strong></h6>
<ul>
% for genre in metadata['genres']:
% if loop.index < 5:
<li>
${genre}
</li>
% endif
% endfor
</ul>
</div>
<div class="summary-content-people-wrapper hidden-phone hidden-tablet">
<div class="summary-content-actors">
<h6><strong>Starring</strong></h6>
<ul>
% for actor in metadata['actors']:
% if loop.index < 5:
<li>
${actor}
</li>
% endif
% endfor
</ul>
</div>
</div>
</div>
</div>
% elif metadata['type'] == 'season':
<div class="span3"></div>
% endif
</div>
</div>
</div>
</div>
<!-- span rule -->
</div>
</div>
<!--}-->
</div>
% else:
<div class="clear"></div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span10 offset1">
<h3>Error retrieving item metadata. This media may not be available in the Plex Media Server database anymore.</h3>
</div>
</div>
</div>
% endif
</%def>
<%def name="javascriptIncludes()">
<script src="interfaces/default/js/jquery.rateit.min.js"></script>
% if metadata['type'] == 'movie':
<script>
// Convert rating to 5 star rating type
var starRating = Math.round(${metadata['rating']} / 2)
$('#stars').attr('data-rateit-value', starRating)
</script>
% endif
</%def>

File diff suppressed because one or more lines are too long

View file

@ -1,142 +1,3 @@
function getThumb(imgElem,id,type) {
if ( type == 'artist' ) {
var thumbURL = "getThumb?ArtistID=" + id;
// var imgURL = "getArtwork?ArtistID=" + id;
} else {
var thumbURL = "getThumb?AlbumID=" + id;
// var imgURL = "getArtwork?AlbumID=" + id;
}
// Get Data from the cache by Artist ID
$.ajax({
url: thumbURL,
cache: true,
success: function(data){
if ( data == "" ) {
var imageUrl = "interfaces/default/images/no-cover-artist.png";
}
else {
var imageUrl = data;
}
$(imgElem).attr("src",imageUrl).hide().fadeIn();
// $(imgElem).wrap('<a href="'+ imgURL +'" rel="dialog" title="' + name + '"></a>');
}
});
}
function getArtwork(imgElem,id,name,type) {
if ( type == 'artist' ) {
var artworkURL = "getArtwork?ArtistID=" + id;
} else {
var artworkURL = "getArtwork?AlbumID=" + id;
}
// Get Data from the cache by Artist ID
$.ajax({
url: artworkURL,
cache: true,
success: function(data){
if ( data == "" || data == undefined ) {
var imageUrl = "interfaces/default/images/no-cover-artist.png";
}
else {
var imageUrl = data;
}
$(imgElem).attr("src",imageUrl).hide().fadeIn();
$(imgElem).wrap('<a href="'+ imageUrl +'" rel="dialog" title="' + name + '"></a>');
}
});
}
function getInfo(elem,id,type) {
if ( type == 'artist' ) {
var infoURL = "getInfo?ArtistID=" + id;
} else {
var infoURL = "getInfo?AlbumID=" + id;
}
// Get Data from the cache by ID
$.ajax({
url: infoURL,
cache: true,
dataType: "json",
success: function(data){
var summary = data.Summary;
$(elem).append(summary);
}
});
}
function getImageLinks(elem,id,type,unveil) {
if ( type == 'artist' ) {
var infoURL = "getImageLinks?ArtistID=" + id;
} else {
var infoURL = "getImageLinks?AlbumID=" + id;
}
// Get Data from the cache by ID
$.ajax({
url: infoURL,
cache: true,
dataType: "json",
success: function(data){
if (!data) {
// Invalid response
return;
}
if (!data.thumbnail) {
var thumbnail = "interfaces/default/images/no-cover-artist.png";
}
else {
var thumbnail = data.thumbnail;
}
if (!data.artwork) {
var artwork = "interfaces/default/images/no-cover-artist.png";
}
else {
var artwork = data.artwork;
}
if (unveil) {
$(elem).attr("data-src", thumbnail);
$(elem).unveil();
}
else {
$(elem).attr("src", thumbnail);
}
}
});
}
function initHeader() {
//settings
var header = $("#container header");
var fadeSpeed = 100, fadeTo = 0.5, topDistance = 20;
var topbarME = function() { $(header).fadeTo(fadeSpeed,1); }, topbarML = function() { $(header).fadeTo(fadeSpeed,fadeTo); };
var inside = false;
//do
$(window).scroll(function() {
position = $(window).scrollTop();
if(position > topDistance && !inside) {
//add events
topbarML();
$(header).bind('mouseenter',topbarME);
$(header).bind('mouseleave',topbarML);
$("#toTop").fadeIn();
inside = true;
}
else if (position < topDistance){
topbarME();
$(header).unbind('mouseenter',topbarME);
$(header).unbind('mouseleave',topbarML);
$("#toTop").fadeOut();
inside = false;
}
});
}
function initConfigCheckbox(elem) { function initConfigCheckbox(elem) {
var config = $(elem).parent().next(); var config = $(elem).parent().next();
if ( $(elem).is(":checked") ) { if ( $(elem).is(":checked") ) {
@ -153,66 +14,6 @@ function initConfigCheckbox(elem) {
} }
}); });
} }
function initActions() {
$("#subhead_menu #menu_link_refresh").button();
$("#subhead_menu #menu_link_edit").button();
$("#subhead_menu .menu_link_edit").button();
$("#subhead_menu #menu_link_delete" ).button();
$("#subhead_menu #menu_link_pauze").button();
$("#subhead_menu #menu_link_resume").button();
$("#subhead_menu #menu_link_getextra").button();
$("#subhead_menu #menu_link_removeextra").button();
$("#subhead_menu #menu_link_wanted" ).button();
$("#subhead_menu #menu_link_check").button();
$("#subhead_menu #menu_link_skipped").button();
$("#subhead_menu #menu_link_retry").button();
$("#subhead_menu #menu_link_new").button();
$("#subhead_menu #menu_link_shutdown").button();
$("#subhead_menu #menu_link_scan").button();
}
function refreshSubmenu() {
var url = $(location).attr('href');
$("#subhead_container").load(url + " #subhead_menu",function(){
initActions();
});
}
function refreshTable() {
var url = $(location).attr('href');
$("table.display").load(url + " table.display tbody, table.display thead", function() {
initThisPage();
});
}
function refreshLoadArtist() {
if ( $(".gradeL").length > 0 ) {
var url = $(location).attr('href');
var loadingRow = $("table.display tr.gradeL")
loadingRow.each(function(){
var row = $(this).index() + 1;
var rowLoad = $("table.display tbody tr:nth-child("+row+")");
$(rowLoad).load(url + " table.display tbody tr:nth-child("+ row +") td", function() {
if ( $(rowLoad).children("#status").text() == 'Active' ) {
// Active
$(rowLoad).removeClass('gradeL').addClass('gradeZ');
initThisPage();
} else {
// Still loading
setTimeout(function(){
refreshLoadArtist();
},3000);
}
});
});
}
}
function refreshTab() {
var url = $(location).attr('href');
var tabId = $('.ui-tabs-panel:visible').attr("id");
$('.ui-tabs-panel:visible').load(url + " #"+ tabId, function() {
initThisPage();
});
}
function showMsg(msg,loader,timeout,ms) { function showMsg(msg,loader,timeout,ms) {
var feedback = $("#ajaxMsg"); var feedback = $("#ajaxMsg");
@ -240,21 +41,6 @@ function showMsg(msg,loader,timeout,ms) {
} }
} }
function showArtistMsg(msg) {
var feedback = $("#ajaxMsg2");
update = $("#updatebar");
if ( update.is(":visible") ) {
var height = update.height() + 35;
feedback.css("bottom",height + "px");
} else {
feedback.removeAttr("style");
}
feedback.fadeIn();
var message = $("<i class='fa fa-refresh fa-spin'></i> " + msg + "</div>");
feedback.css("padding","14px 10px")
$(feedback).prepend(message);
}
function doAjaxCall(url,elem,reload,form) { function doAjaxCall(url,elem,reload,form) {
// Set Message // Set Message
feedback = $("#ajaxMsg"); feedback = $("#ajaxMsg");
@ -312,6 +98,7 @@ function doAjaxCall(url,elem,reload,form) {
$.ajax({ $.ajax({
url: url, url: url,
data: dataString, data: dataString,
type: 'post',
beforeSend: function(jqXHR, settings) { beforeSend: function(jqXHR, settings) {
// Start loader etc. // Start loader etc.
feedback.prepend(loader); feedback.prepend(loader);
@ -371,15 +158,53 @@ function resetFilters(text){
} }
} }
function initFancybox() { function getPlatformImagePath(platformName) {
if ( $("a[rel=dialog]").length > 0 ) {
$.getScript('interfaces/default/js/fancybox/jquery.fancybox-1.3.4.js', function() {
$("head").append("<link rel='stylesheet' href='interfaces/default/js/fancybox/jquery.fancybox-1.3.4.css'>");
$("a[rel=dialog]").fancybox();
});
}
}
$(document).ready(function(){ if (platformName.indexOf("Roku") > -1) {
initHeader(); return 'interfaces/default/images/platforms/roku.png';
}); } else if (platformName.indexOf("Apple TV") > -1) {
return 'interfaces/default/images/platforms/appletv.png';
} else if (platformName.indexOf("Firefox") > -1) {
return 'interfaces/default/images/platforms/firefox.png';
} else if (platformName.indexOf("Chromecast") > -1) {
return 'interfaces/default/images/platforms/chromecast.png';
} else if (platformName.indexOf("Chrome") > -1) {
return 'interfaces/default/images/platforms/chrome.png';
} else if (platformName.indexOf("Android") > -1) {
return 'interfaces/default/images/platforms/android.png';
} else if (platformName.indexOf("Nexus") > -1) {
return 'interfaces/default/images/platforms/android.png';
} else if (platformName.indexOf("iPad") > -1) {
return 'interfaces/default/images/platforms/ios.png';
} else if (platformName.indexOf("iPhone") > -1) {
return 'interfaces/default/images/platforms/ios.png';
} else if (platformName.indexOf("iOS") > -1) {
return 'interfaces/default/images/platforms/ios.png';
} else if (platformName.indexOf("Plex Home Theater") > -1) {
return 'interfaces/default/images/platforms/pht.png';
} else if (platformName.indexOf("Linux/RPi-XMBC") > -1) {
return 'interfaces/default/images/platforms/xbmc.png';
} else if (platformName.indexOf("Safari") > -1) {
return 'interfaces/default/images/platforms/safari.png';
} else if (platformName.indexOf("Internet Explorer") > -1) {
return 'interfaces/default/images/platforms/ie.png';
} else if (platformName.indexOf("Unknown Browser") > -1) {
return 'interfaces/default/images/platforms/dafault.png';
} else if (platformName.indexOf("Windows-XBMC") > -1) {
return 'interfaces/default/images/platforms/xbmc.png';
} else if (platformName.indexOf("Xbox") > -1) {
return 'interfaces/default/images/platforms/xbox.png';
} else if (platformName.indexOf("Samsung") > -1) {
return 'interfaces/default/images/platforms/samsung.png';
} else if (platformName.indexOf("Opera") > -1) {
return 'interfaces/default/images/platforms/opera.png';
} else if (platformName.indexOf("KODI") > -1) {
return 'interfaces/default/images/platforms/kodi.png';
} else if (platformName.indexOf("Mystery 3") > -1) {
return 'interfaces/default/images/platforms/playstation.png';
} else if (platformName.indexOf("Mystery 4") > -1) {
return 'interfaces/default/images/platforms/playstation.png';
} else {
return 'interfaces/default/images/platforms/default.png';
}
}

View file

@ -71,7 +71,6 @@
<script> <script>
$(document).ready(function() { $(document).ready(function() {
initActions();
$('#log_table').dataTable( { $('#log_table').dataTable( {
"processing": false, "processing": false,

View file

@ -5,9 +5,16 @@
</%def> </%def>
<%def name="body()"> <%def name="body()">
<div class="table_wrapper"> <div class="container-fluid">
<div id="shutdown"> <div class="row-fluid">
<h1><i class="fa fa-refresh fa-spin"></i> PlexPy is ${message}</h1> <div class="span12">
<div class="wellbg">
<div class="wellheader">
<div class="dashboard-wellheader">
<h2><i class="fa fa-refresh fa-spin"></i> PlexPy is ${message}</h2>
</div>
</div>
</div>
</div> </div>
</div> </div>
</%def> </%def>

View file

@ -25,6 +25,7 @@ _CONFIG_DEFINITIONS = {
'PMS_IP': (str, 'PMS', '127.0.0.1'), 'PMS_IP': (str, 'PMS', '127.0.0.1'),
'PMS_PORT': (int, 'PMS', 32400), 'PMS_PORT': (int, 'PMS', 32400),
'PMS_PASSWORD': (str, 'PMS', ''), 'PMS_PASSWORD': (str, 'PMS', ''),
'PMS_TOKEN': (str, 'PMS', ''),
'PMS_USERNAME': (str, 'PMS', ''), 'PMS_USERNAME': (str, 'PMS', ''),
'TIME_FORMAT': (str, 'General', 'HH:mm'), 'TIME_FORMAT': (str, 'General', 'HH:mm'),
'API_ENABLED': (int, 'General', 0), 'API_ENABLED': (int, 'General', 0),

View file

@ -26,6 +26,7 @@ import re
import os import os
import json import json
import xmltodict import xmltodict
import math
def multikeysort(items, columns): def multikeysort(items, columns):
@ -111,6 +112,12 @@ def convert_milliseconds(ms):
return minutes return minutes
def convert_milliseconds_to_minutes(ms):
seconds = float(ms) / 1000
minutes = round(seconds / 60, 0)
return math.trunc(minutes)
def convert_seconds(s): def convert_seconds(s):
@ -337,3 +344,15 @@ def convert_xml_to_json(xml):
def convert_xml_to_dict(xml): def convert_xml_to_dict(xml):
o = xmltodict.parse(xml) o = xmltodict.parse(xml)
return o return o
def get_percent(value1, value2):
value1 = cast_to_float(value1)
value2 = cast_to_float(value2)
if value1 != 0 and value2 != 0:
percent = (value1 / value2) * 100
else:
percent = 0
return math.trunc(percent)

112
plexpy/plextv.py Normal file
View file

@ -0,0 +1,112 @@
# This file is part of PlexPy.
#
# PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PlexPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, helpers, common, request
from xml.dom import minidom
from httplib import HTTPSConnection
from urlparse import parse_qsl
from urllib import urlencode
from pynma import pynma
import base64
import cherrypy
import urllib
import urllib2
import plexpy
import os.path
import subprocess
import json
class PlexTV(object):
"""
Plex.tv authentication
"""
def __init__(self, username='', password=''):
self.username = username
self.password = password
def get_plex_auth(self):
http_handler = HTTPSConnection("plex.tv")
base64string = base64.encodestring('%s:%s' % (self.username, self.password)).replace('\n', '')
http_handler.request("POST",
'/users/sign_in.xml',
headers={'Content-Type': 'application/xml; charset=utf-8',
'Content-Length': '0',
'X-Plex-Device-Name': 'PlexPy',
'X-Plex-Product': 'PlexPy',
'X-Plex-Version': 'v0.1 dev',
'X-Plex-Client-Identifier': 'f0864d3531d75b19fa9204eaea456515e2502017',
'Authorization': 'Basic %s' % base64string + ":"
})
response = http_handler.getresponse()
request_status = response.status
request_body = response.read()
logger.debug(u"Plex.tv response status: %r" % request_status)
logger.debug(u"Plex.tv response headers: %r" % response.getheaders())
logger.debug(u"Plex.tv content type: %r" % response.getheader('content-type'))
logger.debug(u"Plex.tv response body: %r" % request_body)
if request_status == 201:
logger.info(u"Plex.tv connection successful.")
return request_body
elif request_status >= 400 and request_status < 500:
logger.info(u"Plex.tv request failed: %s" % response.reason)
return False
else:
logger.info(u"Plex.tv notification failed serverside.")
return False
def get_token(self):
plextv_response = self.get_plex_auth()
if plextv_response:
try:
xml_parse = minidom.parseString(helpers.latinToAscii(plextv_response))
except IOError, e:
logger.warn("Error parsing XML for Plex.tv token: %s" % e)
return False
xml_head = xml_parse.getElementsByTagName('user')
if not xml_head:
logger.warn("Error parsing XML for Plex.tv token: %s" % e)
return False
auth_token = xml_head[0].getAttribute('authenticationToken')
return auth_token
else:
return False
def get_plextv_user_data(self):
plextv_response = self.get_plex_auth()
if plextv_response:
try:
user_data = helpers.convert_xml_to_dict(plextv_response)
except IOError, e:
logger.warn("Error parsing XML for Plex.tv user data: %s" % e)
return False
return user_data
else:
return False

466
plexpy/pmsconnect.py Normal file
View file

@ -0,0 +1,466 @@
# This file is part of PlexPy.
#
# PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PlexPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, helpers, common, request
from xml.dom import minidom
from httplib import HTTPSConnection
from httplib import HTTPConnection
from urlparse import parse_qsl
from urllib import urlencode
from pynma import pynma
import base64
import cherrypy
import urllib
import urllib2
import plexpy
import os.path
import subprocess
import json
class PmsConnect(object):
"""
Retrieve data from Plex Server
"""
def __init__(self):
self.host = plexpy.CONFIG.PMS_IP
self.port = str(plexpy.CONFIG.PMS_PORT)
self.token = plexpy.CONFIG.PMS_TOKEN
"""
Return base url of Plex Server.
Output: string
"""
def get_base_url(self):
if self.host != '' and self.port != '':
base_url = 'http://' + self.host + ':' + self.port
return base_url
else:
return False
"""
Return current sessions.
Optional parameters: output_format { dict, json }
Output: array
"""
def get_sessions(self, output_format=''):
url_command = '/status/sessions'
http_handler = HTTPConnection(self.host, self.port, timeout=10)
http_handler.request("GET", url_command + '?X-Plex-Token=' + self.token)
response = http_handler.getresponse()
request_status = response.status
request_content = response.read()
if output_format == 'dict':
output = helpers.convert_xml_to_dict(request_content)
elif output_format == 'json':
output = helpers.convert_xml_to_json(request_content)
else:
output = request_content
return output
"""
Return metadata for request item.
Parameters required: rating_key { Plex ratingKey }
Optional parameters: output_format { dict, json }
Output: array
"""
def get_metadata(self, rating_key='', output_format=''):
url_command = '/library/metadata/' + rating_key
http_handler = HTTPConnection(self.host, self.port, timeout=10)
http_handler.request("GET", url_command + '?X-Plex-Token=' + self.token)
response = http_handler.getresponse()
request_status = response.status
request_content = response.read()
if output_format == 'dict':
output = helpers.convert_xml_to_dict(request_content)
elif output_format == 'json':
output = helpers.convert_xml_to_json(request_content)
else:
output = request_content
return output
"""
Return processed and validated metadata list for requested item.
Parameters required: rating_key { Plex ratingKey }
Output: array
"""
def get_metadata_details(self, rating_key=''):
metadata = self.get_metadata(rating_key)
metadata_list = []
try:
xml_parse = minidom.parseString(metadata)
except Exception, e:
logger.warn("Error parsing XML for Plex metadata: %s" % e)
return None
except:
logger.warn("Error parsing XML for Plex metadata.")
return None
xml_head = xml_parse.getElementsByTagName('MediaContainer')
if not xml_head:
logger.warn("Error parsing XML for Plex metadata.")
return None
for a in xml_head:
if a.getAttribute('size'):
if a.getAttribute('size') != '1':
metadata_list = {'metadata': None}
return metadata_list
if a.getElementsByTagName('Directory'):
metadata_main = a.getElementsByTagName('Directory')[0]
metadata_type = self.get_xml_attr(metadata_main, 'type')
logger.debug(u"Metadata type: %s" % metadata_type)
elif a.getElementsByTagName('Video'):
metadata_main = a.getElementsByTagName('Video')[0]
metadata_type = self.get_xml_attr(metadata_main, 'type')
logger.debug(u"Metadata type: %s" % metadata_type)
else:
logger.debug(u"Metadata failed")
genres = []
actors = []
writers = []
directors = []
if metadata_main.getElementsByTagName('Genre'):
for genre in metadata_main.getElementsByTagName('Genre'):
genres.append(self.get_xml_attr(genre, 'tag'))
logger.debug(u"Metadata genre: %s" % self.get_xml_attr(genre, 'tag'))
if metadata_main.getElementsByTagName('Role'):
for actor in metadata_main.getElementsByTagName('Role'):
actors.append(self.get_xml_attr(actor, 'tag'))
logger.debug(u"Metadata actor: %s" % self.get_xml_attr(actor, 'tag'))
if metadata_main.getElementsByTagName('Writer'):
for writer in metadata_main.getElementsByTagName('Writer'):
writers.append(self.get_xml_attr(writer, 'tag'))
logger.debug(u"Metadata genre: %s" % self.get_xml_attr(writer, 'tag'))
if metadata_main.getElementsByTagName('Director'):
for director in metadata_main.getElementsByTagName('Director'):
directors.append(self.get_xml_attr(director, 'tag'))
logger.debug(u"Metadata actor: %s" % self.get_xml_attr(director, 'tag'))
if metadata_type == 'show':
metadata = {'type': metadata_type,
'ratingKey': self.get_xml_attr(metadata_main, 'ratingKey'),
'studio': self.get_xml_attr(metadata_main, 'studio'),
'title': self.get_xml_attr(metadata_main, 'title'),
'contentRating': self.get_xml_attr(metadata_main, 'contentRating'),
'summary': self.get_xml_attr(metadata_main, 'summary'),
'rating': self.get_xml_attr(metadata_main, 'rating'),
'duration': helpers.convert_milliseconds_to_minutes(self.get_xml_attr(metadata_main, 'duration')),
'year': self.get_xml_attr(metadata_main, 'year'),
'thumb': self.get_xml_attr(metadata_main, 'thumb'),
'art': self.get_xml_attr(metadata_main, 'art'),
'originallyAvailableAt': self.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'writers': writers,
'directors': directors,
'genres': genres,
'actors': actors
}
metadata_list = {'metadata': metadata}
elif metadata_type == 'episode':
metadata = {'type': metadata_type,
'ratingKey': self.get_xml_attr(metadata_main, 'ratingKey'),
'grandparentTitle': self.get_xml_attr(metadata_main, 'grandparentTitle'),
'parentIndex': self.get_xml_attr(metadata_main, 'parentIndex'),
'index': self.get_xml_attr(metadata_main, 'index'),
'title': self.get_xml_attr(metadata_main, 'title'),
'contentRating': self.get_xml_attr(metadata_main, 'contentRating'),
'summary': self.get_xml_attr(metadata_main, 'summary'),
'duration': helpers.convert_milliseconds_to_minutes(self.get_xml_attr(metadata_main, 'duration')),
'year': self.get_xml_attr(metadata_main, 'year'),
'thumb': self.get_xml_attr(metadata_main, 'thumb'),
'parentThumb': self.get_xml_attr(metadata_main, 'parentThumb'),
'art': self.get_xml_attr(metadata_main, 'art'),
'originallyAvailableAt': self.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'writers': writers,
'directors': directors,
'genres': genres,
'actors': actors
}
metadata_list = {'metadata': metadata}
elif metadata_type == 'movie':
metadata = {'type': metadata_type,
'ratingKey': self.get_xml_attr(metadata_main, 'ratingKey'),
'studio': self.get_xml_attr(metadata_main, 'studio'),
'title': self.get_xml_attr(metadata_main, 'title'),
'contentRating': self.get_xml_attr(metadata_main, 'contentRating'),
'summary': self.get_xml_attr(metadata_main, 'summary'),
'rating': self.get_xml_attr(metadata_main, 'rating'),
'duration': helpers.convert_milliseconds_to_minutes(self.get_xml_attr(metadata_main, 'duration')),
'year': self.get_xml_attr(metadata_main, 'year'),
'thumb': self.get_xml_attr(metadata_main, 'thumb'),
'art': self.get_xml_attr(metadata_main, 'art'),
'originallyAvailableAt': self.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'genres': genres,
'actors': actors,
'writers': writers,
'directors': directors
}
metadata_list = {'metadata': metadata}
elif metadata_type == 'season':
metadata = {'type': metadata_type,
'ratingKey': self.get_xml_attr(metadata_main, 'ratingKey'),
'parentTitle': self.get_xml_attr(metadata_main, 'parentTitle'),
'index': self.get_xml_attr(metadata_main, 'index'),
'title': self.get_xml_attr(metadata_main, 'title'),
'thumb': self.get_xml_attr(metadata_main, 'thumb'),
'art': self.get_xml_attr(metadata_main, 'art'),
}
metadata_list = {'metadata': metadata}
else:
return None
return metadata_list
"""
Validate xml keys to make sure they exist and return their attribute value, return blank value is none found
"""
def get_xml_attr(self, xml_key, attribute, return_bool=False, default_return=''):
if xml_key.getAttribute(attribute):
if return_bool:
return True
else:
return xml_key.getAttribute(attribute)
else:
if return_bool:
return False
else:
return default_return
"""
Return processed and validated session list.
Output: array
"""
def get_current_activity(self):
session_data = self.get_sessions()
session_list = []
try:
xml_parse = minidom.parseString(session_data)
except Exception, e:
logger.warn("Error parsing XML for Plex session data: %s" % e)
return None
except:
logger.warn("Error parsing XML for Plex session data.")
return None
xml_head = xml_parse.getElementsByTagName('MediaContainer')
if not xml_head:
logger.warn("Error parsing XML for Plex session data.")
return None
for a in xml_head:
if a.getAttribute('size'):
if a.getAttribute('size') == '0':
logger.debug(u"No active sessions.")
session_list = {'stream_count': '0',
'sessions': []
}
return session_list
if a.getElementsByTagName('Track'):
session_data = a.getElementsByTagName('Track')
session_type = 'track'
logger.debug(u"Track session active.")
for session in session_data:
session_output = self.get_session_each(session_type, session)
session_list.append(session_output)
if a.getElementsByTagName('Video'):
session_data = a.getElementsByTagName('Video')
session_type = 'video'
logger.debug(u"Video session active.")
for session in session_data:
session_output = self.get_session_each(session_type, session)
session_list.append(session_output)
output = {'stream_count': self.get_xml_attr(xml_head[0], 'size'),
'sessions': session_list
}
return output
"""
Return selected data from current sessions.
This function processes and validates session data
Parameters required: stream_type { track or video }
session { the session dictionary }
Output: dict
"""
def get_session_each(self, stream_type='', session=None):
session_output = None
if stream_type == 'track':
if session.getElementsByTagName('TranscodeSession'):
transcode_session = session.getElementsByTagName('TranscodeSession')[0]
audio_decision = self.get_xml_attr(transcode_session, 'audioDecision')
audio_channels = self.get_xml_attr(transcode_session, 'audioChannels')
audio_codec = self.get_xml_attr(transcode_session, 'audioCodec')
duration = self.get_xml_attr(transcode_session, 'duration')
progress = self.get_xml_attr(transcode_session, 'viewOffset')
else:
media_info = session.getElementsByTagName('Media')[0]
audio_decision = 'direct play'
audio_channels = self.get_xml_attr(media_info, 'audioChannels')
audio_codec = self.get_xml_attr(media_info, 'audioCodec')
duration = self.get_xml_attr(media_info, 'duration')
progress = self.get_xml_attr(session, 'viewOffset')
session_output = {'sessionKey': self.get_xml_attr(session, 'sessionKey'),
'parentThumb': self.get_xml_attr(session, 'parentThumb'),
'thumb': self.get_xml_attr(session, 'thumb'),
'user': self.get_xml_attr(session.getElementsByTagName('User')[0], 'title'),
'player': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'state': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'),
'artist': self.get_xml_attr(session, 'grandparentTitle'),
'album': self.get_xml_attr(session, 'parentTitle'),
'track': self.get_xml_attr(session, 'title'),
'ratingKey': self.get_xml_attr(session, 'ratingKey'),
'audioDecision': audio_decision,
'audioChannels': audio_channels,
'audioCodec': audio_codec,
'duration': duration,
'progress': progress,
'progressPercent': str(helpers.get_percent(progress, duration)),
'type': 'track'
}
elif stream_type == 'video':
if session.getElementsByTagName('TranscodeSession'):
transcode_session = session.getElementsByTagName('TranscodeSession')[0]
audio_decision = self.get_xml_attr(transcode_session, 'audioDecision')
audio_channels = self.get_xml_attr(transcode_session, 'audioChannels')
audio_codec = self.get_xml_attr(transcode_session, 'audioCodec')
video_decision = self.get_xml_attr(transcode_session, 'videoDecision')
video_codec = self.get_xml_attr(transcode_session, 'videoCodec')
width = self.get_xml_attr(transcode_session, 'width')
height = self.get_xml_attr(transcode_session, 'height')
duration = self.get_xml_attr(session, 'duration')
progress = self.get_xml_attr(session, 'viewOffset')
else:
media_info = session.getElementsByTagName('Media')[0]
audio_decision = 'direct play'
audio_channels = self.get_xml_attr(media_info, 'audioChannels')
audio_codec = self.get_xml_attr(media_info, 'audioCodec')
video_decision = 'direct play'
video_codec = self.get_xml_attr(media_info, 'videoCodec')
width = self.get_xml_attr(media_info, 'width')
height = self.get_xml_attr(media_info, 'height')
duration = self.get_xml_attr(media_info, 'duration')
progress = self.get_xml_attr(session, 'viewOffset')
if self.get_xml_attr(session, 'type') == 'episode':
session_output = {'sessionKey': self.get_xml_attr(session, 'sessionKey'),
'art': self.get_xml_attr(session, 'art'),
'thumb': self.get_xml_attr(session, 'thumb'),
'user': self.get_xml_attr(session.getElementsByTagName('User')[0], 'title'),
'player': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'state': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'),
'grandparentTitle': self.get_xml_attr(session, 'grandparentTitle'),
'title': self.get_xml_attr(session, 'title'),
'ratingKey': self.get_xml_attr(session, 'ratingKey'),
'audioDecision': audio_decision,
'audioChannels': audio_channels,
'audioCodec': audio_codec,
'videoDecision': video_decision,
'videoCodec': video_codec,
'height': height,
'width': width,
'duration': duration,
'progress': progress,
'progressPercent': str(helpers.get_percent(progress, duration)),
'type': self.get_xml_attr(session, 'type')
}
elif self.get_xml_attr(session, 'type') == 'movie':
session_output = {'sessionKey': self.get_xml_attr(session, 'sessionKey'),
'art': self.get_xml_attr(session, 'art'),
'thumb': self.get_xml_attr(session, 'thumb'),
'user': self.get_xml_attr(session.getElementsByTagName('User')[0], 'title'),
'player': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'state': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'),
'title': self.get_xml_attr(session, 'title'),
'ratingKey': self.get_xml_attr(session, 'ratingKey'),
'audioDecision': audio_decision,
'audioChannels': audio_channels,
'audioCodec': audio_codec,
'videoDecision': video_decision,
'videoCodec': video_codec,
'height': height,
'width': width,
'duration': duration,
'progress': progress,
'progressPercent': str(helpers.get_percent(progress, duration)),
'type': self.get_xml_attr(session, 'type')
}
else:
logger.warn(u"No known stream types found in session list.")
return session_output
"""
Return image data as array.
Array contains the image content type and image binary
Parameters required: img { Plex image location }
Optional parameters: width { the image width }
height { the image height }
Output: array
"""
def get_image(self, img, width='0', height='0'):
if img != '':
try:
http_handler = HTTPConnection(self.host, self.port, timeout=10)
if width != '0' and height != '0':
image_path = '/photo/:/transcode?url=http://127.0.0.1:' + self.port + img + '&width=' + width + '&height=' + height
else:
image_path = '/photo/:/transcode?url=http://127.0.0.1:' + self.port + img
http_handler.request("GET", image_path + '&X-Plex-Token=' + self.token)
response = http_handler.getresponse()
request_status = response.status
request_content = response.read()
request_content_type = response.getheader('content-type')
logger.debug(u"Content type: %r" % request_content_type)
except IOError, e:
logger.warn(u"Failed to retrieve image. %s" % e)
return None
if request_status == 200:
return [request_content_type, request_content]
else:
logger.warn(u"Failed to retrieve image. Status code %r" % request_status)
return None
return None

View file

@ -13,7 +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 PlexPy. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, db, helpers, notifiers from plexpy import logger, db, helpers, notifiers, plextv, pmsconnect
from plexpy.helpers import checked, radio, today, cleanName from plexpy.helpers import checked, radio, today, cleanName
from xml.dom import minidom from xml.dom import minidom
@ -161,11 +161,22 @@ class WebInterface(object):
interface_list = [name for name in os.listdir(interface_dir) if interface_list = [name for name in os.listdir(interface_dir) if
os.path.isdir(os.path.join(interface_dir, name))] os.path.isdir(os.path.join(interface_dir, name))]
# Initialise blank passwords so we do not expose them in the html forms
# but users are still able to clear them
if plexpy.CONFIG.HTTP_PASSWORD != '':
http_password = ' '
else:
http_password = ''
if plexpy.CONFIG.PMS_PASSWORD != '':
pms_password = ' '
else:
pms_password = ''
config = { config = {
"http_host": plexpy.CONFIG.HTTP_HOST, "http_host": plexpy.CONFIG.HTTP_HOST,
"http_username": plexpy.CONFIG.HTTP_USERNAME, "http_username": plexpy.CONFIG.HTTP_USERNAME,
"http_port": plexpy.CONFIG.HTTP_PORT, "http_port": plexpy.CONFIG.HTTP_PORT,
"http_password": plexpy.CONFIG.HTTP_PASSWORD, "http_password": http_password,
"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_cert": plexpy.CONFIG.HTTPS_CERT, "https_cert": plexpy.CONFIG.HTTPS_CERT,
@ -228,7 +239,7 @@ class WebInterface(object):
"pms_ip": plexpy.CONFIG.PMS_IP, "pms_ip": plexpy.CONFIG.PMS_IP,
"pms_port": plexpy.CONFIG.PMS_PORT, "pms_port": plexpy.CONFIG.PMS_PORT,
"pms_username": plexpy.CONFIG.PMS_USERNAME, "pms_username": plexpy.CONFIG.PMS_USERNAME,
"pms_password": plexpy.CONFIG.PMS_PASSWORD, "pms_password": pms_password,
"plexwatch_database": plexpy.CONFIG.PLEXWATCH_DATABASE, "plexwatch_database": plexpy.CONFIG.PLEXWATCH_DATABASE,
"date_format": plexpy.CONFIG.DATE_FORMAT, "date_format": plexpy.CONFIG.DATE_FORMAT,
"time_format": plexpy.CONFIG.TIME_FORMAT, "time_format": plexpy.CONFIG.TIME_FORMAT,
@ -257,6 +268,30 @@ class WebInterface(object):
# checked items should be zero or one. if they were not sent then the item was not checked # checked items should be zero or one. if they were not sent then the item was not checked
kwargs[checked_config] = 0 kwargs[checked_config] = 0
# Write Plex token to the config
if (not plexpy.CONFIG.PMS_TOKEN or plexpy.CONFIG.PMS_TOKEN == '' \
or kwargs['pms_username'] != plexpy.CONFIG.PMS_USERNAME) \
and (kwargs['pms_username'] != '' or kwargs['pms_password'] != ''):
plex_tv = plextv.PlexTV(kwargs['pms_username'], kwargs['pms_password'])
token = plex_tv.get_token()
if token:
kwargs['pms_token'] = token
logger.info('Plex.tv token sucessfully written to config.')
else:
logger.warn('Unable to write Plex.tv token to config.')
# Clear Plex token if username or password set to blank
if kwargs['pms_username'] == '' or kwargs['pms_password'] == '':
kwargs['pms_token'] = ''
# If passwords exists in config, do not overwrite when blank value received
if kwargs['http_password'] == ' ' and plexpy.CONFIG.HTTP_PASSWORD != '':
kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD
if kwargs['pms_password'] == ' ' and plexpy.CONFIG.PMS_PASSWORD != '':
kwargs['pms_password'] = plexpy.CONFIG.PMS_PASSWORD
for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]: for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]:
# the use prefix is fairly nice in the html, but does not match the actual config # the use prefix is fairly nice in the html, but does not match the actual config
kwargs[plain_config] = kwargs[use_config] kwargs[plain_config] = kwargs[use_config]
@ -325,7 +360,7 @@ class WebInterface(object):
sortcolumn = 'duration' sortcolumn = 'duration'
if search_value == "": if search_value == "":
query = 'SELECT id, time, user, platform, ip_address, title, time, paused_counter, stopped, xml, \ query = 'SELECT id, time, user, platform, ip_address, title, time, paused_counter, stopped, ratingKey, xml, \
round((julianday(datetime(stopped, "unixepoch", "localtime")) - \ round((julianday(datetime(stopped, "unixepoch", "localtime")) - \
julianday(datetime(time, "unixepoch", "localtime"))) * 86400) - \ julianday(datetime(time, "unixepoch", "localtime"))) * 86400) - \
(case when paused_counter is null then 0 else paused_counter end) as duration \ (case when paused_counter is null then 0 else paused_counter end) as duration \
@ -333,7 +368,7 @@ class WebInterface(object):
filtered = myDB.select(query) filtered = myDB.select(query)
totalcount = len(filtered) totalcount = len(filtered)
else: else:
query = 'SELECT id, time, user, platform, ip_address, title, time, paused_counter, stopped, xml, \ query = 'SELECT id, time, user, platform, ip_address, title, time, paused_counter, stopped, ratingKey, xml, \
round((julianday(datetime(stopped, "unixepoch", "localtime")) - \ round((julianday(datetime(stopped, "unixepoch", "localtime")) - \
julianday(datetime(time, "unixepoch", "localtime"))) * 86400) - \ julianday(datetime(time, "unixepoch", "localtime"))) * 86400) - \
(case when paused_counter is null then 0 else paused_counter end) as duration \ (case when paused_counter is null then 0 else paused_counter end) as duration \
@ -354,6 +389,7 @@ class WebInterface(object):
"started": item["time"], "started": item["time"],
"paused": item["paused_counter"], "paused": item["paused_counter"],
"stopped": item["stopped"], "stopped": item["stopped"],
"rating_key": item["ratingKey"],
"duration": item["duration"], "duration": item["duration"],
"percent_complete": 0, "percent_complete": 0,
} }
@ -486,3 +522,111 @@ class WebInterface(object):
logger.warn(msg) logger.warn(msg)
return msg return msg
@cherrypy.expose
def get_pms_token(self):
token = plextv.PlexTV()
result = token.get_token()
if result:
return result
else:
logger.warn('Unable to retrieve Plex.tv token.')
return False
@cherrypy.expose
def get_pms_sessions_json(self, **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_sessions('json')
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return result
else:
logger.warn('Unable to retrieve data.')
return False
@cherrypy.expose
def get_current_activity(self, **kwargs):
try:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_current_activity()
except:
return serve_template(templatename="current_activity.html", activity=None)
if result:
return serve_template(templatename="current_activity.html", activity=result)
else:
return serve_template(templatename="current_activity.html", activity=None)
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_current_activity_header(self, **kwargs):
try:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_current_activity()
except IOError, e:
return serve_template(templatename="current_activity_header.html", activity=None)
if result:
return serve_template(templatename="current_activity_header.html", activity=result['stream_count'])
else:
return serve_template(templatename="current_activity_header.html", activity=None)
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def pms_image_proxy(self, img='', width='0', height='0', **kwargs):
if img != '':
try:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_image(img, width, height)
logger.info('Image proxy queried. Content type is %s' % result[0])
cherrypy.response.headers['Content-type'] = result[0]
return result[1]
except:
logger.warn('Image proxy queried but errors occured.')
return 'No image'
else:
logger.warn('Image proxy queried but no parameters received.')
return 'No image'
@cherrypy.expose
def info(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key)
if result:
return serve_template(templatename="info.html", metadata=result['metadata'], title="Info")
else:
return serve_template(templatename="info.html", metadata='', title="Info")
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_metadata_json(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata(rating_key, 'json')
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return result
else:
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_metadata_xml(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata(rating_key)
if result:
cherrypy.response.headers['Content-type'] = 'application/xml'
return result
else:
logger.warn('Unable to retrieve data.')