Merge commit 'wmcbrine/master'
[pyTivo/TheBayer.git] / mind.py
blob9c6e9742548b965a64470f2c44b0f4349b53ed1f
1 import cookielib
2 import logging
3 import sys
4 import time
5 import urllib2
6 import urllib
7 import warnings
9 import config
11 try:
12 import xml.etree.ElementTree as ElementTree
13 except ImportError:
14 try:
15 import elementtree.ElementTree as ElementTree
16 except ImportError:
17 warnings.warn('Python 2.5 or higher or elementtree is ' +
18 'needed to use the TivoPush')
20 if 'ElementTree' not in locals():
22 class Mind:
23 def __init__(self, *arg, **karg):
24 raise Exception('Python 2.5 or higher or elementtree is ' +
25 'needed to use the TivoPush')
27 else:
29 class Mind:
30 def __init__(self, username, password):
31 self.__logger = logging.getLogger('pyTivo.mind')
32 self.__username = username
33 self.__password = password
34 self.__mind = config.get_mind()
36 cj = cookielib.CookieJar()
37 cp = urllib2.HTTPCookieProcessor(cj)
38 self.__opener = urllib2.build_opener(cp)
40 self.__login()
42 if not self.__pcBodySearch():
43 self.__pcBodyStore('pyTivo', True)
45 def pushVideo(self, tsn, url, description, duration, size,
46 title, subtitle, source='', mime='video/mpeg'):
47 # It looks like tivo only supports one pc per house
48 pc_body_id = self.__pcBodySearch()[0]
50 if not source:
51 source = title
53 data = {
54 'bodyId': 'tsn:' + tsn,
55 'description': description,
56 'duration': duration,
57 'partnerId': 'tivo:pt.3187',
58 'pcBodyId': pc_body_id,
59 'publishDate': time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
60 'size': size,
61 'source': source,
62 'state': 'complete',
63 'title': title
66 if mime == 'video/mp4':
67 data['encodingType'] = 'avcL41MP4'
68 elif mime == 'video/bif':
69 data['encodingType'] = 'vc1ApL3'
70 else:
71 data['encodingType'] = 'mpeg2ProgramStream'
73 data['url'] = url + '?Format=' + mime
75 if subtitle:
76 data['subtitle'] = subtitle
78 offer_id, content_id = self.__bodyOfferModify(data)
79 self.__subscribe(offer_id, content_id, tsn)
81 def getDownloadRequests(self):
82 NEEDED_VALUES = [
83 'bodyId',
84 'bodyOfferId',
85 'description',
86 'partnerId',
87 'pcBodyId',
88 'publishDate',
89 'source',
90 'state',
91 'subscriptionId',
92 'subtitle',
93 'title',
94 'url'
97 # It looks like tivo only supports one pc per house
98 pc_body_id = self.__pcBodySearch()[0]
100 requests = []
101 offer_list = self.__bodyOfferSchedule(pc_body_id)
103 for offer in offer_list.findall('bodyOffer'):
104 d = {}
105 if offer.findtext('state') != 'scheduled':
106 continue
108 for n in NEEDED_VALUES:
109 d[n] = offer.findtext(n)
110 requests.append(d)
112 return requests
114 def completeDownloadRequest(self, request, status, mime='video/mpeg'):
115 if status:
116 if mime == 'video/mp4':
117 request['encodingType'] = 'avcL41MP4'
118 elif mime == 'video/bif':
119 request['encodingType'] = 'vc1ApL3'
120 else:
121 request['encodingType'] = 'mpeg2ProgramStream'
122 request['url'] += '?Format=' + mime
123 request['state'] = 'complete'
124 else:
125 request['state'] = 'cancelled'
126 request['cancellationReason'] = 'httpFileNotFound'
127 request['type'] = 'bodyOfferModify'
128 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S',
129 time.gmtime())
131 offer_id, content_id = self.__bodyOfferModify(request)
132 if status:
133 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
135 def getXMPPLoginInfo(self):
136 # It looks like tivo only supports one pc per house
137 pc_body_id = self.__pcBodySearch()[0]
139 xml = self.__bodyXmppInfoGet(pc_body_id)
141 results = {
142 'server': xml.findtext('server'),
143 'port': int(xml.findtext('port')),
144 'username': xml.findtext('xmppId')
147 for sendPresence in xml.findall('sendPresence'):
148 results.setdefault('presence_list',[]).append(sendPresence.text)
150 return results
152 def __login(self):
154 data = {
155 'cams_security_domain': 'tivocom',
156 'cams_login_config': 'http',
157 'cams_cb_username': self.__username,
158 'cams_cb_password': self.__password,
159 'cams_original_url': '/mind/mind7?type=infoGet'
162 r = urllib2.Request(
163 'https://%s/mind/login' % self.__mind,
164 urllib.urlencode(data)
166 try:
167 result = self.__opener.open(r)
168 except:
169 pass
171 self.__logger.debug('__login\n%s' % (data))
173 def __dict_request(self, data, req):
174 r = urllib2.Request(
175 'https://%s/mind/mind7?type=%s' % (self.__mind, req),
176 dictcode(data),
177 {'Content-Type': 'x-tivo/dict-binary'}
179 result = self.__opener.open(r)
181 xml = ElementTree.parse(result).find('.')
183 self.__logger.debug('%s\n%s\n\n%sg' % (req, data,
184 ElementTree.tostring(xml)))
185 return xml
187 def __bodyOfferModify(self, data):
188 """Create an offer"""
190 xml = self.__dict_request(data, 'bodyOfferModify&bodyId=' +
191 data['bodyId'])
193 offer_id = xml.findtext('offerId')
194 content_id = offer_id.replace('of','ct')
196 return offer_id, content_id
198 def __subscribe(self, offer_id, content_id, tsn):
199 """Push the offer to the tivo"""
200 data = {
201 'bodyId': 'tsn:' + tsn,
202 'idSetSource': {
203 'contentId': content_id,
204 'offerId': offer_id,
205 'type': 'singleOfferSource'
207 'title': 'pcBodySubscription',
208 'uiType': 'cds'
211 return self.__dict_request(data, 'subscribe&bodyId=tsn:' + tsn)
213 def __bodyOfferSchedule(self, pc_body_id):
214 """Get pending stuff for this pc"""
216 data = {'pcBodyId': pc_body_id}
217 return self.__dict_request(data, 'bodyOfferSchedule')
219 def __pcBodySearch(self):
220 """Find PCS"""
222 xml = self.__dict_request({}, 'pcBodySearch')
224 return [id.text for id in xml.findall('pcBody/pcBodyId')]
226 def __collectionIdSearch(self, url):
227 """Find collection ids"""
229 xml = self.__dict_request({'url': url}, 'collectionIdSearch')
230 return xml.findtext('collectionId')
232 def __pcBodyStore(self, name, replace=False):
233 """Setup a new PC"""
235 data = {
236 'name': name,
237 'replaceExisting': str(replace).lower()
240 return self.__dict_request(data, 'pcBodyStore')
242 def __bodyXmppInfoGet(self, body_id):
244 return self.__dict_request({'bodyId': body_id},
245 'bodyXmppInfoGet&bodyId=' + body_id)
248 def dictcode(d):
249 """Helper to create x-tivo/dict-binary"""
250 output = []
252 keys = [str(k) for k in d]
253 keys.sort()
255 for k in keys:
256 v = d[k]
258 output.append( varint( len(k) ) )
259 output.append( k )
261 if isinstance(v, dict):
262 output.append( chr(2) )
263 output.append( dictcode(v) )
265 else:
266 if type(v) == str:
267 try:
268 v = v.decode('utf8')
269 except:
270 if sys.platform == 'darwin':
271 v = v.decode('macroman')
272 else:
273 v = v.decode('iso8859-1')
274 elif type(v) != unicode:
275 v = str(v)
276 v = v.encode('utf-8')
277 output.append( chr(1) )
278 output.append( varint( len(v) ) )
279 output.append( v )
281 output.append( chr(0) )
283 output.append( chr(0x80) )
285 return ''.join(output)
287 def varint(i):
288 output = []
289 while i > 0x7f:
290 output.append( chr(i & 0x7f) )
291 i >>= 7
292 output.append( chr(i | 0x80) )
293 return ''.join(output)
295 def getMind(tsn=None):
296 username = config.get_tsn('tivo_username', tsn)
297 password = config.get_tsn('tivo_password', tsn)
299 if not username or not password:
300 raise Exception("tivo_username and tivo_password required")
302 return Mind(username, password)