Added new files for 0.3.0
Simeon Veldstra

file:eaeaeb194b3aed21c1eea5bc9127d359e22a1b7c(new)
--- /dev/null
+++ b/mp3togo/conf.py
@@ -0,0 +1,237 @@
+# - setup.py -
+# This file is part of mp3togo
+
+# Convert audio files to play on a mp3 player
+# Manage program options
+#
+# (c) Simeon Veldstra 2004, 2006 <reallifesim@gmail.com>
+#
+# This software is free.
+#
+# 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 may redistribute this program under the terms of the
+# GNU General Public Licence version 2
+# Available in this package or at http://www.fsf.org
+
+# requires python-pyvorbis and python-id3 and lame
+# and mpg321
+
+import sys, os
+
+import mp3togo
+import mp3togo.options as options
+
+THREADED=True
+
+class Fail(options.Fail):
+ pass
+
+class Error(Exception):
+ pass
+
+class ErrorUnknownFileType(Error):
+ pass
+
+class ErrorNoFile(Error):
+ pass
+
+class ErrorBadFormat(Error):
+ pass
+
+class ErrorNoXMMSControl(Error):
+ pass
+
+class ErrorXMMSNotRunning(Error):
+ pass
+
+class ErrorUnlocked(Error):
+ pass
+
+class TaskNotReadyError(Error):
+ pass
+
+
+class Options(options.Options):
+ """Subclass options.Options and fill in application specific
+ information."""
+ def __init__(self, argv=None, conffile=None, readconf=True):
+ options.Options.__init__(self, '~/mp3togo')
+
+ # Options to add:
+ # ((name, short option, long option, default value, hook, help text), ...)
+ self._set += [
+ ('brwarning', 't', 'file-tag', '-PORTABLE-', None,
+ '''A string appended to the name of the file to indicate it has been recoded.'''),
+ ('tempdir', 'w', 'work-dir', '/tmp', self._tempdir,
+ '''The path to store temporary files. This could need several hundred megabytes free.'''),
+ ('treedepth', 'd', 'tree-depth', 1, None,
+ '''The number of directory levels to preserve in the output tree. Use 0 to put all output files directly into the target directory. Use 2 to create a directory for the Artist and then the Album (Assuming your music collection is organized in directories by artist then album).'''),
+ ('maxunits', 'm', 'max-size', '0', self._maxunits,
+ '''The disk space available to use in bytes. Append 'M' for megabytes, 'G' for gigabytes or 'K' for kilobytes. Use 0 to use all available space on the filesystem.'''),
+ ('maxsize', '', '', 0L, None, ''),
+ ('maxtempunits', '', 'max-temp-size', '0', self._maxtempunits,
+ '''The disk space available to use for temporary files in bytes. Append 'M' for megabytes, 'G' for gigabytes or 'K' for kilobytes. Use 0 to use all available space on the filesystem.'''),
+ ('maxtempsize', '', '', 0L, None, ''),
+ ('force', 'F', 'force', False, None,
+ '''Overwrite files if they already exist.'''),
+ ('encoder', 'C', 'encoder', 'lame', None,
+ '''The encoder to use to create the output files. Options are wav, lame or oggenc.'''),
+ ('encopts', 'E', 'encoder-options', '', None,
+ '''Compression options for the encoder.'''),
+ ('compfactor', 'z', 'compression-factor', 16.0, None,
+ '''If you change the lame options, you must change this factor to match the compression rate of the new options. This is used to estimate completed file size.'''),
+ ('help', 'h', 'help', False, None, '''Print this message.'''),
+ ('playlist', 'p', 'playlist', '', None,
+ '''The playlist to convert. Playlists can be simple lists of file paths, one per line, or .m3u files or the native XMMS playlist file format.'''),
+ ('readxmms', 'x', 'import-xmms', False, None,
+ '''Get playlist from running instance of XMMS. (You must have xmms-shell installed)'''),
+ ('playerdir', 'o', 'output-dir', os.curdir, self._playerdir,
+ '''Where to write the output files.'''),
+ ('index', '', 'index', False, None,
+ '''Create an index file when in wav output mode.'''),
+ ('nonormal', '', 'no-normalize', False, None,
+ '''Don't normalize the wav file before encoding.''')]
+
+ # Options to not save to the config file:
+ self._nosave_options += []
+
+ # Binaries to check for:
+ self.bin['wav'] = True
+ self.bin['lame'] = False
+ self.bin['oggenc'] = False
+ self.bin['mpg321'] = False
+ self.bin['ogg123'] = False
+ self.bin['flac'] = False
+ self.bin['metaflac'] = False
+ self.bin['normalize-audio'] = False
+ self.bin['xmms-shell'] = False
+
+ self.version = mp3togo.version
+ self._help_preamble = """
+Synopsis:
+ mp3togo [-p playlist] [-o output-dir] [options] [input files]
+
+Description:
+ mp3togo is a program for converting audio files of various
+ formats into a common format suitable for use on a portable
+ mp3 player or an mp3 CD. The files to convert can be
+ specified in a playlist file (m3u, pls and plain text are
+ supported) or given as arguments to the program. mp3togo
+ will go through the list and run mpg321, ogg123, flac and
+ lame to decode and reencode the files. The newly encoded
+ files will be written to the destination directory. The
+ software can retain as much of the subdirectory structure
+ as desired.
+"""
+
+ # Go ahead and read in the data:
+ self.getconf(argv, conffile, readconf)
+
+ # All hooks are called with the lock held
+ # must reference the ._d dict directly
+ def _post_hook(self):
+ #Set up default encoder options if not specified:
+ if not self._d['encopts']:
+ self.reset_encoder_options()
+
+ def _conffile(self, value):
+ self._d['conffile'] = absfile(value)
+
+ def _playerdir(self, dir):
+ self._d['playerdir'] = absfile(dir)
+
+ def _tempdir(self, dir):
+ self._d['tempdir'] = absfile(dir)
+
+ def _maxunits(self, value):
+ #Calculate raw bytes and set maxsize
+ try:
+ if value[-1] in ('m', 'M'):
+ max = long(value[:-1]) * 1048576L
+ elif value[-1] in ('g', 'G'):
+ max = long(value[:-1]) * 1073741824L
+ elif value[-1] in ('k', 'K'):
+ max = long(value[:-1]) * 1024L
+ else:
+ max = long(value)
+ except ValueError:
+ raise Fail, "Bad max-size option: %s " % value
+ self._d['maxsize'] = max
+ self._d['maxunits'] = value
+
+ def _maxtempunits(self, value):
+ #Calculate raw bytes and set maxsize
+ try:
+ if value[-1] in ('m', 'M'):
+ max = long(value[:-1]) * 1048576L
+ elif value[-1] in ('g', 'G'):
+ max = long(value[:-1]) * 1073741824L
+ elif value[-1] in ('k', 'K'):
+ max = long(value[:-1]) * 1024L
+ else:
+ max = long(value)
+ except ValueError:
+ raise Fail, "Bad max-temp-size option: %s " % value
+ self._d['maxtempsize'] = max
+ self._d['maxtempunits'] = value
+
+ def _arg_files(self, value):
+ l = []
+ for f in value:
+ f = absfile(f)
+ if os.path.exists(f):
+ l.append(f)
+ self._d['arg_files'] = l
+
+ def reset_encoder_options(self):
+ """Reset encoder options to defaults."""
+ if self._d['encoder'] == 'lame':
+ self._d['encopts'] = '--abr 96'
+ elif self._d['encoder'] == 'oggenc':
+ self._d['encopts'] = '-m 96 -M 225 -b 100'
+ elif self._d['encoder'] == 'wav':
+ self._d['encopts'] = ''
+
+ def cleanfilename(self, name):
+ """Remove nasty characters from a filename"""
+ name = name.replace(' ', '_')
+ name = name.replace('"', "")
+ name = name.replace(':', '.')
+ name = name.replace("?", "")
+ name = name.replace('*', '')
+ name = name.replace('`', "'")
+ name = name.replace('&', '+')
+ name = name.replace(';', '.')
+ name = name.replace('#', '-')
+ if name[0] == '.':
+ name = '_' + name
+ return name
+
+def absfile(name):
+ name = name.strip()
+ name = os.path.expanduser(name)
+ if name:
+ name = os.path.abspath(name)
+ return name
+
+def getfiletype(filename):
+ """Return the format of an audio file"""
+ # Could use some more magic here
+ ft = os.path.splitext(filename)[1][1:]
+ if ft not in ('mp3', 'ogg', 'flac', 'wav'):
+ raise ErrorUnknownFileType
+ return ft
+
+def checkfile(name):
+ """Verify the existence of an audio file"""
+ name = name.replace('\n', '')
+ if not os.path.exists(name):
+ raise ErrorNoFile
+ name = absfile(name)
+ getfiletype(name) # Throws exception if unknown
+ return name
+
file:d16ea42a99fe6886ffe73643ff4d7a2e9225db96(new)
--- /dev/null
+++ b/mp3togo/filelist.py
@@ -0,0 +1,136 @@
+# - filelist.py -
+# This file is part of mp3togo
+
+# Convert audio files to play on a mp3 player
+#
+# (c) Simeon Veldstra 2004, 2006 <reallifesim@gmail.com>
+#
+# This software is free.
+#
+# 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 may redistribute this program under the terms of the
+# GNU General Public Licence version 2
+# Available in this package or at http://www.fsf.org
+
+
+"""Create a list of files for processing."""
+
+import mp3togo.conf as setup
+
+if setup.THREADED == True:
+ import threading
+else:
+ import dummy_threading as threading
+
+import sys, os
+
+
+
+class FileList:
+ """A list of verified music files."""
+
+ def __init__(self):
+ self._list = []
+ self._i = 0
+ self._lock = threading.Lock()
+ self._running = False
+
+ def run(self):
+ """Must call run after loading the list
+ and before starting the processing."""
+ self._lock.acquire()
+ self._running = True
+
+ def running(self):
+ return self._running
+
+ def tuple(self):
+ return tuple(self._list)
+
+ def addfiles(self, names, block=True):
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ if type(names) not in (type([]), type(())):
+ names = (names,)
+
+ for name in names:
+ try:
+ name = setup.checkfile(name)
+ except:
+ continue
+
+ self._list.append(name)
+
+ self._lock.release()
+ return True
+
+ def addplaylist(self, lst, block=True):
+ if not os.path.exists(lst):
+ raise setup.ErrorNoFile
+
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ try:
+ pl = file(lst)
+ for line in pl:
+ if line.startswith('/'):
+ try:
+ name = setup.checkfile(line)
+ except:
+ continue
+ self._list.append(name)
+ elif line.startswith("File") and line.find('=') > -1:
+ name = line.strip().split('=', 1)[1]
+ name = setup.checkfile(name)
+ self._list.append(name)
+ finally:
+ self._lock.release()
+ return True
+
+ def addXMMS(self, session=0, block=True):
+ try:
+ import xmms.control
+ except ImportError:
+ raise setup.ErrorNoXMMSControl
+ if not xmms.control.is_running:
+ raise setup.ErrorXMMSNotRunning
+ res = []
+ for i in range(xmms.control.get_playlist_length(session)):
+ res.append(xmms.control.get_playlist_file(i))
+
+ # locks self in addfiles
+ self.addfiles(res, block)
+
+ def removefile(self, name, block=True):
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ while name in self._list:
+ del self._list[self._list.index(name)]
+
+ self._lock.release()
+ return True
+
+ def __iter__(self):
+ if not self._lock.locked():
+ raise setup.ErrorUnlocked
+
+ for self._i in range(len(self._list)):
+ yield self._list[self._i]
+
+ def cur_file(self):
+ return self._i
+
+ def __len__(self):
+ return len(self._list)
+
+ def __contains__(self, name):
+ return name in self._list
+
+
file:0cde48726620ebe16e35bc49eb8c4173d1704c7f(new)
--- /dev/null
+++ b/mp3togo/helpers.py
@@ -0,0 +1,226 @@
+#
+# helpers.py: functions to interpret coder program output.
+# mp3togo Portable Music Manager
+#
+# Copyright 2005: Simeon Veldstra <reallifesim@gmail.com>
+#
+# 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; either version 2
+# of the License, or (at your option) any later version.
+#
+# 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.
+#
+# ***********************************************************
+# The code to parse output from external programs is based on
+# the jack CD ripping program by Arne Zellentin.
+#
+# 'jack' is responsible for my full hard disks
+# and has inspired me to produce this program.
+# Thanks for everything Arne.
+
+
+import os
+import shutil
+
+import mp3togo.conf as conf
+
+
+class frames:
+ def __init__(self):
+ self.frames = 0
+ self.tframes = 0
+
+def parse_ogg123(buf):
+ s = buf.split('\r')
+ def sumtime(t):
+ sum = float(t.split(':')[0]) * 60
+ sum += float(t.split(':')[1])
+ return sum
+ for l in s:
+ if l.startswith('Time') and l.endswith('% '):
+ line = l.split(' ')
+ return sumtime(line[1])/sumtime(line[4]) * 100.0
+ return 0
+
+def parse_mpg321(buf, f=frames()):
+ s = buf.split('\r')
+ if len(s) >= 2:
+ s = s[-2]
+ else:
+ s = s[-1]
+ if not f.tframes:
+ y0 = s.find('[')
+ y1 = s.find(']')
+ if y0 != -1 and y1 != -1:
+ try:
+ f.tframes = float(s[y0+1:y1])
+ except ValueError:
+ pass
+ if f.tframes:
+ try:
+ r = float(s.split()[1])
+ except ValueError:
+ r = f.tframes
+ if r < f.frames:
+ f.tframes = f.frames = 0
+ return 0
+ f.frames = r
+ return f.frames/f.tframes * 100.0
+ return 0
+
+def parse_oggenc(buf):
+ # From "jack"
+ s = buf.split('\r')
+ if len(s) >= 2:
+ s = s[-2]
+ if len(s) == 1:
+ s = s[0]
+ y0 = s.find("[")
+ y1 = s.find("%]")
+ if y0 != -1 and y1 != -1:
+ percent = float(s[y0 + 1:y1])
+ else:
+ percent = 0
+ return percent
+
+def parse_lame(buf):
+ # originaly from "jack"
+ s = buf.split('\r')
+ if len(s) >= 2:
+ s = s[-1] or s[-2]
+ if len(s) == 1: s=s[0]
+ if s.find("%") >= 0: # status reporting starts here
+ y = s.split("/")
+ y1 = y[1].split("(")[0]
+ percent = float(y[0]) / float(y1) * 100.0
+ elif s.find("Frame:") >= 0: # older versions, like 3.13
+ y = s.split("/")
+ y0 = y[0].split("[")[-1]
+ y1 = y[1].split("]")[0]
+ percent = float(y0) / float(y1) * 100.0
+ else:
+ percent = 0
+ return percent
+
+def parse_flac_enc(buf):
+ # From "jack"
+ s = buf.split('\r')
+ if len (s) >= 2: s = s[-2]
+ if len (s) == 1: s = s[0]
+ y0 = s.find(": ")
+ y1 = s.find("%")
+ if y0 != -1 and y1 != -1:
+ return float(s[y0 + 1:y1])
+ else:
+ return 0
+
+def parse_flac_dec(buf):
+ # I wrote this one myself
+ buf = buf.split('\r')
+ buf = buf[-1]
+ buf = buf.replace('%', '')
+ try:
+ return int(buf.split()[1])
+ except IndexError:
+ return 0
+
+### wav file "encoder/decoder"
+def recode_wav(input, output, args=None):
+ """Factory returns function to copy wav file
+
+ Returned function takes no args and copies
+ input to output. Throws OSError on error."""
+ return lambda: shutil.copyfile(input, output)
+
+## Command line escape sequences:
+#
+# escape: for:
+# ---------------------
+# %% %
+# %o output filename
+# %i input filename
+# %a args
+
+helpers = {
+ 'oggenc' : {'parser': parse_oggenc,
+ 'cmd': 'oggenc %a -o %o %i',
+ 'type': 'ogg',
+ 'action': 'encode'},
+ 'lame' : {'parser': parse_lame,
+ 'cmd': 'lame --nohist %a %i %o',
+ 'type': 'mp3',
+ 'action': 'encode'},
+ 'flac_enc':{'parser': parse_flac_enc,
+ 'cmd': 'flac ???????',
+ 'type': 'flac',
+ 'action': 'encode'},
+ 'wav_enc': {'parser': None,
+ 'cmd': recode_wav,
+ 'type': 'wav',
+ 'action': 'encode'},
+ 'ogg123' : {'parser': parse_ogg123,
+ 'cmd': 'ogg123 -d wav -f %o %i',
+ 'type': 'ogg',
+ 'action': 'decode'},
+ 'mpg321' : {'parser': parse_mpg321,
+ 'cmd': 'mpg321 -v -w %o %i',
+ 'type': 'mp3',
+ 'action': 'decode'},
+ 'flac_dec':{'parser': parse_flac_dec,
+ 'cmd': 'flac --decode -F -o %o %i',
+ 'type': 'flac',
+ 'action': 'decode'},
+ 'wav_dec': {'parser': None,
+ 'cmd': recode_wav,
+ 'type': 'wav',
+ 'action': 'decode'},
+ }
+
+
+def find_helper(type, action):
+ """Return the name of the helper that performs action on type
+
+ Raises KeyError if type or action is unknown."""
+ return dict([(helpers[x]['type'], x)
+ for x in helpers.keys()
+ if helpers[x]['action'] == action]
+ )[type]
+
+def make_args(helper, input, output, args=''):
+ """Substitute for command line args"""
+ esc = {'z': '%', 'i': "###input###", 'o': "###output###", 'a': args}
+ out = ""
+ fmt = helpers[helper]['cmd']
+ fmt = fmt.replace('%%', '%z')
+ fmt = fmt.split('%')
+ while fmt:
+ out += fmt[0]
+ if len(fmt) <= 1:
+ break
+ fmt = fmt[1:]
+ code = fmt[0] and fmt[0][0]
+ if code in esc.keys():
+ fmt[0] = esc[code] + fmt[0][1:]
+ else:
+ raise conf.ErrorBadFormat
+ out = out.split()
+ if "###input###" in out:
+ out[out.index("###input###")] = input
+ if "###output###" in out:
+ out[out.index("###output###")] = output
+ return out
+
+def est_decoded_size(filename):
+ # This could be much better
+ tp = conf.getfiletype(filename)
+ factors = {'mp3': 16.5, 'ogg': 16.5, 'flac': 2.8, 'wav': 1}
+ return os.stat(filename).st_size * factors[tp]
file:e40b93b5517c48b8e25bd4f63930cbfbd2f9abdc(new)
--- /dev/null
+++ b/mp3togo/pool.py
@@ -0,0 +1,147 @@
+# - pool.py -
+# This file is part of mp3togo
+
+# Convert audio files to play on a mp3 player
+# Manage disk space for program output.
+#
+# (c) Simeon Veldstra 2006 <reallifesim@gmail.com>
+#
+# This software is free.
+#
+# 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 may redistribute this program under the terms of the
+# GNU General Public Licence version 2
+# Available in this package or at http://www.fsf.org
+
+import mp3togo.conf as setup
+
+if setup.THREADED == True:
+ import threading
+else:
+ import dummy_threading as threading
+
+import os
+import sys
+
+import mp3togo.track as track
+
+
+SIZE = 0
+USED = 1
+NEED = 2
+
+
+class Pool:
+ """Manage storage"""
+
+ def __init__(self, opts):
+ self._active = []
+ self._tmp = [None, 0, 0]
+ self._out = [None, 0, 0]
+
+ td = opts['tempdir']
+ free_tmp = os.statvfs(td).f_bfree * os.statvfs(td).f_frsize
+ if opts['maxtempsize'] and free_tmp > opts['maxtempsize']:
+ self._tmp[SIZE] = opts['maxtempsize']
+ else:
+ self._tmp[SIZE] = free_tmp
+
+ pd = opts['playerdir']
+ free_out = os.statvfs(pd).f_bfree * os.statvfs(pd).f_frsize
+ if opts['maxsize'] and free_out > opts['maxsize']:
+ self._out[SIZE] = opts['maxsize']
+ else:
+ self._out[SIZE] = free_out
+
+ self.lock = threading.RLock()
+ self.produced_bytes = 0
+
+ def add_track(self, trk):
+ self.lock.acquire()
+ for tsk in trk._queue:
+ if not self._add_task(tsk):
+ self.rm_track(trk)
+ self.lock.release()
+ return False
+ self.lock.release()
+ return True
+
+ def _add_task(self, tsk):
+ self.lock.acquire()
+ tmp = self._tmp
+ out = self._out
+ if tsk.tmpsize < tmp[SIZE] - (tmp[USED] + tmp[NEED]) and \
+ tsk.outsize < out[SIZE] - (out[USED] + out[NEED]):
+ if tsk.tmpsize > 0:
+ tmp[NEED] += tsk.tmpsize
+ if tsk.outsize:
+ out[NEED] += tsk.outsize
+ self._active.append(tsk)
+ self.lock.release()
+ return True
+ else:
+ self.lock.release()
+ return False
+
+ def rm_track(self, trk):
+ self.lock.acquire()
+ tdone = 0
+ for tsk in trk._queue:
+ self._rm_task(tsk)
+ tdone += tsk.outsize
+ if trk.bytes and trk() == track.DONE:
+ self.produced_bytes += trk.bytes
+ self._out[USED] = self._out[USED] - tdone + trk.bytes
+ self.lock.release()
+
+ def _rm_task(self, tsk):
+ if tsk in self._active:
+ if tsk.tmpsize:
+ if tsk.tmpsize > 0:
+ self._tmp[NEED] -= tsk.tmpsize
+ if tsk._status == 'done':
+ self._tmp[USED] += tsk.tmpsize
+ if tsk.outsize:
+ self._out[NEED] -= tsk.outsize
+ if tsk._status == 'done':
+ self._out[USED] += tsk.outsize
+ del self._active[self._active.index(tsk)]
+
+ def tmp_willgrow(self):
+ self.lock.acquire()
+ ret = 0
+ for tsk in self._active:
+ if tsk.tmpsize < 0:
+ ret += -tsk.tmpsize
+ self.lock.release()
+ return ret
+
+ def tmpspace(self):
+ self.lock.acquire()
+ tmp = self._tmp[SIZE] - (self._tmp[USED] + self._tmp[NEED])
+ self.lock.release()
+ return tmp
+
+ def outspace(self):
+ self.lock.acquire()
+ out = self._out[SIZE] - (self._out[USED] + self._out[NEED])
+ self.lock.release()
+ return out
+
+ def run(self):
+ """Keep an eye on the situation."""
+ # Will need this later
+ pass
+
+ def normalize(self):
+ """Recalculate the available space."""
+ self.lock.acquire()
+ pass
+ self.lock.release()
+
+
+
file:c9a97c6c71ecd1379fe680eb3f163a2cd74483e3(new)
--- /dev/null
+++ b/mp3togo/tags.py
@@ -0,0 +1,484 @@
+# - tags.py -
+# This file is part of mp3togo
+
+# Convert audio files to play on a mp3 player
+# Manage program options
+#
+# (c) Simeon Veldstra 2004, 2006 <reallifesim@gmail.com>
+#
+# This software is free.
+#
+# 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 may redistribute this program under the terms of the
+# GNU General Public Licence version 2
+# Available in this package or at http://www.fsf.org
+
+import mp3togo.conf as setup
+
+if setup.THREADED == True:
+ import threading
+else:
+ import dummy_threading as threading
+
+import os, sys
+import UserDict
+
+import ogg.vorbis
+import ID3
+
+HAVE_METAFLAC=False
+for path in os.environ['PATH'].split(':'):
+ if os.path.exists(os.path.join(path, 'metaflac')):
+ HAVE_METAFLAC=True
+ break
+
+
+class Tags(UserDict.DictMixin):
+ """Manage per track metadata.
+
+ Provide a filename in the constructor
+ Call read() to read in the tags
+ modify tags if necessary
+ Call write(name) with the output file the tags
+ are to be written to
+ Tags are available through a dictionary interface
+ format(fmt) returns a formatted string of metadata
+ """
+
+ def __init__(self, filename):
+ if not os.path.exists(filename):
+ raise setup.ErrorNoFile
+ self._file = filename
+ self._type = setup.getfiletype(filename)
+ self._tags = {}
+ self._readok = threading.Event()
+ self._readok.clear()
+ self._lock = threading.Lock()
+
+
+ def read(self, block=True):
+ if not self._lock.acquire(block) and not block:
+ return False
+ self._readok.clear()
+
+ def copytags(d):
+ o = {}
+ for k in d.keys():
+ o[k] = list(d[k])
+ return o
+
+ if self._type == 'mp3':
+ info = ID3.ID3(self._file, as_tuple=1).as_dict()
+ self._tags = copytags(info)
+ del info
+ elif self._type == 'ogg':
+ info = ogg.vorbis.VorbisFile(self._file).comment().as_dict()
+ self._tags = copytags(info)
+ del info
+ elif self._type == 'flac':
+ if HAVE_METAFLAC:
+ cmd = '%s --export-tags-to=- "%s" ' % ('metaflac', self._file)
+ fd = os.popen(cmd)
+ info = fd.read()
+ fd.close()
+ info = map(lambda x: x.split('=', 1), info.split('\n'))
+ info = filter(lambda x: len(x) == 2, info)
+ info = map(lambda x: [x[0].upper(), x[1:]], info)
+ self._tags = dict(info)
+ elif self._type == 'wav':
+ pass
+
+ # Try to guess from the file's path - better than nothing
+ path = os.path.splitext(self._file)[0]
+ path = path.replace('_', ' ')
+ for id, depth in [('ARTIST', -3), ('ALBUM', -2), ('TITLE', -1)]:
+ if not id in self._tags or self._tags[id][0] == '':
+ try:
+ self._tags[id] = [path.split(os.sep)[depth]]
+ except IndexError:
+ self._tags[id] = "%s unknown" % id.lower()
+
+ self._readok.set()
+ self._lock.release()
+ return True
+
+
+ def write(self, filename, block=True):
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ if not os.path.exists(filename):
+ self._lock.release()
+ raise setup.ErrorNoFile
+
+ def puttags(d):
+ for key in self._tags.keys(): # Not self.keys()
+ if key == 'GENRE' and fmt == 'mp3':
+ if type(self._tags[key][0]) is type(1) and 0 <= self._tags[key][0] < 256:
+ d[key] = self._tags[key][0]
+ elif self._tags[key][0] in map(str, range(256)):
+ d[key] = int(self._tags[key][0])
+ else:
+ d[key] = int(genres.get(self._tags[key][0], '255'))
+ else:
+ d[key] = self._tags[key][0]
+ # No! don't unlock here dumbass! return from puttags
+ return d
+
+ try:
+ fmt = setup.getfiletype(filename)
+ except setup.ErrorUnknownFileType:
+ self._lock.release()
+ raise
+
+ if fmt == 'ogg':
+ vf = ogg.vorbis.VorbisFile(filename)
+ out = vf.comment()
+ puttags(out)
+ out.write_to(filename)
+ del out
+ del vf
+ elif fmt == 'mp3':
+ out = ID3.ID3(filename, as_tuple=1)
+ puttags(out)
+ out.write()
+ del out
+ else:
+ self._lock.release()
+ raise setup.ErrorUnknownFileType
+
+ self._lock.release()
+ return True
+
+
+ def writeindex(self, filename, indexname, block=True):
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ try:
+ ifile = file(indexname, 'a')
+ ifile.write(filename + "\n")
+ keys = self._tags.keys()
+ keys.sort()
+ for k in keys:
+ for q in self._tags[k]:
+ ifile.write("%s: %s\n" % (k, q))
+ ifile.write("\n")
+ ifile.close()
+ except:
+ self._lock.release()
+ raise
+
+ self._lock.release()
+ return True
+
+ def format(self, fmt, block=True):
+ """Pretty print the tag information
+
+ The following format strings apply:
+ %% Literal %
+ %n Track number
+ %a Artist
+ %t Track title
+ %l Album title
+ %y Album release year
+ %g Album genre"""
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ esc = {'n': 'TRACKNUMBER',
+ 'a': 'ARTIST',
+ 't': 'TITLE',
+ 'l': 'ALBUM',
+ 'y': 'YEAR',
+ 'g': 'GENRE_NAME'}
+ #'z': Used for literal '%'
+
+ out = ""
+ fmt = fmt.replace('%%', '%z')
+ fmt = fmt.split('%')
+ while fmt:
+ out += fmt[0]
+ if len(fmt) <= 1:
+ break
+ fmt = fmt[1:]
+ code = fmt[0] and fmt[0][0]
+ if code == 'z':
+ fmt[0] = '%' + fmt[0][1:]
+ elif code in esc.keys():
+ fmt[0] = self._tags[esc[code]][0] + fmt[0][1:]
+ else:
+ self._lock.release()
+ raise setup.ErrorBadFormat
+
+ self._lock.release()
+ return out
+
+
+ def __getitem__(self, key, block=True):
+
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ if key == 'GENRE_NAME':
+ if not self._tags.has_key('GENRE'):
+ out = ''
+ else:
+ out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
+ else:
+ try:
+ out = self._tags[key.upper()]
+ except KeyError:
+ self._lock.release()
+ raise
+
+ self._lock.release()
+ return out
+
+
+ def __setitem__(self, key, value, block=True):
+
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ if type(value) != type([]):
+ value = [value]
+ self._tags[key.upper()] = value
+
+ self._lock.release()
+ return True
+
+
+ def setorappend(self, key, value, block=True):
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ key = key.upper()
+ if self._tags.has_key(key):
+ self._tags[key].append(value)
+ else:
+ self[key] = value
+
+ self._lock.release()
+ return True
+
+
+ def __delitem__(self, key, block=True):
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ try:
+ del self._tags[key]
+ except KeyError:
+ self._lock.release()
+ raise
+
+ self._lock.release()
+ return True
+
+
+ def keys(self, block=True):
+ if not self._lock.acquire(block) and not block:
+ return False
+
+ if self._tags.has_key('GENRE'):
+ out = self._tags.keys() + ['GENRE_NAME']
+ else:
+ out = self._tags.keys()
+
+ self._lock.release()
+ return out
+
+
+def remove_from_index(trackname, indexname):
+ """Remove an entry from an index file."""
+ if not os.path.exists(indexname):
+ return True
+ fp = file(indexname, 'r')
+ lines = fp.readlines()
+ fp.close()
+ i = 0
+ new = []
+ while i < len(lines):
+ if lines[i] == trackname + '\n':
+ while 1:
+ i += 1
+ if i >= len(lines):
+ break
+ if lines[i] == '\n':
+ i += 1
+ break
+ if i >= len(lines):
+ break
+ new.append(lines[i])
+ i += 1
+ if new:
+ fp = file(indexname, 'w')
+ fp.writelines(new)
+ fp.close()
+ else:
+ os.unlink(indexname)
+ return True
+
+
+#DATA
+
+genres = {
+'Blues': '0',
+'Classic Rock': '1',
+'Country': '2',
+'Dance': '3',
+'Disco': '4',
+'Funk': '5',
+'Grunge': '6',
+'Hip-Hop': '7',
+'Jazz': '8',
+'Metal': '9',
+'New Age': '10',
+'Oldies': '11',
+'Other': '12',
+'Pop': '13',
+'R&B': '14',
+'Rap': '15',
+'Reggae': '16',
+'Rock': '17',
+'Techno': '18',
+'Industrial': '19',
+'Alternative': '20',
+'Ska': '21',
+'Death Metal': '22',
+'Pranks': '23',
+'Soundtrack': '24',
+'Euro-Techno': '25',
+'Ambient': '26',
+'Trip-Hop': '27',
+'Vocal': '28',
+'Jazz+Funk': '29',
+'Fusion': '30',
+'Trance': '31',
+'Classical': '32',
+'Instrumental': '33',
+'Acid': '34',
+'House': '35',
+'Game': '36',
+'Sound Clip': '37',
+'Gospel': '38',
+'Noise': '39',
+'Alt. Rock': '40',
+'Bass': '41',
+'Soul': '42',
+'Punk': '43',
+'Space': '44',
+'Meditative': '45',
+'Instrum. Pop': '46',
+'Instrum. Rock': '47',
+'Ethnic': '48',
+'Gothic': '49',
+'Darkwave': '50',
+'Techno-Indust.': '51',
+'Electronic': '52',
+'Pop-Folk': '53',
+'Eurodance': '54',
+'Dream': '55',
+'Southern Rock': '56',
+'Comedy': '57',
+'Cult': '58',
+'Gangsta': '59',
+'Top 40': '60',
+'Christian Rap': '61',
+'Pop/Funk': '62',
+'Jungle': '63',
+'Native American': '64',
+'Cabaret': '65',
+'New Wave': '66',
+'Psychedelic': '67',
+'Rave': '68',
+'Showtunes': '69',
+'Trailer': '70',
+'Lo-Fi': '71',
+'Tribal': '72',
+'Acid Punk': '73',
+'Acid Jazz': '74',
+'Polka': '75',
+'Retro': '76',
+'Musical': '77',
+'Rock & Roll': '78',
+'Hard Rock': '79',
+'Folk': '80',
+'Folk/Rock': '81',
+'National Folk': '82',
+'Swing': '83',
+'Fusion': '84',
+'Bebob': '85',
+'Latin': '86',
+'Revival': '87',
+'Celtic': '88',
+'Bluegrass': '89',
+'Avantgarde': '90',
+'Gothic Rock': '91',
+'Progress. Rock': '92',
+'Psychedel. Rock': '93',
+'Symphonic Rock': '94',
+'Slow Rock': '95',
+'Big Band': '96',
+'Chorus': '97',
+'Easy Listening': '98',
+'Acoustic': '99',
+'Humour': '100',
+'Speech': '101',
+'Chanson': '102',
+'Opera': '103',
+'Chamber Music': '104',
+'Sonata': '105',
+'Symphony': '106',
+'Booty Bass': '107',
+'Primus': '108',
+'Porn Groove': '109',
+'Satire': '110',
+'Slow Jam': '111',
+'Club': '112',
+'Tango': '113',
+'Samba': '114',
+'Folklore': '115',
+'Ballad': '116',
+'Power Ballad': '117',
+'Rhythmic Soul': '118',
+'Freestyle': '119',
+'Duet': '120',
+'Punk Rock': '121',
+'Drum Solo': '122',
+'A Capella': '123',
+'Euro-House': '124',
+'Dance Hall': '125',
+'Goa': '126',
+'Drum & Bass': '127',
+'Club-House': '128',
+'Hardcore': '129',
+'Terror': '130',
+'Indie': '131',
+'BritPop': '132',
+'Negerpunk': '133',
+'Polsk Punk': '134',
+'Beat': '135',
+'Christian Gangsta Rap': '136',
+'Heavy Metal': '137',
+'Black Metal': '138',
+'Crossover': '139',
+'Contemporary Christian': '140',
+'Christian Rock': '141',
+'Merengue': '142',
+'Salsa': '143',
+'Thrash Metal': '144',
+'Anime': '145',
+'Jpop': '146',
+'Synthpop': '147'}
+
+genrenumbers = {}
+for key, value in genres.items():
+ genrenumbers[value] = key
+
+#END
file:8e70d6d565988e5da484dbe64e50c0eb73f674b0(new)
--- /dev/null
+++ b/mp3togo/task.py
@@ -0,0 +1,329 @@
+# - task.py -
+# This file is part of mp3togo
+
+# Convert audio files to play on a mp3 player
+# Manage a sub process.
+#
+# (c) Simeon Veldstra 2006 <reallifesim@gmail.com>
+#
+# This software is free.
+#
+# 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 may redistribute this program under the terms of the
+# GNU General Public Licence version 2
+# Available in this package or at http://www.fsf.org
+
+import mp3togo.conf as setup
+
+if setup.THREADED == True:
+ import threading
+else:
+ import dummy_threading as threading
+
+import os
+import sys
+import time
+import select
+import pty
+import fcntl
+import signal
+
+
+READY = 'ready'
+RUNNING = 'running'
+PAUSED = 'paused'
+STOPPED = 'stopped'
+FAILED = 'failed'
+DONE = 'done'
+
+
+class SimpleTask:
+ """Run a callable and save its output"""
+
+ def __init__(self, parent, action, filter=None, reverse=None, tmpsize=0, outsize=0, name=''):
+ if not action:
+ raise TypeError
+ if str(self.__class__).endswith('SimpleTask'):
+ if not callable(action):
+ raise TypeError
+ for func in (filter, reverse):
+ if func:
+ if not callable(func):
+ raise TypeError
+
+ self.name = name
+ self.tmpsize = tmpsize
+ self.outsize = outsize
+ self._parent = parent
+ self._action = action
+ self._filter = filter
+ self._reverse = reverse
+ # Hold runlock while accessing self._status
+ self._runlock = threading.Lock()
+ # Hold outlock while accessing self._output
+ self._outlock = threading.Lock()
+ self._status = READY
+ self._output = None
+ self._start_time = 0
+ self._finish_time = 0
+
+ def run(self):
+ """Run the callable"""
+ self.check_sibling()
+ self._runlock.acquire()
+ if self._status != READY:
+ self._runlock.release()
+ return False
+ self._status = RUNNING
+ self._start_time = time.time()
+
+ try:
+ out = self._action()
+ if self._filter:
+ out = self._filter(out)
+ self._outlock.acquire()
+ self._output = out
+ self._outlock.release()
+ except:
+ self._status = FAILED
+ self._runlock.release()
+ return True
+ self._status = DONE
+ self._finish_time = time.time()
+ self._runlock.release()
+ return True
+
+ def pause(self):
+ return False
+
+ def stop(self):
+ return False
+
+ def undo(self):
+ """Undo the action, if possible.
+
+ Runs the reverse function passed in.
+ reverse should return a True value on success or
+ False in the event of a failure. undo returns None
+ if no reverse function was passed in.
+
+ If reverse succeeds, the task object should be in a
+ consistent READY state and run() can start again at the
+ top. If it fails or throws an exception, the state will
+ remain as it is.
+
+ """
+ self._runlock.acquire()
+ if self._status not in (DONE, FAILED, STOPPED):
+ self._runlock.release()
+ raise setup.TaskNotReadyError
+ ret = None
+ if self._reverse:
+ try:
+ ret = self._reverse()
+ except:
+ self._runlock.release()
+ return False
+ if ret:
+ self._status = READY
+ self._runlock.release()
+ return ret
+
+ def status(self):
+ self._runlock.acquire()
+ ret = self._status
+ self._runlock.release()
+ return ret
+
+ def wait(self):
+ pass
+
+ def wait_unpause(self):
+ pass
+
+ def space_req(self):
+ return (self.tmpsize, self.outsize)
+
+ def output(self):
+ self._outlock.acquire()
+ out = self._output
+ self._outlock.release()
+ return out
+
+ def elapsed_time(self):
+ if not self._finish_time:
+ return False
+ return self._finish_time - self._start_time
+
+ def check_sibling(self):
+ bro = self.prev()
+ if bro and bro.status() != DONE:
+ raise setup.TaskNotReadyError
+
+ def next(self):
+ """Return the next task in the queue."""
+ try:
+ queue = self._parent.tasks()
+ except:
+ return None
+ qi = list(queue).index(self)
+ if qi + 1 < len(queue):
+ return queue[qi + 1]
+
+ def prev(self):
+ """Return the previous task in the queue."""
+ try:
+ queue = self._parent.tasks()
+ except:
+ return None
+ qi = list(queue).index(self)
+ if qi - 1 >= 0:
+ return queue[qi - 1]
+
+
+class Task(SimpleTask):
+ """Run a command in a subprocess and monitor its progress"""
+
+ def __init__(self, parent, action, filter=None, reverse=None, tmpsize=0, outsize=0, name=''):
+ if type(action) not in (list, tuple):
+ raise TypeError
+ for element in action:
+ if type(element) is not type(''):
+ raise TypeError
+ self._paused = threading.Lock()
+ self._pid = None
+ self._eater = None
+ SimpleTask.__init__(self, parent, action, filter, reverse, tmpsize, outsize, name)
+
+ def run(self):
+ """Run the command in a sub process
+
+ This will probably only work on Linux.
+
+ """
+
+ self.check_sibling()
+ self._runlock.acquire()
+ self._start_time = time.time()
+ if self._status == PAUSED:
+ self._paused.release()
+ self._status = RUNNING
+ self._runlock.release()
+ return True
+ if self._status != READY:
+ self._runlock.release()
+ return False
+ self._status = RUNNING
+
+ fp = None
+
+ def eatout():
+ while not fp:
+ pass
+ while self._status in (RUNNING, PAUSED):
+ wpid, status = os.waitpid(pid, os.WNOHANG)
+ if wpid == pid:
+ # Child exited
+ self._runlock.acquire()
+ if self._paused.locked():
+ self._paused.release()
+ if os.WIFSIGNALED(status):
+ self._status = FAILED
+ elif os.WEXITSTATUS(status) == 0:
+ self._status = DONE
+ else:
+ self._status = FAILED
+ self._runlock.release()
+ break
+ if self._paused.locked():
+ continue
+ rfd, wfd, xfd = select.select([master_fd], [], [], 5)
+ if not rfd:
+ break
+ try:
+ if master_fd in rfd:
+ out = fp.read()
+ except IOError:
+ break
+ if self._filter:
+ try:
+ out = self._filter(out)
+ except:
+ out = ''
+ self._outlock.acquire()
+ self._output = out
+ self._outlock.release()
+ # Child still running, no more output: block on child's exit
+ if self._status in (RUNNING, PAUSED):
+ self._runlock.acquire()
+ if self._paused.locked():
+ self._paused.release()
+ try:
+ wpid, status = os.waitpid(pid, 0)
+ if os.WIFSIGNALED(status):
+ self._status = FAILED
+ elif os.WEXITSTATUS(status) == 0:
+ self._status = DONE
+ else:
+ self._status = FAILED
+ except OSError:
+ self._status = FAILED
+ self._runlock.release()
+ self._finish_time = time.time()
+
+ self._eater = threading.Thread(None, eatout)
+ self._eater.start()
+
+ pid, master_fd = pty.fork()
+ if pid == 0:
+ time.sleep(0.0125)
+ try:
+ os.execvp(self._action[0], self._action)
+ except:
+ os._exit(1)
+ self._pid = pid
+ fp = os.fdopen(master_fd, 'r', 0)
+ if os.uname()[0] == "Linux":
+ fcntl.fcntl(master_fd, fcntl.F_SETFL, os.O_NONBLOCK)
+ time.sleep(0.0125)
+ self._runlock.release()
+ return True
+
+ def pause(self):
+ self._runlock.acquire()
+ if self._status == RUNNING:
+ self._paused.acquire()
+ self._status = PAUSED
+ self._runlock.release()
+ return True
+ else:
+ self._runlock.release()
+ return False
+
+ def stop(self):
+ """Stop the running process"""
+ self._runlock.acquire()
+ if self._status == RUNNING:
+ try:
+ os.kill(self._pid, signal.SIGKILL)
+ self._status = STOPPED
+ self._runlock.release()
+ self._eater.join()
+ return True
+ except OSError:
+ pass
+ self._runlock.release()
+ return False
+
+ def wait(self):
+ if self._eater:
+ self._eater.join()
+
+ def wait_unpause(self):
+ self._paused.acquire()
+ self._paused.release()
+
file:99090b37cb27b8e6359e27801accd782d52b549c(new)
--- /dev/null
+++ b/mp3togo/track.py
@@ -0,0 +1,242 @@
+# - track.py -
+# This file is part of mp3togo
+
+# Convert audio files to play on a mp3 player
+# Manage the transform of a single file
+#
+# (c) Simeon Veldstra 2006 <reallifesim@gmail.com>
+#
+# This software is free.
+#
+# 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 may redistribute this program under the terms of the
+# GNU General Public Licence version 2
+# Available in this package or at http://www.fsf.org
+
+import os
+import sys
+
+import mp3togo.conf as setup
+
+if setup.THREADED == True:
+ import threading
+else:
+ import dummy_threading as threading
+
+import mp3togo.tags as tags
+import mp3togo.task as task
+import mp3togo.helpers
+
+helpers = mp3togo.helpers.helpers
+
+
+READY = "ready"
+RUNNING = "running"
+DONE = 0
+FAILED = None
+
+DOGGFACTOR = 10
+DMP3FACTOR = 10
+DFLACFACTOR = 3
+
+def cache_out(out, cache=['']):
+ cache[0] += out
+ return cache[0]
+
+
+class Track:
+ """Encapsulate the transformation of a file."""
+
+ def __init__(self, filename, opts):
+
+ self.bytes = 0
+ self._filename = filename
+ self._queue = ()
+ self._state = FAILED
+
+ def undo_decode():
+ if os.path.exists(self._wavname):
+ os.unlink(self._wavname)
+ return True
+
+ def undo_encode():
+ if os.path.exists(self._outname):
+ os.unlink(self._outname)
+ return True
+
+ # Sentinel tasks
+ def finish():
+ if os.path.exists(self._outname):
+ self.bytes = os.stat(self._outname).st_size
+ self._state = DONE
+ return DONE
+ def start():
+ self.bytes = 0
+ self._state = RUNNING
+ return RUNNING
+ def abort():
+ self._state = FAILED
+ return FAILED
+ job = task.SimpleTask(self, start, None, abort, name="Start")
+ tasks = [job]
+ del job
+
+ # Names
+ filetype = setup.getfiletype(filename)
+ dir, base = os.path.split(filename)
+ base = os.path.splitext(base)[0]
+ base = base + opts['brwarning']
+ base = opts.cleanfilename(base)
+ if opts['treedepth']:
+ head, dir = os.path.split(dir)
+ for i in range(opts['treedepth'] -1):
+ head, tail = os.path.split(head)
+ dir = os.path.join(tail, dir)
+ dir = opts.cleanfilename(dir)
+ else:
+ dir = ''
+ self._outdir = os.path.join(opts['playerdir'], dir)
+ self._outname = os.path.join(self._outdir, base)
+ self._wavname = os.path.join(opts['tempdir'], base) + '.wav'
+
+ if os.path.exists(self._outname) and not opts['force']:
+ opts.log(1, "Skipping existing file: %s\n" % self._outname)
+ # Should throw an exception?
+ return
+
+ def make_dirs():
+ if not os.path.isdir(self._outdir):
+ os.makedirs(self._outdir)
+ return True
+
+ def rm_dirs():
+ if opts['treedepth']:
+ tail = self._outdir
+ for i in range(opts['treedepth']):
+ try:
+ os.rmdir(tail)
+ except OSError:
+ return True
+ tail, head = os.path.split(tail)
+ return True
+
+ job = task.SimpleTask(self, make_dirs, None,
+ rm_dirs, name="Creating dirs")
+ tasks.append(job)
+ del job
+
+ # Decode
+ prog = mp3togo.helpers.find_helper(filetype, 'decode')
+ tmpreq = mp3togo.helpers.est_decoded_size(self._filename)
+ jobname = "Decoding %s" % filetype
+ if callable(helpers[prog]['cmd']):
+ # SimpleTask
+ func = helpers[prog]['cmd'](self._filename, self._wavname)
+ job = task.SimpleTask(self, func, None, undo_decode,
+ tmpsize=tmpreq, name=jobname)
+ else:
+ # Task
+ args = mp3togo.helpers.make_args(prog, self._filename,
+ self._wavname)
+ job = task.Task(self, args, helpers[prog]['parser'],
+ undo_decode, tmpsize=tmpreq, name=jobname)
+ tasks.append(job)
+ del job
+
+ # Normalize
+ if opts.bin['normalize-audio']:
+ ncmd = [opts.bin['normalize-audio'], self._wavname]
+ job = task.Task(self, ncmd, lambda: '',
+ lambda: True, name="Normalizing")
+ tasks.append(job)
+ del job
+ else:
+ if not opts['nonormal']:
+ opts.log(2, "'normalize-audio' binary not found, skipping.")
+
+ # Encode
+ encoder = opts['encoder']
+ prog = helpers[encoder]
+ self._outname += '.' + prog['type']
+ outreq = tmpreq / opts['compfactor']
+ jobname = "Encoding %s" % prog['type']
+ filter = prog['parser']
+ if callable(prog['cmd']):
+ func = prog['cmd'](self._wavname, self._outname)
+ job = task.SimpleTask(self, func, None, undo_encode,
+ outsize=outreq, name=jobname)
+ else:
+ # Task
+ args = mp3togo.helpers.make_args(encoder, self._wavname,
+ self._outname, opts['encopts'])
+ job = task.Task(self, args, filter,
+ undo_encode, outsize=outreq, name=jobname)
+ tasks.append(job)
+ del job
+
+ # Clean up wav
+ job = task.SimpleTask(self, undo_decode, None, undo_decode,
+ tmpsize=-tmpreq,
+ name='Cleaning')
+ tasks.append(job)
+ del job
+
+ # Read the tags
+ self.tags = tags.Tags(filename)
+ self.tags.read()
+
+ # Write index file
+ if opts['index']:
+ indexname = os.path.join(self._outdir, '2go.index')
+ def undo_index():
+ tags.remove_from_index(self._outname, indexname)
+ return True
+ def write_index():
+ self.tags.writeindex(self._outname, indexname)
+ return True
+ job = task.SimpleTask(self, write_index, None,
+ undo_index, name='Writing index')
+ tasks.append(job)
+ del job
+
+ # Tag
+ def tag_output():
+ self.tags.write(self._outname)
+ return True
+ job = task.SimpleTask(self, tag_output, None,
+ lambda: True, name="Tagging")
+ tasks.append(job)
+ del job
+
+ # Completion sentinel
+ job = task.SimpleTask(self, finish, None, start, name="Done")
+ tasks.append(job)
+ del job
+
+ # Ready to go
+ self._queue = tuple(tasks)
+ self._state = READY
+ return
+
+ def tasks(self):
+ return self._queue
+
+ def getfirst(self):
+ return self._queue[0]
+
+ def __call__(self):
+ return self._state
+
+ def __del__(self):
+ self.close()
+
+ def close(self):
+ for child in self._queue:
+ child._parent = None
+ self._queue = ()
+
+