Patch to the qt-faststart patch -- attempt to pass through if parse
[pyTivo/wmcbrine.git] / mind.py
blobdef875cd8c09af6017732c61cd1a9fb19b451a97
1 import cookielib
2 import logging
3 import time
4 import urllib2
5 import urllib
6 import warnings
8 import config
10 try:
11 import xml.etree.ElementTree as ElementTree
12 except ImportError:
13 try:
14 import elementtree.ElementTree as ElementTree
15 except ImportError:
16 warnings.warn('Python 2.5 or higher or elementtree is ' +
17 'needed to use the TivoPush')
19 if 'ElementTree' not in locals():
21 class Mind:
22 def __init__(self, *arg, **karg):
23 raise Exception('Python 2.5 or higher or elementtree is ' +
24 'needed to use the TivoPush')
26 else:
28 class Mind:
29 def __init__(self, username, password):
30 self.__logger = logging.getLogger('pyTivo.mind')
31 self.__username = username
32 self.__password = password
34 cj = cookielib.CookieJar()
35 cp = urllib2.HTTPCookieProcessor(cj)
36 self.__opener = urllib2.build_opener(cp)
38 self.__login()
40 if not self.__pcBodySearch():
41 self.__pcBodyStore('pyTivo', True)
43 def pushVideo(self, tsn, url, description, duration, size,
44 title, subtitle, mime=''):
45 # It looks like tivo only supports one pc per house
46 pc_body_id = self.__pcBodySearch()[0]
48 data = {
49 'bodyId': 'tsn:' + tsn,
50 'description': description,
51 'duration': duration,
52 'partnerId': 'tivo:pt.3187',
53 'pcBodyId': pc_body_id,
54 'publishDate': time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
55 'size': size,
56 'source': title,
57 'state': 'complete',
58 'title': title
61 if mime == 'video/mp4':
62 data['encodingType'] = 'avcL41MP4'
63 data['url'] = url + '?Format=' + mime
64 else:
65 data['encodingType'] = 'mpeg2ProgramStream'
66 data['url'] = url
68 if subtitle:
69 data['subtitle'] = subtitle
71 offer_id, content_id = self.__bodyOfferModify(data)
72 self.__subscribe(offer_id, content_id, tsn)
74 def getDownloadRequests(self):
75 NEEDED_VALUES = [
76 'bodyId',
77 'bodyOfferId',
78 'description',
79 'partnerId',
80 'pcBodyId',
81 'publishDate',
82 'source',
83 'state',
84 'subscriptionId',
85 'subtitle',
86 'title',
87 'url'
90 # It looks like tivo only supports one pc per house
91 pc_body_id = self.__pcBodySearch()[0]
93 requests = []
94 offer_list = self.__bodyOfferSchedule(pc_body_id)
96 for offer in offer_list.findall('bodyOffer'):
97 d = {}
98 if offer.findtext('state') != 'scheduled':
99 continue
101 for n in NEEDED_VALUES:
102 d[n] = offer.findtext(n)
103 requests.append(d)
105 return requests
107 def completeDownloadRequest(self, request):
108 request['encodingType'] = 'mpeg2ProgramStream'
109 request['state'] = 'complete'
110 request['type'] = 'bodyOfferModify'
111 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S',
112 time.gmtime())
114 offer_id, content_id = self.__bodyOfferModify(request)
115 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
118 def getXMPPLoginInfo(self):
119 # It looks like tivo only supports one pc per house
120 pc_body_id = self.__pcBodySearch()[0]
122 xml = self.__bodyXmppInfoGet(pc_body_id)
124 results = {}
125 results['server'] = xml.findtext('server')
126 results['port'] = int(xml.findtext('port'))
127 results['username'] = xml.findtext('xmppId')
129 for sendPresence in xml.findall('sendPresence'):
130 results.setdefault('presence_list',[]).append(sendPresence.text)
132 return results
134 def __login(self):
136 data = {
137 'cams_security_domain': 'tivocom',
138 'cams_login_config': 'http',
139 'cams_cb_username': self.__username,
140 'cams_cb_password': self.__password,
141 'cams_original_url': '/mind/mind7?type=infoGet'
144 r = urllib2.Request(
145 'https://mind.tivo.com:8181/mind/login',
146 urllib.urlencode(data)
148 try:
149 result = self.__opener.open(r)
150 except:
151 pass
153 self.__logger.debug('__login\n%s' % (data))
155 def __dict_request(self, data, req):
156 r = urllib2.Request(
157 'https://mind.tivo.com:8181/mind/mind7?type=' + req,
158 dictcode(data),
159 {'Content-Type': 'x-tivo/dict-binary'}
161 result = self.__opener.open(r)
163 xml = ElementTree.parse(result).find('.')
165 self.__logger.debug('%s\n%s\n\n%sg' % (req, data,
166 ElementTree.tostring(xml)))
167 return xml
169 def __bodyOfferModify(self, data):
170 """Create an offer"""
172 xml = self.__dict_request(data, 'bodyOfferModify&bodyId=' +
173 data['bodyId'])
175 if xml.findtext('state') != 'complete':
176 raise Exception(ElementTree.tostring(xml))
178 offer_id = xml.findtext('offerId')
179 content_id = offer_id.replace('of','ct')
181 return offer_id, content_id
184 def __subscribe(self, offer_id, content_id, tsn):
185 """Push the offer to the tivo"""
186 data = {
187 'bodyId': 'tsn:' + tsn,
188 'idSetSource': {
189 'contentId': content_id,
190 'offerId': offer_id,
191 'type': 'singleOfferSource'
193 'title': 'pcBodySubscription',
194 'uiType': 'cds'
197 return self.__dict_request(data, 'subscribe&bodyId=tsn:' + tsn)
199 def __bodyOfferSchedule(self, pc_body_id):
200 """Get pending stuff for this pc"""
202 data = {'pcBodyId': pc_body_id}
203 return self.__dict_request(data, 'bodyOfferSchedule')
205 def __pcBodySearch(self):
206 """Find PCS"""
208 xml = self.__dict_request({}, 'pcBodySearch')
210 return [id.text for id in xml.findall('pcBody/pcBodyId')]
212 def __collectionIdSearch(self, url):
213 """Find collection ids"""
215 xml = self.__dict_request({'url': url}, 'collectionIdSearch')
216 return xml.findtext('collectionId')
218 def __pcBodyStore(self, name, replace=False):
219 """Setup a new PC"""
221 data = {
222 'name': name,
223 'replaceExisting': str(replace).lower()
226 return self.__dict_request(data, 'pcBodyStore')
228 def __bodyXmppInfoGet(self, body_id):
230 return self.__dict_request({'bodyId': body_id},
231 'bodyXmppInfoGet&bodyId=' + body_id)
234 def dictcode(d):
235 """Helper to create x-tivo/dict-binary"""
236 output = []
238 keys = [str(k) for k in d]
239 keys.sort()
241 for k in keys:
242 v = d[k]
244 output.append( varint( len(k) ) )
245 output.append( k )
247 if isinstance(v, dict):
248 output.append( chr(2) )
249 output.append( dictcode(v) )
251 else:
252 v = unicode(v).encode('utf-8')
253 output.append( chr(1) )
254 output.append( varint( len(v) ) )
255 output.append( v )
257 output.append( chr(0) )
259 output.append( chr(0x80) )
261 return ''.join(output)
263 def varint(i):
264 output = []
265 while i > 0x7f:
266 output.append( chr(i & 0x7f) )
267 i >>= 7
268 output.append( chr(i | 0x80) )
269 return ''.join(output)
271 def getMind():
272 username = config.getTivoUsername()
273 password = config.getTivoPassword()
275 if not username or not password:
276 raise Exception("tivo_username and tivo_password required")
278 return Mind(username, password)