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
):
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
)
126 'cams_security_domain' : 'tivocom',
127 'cams_login_config' : 'http',
128 'cams_cb_username' : self
.__username
,
129 'cams_cb_password' : self
.__password
,
130 'cams_original_url' : '/mind/mind7?type=infoGet'
134 'https://mind.tivo.com:8181/mind/login',
135 urllib
.urlencode(data
)
138 result
= self
.__opener
.open(r
)
142 self
.__logger
.debug('__login\n%s' % (data
))
144 def __bodyOfferModify(self
, data
):
145 """Create an offer"""
147 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferModify&bodyId=' + data
['bodyId'],
149 {'Content-Type' : 'x-tivo/dict-binary'}
151 result
= self
.__opener
.open(r
)
153 xml
= ElementTree
.parse(result
).find('.')
155 self
.__logger
.debug('__bodyOfferModify\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
157 if xml
.findtext('state') != 'complete':
158 raise Exception(ElementTree
.tostring(xml
))
160 offer_id
= xml
.findtext('offerId')
161 content_id
= offer_id
.replace('of','ct')
163 return offer_id
, content_id
166 def __subscribe(self
, offer_id
, content_id
, tsn
):
167 """Push the offer to the tivo"""
169 'bodyId' : 'tsn:' + tsn
,
171 'contentId': content_id
,
172 'offerId' : offer_id
,
173 'type' : 'singleOfferSource',
175 'title' : 'pcBodySubscription',
180 'https://mind.tivo.com:8181/mind/mind7?type=subscribe&bodyId=tsn:' + tsn
,
182 {'Content-Type' : 'x-tivo/dict-binary'}
184 result
= self
.__opener
.open(r
)
186 xml
= ElementTree
.parse(result
).find('.')
188 self
.__logger
.debug('__subscribe\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
192 def __bodyOfferSchedule(self
, pc_body_id
):
193 """Get pending stuff for this pc"""
195 data
= {'pcBodyId' : pc_body_id
,}
197 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferSchedule',
199 {'Content-Type' : 'x-tivo/dict-binary'}
201 result
= self
.__opener
.open(r
)
203 xml
= ElementTree
.parse(result
).find('.')
205 self
.__logger
.debug('bodyOfferSchedule\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
209 def __pcBodySearch(self
):
214 'https://mind.tivo.com:8181/mind/mind7?type=pcBodySearch',
216 {'Content-Type' : 'x-tivo/dict-binary'}
218 result
= self
.__opener
.open(r
)
220 xml
= ElementTree
.parse(result
).find('.')
223 self
.__logger
.debug('__pcBodySearch\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
225 return [id.text
for id in xml
.findall('pcBody/pcBodyId')]
227 def __collectionIdSearch(self
, url
):
228 """Find collection ids"""
232 'https://mind.tivo.com:8181/mind/mind7?type=collectionIdSearch',
234 {'Content-Type' : 'x-tivo/dict-binary'}
236 result
= self
.__opener
.open(r
)
238 xml
= ElementTree
.parse( result
).find('.')
239 collection_id
= xml
.findtext('collectionId')
241 self
.__logger
.debug('__collectionIdSearch\n%s\n\n%sg' % (data
, ElementTree
.tostring(xml
)))
245 def __pcBodyStore(self
, name
, replace
=False):
250 'replaceExisting' : str(replace
).lower(),
254 'https://mind.tivo.com:8181/mind/mind7?type=pcBodyStore',
256 {'Content-Type' : 'x-tivo/dict-binary'}
258 result
= self
.__opener
.open(r
)
260 xml
= ElementTree
.parse(result
).find('.')
262 self
.__logger
.debug('__pcBodySearch\n%s\n\n%s' % (data
, ElementTree
.tostring(xml
)))
266 def __bodyXmppInfoGet(self
, body_id
):
273 'https://mind.tivo.com:8181/mind/mind7?type=bodyXmppInfoGet&bodyId=' + body_id
,
275 {'Content-Type' : 'x-tivo/dict-binary'}
278 result
= self
.__opener
.open(r
)
280 xml
= ElementTree
.parse(result
).find('.')
282 self
.__logger
.debug('__bodyXmppInfoGe\n%s\n\n%s' % (data
, ElementTree
.tostring(xml
)))
288 """Helper to create x-tivo/dict-binary"""
291 keys
= [str(k
) for k
in d
]
297 output
.append( varint( len(k
) ) )
300 if isinstance(v
, dict):
301 output
.append( struct
.pack('>B', 0x02) )
302 output
.append( dictcode(v
) )
305 v
= unicode(v
).encode('utf-8')
306 output
.append( struct
.pack('>B', 0x01) )
307 output
.append( varint( len(v
) ) )
310 output
.append( struct
.pack('>B', 0x00) )
312 output
.append( struct
.pack('>B', 0x80) )
314 return ''.join(output
)
321 bits
.append(i
& 0x01)
334 for bit
, p
in zip(mybits
, itertools
.count()):
335 byte
+= bit
* (2 ** p
)
339 output
[-1] = output
[-1] |
0x80
340 return ''.join([chr(b
) for b
in output
])
344 username
= config
.getTivoUsername()
345 password
= config
.getTivoPassword()
347 if not username
or not password
:
348 raise Exception("tivo_username and tivo_password required")
350 m
= Mind(username
, password
)