remove print statements
[tivo4tiny.git] / tivo4tiny
blobe0546dba4069d46c64a655c980d20607868850f4
1 #!/usr/bin/python
3 # ---------------------
4 # Tivo 4 Tiny
5 # (c) 2008 Billy Charlton, San Francisco CA, USA
6 # Licensed under the GNU GPL Version 3.0 http://www.gnu.org
8 # Dependencies:
9 # curl, tivodecode, mencoder, python-gtk2, gconf, avahi-utils
11 # SAMPLE CMDs
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:
17 """
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]
22 port = [443]
23 txt = ["TSN=5950001C0202DDC" "platform=tcd/Series2" "swversion=9.1-01-2-595" "path=/TiVoConnect?Command=QueryContainer&Container=%2FNowPlaying" "protocol=https"]
24 """
26 import pygtk
27 pygtk.require('2.0')
28 import gtk
29 import gtk.glade
30 import gconf
31 import xml.dom.minidom
32 import os
33 import subprocess
34 import datetime
35 import threading
36 import tempfile
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.
42 CONVERSIONS = [
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"]
49 RAWITEM = 3
51 # Tree model columns
52 S_CHANNEL = 0
53 S_TITLE = 1
54 S_DATE = 2
55 S_LENGTH = 3
56 S_ACTION = 4
57 S_URL = 5
58 S_SIZE = 6
59 S_SIMPLETITLE=7
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.
65 def threaded(f):
66 def wrapper(*args):
67 t = threading.Thread(target=f, args=args)
68 t.start()
69 return wrapper
72 # This helper class keeps track of file transfer progress, one for each transfer thread.
73 class ProgressKeeper:
74 def __init__(self):
75 self.progress = 0
77 class TivoSucker:
78 # Fetch the shows from the selected Tivo.
79 @threaded
80 def populateShows(self):
81 def getText(nodelist):
82 rc = ""
83 for node in nodelist:
84 if node.nodeType == node.TEXT_NODE:
85 rc = rc + node.data
86 return rc
88 # Which Tivo is selected?
89 item = self.ComboWhichTivo.get_active()
90 if (item < 0): return
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
100 # print pullshowsCmd
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.
118 try:
119 xmldatafile = os.popen(pullshowsCmd,'r')
120 dom = xml.dom.minidom.parse(xmldatafile)
121 if (dom == None):
122 # uh oh didn't find a tivo!
123 return
124 except:
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()
131 return
133 # Now let's walk through the file.
134 shows = dom.getElementsByTagName("Item")
136 for show in shows:
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,
148 # (but not both):
149 episode = details.getElementsByTagName("EpisodeTitle")
150 if (episode.length > 0):
151 title += "<small><span foreground=\"#007000\">"+getText(episode[0].childNodes)+"</span></small>"
152 else:
153 description = ""
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>"
161 station = ""
162 ss = details.getElementsByTagName("SourceStation")
163 if (ss.length > 0): station = getText(ss[0].childNodes) + "\n"
165 showsize = ""
166 ss = details.getElementsByTagName("SourceSize")
167 if (ss.length > 0): showsize = getText(ss[0].childNodes)
169 duration = hours = minutes = ""
170 d = details.getElementsByTagName("Duration")
171 if (d.length > 0):
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)
177 cdate = ""
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):
207 total = 0.0
208 denom = 0.0
209 allfinished = True
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):
214 allfinished = False
216 # if they're all finished, erase the whole queue and set everything to blank.
217 if (allfinished):
218 self.Transfers = []
219 self.progressBarAll.set_fraction(0)
220 self.progressBarAll.set_sensitive(False)
221 self.labelProgress.set_text("")
222 return
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:
227 denom += 1
228 if (each.progress > -1): total += each.progress
229 else: total += 1
231 self.progressBarAll.set_sensitive(True)
232 self.progressBarAll.set_fraction(total/denom)
235 # Fetch a show; each transfer runs in its own thread
236 @threaded
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()
250 if (item < 0):
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
255 fractionscaler = 2.0
256 if (item==RAWITEM):
257 fractionscaler = 1.0
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)
268 k = ""
269 c = 'x'
270 while (c != ""):
271 c = cmdout.read(1)
272 if (c=='\r'):
273 update = k[15:21]
274 val = 0
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])
279 if (val > 0):
280 frac = val / showsize / fractionscaler
281 gtk.gdk.threads_enter()
282 self.labelProgress.set_text("Receiving "+k[15:21]+"...")
283 gtk.gdk.threads_leave()
285 pk.progress = frac
286 self.updateProgress()
287 k = c
288 else:
289 k = k + c
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):
302 pk.progress = 0.50
303 self.updateProgress()
305 # Decode Tivo stream
306 cmd = "nice -n 10 tivodecode -m "+self.mak+" -o "+filedecoded.name+" "+filefetch.name
307 cmdin, cmdout = os.popen4(cmd)
309 k = "line"
310 while (k != ""):
311 k = cmdout.readline()
312 print k
314 # Prep for final transcoding!
315 filefetch.close()
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:
332 else:
333 cmd = "nice -n 10 mencoder "+filedecoded.name+" -o \"" + filefinal + deviceformat + "\" -of avi " + deviceoptions
335 print cmd
336 cmdin, cmdout = os.popen4(cmd)
338 k = "line"
339 while (k != ""):
340 k = cmdout.readline()
341 if (k[:4]=="Pos:"):
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()
352 # All done!
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"):
362 filedecoded.close()
364 self.ConverterLock.release()
367 # Use Avahi to populate list of Tivos on the network.
368 def findTivos(self):
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)
378 # Run avahi-browse
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")
383 k = "line"
384 foundativo = ""
385 ipaddr=""
387 # If there's already a Tivo selected in gconf, use that one.
388 lastTivo = self.gconfclient.get_string ("/apps/tivo4tiny/which_tivo")
390 while (k != ""):
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])
404 lastTivo = None
405 else:
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):
421 gtk.main_quit()
422 return False
424 # Prefs dialog should remain in existence even after closing/hiding
425 def prefs_delete_event(self, widget, event, data=None):
426 return True
428 def showAddWindow(self):
429 self.addWindow.show_all()
431 def showPrefsWindow(self, widget):
432 self.PrefsWindow.show_all()
434 def __init__(self):
435 # Some threading variables
436 self.FetchLock = threading.Lock()
437 self.ConverterLock = threading.Lock()
438 self.Transfers = []
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")
462 if (self.window):
463 self.window.connect("destroy", gtk.main_quit)
465 # Preference vars
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)
494 else:
495 self.EntryFolder.set_text(self.destdir)
497 if (self.videoformat == None):
498 self.ComboFormat.set_active(0)
499 else:
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)
513 # set the columns
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()
578 if (item < 0):
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()
600 print "add",entry
602 if (len(entry)>0):
603 tivo = entry
604 ipaddr = entry
606 # then create an entry for that item
607 self.ComboWhichTivo.get_model().prepend([tivo,ipaddr])
608 self.ComboWhichTivo.set_active(0)
609 # Save the choice
610 self.gconfclient.set_string ("/apps/tivo4tiny/which_tivo", tivo)
611 # And, now that it's all done, let's repopulate the show list.
612 self.populateShows()
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
627 self.showAddWindow()
629 else:
630 # Save the choice
631 self.gconfclient.set_string ("/apps/tivo4tiny/which_tivo", tivo)
632 # And, now that it's all done, let's repopulate the show list.
633 self.populateShows()
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()
642 def chooseFolderClicked(self, widget):
643 print "Not yet implemented"
645 # -------------------------------------------------------
646 # Weird little support functions, from the web
648 # Hex number converter
649 from string import upper
650 def str2int(s, base):
651 accum = 0
653 # ASCII/UNICODE values of these characters
654 zero = ord('0')
655 nine = ord('9')
656 alpha = ord('A')
657 omega = ord('Z')
659 for n in range(len(s)):
660 numeral = ord(upper(s[-(n+1)]))
662 if numeral in range(zero, nine+1):
663 accum += (numeral - zero) * (base ** n)
664 elif (numeral in range(alpha, omega)) and (base > 10):
665 accum += ((numeral - alpha) + 10) * (base ** n)
666 else:
667 # invalid number
668 accum = 0
669 break
670 return accum
673 # -----------------------------------------------------
674 # Run it!
675 sucker = TivoSucker()
676 sucker.findTivos()
677 # sucker.populateShows();
679 gtk.gdk.threads_init()
680 gtk.main()