2016-05-04, 12:58
Thats awesome. Can you send a pr on github? What tvheadend version is this tested against?
(2016-05-04, 10:11)kevcompton Wrote: Hi,
I have managed to get the tvheadend module working again with HTS Tvheadend 4.1-361~gd9cf931.
Still needs a bit of tidying up but at least you can now connect, list recordings, set timers, delete recordings. Need to tidy up the channel list.
Tried Private messaging hellow but wouldn't let me. Code is below (2 file changes). You will also have to uncomment of adding module in main code (Htpc.py).
Hope this helps someone.
Kevin
tvheadend.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cherrypy
import htpc
import logging
import urllib2
import urllib
import base64
import json
from cherrypy.lib.auth2 import require
class TVHeadend(object):
def __init__(self):
self.logger = logging.getLogger('modules.tvheadend')
htpc.MODULES.append({
'name': 'TVHeadend',
'id': 'tvheadend',
'test': htpc.WEBDIR + 'TVHeadend/ping',
'fields': [
{'type': 'bool', 'label': 'Enable', 'name': 'tvheadend_enable'},
{'type': 'text', 'label': 'Menu name', 'name': 'tvheadend_name'},
{'type': 'text', 'label': 'IP / Host *', 'name': 'tvheadend_host'},
{'type': 'text', 'label': 'Port *', 'name': 'tvheadend_port'},
{'type': 'text', 'label': 'Username', 'name': 'tvheadend_username'},
{'type': 'password', 'label': 'Password', 'name': 'tvheadend_password'},
{'type': 'text', 'label': 'Reverse proxy link', 'placeholder': '', 'desc': 'Reverse proxy link, e.g. https://domain.com/tvh', 'name': 'tvheadend_reverse_proxy_link'},
]
})
@cherrypy.expose()
@require()
def index(self):
return htpc.LOOKUP.get_template("tvheadend.html").render(scriptname="tvheadend", webinterface=self.webinterface())
def webinterface(self):
ip = htpc.settings.get('tvheadend_host')
port = htpc.settings.get('tvheadend_port')
url = 'http://%s:%s/' % (ip, port)
if htpc.settings.get('tvheadend_reverse_proxy_link'):
url = htpc.settings.get('tvheadend_reverse_proxy_link')
return url
@cherrypy.expose()
@require()
@cherrypy.tools.json_out()
def GetEPG(self, strLimit="300", strChannel=""):
return self.fetch("api/epg/events/grid", {'limit': strLimit, 'start': "0", 'channel': strChannel })
@cherrypy.expose()
@require()
@cherrypy.tools.json_out()
def GetChannels(self):
return self.fetch("api/channel/grid", { 'dir': 'ASC', 'sort': 'tags', 'limit': 1000})
@cherrypy.expose()
@require()
@cherrypy.tools.json_out()
def GetChannelTags(self):
return self.fetch("api/channeltag/list", {'op': 'listTags'})
@cherrypy.expose()
@require()
@cherrypy.tools.json_out()
def DVRAdd(self, strEventID=""):
return self.fetch("api/dvr/entry/create_by_event", {'event_id': strEventID, 'config_uuid': ''})
@cherrypy.expose()
@require()
@cherrypy.tools.json_out()
def DVRDel(self, strEntryID=""):
return self.fetch("api/idnode/delete", {'uuid': strEntryID})
@cherrypy.expose()
@require()
@cherrypy.tools.json_out()
def DVRList(self, strType=""):
return self.fetch("api/dvr/entry/grid_" + strType, None)
#return self.fetch("dvrlist_" + strType, None)
def fetch(self, strQuery, rgpData):
rgpHeaders = {}
username = htpc.settings.get("tvheadend_username", "")
password = htpc.settings.get("tvheadend_password", "")
if username and password:
rgpHeaders['Authorization'] = 'Basic %s' % base64.encodestring('%s:%s' % (username, password)).strip('\n')
# Lame debug to get as much info as possible
self.logger.debug('strQuery: %s' % strQuery)
self.logger.debug('rgpData: %s' % rgpData)
strResponse = None
strData = None
if rgpData is not None:
strData = urllib.urlencode(rgpData)
url = "http://%s:%s/%s" % (htpc.settings.get("tvheadend_host", ""), htpc.settings.get("tvheadend_port", ""), strQuery)
self.logger.debug('url: %s' % url)
self.logger.debug('encoded: %s' % strData)
try:
pRequest = urllib2.Request("http://%s:%s/%s" % (htpc.settings.get("tvheadend_host", ""), htpc.settings.get("tvheadend_port", ""), strQuery), data = strData, headers = rgpHeaders)
strResponse = urllib2.urlopen(pRequest).read()
return json.loads(strResponse)
except Exception as e:
self.logger.error('%s %s failed error: %s' % strQuery, rgpData, e)
tvheadend.js:
function parseJSON(strQuery, pCallback) {
$(".spinner").show();
$.getJSON(WEBDIR + "tvheadend/" + strQuery, function (pResult) {
if (pCallback == null) {
$(".spinner").hide();
return;
}
pCallback(pResult);
$(".spinner").hide();
});
}
function convertTimestamp(nTimestamp) {
var strDate = new Date(nTimestamp * 1000).toString();
return strDate.substring(0, strDate.indexOf(' GMT')); // Strip GMT crap
}
function showEPG(pChannel) {
parseJSON("GetEPG/300/" + pChannel.uuid, function(pResult) {
var strTable = $("<table>").addClass("table table-striped table-hover").append(
$("<tr>").append("<th>Name</th>")
.append("<th>Start</th>")
.append("<th>End</th>")
.append("<th>Actions</th>")
);
$.each(pResult.entries, function(nIndex, pEntry) {
strTable.append($("<tr>")
.append($("<td>").text(pEntry.title))
.append($("<td>").text(convertTimestamp(pEntry.start)))
.append($("<td>").text(convertTimestamp(pEntry.stop)))
.append($("<td>")
.append($("<a>").text("REC").click(function(pEvent) {
pEvent.preventDefault();
parseJSON("DVRAdd/" + pEntry.eventId, null);
}))
));
});
showModal(pChannel.name, strTable,
{
/*'Watch' : function() {
strTable.html("<video controls autoplay>"
+ "<source src=\"http://192.168.1.11:9981/stream/channel/" + pChannel.uuid + "\"></source>"
+ "</video>");
}*/
}
);
});
}
function getChannelTags() {
parseJSON("GetChannelTags", function(pResult) {
$.each(pResult.entries, function(nIndex, pEntry) {
// Add nav tabs
$(".nav.nav-tabs").append($("<li>")
.append($("<a>")
.attr("href", "#tag-" + pEntry.key)
.attr("data-toggle", "tab")
.text(pEntry.val)));
// Add tab pane
var strTabPane = $("<div>").attr("id", "tag-" + pEntry.key)
.attr("class", "tab-pane");
$(".tab-content").append(strTabPane.append($("<ul>").attr("id", "tag-" + pEntry.key + "-grid").attr("class", "thumbnails")));
});
getChannels();
});
$(window).trigger("hashchange");
}
function getChannels() {
parseJSON("GetChannels", function(pResult) {
$.each(pResult.entries, function(nIndex, pEntry) {
var strHTML = $("<div>").attr("class", "channel");
var pHTMLEntry = null;
if (pEntry.icon != undefined) {
pHTMLEntry = $("<img>").attr("src", pEntry.icon);
}
else {
pHTMLEntry = $("<a>").text(pEntry.name);
}
pHTMLEntry.click(function (pEvent) {
showEPG(pEntry);
});
strHTML.append(pHTMLEntry);
$.each(pEntry.tags, function(nIndex, nTag) {
$("#tag-" + nTag + "-grid").append($("<li>").append(strHTML));
});
});
});
}
function parseRecordings(strType) {
var strTable = $("<table>").attr("id", "recordings_" + strType)
.attr("class", "recordingtable")
.append($("<tr>")
.append("<th>Channel</th>")
.append("<th>Title</th>")
.append("<th>Start</th>")
.append("<th>End</th>")
.append("<th>Status</th>")
.append("<th>Actions</th>")
);
parseJSON("DVRList/" + strType, function(pResult) {
$.each(pResult.entries, function(nIndex, pEntry) {
strTable.append($("<tr>").attr("id", "recording-" + pEntry.uuid)
.append($("<td>").text(pEntry.channelname))
.append($("<td>").text(pEntry.disp_title))
.append($("<td>").text(convertTimestamp(pEntry.start)))
.append($("<td>").text(convertTimestamp(pEntry.stop)))
.append($("<td>").text(pEntry.status))
.append($("<td>").append($("<a>").text("DEL").click(function(pEvent) {
pEvent.preventDefault();
parseJSON("DVRDel/" + pEntry.uuid, function(pResult) {
if (pResult.success == 1) {
$("#recording-" + pEntry.uuid).fadeOut();
}
});
})))
);
});
});
$("#recordings").append("<h2>" + strType.charAt(0).toUpperCase() + strType.slice(1) + " recordings</h2>")
.append(strTable);
}
function getRecordings() {
parseRecordings("upcoming");
parseRecordings("finished");
}
$(document).ready(function () {
getChannelTags();
getChannels();
getRecordings();
});