オーディオプレーヤ "Exaile"のリッピングプラグイン(pyRipper)
Exaileのプラグインを作ってみました。(Linux環境でのみ動作確認を行っています)
pyRipper.pyというファイルのみです。
pyRipper.pyを ~/.exaile/plugins/に置き、Exaileを再起動してください。
pyRipper.pyを ~/.exaile/plugins/に置き、Exaileを再起動してください。
Exaile起動後、PluginManagerを開いて、Enableチェックボックスを
ONにすることで有効になります。
ONにすることで有効になります。
実行するためには、以下のものが必要です。
- exaile
- python
- pygtk
- cdparanoia
- oggenc
日本語の扱いについて
Exaileでアルバム名やトラック名が文字化けしている場合には
エンコードしたOGGファイルの名前も文字化けしているかもしれません。
日本語の扱いについては今後対応していこうと思っています。
Exaileでアルバム名やトラック名が文字化けしている場合には
エンコードしたOGGファイルの名前も文字化けしているかもしれません。
日本語の扱いについては今後対応していこうと思っています。
このページの下のほうにスクリプトファイルを添付しました。
import gtk import plugins import os import os.path import threading import time import signal PLUGIN_NAME = 'Audio CD Ripper Plugin' PLUGIN_AUTHORS = ['kani <jkani4@gmail.com>'] PLUGIN_VERSION = '0.1' PLUGIN_ENABLED = False PLUGIN_DESCRIPTION = r'''This plugin converts audio data into various format. But now, this can convert to Ogg Vorbis only. ''' b = gtk.Button() PLUGIN_ICON = b.render_icon(gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU) b.destroy() STR_RIPPER_COMMAND = 'cdparanoia' STR_OGG_ENCODER_COMMAND = 'oggenc' # # Functions for geting/setting configuration # ## def get_conf_str(name, defStr): return APP.settings.get_str(name, default=defStr, plugin=plugins.name(__file__)) ## def set_conf_str(name, someStr): APP.settings.set_str(name, someStr, plugin=plugins.name(__file__)) ## def get_conf_int(name, defVal): return APP.settings.get_int(name, default=defVal, plugin=plugins.name(__file__)) ## def set_conf_int(name, someVal): APP.settings.set_int(name, someVal, plugin=plugins.name(__file__)) ## def get_conf_bool(name, defVal): return APP.settings.get_boolean(name, default=defVal, plugin=plugins.name(__file__)) ## def set_conf_bool(name, someFlag): APP.settings.set_boolean(name, someFlag, plugin=plugins.name(__file__)) ## def get_conf_tmp_path(): return get_conf_str('TEMP_DIRECTORY_PATH', '~/') ## def set_conf_tmp_path(newPath): set_conf_str('TEMP_DIRECTORY_PATH', newPath) ## def get_conf_out_path(): return get_conf_str('OUT_DIRECTORY_PATH', '~/') ## def set_conf_out_path(newPath): set_conf_str('OUT_DIRECTORY_PATH', newPath) ## def get_conf_delete_wav(): return get_conf_bool('IS_DELETE_WAV', True) ## def set_conf_delete_wav(newFlag): set_conf_bool('IS_DELETE_WAV', newFlag) ## def get_conf_ripper_path(): return get_conf_str('RIPPER_PATH', '/usr/bin') ## def set_conf_ripper_path(newPath): set_conf_str('RIPPER_PATH', newPath) ## def get_conf_ogg_encoder_path(): return get_conf_str('OGG_ENCODER_PATH', '/usr/bin') ## def set_conf_ogg_encoder_path(newPath): set_conf_str('OGG_ENCODER_PATH', newPath) ## def get_conf_ogg_encoder_quality(): return get_conf_int('OGG_ENCODER_QUALITY', 5) ## def set_conf_ogg_encoder_quality(newVal): set_conf_int('OGG_ENCODER_QUALITY', newVal) ## def get_conf_ripp_only(): return get_conf_bool('RIPP_ONLY', False) ## def set_conf_ripp_only(newFlag): set_conf_bool('RIPP_ONLY', newFlag) gDictOggOutputDir = {'OGG_OPT_INTO_OUTDIR': 0, 'OGG_OPT_INTO_ARTIST_ALBUML_DIR': 1} ## def get_conf_ogg_output_dir(): return get_conf_int('OGG_ENCODER_OUTPUT_DIR', gDictOggOutputDir['OGG_OPT_INTO_OUTDIR']) ## def set_conf_ogg_output_dir(newVal): set_conf_int('OGG_ENCODER_OUTPUT_DIR', newVal) # # class RipperConfigDlg: ## def __init__(self, parent=None): self.tmpPathStr = get_conf_tmp_path() self.outPathStr = get_conf_out_path() self.rippCmdPathStr = get_conf_ripper_path() self.oggEncCmdPathStr = get_conf_ogg_encoder_path() self.oggQuality = get_conf_ogg_encoder_quality() self.isDeleteWav = get_conf_delete_wav() self.rippOnly = get_conf_ripp_only() self.oggOutputDir = get_conf_ogg_output_dir() # Create 'Config' dialog. self.dlg = gtk.Dialog('Config', parent, gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) self.notebook = gtk.Notebook() self.notebook.set_tab_pos(gtk.POS_TOP) # Create general setting page widget, label = self.create_general_page() self.notebook.append_page(widget, label) # Create ripper setting page widget, label = self.create_ripper_page() self.notebook.append_page(widget, label) # Create encoder setting page widget, label = self.create_encoder_page() self.notebook.append_page(widget, label) self.dlg.vbox.pack_start(self.notebook, False, False, 2) self.dlg.vbox.show_all() ## def create_general_page(self): contents = gtk.VBox() pageLabel = gtk.Label('General') label = gtk.Label('Temp Directory:') label.set_alignment(0.0, 0.5) contents.pack_start(label, False, False, 2) self.entryTmpPath = gtk.Entry() self.entryTmpPath.set_text(self.tmpPathStr) contents.pack_start(self.entryTmpPath, False, False, 2) HSep = gtk.HSeparator() contents.pack_start(HSep, False, False, 2) label = gtk.Label('Directory for ogg files output:') label.set_alignment(0.05, 0.5) contents.pack_start(label, False, False, 2) self.entryOutPath = gtk.Entry() self.entryOutPath.set_text(self.outPathStr) contents.pack_start(self.entryOutPath) self.checkRippOnly = gtk.CheckButton('Ripping wav only') self.checkDelWav = gtk.CheckButton('Delete ".wav" files after conversion finished.') self.checkRippOnly.connect('clicked', self.clicked_ripp_only, None) self.checkRippOnly.set_active(self.rippOnly) contents.pack_start(self.checkRippOnly, False, False, 2) self.checkDelWav.connect('clicked', self.clicked_del_wav, None) if self.rippOnly: self.checkDelWav.set_active(False) else: self.checkDelWav.set_active(self.isDeleteWav) contents.pack_start(self.checkDelWav, False, False, 2) return contents, pageLabel ## def create_ripper_page(self): contents = gtk.VBox() pageLabel = gtk.Label('Ripper') label = gtk.Label('Command path:') label.set_alignment(0.0, 0.5) contents.pack_start(label, False, False, 2) self.entryRippCmd = gtk.Entry() self.entryRippCmd.set_text(self.rippCmdPathStr) contents.pack_start(self.entryRippCmd, False, False, 2) return contents, pageLabel ## def create_encoder_page(self): contents = gtk.VBox() pageLabel = gtk.Label('Encoder') label = gtk.Label('Encoder path:') label.set_alignment(0.0, 0.5) contents.pack_start(label, False, False, 2) self.entryOggEncPath = gtk.Entry() self.entryOggEncPath.set_text(self.oggEncCmdPathStr) contents.pack_start(self.entryOggEncPath, False, False, 2) separator = gtk.HSeparator() contents.pack_start(separator, False, False, 2) hbox = gtk.HBox() label = gtk.Label('Quality:') hbox.pack_start(label, False, False, 2) self.comboOggQuality = gtk.combo_box_new_text() for i in range(1, 11): self.comboOggQuality.append_text(str(i)) self.comboOggQuality.set_active(self.oggQuality - 1) hbox.pack_start(self.comboOggQuality, True, False, 0) contents.pack_start(hbox, False, False, 2) frame = gtk.Frame(' Output options ') contents.pack_start(frame, False, False, 0) radiobox = gtk.VBox() frame.add(radiobox) self.oggOutRadio1 = gtk.RadioButton(None, 'Put ogg files into the ogg output directory.') radiobox.pack_start(self.oggOutRadio1, False, False, 0) self.oggOutRadio2 = gtk.RadioButton(self.oggOutRadio1, 'Put ogg files into "artist-name/album-name/" directory \nunder the ogg output directory.') radiobox.pack_start(self.oggOutRadio2, False, False, 0) dir_opt = get_conf_ogg_output_dir() if dir_opt == gDictOggOutputDir['OGG_OPT_INTO_OUTDIR']: self.oggOutRadio1.set_active(True) elif dir_opt == gDictOggOutputDir['OGG_OPT_INTO_ARTIST_ALBUML_DIR']: self.oggOutRadio2.set_active(True) return contents, pageLabel ## def clicked_ripp_only(self, widget, data=None): if self.checkRippOnly.get_active(): self.checkDelWav.set_active(False) ## def clicked_del_wav(self, widget, data=None): if self.checkDelWav.get_active(): self.checkRippOnly.set_active(False) ## def run(self): return self.dlg.run() ## def destroy(self): self.dlg.destroy() ## def save_config(self): set_conf_tmp_path(self.entryTmpPath.get_text()) set_conf_out_path(self.entryOutPath.get_text()) set_conf_delete_wav(self.checkDelWav.get_active()) set_conf_ripper_path(self.entryRippCmd.get_text()) set_conf_ogg_encoder_path(self.entryOggEncPath.get_text()) set_conf_ogg_encoder_quality(self.comboOggQuality.get_active()+1) set_conf_ripp_only(self.checkRippOnly.get_active()) if self.oggOutRadio1.get_active(): set_conf_ogg_output_dir(gDictOggOutputDir['OGG_OPT_INTO_OUTDIR']) elif self.oggOutRadio2.get_active(): set_conf_ogg_output_dir(gDictOggOutputDir['OGG_OPT_INTO_ARTIST_ALBUML_DIR']) # # class CmdThread(threading.Thread): ## def __init__(self, cmd, opts, targets=None, outArray=None): threading.Thread.__init__(self) self.cmd = cmd self.cmdOpts = opts self.cmdTargets = targets self.outArray = outArray self.counter = 0 self.cmd_pid = 0 self.killed = False self.cbProgressFunc = None self.cbEndFunc = None print '***** thread created *****' ## def run(self): print '****** thread start ******' self.preparation_func() while self.killed == False: if self.cmdTargets != None and len(self.cmdTargets) == 0: if self.cbProgressFunc != None: self.cbProgressFunc(self.counter, False) time.sleep(1) continue if self.cbProgressFunc != None: self.cbProgressFunc(self.counter, True) args = self.generate_cmd_args(self.cmd, self.cmdOpts, self.cmdTargets) self.cmd_pid = os.spawnv(os.P_NOWAIT, args[0], args) print 'pid = %d' % self.cmd_pid os.waitpid(self.cmd_pid, 0) self.cmd_pid = 0 if self.outArray != None: self.outArray.append(self.generate_output_name(args)) self.counter += 1 self.cleanup_func() if self.cbEndFunc() != None: self.cbEndFunc() ## def preparation_func(self): pass ## def cleanup_func(self): pass ## def set_cb_progress_func(self, func): self.cbProgressFunc = func ## def set_cb_end_func(self, func): self.cbEndFunc = func ## def stop(self): print 'pid = %d' % self.cmd_pid if self.cmd_pid: os.kill(self.cmd_pid, signal.SIGTERM) print '****** killed(pid:%d) ******' % self.cmd_pid self.cmd_pid = 0 self.killed = True ## def generate_cmd_args(self, cmd, opts, targets): arr = [cmd] + opts if targets and len(targets) > 0: arr.append(targets.pop(0)) return arr ## def generate_output_name(self, target): return target ## def get_counter_value(self): return self.counter # # class WavCmdThread(CmdThread): ## def generate_cmd_args(self, cmd, opts, targets): arr = [cmd] if targets and len(targets) > 0: tup = targets.pop(0) arr.append(str(tup[0])) arr.append(tup[1]) return arr def generate_output_name(self, seq): return seq[len(seq) - 1] ## def set_working_directory(self, workPath): self.workPath = workPath ## def preparation_func(self): self.savePath = os.getcwd() os.chdir(self.workPath) ## def cleanup_func(self): os.chdir(self.savePath) # # class OggCmdThread(CmdThread): ## def generate_cmd_args(self, cmd, opts, targets): arr = [cmd] + opts if targets != None and len(targets) > 0: name = targets.pop(0) head, ext = os.path.splitext(name) if len(head) > 0: outpath = self.outputPath + '/' + head + '.ogg' arr += [name, '-o', outpath] return arr ## def set_output_path(self, path): self.outputPath = path # # class CmdThreadKiller(threading.Thread): ## def __init__(self, max_count): threading.Thread.__init__(self) self.cmdThreads = [] self.cmdMaxCount = max_count self.forceKill = False self.cbEnd = None print '**** Killer Initialized ****' ## def run(self): print '**** Killer started! *****' while True: if self.forceKill: break cnt = len(self.cmdThreads) if cnt == 0: time.sleep(0.5) continue tmp = [] for i in range(cnt): cmd = self.cmdThreads.pop(0) if cmd.get_counter_value() >= self.cmdMaxCount: cmd.stop() else: tmp.append(cmd) self.cmdThreads = tmp if len(self.cmdThreads) == 0: break if self.forceKill: print '****** force kill ******' for cmd in self.cmdThreads: cmd.stop() if self.cbEnd != None: self.cbEnd() ## def add_target(self, target): if target == None: return self.cmdThreads.append(target) ## def kill_all(self): self.forceKill = True ## def set_cb_end(self, func): self.cbEnd = func # # class RipperWindow: ## def __init__(self, artist='', album='', targets=[]): ''' artist : artist name of the CD albume : album name of the CD targets : sequance of the items which must contain the track number and the track name. Ex. [(1, "track#1"), (2, "track#2")] or [[2, "track#2"], [3, "track#3"]] so so.. ''' self.artistName = artist self.albumName = album self.targets = targets self.oggTargets = [] self.wind = gtk.Window(gtk.WINDOW_TOPLEVEL) self.wind.set_size_request(250, -1) self.wind.set_deletable(False) self.wind.set_modal(True) # # Add widgets that shows progress of Ripping # frameWav = gtk.Frame(' To WAV ') wavbox = gtk.VBox() frameWav.add(wavbox) self.wavName = gtk.Label('---') self.wavName.set_alignment(0.05, 0.5) wavbox.pack_start(self.wavName, False, False, 2) self.wavProgress = gtk.ProgressBar() self.wavProgress.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT) wavbox.pack_start(self.wavProgress, False, False, 4) vbox = gtk.VBox() vbox.pack_start(frameWav, False, False, 2) # # Add widgets that shows Progress of converting ogg # if get_conf_ripp_only() == False: frameOgg = gtk.Frame(' To OGG ') oggbox = gtk.VBox() frameOgg.add(oggbox) self.oggName = gtk.Label('---') self.oggName.set_alignment(0.05, 0.5) oggbox.pack_start(self.oggName, False, False, 2) self.oggProgress = gtk.ProgressBar() self.oggProgress.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT) oggbox.pack_start(self.oggProgress, False, False, 4) vbox.pack_start(frameOgg, False, False, 2) self.bt = gtk.Button('Cancel') self.bt.connect('clicked', self.close, None) vbox.pack_start(self.bt, False, False, 2) self.wind.add(vbox) self.wind.show_all() # # Create thread for killing threads(wav ripping thread, ogg converting thread) # self.cmdKiller = CmdThreadKiller(len(self.targets)) self.cmdKiller.set_cb_end(self.cb_func_killer_end) self.cmdKiller.start() # # Create wav ripping thread # wavThread = self.setup_ripper_thread() self.cmdKiller.add_target(wavThread) wavThread.start() # # Create ogg converting thread # if get_conf_ripp_only() == False: oggThread = self.setup_ogg_thread() self.cmdKiller.add_target(oggThread) oggThread.start() ## def setup_ripper_thread(self): ripperCmd = os.path.join(get_conf_ripper_path(), STR_RIPPER_COMMAND) ripperCmd = os.path.normpath(ripperCmd) targetCopy = self.targets[:] wavThread = WavCmdThread(ripperCmd, [], targetCopy, self.oggTargets) tmpPath = os.path.normpath(os.path.expanduser(get_conf_tmp_path())) wavThread.set_working_directory(tmpPath) wavThread.set_cb_progress_func(self.cb_progress_func_wav) wavThread.set_cb_end_func(self.cb_func_wav_end) return wavThread ## def setup_ogg_thread(self): oggEncCmd = os.path.join(get_conf_ogg_encoder_path(), STR_OGG_ENCODER_COMMAND) oggEncCmd = os.path.normpath(oggEncCmd) quality = get_conf_ogg_encoder_quality() oggThread = OggCmdThread(oggEncCmd, ['-q', str(quality)], self.oggTargets) oggThread.set_cb_progress_func(self.cb_progress_func_ogg) oggThread.set_cb_end_func(self.cb_func_ogg_end) outPath = get_conf_out_path() if get_conf_ogg_output_dir() == gDictOggOutputDir['OGG_OPT_INTO_ARTIST_ALBUML_DIR']: if self.artistName == '': self.artistName = 'Unknown_Artist' if self.albumName == '': self.albumName = 'Unknown_album' outPath += '/%s/%s/' % (self.artistName, self.albumName) outPath = os.path.normpath(os.path.expanduser(outPath)) oggThread.set_output_path(outPath) return oggThread ## def cb_progress_func_wav(self, curStep, hasTarget): totalSteps = len(self.targets) self.wavProgress.set_fraction(float(curStep) / float(totalSteps)) self.wavProgress.set_text('%d / %d' % (curStep, totalSteps)) if hasTarget == False: self.wavName.set_text('..waiting..') return if curStep < totalSteps: self.wavName.set_text(self.targets[curStep][1]) ## def cb_progress_func_ogg(self, curStep, hasTarget): totalSteps = len(self.targets) self.oggProgress.set_fraction(float(curStep) / float(totalSteps)) self.oggProgress.set_text('%d / %d' % (curStep, totalSteps)) if hasTarget == False: self.oggName.set_text('..waiting..') return if curStep < totalSteps: name, ext = os.path.splitext(self.targets[curStep][1]) self.oggName.set_text(name + '.ogg') ## def cb_func_wav_end(self): self.wavName.set_text('!! Complete !!') ## def cb_func_ogg_end(self): self.oggName.set_text('!! Complete !!') ## def remove_wav_files(self, targets): if len(targets) == 0: return for t in targets: delPath = os.path.join(os.path.expanduser(get_conf_tmp_path()), t[1]) delPath = os.path.normpath(delPath) try: os.remove(delPath) except: pass ## def cb_func_killer_end(self): # Remove all wav files if option is on. if get_conf_delete_wav(): self.remove_wav_files(self.targets) self.bt.set_label('Close') print '!! Finished !!' ## def close(self, widget, data=None): self.cmdKiller.kill_all() self.wind.destroy() # # def convert_to_ogg(widget, wind): if wind == None: return artist = '' album = '' convertTargets = [] # # Get the selection of the track(s) for ripping. # selected = wind.tracks.get_selected_tracks() if len(selected) > 0: artist = selected[0].get_artist() album = selected[0].get_album() convertTargets = [(sel.get_track(), sel.get_title() + '.wav') for sel in selected] # # Ready..Go!! # progWind = RipperWindow(artist, album, convertTargets) ### # # Below describes functions for exaile plugin. # ### gToolBar = None gCnvButton = None # # def initialize(): ''' Called when the plugin is enabled. ''' # # Append custom button for ripping. # global gToolBar global gCnvButton if gCnvButton == None: gToolBar = APP.xml.get_widget('play_toolbar') gCnvButton = gtk.Button('Conv2Ogg') gToolBar.pack_start(gCnvButton, True, False, 0) gToolBar.show_all() gCnvButton.connect('clicked', convert_to_ogg, APP) return True # # def destroy(): ''' Called when the plugin is disabled. ''' global gToolBar global gCnvButton if gToolBar and gCnvButton: gToolBar.remove(gCnvButton) gToolBar.show_all() gCnvButton = None # # def configure(): ''' Called when the user click Configure button in the Plugin Manager. ''' configDlg = RipperConfigDlg() response = configDlg.run() if response == gtk.RESPONSE_ACCEPT: print 'response is OK' configDlg.save_config() configDlg.destroy()