3 # ---------------------
5 # (c) 2008 Billy Charlton, San Francisco CA, USA
6 # Licensed under the GNU GPL Version 3.0 http://www.gnu.org
9 # curl, tivodecode, mencoder, python-gtk2, gconf, avahi-utils
12 # curl -k -c /tmp/curl-cookies.txt --digest -u tivo:6060730506 https://192.168.0.201/TiVoConnect?Command=QueryContainer\&Container=\%2FNowPlaying > x
13 # mencoder -vf scale=-10:240 <infile> -o <of> -of avi -ofps 15 -ovc lavc -oac lavc -lavcopts vcodec=mpeg4:vbitrate=230:acodec=mp3:abitrate=64
14 # avahi-browse -l -r -t _tivo-videos._tcp
16 # SAMPLE AVAHI OUTPUT:
18 + ath0 IPv4 Pocky3DVD _tivo-videos._tcp local
19 = ath0 IPv4 Pocky3DVD _tivo-videos._tcp local
20 hostname = [DVR-2DDC.local]
21 address = [192.168.0.201]
23 txt = ["TSN=5950001C0202DDC" "platform=tcd/Series2" "swversion=9.1-01-2-595" "path=/TiVoConnect?Command=QueryContainer&Container=%2FNowPlaying" "protocol=https"]
31 import xml
.dom
.minidom
38 # Available video translations, appended to mencoder command line.
39 # Feel free to add more. See mencoder man page for explanation of these arcane codes.
40 # BB Curve screen is portrait, so scale 240:-10. Others are landscape, so scale -10:240.
41 # BB Curve can only handle 15fps well. Others can attempt 30fps, so just use native fps.
43 ["iPod/iPhone/BB Curve" , ".avi", "-vf pp=md,scale=-10:240 -ovc lavc -oac lavc -lavcopts vcodec=mpeg4:vbitrate=230:acodec=libmp3lame:abitrate=128"],
44 ["BlackBerry Pearl" , ".avi", "-vf pp=md,scale=240:-10 -ofps 15 -ovc lavc -oac lavc -lavcopts vcodec=mpeg4:vbitrate=230:acodec=libmp3lame:abitrate=128"],
45 ["Full-size MPEG-4" , ".avi", "-vf scale=0:0 -ovc lavc -oac lavc -lavcopts vcodec=mpeg4:acodec=libmp3lame:abitrate=192"],
46 ["Raw Copy (MPEG-2)" , ".mpg", "xxx"]
61 # threaded function support. This is a function "decorator" that makes any function
62 # defined with the @threaded prefix become its own thread instead of a function in main.
63 # We'll use this to do off-main processing such as copying & transcoding.
67 t
= threading
.Thread(target
=f
, args
=args
)
72 # This helper class keeps track of file transfer progress, one for each transfer thread.
78 # Fetch the shows from the selected Tivo.
80 def populateShows(self
):
81 def getText(nodelist
):
84 if node
.nodeType
== node
.TEXT_NODE
:
88 # Which Tivo is selected?
89 item
= self
.ComboWhichTivo
.get_active()
92 tivo
= self
.ComboWhichTivo
.get_model()[item
][0]
93 ipaddr
= self
.ComboWhichTivo
.get_model()[item
][1]
95 # Suck data from tivo!
96 credentials
= "tivo:" + self
.mak
97 fetchurl
= 'https://' + ipaddr
+ '/TiVoConnect?Command=QueryContainer\&Container=\%2FNowPlaying\&Recurse=Yes'
99 pullshowsCmd
= "curl -s -k --digest -u " + credentials
+ " " + fetchurl
102 gtk
.gdk
.threads_enter()
103 self
.ListView
.set_model(None)
104 self
.ListStore
.clear()
105 self
.progressBarAll
.set_fraction(0)
106 self
.progressBarAll
.set_sensitive(False)
107 self
.labelStatus
.set_markup("<i>Retrieving show listings from " + tivo
+ "...</i>")
108 # self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
109 # Disable update button
110 self
.buttonUpdate
.set_sensitive(False)
111 self
.ComboWhichTivo
.set_sensitive(False)
112 gtk
.gdk
.threads_leave()
114 # Need to test this for success -- curl sends an error msg
115 # but right now i don't know how to trap it.
116 # May need Popen class instead of os.popen method.
119 xmldatafile
= os
.popen(pullshowsCmd
,'r')
120 dom
= xml
.dom
.minidom
.parse(xmldatafile
)
122 # uh oh didn't find a tivo!
125 # Something went wrong. Boo! Well, enable GUI and just give up.
126 gtk
.gdk
.threads_enter()
127 self
.buttonUpdate
.set_sensitive(True)
128 self
.ComboWhichTivo
.set_sensitive(True)
129 self
.labelStatus
.set_markup("<i>Could not connect to " + tivo
+ ".</i>")
130 gtk
.gdk
.threads_leave()
133 # Now let's walk through the file.
134 shows
= dom
.getElementsByTagName("Item")
137 content
= show
.getElementsByTagName("Content")[0]
138 url
= content
.getElementsByTagName("Url")[0]
139 urltext
= getText(url
.childNodes
)
141 details
= show
.getElementsByTagName("Details")[0]
143 t
= details
.getElementsByTagName("Title")[0]
144 simpletitle
= getText(t
.childNodes
)
145 title
= '<b>' + simpletitle
+ '</b>\n'
147 # Append either the episode or the long description to the title,
149 episode
= details
.getElementsByTagName("EpisodeTitle")
150 if (episode
.length
> 0):
151 title
+= "<small><span foreground=\"#007000\">"+getText(episode
[0].childNodes
)+"</span></small>"
154 desc
= details
.getElementsByTagName("Description")
155 if (desc
.length
> 0):
156 description
= getText(desc
[0].childNodes
)
157 description
= description
[:description
.find("Copyright Tribune Media")]
158 if (len(description
)>40): description
= description
[:40]+"..."
159 title
+= "<small><span foreground=\"#007000\">"+description
+"</span></small>"
162 ss
= details
.getElementsByTagName("SourceStation")
163 if (ss
.length
> 0): station
= getText(ss
[0].childNodes
) + "\n"
166 ss
= details
.getElementsByTagName("SourceSize")
167 if (ss
.length
> 0): showsize
= getText(ss
[0].childNodes
)
169 duration
= hours
= minutes
= ""
170 d
= details
.getElementsByTagName("Duration")
172 duration
= float(getText(d
[0].childNodes
))/60000 + 0.5
173 hours
= int(duration
/60)
174 minutes
= int (duration
- hours
*60)
175 duration
= '%s:%02d' % (hours
,minutes
)
178 desc
= details
.getElementsByTagName("CaptureDate")
179 if (desc
.length
> 0):
180 hextime
= getText(desc
[0].childNodes
)[2:]
181 unixtime
= str2int(hextime
,16) + 30
182 dd
= datetime
.datetime
.fromtimestamp(unixtime
)
183 cdate
= dd
.strftime("%Y-%m-%d\n<small>%I:%M %p %A</small>")
185 # Add to the liststore! Woot!!
186 self
.ListStore
.append([station
, title
, cdate
, duration
, "Transfer", urltext
, showsize
, simpletitle
])
188 # All done adding shows, now display the tree.
189 gtk
.gdk
.threads_enter()
190 self
.ListView
.set_model(self
.ListStore
)
191 self
.buttonUpdate
.set_sensitive(True)
192 self
.ComboWhichTivo
.set_sensitive(True)
193 self
.labelStatus
.set_markup("<i>Idle. Double-click a show to begin transfer.</i>")
194 # arrow = gtk.gdk.Cursor(gtk.gdk.ARROW)
195 # self.window.set_cursor(arrow)
196 gtk
.gdk
.threads_leave()
199 # Clicked on start-transfer or double-clicked row
200 def addTransfer(self
, tv
, pa
, col
):
201 show
= self
.ListStore
.get_iter(pa
)
202 pk
= ProgressKeeper()
203 self
.Transfers
.append(pk
)
204 self
.startTransfer(show
, pk
)
206 def updateProgress(self
):
211 # First check if every transfer is complete. progress of each is set to -1 if done.
212 for each
in self
.Transfers
:
213 if (each
.progress
> -1):
216 # if they're all finished, erase the whole queue and set everything to blank.
219 self
.progressBarAll
.set_fraction(0)
220 self
.progressBarAll
.set_sensitive(False)
221 self
.labelProgress
.set_text("")
224 # But if they're not all done, sum up the fraction for each. Use 1.0 for any in
225 # the queue that are already finished, until all are done. Otherwise things go freaky.
226 for each
in self
.Transfers
:
228 if (each
.progress
> -1): total
+= each
.progress
231 self
.progressBarAll
.set_sensitive(True)
232 self
.progressBarAll
.set_fraction(total
/denom
)
235 # Fetch a show; each transfer runs in its own thread
237 def startTransfer(self
, show
, pk
):
238 # Get info about the show
239 url
= self
.ListStore
.get_value(show
,S_URL
)
240 title
= self
.ListStore
.get_value(show
,S_SIMPLETITLE
)
241 showdate
= self
.ListStore
.get_value(show
,S_DATE
)
242 showsize
= int(self
.ListStore
.get_value(show
,S_SIZE
))
244 gtk
.gdk
.threads_enter()
245 self
.labelStatus
.set_markup("<i>Queued "+title
+".</i>")
246 gtk
.gdk
.threads_leave()
248 # Get video options from prefs panel
249 item
= self
.ComboFormat
.get_active()
251 # what to do if no format suggested?
252 item
= 0 # Use the default (iPod)
254 # This is used to scale the progress bar value
259 # Set up some temp files for file transfers
260 filefetch
= tempfile
.NamedTemporaryFile('w+b',-1,".mpg","tivo-")
261 filedecoded
= tempfile
.NamedTemporaryFile('w+b',-1,".mpg","tivo-")
263 fetchcmd
= "nice -n 2 curl -k -c /tmp/tivo-cookies.txt --digest -u tivo:"+self
.mak
+" \""+url
+"\" > " + filefetch
.name
265 self
.FetchLock
.acquire()
266 cmdin
, cmdout
= os
.popen4(fetchcmd
)
275 if (update
[-1:] == 'k'): val
= 1000 * float(update
[:-1])
276 if (update
[-1:] == 'M'): val
= 1000000 * float(update
[:-1])
277 if (update
[-1:] == 'G'): val
= 1000000000 * float(update
[:-1])
280 frac
= val
/ showsize
/ fractionscaler
281 gtk
.gdk
.threads_enter()
282 self
.labelProgress
.set_text("Receiving "+k
[15:21]+"...")
283 gtk
.gdk
.threads_leave()
286 self
.updateProgress()
291 # Done copying. Release file-transfer lock so next thread can start
292 self
.FetchLock
.release()
294 # And then grab converter lock so we can start the CPU-intensive work
295 self
.ConverterLock
.acquire()
297 gtk
.gdk
.threads_enter()
298 self
.labelProgress
.set_text("Decoding...")
299 gtk
.gdk
.threads_leave()
301 if (item
!= RAWITEM
):
303 self
.updateProgress()
306 cmd
= "nice -n 10 tivodecode -m "+self
.mak
+" -o "+filedecoded
.name
+" "+filefetch
.name
307 cmdin
, cmdout
= os
.popen4(cmd
)
311 k
= cmdout
.readline()
314 # Prep for final transcoding!
316 showdate
= showdate
.replace("\n","")
317 showdate
= showdate
.replace("<small>"," - ")
318 showdate
= showdate
.replace("</small>","")
319 title
= title
.replace("/","-")
321 filefinal
= self
.destdir
+"/" + title
+ " - " + showdate
323 devicename
= self
.ComboFormat
.get_model()[item
][0]
324 deviceformat
= self
.ComboFormat
.get_model()[item
][1]
325 deviceoptions
= self
.ComboFormat
.get_model()[item
][2]
327 # If we're just doing a raw copy, this part is easy:
328 if (item
== RAWITEM
):
329 cmd
= "mv "+filedecoded
.name
+ " \"" + filefinal
+ deviceformat
+ "\""
331 # But anything other than a raw copy needs a call to mencoder:
333 cmd
= "nice -n 10 mencoder "+filedecoded
.name
+" -o \"" + filefinal
+ deviceformat
+ "\" -of avi " + deviceoptions
336 cmdin
, cmdout
= os
.popen4(cmd
)
340 k
= cmdout
.readline()
342 pr
= k
[k
.find("("):k
.find("%")]
343 msg
= "Transcoding " + pr
+ "%)"
345 gtk
.gdk
.threads_enter()
346 self
.labelProgress
.set_text(msg
)
347 gtk
.gdk
.threads_leave()
349 pk
.progress
= float(pr
[1:])/200 + 0.5
350 self
.updateProgress()
353 pk
.progress
= -1 # Magic number indicates we're all finished
354 self
.updateProgress()
356 gtk
.gdk
.threads_enter()
357 self
.labelStatus
.set_markup("<i>Completed "+title
+".</i>")
358 gtk
.gdk
.threads_leave()
360 # Can't close the temp file if we already moved it ;-)
361 if (devicename
[:8] != "Raw Copy"):
364 self
.ConverterLock
.release()
367 # Use Avahi to populate list of Tivos on the network.
369 # Clear list of Tivos
370 self
.ComboWhichTivo
.set_model(None)
371 self
.ComboWhichTivo
.clear()
372 liststore
= gtk
.ListStore(str,str)
374 cell
= gtk
.CellRendererText()
375 self
.ComboWhichTivo
.pack_start(cell
, True)
376 self
.ComboWhichTivo
.add_attribute(cell
,'text',0)
379 cmd
= 'avahi-browse -l -r -t _tivo-videos._tcp'
380 cmdin
, cmdout
= os
.popen4(cmd
)
381 ## cmdout = open("/home/billy/dev/tivo4tiny/avahi.txt")
387 # If there's already a Tivo selected in gconf, use that one.
388 lastTivo
= self
.gconfclient
.get_string ("/apps/tivo4tiny/which_tivo")
391 k
= cmdout
.readline()
392 # First check for '=' which means a new Tivo
393 if (k
.find("=") == 0):
394 foundativo
= k
.split(' ')[3]
396 # Then check for 'address' line which has the IP address
397 if (k
.find("address")>-1):
398 ipaddr
= k
[1+k
.find('['):k
.find(']')]
400 # Add this Tivo to combobox.
401 # If this is the one from the last time we ran, be sure it's the first choice
402 if (foundativo
== lastTivo
):
403 liststore
.prepend([foundativo
,ipaddr
])
406 liststore
.append([foundativo
,ipaddr
])
408 # Finally, add the last tivo if it hasn't been found already
409 if (lastTivo
!= None):
410 liststore
.prepend([lastTivo
,lastTivo
])
412 liststore
.append(["Manual...","0.0.0.0"])
413 self
.ComboWhichTivo
.set_model(liststore
)
415 # if there are any Tivos found, set first item to active selection,
416 if (len(liststore
) >1): # >1 because "Manual..." entry is always there
417 self
.ComboWhichTivo
.set_active(0)
419 # close the window and quit
420 def delete_event(self
, widget
, event
, data
=None):
424 # Prefs dialog should remain in existence even after closing/hiding
425 def prefs_delete_event(self
, widget
, event
, data
=None):
428 def showAddWindow(self
):
429 self
.addWindow
.show_all()
431 def showPrefsWindow(self
, widget
):
432 self
.PrefsWindow
.show_all()
435 # Some threading variables
436 self
.FetchLock
= threading
.Lock()
437 self
.ConverterLock
= threading
.Lock()
440 # Set Glade file and init base window
441 self
.gladefile
= "/usr/share/tivo4tiny/tivo4tiny.glade"
442 self
.wTree
= gtk
.glade
.XML(self
.gladefile
)
444 self
.labelProgress
= self
.wTree
.get_widget("labelProgress")
445 self
.labelStatus
= self
.wTree
.get_widget("labelStatus")
446 self
.buttonUpdate
= self
.wTree
.get_widget("ButtonUpdateList")
447 self
.ComboWhichTivo
= self
.wTree
.get_widget("ComboWhichTivo")
448 self
.progressBarAll
= self
.wTree
.get_widget("ProgressBarAll")
449 self
.progressBarAll
.set_sensitive(False)
451 #Get the main windows, and connect the "destroy" events
452 self
.addWindow
= self
.wTree
.get_widget("dialogAddTivo")
453 self
.addEntry
= self
.wTree
.get_widget("entryAddTivo")
454 self
.addWindow
.connect("delete_event", self
.prefs_delete_event
)
455 self
.addWindow
.set_deletable(False)
457 self
.PrefsWindow
= self
.wTree
.get_widget("PrefsDialog")
458 self
.PrefsWindow
.connect("delete_event", self
.prefs_delete_event
)
459 self
.PrefsWindow
.set_deletable(False)
461 self
.window
= self
.wTree
.get_widget("MainWindow")
463 self
.window
.connect("destroy", gtk
.main_quit
)
466 self
.EntryMak
= self
.wTree
.get_widget("EntryMak")
467 self
.EntryFolder
= self
.wTree
.get_widget("EntryFolder")
468 self
.ComboFormat
= self
.wTree
.get_widget("ComboFormat")
470 # Populate the video conversion options
471 self
.ComboFormat
.set_model(None)
472 self
.ComboFormat
.clear()
473 liststore
= gtk
.ListStore(str,str,str)
474 cell
= gtk
.CellRendererText()
475 self
.ComboFormat
.pack_start(cell
, True)
476 self
.ComboFormat
.add_attribute(cell
,'text',0)
477 for each
in CONVERSIONS
:
478 liststore
.append([each
[0], each
[1], each
[2]])
479 self
.ComboFormat
.set_model(liststore
)
481 # Initialize GConf and pull prefs from gconf database
482 self
.gconfclient
= gconf
.client_get_default ();
483 self
.gconfclient
.add_dir ("/apps/tivo4tiny",gconf
.CLIENT_PRELOAD_NONE
)
485 self
.mak
= self
.gconfclient
.get_string ("/apps/tivo4tiny/media_access_key")
486 self
.destdir
= self
.gconfclient
.get_string ("/apps/tivo4tiny/dest_dir")
487 self
.videoformat
= self
.gconfclient
.get_int("/apps/tivo4tiny/video_format")
489 if (self
.mak
!= None): self
.EntryMak
.set_text(self
.mak
)
490 if (self
.destdir
== None):
491 print "No destination set. Setting to HOME."
492 self
.destdir
= os
.path
.expanduser("~")
493 self
.EntryFolder
.set_text(self
.destdir
)
495 self
.EntryFolder
.set_text(self
.destdir
)
497 if (self
.videoformat
== None):
498 self
.ComboFormat
.set_active(0)
500 self
.ComboFormat
.set_active(self
.videoformat
)
503 # create a ListStore with several columns to use as the model
504 # Icon, Show, Date, Length, Action, Link
505 # (this link will need to change later)
507 self
.ListStore
= gtk
.ListStore(str, str, str, str, str, str, str, str)
509 # create the TreeView using treestore
510 self
.ListView
= self
.wTree
.get_widget("ListView")
511 self
.ListView
.set_model(None)
514 cell
= gtk
.CellRendererText()
516 ColumnIcon
= gtk
.TreeViewColumn('Channel')
517 ColumnIcon
.pack_start(cell
,True)
518 ColumnIcon
.set_attributes(cell
, text
=0)
519 ColumnIcon
.set_sort_column_id(0)
521 ColumnShow
= gtk
.TreeViewColumn('Show')
522 ColumnShow
.pack_start(cell
,True)
523 ColumnShow
.set_attributes(cell
, markup
=1)
524 ColumnShow
.set_sort_column_id(1)
525 ColumnShow
.set_expand(True)
527 ColumnDate
= gtk
.TreeViewColumn('Date')
528 ColumnDate
.pack_start(cell
,True)
529 ColumnDate
.set_attributes(cell
, markup
=2)
530 ColumnDate
.set_sort_column_id(2)
532 ColumnLength
= gtk
.TreeViewColumn('Length')
533 ColumnLength
.pack_start(cell
,True)
534 ColumnLength
.set_attributes(cell
, text
=3)
535 ColumnLength
.set_sort_column_id(3)
537 ColumnAction
= gtk
.TreeViewColumn('Action')
538 ColumnAction
.pack_start(cell
,True)
539 ColumnAction
.set_attributes(cell
, text
=4)
541 self
.ListView
.append_column(ColumnIcon
)
542 self
.ListView
.append_column(ColumnShow
)
543 self
.ListView
.append_column(ColumnDate
)
544 self
.ListView
.append_column(ColumnLength
)
545 self
.ListView
.append_column(ColumnAction
)
546 self
.ListStore
.set_sort_column_id(1,gtk
.SORT_ASCENDING
)
548 # Connect glade events
549 dic
= { "on_ListView_row_activated" : self
.addTransfer
,
550 "on_MainWindow_destroy" : gtk
.main_quit
,
551 "on_PrefsDialog_destroy" : self
.closePrefsWindow
,
552 "on_dialogAddTivo_destroy" : self
.closeAddWindow
,
553 "on_dialogAddTivo_close" : self
.closeAddWindow
,
554 "on_ButtonShowPrefs_clicked" : self
.showPrefsWindow
,
555 "on_EntryMak_changed" : self
.entryMakChanged
,
556 "on_ComboFormat_changed" : self
.comboFormatChanged
,
557 "on_ComboWhichTivo_changed" : self
.buttonWhichTivoClicked
,
558 "on_EntryFolder_changed" : self
.entryFolderChanged
,
559 "on_ButtonPrefsOK_clicked" : self
.prefsOKClicked
,
560 "on_btnAddTivoOK_clicked" : self
.addManualTivo
,
561 "on_btnAddTivoCancel_clicked" : self
.closeAddWindow
,
562 "on_ButtonChooseFolder_clicked" : self
.chooseFolderClicked
,
563 "on_ButtonUpdateList_clicked" : self
.buttonWhichTivoClicked
565 self
.wTree
.signal_autoconnect(dic
)
567 self
.window
.show_all()
568 if (self
.mak
==None): self
.PrefsWindow
.show_all()
571 def entryMakChanged(self
, widget
):
572 self
.mak
= self
.EntryMak
.get_text()
573 # print "MAK changed! ", self.mak
574 self
.gconfclient
.set_string ("/apps/tivo4tiny/media_access_key",self
.mak
)
576 def comboFormatChanged(self
, widget
):
577 item
= self
.ComboFormat
.get_active()
579 # what to do if no format suggested?
580 item
= 0 # Use the default (iPod)
581 self
.gconfclient
.set_int("/apps/tivo4tiny/video_format", item
)
583 # devicename = self.ComboFormat.get_model()[item][0]
584 # deviceoptions = self.ComboFormat.get_model()[item][1]
585 # print "Using",devicename, "options:",deviceoptions
587 def entryFolderChanged(self
, widget
):
588 self
.destdir
= self
.EntryFolder
.get_text()
589 self
.gconfclient
.set_string ("/apps/tivo4tiny/dest_dir",self
.destdir
)
591 def closePrefsWindow(self
, widget
):
592 self
.PrefsWindow
.hide_all()
594 def closeAddWindow(self
, widget
):
595 self
.addWindow
.hide_all()
597 # User typed in a manual IP address and clicked "OK"
598 def addManualTivo(self
,widget
):
599 entry
= self
.addEntry
.get_text()
606 # then create an entry for that item
607 self
.ComboWhichTivo
.get_model().prepend([tivo
,ipaddr
])
608 self
.ComboWhichTivo
.set_active(0)
610 self
.gconfclient
.set_string ("/apps/tivo4tiny/which_tivo", tivo
)
611 # And, now that it's all done, let's repopulate the show list.
614 self
.addWindow
.hide_all()
617 def buttonWhichTivoClicked(self
, widget
):
618 # Which Tivo is selected?
619 item
= self
.ComboWhichTivo
.get_active()
620 if (item
< 0): return
622 tivo
= self
.ComboWhichTivo
.get_model()[item
][0]
623 ipaddr
= self
.ComboWhichTivo
.get_model()[item
][1]
625 if (tivo
== "Manual..."):
626 # Ask what IP address to connect to
631 self
.gconfclient
.set_string ("/apps/tivo4tiny/which_tivo", tivo
)
632 # And, now that it's all done, let's repopulate the show list.
635 def prefsOKClicked(self
, widget
):
636 self
.PrefsWindow
.hide_all()
637 self
.buttonUpdate
.set_sensitive(True)
639 def addOKClicked(self
, widget
):
640 self
.addWindow
.hide_all()
643 def chooseFolderClicked(self
, widget
):
644 print "Not yet implemented"
646 # -------------------------------------------------------
647 # Weird little support functions, from the web
649 # Hex number converter
650 from string
import upper
651 def str2int(s
, base
):
654 # ASCII/UNICODE values of these characters
660 for n
in range(len(s
)):
661 numeral
= ord(upper(s
[-(n
+1)]))
663 if numeral
in range(zero
, nine
+1):
664 accum
+= (numeral
- zero
) * (base
** n
)
665 elif (numeral
in range(alpha
, omega
)) and (base
> 10):
666 accum
+= ((numeral
- alpha
) + 10) * (base
** n
)
674 # -----------------------------------------------------
676 sucker
= TivoSucker()
678 # sucker.populateShows();
680 gtk
.gdk
.threads_init()