1 import os
, socket
, re
, sys
, ConfigParser
, config
, time
2 import urllib2
, cookielib
, thread
3 from xml
.dom
import minidom
4 from ConfigParser
import NoOptionError
5 from Cheetah
.Template
import Template
6 from plugin
import Plugin
7 from urllib
import unquote_plus
, quote
, unquote
8 from urlparse
import urlparse
9 from xml
.sax
.saxutils
import escape
10 from lrucache
import LRUCache
12 SCRIPTDIR
= os
.path
.dirname(__file__
)
16 p
= os
.path
.dirname(__file__
)
17 p
= p
.split(os
.path
.sep
)
20 p
= os
.path
.sep
.join(p
)
21 config_file_path
= os
.path
.join(p
, 'pyTivo.conf')
23 status
= {} #Global variable to control download threads
24 tivo_cache
= {} #Cache of TiVo NPL
27 CONTENT_TYPE
= 'text/html'
29 def Reset(self
, handler
, query
):
31 handler
.server
.reset()
32 if 'last_page' in query
:
33 last_page
= query
['last_page'][0]
37 subcname
= query
['Container'][0]
38 cname
= subcname
.split('/')[0]
39 handler
.send_response(200)
41 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'redirect.tmpl'))
44 t
.url
= '/TiVoConnect?Command='+ last_page
+'&Container=' + cname
45 t
.text
= '<h3>The pyTivo Server has been soft reset.</h3> <br>pyTivo has reloaded the pyTivo.conf'+\
46 'file and all changed should now be in effect. <br> The'+ \
47 '<a href="/TiVoConnect?Command='+ last_page
+'&Container='+ cname
+'"> previous</a> page will reload in 3 seconds.'
48 handler
.wfile
.write(t
)
50 def Admin(self
, handler
, query
):
51 #Read config file new each time in case there was any outside edits
52 config
= ConfigParser
.ConfigParser()
53 config
.read(config_file_path
)
56 for section
in config
.sections():
57 if not(section
.startswith('_tivo_') or section
.startswith('Server')):
58 if not(config
.has_option(section
,'type')):
59 shares_data
.append((section
, dict(config
.items(section
, raw
=True))))
60 elif config
.get(section
,'type').lower() != 'admin':
61 shares_data
.append((section
, dict(config
.items(section
, raw
=True))))
63 subcname
= query
['Container'][0]
64 cname
= subcname
.split('/')[0]
65 handler
.send_response(200)
67 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'settings.tmpl'))
69 t
.server_data
= dict(config
.items('Server', raw
=True))
70 t
.server_known
= ["port", "guid", "ffmpeg", "beacon", "hack83", "debug", \
71 "optres", "audio_br", "video_br", "max_video_br", "width",\
72 "height", "ffmpeg_prams", "bufsize", "precache"]
73 t
.shares_data
= shares_data
74 t
.shares_known
= ["type", "path", "auto_subshares"]
75 t
.tivos_data
= [ (section
, dict(config
.items(section
, raw
=True))) for section
in config
.sections() \
76 if section
.startswith('_tivo_')]
77 t
.tivos_known
= ["aspect169", "audio_br", "video_br", "width", "height", "ffmpeg_prams", "shares"]
78 handler
.wfile
.write(t
)
80 def UpdateSettings(self
, handler
, query
):
81 config
= ConfigParser
.ConfigParser()
82 config
.read(config_file_path
)
84 if key
.startswith('Server.'):
85 section
, option
= key
.split('.')
86 if option
== "new__setting":
87 new_setting
= query
[key
][0]
89 if option
== "new__value":
90 new_value
= query
[key
][0]
92 if query
[key
][0] == " ":
93 config
.remove_option(section
, option
)
95 config
.set(section
, option
, query
[key
][0])
96 if not(new_setting
== ' ' and new_value
== ' '):
97 config
.set('Server', new_setting
, new_value
)
99 sections
= query
['Section_Map'][0].split(']')
100 sections
.pop() #last item is junk
101 for section
in sections
:
102 ID
, name
= section
.split('|')
103 if query
[ID
][0] == "Delete_Me":
104 config
.remove_section(name
)
106 if query
[ID
][0] != name
:
107 config
.remove_section(name
)
108 config
.add_section(query
[ID
][0])
110 if key
.startswith(ID
+ '.'):
111 junk
, option
= key
.split('.')
112 if option
== "new__setting":
113 new_setting
= query
[key
][0]
115 if option
== "new__value":
116 new_value
= query
[key
][0]
118 if query
[key
][0] == " ":
119 config
.remove_option(query
[ID
][0], option
)
121 config
.set(query
[ID
][0], option
, query
[key
][0])
122 if not(new_setting
== ' ' and new_value
== ' '):
123 config
.set(query
[ID
][0], new_setting
, new_value
)
124 if query
['new_Section'][0] != " ":
125 config
.add_section(query
['new_Section'][0])
126 f
= open(config_file_path
, "w")
130 subcname
= query
['Container'][0]
131 cname
= subcname
.split('/')[0]
132 handler
.send_response(200)
133 handler
.end_headers()
134 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'redirect.tmpl'))
137 t
.url
= '/TiVoConnect?Command=Admin&Container=' + cname
138 t
.text
= '<h3>Your Settings have been saved.</h3> <br>You settings have been saved to the pyTivo.conf file.'+\
139 'However you will need to do a <b>Soft Reset</b> before these changes will take effect.'+\
140 '<br> The <a href="/TiVoConnect?Command=Admin&Container='+ cname
+'"> Admin</a> page will reload in 10 seconds.'
141 handler
.wfile
.write(t
)
143 def NPL(self
, handler
, query
):
144 subcname
= query
['Container'][0]
145 cname
= subcname
.split('/')[0]
147 for name
, data
in config
.getShares():
149 if 'tivo_mak' in data
:
150 tivo_mak
= data
['tivo_mak']
153 if 'togo_path' in data
:
154 togo_path
= data
['togo_path']
159 tivoIP
= query
['TiVo'][0]
160 theurl
= 'https://'+ tivoIP
+'/TiVoConnect?Command=QueryContainer&Container=/NowPlaying'
161 if 'Folder' in query
:
162 folder
+= str(query
['Folder'][0])
163 theurl
+= '/' + folder
165 password
= tivo_mak
#TiVo MAK
167 r
=urllib2
.Request(theurl
)
168 auth_handler
= urllib2
.HTTPDigestAuthHandler()
169 auth_handler
.add_password('TiVo DVR', tivoIP
, 'tivo', password
)
170 opener
= urllib2
.build_opener(auth_handler
)
171 urllib2
.install_opener(opener
)
173 if theurl
in tivo_cache
: #check to see if we have accessed this page before
174 if tivo_cache
[theurl
]['thepage'] == '' or (time
.time() - tivo_cache
[theurl
]['thepage_time']) >= 60: #if page is empty or old then retreive it
176 handle
= urllib2
.urlopen(r
)
178 handler
.send_response(200)
179 handler
.end_headers()
180 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'redirect.tmpl'))
183 t
.url
= '/TiVoConnect?Command=NPL&Container=' + cname
184 t
.text
= '<h3>Unable to Connect to TiVo.</h3> <br>pyTivo was unable to connect to the TiVo at ' + tivoIP
+\
185 '<br>This most likely caused by an incorrect Media Access Key. Please return to the ToGo page and double check your Media Access Key.' +\
186 '<br> The <a href="/TiVoConnect?Command=NPL&Container='+ cname
+ '"> ToGo</a> page will reload in 20 seconds.'
187 handler
.wfile
.write(t
)
189 tivo_cache
[theurl
]['thepage'] = handle
.read()
190 tivo_cache
[theurl
]['thepage_time'] = time
.time()
193 handle
= urllib2
.urlopen(r
)
195 handler
.send_response(200)
196 handler
.end_headers()
197 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'redirect.tmpl'))
200 t
.url
= '/TiVoConnect?Command=NPL&Container=' + cname
201 t
.text
= '<h3>Unable to Connect to TiVo.</h3> <br>pyTivo was unable to connect to the TiVo at ' + tivoIP
+\
202 '<br>This most likely caused by an incorrect Media Access Key. Please return to the ToGo page and double check your Media Access Key.' +\
203 '<br> The <a href="/TiVoConnect?Command=NPL&Container='+ cname
+ '"> ToGo</a> page will reload in 20 seconds.'
204 handler
.wfile
.write(t
)
206 tivo_cache
[theurl
] = {}
207 tivo_cache
[theurl
]['thepage'] = handle
.read()
208 tivo_cache
[theurl
]['thepage_time'] = time
.time()
210 xmldoc
= minidom
.parseString(tivo_cache
[theurl
]['thepage'])
211 items
= xmldoc
.getElementsByTagName('Item')
216 entry
['Title'] = item
.getElementsByTagName("Title")[0].firstChild
.data
217 entry
['ContentType'] = item
.getElementsByTagName("ContentType")[0].firstChild
.data
218 if (len(item
.getElementsByTagName("UniqueId")) >= 1):
219 entry
['UniqueId'] = item
.getElementsByTagName("UniqueId")[0].firstChild
.data
220 if entry
['ContentType'] == 'x-tivo-container/folder':
221 entry
['TotalItems'] = item
.getElementsByTagName("TotalItems")[0].firstChild
.data
222 entry
['LastChangeDate'] = item
.getElementsByTagName("LastChangeDate")[0].firstChild
.data
223 entry
['LastChangeDate'] = time
.strftime("%b %d, %Y", time
.localtime(int(entry
['LastChangeDate'], 16)))
225 link
= item
.getElementsByTagName("Links")[0]
226 if (len(link
.getElementsByTagName("CustomIcon")) >= 1):
227 entry
['Icon'] = link
.getElementsByTagName("CustomIcon")[0].getElementsByTagName("Url")[0].firstChild
.data
228 if (len(link
.getElementsByTagName("Content")) >= 1):
229 entry
['Url'] = link
.getElementsByTagName("Content")[0].getElementsByTagName("Url")[0].firstChild
.data
230 parse_url
= urlparse(entry
['Url'])
231 entry
['Url'] = quote('http://' + parse_url
[1].split(':')[0] + parse_url
[2] + "?" + parse_url
[4])
232 keys
= ['SourceSize', 'Duration', 'CaptureDate', 'EpisodeTitle', 'Description', 'SourceChannel', 'SourceStation']
235 entry
[key
] = item
.getElementsByTagName(key
)[0].firstChild
.data
238 entry
['SourceSize'] = "%.3f GB" % float(float(entry
['SourceSize'])/(1024*1024*1024))
239 entry
['Duration'] = str(int(entry
['Duration'])/(60*60*1000)).zfill(2) + ':' \
240 + str((int(entry
['Duration'])%(60*60*1000))/(60*1000)).zfill(2) + ':' \
241 + str((int(entry
['Duration'])/1000)%60).zfill(2)
242 entry
['CaptureDate'] = time
.strftime("%b %d, %Y", time
.localtime(int(entry
['CaptureDate'], 16)))
249 subcname
= query
['Container'][0]
250 cname
= subcname
.split('/')[0]
251 handler
.send_response(200)
252 handler
.end_headers()
253 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'npl.tmpl'))
256 t
.tivo_mak
= tivo_mak
257 t
.togo_path
= togo_path
258 t
.tivos
= handler
.tivos
264 handler
.wfile
.write(t
)
266 def get_tivo_file(self
, url
, mak
, tivoIP
, outfile
):
268 cj
= cookielib
.LWPCookieJar()
270 r
=urllib2
.Request(url
)
271 auth_handler
= urllib2
.HTTPDigestAuthHandler()
272 auth_handler
.add_password('TiVo DVR', tivoIP
, 'tivo', mak
)
273 opener
= urllib2
.build_opener(urllib2
.HTTPCookieProcessor(cj
), auth_handler
)
274 urllib2
.install_opener(opener
)
277 handle
= urllib2
.urlopen(r
)
279 #If we get "Too many transfers error" try a second time. For some reason
280 #urllib2 does not properly close connections when a transfer is canceled.
283 handle
= urllib2
.urlopen(r
)
285 status
[url
]['running'] = False
286 status
[url
]['error'] = e
.code
289 status
[url
]['running'] = False
290 status
[url
]['error'] = e
.code
293 f
= open(outfile
, 'wb')
295 start_time
= time
.time()
296 output
= handle
.read(1024)
297 while status
[url
]['running'] and output
!= '':
300 if ((time
.time() - start_time
) >= 5):
301 status
[url
]['rate'] = int(kilobytes
/(time
.time() - start_time
))
303 start_time
= time
.time()
304 output
= handle
.read(1024)
305 status
[url
]['running'] = False
310 def ToGo(self
, handler
, query
):
311 subcname
= query
['Container'][0]
312 cname
= subcname
.split('/')[0]
313 for name
, data
in config
.getShares():
315 if 'tivo_mak' in data
:
316 tivo_mak
= data
['tivo_mak']
319 if 'togo_path' in data
:
320 togo_path
= data
['togo_path']
323 if tivo_mak
!= "" and togo_path
!= "":
324 parse_url
= urlparse(str(query
['Url'][0]))
325 theurl
= 'http://' + parse_url
[1].split(':')[0] + parse_url
[2] + "?" + parse_url
[4]
326 password
= tivo_mak
#TiVo MAK
327 tivoIP
= query
['TiVo'][0]
328 name
= unquote(parse_url
[2])[10:300].split('.')
329 name
.insert(-1," - " + unquote(parse_url
[4]).split("id=")[1] + ".")
330 outfile
= os
.path
.join(togo_path
, "".join(name
))
332 status
[theurl
] = {'running':True, 'error':'', 'rate':'', 'finished':False}
334 thread
.start_new_thread(Admin
.get_tivo_file
, (self
, theurl
, password
, tivoIP
, outfile
))
336 handler
.send_response(200)
337 handler
.end_headers()
338 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'redirect.tmpl'))
341 t
.url
= '/TiVoConnect?Command=NPL&Container=' + cname
+ '&TiVo=' + query
['TiVo'][0] + '&Folder=' + query
['Folder'][0]
342 t
.text
= '<h3>Transfer Initiated.</h3> <br>You selected transfer has been initiated.'+\
343 '<br> The <a href="/TiVoConnect?Command=NPL&Container='+ cname
+ '&TiVo=' + query
['TiVo'][0] + '&Folder=' + query
['Folder'][0] +'"> ToGo</a> page will reload in 3 seconds.'
344 handler
.wfile
.write(t
)
346 handler
.send_response(200)
347 handler
.end_headers()
348 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'redirect.tmpl'))
351 t
.url
= '/TiVoConnect?Command=NPL&Container=' + cname
+ '&TiVo=' + query
['TiVo'][0] + '&Folder=' + query
['Folder'][0]
352 t
.text
= '<h3>Missing Data.</h3> <br>You must set both "tivo_mak" and "togo_path" before using this function.'+\
353 '<br> The <a href="/TiVoConnect?Command=NPL&Container='+ cname
+ '&TiVo=' + query
['TiVo'][0] + '&Folder=' + query
['Folder'][0] +'"> ToGo</a> page will reload in 10 seconds.'
354 handler
.wfile
.write(t
)
356 def ToGoStop(self
, handler
, query
):
357 parse_url
= urlparse(str(query
['Url'][0]))
358 theurl
= 'http://' + parse_url
[1].split(':')[0] + parse_url
[2] + "?" + parse_url
[4]
360 status
[theurl
]['running'] = False
362 subcname
= query
['Container'][0]
363 cname
= subcname
.split('/')[0]
364 handler
.send_response(200)
365 handler
.end_headers()
366 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'redirect.tmpl'))
369 t
.url
= '/TiVoConnect?Command=NPL&Container=' + cname
+ '&TiVo=' + query
['TiVo'][0] + '&Folder=' + query
['Folder'][0]
370 t
.text
= '<h3>Transfer Stopped.</h3> <br>Your transfer has been stopped.'+\
371 '<br> The <a href="/TiVoConnect?Command=NPL&Container='+ cname
+ '&TiVo=' + query
['TiVo'][0] + '&Folder=' + query
['Folder'][0] +'"> ToGo</a> page will reload in 3 seconds.'
372 handler
.wfile
.write(t
)
375 def SaveNPL(self
, handler
, query
):
376 config
= ConfigParser
.ConfigParser()
377 config
.read(config_file_path
)
378 if 'tivo_mak' in query
:
379 config
.set(query
['Container'][0], 'tivo_mak', query
['tivo_mak'][0])
380 if 'togo_path' in query
:
381 config
.set(query
['Container'][0], 'togo_path', query
['togo_path'][0])
382 f
= open(config_file_path
, "w")
386 subcname
= query
['Container'][0]
387 cname
= subcname
.split('/')[0]
388 handler
.send_response(200)
389 handler
.end_headers()
390 t
= Template(file=os
.path
.join(SCRIPTDIR
,'templates', 'redirect.tmpl'))
393 t
.url
= '/TiVoConnect?last_page=NPL&Command=Reset&Container=' + cname
394 t
.text
= '<h3>Your Settings have been saved.</h3> <br>You settings have been saved to the pyTivo.conf file.'+\
395 'pyTivo will now do a <b>Soft Reset</b> to allow these changes to take effect.'+\
396 '<br> The <a href="/TiVoConnect?last_page=NPL&Command=Reset&Container='+ cname
+'"> Reset</a> will occur in 2 seconds.'
397 handler
.wfile
.write(t
)