Make substitutions for most invalid characters rather than stripping them.
[pyTivo/wmcbrine.git] / mind.py
blob3bef95b23938213ca90822cfa3e100e4e338b7bc
1 import cookielib
2 import logging
3 import sys
4 import time
5 import urllib2
6 import urllib
7 import warnings
8 import xml.etree.ElementTree as ElementTree
10 import config
11 import metadata
13 class Mind:
14 def __init__(self, username, password, tsn):
15 self.__logger = logging.getLogger('pyTivo.mind')
16 self.__username = username
17 self.__password = password
18 self.__mind = config.get_mind(tsn)
20 cj = cookielib.CookieJar()
21 cp = urllib2.HTTPCookieProcessor(cj)
22 self.__opener = urllib2.build_opener(cp)
24 self.__login()
26 def pushVideo(self, tsn, url, description, duration, size,
27 title, subtitle, source='', mime='video/mpeg',
28 tvrating=None):
29 # It looks like tivo only supports one pc per house
30 pc_body_id = self.__pcBodySearch()
32 if not source:
33 source = title
35 data = {
36 'bodyId': 'tsn:' + tsn,
37 'description': description,
38 'duration': duration,
39 'partnerId': 'tivo:pt.3187',
40 'pcBodyId': pc_body_id,
41 'publishDate': time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
42 'size': size,
43 'source': source,
44 'state': 'complete',
45 'title': title
48 rating = metadata.get_tv(tvrating)
49 if rating:
50 data['tvRating'] = rating.lower()
52 mtypes = {'video/mp4': 'avcL41MP4', 'video/bif': 'vc1ApL3'}
53 data['encodingType'] = mtypes.get(mime, 'mpeg2ProgramStream')
55 data['url'] = url + '?Format=' + mime
57 if subtitle:
58 data['subtitle'] = subtitle
60 offer_id, content_id = self.__bodyOfferModify(data)
61 self.__subscribe(offer_id, content_id, tsn)
63 def getDownloadRequests(self):
64 NEEDED_VALUES = [
65 'bodyId',
66 'bodyOfferId',
67 'description',
68 'partnerId',
69 'pcBodyId',
70 'publishDate',
71 'source',
72 'state',
73 'subscriptionId',
74 'subtitle',
75 'title',
76 'url'
79 # It looks like tivo only supports one pc per house
80 pc_body_id = self.__pcBodySearch()
82 requests = []
83 offer_list = self.__bodyOfferSchedule(pc_body_id)
85 for offer in offer_list.findall('bodyOffer'):
86 d = {}
87 if offer.findtext('state') != 'scheduled':
88 continue
90 for n in NEEDED_VALUES:
91 d[n] = offer.findtext(n)
92 requests.append(d)
94 return requests
96 def completeDownloadRequest(self, request, status, mime='video/mpeg'):
97 if status:
98 mtypes = {'video/mp4': 'avcL41MP4', 'video/bif': 'vc1ApL3'}
99 request['encodingType'] = mtypes.get(mime, 'mpeg2ProgramStream')
100 request['url'] += '?Format=' + mime
101 request['state'] = 'complete'
102 else:
103 request['state'] = 'cancelled'
104 request['cancellationReason'] = 'httpFileNotFound'
105 request['type'] = 'bodyOfferModify'
106 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S',
107 time.gmtime())
109 offer_id, content_id = self.__bodyOfferModify(request)
110 if status:
111 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
113 def getXMPPLoginInfo(self):
114 # It looks like tivo only supports one pc per house
115 pc_body_id = self.__pcBodySearch()
117 xml = self.__bodyXmppInfoGet(pc_body_id)
119 results = {
120 'server': xml.findtext('server'),
121 'port': int(xml.findtext('port')),
122 'username': xml.findtext('xmppId')
125 for sendPresence in xml.findall('sendPresence'):
126 results.setdefault('presence_list',[]).append(sendPresence.text)
128 return results
130 def __login(self):
132 data = {
133 'cams_security_domain': 'tivocom',
134 'cams_login_config': 'http',
135 'cams_cb_username': self.__username,
136 'cams_cb_password': self.__password,
137 'cams_original_url': '/mind/mind7?type=infoGet'
140 r = urllib2.Request(
141 'https://%s/mind/login' % self.__mind,
142 urllib.urlencode(data)
144 try:
145 result = self.__opener.open(r)
146 except:
147 pass
149 self.__logger.debug('__login\n%s' % (data))
151 def __dict_request(self, data, req):
152 r = urllib2.Request(
153 'https://%s/mind/mind7?type=%s' % (self.__mind, req),
154 dictcode(data),
155 {'Content-Type': 'x-tivo/dict-binary'}
157 result = self.__opener.open(r)
159 xml = ElementTree.parse(result).find('.')
161 self.__logger.debug('%s\n%s\n\n%sg' % (req, data,
162 ElementTree.tostring(xml)))
163 return xml
165 def __bodyOfferModify(self, data):
166 """Create an offer"""
168 xml = self.__dict_request(data, 'bodyOfferModify&bodyId=' +
169 data['bodyId'])
171 offer_id = xml.findtext('offerId')
172 if offer_id:
173 content_id = offer_id.replace('of','ct')
175 return offer_id, content_id
176 else:
177 raise Exception(ElementTree.tostring(xml))
179 def __subscribe(self, offer_id, content_id, tsn):
180 """Push the offer to the tivo"""
181 data = {
182 'bodyId': 'tsn:' + tsn,
183 'idSetSource': {
184 'contentId': content_id,
185 'offerId': offer_id,
186 'type': 'singleOfferSource'
188 'title': 'pcBodySubscription',
189 'uiType': 'cds'
192 return self.__dict_request(data, 'subscribe&bodyId=tsn:' + tsn)
194 def __bodyOfferSchedule(self, pc_body_id):
195 """Get pending stuff for this pc"""
197 data = {'pcBodyId': pc_body_id}
198 return self.__dict_request(data, 'bodyOfferSchedule')
200 def __pcBodySearch(self):
201 """Find PCS"""
203 xml = self.__dict_request({}, 'pcBodySearch')
204 id = xml.findtext('.//pcBodyId')
205 if not id:
206 xml = self.__pcBodyStore('pyTivo', True)
207 id = xml.findtext('.//pcBodyId')
209 return id
211 def __collectionIdSearch(self, url):
212 """Find collection ids"""
214 xml = self.__dict_request({'url': url}, 'collectionIdSearch')
215 return xml.findtext('collectionId')
217 def __pcBodyStore(self, name, replace=False):
218 """Setup a new PC"""
220 data = {
221 'name': name,
222 'replaceExisting': str(replace).lower()
225 return self.__dict_request(data, 'pcBodyStore')
227 def __bodyXmppInfoGet(self, body_id):
229 return self.__dict_request({'bodyId': body_id},
230 'bodyXmppInfoGet&bodyId=' + body_id)
233 def dictcode(d):
234 """Helper to create x-tivo/dict-binary"""
235 output = []
237 keys = [str(k) for k in d]
238 keys.sort()
240 for k in keys:
241 v = d[k]
243 output.append( varint( len(k) ) )
244 output.append( k )
246 if isinstance(v, dict):
247 output.append( chr(2) )
248 output.append( dictcode(v) )
250 else:
251 if type(v) == str:
252 try:
253 v = v.decode('utf8')
254 except:
255 if sys.platform == 'darwin':
256 v = v.decode('macroman')
257 else:
258 v = v.decode('iso8859-1')
259 elif type(v) != unicode:
260 v = str(v)
261 v = v.encode('utf-8')
262 output.append( chr(1) )
263 output.append( varint( len(v) ) )
264 output.append( v )
266 output.append( chr(0) )
268 output.append( chr(0x80) )
270 return ''.join(output)
272 def varint(i):
273 output = []
274 while i > 0x7f:
275 output.append( chr(i & 0x7f) )
276 i >>= 7
277 output.append( chr(i | 0x80) )
278 return ''.join(output)
280 def getMind(tsn=None):
281 username = config.get_tsn('tivo_username', tsn)
282 password = config.get_tsn('tivo_password', tsn)
284 if not username or not password:
285 raise Exception("tivo_username and tivo_password required")
287 return Mind(username, password, tsn)