fifth commit
[rox-ripper.git] / ripper.py
bloba25c5d5caea66081e35f267bea0cca492fc4033e
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
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 = os.path.split(os.path.abspath(sys.argv[0]))[0]
46 #Options.xml processing
47 rox.setup_app_options(APP_NAME)
49 #assume that everyone puts their music in ~/Music
50 LIBRARY = Option('library', '~/Music')
52 #RIPPER options
53 RIPPER = Option('ripper', 'cdda2wav')
54 RIPPER_DEV = Option('ripper_dev', '/dev/cdrom')
55 RIPPER_LUN = Option('ripper_lun', 'ATAPI:0,1,0')
56 RIPPER_OPTS = Option('ripper_opts', '-x -H')
58 EJECT_AFTER_RIP = Option('eject_after_rip', '0')
60 #ENCODER options
61 ENCODER = Option('encoder', 'MP3')
63 MP3_ENCODER = Option('mp3_encoder', 'lame')
64 MP3_ENCODER_OPTS = Option('mp3_encoder_opts', '--vbr-new -b160 --nohist --add-id3v2')
66 OGG_ENCODER = Option('ogg_encoder', 'oggenc')
67 OGG_ENCODER_OPTS = Option('ogg_encoder_opts', '-q5')
69 #CDDB Server and Options
70 CDDB_SERVER = Option('cddb_server', 'http://freedb.freedb.org/~cddb/cddb.cgi')
72 rox.app_options.notify()
75 #Column indicies
76 COL_ENABLE = 0
77 COL_TRACK = 1
78 COL_TIME = 2
79 COL_STATUS = 3
82 # My gentoo python doesn't have universal line ending support compiled in
83 # and these guys (cdda2wav and lame) use CR line endings to pretty-up their output.
84 def myreadline(file):
85 '''Return a line of input using \r or \n as terminators'''
86 line = ''
87 while '\n' not in line and '\r' not in line:
88 char = file.read(1)
89 if char == '': return line
90 line += char
91 return line
94 def which(filename):
95 '''Return the full path of an executable if found on the path'''
96 if (filename == None) or (filename == ''):
97 return None
99 env_path = os.getenv('PATH').split(':')
100 for p in env_path:
101 if os.access(p+'/'+filename, os.X_OK):
102 return p+'/'+filename
103 return None
106 def strip_illegal(instr):
107 '''remove illegal (filename) characters from string'''
108 str = instr
109 str = str.strip()
110 str = string.translate(str, string.maketrans(r'/+{}*.?', r'--()___'))
111 return str
114 class Ripper(rox.Window):
115 '''Rip and Encode a CD'''
116 def __init__(self):
117 rox.Window.__init__(self)
119 self.set_title(APP_NAME)
120 self.set_default_size(450, 500)
121 self.set_position(gtk.WIN_POS_MOUSE)
123 #capture wm delete event
124 self.connect("delete_event", self.delete_event)
126 # Update things when options change
127 rox.app_options.add_notify(self.get_options)
130 #song list
131 #######################################
132 swin = gtk.ScrolledWindow()
133 self.scroll_window = swin
134 swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
135 swin.set_shadow_type(gtk.SHADOW_IN)
137 self.store = gtk.ListStore(int, str, str, str)
138 view = gtk.TreeView(self.store)
139 self.view = view
140 swin.add(view)
141 view.set_rules_hint(True)
143 cell = gtk.CellRendererToggle()
144 cell.connect('toggled', self.toggle_check)
145 column = gtk.TreeViewColumn('', cell, active=COL_ENABLE)
146 view.append_column(column)
147 column.set_resizable(False)
148 column.set_reorderable(False)
150 cell = gtk.CellRendererText()
151 column = gtk.TreeViewColumn(_('Track'), cell, text = COL_TRACK)
152 view.append_column(column)
153 column.set_resizable(True)
154 #column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
155 column.set_reorderable(False)
157 cell = gtk.CellRendererText()
158 column = gtk.TreeViewColumn(_('Time'), cell, text = COL_TIME)
159 view.append_column(column)
160 column.set_resizable(True)
161 column.set_reorderable(False)
163 cell = gtk.CellRendererText()
164 column = gtk.TreeViewColumn(_('Status'), cell, text = COL_STATUS)
165 view.append_column(column)
166 column.set_resizable(True)
167 column.set_reorderable(False)
169 view.connect('row-activated', self.activate)
170 self.selection = view.get_selection()
171 self.handler = self.selection.connect('changed', self.set_selection)
174 self.toolbar = gtk.Toolbar()
175 self.toolbar.set_style(gtk.TOOLBAR_ICONS)
176 self.toolbar.insert_stock(gtk.STOCK_PREFERENCES,
177 _('Settings'), None, self.show_options, None, 0)
178 self.stop_btn = self.toolbar.insert_stock(gtk.STOCK_STOP,
179 _('Stop'), None, self.stop, None, 0)
180 self.rip_btn = self.toolbar.insert_stock(gtk.STOCK_EXECUTE,
181 _('Rip & Encode'), None, self.rip_n_encode, None, 0)
182 self.refresh_btn = self.toolbar.insert_stock(gtk.STOCK_REFRESH,
183 _('Reload CD'), None, self.do_get_tracks, None, 0)
185 self.toolbar.insert_stock(gtk.STOCK_GO_UP,
186 _('Show destination dir'), None, self.show_dir, None, 0)
188 self.toolbar.insert_stock(gtk.STOCK_CLOSE,
189 _('Close'), None, self.close, None, 0)
192 self.table = gtk.Table(5, 2, False)
193 x_pad = 2
194 y_pad = 1
196 self.artist_entry = gtk.Entry(max=255)
197 self.artist_entry.connect('changed', self.stuff_changed)
198 self.table.attach(gtk.Label(str=_('Artist')), 0, 1, 2, 3, 0, 0, 4, y_pad)
199 self.table.attach(self.artist_entry, 1, 2, 2, 3, gtk.EXPAND|gtk.FILL, 0, x_pad, y_pad)
201 self.album_entry = gtk.Entry(max=255)
202 self.album_entry.connect('changed', self.stuff_changed)
203 self.table.attach(gtk.Label(str=_('Album')), 0, 1, 3, 4, 0, 0, 4, y_pad)
204 self.table.attach(self.album_entry, 1, 2, 3, 4, gtk.EXPAND|gtk.FILL, 0, x_pad, y_pad)
206 genres.genre_list.sort()
207 self.genre_combo = gtk.Combo()
208 self.genre_combo.set_popdown_strings(genres.genre_list)
209 self.genre_combo.entry.connect('changed', self.stuff_changed)
210 self.table.attach(gtk.Label(str=_('Genre')), 0, 1, 4, 5, 0, 0, 4, y_pad)
211 self.table.attach(self.genre_combo, 1, 2, 4, 5, gtk.EXPAND|gtk.FILL, 0, x_pad, y_pad)
213 self.year_entry = gtk.Entry(max=4)
214 self.year_entry.connect('changed', self.stuff_changed)
215 self.table.attach(gtk.Label(str=_('Year')), 0, 1, 5, 6, 0, 0, 4, y_pad)
216 self.table.attach(self.year_entry, 1, 2, 5, 6, gtk.EXPAND|gtk.FILL, 0, x_pad, y_pad)
219 # Create layout, pack and show widgets
220 self.vbox = gtk.VBox()
221 self.add(self.vbox)
222 self.vbox.pack_start(self.toolbar, False, True, 0)
223 self.vbox.pack_start(self.table, False, True, 0)
224 self.vbox.pack_start(self.scroll_window, True, True, 0)
225 self.vbox.show_all()
227 # Menu
228 self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
229 self.connect('button-press-event', self.button_press)
230 view.add_events(gtk.gdk.BUTTON_PRESS_MASK)
231 view.connect('button-press-event', self.button_press)
233 Menu.set_save_name(APP_NAME)
234 self.menu = Menu.Menu('main', [
235 Menu.Action(_('Rip & Encode'), 'rip_n_encode', '', gtk.STOCK_EXECUTE),
236 Menu.Action(_('Reload CD'), 'do_get_tracks', '', gtk.STOCK_REFRESH),
237 Menu.Action(_('Stop'), 'stop', '', gtk.STOCK_STOP),
238 Menu.Separator(),
239 Menu.Action(_('Settings'), 'show_options', '', gtk.STOCK_PREFERENCES),
240 Menu.Action(_("Quit"), 'close', '', gtk.STOCK_CLOSE),
242 self.menu.attach(self,self)
244 # Defaults and Misc
245 self.cddb_thd = None
246 self.ripper_thd = None
247 self.encoder_thd = None
248 self.is_ripping = False
249 self.is_encoding = False
250 self.is_cddbing = False
251 self.stop_request = False
253 cd_logic.set_dev(RIPPER_DEV.value)
254 self.cd_status = cd_logic.check_dev()
255 self.disc_id = None
256 self.cd_status_changed = False
258 if self.cd_status in [CDROM.CDS_TRAY_OPEN, CDROM.CDS_NO_DISC]:
259 self.no_disc()
260 else:
261 self.do_get_tracks()
262 gtk.timeout_add(1000, self.update_gui)
265 def update_gui(self):
266 '''Update button status based on current state'''
267 cd_status = cd_logic.check_dev()
268 if self.cd_status != cd_status:
269 self.cd_status = cd_status
270 self.cd_status_changed = True
272 if self.is_ripping or self.is_encoding:
273 self.stop_btn.set_sensitive(True)
274 self.rip_btn.set_sensitive(False)
275 self.refresh_btn.set_sensitive(False)
277 if not self.is_ripping and not self.is_encoding and not self.is_cddbing:
278 self.stop_btn.set_sensitive(False)
279 self.rip_btn.set_sensitive(True)
280 self.refresh_btn.set_sensitive(True)
282 #get tracks if cd changed and not doing other things
283 if self.cd_status_changed:
284 if self.cd_status in [CDROM.CDS_TRAY_OPEN, CDROM.CDS_NO_DISC]:
285 self.no_disc()
286 self.disc_id = None
287 else:
288 disc_id = cd_logic.get_disc_id()
289 if self.disc_id <> disc_id:
290 self.disc_id = disc_id
291 self.do_get_tracks()
292 self.cd_status_changed = False
294 #need this to keep the timer running(?)
295 return True
298 def stuff_changed(self, button=None):
299 '''Get new text from edit boxes and save it'''
300 self.genre = self.genre_combo.entry.get_text()
301 self.artist = self.artist_entry.get_text()
302 self.album = self.album_entry.get_text()
303 self.year = self.year_entry.get_text()
306 def runit(self, it):
307 '''Run a function in a thread'''
308 thd_it = Thread(name='mythread', target=it)
309 thd_it.setDaemon(True)
310 thd_it.start()
311 return thd_it
314 def stop(self, it):
315 '''Stop current rip/encode process'''
316 self.stop_request = True
318 def show_dir(self, *dummy):
319 ''' Pops up a filer window. '''
320 temp = os.path.join(os.path.expanduser(LIBRARY.value), self.artist, self.album)
321 filer.show_file(temp)
323 def do_get_tracks(self, button=None):
324 '''Get the track info (cddb and cd) in a thread'''
325 if self.is_ripping:
326 return
327 self.cddb_thd = self.runit(self.get_tracks)
330 def no_disc(self):
331 '''Clear all info and display <no disc>'''
332 #print "no disc in tray?"
333 gtk.threads_enter()
334 self.store.clear()
335 self.artist_entry.set_text(_('<no disc>'))
336 self.album_entry.set_text('')
337 self.genre_combo.entry.set_text('')
338 self.year_entry.set_text('')
339 self.view.columns_autosize()
340 gtk.threads_leave()
343 def get_tracks(self):
344 '''Get the track info (cddb and cd)'''
345 self.is_cddbing = True
346 stuff = self.get_cddb()
347 gtk.threads_enter()
348 (count, artist, album, genre, year, tracklist) = stuff
349 #print count, artist, album, genre, year, tracklist
351 self.artist = artist
352 self.count = count
353 self.album = album
354 self.genre = genre
355 self.year = year
356 self.tracklist = tracklist
358 if artist: self.artist_entry.set_text(artist)
359 if album: self.album_entry.set_text(album)
360 if genre: self.genre_combo.entry.set_text(genre)
361 if year: self.year_entry.set_text(year)
363 self.store.clear()
364 for track in tracklist:
365 #print song
366 iter = self.store.append(None)
367 self.store.set(iter, COL_TRACK, track[0])
368 self.store.set(iter, COL_TIME, track[1])
369 self.store.set(iter, COL_ENABLE, True)
371 self.view.columns_autosize()
372 gtk.threads_leave()
373 self.is_cddbing = False
376 def get_cddb(self):
377 '''Query cddb for track and cd info'''
378 gtk.threads_enter()
379 dlg = gtk.MessageDialog(buttons=gtk.BUTTONS_CANCEL, message_format="Getting Track Info.")
380 dlg.set_position(gtk.WIN_POS_NONE)
381 (a, b) = dlg.get_size()
382 (x, y) = self.get_position()
383 (dx, dy) = self.get_size()
384 dlg.move(x+dx/2-a/2, y+dy/2-b/2)
385 dlg.show()
386 gtk.threads_leave()
388 count = artist = genre = album = year = ''
389 tracklist = []
390 tracktime = []
392 #Note: all the nested try statements are to ensure that as much
393 #info is processed as possible. One exception should not stop
394 #the whole thing and return nothing.
396 try:
397 count = cd_logic.total_tracks()
398 cddb_id = cd_logic.get_cddb_id()
400 #PyCDDB wants a string delimited by spaces, go figure.
401 cddb_id_string = ''
402 for n in cddb_id:
403 cddb_id_string += str(n)+' '
404 #print disc_id
405 #print cddb_id, cddb_id_string
407 for i in range(count):
408 tracktime = cd_logic.get_track_time_total(i+1)
409 track_time = time.strftime('%M:%S', time.gmtime(tracktime))
410 tracklist.append((_('Track')+`i`,track_time))
412 try:
413 db = PyCDDB.PyCDDB(CDDB_SERVER.value)
414 query_info = db.query(cddb_id_string)
415 #print query_info
417 gtk.threads_enter()
418 dlg.set_title(_('Got Disc Info'))
419 gtk.threads_leave()
421 #make sure we didn't get an error, then query CDDB
422 if len(query_info) > 0:
423 rndm = Random()
424 index = rndm.randrange(0, len(query_info))
425 read_info = db.read(query_info[index])
426 #print read_info
427 gtk.threads_enter()
428 dlg.set_title(_('Got Track Info'))
429 gtk.threads_leave()
431 try:
432 (artist, album) = query_info[index]['title'].split('/')
433 artist = artist.strip()
434 album = album.strip()
435 genre = query_info[index]['category']
436 if genre in ['misc', 'data']:
437 genre = 'Other'
439 print query_info['year']
440 print read_info['EXTD']
441 print read_info['YEARD']
443 #x = re.match(r'.*YEAR: (.+).*',read_info['EXTD'])
444 #if x:
445 # print x.group(1)
446 # year = x.group(1)
447 except:
448 pass
450 if len(read_info['TTITLE']) > 0:
451 for i in range(count):
452 try:
453 track_name = read_info['TTITLE'][i]
454 track_time = tracklist[i][1]
455 #print i, track_name, track_time
456 tracklist[i] = (track_name, track_time)
457 except:
458 pass
459 except:
460 pass
462 except:
463 pass
465 gtk.threads_enter()
466 dlg.destroy()
467 gtk.threads_leave()
468 return count, artist, album, genre, year, tracklist
471 def get_cdda2wav(self, tracknum, track):
472 '''Run cdda2wav to rip a track from the CD'''
473 cdda2wav_cmd = RIPPER.value
474 cdda2wav_dev = RIPPER_DEV.value
475 cdda2wav_lun = RIPPER_LUN.value
476 cdda2wav_args = '-g -D%s -A%s -t %d "%s"' % (
477 cdda2wav_lun, cdda2wav_dev, tracknum+1, strip_illegal(track))
478 cdda2wav_opts = RIPPER_OPTS.value
479 #print cdda2wav_opts, cdda2wav_args
481 thing = popen2.Popen4(cdda2wav_cmd+' '+cdda2wav_opts+' '+cdda2wav_args )
482 outfile = thing.fromchild
484 while True:
485 line = myreadline(outfile)
486 if line:
487 x = re.match("(.+[\s]+)([0-9]+)%", line)
488 if x:
489 percent = int(x.group(2))
490 self.status_update(tracknum, 'rip', percent)
491 else:
492 break
493 if self.stop_request:
494 break
496 if self.stop_request:
497 os.kill(thing.pid, signal.SIGKILL)
499 code = thing.wait()
500 self.status_update(tracknum, 'rip', 100)
501 #print code
502 return code
505 def get_lame(self, tracknum, track, artist, genre, album, year):
506 '''Run lame to encode a wav file to mp3'''
507 try:
508 int_year = int(year)
509 except:
510 int_year = 1
512 lame_cmd = MP3_ENCODER.value
513 lame_opts = MP3_ENCODER_OPTS.value
514 lame_tags = '--ta "%s" --tt "%s" --tl "%s" --tg "%s" --tn %d --ty %d' % (
515 artist, track, album, genre, tracknum+1, int_year)
516 lame_args = '"%s" "%s"' % (strip_illegal(track)+'.wav', strip_illegal(track)+'.mp3')
518 #print lame_opts, lame_tags, lame_args
520 thing = popen2.Popen4(lame_cmd+' '+lame_opts+' '+lame_tags+' '+lame_args )
521 outfile = thing.fromchild
523 while True:
524 line = myreadline(outfile)
525 if line:
526 #print line
527 #for some reason getting this right for lame was a royal pain.
528 x = re.match(r"^[\s]+([0-9]+)/([0-9]+)", line)
529 if x:
530 percent = int(100 * (float(x.group(1)) / float(x.group(2))))
531 self.status_update(tracknum, 'enc', percent)
532 else:
533 break
534 if self.stop_request:
535 break
537 if self.stop_request:
538 os.kill(thing.pid, signal.SIGKILL)
539 elif HAVE_XATTR:
540 try:
541 filename = strip_illegal(track)+'.mp3'
542 xattr.setxattr(filename, 'user.Title', track)
543 xattr.setxattr(filename, 'user.Artist', artist)
544 xattr.setxattr(filename, 'user.Album', album)
545 xattr.setxattr(filename, 'user.Genre', genre)
546 xattr.setxattr(filename, 'user.Track', '%d' % tracknum)
547 xattr.setxattr(filename, 'user.Year', year)
548 except:
549 pass
551 code = thing.wait()
552 self.status_update(tracknum, 'enc', 100)
553 #print code
554 return code
557 def get_ogg(self, tracknum, track, artist, genre, album, year):
558 '''Run oggenc to encode a wav file to ogg'''
559 try:
560 int_year = int(year)
561 except:
562 int_year = 1
564 ogg_cmd = OGG_ENCODER.value
565 ogg_opts = OGG_ENCODER_OPTS.value
566 ogg_tags = '-a "%s" -t "%s" -l "%s" -G "%s" -N %d -d %d' % (
567 artist, track, album, genre, tracknum+1, int_year)
568 ogg_args = '"%s"' % (strip_illegal(track)+'.wav')
570 #print ogg_opts, ogg_tags, ogg_args
572 thing = popen2.Popen4(ogg_cmd+' '+ogg_opts+' '+ogg_tags+' '+ogg_args )
573 outfile = thing.fromchild
575 while True:
576 line = myreadline(outfile)
577 if line:
578 #print line
579 #for some reason getting this right for ogg was a royal pain.
580 x = re.match('^.*\[[\s]*([.0-9]+)%\]', line)
581 if x:
582 percent = float(x.group(1))
583 self.status_update(tracknum, 'enc', percent)
584 else:
585 break
586 if self.stop_request:
587 break
589 if self.stop_request:
590 os.kill(thing.pid, signal.SIGKILL)
591 elif HAVE_XATTR:
592 try:
593 filename = strip_illegal(track)+'.ogg'
594 xattr.setxattr(filename, 'user.Title', track)
595 xattr.setxattr(filename, 'user.Artist', artist)
596 xattr.setxattr(filename, 'user.Album', album)
597 xattr.setxattr(filename, 'user.Genre', genre)
598 xattr.setxattr(filename, 'user.Track', '%d' % tracknum)
599 xattr.setxattr(filename, 'user.Year', year)
600 except:
601 pass
603 code = thing.wait()
604 self.status_update(tracknum, 'enc', 100)
605 #print code
606 return code
609 def rip_n_encode(self, button=None):
610 '''Process all selected tracks (rip and encode)'''
611 try:
612 os.chdir(os.path.expanduser(LIBRARY.value))
613 except:
614 try:
615 os.mkdir(os.path.expanduser(LIBRARY.value))
616 os.chdir(os.path.expanduser(LIBRARY.value))
617 except:
618 rox.alert("Failed to find or create Library dir")
620 if self.count and self.artist and self.album:
621 try: os.mkdir(self.artist)
622 except: pass
624 try: os.mkdir(self.artist+'/'+self.album)
625 except: pass
627 try: os.chdir(self.artist+'/'+self.album)
628 except: pass
630 self.stop_request = False
632 #the queue to feed tracks from ripper to encoder
633 self.wavqueue = Queue.Queue(1000)
635 self.ripper_thd = self.runit(self.ripit)
636 self.encoder_thd = self.runit(self.encodeit)
639 def ripit(self):
640 '''Thread to rip all selected tracks'''
641 self.is_ripping = True
642 for i in range(self.count):
643 if self.stop_request:
644 break;
646 if self.store[i][COL_ENABLE]:
647 track = self.store[i][COL_TRACK]
648 #print i, track
649 status = self.get_cdda2wav(i, track)
650 if status <> 0:
651 print 'cdda2wav died %d' % status
652 self.status_update(i, 'rip_error', 0)
653 else:
654 #push this track on the queue for the encoder
655 if self.wavqueue:
656 self.wavqueue.put((track, i))
658 #push None object to tell encoder we're done
659 if self.wavqueue:
660 self.wavqueue.put((None, None))
662 self.is_ripping = False
663 cd_logic.stop()
664 if EJECT_AFTER_RIP.int_value:
665 cd_logic.eject()
668 def encodeit(self):
669 '''Thread to encode all tracks from the wavqueue'''
670 self.is_encoding = True
671 while True:
672 if self.stop_request:
673 break
674 (track, tracknum) = self.wavqueue.get(True)
675 if track == None:
676 break
678 if ENCODER.value == 'MP3':
679 status = self.get_lame(tracknum, track, self.artist, self.genre, self.album, self.year)
680 else:
681 status = self.get_ogg(tracknum, track, self.artist, self.genre, self.album, self.year)
683 if status <> 0:
684 print 'encoder died %d' % status
685 self.status_update(tracknum, 'enc_error', 0)
686 try: os.unlink(strip_illegal(track)+".wav")
687 except: pass
688 try: os.unlink(strip_illegal(track)+".inf")
689 except: pass
691 self.is_encoding = False
692 del self.wavqueue
695 def status_update(self, row, state, percent):
696 '''Callback from rip/encode threads to update display'''
697 gtk.threads_enter()
699 iter = self.store.get_iter((row,))
700 if not iter: return
702 if state == 'rip':
703 if percent < 100:
704 self.store.set_value(iter, COL_STATUS, _('Ripping')+': %d%%' % percent)
705 else:
706 self.store.set_value(iter, COL_STATUS, _('Ripping')+': '+_('done'))
708 if state == 'enc':
709 if percent < 100:
710 self.store.set_value(iter, COL_STATUS, _('Encoding')+': %d%%' % percent)
711 else:
712 self.store.set_value(iter, COL_STATUS, _('Encoding')+': '+_('done'))
714 if state == 'rip_error':
715 self.store.set_value(iter, COL_STATUS, _('Ripping')+': '+_('error'))
717 if state == 'enc_error':
718 self.store.set_value(iter, COL_STATUS, _('Encoding')+': '+_('error'))
720 gtk.threads_leave()
723 def activate(self, view, path, column):
724 '''Edit a track name'''
725 model, iter = self.view.get_selection().get_selected()
726 if iter:
727 track = model.get_value(iter, COL_TRACK)
728 dlg = gtk.Dialog(APP_NAME)
729 dlg.set_position(gtk.WIN_POS_NONE)
730 dlg.set_default_size(350, 100)
731 (a, b) = dlg.get_size()
732 (x, y) = self.get_position()
733 (dx, dy) = self.get_size()
734 dlg.move(x+dx/2-a/2, y+dy/2-b/2)
735 dlg.show()
737 entry = gtk.Entry()
738 entry.set_text(track)
739 dlg.set_position(gtk.WIN_POS_MOUSE)
740 entry.show()
741 entry.set_activates_default(True)
742 dlg.vbox.pack_start(entry)
744 dlg.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
745 dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
746 dlg.set_default_response(gtk.RESPONSE_OK)
747 response = dlg.run()
749 if response == gtk.RESPONSE_OK:
750 track = entry.get_text()
751 #print track
752 model.set_value(iter, COL_TRACK, track)
753 self.view.columns_autosize()
755 dlg.destroy()
758 def toggle_check(self, cell, rownum):
759 '''Toggle state for each song'''
760 row = self.store[rownum]
761 row[COL_ENABLE] = not row[COL_ENABLE]
762 self.store.row_changed(rownum, row.iter)
765 def set_selection(self, thing):
766 '''Get current selection'''
767 #model, iter = self.view.get_selection().get_selected()
768 #if iter:
769 # track = model.get_value(iter, COL_TRACK)
771 def button_press(self, text, event):
772 '''Popup menu handler'''
773 if event.button != 3:
774 return 0
775 self.menu.popup(self, event)
776 return 1
778 def show_options(self, button=None):
779 '''Show Options dialog'''
780 rox.edit_options()
782 def get_options(self):
783 '''Get changed Options'''
784 pass
786 def delete_event(self, ev, e1):
787 '''Bye-bye'''
788 self.close()
790 def close(self, button = None):
791 '''We're outta here!'''
792 self.destroy()