Allow for pyTivo-style URLs; more Unicode in ToGo.
[pyTivo/wmcbrine.git] / mind.py
blob7f4011177a149ebbfeaf23190eab6053cbe79c25
1 import cookielib
2 import logging
3 import sys
4 import time
5 import urllib2
6 import urllib
7 import xml.etree.ElementTree as ElementTree
9 import config
10 import metadata
12 class Mind:
13 def __init__(self, username, password, tsn):
14 self.__logger = logging.getLogger('pyTivo.mind')
15 self.__username = username
16 self.__password = password
17 self.__mind = config.get_mind(tsn)
19 cj = cookielib.CookieJar()
20 cp = urllib2.HTTPCookieProcessor(cj)
21 self.__opener = urllib2.build_opener(cp)
23 self.__login()
25 def pushVideo(self, tsn, url, description, duration, size,
26 title, subtitle, source='', mime='video/mpeg',
27 tvrating=None):
28 # It looks like tivo only supports one pc per house
29 pc_body_id = self.__pcBodySearch()
31 if not source:
32 source = title
34 data = {
35 'bodyId': 'tsn:' + tsn,
36 'description': description,
37 'duration': duration,
38 'partnerId': 'tivo:pt.3187',
39 'pcBodyId': pc_body_id,
40 'publishDate': time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
41 'size': size,
42 'source': source,
43 'state': 'complete',
44 'title': title
47 rating = metadata.get_tv(tvrating)
48 if rating:
49 data['tvRating'] = rating.lower()
51 mtypes = {'video/mp4': 'avcL41MP4', 'video/bif': 'vc1ApL3'}
52 data['encodingType'] = mtypes.get(mime, 'mpeg2ProgramStream')
54 data['url'] = url + '?Format=' + mime
56 if subtitle:
57 data['subtitle'] = subtitle
59 offer_id, content_id = self.__bodyOfferModify(data)
60 self.__subscribe(offer_id, content_id, tsn)
62 def getDownloadRequests(self):
63 NEEDED_VALUES = [
64 'bodyId',
65 'bodyOfferId',
66 'description',
67 'partnerId',
68 'pcBodyId',
69 'publishDate',
70 'source',
71 'state',
72 'subscriptionId',
73 'subtitle',
74 'title',
75 'url'
78 # It looks like tivo only supports one pc per house
79 pc_body_id = self.__pcBodySearch()
81 requests = []
82 offer_list = self.__bodyOfferSchedule(pc_body_id)
84 for offer in offer_list.findall('bodyOffer'):
85 d = {}
86 if offer.findtext('state') != 'scheduled':
87 continue
89 for n in NEEDED_VALUES:
90 d[n] = offer.findtext(n)
91 requests.append(d)
93 return requests
95 def completeDownloadRequest(self, request, status, mime='video/mpeg'):
96 if status:
97 mtypes = {'video/mp4': 'avcL41MP4', 'video/bif': 'vc1ApL3'}
98 request['encodingType'] = mtypes.get(mime, 'mpeg2ProgramStream')
99 request['url'] += '?Format=' + mime
100 request['state'] = 'complete'
101 else:
102 request['state'] = 'cancelled'
103 request['cancellationReason'] = 'httpFileNotFound'
104 request['type'] = 'bodyOfferModify'
105 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S',
106 time.gmtime())
108 offer_id, content_id = self.__bodyOfferModify(request)
109 if status:
110 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
112 def getXMPPLoginInfo(self):
113 # It looks like tivo only supports one pc per house
114 pc_body_id = self.__pcBodySearch()
116 xml = self.__bodyXmppInfoGet(pc_body_id)
118 results = {
119 'server': xml.findtext('server'),
120 'port': int(xml.findtext('port')),
121 'username': xml.findtext('xmppId')
124 for sendPresence in xml.findall('sendPresence'):
125 results.setdefault('presence_list',[]).append(sendPresence.text)
127 return results
129 def __login(self):
131 data = {
132 'cams_security_domain': 'tivocom',
133 'cams_login_config': 'http',
134 'cams_cb_username': self.__username,
135 'cams_cb_password': self.__password,
136 'cams_original_url': '/mind/mind7?type=infoGet'
139 r = urllib2.Request(
140 'https://%s/mind/login' % self.__mind,
141 urllib.urlencode(data)
143 try:
144 result = self.__opener.open(r)
145 except:
146 pass
148 self.__logger.debug('__login\n%s' % (data))
150 def __dict_request(self, data, req):
151 r = urllib2.Request(
152 'https://%s/mind/mind7?type=%s' % (self.__mind, req),
153 dictcode(data),
154 {'Content-Type': 'x-tivo/dict-binary'}
156 result = self.__opener.open(r)
158 xml = ElementTree.parse(result).find('.')
160 self.__logger.debug('%s\n%s\n\n%sg' % (req, data,
161 ElementTree.tostring(xml)))
162 return xml
164 def __bodyOfferModify(self, data):
165 """Create an offer"""
167 xml = self.__dict_request(data, 'bodyOfferModify&bodyId=' +
168 data['bodyId'])
170 offer_id = xml.findtext('offerId')
171 if offer_id:
172 content_id = offer_id.replace('of','ct')
174 return offer_id, content_id
175 else:
176 raise Exception(ElementTree.tostring(xml))
178 def __subscribe(self, offer_id, content_id, tsn):
179 """Push the offer to the tivo"""
180 data = {
181 'bodyId': 'tsn:' + tsn,
182 'idSetSource': {
183 'contentId': content_id,
184 'offerId': offer_id,
185 'type': 'singleOfferSource'
187 'title': 'pcBodySubscription',
188 'uiType': 'cds'
191 return self.__dict_request(data, 'subscribe&bodyId=tsn:' + tsn)
193 def __bodyOfferSchedule(self, pc_body_id):
194 """Get pending stuff for this pc"""
196 data = {'pcBodyId': pc_body_id}
197 return self.__dict_request(data, 'bodyOfferSchedule')
199 def __pcBodySearch(self):
200 """Find PCS"""
202 xml = self.__dict_request({}, 'pcBodySearch')
203 id = xml.findtext('.//pcBodyId')
204 if not id:
205 xml = self.__pcBodyStore('pyTivo', True)
206 id = xml.findtext('.//pcBodyId')
208 return id
210 def __collectionIdSearch(self, url):
211 """Find collection ids"""
213 xml = self.__dict_request({'url': url}, 'collectionIdSearch')
214 return xml.findtext('collectionId')
216 def __pcBodyStore(self, name, replace=False):
217 """Setup a new PC"""
219 data = {
220 'name': name,
221 'replaceExisting': str(replace).lower()
224 return self.__dict_request(data, 'pcBodyStore')
226 def __bodyXmppInfoGet(self, body_id):
228 return self.__dict_request({'bodyId': body_id},
229 'bodyXmppInfoGet&bodyId=' + body_id)
232 def dictcode(d):
233 """Helper to create x-tivo/dict-binary"""
234 output = []
236 keys = [str(k) for k in d]
237 keys.sort()
239 for k in keys:
240 v = d[k]
242 output.append( varint( len(k) ) )
243 output.append( k )
245 if isinstance(v, dict):
246 output.append( chr(2) )
247 output.append( dictcode(v) )
249 else:
250 if type(v) == str:
251 try:
252 v = v.decode('utf8')
253 except:
254 if sys.platform == 'darwin':
255 v = v.decode('macroman')
256 else:
257 v = v.decode('iso8859-1')
258 elif type(v) != unicode:
259 v = str(v)
260 v = v.encode('utf-8')
261 output.append( chr(1) )
262 output.append( varint( len(v) ) )
263 output.append( v )
265 output.append( chr(0) )
267 output.append( chr(0x80) )
269 return ''.join(output)
271 def varint(i):
272 output = []
273 while i > 0x7f:
274 output.append( chr(i & 0x7f) )
275 i >>= 7
276 output.append( chr(i | 0x80) )
277 return ''.join(output)
279 def getMind(tsn=None):
280 username = config.get_tsn('tivo_username', tsn)
281 password = config.get_tsn('tivo_password', tsn)
283 if not username or not password:
284 raise Exception("tivo_username and tivo_password required")
286 return Mind(username, password, tsn)