From 6413f2b4a6973fc72ba286d1ac250e89e5a5756e Mon Sep 17 00:00:00 2001 From: Jason Michalski Date: Wed, 26 Mar 2008 01:20:52 -0500 Subject: [PATCH] Added push support. The root page displays links to video shares. The xsl makes them look "nice". Click the send to tivo link to push the file. --- config.py | 6 + httpserver.py | 6 + mind.py | 228 +++++++++++++++++++++++++++++++++ plugins/video/templates/container.tmpl | 12 ++ plugins/video/templates/container.xsl | 57 +++++++++ plugins/video/video.py | 52 ++++++++ templates/info_page.tmpl | 3 +- 7 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 mind.py create mode 100644 plugins/video/templates/container.xsl diff --git a/config.py b/config.py index 8e6ca94..12b9410 100644 --- a/config.py +++ b/config.py @@ -22,6 +22,12 @@ def getGUID(): guid = '123456' return guid +def getTivoUsername(): + return config.get('Server', 'tivo_username') + +def getTivoPassword(): + return config.get('Server', 'tivo_password') + def getBeaconAddresses(): if config.has_option('Server', 'beacon'): beacon_ips = config.get('Server', 'beacon') diff --git a/httpserver.py b/httpserver.py index eac6807..28a63c8 100644 --- a/httpserver.py +++ b/httpserver.py @@ -128,6 +128,12 @@ class TivoHTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): t.admin = '
No Admin plugin installed in pyTivo.conf
If you wish to use'\ + ' the admin plugin add the following lines to pyTivo.conf

'\ + '[Admin]
type=admin' + + t.shares = 'Video shares:
' + for section, settings in config.getShares(): + if settings.get('type') == 'video': + t.shares += '' + section + '
' self.wfile.write(t) self.end_headers() diff --git a/mind.py b/mind.py new file mode 100644 index 0000000..d048738 --- /dev/null +++ b/mind.py @@ -0,0 +1,228 @@ +import cookielib +import urllib2 +import urllib +import struct +import httplib +import xml.etree.ElementTree as ElementTree +import time + + +class Mind: + def __init__(self, username, password, debug=False): + self.__username = username + self.__password = password + + self.__debug = debug + + self.__cj = cookielib.CookieJar() + self.__opener = urllib2.build_opener(urllib2.HTTPSHandler(debuglevel=1), urllib2.HTTPCookieProcessor(self.__cj)) + + self.__login() + + if not self.__pcBodySearch(): + self.__pcBodyStore('pyTivo', True) + + def pushVideo(self, tsn, url, description='test', duration='40000', size='3000000', title='test', subtitle='test'): + + # It looks like tivo only supports one pc per house + pc_body_id = self.__pcBodySearch()[0] + offer_id, content_id = self.__bodyOfferModify(tsn, pc_body_id, description, duration, size, title, subtitle, url) + self.__subscribe(offer_id, content_id, tsn) + + def bodyOfferSchedule(self, pc_body_id): + + data = {'pcBodyId' : pc_body_id,} + r = urllib2.Request( + '/Steph%27s%20Videos/The%20Fairly%20Odd%20Parents%20-%20Channel%20Chasers.xvid-pyro.avi', + dictcode(data), + {'Content-Type' : 'x-tivo/dict-binary'} + ) + result = self.__opener.open(r) + + self.__log('bodyOfferSchedule\n%s\n\n%sg' % (data, result)) + + return result.read() + + def __log(self, message): + if self.__debug: + print message + print + + def __login(self): + + data = { + 'cams_security_domain' : 'tivocom', + 'cams_login_config' : 'http', + 'cams_cb_username' : self.__username, + 'cams_cb_password' : self.__password, + 'cams_original_url' : '/mind/mind7?type=infoGet' + } + + r = urllib2.Request( + 'https://mind.tivo.com:8181/mind/login', + urllib.urlencode(data) + ) + try: + result = self.__opener.open(r) + except: + pass + + self.__log('__login\n%s' % (data)) + + def __bodyOfferModify(self, tsn, pc_body_id, description, duration, size, title, subtitle, url): + + data = { + 'bodyId' : 'tsn:' + tsn, + 'description' : description, + 'duration' : duration, + 'encodingType' : 'mpeg2ProgramStream', + 'partnerId' : 'tivo:pt.3187', + 'pcBodyId' : pc_body_id, + 'publishDate' : time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()), + 'size' : size, + 'source' : 'file:/C%3A%2FDocuments%20and%20Settings%2FStephanie%2FDesktop%2FVideo', + 'state' : 'complete', + 'subtitle' : subtitle, + 'title' : title, + 'url' : url, + } + r = urllib2.Request( + 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferModify&bodyId=tsn:' + tsn, + dictcode(data), + {'Content-Type' : 'x-tivo/dict-binary'} + ) + result = self.__opener.open(r) + + xml = ElementTree.parse(result).find('.') + + if xml.findtext('state') != 'complete': + raise Exception(ElementTree.tostring(xml)) + + offer_id = xml.findtext('offerId') + content_id = offer_id.replace('of','ct') + + self.__log('__bodyOfferModify\n%s\n\n%sg' % (data, ElementTree.tostring(xml))) + return offer_id, content_id + + + def __subscribe(self, offer_id, content_id, tsn): + data = { + 'bodyId' : 'tsn:' + tsn, + 'idSetSource' : { + 'contentId': content_id, + 'offerId' : offer_id, + 'type' : 'singleOfferSource', + }, + 'title' : 'pcBodySubscription', + 'uiType' : 'cds', + } + + r = urllib2.Request( + 'https://mind.tivo.com:8181/mind/mind7?type=subscribe&bodyId=tsn:' + tsn, + dictcode(data), + {'Content-Type' : 'x-tivo/dict-binary'} + ) + result = self.__opener.open(r) + + xml = ElementTree.parse(result).find('.') + + self.__log('__subscribe\n%s\n\n%sg' % (data, ElementTree.tostring(xml))) + + return xml + + def __pcBodySearch(self): + + data = {} + r = urllib2.Request( + 'https://mind.tivo.com:8181/mind/mind7?type=pcBodySearch', + dictcode(data), + {'Content-Type' : 'x-tivo/dict-binary'} + ) + result = self.__opener.open(r) + + xml = ElementTree.parse(result).find('.') + + + self.__log('__pcBodySearch\n%s\n\n%sg' % (data, ElementTree.tostring(xml))) + + return [id.text for id in xml.findall('pcBody/pcBodyId')] + + def __collectionIdSearch(self, url): + + data = {'url' : url} + r = urllib2.Request( + 'https://mind.tivo.com:8181/mind/mind7?type=collectionIdSearch', + dictcode(data), + {'Content-Type' : 'x-tivo/dict-binary'} + ) + result = self.__opener.open(r) + + xml = ElementTree.parse( result ).find('.') + collection_id = xml.findtext('collectionId') + + self.__log('__collectionIdSearch\n%s\n\n%sg' % (data, ElementTree.tostring(xml))) + + return collection_id + + def __pcBodyStore(self, name, replace=False): + + data = { + 'name' : name, + 'replaceExisting' : str(replace).lower(), + } + + r = urllib2.Request( + 'https://mind.tivo.com:8181/mind/mind7?type=pcBodyStore', + dictcode(data), + {'Content-Type' : 'x-tivo/dict-binary'} + ) + result = self.__opener.open(r) + + xml = ElementTree.parse(result).find('.') + + self.__log('__pcBodySearch\n%s\n\n%s' % (data, ElementTree.tostring(xml))) + + return xml + + +def dictcode(d): + """Helper to create x-tivo/dict-binary""" + output = [] + + keys = [str(k) for k in d] + keys.sort() + + for k in keys: + v = d[k] + + l = len(k) | 128 + output.append( struct.pack('>B', l) ) + output.append( k ) + + if isinstance(v, dict): + output.append( struct.pack('>B', 0x02) ) + output.append( dictcode(v) ) + + else: + v = str(v) + output.append( struct.pack('>B', 0x01) ) + l = len(v) | 128 + output.append( struct.pack('>B', l) ) + output.append( v ) + + output.append( struct.pack('>B', 0x00) ) + + output.append( struct.pack('>B', 0x80) ) + + return ''.join(output) + + +if __name__ == '__main__': + username = 'armooo@armooo.net' + password = 'in(into)' + tsn = '6520001802C0F2A' + url = 'http://10.0.1.52:9032/Steph%27s%20Videos/Weekend%20%28Godard%201967%29.avi' + + mind = Mind(username, password, True) + mind.pushVideo(tsn, url) + diff --git a/plugins/video/templates/container.tmpl b/plugins/video/templates/container.tmpl index 8151468..a2e9e01 100644 --- a/plugins/video/templates/container.tmpl +++ b/plugins/video/templates/container.tmpl @@ -1,5 +1,13 @@ + + + #for $tivo in $tivos + #if $tivo + $tivo + #end if + #end for + $start #echo len($videos) #
@@ -60,6 +68,10 @@ No /TiVoConnect?Command=TVBusQuery&Container=$quote($container)&File=$quote($video.part_path) + + $quote($container) + $quote($video.part_path) + #end if diff --git a/plugins/video/templates/container.xsl b/plugins/video/templates/container.xsl new file mode 100644 index 0000000..b2b5148 --- /dev/null +++ b/plugins/video/templates/container.xsl @@ -0,0 +1,57 @@ + + + + + +

+ + + + + + + + + + + + + + + + + + + + + +
Title
Open Folder
+ + + + + + + + + + + + + + +
+ + +
+
+ diff --git a/plugins/video/video.py b/plugins/video/video.py index bb272c2..4b9ead0 100644 --- a/plugins/video/video.py +++ b/plugins/video/video.py @@ -8,6 +8,7 @@ from UserDict import DictMixin from datetime import datetime, timedelta import config import time +import mind from debug import debug_write, fn_attr SCRIPTDIR = os.path.dirname(__file__) @@ -403,6 +404,7 @@ class Video(Plugin): t.escape = escape t.crc = zlib.crc32 t.guid = config.getGUID() + t.tivos = handler.tivos handler.wfile.write(t) def TVBusQuery(self, handler, query): @@ -423,6 +425,56 @@ class Video(Plugin): t.escape = escape handler.wfile.write(t) + def XSL(self, handler, query): + file = open(os.path.join(SCRIPTDIR, 'templates', 'container.xsl')) + handler.send_response(200) + handler.end_headers() + handler.wfile.write(file.read()) + + + def Push(self, handler, query): + file = unquote(query['File'][0]) + tsn = query['tsn'][0] + path = self.get_local_path(handler, query) + file_path = path + file + + file_info = VideoDetails() + file_info['valid'] = transcode.supported_format(file_path) + if file_info['valid']: + file_info.update(self.__metadata_full(file_path, tsn)) + + import socket + ip, port = handler.connection.getsockname() + container = quote(query['Container'][0].split('/')[0]) + + url = 'http://%s:%s/%s%s' % (ip, port, container, quote(file)) + + print 'tsn', tsn + print 'url', url + print query + + username = config.getTivoUsername() + password = config.getTivoPassword() + + if not username or not password: + raise Exception("tivo_username and tivo_password required") + + m = mind.Mind(username, password, True) + m.pushVideo( + tsn = tsn, + url = url, + description = file_info['description'], + duration = file_info['duration'], + size = file_info['size'], + title = file_info['title'], + subtitle = file_info['name']) + + referer = handler.headers.getheader('Referer') + handler.send_response(302) + handler.send_header('Location', referer) + handler.end_headers() + + class VideoDetails(DictMixin): def __init__(self, d=None): diff --git a/templates/info_page.tmpl b/templates/info_page.tmpl index d2c45fd..8142a69 100644 --- a/templates/info_page.tmpl +++ b/templates/info_page.tmpl @@ -1,6 +1,7 @@ This is a pyTivo server
- $admin + $admin
+ $shares
-- 2.11.4.GIT