12 import xml
.etree
.ElementTree
as ElementTree
15 import elementtree
.ElementTree
as ElementTree
17 warnings
.warn('Python 2.5 or higher or elementtree is needed to use the TivoPush')
19 if 'ElementTree' not in locals():
22 def __init__(self
, *arg
, **karg
):
23 raise Exception('Python 2.5 or higher or elementtree is needed to use the TivoPush')
28 def __init__(self
, username
, password
, debug
=False):
29 self
.__username
= username
30 self
.__password
= password
34 self
.__cj
= cookielib
.CookieJar()
35 self
.__opener
= urllib2
.build_opener(urllib2
.HTTPSHandler(debuglevel
=1), 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
):
44 'bodyId' : 'tsn:' + tsn
,
45 'description' : description
,
46 'duration' : duration
,
47 'encodingType' : 'mpeg2ProgramStream',
48 'partnerId' : 'tivo:pt.3187',
49 'pcBodyId' : pc_body_id
,
50 'publishDate' : time
.strftime('%Y-%m-%d %H:%M%S', time
.gmtime()),
52 'source' : 'file:/C%3A%2FDocuments%20and%20Settings%2FStephanie%2FDesktop%2FVideo',
54 'subtitle' : subtitle
,
59 # It looks like tivo only supports one pc per house
60 pc_body_id
= self
.__pcBodySearch
()[0]
61 offer_id
, content_id
= self
.__bodyOfferModify
(data
)
62 self
.__subscribe
(offer_id
, content_id
, tsn
)
64 def getDownloadRequests(self
):
80 # It looks like tivo only supports one pc per house
81 pc_body_id
= self
.__pcBodySearch
()[0]
84 offer_list
= self
.__bodyOfferSchedule
(pc_body_id
)
86 for offer
in offer_list
.findall('bodyOffer'):
88 if offer
.findtext('state') != 'scheduled':
91 for n
in NEEDED_VALUES
:
92 d
[n
] = offer
.findtext(n
)
97 def completeDownloadRequest(self
, request
):
98 request
['encodingType'] = 'mpeg2ProgramStream'
99 request
['state'] = 'complete'
100 request
['type'] = 'bodyOfferModify'
101 request
['updateDate'] = time
.strftime('%Y-%m-%d %H:%M%S', time
.gmtime())
103 offer_id
, content_id
= self
.__bodyOfferModify
(request
)
104 self
.__subscribe
(offer_id
, content_id
, request
['bodyId'][4:])
107 def getXMPPLoginInfo(self
):
108 # It looks like tivo only supports one pc per house
109 pc_body_id
= self
.__pcBodySearch
()[0]
111 xml
= self
.__bodyXmppInfoGet
(pc_body_id
)
114 results
['server'] = xml
.findtext('server')
115 results
['port'] = int(xml
.findtext('port'))
116 results
['username'] = xml
.findtext('xmppId')
118 for sendPresence
in xml
.findall('sendPresence'):
119 results
.setdefault('presence_list', []).append(sendPresence
.text
)
123 def __log(self
, message
):
131 'cams_security_domain' : 'tivocom',
132 'cams_login_config' : 'http',
133 'cams_cb_username' : self
.__username
,
134 'cams_cb_password' : self
.__password
,
135 'cams_original_url' : '/mind/mind7?type=infoGet'
139 'https://mind.tivo.com:8181/mind/login',
140 urllib
.urlencode(data
)
143 result
= self
.__opener
.open(r
)
147 self
.__log
('__login\n%s' % (data
))
149 def __bodyOfferModify(self
, data
):
150 """Create an offer"""
152 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferModify&bodyId=' + data
['bodyId'],
154 {'Content-Type' : 'x-tivo/dict-binary'}
156 result
= self
.__opener
.open(r
)
158 xml
= ElementTree
.parse(result
).find('.')
160 self
.__log
('__bodyOfferModify\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
162 if xml
.findtext('state') != 'complete':
163 raise Exception(ElementTree
.tostring(xml
))
165 offer_id
= xml
.findtext('offerId')
166 content_id
= offer_id
.replace('of','ct')
168 return offer_id
, content_id
171 def __subscribe(self
, offer_id
, content_id
, tsn
):
172 """Push the offer to the tivo"""
174 'bodyId' : 'tsn:' + tsn
,
176 'contentId': content_id
,
177 'offerId' : offer_id
,
178 'type' : 'singleOfferSource',
180 'title' : 'pcBodySubscription',
185 'https://mind.tivo.com:8181/mind/mind7?type=subscribe&bodyId=tsn:' + tsn
,
187 {'Content-Type' : 'x-tivo/dict-binary'}
189 result
= self
.__opener
.open(r
)
191 xml
= ElementTree
.parse(result
).find('.')
193 self
.__log
('__subscribe\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
197 def __bodyOfferSchedule(self
, pc_body_id
):
198 """Get pending stuff for this pc"""
200 data
= {'pcBodyId' : pc_body_id
,}
202 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferSchedule',
204 {'Content-Type' : 'x-tivo/dict-binary'}
206 result
= self
.__opener
.open(r
)
208 xml
= ElementTree
.parse(result
).find('.')
210 self
.__log
('bodyOfferSchedule\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
214 def __pcBodySearch(self
):
219 'https://mind.tivo.com:8181/mind/mind7?type=pcBodySearch',
221 {'Content-Type' : 'x-tivo/dict-binary'}
223 result
= self
.__opener
.open(r
)
225 xml
= ElementTree
.parse(result
).find('.')
228 self
.__log
('__pcBodySearch\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
230 return [id.text
for id in xml
.findall('pcBody/pcBodyId')]
232 def __collectionIdSearch(self
, url
):
233 """Find collection ids"""
237 'https://mind.tivo.com:8181/mind/mind7?type=collectionIdSearch',
239 {'Content-Type' : 'x-tivo/dict-binary'}
241 result
= self
.__opener
.open(r
)
243 xml
= ElementTree
.parse( result
).find('.')
244 collection_id
= xml
.findtext('collectionId')
246 self
.__log
('__collectionIdSearch\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
250 def __pcBodyStore(self
, name
, replace
=False):
255 'replaceExisting' : str(replace
).lower(),
259 'https://mind.tivo.com:8181/mind/mind7?type=pcBodyStore',
261 {'Content-Type' : 'x-tivo/dict-binary'}
263 result
= self
.__opener
.open(r
)
265 xml
= ElementTree
.parse(result
).find('.')
267 self
.__log
('__pcBodySearch\n%s\n\n%s' % (data
, ElementTree
.tostring(xml
)))
271 def __bodyXmppInfoGet(self
, body_id
):
278 'https://mind.tivo.com:8181/mind/mind7?type=bodyXmppInfoGet&bodyId=' + body_id
,
280 {'Content-Type' : 'x-tivo/dict-binary'}
283 result
= self
.__opener
.open(r
)
285 xml
= ElementTree
.parse(result
).find('.')
287 self
.__log
('__bodyXmppInfoGe\n%s\n\n%s' % (data
, ElementTree
.tostring(xml
)))
293 """Helper to create x-tivo/dict-binary"""
296 keys
= [str(k
) for k
in d
]
302 output
.append( varint( len(k
) ) )
305 if isinstance(v
, dict):
306 output
.append( struct
.pack('>B', 0x02) )
307 output
.append( dictcode(v
) )
310 v
= unicode(v
).encode('utf-8')
311 output
.append( struct
.pack('>B', 0x01) )
312 output
.append( varint( len(v
) ) )
315 output
.append( struct
.pack('>B', 0x00) )
317 output
.append( struct
.pack('>B', 0x80) )
319 return ''.join(output
)
326 bits
.append(i
& 0x01)
339 for bit
, p
in zip(mybits
, itertools
.count()):
340 byte
+= bit
* (2 ** p
)
344 output
[-1] = output
[-1] |
0x80
345 return ''.join([chr(b
) for b
in output
])
349 username
= config
.getTivoUsername()
350 password
= config
.getTivoPassword()
352 if not username
or not password
:
353 raise Exception("tivo_username and tivo_password required")
355 m
= Mind(username
, password
, True)