mimetype.guess_type() returns a tuple, and it might be (None, None).
[pyTivo/TheBayer.git] / mind.py
blob563245013fb361ed9025b88b8e9dfd444283b897
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, tsn):
31 self.__logger = logging.getLogger('pyTivo.mind')
32 self.__username = username
33 self.__password = password
34 self.__mind = config.get_mind(tsn)
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 tvrating=None):
48 # It looks like tivo only supports one pc per house
49 pc_body_id = self.__pcBodySearch()[0]
51 if not source:
52 source = title
54 data = {
55 'bodyId': 'tsn:' + tsn,
56 'description': description,
57 'duration': duration,
58 'partnerId': 'tivo:pt.3187',
59 'pcBodyId': pc_body_id,
60 'publishDate': time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
61 'size': size,
62 'source': source,
63 'state': 'complete',
64 'title': title
67 ratings = {'x1': 'y7', 'x2': 'y', 'x3': 'g', 'x4': 'pg',
68 'x5': '14', 'x6': 'ma', 'x7': 'nr'}
69 if tvrating in ratings:
70 data['tvRating'] = ratings[tvrating]
72 if mime == 'video/mp4':
73 data['encodingType'] = 'avcL41MP4'
74 elif mime == 'video/bif':
75 data['encodingType'] = 'vc1ApL3'
76 else:
77 data['encodingType'] = 'mpeg2ProgramStream'
79 data['url'] = url + '?Format=' + mime
81 if subtitle:
82 data['subtitle'] = subtitle
84 offer_id, content_id = self.__bodyOfferModify(data)
85 self.__subscribe(offer_id, content_id, tsn)
87 def getDownloadRequests(self):
88 NEEDED_VALUES = [
89 'bodyId',
90 'bodyOfferId',
91 'description',
92 'partnerId',
93 'pcBodyId',
94 'publishDate',
95 'source',
96 'state',
97 'subscriptionId',
98 'subtitle',
99 'title',
100 'url'
103 # It looks like tivo only supports one pc per house
104 pc_body_id = self.__pcBodySearch()[0]
106 requests = []
107 offer_list = self.__bodyOfferSchedule(pc_body_id)
109 for offer in offer_list.findall('bodyOffer'):
110 d = {}
111 if offer.findtext('state') != 'scheduled':
112 continue
114 for n in NEEDED_VALUES:
115 d[n] = offer.findtext(n)
116 requests.append(d)
118 return requests
120 def completeDownloadRequest(self, request, status, mime='video/mpeg'):
121 if status:
122 if mime == 'video/mp4':
123 request['encodingType'] = 'avcL41MP4'
124 elif mime == 'video/bif':
125 request['encodingType'] = 'vc1ApL3'
126 else:
127 request['encodingType'] = 'mpeg2ProgramStream'
128 request['url'] += '?Format=' + mime
129 request['state'] = 'complete'
130 else:
131 request['state'] = 'cancelled'
132 request['cancellationReason'] = 'httpFileNotFound'
133 request['type'] = 'bodyOfferModify'
134 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S',
135 time.gmtime())
137 offer_id, content_id = self.__bodyOfferModify(request)
138 if status:
139 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
141 def getXMPPLoginInfo(self):
142 # It looks like tivo only supports one pc per house
143 pc_body_id = self.__pcBodySearch()[0]
145 xml = self.__bodyXmppInfoGet(pc_body_id)
147 results = {
148 'server': xml.findtext('server'),
149 'port': int(xml.findtext('port')),
150 'username': xml.findtext('xmppId')
153 for sendPresence in xml.findall('sendPresence'):
154 results.setdefault('presence_list',[]).append(sendPresence.text)
156 return results
158 def __login(self):
160 data = {
161 'cams_security_domain': 'tivocom',
162 'cams_login_config': 'http',
163 'cams_cb_username': self.__username,
164 'cams_cb_password': self.__password,
165 'cams_original_url': '/mind/mind7?type=infoGet'
168 r = urllib2.Request(
169 'https://%s/mind/login' % self.__mind,
170 urllib.urlencode(data)
172 try:
173 result = self.__opener.open(r)
174 except:
175 pass
177 self.__logger.debug('__login\n%s' % (data))
179 def __dict_request(self, data, req):
180 r = urllib2.Request(
181 'https://%s/mind/mind7?type=%s' % (self.__mind, req),
182 dictcode(data),
183 {'Content-Type': 'x-tivo/dict-binary'}
185 result = self.__opener.open(r)
187 xml = ElementTree.parse(result).find('.')
189 self.__logger.debug('%s\n%s\n\n%sg' % (req, data,
190 ElementTree.tostring(xml)))
191 return xml
193 def __bodyOfferModify(self, data):
194 """Create an offer"""
196 xml = self.__dict_request(data, 'bodyOfferModify&bodyId=' +
197 data['bodyId'])
199 offer_id = xml.findtext('offerId')
200 content_id = offer_id.replace('of','ct')
202 return offer_id, content_id
204 def __subscribe(self, offer_id, content_id, tsn):
205 """Push the offer to the tivo"""
206 data = {
207 'bodyId': 'tsn:' + tsn,
208 'idSetSource': {
209 'contentId': content_id,
210 'offerId': offer_id,
211 'type': 'singleOfferSource'
213 'title': 'pcBodySubscription',
214 'uiType': 'cds'
217 return self.__dict_request(data, 'subscribe&bodyId=tsn:' + tsn)
219 def __bodyOfferSchedule(self, pc_body_id):
220 """Get pending stuff for this pc"""
222 data = {'pcBodyId': pc_body_id}
223 return self.__dict_request(data, 'bodyOfferSchedule')
225 def __pcBodySearch(self):
226 """Find PCS"""
228 xml = self.__dict_request({}, 'pcBodySearch')
230 return [id.text for id in xml.findall('pcBody/pcBodyId')]
232 def __collectionIdSearch(self, url):
233 """Find collection ids"""
235 xml = self.__dict_request({'url': url}, 'collectionIdSearch')
236 return xml.findtext('collectionId')
238 def __pcBodyStore(self, name, replace=False):
239 """Setup a new PC"""
241 data = {
242 'name': name,
243 'replaceExisting': str(replace).lower()
246 return self.__dict_request(data, 'pcBodyStore')
248 def __bodyXmppInfoGet(self, body_id):
250 return self.__dict_request({'bodyId': body_id},
251 'bodyXmppInfoGet&bodyId=' + body_id)
254 def dictcode(d):
255 """Helper to create x-tivo/dict-binary"""
256 output = []
258 keys = [str(k) for k in d]
259 keys.sort()
261 for k in keys:
262 v = d[k]
264 output.append( varint( len(k) ) )
265 output.append( k )
267 if isinstance(v, dict):
268 output.append( chr(2) )
269 output.append( dictcode(v) )
271 else:
272 if type(v) == str:
273 try:
274 v = v.decode('utf8')
275 except:
276 if sys.platform == 'darwin':
277 v = v.decode('macroman')
278 else:
279 v = v.decode('iso8859-1')
280 elif type(v) != unicode:
281 v = str(v)
282 v = v.encode('utf-8')
283 output.append( chr(1) )
284 output.append( varint( len(v) ) )
285 output.append( v )
287 output.append( chr(0) )
289 output.append( chr(0x80) )
291 return ''.join(output)
293 def varint(i):
294 output = []
295 while i > 0x7f:
296 output.append( chr(i & 0x7f) )
297 i >>= 7
298 output.append( chr(i | 0x80) )
299 return ''.join(output)
301 def getMind(tsn=None):
302 username = config.get_tsn('tivo_username', tsn)
303 password = config.get_tsn('tivo_password', tsn)
305 if not username or not password:
306 raise Exception("tivo_username and tivo_password required")
308 return Mind(username, password, tsn)