Latest version
[rox-ripper.git] / ripper.py
blob0657cbccb88f983290f7f903713566eced67c2ae
1 """
2 ripper.py
3 GUI front-end to cdda2wav and lame.
5 Copyright 2004 Kenneth Hayber <khayber@socal.rr.com>
6 All rights reserved.
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License.
12 This program is distributed in the hope that it will be useful
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 """
23 import gtk, os, sys, signal, re, string, socket, time, popen2, threading, Queue
24 from random import Random
25 from threading import *
27 import rox
28 from rox import i18n, app_options, Menu, filer, InfoWin
29 from rox.options import Option
31 import PyCDDB, cd_logic, CDROM, genres
33 try:
34 import xattr
35 HAVE_XATTR = True
36 except:
37 HAVE_XATTR = False
39 _ = rox.i18n.translation(os.path.join(rox.app_dir, 'Messages'))
41 #Who am I and how did I get here?
42 APP_NAME = 'Ripper' #I could call it Mr. Giles, but that would be gay.
43 APP_PATH = rox.app_dir
46 #Options.xml processing
47 from rox import choices
48 choices.migrate(APP_NAME, 'hayber.us')
49 rox.setup_app_options(APP_NAME, site='hayber.us')
50 Menu.set_save_name(APP_NAME, site='hayber.us')
52 #assume that everyone puts their music in ~/Music
53 LIBRARY = Option('library', '~/Music')
55 #RIPPER options
56 RIPPER = Option('ripper', 'cdda2wav')
57 RIPPER_DEV = Option('ripper_dev', '/dev/cdrom')
58 RIPPER_LUN = Option('ripper_lun', 'ATAPI:0,1,0')
59 RIPPER_OPTS = Option('ripper_opts', '-x -H')
61 EJECT_AFTER_RIP = Option('eject_after_rip', '0')
63 #ENCODER options
64 ENCODER = Option('encoder', 'MP3')
66 MP3_ENCODER = Option('mp3_encoder', 'lame')
67 MP3_ENCODER_OPTS = Option('mp3_encoder_opts', '--vbr-new -b160 --nohist --add-id3v2')
69 OGG_ENCODER = Option('ogg_encoder', 'oggenc')
70 OGG_ENCODER_OPTS = Option('ogg_encoder_opts', '-q5')
72 #CDDB Server and Options
73 CDDB_SERVER = Option('cddb_server', 'http://freedb.freedb.org/~cddb/cddb.cgi')
75 rox.app_options.notify()
78 #Column indicies
79 COL_ENABLE = 0
80 COL_TRACK = 1
81 COL_TIME = 2
82 COL_STATUS = 3
85 # My gentoo python doesn't have universal line ending support compiled in
86 # and these guys (cdda2wav and lame) use CR line endings to pretty-up their output.
87 def myreadline(file):
88 '''Return a line of input using \r or \n as terminators'''
89 line = ''
90 while '\n' not in line and '\r' not in line:
91 char = file.read(1)
92 if char == '': return line
93 line += char
94 return line
97 def which(filename):
98 '''Return the full path of an executable if found on the path'''
99 if (filename == None) or (filename == ''):
100 return None
102 env_path = os.getenv('PATH').split(':')
103 for p in env_path:
104 if os.access(p+'/'+filename, os.X_OK):
105 return p+'/'+filename
106 return None
109 def strip_illegal(instr):
110 '''remove illegal (filename) characters from string'''
111 str = instr
112 str = str.strip()
113 str = string.translate(str, string.maketrans(r'/+{}*.?', r'--()___'))
114 return str
117 class Ripper(rox.Window):
118 '''Rip and Encode a CD'''
119 def __init__(self):
120 rox.Window.__init__(self)
122 self.set_title(APP_NAME)
123 self.set_default_size(450, 500)
124 self.set_position(gtk.WIN_POS_MOUSE)
126 #capture wm delete event
127 self.connect("delete_event", self.delete_event)
129 # Update things when options change
130 rox.app_options.add_notify(self.get_options)
133 #song list
134 #######################################
135 swin = gtk.ScrolledWindow()
136 self.scroll_window = swin
137 swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
138 swin.set_shadow_type(gtk.SHADOW_IN)
140 self.store = gtk.ListStore(int, str, str, str)
141 view = gtk.TreeView(self.store)
142 self.view = view
143 swin.add(view)
144 view.set_rules_hint(True)
146 cell = gtk.CellRendererToggle()
147 cell.connect('toggled', self.toggle_check)
148 column = gtk.TreeViewColumn('', cell, active=COL_ENABLE)
149 view.append_column(column)
150 column.set_resizable(False)
151 column.set_reorderable(False)
153 cell = gtk.CellRendererText()
154 column = gtk.TreeViewColumn(_('Track'), cell, text = COL_TRACK)
155 view.append_column(column)
156 column.set_resizable(True)
157 #column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
158 column.set_reorderable(False)
160 cell = gtk.CellRendererText()
161 column = gtk.TreeViewColumn(_('Time'), cell, text = COL_TIME)
162 view.append_column(column)
163 column.set_resizable(True)
164 column.set_reorderable(False)
166 cell = gtk.CellRendererText()
167 column = gtk.TreeViewColumn(_('Status'), cell, text = COL_STATUS)
168 view.append_column(column)
169 column.set_resizable(True)
170 column.set_reorderable(False)
172 view.connect('row-activated', self.activate)
173 self.selection = view.get_selection()
174 self.handler = self.selection.connect('changed', self.set_selection)
177 self.toolbar = gtk.Toolbar()
178 self.toolbar.set_style(gtk.TOOLBAR_ICONS)
179 self.toolbar.insert_stock(gtk.STOCK_PREFERENCES,
180 _('Settings'), None, self.show_options, None, 0)
181 self.stop_btn = self.toolbar.insert_stock(gtk.STOCK_STOP,
182 _('Stop'), None, self.stop, None, 0)
183 self.rip_btn = self.toolbar.insert_stock(gtk.STOCK_EXECUTE,
184 _('Rip & Encode'), None, self.rip_n_encode, None, 0)
185 self.refresh_btn = self.toolbar.insert_stock(gtk.STOCK_REFRESH,
186 _('Reload CD'), None, self.do_get_tracks, None, 0)
188 self.toolbar.insert_stock(gtk.STOCK_GO_UP,
189 _('Show destination dir'), None, self.show_dir, None, 0)
191 self.toolbar.insert_stock(gtk.STOCK_CLOSE,
192 _('Close'), None, self.close, None, 0)
195 self.table = gtk.Table(5, 2, False)
196 x_pad = 2
197 y_pad = 1
199 self.artist_entry = gtk.Entry(max=255)
200 self.artist_entry.connect('changed', self.stuff_changed)
201 self.table.attach(gtk.Label(str=_('Artist')), 0, 1, 2, 3, 0, 0, 4, y_pad)
202 self.table.attach(self.artist_entry, 1, 2, 2, 3, gtk.EXPAND|gtk.FILL, 0, x_pad, y_pad)
204 self.album_entry = gtk.Entry(max=255)
205 self.album_entry.connect('changed', self.stuff_changed)
206 self.table.attach(gtk.Label(str=_('Album')), 0, 1, 3, 4, 0, 0, 4, y_pad)
207 self.table.attach(self.album_entry, 1, 2, 3, 4, gtk.EXPAND|gtk.FILL, 0, x_pad, y_pad)
209 genres.genre_list.sort()
210 self.genre_combo = gtk.Combo()
211 self.genre_combo.set_popdown_strings(genres.genre_list)
212 self.genre_combo.entry.connect('changed', self.stuff_changed)
213 self.table.attach(gtk.Label(str=_('Genre')), 0, 1, 4, 5, 0, 0, 4, y_pad)
214 self.table.attach(self.genre_combo, 1, 2, 4, 5, gtk.EXPAND|gtk.FILL, 0, x_pad, y_pad)
216 self.year_entry = gtk.Entry(max=4)
217 self.year_entry.connect('changed', self.stuff_changed)
218 self.table.attach(gtk.Label(str=_('Year')), 0, 1, 5, 6, 0, 0, 4, y_pad)
219 self.table.attach(self.year_entry, 1, 2, 5, 6, gtk.EXPAND|gtk.FILL, 0, x_pad, y_pad)
222 # Create layout, pack and show widgets
223 self.vbox = gtk.VBox()
224 self.add(self.vbox)
225 self.vbox.pack_start(self.toolbar, False, True, 0)
226 self.vbox.pack_start(self.table, False, True, 0)
227 self.vbox.pack_start(self.scroll_window, True, True, 0)
228 self.vbox.show_all()
230 # Menu
231 self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
232 self.connect('button-press-event', self.button_press)
233 view.add_events(gtk.gdk.BUTTON_PRESS_MASK)
234 view.connect('button-press-event', self.button_press)
236 self.menu = Menu.Menu('main', [
237 Menu.Action(_('Rip & Encode'), 'rip_n_encode', '', gtk.STOCK_EXECUTE),
238 Menu.Action(_('Reload CD'), 'do_get_tracks', '', gtk.STOCK_REFRESH),
239 Menu.Action(_('Stop'), 'stop', '', gtk.STOCK_STOP),
240 Menu.Separator(),
241 Menu.Action(_('Options'), 'show_options', '', gtk.STOCK_PREFERENCES),
242 Menu.Action(_('Info'), 'get_info', '', gtk.STOCK_DIALOG_INFO),
243 Menu.Action(_("Quit"), 'close', '', gtk.STOCK_CLOSE),
245 self.menu.attach(self,self)
247 # Defaults and Misc
248 self.cddb_thd = None
249 self.ripper_thd = None
250 self.encoder_thd = None
251 self.is_ripping = False
252 self.is_encoding = False
253 self.is_cddbing = False
254 self.stop_request = False
256 cd_logic.set_dev(RIPPER_DEV.value)
257 self.cd_status = cd_logic.check_dev()
258 self.disc_id = None
259 self.cd_status_changed = False
261 if self.cd_status in [CDROM.CDS_TRAY_OPEN, CDROM.CDS_NO_DISC]:
262 self.no_disc()
263 else:
264 self.do_get_tracks()
265 gtk.timeout_add(1000, self.update_gui)
268 def update_gui(self):
269 '''Update button status based on current state'''
270 cd_status = cd_logic.check_dev()
271 if self.cd_status != cd_status:
272 self.cd_status = cd_status
273 self.cd_status_changed = True
275 if self.is_ripping or self.is_encoding:
276 self.stop_btn.set_sensitive(True)
277 self.rip_btn.set_sensitive(False)
278 self.refresh_btn.set_sensitive(False)
280 if not self.is_ripping and not self.is_encoding and not self.is_cddbing:
281 self.stop_btn.set_sensitive(False)
282 self.rip_btn.set_sensitive(True)
283 self.refresh_btn.set_sensitive(True)
285 #get tracks if cd changed and not doing other things
286 if self.cd_status_changed:
287 if self.cd_status in [CDROM.CDS_TRAY_OPEN, CDROM.CDS_NO_DISC]:
288 self.no_disc()
289 self.disc_id = None
290 else:
291 disc_id = cd_logic.get_disc_id()
292 if self.disc_id <> disc_id:
293 self.disc_id = disc_id
294 self.do_get_tracks()
295 self.cd_status_changed = False
297 #need this to keep the timer running(?)
298 return True
301 def stuff_changed(self, button=None):
302 '''Get new text from edit boxes and save it'''
303 self.genre = self.genre_combo.entry.get_text()
304 self.artist = self.artist_entry.get_text()
305 self.album = self.album_entry.get_text()
306 self.year = self.year_entry.get_text()
309 def runit(self, it):
310 '''Run a function in a thread'''
311 thd_it = Thread(name='mythread', target=it)
312 thd_it.setDaemon(True)
313 thd_it.start()
314 return thd_it
317 def stop(self, it):
318 '''Stop current rip/encode process'''
319 self.stop_request = True
321 def show_dir(self, *dummy):
322 ''' Pops up a filer window. '''
323 temp = os.path.join(os.path.expanduser(LIBRARY.value), self.artist, self.album)
324 filer.show_file(temp)
326 def do_get_tracks(self, button=None):
327 '''Get the track info (cddb and cd) in a thread'''
328 if self.is_ripping:
329 return
330 self.cddb_thd = self.runit(self.get_tracks)
333 def no_disc(self):
334 '''Clear all info and display <no disc>'''
335 #print "no disc in tray?"
336 gtk.threads_enter()
337 self.store.clear()
338 self.artist_entry.set_text(_('<no disc>'))
339 self.album_entry.set_text('')
340 self.genre_combo.entry.set_text('')
341 self.year_entry.set_text('')
342 self.view.columns_autosize()
343 gtk.threads_leave()
346 def get_tracks(self):
347 '''Get the track info (cddb and cd)'''
348 self.is_cddbing = True
349 stuff = self.get_cddb()
350 gtk.threads_enter()
351 (count, artist, album, genre, year, tracklist) = stuff
352 #print count, artist, album, genre, year, tracklist
354 self.artist = artist
355 self.count = count
356 self.album = album
357 self.genre = genre
358 self.year = year
359 self.tracklist = tracklist
361 if artist: self.artist_entry.set_text(artist)
362 if album: self.album_entry.set_text(album)
363 if genre: self.genre_combo.entry.set_text(genre)
364 if year: self.year_entry.set_text(year)
366 self.store.clear()
367 for track in tracklist:
368 #print song
369 iter = self.store.append(None)
370 self.store.set(iter, COL_TRACK, track[0])
371 self.store.set(iter, COL_TIME, track[1])
372 self.store.set(iter, COL_ENABLE, True)
374 self.view.columns_autosize()
375 gtk.threads_leave()
376 self.is_cddbing = False
379 def get_cddb(self):
380 '''Query cddb for track and cd info'''
381 gtk.threads_enter()
382 dlg = gtk.MessageDialog(buttons=gtk.BUTTONS_CANCEL, message_format="Getting Track Info.")
383 # dlg.set_position(gtk.WIN_POS_NONE)
384 # (a, b) = dlg.get_size()
385 # (x, y) = self.get_position()
386 # (dx, dy) = self.get_size()
387 # dlg.move(x+dx/2-a/2, y+dy/2-b/2)
388 dlg.show()
389 gtk.threads_leave()
391 count = artist = genre = album = year = ''
392 tracklist = []
393 tracktime = []
395 #Note: all the nested try|except|pass statements are to ensure that as much
396 #info is processed as possible. One exception should not stop
397 #the whole thing and return nothing.
399 try:
400 count = cd_logic.total_tracks()
401 cddb_id = cd_logic.get_cddb_id()
403 #PyCDDB wants a string delimited by spaces, go figure.
404 cddb_id_string = ''
405 for n in cddb_id:
406 cddb_id_string += str(n)+' '
407 #print disc_id
408 #print cddb_id, cddb_id_string
410 for i in range(count):
411 tracktime = cd_logic.get_track_time_total(i+1)
412 track_time = time.strftime('%M:%S', time.gmtime(tracktime))
413 tracklist.append((_('Track')+`i`,track_time))
415 try:
416 db = PyCDDB.PyCDDB(CDDB_SERVER.value)
417 query_info = db.query(cddb_id_string)
418 #print query_info
420 gtk.threads_enter()
421 dlg.set_title(_('Got Disc Info'))
422 gtk.threads_leave()
424 #make sure we didn't get an error, then query CDDB
425 if len(query_info) > 0:
426 rndm = Random()
427 index = rndm.randrange(0, len(query_info))
428 read_info = db.read(query_info[index])
429 #print read_info
430 gtk.threads_enter()
431 dlg.set_title(_('Got Track Info'))
432 gtk.threads_leave()
434 try:
435 (artist, album) = query_info[index]['title'].split('/')
436 artist = artist.strip()
437 album = album.strip()
438 genre = query_info[index]['category']
439 if genre in ['misc', 'data']:
440 genre = 'Other'
442 print query_info['year']
443 print read_info['EXTD']
444 print read_info['YEARD']
446 #x = re.match(r'.*YEAR: (.+).*',read_info['EXTD'])
447 #if x:
448 # print x.group(1)
449 # year = x.group(1)
450 except:
451 pass
453 if len(read_info['TTITLE']) > 0:
454 for i in range(count):
455 try:
456 track_name = read_info['TTITLE'][i]
457 track_time = tracklist[i][1]
458 #print i, track_name, track_time
459 tracklist[i] = (track_name, track_time)
460 except:
461 pass
462 except:
463 pass
465 except:
466 pass
468 gtk.threads_enter()
469 dlg.destroy()
470 gtk.threads_leave()
471 return count, artist, album, genre, year, tracklist
474 def get_cdda2wav(self, tracknum, track):
475 '''Run cdda2wav to rip a track from the CD'''
476 cdda2wav_cmd = RIPPER.value
477 cdda2wav_dev = RIPPER_DEV.value
478 cdda2wav_lun = RIPPER_LUN.value
479 cdda2wav_args = '-g -D%s -A%s -t %d "%s"' % (
480 cdda2wav_lun, cdda2wav_dev, tracknum+1, strip_illegal(track))
481 cdda2wav_opts = RIPPER_OPTS.value
482 #print cdda2wav_opts, cdda2wav_args
484 thing = popen2.Popen4(cdda2wav_cmd+' '+cdda2wav_opts+' '+cdda2wav_args )
485 outfile = thing.fromchild
487 while True:
488 line = myreadline(outfile)
489 if line:
490 x = re.match("(.+[\s]+)([0-9]+)%", line)
491 if x:
492 percent = int(x.group(2))
493 self.status_update(tracknum, 'rip', percent)
494 else:
495 break
496 if self.stop_request:
497 break
499 if self.stop_request:
500 os.kill(thing.pid, signal.SIGKILL)
502 code = thing.wait()
503 self.status_update(tracknum, 'rip', 100)
504 #print code
505 return code
508 def get_lame(self, tracknum, track, artist, genre, album, year):
509 '''Run lame to encode a wav file to mp3'''
510 try:
511 int_year = int(year)
512 except:
513 int_year = 1
515 lame_cmd = MP3_ENCODER.value
516 lame_opts = MP3_ENCODER_OPTS.value
517 lame_tags = '--ta "%s" --tt "%s" --tl "%s" --tg "%s" --tn %d --ty %d' % (
518 artist, track, album, genre, tracknum+1, int_year)
519 lame_args = '"%s" "%s"' % (strip_illegal(track)+'.wav', strip_illegal(track)+'.mp3')
521 #print lame_opts, lame_tags, lame_args
523 thing = popen2.Popen4(lame_cmd+' '+lame_opts+' '+lame_tags+' '+lame_args )
524 outfile = thing.fromchild
526 while True:
527 line = myreadline(outfile)
528 if line:
529 #print line
530 #for some reason getting this right for lame was a royal pain.
531 x = re.match(r"^[\s]+([0-9]+)/([0-9]+)", line)
532 if x:
533 percent = int(100 * (float(x.group(1)) / float(x.group(2))))
534 self.status_update(tracknum, 'enc', percent)
535 else:
536 break
537 if self.stop_request:
538 break
540 if self.stop_request:
541 os.kill(thing.pid, signal.SIGKILL)
542 elif HAVE_XATTR:
543 try:
544 filename = strip_illegal(track)+'.mp3'
545 xattr.setxattr(filename, 'user.Title', track)
546 xattr.setxattr(filename, 'user.Artist', artist)
547 xattr.setxattr(filename, 'user.Album', album)
548 xattr.setxattr(filename, 'user.Genre', genre)
549 xattr.setxattr(filename, 'user.Track', '%d' % tracknum)
550 xattr.setxattr(filename, 'user.Year', year)
551 except:
552 pass
554 code = thing.wait()
555 self.status_update(tracknum, 'enc', 100)
556 #print code
557 return code
560 def get_ogg(self, tracknum, track, artist, genre, album, year):
561 '''Run oggenc to encode a wav file to ogg'''
562 try:
563 int_year = int(year)
564 except:
565 int_year = 1
567 ogg_cmd = OGG_ENCODER.value
568 ogg_opts = OGG_ENCODER_OPTS.value
569 ogg_tags = '-a "%s" -t "%s" -l "%s" -G "%s" -N %d -d %d' % (
570 artist, track, album, genre, tracknum+1, int_year)
571 ogg_args = '"%s"' % (strip_illegal(track)+'.wav')
573 #print ogg_opts, ogg_tags, ogg_args
575 thing = popen2.Popen4(ogg_cmd+' '+ogg_opts+' '+ogg_tags+' '+ogg_args )
576 outfile = thing.fromchild
578 while True:
579 line = myreadline(outfile)
580 if line:
581 #print line
582 #for some reason getting this right for ogg was a royal pain.
583 x = re.match('^.*\[[\s]*([.0-9]+)%\]', line)
584 if x:
585 percent = float(x.group(1))
586 self.status_update(tracknum, 'enc', percent)
587 else:
588 break
589 if self.stop_request:
590 break
592 if self.stop_request:
593 os.kill(thing.pid, signal.SIGKILL)
594 elif HAVE_XATTR:
595 try:
596 filename = strip_illegal(track)+'.ogg'
597 xattr.setxattr(filename, 'user.Title', track)
598 xattr.setxattr(filename, 'user.Artist', artist)
599 xattr.setxattr(filename, 'user.Album', album)
600 xattr.setxattr(filename, 'user.Genre', genre)
601 xattr.setxattr(filename, 'user.Track', '%d' % tracknum)
602 xattr.setxattr(filename, 'user.Year', year)
603 except:
604 pass
606 code = thing.wait()
607 self.status_update(tracknum, 'enc', 100)
608 #print code
609 return code
612 def rip_n_encode(self, button=None):
613 '''Process all selected tracks (rip and encode)'''
614 try:
615 os.chdir(os.path.expanduser(LIBRARY.value))
616 except:
617 try:
618 os.mkdir(os.path.expanduser(LIBRARY.value))
619 os.chdir(os.path.expanduser(LIBRARY.value))
620 except:
621 rox.alert("Failed to find or create Library dir")
623 if self.count and self.artist and self.album:
624 try: os.mkdir(self.artist)
625 except: pass
627 try: os.mkdir(self.artist+'/'+self.album)
628 except: pass
630 try: os.chdir(self.artist+'/'+self.album)
631 except: pass
633 self.stop_request = False
635 #the queue to feed tracks from ripper to encoder
636 self.wavqueue = Queue.Queue(1000)
638 self.ripper_thd = self.runit(self.ripit)
639 self.encoder_thd = self.runit(self.encodeit)
642 def ripit(self):
643 '''Thread to rip all selected tracks'''
644 self.is_ripping = True
645 for i in range(self.count):
646 if self.stop_request:
647 break;
649 if self.store[i][COL_ENABLE]:
650 track = self.store[i][COL_TRACK]
651 #print i, track
652 status = self.get_cdda2wav(i, track)
653 if status <> 0:
654 print 'cdda2wav died %d' % status
655 self.status_update(i, 'rip_error', 0)
656 else:
657 #push this track on the queue for the encoder
658 if self.wavqueue:
659 self.wavqueue.put((track, i))
661 #push None object to tell encoder we're done
662 if self.wavqueue:
663 self.wavqueue.put((None, None))
665 self.is_ripping = False
666 cd_logic.stop()
667 if EJECT_AFTER_RIP.int_value:
668 cd_logic.eject()
671 def encodeit(self):
672 '''Thread to encode all tracks from the wavqueue'''
673 self.is_encoding = True
674 while True:
675 if self.stop_request:
676 break
677 (track, tracknum) = self.wavqueue.get(True)
678 if track == None:
679 break
681 if ENCODER.value == 'MP3':
682 status = self.get_lame(tracknum, track, self.artist, self.genre, self.album, self.year)
683 else:
684 status = self.get_ogg(tracknum, track, self.artist, self.genre, self.album, self.year)
686 if status <> 0:
687 print 'encoder died %d' % status
688 self.status_update(tracknum, 'enc_error', 0)
689 try: os.unlink(strip_illegal(track)+".wav")
690 except: pass
691 try: os.unlink(strip_illegal(track)+".inf")
692 except: pass
694 self.is_encoding = False
695 del self.wavqueue
698 def status_update(self, row, state, percent):
699 '''Callback from rip/encode threads to update display'''
700 gtk.threads_enter()
702 iter = self.store.get_iter((row,))
703 if not iter: return
705 if state == 'rip':
706 if percent < 100:
707 self.store.set_value(iter, COL_STATUS, _('Ripping')+': %d%%' % percent)
708 else:
709 self.store.set_value(iter, COL_STATUS, _('Ripping')+': '+_('done'))
711 if state == 'enc':
712 if percent < 100:
713 self.store.set_value(iter, COL_STATUS, _('Encoding')+': %d%%' % percent)
714 else:
715 self.store.set_value(iter, COL_STATUS, _('Encoding')+': '+_('done'))
717 if state == 'rip_error':
718 self.store.set_value(iter, COL_STATUS, _('Ripping')+': '+_('error'))
720 if state == 'enc_error':
721 self.store.set_value(iter, COL_STATUS, _('Encoding')+': '+_('error'))
723 gtk.threads_leave()
726 def activate(self, view, path, column):
727 '''Edit a track name'''
728 model, iter = self.view.get_selection().get_selected()
729 if iter:
730 track = model.get_value(iter, COL_TRACK)
731 dlg = gtk.Dialog(APP_NAME)
732 # dlg.set_position(gtk.WIN_POS_NONE)
733 dlg.set_default_size(350, 100)
734 # (a, b) = dlg.get_size()
735 # (x, y) = self.get_position()
736 # (dx, dy) = self.get_size()
737 # dlg.move(x+dx/2-a/2, y+dy/2-b/2)
738 dlg.show()
740 entry = gtk.Entry()
741 entry.set_text(track)
742 # dlg.set_position(gtk.WIN_POS_MOUSE)
743 entry.show()
744 entry.set_activates_default(True)
745 dlg.vbox.pack_start(entry)
747 dlg.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
748 dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
749 dlg.set_default_response(gtk.RESPONSE_OK)
750 response = dlg.run()
752 if response == gtk.RESPONSE_OK:
753 track = entry.get_text()
754 #print track
755 model.set_value(iter, COL_TRACK, track)
756 self.view.columns_autosize()
758 dlg.destroy()
761 def toggle_check(self, cell, rownum):
762 '''Toggle state for each song'''
763 row = self.store[rownum]
764 row[COL_ENABLE] = not row[COL_ENABLE]
765 self.store.row_changed(rownum, row.iter)
768 def set_selection(self, thing):
769 '''Get current selection'''
770 #model, iter = self.view.get_selection().get_selected()
771 #if iter:
772 # track = model.get_value(iter, COL_TRACK)
774 def button_press(self, text, event):
775 '''Popup menu handler'''
776 if event.button != 3:
777 return 0
778 self.menu.popup(self, event)
779 return 1
781 def show_options(self, button=None):
782 '''Show Options dialog'''
783 rox.edit_options()
785 def get_options(self):
786 '''Get changed Options'''
787 pass
789 def get_info(self):
790 InfoWin.infowin(APP_NAME)
792 def delete_event(self, ev, e1):
793 '''Bye-bye'''
794 self.close()
796 def close(self, button = None):
797 '''We're outta here!'''
798 self.destroy()