13 import xml
.etree
.ElementTree
as ElementTree
16 import elementtree
.ElementTree
as ElementTree
18 warnings
.warn('Python 2.5 or higher or elementtree is needed to use the TivoPush')
20 if 'ElementTree' not in locals():
23 def __init__(self
, *arg
, **karg
):
24 raise Exception('Python 2.5 or higher or elementtree is needed to use the TivoPush')
29 def __init__(self
, username
, password
):
30 self
.__logger
= logging
.getLogger('pyTivo.mind')
31 self
.__username
= username
32 self
.__password
= password
34 self
.__cj
= cookielib
.CookieJar()
35 self
.__opener
= urllib2
.build_opener(urllib2
.HTTPCookieProcessor(self
.__cj
))
39 if not self
.__pcBodySearch
():
40 self
.__pcBodyStore
('pyTivo', True)
42 def pushVideo(self
, tsn
, url
, description
, duration
, size
, title
, subtitle
):
43 # It looks like tivo only supports one pc per house
44 pc_body_id
= self
.__pcBodySearch
()[0]
47 'bodyId' : 'tsn:' + tsn
,
48 'description' : description
,
49 'duration' : duration
,
50 'encodingType' : 'mpeg2ProgramStream',
51 'partnerId' : 'tivo:pt.3187',
52 'pcBodyId' : pc_body_id
,
53 'publishDate' : time
.strftime('%Y-%m-%d %H:%M%S', time
.gmtime()),
55 'source' : 'file:/C%3A%2FDocuments%20and%20Settings%2FStephanie%2FDesktop%2FVideo',
57 'subtitle' : subtitle
,
62 offer_id
, content_id
= self
.__bodyOfferModify
(data
)
63 self
.__subscribe
(offer_id
, content_id
, tsn
)
65 def getDownloadRequests(self
):
81 # It looks like tivo only supports one pc per house
82 pc_body_id
= self
.__pcBodySearch
()[0]
85 offer_list
= self
.__bodyOfferSchedule
(pc_body_id
)
87 for offer
in offer_list
.findall('bodyOffer'):
89 if offer
.findtext('state') != 'scheduled':
92 for n
in NEEDED_VALUES
:
93 d
[n
] = offer
.findtext(n
)
98 def completeDownloadRequest(self
, request
):
99 request
['encodingType'] = 'mpeg2ProgramStream'
100 request
['state'] = 'complete'
101 request
['type'] = 'bodyOfferModify'
102 request
['updateDate'] = time
.strftime('%Y-%m-%d %H:%M%S', time
.gmtime())
104 offer_id
, content_id
= self
.__bodyOfferModify
(request
)
105 self
.__subscribe
(offer_id
, content_id
, request
['bodyId'][4:])
108 def getXMPPLoginInfo(self
):
109 # It looks like tivo only supports one pc per house
110 pc_body_id
= self
.__pcBodySearch
()[0]
112 xml
= self
.__bodyXmppInfoGet
(pc_body_id
)
115 results
['server'] = xml
.findtext('server')
116 results
['port'] = int(xml
.findtext('port'))
117 results
['username'] = xml
.findtext('xmppId')
119 for sendPresence
in xml
.findall('sendPresence'):
120 results
.setdefault('presence_list', []).append(sendPresence
.text
)
127 'cams_security_domain' : 'tivocom',
128 'cams_login_config' : 'http',
129 'cams_cb_username' : self
.__username
,
130 'cams_cb_password' : self
.__password
,
131 'cams_original_url' : '/mind/mind7?type=infoGet'
135 'https://mind.tivo.com:8181/mind/login',
136 urllib
.urlencode(data
)
139 result
= self
.__opener
.open(r
)
143 self
.__logger
.debug('__login\n%s' % (data
))
145 def __bodyOfferModify(self
, data
):
146 """Create an offer"""
148 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferModify&bodyId=' + data
['bodyId'],
150 {'Content-Type' : 'x-tivo/dict-binary'}
152 result
= self
.__opener
.open(r
)
154 xml
= ElementTree
.parse(result
).find('.')
156 self
.__logger
.debug('__bodyOfferModify\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
158 if xml
.findtext('state') != 'complete':
159 raise Exception(ElementTree
.tostring(xml
))
161 offer_id
= xml
.findtext('offerId')
162 content_id
= offer_id
.replace('of','ct')
164 return offer_id
, content_id
167 def __subscribe(self
, offer_id
, content_id
, tsn
):
168 """Push the offer to the tivo"""
170 'bodyId' : 'tsn:' + tsn
,
172 'contentId': content_id
,
173 'offerId' : offer_id
,
174 'type' : 'singleOfferSource',
176 'title' : 'pcBodySubscription',
181 'https://mind.tivo.com:8181/mind/mind7?type=subscribe&bodyId=tsn:' + tsn
,
183 {'Content-Type' : 'x-tivo/dict-binary'}
185 result
= self
.__opener
.open(r
)
187 xml
= ElementTree
.parse(result
).find('.')
189 self
.__logger
.debug('__subscribe\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
193 def __bodyOfferSchedule(self
, pc_body_id
):
194 """Get pending stuff for this pc"""
196 data
= {'pcBodyId' : pc_body_id
,}
198 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferSchedule',
200 {'Content-Type' : 'x-tivo/dict-binary'}
202 result
= self
.__opener
.open(r
)
204 xml
= ElementTree
.parse(result
).find('.')
206 self
.__logger
.debug('bodyOfferSchedule\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
210 def __pcBodySearch(self
):
215 'https://mind.tivo.com:8181/mind/mind7?type=pcBodySearch',
217 {'Content-Type' : 'x-tivo/dict-binary'}
219 result
= self
.__opener
.open(r
)
221 xml
= ElementTree
.parse(result
).find('.')
224 self
.__logger
.debug('__pcBodySearch\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
226 return [id.text
for id in xml
.findall('pcBody/pcBodyId')]
228 def __collectionIdSearch(self
, url
):
229 """Find collection ids"""
233 'https://mind.tivo.com:8181/mind/mind7?type=collectionIdSearch',
235 {'Content-Type' : 'x-tivo/dict-binary'}
237 result
= self
.__opener
.open(r
)
239 xml
= ElementTree
.parse( result
).find('.')
240 collection_id
= xml
.findtext('collectionId')
242 self
.__logger
.debug('__collectionIdSearch\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
246 def __pcBodyStore(self
, name
, replace
=False):
251 'replaceExisting' : str(replace
).lower(),
255 'https://mind.tivo.com:8181/mind/mind7?type=pcBodyStore',
257 {'Content-Type' : 'x-tivo/dict-binary'}
259 result
= self
.__opener
.open(r
)
261 xml
= ElementTree
.parse(result
).find('.')
263 self
.__logger
.debug('__pcBodySearch\n%s\n\n%s' % (data
, ElementTree
.tostring(xml
)))
267 def __bodyXmppInfoGet(self
, body_id
):
274 'https://mind.tivo.com:8181/mind/mind7?type=bodyXmppInfoGet&bodyId=' + body_id
,
276 {'Content-Type' : 'x-tivo/dict-binary'}
279 result
= self
.__opener
.open(r
)
281 xml
= ElementTree
.parse(result
).find('.')
283 self
.__logger
.debug('__bodyXmppInfoGe\n%s\n\n%s' % (data
, ElementTree
.tostring(xml
)))
289 """Helper to create x-tivo/dict-binary"""
292 keys
= [str(k
) for k
in d
]
298 output
.append( varint( len(k
) ) )
301 if isinstance(v
, dict):
302 output
.append( struct
.pack('>B', 0x02) )
303 output
.append( dictcode(v
) )
306 v
= unicode(v
).encode('utf-8')
307 output
.append( struct
.pack('>B', 0x01) )
308 output
.append( varint( len(v
) ) )
311 output
.append( struct
.pack('>B', 0x00) )
313 output
.append( struct
.pack('>B', 0x80) )
315 return ''.join(output
)
322 bits
.append(i
& 0x01)
335 for bit
, p
in zip(mybits
, itertools
.count()):
336 byte
+= bit
* (2 ** p
)
340 output
[-1] = output
[-1] |
0x80
341 return ''.join([chr(b
) for b
in output
])
345 username
= config
.getTivoUsername()
346 password
= config
.getTivoPassword()
348 if not username
or not password
:
349 raise Exception("tivo_username and tivo_password required")
351 m
= Mind(username
, password
)