summaryrefslogtreecommitdiff
path: root/soundconverter/ui.py
diff options
context:
space:
mode:
Diffstat (limited to 'soundconverter/ui.py')
-rw-r--r--soundconverter/ui.py1509
1 files changed, 1509 insertions, 0 deletions
diff --git a/soundconverter/ui.py b/soundconverter/ui.py
new file mode 100644
index 0000000..dbedefd
--- /dev/null
+++ b/soundconverter/ui.py
@@ -0,0 +1,1509 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# SoundConverter - GNOME application for converting between audio formats.
+# Copyright 2004 Lars Wirzenius
+# Copyright 2005-2012 Gautier Portet
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 3 of the License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+import os
+from os.path import basename, dirname
+import time
+import sys
+import gtk
+import gobject
+import gnome
+import gnomevfs
+import urllib
+from gettext import gettext as _
+
+from gconfstore import GConfStore
+from fileoperations import filename_to_uri, beautify_uri
+from fileoperations import unquote_filename, vfs_walk
+from fileoperations import use_gnomevfs
+from gstreamer import ConverterQueue
+from gstreamer import available_elements, TypeFinder, TagReader
+from gstreamer import audio_profiles_list, audio_profiles_dict
+from soundfile import SoundFile
+from settings import locale_patterns_dict, custom_patterns, filepattern, settings
+from namegenerator import TargetNameGenerator
+from queue import TaskQueue
+from utils import log, debug
+from messagearea import MessageArea
+from error import show_error
+
+# Names of columns in the file list
+MODEL = [ gobject.TYPE_STRING, # visible filename
+ gobject.TYPE_PYOBJECT, # soundfile
+ gobject.TYPE_FLOAT, # progress
+ gobject.TYPE_STRING, # status
+ gobject.TYPE_STRING, # complete filename
+ ]
+
+COLUMNS = ['filename']
+
+#VISIBLE_COLUMNS = ['filename']
+#ALL_COLUMNS = VISIBLE_COLUMNS + ['META']
+
+MP3_CBR, MP3_ABR, MP3_VBR = range(3)
+
+
+def gtk_iteration():
+ while gtk.events_pending():
+ gtk.main_iteration(False)
+
+
+def gtk_sleep(duration):
+ start = time.time()
+ while time.time() < start + duration:
+ time.sleep(0.010)
+ gtk_iteration()
+
+
+class ErrorDialog:
+
+ def __init__(self, builder):
+ self.dialog = builder.get_object('error_dialog')
+ self.dialog.set_transient_for(builder.get_object('window'))
+ self.primary = builder.get_object('primary_error_label')
+ self.secondary = builder.get_object('secondary_error_label')
+
+ def show_error(self, primary, secondary):
+ self.primary.set_markup(primary)
+ self.secondary.set_markup(secondary)
+ try:
+ sys.stderr.write(_('\nError: %s\n%s\n') % (primary, secondary))
+ except:
+ pass
+ self.dialog.run()
+ self.dialog.hide()
+
+
+class MsgAreaErrorDialog_:
+
+ def __init__(self, builder):
+ self.dialog = builder.get_object('error_frame')
+ self.primary = builder.get_object('label_error')
+
+ def show_error(self, primary, secondary):
+ try:
+ sys.stderr.write(_('\nError: %s\n%s\n') % (primary, secondary))
+ except:
+ pass
+ #self.msg_area.set_text_and_icon(gtk.STOCK_DIALOG_ERROR, primary, secondary)
+ #self.msg_area.show()
+ self.primary.set_text(primary)
+ self.dialog.show()
+
+
+ def show_exception(self, exception):
+ self.show('<b>%s</b>' % gobject.markup_escape_text(exception.primary),
+ exception.secondary)
+
+
+class FileList:
+ """List of files added by the user."""
+
+ # List of MIME types which we accept for drops.
+ drop_mime_types = ['text/uri-list', 'text/plain', 'STRING']
+
+ def __init__(self, window, builder):
+ self.window = window
+ self.typefinders = TaskQueue()
+ self.filelist = set()
+
+ self.model = apply(gtk.ListStore, MODEL)
+
+ self.widget = builder.get_object('filelist')
+ self.sortedmodel = gtk.TreeModelSort(self.model)
+ self.widget.set_model(self.sortedmodel)
+ self.sortedmodel.set_sort_column_id(4, gtk.SORT_ASCENDING)
+ self.widget.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+
+ self.widget.drag_dest_set(gtk.DEST_DEFAULT_ALL,
+ map(lambda i:
+ (self.drop_mime_types[i], 0, i),
+ range(len(self.drop_mime_types))),
+ gtk.gdk.ACTION_COPY)
+ self.widget.connect('drag_data_received', self.drag_data_received)
+
+ renderer = gtk.CellRendererProgress()
+ column = gtk.TreeViewColumn('progress',
+ renderer,
+ value=2,
+ text=3,
+ )
+ self.widget.append_column(column)
+ self.progress_column = column
+ self.progress_column.set_visible(False)
+
+ renderer = gtk.CellRendererText()
+ import pango
+ renderer.set_property('ellipsize', pango.ELLIPSIZE_MIDDLE)
+ column = gtk.TreeViewColumn('Filename',
+ renderer,
+ markup=0,
+ )
+ column.set_expand(True)
+ self.widget.append_column(column)
+
+ self.window.progressbarstatus.hide()
+
+ self.waiting_files = []
+ # add files to filelist in batches. Much faster, and suffisant.
+ gobject.timeout_add(100, self.commit_waiting_files)
+ self.waiting_files_last = 0
+
+ def drag_data_received(self, widget, context, x, y, selection,
+ mime_id, time):
+ widget.stop_emission('drag_data_received')
+ if mime_id >= 0 and mime_id < len(self.drop_mime_types):
+ self.add_uris([uri.strip() for uri in selection.data.split('\n')])
+ context.finish(True, False, time)
+
+ def get_files(self):
+ return [i[1] for i in self.sortedmodel]
+
+ def update_progress(self, queue):
+ if queue.running:
+ progress = queue.progress if queue.progress else 0
+ self.window.progressbarstatus.set_fraction(progress)
+ return True
+ return False
+
+ def found_type(self, sound_file, mime):
+ debug('found_type', sound_file.filename)
+
+ self.append_file(sound_file)
+ self.window.set_sensitive()
+
+ def add_uris(self, uris, base=None, extensions=None):
+ files = []
+ self.window.set_status(_('Scanning files...'))
+
+ base = None
+
+ for uri in uris:
+ if not uri:
+ continue
+ if uri.startswith('cdda:'):
+ show_error('Cannot read from Audio CD.',
+ 'Use SoundJuicer Audio CD Extractor instead.')
+ return
+ try:
+ info = gnomevfs.get_file_info(gnomevfs.URI(uri),
+ gnomevfs.FILE_INFO_FOLLOW_LINKS)
+ except gnomevfs.NotFoundError:
+ log('uri not found: \'%s\'' % uri)
+ continue
+ except gnomevfs.InvalidURIError:
+ log('invalid uri: \'%s\'' % uri)
+ continue
+ except gnomevfs.AccessDeniedError:
+ log('access denied: \'%s\'' % uri)
+ continue
+ except TypeError, e:
+ log('add error: %s (\'%s\')' % (e, uri))
+ continue
+ except:
+ log('error in get_file_info: %s' % (uri))
+ continue
+
+ if info.type == gnomevfs.FILE_TYPE_DIRECTORY:
+ log('walking: \'%s\'' % uri)
+ if len(uris) == 1:
+ # if only one folder is passed to the function,
+ # use its parent as base path.
+ base = os.path.dirname(uri)
+ filelist = vfs_walk(gnomevfs.URI(uri))
+ accepted = []
+ if extensions:
+ for f in filelist:
+ for extension in extensions:
+ if f.lower().endswith(extension):
+ accepted.append(f)
+ filelist = accepted
+ files.extend(filelist)
+ else:
+ files.append(uri)
+
+ files = [f for f in files if not f.endswith('~SC~')]
+
+ if not base:
+ base = os.path.commonprefix(files)
+ if base and not base.endswith('/'):
+ # we want a common folder
+ base = base[0:base.rfind('/')]
+ base += '/'
+ else:
+ base += '/'
+
+ for f in files:
+ sound_file = SoundFile(f, base)
+ if sound_file.uri in self.filelist:
+ log('file already present: \'%s\'' % sound_file.uri)
+ continue
+
+ typefinder = TypeFinder(sound_file)
+ typefinder.set_found_type_hook(self.found_type)
+ self.typefinders.add_task(typefinder)
+
+ for i in self.model:
+ i[0] = self.format_cell(i[1])
+
+ if files and not self.typefinders.running:
+ self.window.progressbarstatus.show()
+ self.typefinders.queue_ended = self.typefinder_queue_ended
+ self.typefinders.start()
+ gobject.timeout_add(100, self.update_progress, self.typefinders)
+ else:
+ self.window.set_status()
+
+ def typefinder_queue_ended(self):
+ if not self.waiting_files:
+ self.window.set_status()
+ self.window.progressbarstatus.hide()
+
+ def abort(self):
+ self.typefinders.abort()
+
+ def format_cell(self, sound_file):
+ return '%s' % gobject.markup_escape_text(unquote_filename(
+ sound_file.filename))
+
+ def set_row_progress(self, number, progress=None, text=None):
+ self.progress_column.set_visible(True)
+ if progress is not None:
+ if self.model[number][2] == 1.0:
+ return # already...
+ self.model[number][2] = progress * 100.0
+ if text is not None:
+ self.model[number][3] = text
+
+ def hide_row_progress(self):
+ self.progress_column.set_visible(False)
+
+ def append_file(self, sound_file):
+ self.waiting_files.append(sound_file)
+
+ def commit_waiting_files(self):
+ if self.waiting_files_last != len(self.waiting_files):
+ # still adding files
+ self.waiting_files_last = len(self.waiting_files)
+ return True
+
+ if self.waiting_files:
+ self.window.set_status(_('Adding files...'))
+ save = self.widget.get_model()
+ self.widget.set_model(None)
+ n = 0.0
+ next = time.time()
+ while self.waiting_files:
+ self._append_file(self.waiting_files.pop())
+ n += 1
+ if time.time() > next:
+ # keep UI responsive
+ gtk_iteration()
+ self.window.progressbarstatus.set_fraction(n/self.waiting_files_last)
+ next = time.time() + 0.01
+ self.widget.set_model(save)
+
+ self.window.set_status()
+ self.window.progressbarstatus.hide()
+ return True
+
+ def _append_file(self, sound_file):
+ self.model.append([self.format_cell(sound_file), sound_file, 0.0, '',
+ sound_file.uri])
+ self.filelist.add(sound_file.uri)
+ sound_file.filelist_row = len(self.model) - 1
+
+ def remove(self, iter):
+ uri = self.model.get(iter, 1)[0].uri
+ self.filelist.remove(uri)
+ self.model.remove(iter)
+
+ def is_nonempty(self):
+ try:
+ self.model.get_iter((0,))
+ except ValueError:
+ return False
+ return True
+
+
+class GladeWindow(object):
+
+ callbacks = {}
+ builder = None
+
+ def __init__(self, builder):
+ '''
+ Init GladeWindow, stores the objects's potential callbacks for later.
+ You have to call connect_signals() when all descendants are ready.'''
+ GladeWindow.builder = builder
+ GladeWindow.callbacks.update(dict([[x, getattr(self, x)]
+ for x in dir(self) if x.startswith('on_')]))
+
+ def __getattr__(self, attribute):
+ '''Allow direct use of window widget.'''
+ widget = GladeWindow.builder.get_object(attribute)
+ if widget is None:
+ raise AttributeError('Widget \'%s\' not found' % attribute)
+ self.__dict__[attribute] = widget # cache result
+ return widget
+
+ @staticmethod
+ def connect_signals():
+ '''Connect all GladeWindow objects to theirs respective signals'''
+ GladeWindow.builder.connect_signals(GladeWindow.callbacks)
+
+
+class PreferencesDialog(GladeWindow, GConfStore):
+
+ basename_patterns = [
+ ('%(.inputname)s', _('Same as input, but replacing the suffix')),
+ ('%(.inputname)s%(.ext)s',
+ _('Same as input, but with an additional suffix')),
+ ('%(track-number)02d-%(title)s', _('Track number - title')),
+ ('%(title)s', _('Track title')),
+ ('%(artist)s-%(title)s', _('Artist - title')),
+ ('Custom', _('Custom filename pattern')),
+ ]
+
+ subfolder_patterns = [
+ ('%(artist)s/%(album)s', _('artist/album')),
+ ('%(artist)s-%(album)s', _('artist-album')),
+ ('%(artist)s - %(album)s', _('artist - album')),
+ ]
+
+ defaults = {
+ 'same-folder-as-input': 1,
+ 'selected-folder': os.path.expanduser('~'),
+ 'create-subfolders': 0,
+ 'subfolder-pattern-index': 0,
+ 'name-pattern-index': 0,
+ 'custom-filename-pattern': '{Track} - {Title}',
+ 'replace-messy-chars': 0,
+ 'output-mime-type': 'audio/x-vorbis',
+ 'output-suffix': '.ogg',
+ 'vorbis-quality': 0.6,
+ 'vorbis-oga-extension': 0,
+ 'mp3-mode': 'vbr',
+ 'mp3-cbr-quality': 192,
+ 'mp3-abr-quality': 192,
+ 'mp3-vbr-quality': 3,
+ 'aac-quality': 192,
+ 'opus-bitrate': 96,
+ 'flac-compression': 8,
+ 'wav-sample-width': 16,
+ 'delete-original': 0,
+ 'output-resample': 0,
+ 'resample-rate': 48000,
+ 'flac-speed': 0, # TODO used ?
+ 'force-mono': 0,
+ 'last-used-folder': None,
+ 'audio-profile': None,
+ 'limit-jobs': 0,
+ 'number-of-jobs': 1,
+ }
+
+ sensitive_names = ['vorbis_quality', 'choose_folder', 'create_subfolders',
+ 'subfolder_pattern', 'jobs_spinbutton', 'resample_hbox',
+ 'force_mono']
+
+ def __init__(self, builder, parent):
+ GladeWindow.__init__(self, builder)
+ GConfStore.__init__(self, '/apps/SoundConverter', self.defaults)
+
+ self.dialog = builder.get_object('prefsdialog')
+ self.dialog.set_transient_for(parent)
+ self.example = builder.get_object('example_filename')
+ self.force_mono = builder.get_object('force_mono')
+
+ self.target_bitrate = None
+ self.convert_setting_from_old_version()
+
+ self.sensitive_widgets = {}
+ for name in self.sensitive_names:
+ self.sensitive_widgets[name] = builder.get_object(name)
+ assert self.sensitive_widgets[name] is not None
+ self.set_widget_initial_values(builder)
+ self.set_sensitive()
+
+ tip = [_('Available patterns:')]
+ for k in sorted(locale_patterns_dict.values()):
+ tip.append(k)
+ self.custom_filename.set_tooltip_text('\n'.join(tip))
+
+ #self.resample_rate.connect('changed', self._on_resample_rate_changed)
+
+ def convert_setting_from_old_version(self):
+ """ try to convert previous settings"""
+
+ # vorbis quality was once stored as an int enum
+ try:
+ self.get_float('vorbis-quality')
+ except gobject.GError:
+ log('deleting old settings...')
+ [self.gconf.unset(self.path(k)) for k in self.defaults.keys()]
+
+ self.gconf.clear_cache()
+
+ def set_widget_initial_values(self, builder):
+
+ self.quality_tabs.set_show_tabs(False)
+
+ if self.get_int('same-folder-as-input'):
+ w = self.same_folder_as_input
+ else:
+ w = self.into_selected_folder
+ w.set_active(True)
+
+ uri = filename_to_uri(self.get_string('selected-folder'))
+ self.target_folder_chooser.set_uri(uri)
+ self.update_selected_folder()
+
+ w = self.create_subfolders
+ w.set_active(self.get_int('create-subfolders'))
+
+ w = self.subfolder_pattern
+ active = self.get_int('subfolder-pattern-index')
+ model = w.get_model()
+ model.clear()
+ for pattern, desc in self.subfolder_patterns:
+ i = model.append()
+ model.set(i, 0, desc)
+ w.set_active(active)
+
+ if self.get_int('replace-messy-chars'):
+ w = self.replace_messy_chars
+ w.set_active(True)
+
+ if self.get_int('delete-original'):
+ self.delete_original.set_active(True)
+
+ mime_type = self.get_string('output-mime-type')
+
+ widgets = ( ('audio/x-vorbis', 'vorbisenc'),
+ ('audio/mpeg' , 'lame'),
+ ('audio/x-flac' , 'flacenc'),
+ ('audio/x-wav' , 'wavenc'),
+ ('audio/x-m4a' , 'faac'),
+ ('audio/ogg; codecs=opus' , 'opusenc'),
+ ('gst-profile' , None),
+ ) # must be in same order in output_mime_type
+
+ # desactivate output if encoder plugin is not present
+ widget = self.output_mime_type
+ model = widget.get_model()
+ assert len(model) == len(widgets), 'model:%d widgets:%d' % (len(model),
+ len(widgets))
+
+ if not self.gstprofile.get_model().get_n_columns():
+ self.gstprofile.set_model(gtk.ListStore(str))
+ cell = gtk.CellRendererText()
+ self.gstprofile.pack_start(cell)
+ self.gstprofile.add_attribute(cell,'text',0)
+ self.gstprofile.set_active(0)
+
+ # check if we can found the stored audio profile
+ found_profile = False
+ stored_profile = self.get_string('audio-profile')
+ for i, profile in enumerate(audio_profiles_list):
+ description, extension, pipeline = profile
+ self.gstprofile.get_model().append(['%s (.%s)' % (description, extension)])
+ if description == stored_profile:
+ self.gstprofile.set_active(i)
+ found_profile = True
+ if not found_profile and stored_profile:
+ # reset default output
+ log('Cannot find audio profile "%s", resetting to default output.'
+ % stored_profile)
+ self.set_string('audio-profile', '')
+ self.gstprofile.set_active(0)
+ mime_type = self.defaults['output-mime-type']
+
+ self.present_mime_types = []
+ i = 0
+ model = self.output_mime_type.get_model()
+ for b in widgets:
+ mime, encoder_name = b
+ # valid encoder?
+ encoder_present = encoder_name and encoder_name in available_elements
+ # valid profile?
+ profile_present = mime == 'gst-profile' and audio_profiles_list
+ if encoder_present or profile_present:
+ # add to supported outputs
+ self.present_mime_types.append(mime)
+ i += 1
+ else:
+ # remove it.
+ del model[i]
+ if mime_type == mime:
+ mime_type = self.defaults['output-mime-type']
+ for i, mime in enumerate(self.present_mime_types):
+ if mime_type == mime:
+ widget.set_active(i)
+ self.change_mime_type(mime_type)
+
+ # display information about mp3 encoding
+ if 'lame' not in available_elements:
+ w = self.lame_absent
+ w.show()
+
+ w = self.vorbis_quality
+ quality = self.get_float('vorbis-quality')
+ quality_setting = {0: 0, 0.2: 1, 0.4: 2, 0.6: 3, 0.8: 4, 1.0: 5}
+ w.set_active(-1)
+ for k, v in quality_setting.iteritems():
+ if abs(quality - k) < 0.01:
+ self.vorbis_quality.set_active(v)
+ if self.get_int('vorbis-oga-extension'):
+ self.vorbis_oga_extension.set_active(True)
+
+ w = self.aac_quality
+ quality = self.get_int('aac-quality')
+ quality_setting = {64: 0, 96: 1, 128: 2, 192: 3, 256: 4, 320: 5}
+ w.set_active(quality_setting.get(quality, -1))
+
+ w = self.opus_quality
+ quality = self.get_int('opus-bitrate')
+ quality_setting = {48: 0, 64: 1, 96: 2, 128: 3, 160: 4, 192: 5}
+ w.set_active(quality_setting.get(quality, -1))
+
+ w = self.flac_compression
+ quality = self.get_int('flac-compression')
+ quality_setting = {0: 0, 5: 1, 8: 2}
+ w.set_active(quality_setting.get(quality, -1))
+
+ w = self.wav_sample_width
+ quality = self.get_int('wav-sample-width')
+ quality_setting = {8: 0, 16: 1, 32: 2}
+ w.set_active(quality_setting.get(quality, -1))
+
+ self.mp3_quality = self.mp3_quality
+ self.mp3_mode = self.mp3_mode
+
+ mode = self.get_string('mp3-mode')
+ self.change_mp3_mode(mode)
+
+ w = self.basename_pattern
+ active = self.get_int('name-pattern-index')
+ model = w.get_model()
+ model.clear()
+ for pattern, desc in self.basename_patterns:
+ iter = model.append()
+ model.set(iter, 0, desc)
+ w.set_active(active)
+
+ self.custom_filename.set_text(self.get_string(
+ 'custom-filename-pattern'))
+ if self.basename_pattern.get_active() == len(self.basename_patterns)-1:
+ self.custom_filename_box.set_sensitive(True)
+ else:
+ self.custom_filename_box.set_sensitive(False)
+
+
+ self.resample_toggle.set_active(self.get_int('output-resample'))
+
+ cell = gtk.CellRendererText()
+ self.resample_rate.pack_start(cell, True)
+ self.resample_rate.add_attribute(cell, 'text', 0)
+ rates = [8000, 11025, 22050, 44100, 48000, 96000]
+ rate = self.get_int('resample-rate')
+ try:
+ idx = rates.index(rate)
+ except ValueError:
+ idx = -1
+ self.resample_rate.set_active(idx)
+
+ self.force_mono.set_active(self.get_int('force-mono'))
+
+ self.jobs.set_active(self.get_int('limit-jobs'))
+ self.jobs_spinbutton.set_value(self.get_int('number-of-jobs'))
+
+ self.update_jobs()
+ self.update_example()
+
+ def update_selected_folder(self):
+ self.into_selected_folder.set_use_underline(False)
+ self.into_selected_folder.set_label(_('Into folder %s') %
+ beautify_uri(self.get_string('selected-folder')))
+
+ def get_bitrate_from_settings(self):
+ bitrate = 0
+ aprox = True
+ mode = self.get_string('mp3-mode')
+
+ mime_type = self.get_string('output-mime-type')
+
+ if mime_type == 'audio/x-vorbis':
+ quality = self.get_float('vorbis-quality')*10
+ quality = int(quality)
+ bitrates = (64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 500)
+ bitrate = bitrates[quality]
+
+ elif mime_type == 'audio/x-m4a':
+ bitrate = self.get_int('aac-quality')
+
+ elif mime_type == 'audio/ogg; codecs=opus':
+ bitrate = self.get_int('opus-bitrate')
+
+ elif mime_type == 'audio/mpeg':
+ quality = {
+ 'cbr': 'mp3-cbr-quality',
+ 'abr': 'mp3-abr-quality',
+ 'vbr': 'mp3-vbr-quality'
+ }
+ bitrate = self.get_int(quality[mode])
+ if mode == 'vbr':
+ # hum, not really, but who cares? :)
+ bitrates = (320, 256, 224, 192, 160, 128, 112, 96, 80, 64)
+ bitrate = bitrates[bitrate]
+ if mode == 'cbr':
+ aprox = False
+
+ if bitrate:
+ if aprox:
+ return '~%d kbps' % bitrate
+ else:
+ return '%d kbps' % bitrate
+ else:
+ return 'N/A'
+
+ def update_example(self):
+ sound_file = SoundFile('foo/bar.flac')
+ sound_file.tags.update({'track-number': 1, 'track-count': 99})
+ sound_file.tags.update({'disc-number': 2, 'disc-count': 9})
+ sound_file.tags.update(locale_patterns_dict)
+
+ s = gobject.markup_escape_text(beautify_uri(
+ self.generate_filename(sound_file, for_display=True)))
+ p = 0
+ replaces = []
+
+ while 1:
+ b = s.find('{', p)
+ if b == -1:
+ break
+ e = s.find('}', b)
+
+ tag = s[b:e+1]
+ if tag.lower() in [
+ v.lower() for v in locale_patterns_dict.values()]:
+ k = tag
+ l = k.replace('{', '<b>{')
+ l = l.replace('}', '}</b>')
+ replaces.append([k, l])
+ else:
+ k = tag
+ l = k.replace('{', '<span foreground=\'red\'><i>{')
+ l = l.replace('}', '}</i></span>')
+ replaces.append([k, l])
+ p = b+1
+
+ for k, l in replaces:
+ s = s.replace(k, l)
+
+ self.example.set_markup(s)
+
+ markup = '<small>%s</small>' % (_('Target bitrate: %s') %
+ self.get_bitrate_from_settings())
+ self.aprox_bitrate.set_markup(markup)
+
+ def get_output_suffix(self):
+ self.gconf.clear_cache()
+ output_type = self.get_string('output-mime-type')
+ profile = self.get_string('audio-profile')
+ profile_ext = audio_profiles_dict[profile][1] if profile else ''
+ output_suffix = {
+ 'audio/x-vorbis': '.ogg',
+ 'audio/x-flac': '.flac',
+ 'audio/x-wav': '.wav',
+ 'audio/mpeg': '.mp3',
+ 'audio/x-m4a': '.m4a',
+ 'audio/ogg; codecs=opus': '.opus',
+ 'gst-profile': '.' + profile_ext,
+ }.get(output_type, '.?')
+ if output_suffix == '.ogg' and self.get_int('vorbis-oga-extension'):
+ output_suffix = '.oga'
+ return output_suffix
+
+ def generate_filename(self, sound_file, for_display=False):
+ generator = TargetNameGenerator()
+ generator.suffix = self.get_output_suffix()
+
+ if not self.get_int('same-folder-as-input'):
+ folder = self.get_string('selected-folder')
+ folder = filename_to_uri(folder)
+ generator.folder = folder
+
+ if self.get_int('create-subfolders'):
+ generator.subfolders = self.get_subfolder_pattern()
+
+ generator.basename = self.get_basename_pattern()
+
+ if for_display:
+ generator.replace_messy_chars = False
+ return unquote_filename(generator.get_target_name(sound_file))
+ else:
+ generator.replace_messy_chars = self.get_int('replace-messy-chars')
+ return generator.get_target_name(sound_file)
+
+ def generate_temp_filename(self, soundfile):
+ folder = dirname(soundfile.uri)
+ if not self.get_int('same-folder-as-input'):
+ folder = self.get_string('selected-folder')
+ folder = filename_to_uri(folder)
+ return folder + '/' + basename(soundfile.filename)
+
+ def process_custom_pattern(self, pattern):
+ for k in custom_patterns:
+ pattern = pattern.replace(k, custom_patterns[k])
+ return pattern
+
+ def set_sensitive(self):
+ for widget in self.sensitive_widgets.values():
+ widget.set_sensitive(False)
+
+ x = self.get_int('same-folder-as-input')
+ for name in ['choose_folder', 'create_subfolders',
+ 'subfolder_pattern']:
+ self.sensitive_widgets[name].set_sensitive(not x)
+
+ self.sensitive_widgets['vorbis_quality'].set_sensitive(
+ self.get_string('output-mime-type') == 'audio/x-vorbis')
+
+ self.sensitive_widgets['jobs_spinbutton'].set_sensitive(
+ self.get_int('limit-jobs'))
+
+ if self.get_string('output-mime-type') == 'gst-profile':
+ self.sensitive_widgets['resample_hbox'].set_sensitive(False)
+ self.sensitive_widgets['force_mono'].set_sensitive(False)
+ else:
+ self.sensitive_widgets['resample_hbox'].set_sensitive(True)
+ self.sensitive_widgets['force_mono'].set_sensitive(True)
+
+
+ def run(self):
+ self.dialog.run()
+ self.dialog.hide()
+
+ def on_delete_original_toggled(self, button):
+ if button.get_active():
+ self.set_int('delete-original', 1)
+ else:
+ self.set_int('delete-original', 0)
+
+ def on_same_folder_as_input_toggled(self, button):
+ if button.get_active():
+ self.set_int('same-folder-as-input', 1)
+ self.set_sensitive()
+ self.update_example()
+
+ def on_into_selected_folder_toggled(self, button):
+ if button.get_active():
+ self.set_int('same-folder-as-input', 0)
+ self.set_sensitive()
+ self.update_example()
+
+ def on_choose_folder_clicked(self, button):
+ ret = self.target_folder_chooser.run()
+ folder = self.target_folder_chooser.get_uri()
+ self.target_folder_chooser.hide()
+ if ret == gtk.RESPONSE_OK:
+ if folder:
+ self.set_string('selected-folder', urllib.unquote(folder))
+ self.update_selected_folder()
+ self.update_example()
+
+ def on_create_subfolders_toggled(self, button):
+ if button.get_active():
+ self.set_int('create-subfolders', 1)
+ else:
+ self.set_int('create-subfolders', 0)
+ self.update_example()
+
+ def on_subfolder_pattern_changed(self, combobox):
+ self.set_int('subfolder-pattern-index', combobox.get_active())
+ self.update_example()
+
+ def get_subfolder_pattern(self):
+ index = self.get_int('subfolder-pattern-index')
+ if index < 0 or index >= len(self.subfolder_patterns):
+ index = 0
+ return self.subfolder_patterns[index][0]
+
+ def on_basename_pattern_changed(self, combobox):
+ self.set_int('name-pattern-index', combobox.get_active())
+ if combobox.get_active() == len(self.basename_patterns)-1:
+ self.custom_filename_box.set_sensitive(True)
+ else:
+ self.custom_filename_box.set_sensitive(False)
+ self.update_example()
+
+ def get_basename_pattern(self):
+ index = self.get_int('name-pattern-index')
+ if index < 0 or index >= len(self.basename_patterns):
+ index = 0
+ if self.basename_pattern.get_active() == len(self.basename_patterns)-1:
+ return self.process_custom_pattern(self.custom_filename.get_text())
+ else:
+ return self.basename_patterns[index][0]
+
+ def on_custom_filename_changed(self, entry):
+ self.set_string('custom-filename-pattern', entry.get_text())
+ self.update_example()
+
+ def on_replace_messy_chars_toggled(self, button):
+ if button.get_active():
+ self.set_int('replace-messy-chars', 1)
+ else:
+ self.set_int('replace-messy-chars', 0)
+ self.update_example()
+
+ def change_mime_type(self, mime_type):
+ self.set_string('output-mime-type', mime_type)
+ self.set_sensitive()
+ self.update_example()
+ tabs = {
+ 'audio/x-vorbis': 0,
+ 'audio/mpeg': 1,
+ 'audio/x-flac': 2,
+ 'audio/x-wav': 3,
+ 'audio/x-m4a': 4,
+ 'audio/ogg; codecs=opus': 5,
+ 'gst-profile': 6,
+ }
+ self.quality_tabs.set_current_page(tabs[mime_type])
+
+ def on_output_mime_type_changed(self, combo):
+ self.change_mime_type(
+ self.present_mime_types[combo.get_active()]
+ )
+
+ def on_output_mime_type_ogg_vorbis_toggled(self, button):
+ if button.get_active():
+ self.change_mime_type('audio/x-vorbis')
+
+ def on_output_mime_type_flac_toggled(self, button):
+ if button.get_active():
+ self.change_mime_type('audio/x-flac')
+
+ def on_output_mime_type_wav_toggled(self, button):
+ if button.get_active():
+ self.change_mime_type('audio/x-wav')
+
+ def on_output_mime_type_mp3_toggled(self, button):
+ if button.get_active():
+ self.change_mime_type('audio/mpeg')
+
+ def on_output_mime_type_aac_toggled(self, button):
+ if button.get_active():
+ self.change_mime_type('audio/x-m4a')
+
+ def on_output_mime_type_opus_toggled(self, button):
+ if button.get_active():
+ self.change_mime_type('audio/ogg; codecs=opus')
+
+ def on_vorbis_quality_changed(self, combobox):
+ if combobox.get_active() == -1:
+ return # just de-selectionning
+ quality = (0, 0.2, 0.4, 0.6, 0.8, 1.0)
+ fquality = quality[combobox.get_active()]
+ self.set_float('vorbis-quality', fquality)
+ self.hscale_vorbis_quality.set_value(fquality*10)
+ self.update_example()
+
+ def on_hscale_vorbis_quality_value_changed(self, hscale):
+ fquality = hscale.get_value()
+ if abs(self.get_float('vorbis-quality') - fquality/10.0) < 0.001:
+ return # already at right value
+ self.set_float('vorbis-quality', fquality/10.0)
+ self.vorbis_quality.set_active(-1)
+ self.update_example()
+
+ def on_vorbis_oga_extension_toggled(self, toggle):
+ self.set_int('vorbis-oga-extension', toggle.get_active())
+ self.update_example()
+
+ def on_aac_quality_changed(self, combobox):
+ quality = (64, 96, 128, 192, 256, 320)
+ self.set_int('aac-quality', quality[combobox.get_active()])
+ self.update_example()
+
+ def on_opus_quality_changed(self, combobox):
+ quality = (48, 64, 96, 128, 160, 192)
+ self.set_int('opus-bitrate', quality[combobox.get_active()])
+ self.update_example()
+
+ def on_wav_sample_width_changed(self, combobox):
+ quality = (8, 16, 32)
+ self.set_int('wav-sample-width', quality[combobox.get_active()])
+ self.update_example()
+
+ def on_flac_compression_changed(self, combobox):
+ quality = (0, 5, 8)
+ self.set_int('flac-compression', quality[combobox.get_active()])
+ self.update_example()
+
+ def on_gstprofile_changed(self, combobox):
+ profile = audio_profiles_list[combobox.get_active()]
+ description, extension, pipeline = profile
+ self.set_string('audio-profile', description)
+ self.update_example()
+
+ def on_force_mono_toggle(self, button):
+ if button.get_active():
+ self.set_int('force-mono', 1)
+ else:
+ self.set_int('force-mono', 0)
+ self.update_example()
+
+ def change_mp3_mode(self, mode):
+
+ keys = {'cbr': 0, 'abr': 1, 'vbr': 2}
+ self.mp3_mode.set_active(keys[mode])
+
+ keys = {
+ 'cbr': 'mp3-cbr-quality',
+ 'abr': 'mp3-abr-quality',
+ 'vbr': 'mp3-vbr-quality',
+ }
+ quality = self.get_int(keys[mode])
+
+ quality_to_preset = {
+ 'cbr': {64: 0, 96: 1, 128: 2, 192: 3, 256: 4, 320: 5},
+ 'abr': {64: 0, 96: 1, 128: 2, 192: 3, 256: 4, 320: 5},
+ 'vbr': {9: 0, 7: 1, 5: 2, 3: 3, 1: 4, 0: 5}, # inverted !
+ }
+
+ range_ = {
+ 'cbr': 14,
+ 'abr': 14,
+ 'vbr': 10,
+ }
+ self.hscale_mp3.set_range(0, range_[mode])
+
+ if quality in quality_to_preset[mode]:
+ self.mp3_quality.set_active(quality_to_preset[mode][quality])
+ self.update_example()
+
+ def on_mp3_mode_changed(self, combobox):
+ mode = ('cbr', 'abr', 'vbr')[combobox.get_active()]
+ self.set_string('mp3-mode', mode)
+ self.change_mp3_mode(mode)
+
+ def on_mp3_quality_changed(self, combobox):
+ keys = {
+ 'cbr': 'mp3-cbr-quality',
+ 'abr': 'mp3-abr-quality',
+ 'vbr': 'mp3-vbr-quality'
+ }
+ quality = {
+ 'cbr': (64, 96, 128, 192, 256, 320),
+ 'abr': (64, 96, 128, 192, 256, 320),
+ 'vbr': (9, 7, 5, 3, 1, 0),
+ }
+ mode = self.get_string('mp3-mode')
+ self.set_int(keys[mode], quality[mode][combobox.get_active()])
+ self.update_example()
+
+ def on_hscale_mp3_value_changed(self, widget):
+ mode = self.get_string('mp3-mode')
+ keys = {
+ 'cbr': 'mp3-cbr-quality',
+ 'abr': 'mp3-abr-quality',
+ 'vbr': 'mp3-vbr-quality'
+ }
+ quality = {
+ 'cbr': (32, 40, 48, 56, 64, 80, 96, 112,
+ 128, 160, 192, 224, 256, 320),
+ 'abr': (32, 40, 48, 56, 64, 80, 96, 112,
+ 128, 160, 192, 224, 256, 320),
+ 'vbr': (9, 8, 7, 6, 5, 4, 3, 2, 1, 0),
+ }
+ self.set_int(keys[mode], quality[mode][int(widget.get_value())])
+ self.mp3_quality.set_active(-1)
+ self.update_example()
+
+ def on_resample_rate_changed(self, combobox):
+ model = combobox.get_model()
+ iter = combobox.get_active_iter()
+ changeto = model.get_value(iter, 0)
+ self.set_int('resample-rate', int(changeto))
+
+ def on_resample_toggle(self, rstoggle):
+ self.set_int('output-resample', rstoggle.get_active())
+ self.resample_rate.set_sensitive(rstoggle.get_active())
+
+ def on_jobs_toggled(self, jtoggle):
+ self.set_int('limit-jobs', jtoggle.get_active())
+ self.jobs_spinbutton.set_sensitive(jtoggle.get_active())
+ self.update_jobs()
+
+ def on_jobs_spinbutton_value_changed(self, jspinbutton):
+ self.set_int('number-of-jobs', int(jspinbutton.get_value()))
+ self.update_jobs()
+
+ def update_jobs(self):
+ if self.get_int('limit-jobs'):
+ settings['jobs'] = self.get_int('number-of-jobs')
+ else:
+ settings['jobs'] = settings['max-jobs']
+ self.set_sensitive()
+
+
+class CustomFileChooser:
+ """
+ Custom file chooser.\n
+ """
+
+ def __init__(self, builder, parent):
+ """
+ Constructor
+ Load glade object, create a combobox
+ """
+ self.dlg = builder.get_object('custom_file_chooser')
+ self.dlg.set_title(_('Open a file'))
+ self.dlg.set_transient_for(parent)
+
+ # setup
+ self.fcw = builder.get_object('filechooserwidget')
+ self.fcw.set_local_only(not use_gnomevfs)
+ self.fcw.set_select_multiple(True)
+
+ self.pattern = []
+
+ # Create combobox model
+ self.combo = builder.get_object('filtercombo')
+ self.combo.connect('changed', self.on_combo_changed)
+ self.store = gtk.ListStore(str)
+ self.combo.set_model(self.store)
+ combo_rend = gtk.CellRendererText()
+ self.combo.pack_start(combo_rend, True)
+ self.combo.add_attribute(combo_rend, 'text', 0)
+
+ # TODO: get all (gstreamer) knew files
+ for name, pattern in filepattern:
+ self.add_pattern(name, pattern)
+ self.combo.set_active(0)
+
+ def add_pattern(self, name, pat):
+ """
+ Add a new pattern to the combobox.
+ @param name: The pattern name.
+ @type name: string
+ @param pat: the pattern
+ @type pat: string
+ """
+ self.pattern.append(pat)
+ self.store.append(['%s (%s)' % (name, pat)])
+
+ def filter_cb(self, info, pattern):
+ filename = info[2]
+ return filename.lower().endswith(pattern[1:])
+
+ def on_combo_changed(self, w):
+ """
+ Callback for combobox 'changed' signal\n
+ Set a new filter for the filechooserwidget
+ """
+ filter = gtk.FileFilter()
+ active = self.combo.get_active()
+ if active:
+ filter.add_custom(gtk.FILE_FILTER_DISPLAY_NAME, self.filter_cb,
+ self.pattern[self.combo.get_active()])
+ else:
+ filter.add_pattern('*.*')
+ self.fcw.set_filter(filter)
+
+ def __getattr__(self, attr):
+ """
+ Redirect all missing attributes/methods
+ to dialog.
+ """
+ try:
+ # defaut to dialog attributes
+ return getattr(self.dlg, attr)
+ except AttributeError:
+ # fail back to inner file chooser widget
+ return getattr(self.fcw, attr)
+
+_old_progress = 0
+_old_total = 0
+
+class SoundConverterWindow(GladeWindow):
+
+ """Main application class."""
+
+ sensitive_names = ['remove', 'clearlist',
+ 'toolbutton_clearlist', 'convert_button']
+ unsensitive_when_converting = ['remove', 'clearlist', 'prefs_button',
+ 'toolbutton_addfile', 'toolbutton_addfolder', 'convert_button',
+ 'toolbutton_clearlist', 'filelist', 'menubar']
+
+ def __init__(self, builder):
+ self.paused_time = 0
+ GladeWindow.__init__(self, builder)
+
+ self.widget = builder.get_object('window')
+ self.prefs = PreferencesDialog(builder, self.widget)
+ self.addchooser = CustomFileChooser(builder, self.widget)
+ GladeWindow.connect_signals()
+
+ self.filelist = FileList(self, builder)
+ self.filelist_selection = self.filelist.widget.get_selection()
+ self.filelist_selection.connect('changed', self.selection_changed)
+ self.existsdialog = builder.get_object('existsdialog')
+ self.existsdialog.message = builder.get_object('exists_message')
+ self.existsdialog.apply_to_all = builder.get_object('apply_to_all')
+
+ self.addfolderchooser = gtk.FileChooserDialog(_('Add Folder...'),
+ self.widget, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
+ gtk.RESPONSE_OK))
+ self.addfolderchooser.set_select_multiple(True)
+ self.addfolderchooser.set_local_only(not use_gnomevfs)
+
+ self.combo = gtk.ComboBox()
+ self.store = gtk.ListStore(str)
+ self.combo.set_model(self.store)
+ combo_rend = gtk.CellRendererText()
+ self.combo.pack_start(combo_rend, True)
+ self.combo.add_attribute(combo_rend, 'text', 0)
+
+ # TODO: get all (gstreamer) knew files
+ for files in filepattern:
+ self.store.append(['%s (%s)' % (files[0], files[1])])
+
+ self.combo.set_active(0)
+ self.addfolderchooser.set_extra_widget(self.combo)
+
+ self.aboutdialog.set_property('name', NAME)
+ self.aboutdialog.set_property('version', VERSION)
+ self.aboutdialog.set_transient_for(self.widget)
+
+ self.converter = ConverterQueue(self)
+
+ self.sensitive_widgets = {}
+ for name in self.sensitive_names:
+ self.sensitive_widgets[name] = builder.get_object(name)
+ for name in self.unsensitive_when_converting:
+ self.sensitive_widgets[name] = builder.get_object(name)
+
+ self.set_sensitive()
+ self.set_status()
+
+
+ #msg = _('The output file <i>%s</i>\n exists already.\n '\
+ # 'Do you want to skip the file, overwrite it or'\
+ # ' cancel the conversion?\n') % '/foo/bar/baz'
+ vbox = self.vbox_status
+ self.msg_area = msg_area = MessageArea()
+ #msg_area.add_button('_Overwrite', 1)
+ #msg_area.add_button('_Skip', 2)
+ msg_area.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE)
+ #checkbox = gtk.CheckButton('Apply to _all queue')
+ #checkbox.show()
+ #msg_area.set_text_and_icon(gtk.STOCK_DIALOG_ERROR, 'Access Denied', msg, checkbox)
+
+ #msg_area.connect("response", self.OnMessageAreaReponse, msg_area)
+ #msg_area.connect("close", self.OnMessageAreaClose, msg_area)
+ vbox.pack_start(msg_area, False, False)
+ #msg_area.show()
+
+
+
+ # This bit of code constructs a list of methods for binding to Gtk+
+ # signals. This way, we don't have to maintain a list manually,
+ # saving editing effort. It's enough to add a method to the suitable
+ # class and give the same name in the .glade file.
+
+ def __getattr__(self, attribute):
+ """Allow direct use of window widget."""
+ widget = self.builder.get_object(attribute)
+ if widget is None:
+ raise AttributeError('Widget \'%s\' not found' % attribute)
+ self.__dict__[attribute] = widget # cache result
+ return widget
+
+ def close(self, *args):
+ debug('closing...')
+ self.filelist.abort()
+ self.converter.abort()
+ self.widget.hide_all()
+ self.widget.destroy()
+ # wait one second...
+ # yes, this sucks badly, but signals can still be called by gstreamer
+ # so wait a bit for things to calm down, and quit.
+ gtk_sleep(1)
+ gtk.main_quit()
+ return True
+
+ on_window_delete_event = close
+ on_quit_activate = close
+ on_quit_button_clicked = close
+
+ def on_add_activate(self, *args):
+ last_folder = self.prefs.get_string('last-used-folder')
+ if last_folder:
+ self.addchooser.set_current_folder_uri(last_folder)
+
+ ret = self.addchooser.run()
+ folder = self.addchooser.get_current_folder_uri()
+ self.addchooser.hide()
+ if ret == gtk.RESPONSE_OK and folder:
+ self.filelist.add_uris(self.addchooser.get_uris())
+ self.prefs.set_string('last-used-folder', folder)
+ self.set_sensitive()
+
+ def on_addfolder_activate(self, *args):
+ last_folder = self.prefs.get_string('last-used-folder')
+ if last_folder:
+ self.addfolderchooser.set_current_folder_uri(last_folder)
+
+ ret = self.addfolderchooser.run()
+ folders = self.addfolderchooser.get_uris()
+ folder = self.addfolderchooser.get_current_folder_uri()
+ self.addfolderchooser.hide()
+ if ret == gtk.RESPONSE_OK:
+ extensions = None
+ if self.combo.get_active():
+ patterns = filepattern[self.combo.get_active()][1].split(';')
+ extensions = [os.path.splitext(p)[1] for p in patterns]
+ self.filelist.add_uris(folders, extensions=extensions)
+ if folder:
+ self.prefs.set_string('last-used-folder', folder)
+
+ self.set_sensitive()
+
+ def on_remove_activate(self, *args):
+ model, paths = self.filelist_selection.get_selected_rows()
+ while paths:
+ # Remove files
+ childpath = model.convert_path_to_child_path(paths[0])
+ i = self.filelist.model.get_iter(childpath)
+ self.filelist.remove(i)
+ model, paths = self.filelist_selection.get_selected_rows()
+ # re-assign row numbers
+ files = self.filelist.get_files()
+ for i, sound_file in enumerate(files):
+ sound_file.filelist_row = i
+ self.set_sensitive()
+
+ def on_clearlist_activate(self, *args):
+ self.filelist.model.clear()
+ self.filelist.filelist.clear()
+ self.set_sensitive()
+ self.set_status()
+
+ def on_progress(self):
+ if self.pulse_progress > 0: # still waiting for tags
+ self.set_progress(self.pulse_progress, display_time=False)
+ return True
+ if self.pulse_progress == -1: # still waiting for add
+ self.set_progress()
+ return True
+ if self.pulse_progress == False: # conversion ended
+ return False
+
+ perfile = {}
+ for s in self.filelist.get_files():
+ perfile[s] = None
+ running, progress = self.converter.get_progress(perfile)
+
+ if running:
+ self.set_progress(progress)
+ for sound_file, taskprogress in perfile.iteritems():
+ if taskprogress > 0.0:
+ sound_file.progress = taskprogress
+ self.set_file_progress(sound_file, taskprogress)
+ if taskprogress is None and sound_file.progress:
+ self.set_file_progress(sound_file, 1.0)
+ sound_file.progress = None
+ return running
+
+ def do_convert(self):
+ self.pulse_progress = -1
+ gobject.timeout_add(100, self.on_progress)
+ self.progressbar.set_text(_('Preparing conversion...'))
+ files = self.filelist.get_files()
+ total = len(files)
+ for i, sound_file in enumerate(files):
+ gtk_iteration()
+ self.pulse_progress = float(i)/total # TODO: still needed?
+ sound_file.progress = None
+ self.converter.add(sound_file)
+ # all was OK
+ self.set_status('')
+ self.pulse_progress = None
+ self.converter.start()
+ self.set_sensitive()
+
+ def on_convert_button_clicked(self, *args):
+ # reset and show progress bar
+ self.set_progress(0)
+ self.progress_frame.show()
+ self.status_frame.hide()
+ self.progress_time = time.time()
+ self.set_progress()
+ self.set_status(_('Converting'))
+ for soundfile in self.filelist.get_files():
+ self.set_file_progress(soundfile, 0.0)
+ # start conversion
+ self.do_convert()
+ # update ui
+ self.set_sensitive()
+
+ def on_button_pause_clicked(self, *args):
+ self.converter.toggle_pause(not self.converter.paused)
+
+ if self.converter.paused:
+ self.current_pause_start = time.time()
+ else:
+ self.paused_time += time.time() - self.current_pause_start
+
+ def on_button_cancel_clicked(self, *args):
+ self.converter.abort()
+ self.set_status(_('Canceled'))
+ self.set_sensitive()
+ self.conversion_ended()
+
+ def on_select_all_activate(self, *args):
+ self.filelist.widget.get_selection().select_all()
+
+ def on_clear_activate(self, *args):
+ self.filelist.widget.get_selection().unselect_all()
+
+ def on_preferences_activate(self, *args):
+ self.prefs.run()
+
+ on_prefs_button_clicked = on_preferences_activate
+
+ def on_about_activate(self, *args):
+ about = self.aboutdialog
+ about.set_property('name', NAME)
+ about.set_property('version', VERSION)
+ about.set_transient_for(self.widget)
+ #TODO: about.set_property('translator_credits', TRANSLATORS)
+ about.show()
+
+ def on_aboutdialog_response(self, *args):
+ self.aboutdialog.hide()
+
+ def selection_changed(self, *args):
+ self.set_sensitive()
+
+ def conversion_ended(self):
+ self.pulse_progress = False
+ self.progress_frame.hide()
+ self.filelist.hide_row_progress()
+ self.status_frame.show()
+ self.widget.set_sensitive(True)
+ try:
+ from gi.repository import Unity
+ launcher = Unity.LauncherEntry.get_for_desktop_id ("soundconverter.desktop")
+ launcher.set_property("progress_visible", False)
+ except ImportError:
+ pass
+
+
+ def set_widget_sensitive(self, name, sensitivity):
+ self.sensitive_widgets[name].set_sensitive(sensitivity)
+
+ def set_sensitive(self):
+ """update the sensitive state of UI for the current state"""
+ for w in self.unsensitive_when_converting:
+ self.set_widget_sensitive(w, not self.converter.running)
+
+ if not self.converter.running:
+ self.set_widget_sensitive('remove',
+ self.filelist_selection.count_selected_rows() > 0)
+ self.set_widget_sensitive('convert_button',
+ self.filelist.is_nonempty())
+
+ def set_file_progress(self, sound_file, progress):
+ row = sound_file.filelist_row
+ self.filelist.set_row_progress(row, progress)
+
+ def set_progress(self, fraction=None, display_time=True):
+ if not fraction:
+ if fraction is None:
+ self.progressbar.pulse()
+ else:
+ self.progressbar.set_fraction(0)
+ self.progressbar.set_text('')
+ self.progressfile.set_markup('')
+ self.filelist.hide_row_progress()
+ return
+
+ if self.converter.paused:
+ self.progressbar.set_text(_('Paused'))
+ return
+
+ fraction = min(max(fraction, 0.0), 1.0)
+ self.progressbar.set_fraction(fraction)
+
+ if display_time:
+ t = time.time() - self.converter.run_start_time - \
+ self.paused_time
+ if (t < 1):
+ # wait a bit not to display crap
+ self.progressbar.pulse()
+ return
+
+ r = (t / fraction - t)
+ s = max(r % 60, 1)
+ m = r / 60
+
+ remaining = _('%d:%02d left') % (m, s)
+ self.progressbar.set_text(remaining)
+ self.progress_time = time.time()
+
+ def set_status(self, text=None):
+ if not text:
+ text = _('Ready')
+ self.statustext.set_markup(text)
+ gtk_iteration()
+
+ def is_active(self):
+ return self.widget.is_active()
+
+
+NAME = VERSION = None
+win = None
+
+def gui_main(name, version, gladefile, input_files):
+ global NAME, VERSION
+ NAME, VERSION = name, version
+ gnome.init(name, version)
+ builder = gtk.Builder()
+ builder.set_translation_domain(name.lower())
+ builder.add_from_file(gladefile)
+
+ global win
+ win = SoundConverterWindow(builder)
+ import error
+ error.set_error_handler(ErrorDialog(builder))
+
+ #error_dialog = MsgAreaErrorDialog(builder)
+ #error_dialog.msg_area = win.msg_area
+ #error.set_error_handler(error_dialog)
+
+ gobject.idle_add(win.filelist.add_uris, input_files)
+ win.set_sensitive()
+ gtk.main()