Version 0.4.0
Add caching support for processed files.
Add support for Amarok playlist grabbing - Justus Pendleton
Support specifing playlists as free arguments - idea: Christopher Arndt

--
sim

file:bf79b4c4d44a057321ea4cc52a77103b7172ca42 -> file:980addefe24712d0e8924f4be5d4839de5a7cabf
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+mp3togo (0.4.0) unstable; urgency=low
+
+ * Add caching support for processed files.
+ * Add support for Amarok playlist grabbing - Justus Pendleton
+ * Support specifing playlists as free arguments - idea: Christopher Arndt
+
+ -- Simeon Veldstra <reallifesim@gmail.com> Thu, 18 May 2006 00:30:09 -0700
+
mp3togo (0.3.2) unstable; urgency=low
* Really be graceful in the absence of third party modules.
file:38dbd6d27ac4508eb0f64a160a1bff8ae5b23fd1 -> file:ccd5f78cd6cd38860fe726c9f4575a8596f1c492
--- a/debian/mp3togo.1
+++ b/debian/mp3togo.1
@@ -134,4 +134,4 @@ License can be found in /usr/share/commo
.PP
mp3togo can be found online at http://puddle.ca/mp3togo
-.\" created by instant / docbook-to-man, Wed 17 May 2006, 18:51
+.\" created by instant / docbook-to-man, Thu 18 May 2006, 13:05
file:12e6e7b89dcfd84a6b800871743f2b5e0598207d -> file:ddfa839de3211786fc4719e303d9724ce94dc6b6
--- a/mp3togo/__init__.py
+++ b/mp3togo/__init__.py
@@ -21,5 +21,5 @@
# __all__ = ('converter', 'main', 'options', 'setup')
-version = '0.3.2'
+version = '0.4.0'
file:fc57f0c3a5c1dd9fd7f676285b4f24fcab9b4662 -> file:a7031634a952c7241167419d0a8e1491edefb7d9
--- a/mp3togo/conf.py
+++ b/mp3togo/conf.py
@@ -1,4 +1,4 @@
-# - setup.py -
+# - conf.py -
# This file is part of mp3togo
# Convert audio files to play on a mp3 player
@@ -25,7 +25,6 @@ import sys, os
import mp3togo
import mp3togo.options as options
-THREADED=True
class Fail(options.Fail):
pass
@@ -39,6 +38,9 @@ class ErrorUnknownFileType(Error):
class ErrorNoFile(Error):
pass
+class ErrorNoCache(Error):
+ pass
+
class ErrorBadFormat(Error):
pass
@@ -48,6 +50,12 @@ class ErrorNoXMMSControl(Error):
class ErrorXMMSNotRunning(Error):
pass
+class ErrorNoDCOP(Error):
+ pass
+
+class ErrorAmarokNotRunning(Error):
+ pass
+
class ErrorUnlocked(Error):
pass
@@ -66,14 +74,14 @@ class Options(options.Options):
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,
+ ('tempdir', 'w', 'work-dir', '/tmp', self._absfile,
'''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,
+ ('maxunits', 'm', 'max-size', '0', self._units,
'''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,
+ ('maxtempunits', '', 'max-temp-size', '0', self._units,
'''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,
@@ -84,18 +92,27 @@ class Options(options.Options):
'''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.'''),
+ ('makecache', '', 'make-cache', '', self._absfile, '''Make an output file cache at the given path.'''),
+ ('cachesize', '', '', 0L, None, ''),
+ ('cacheunits', '', 'cache-size', '0', self._units, '''The size for the new cache.'''),
+ ('usecache', '', 'use-cache', '', self._absfile, '''Use the cache stored at the given path.'''),
('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.'''),
+ '''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. Playlists can be also listed on the command line as free arguments after all of the options if they have a recognizable extension. Currently .m3u and .pls are recognized.'''),
('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,
+ '''Get playlist from running instance of XMMS. (You must have the python-xmms module installed)'''),
+ ('readamarok', '', 'import-amarok', False, None,
+ '''Get playlist from running instance of Amarok.'''),
+ ('playerdir', 'o', 'output-dir', os.curdir, self._absfile,
'''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.''')]
+ # Override hook defined in Base class
+ self._conffile = self._absfile
+
# Options to not save to the config file:
self._nosave_options += []
@@ -108,7 +125,7 @@ class Options(options.Options):
self.bin['flac'] = False
self.bin['metaflac'] = False
self.bin['normalize-audio'] = False
- self.bin['xmms-shell'] = False
+ self.bin['dcop'] = False
self.version = mp3togo.version
self._help_preamble = """
@@ -129,10 +146,9 @@ Description:
"""
# Check for Third party modules:
- self.mod = {'ogg.vorbis': False,
- 'ID3': False,
- 'fictionalmod': False,
- 'xmms': False}
+ self.mod['ogg.vorbis'] = False
+ self.mod['ID3'] = False
+ self.mod['xmms'] = False
# Go ahead and read in the data:
self.getconf(argv, conffile, readconf)
@@ -140,39 +156,17 @@ Description:
# All hooks are called with the lock held
- # must reference the ._d dict directly
+ # and 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 _absfile(self, name, value):
+ self._d[name] = absfile(value)
- def _maxtempunits(self, value):
- #Calculate raw bytes and set maxsize
+ def _units(self, name, value):
+ #Calculate raw bytes and set [max|temp|cache]size
try:
if value[-1] in ('m', 'M'):
max = long(value[:-1]) * 1048576L
@@ -183,17 +177,18 @@ Description:
else:
max = long(value)
except ValueError:
- raise Fail, "Bad max-temp-size option: %s " % value
- self._d['maxtempsize'] = max
- self._d['maxtempunits'] = value
+ raise Fail, "Bad %s option: %s " % (name, value)
+ sizename = name.replace('units', 'size')
+ self._d[sizename] = max # 'maxsize'
+ self._d[name] = value # 'maxunits'
- def _arg_files(self, value):
+ def _arg_files(self, name, value):
l = []
for f in value:
f = absfile(f)
if os.path.exists(f):
l.append(f)
- self._d['arg_files'] = l
+ self._d[name] = l
def reset_encoder_options(self):
"""Reset encoder options to defaults."""
file:9ebbb8b48eb57402a147aca7b4932e091c63d15e(deleted)
--- a/mp3togo/converter.py
+++ /dev/null
@@ -1,259 +0,0 @@
-# - converter.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
-
-
-"""Process a list of files.
-
- Do the conversion. Pass eatlist an Options instance."""
-
-import sys
-import os
-import ogg.vorbis
-import ID3
-
-
-# iterate through the playlist
-def eatlist(opts, playlist):
- """Reencode Files.
-
- Re-encodes a list of files into a
- portable friendly format. Ignores
- nonexistant files. Understands xmms
- '.pls' playlists and .m3u files."""
-
- opts.log(1, "Writing files to %s" % opts['playerdir'])
- bytecount = 0
- for filename in playlist:
- if not os.path.exists(filename):
- opts.log(2, "\"%s\" is not a valid file" % filename)
- continue
-
- #cook up some filenames
- pathto, outname = os.path.split(filename)
- outname, extension = os.path.splitext(outname)
- extension = extension.lower()
- outname = outname + opts['brwarning']
- outname = outname.replace(' ', '_').replace("'", "")
- outname = outname.replace(':', '').replace("?", "")
- outname = outname.replace('*', '')
- outname = outname.replace('`', '')
- wavname = os.path.join(opts['tempdir'], outname) + ".wav"
- if opts['treedepth']:
- pathto, dir = os.path.split(pathto)
- for i in range(opts['treedepth'] -1):
- pathto, e = os.path.split(pathto)
- dir = os.path.join(e, dir)
- else:
- dir = ''
- mp3name = os.path.join(opts['playerdir'], dir)
- mp3name = mp3name.replace(':', '') # lyra chokes on ':'
- if not os.path.isdir(mp3name):
- os.makedirs(mp3name)
- indexname = os.path.join(mp3name, "track.index")
- mp3name = os.path.join(mp3name, outname)
- if opts['encoder'] == 'lame':
- mp3name += ".mp3"
- elif opts['encoder'] == 'oggenc':
- mp3name += ".ogg"
- elif opts['encoder'] == 'wave':
- mp3name += ".wav"
-
- #set up decoder
- if opts['encoder'] == 'wave':
- wavname = mp3name # decode right to the output file in wave mode
- if extension == ".ogg":
- if not opts.bin['ogg123']:
- opts.log(1, "'ogg123' binary not found! Aborting track.")
- continue
- decodecmd = "%s --device wav --file \"%s\" \"%s\" " % \
- (opts.bin['ogg123'], wavname, filename)
- tags = ogg.vorbis.VorbisFile(filename).comment().as_dict()
- elif extension == ".mp3":
- if not opts.bin['mpg321']:
- opts.log(1, "'mpg321' binary not found! Aborting track.")
- continue
- decodecmd = "%s --wav \"%s\" \"%s\" " % \
- (opts.bin['mpg321'], wavname, filename)
- tags = ID3.ID3(filename, as_tuple=1).as_dict()
- elif extension == ".flac":
- if not opts.bin['flac']:
- opts.log(1, "'flac' binary not found! Aborting track.")
- continue
- decodecmd = '%s --decode -F -o "%s" "%s" ' %\
- (opts.bin['flac'], wavname, filename)
- tags = {}
- if opts.bin['metaflac']:
- cmd = '%s --export-tags-to=- "%s" ' % (opts.bin['metaflac'], filename)
- fd = os.popen(cmd)
- data = fd.read()
- fd.close()
- data = data.split('\n')
- for tag in data:
- pair = tag.split('=', 1)
- if len(pair) == 2:
- tags[pair[0].upper()] = (pair[1],)
- elif extension == ".wav":
- # make a copy so we don't normalize the original
- decodecmd = 'cp "%s" "%s" ' % (filename, wavname)
- tags = {}
- else:
- #ignore files we don't understand
- opts.log(2, "Ignoring: %s" % filename)
- continue
-
-
- #guess missing tags
- name = os.path.splitext(filename)[0].replace('_', ' ')
- for id, depth in [('ARTIST', -3), ('ALBUM', -2), ('TITLE', -1)]:
- if not id in tags or tags[id][0] == '':
- try:
- tags[id] = [name.split(os.sep)[depth]]
- if tags[id][0][-4:].lower() in [".ogg", ".mp3"]:
- tags[id][0] = tags[id][0][:-4]
- except IndexError:
- tags[id] = "%s unknown" % id.lower()
- tags[id] = [tags[id][0].replace('`', '\`')]
- #tags[id][0] = tags[id][0].replace('*', '')
- #tags[id][0] = tags[id][0].replace(':', '')
-
- #set up encoder
- if opts['encoder'] == 'lame':
- if not opts.bin['lame']:
- opts.log(1, "'lame' binary not found! Aborting track.")
- continue
- encodecmd = "%s %s \"%s\" \"%s\"" % \
- (opts.bin['lame'], opts['encopts'], wavname, mp3name)
- elif opts['encoder'] == 'oggenc':
- if not opts.bin['oggenc']:
- opts.log(1, "'oggenc' binary not found! Aborting track.")
- continue
- encodecmd = "%s %s -o \"%s\" \"%s\"" %\
- (opts.bin['oggenc'], opts['encopts'], mp3name, wavname)
- else:
- encodecmd = ''
-
- # Set up Normalizer
- if opts.bin['normalize-audio']:
- normcmd = '%s "%s" ' % (opts.bin['normalize-audio'], wavname)
- else:
- normcmd = ''
- if not opts['nonormal']:
- opts.log(3, "'normalize-audio' binary not found, skipping pass.")
-
- # If the target file exists, don't do it, unless we have a mandate.
- if os.path.exists(mp3name) and not opts['force']:
- opts.log(1, "Skipping existing file: %s\n" % mp3name)
- continue
-
- # Be quiet if asked
- if opts['verbosity'] == 0:
- decodecmd += " >/dev/null 2>&1"
- normcmd += " >/dev/null 2>&1"
- if encodecmd:
- encodecmd += " >/dev/null 2>&1"
-
- # Check for enough tempspace for wav
- dir = os.path.dirname(wavname)
- need = os.stat(filename).st_size * 10 # Require 10x encoded file size
- free = os.statvfs(dir).f_bfree * os.statvfs(dir).f_frsize
- if need > free:
- opts.log(1, "Not enough free space in %s to decode %s, skipping." % \
- (dir, os.path.basename(filename)))
- continue
-
- # Actually read and write files:
- # Decode
- opts.log(2, "Decoding...")
- ret = os.system(deunicode(decodecmd))
- if ret != 0:
- opts.log(1, "Error decoding file %s, skipping file." % filename)
- if os.path.exists(wavname):
- os.unlink(wavname)
- continue
- if (bytecount + (os.stat(wavname).st_size/opts['compfactor'])) > opts['maxsize']:
- opts.log(1, "Size limit reached - aborting: %s" % filename)
- os.unlink(wavname)
- return 1
-
- # Normalize
- if not opts['nonormal'] and normcmd:
- opts.log(2, "Normalizing...")
- ret = os.system(normcmd)
- if ret != 0:
- opts.log(1, "Error normalizing %s, attempting to continue" % wavname)
-
- # Encode
- opts.log(2, "Encoding...")
- if opts['encoder'] != 'wave':
- opts.log(1, '')
- ret = os.system(deunicode(encodecmd))
- #if wavname != mp3name:
- os.unlink(wavname)
- if ret != 0:
- opts.log(1, "Error encoding file %s, skipping file." % wavname)
- if os.path.exists(mp3name):
- os.unlink(mp3name)
- continue
- bytecount += os.stat(mp3name).st_size
-
- # Tag
- opts.log(2, 'Tagging...')
- if opts['encoder'] == 'lame':
- out = ID3.ID3(mp3name, as_tuple=1)
- elif opts['encoder'] == 'oggenc':
- vf = ogg.vorbis.VorbisFile(mp3name)
- out = vf.comment()
- else:
- out = {}
-
- out['TITLE'] = tags['TITLE'][0]
- out['ARTIST'] = tags['ARTIST'][0]
- out['ALBUM'] = tags['ALBUM'][0]
- if tags.has_key('TRACKNUMBER'):
- out['TRACKNUMBER'] = tags['TRACKNUMBER'][0]
- if opts['brwarning']:
- out['COMMENT'] = opts['brwarning']
-
- if opts['encoder'] == 'lame':
- out.write()
- del out
- elif opts['encoder'] == 'oggenc':
- out.write_to(mp3name)
- del out
- del vf
- if opts['index']:
- try:
- ifile = file(indexname, 'a')
- ifile.write(mp3name + "\n")
- for k in out:
- ifile.write("%s: %s\n" % (k, out[k]))
- ifile.write("\n\n")
- ifile.close()
- except IOError:
- opts.log(1, "Can't open index file %s" % indexname)
-
- opts.log(1, "\n")
-
-
-
-def deunicode(str):
- if type(str) is type(u""):
- str = str.encode('ascii', 'replace')
- str = str.replace('\0', '')
- return str
-
file:94c76392b31f99a8d13d78631f9663d965457e93 -> file:a26b6a880389e739bf13c368c3285fb859ab1706
--- a/mp3togo/filelist.py
+++ b/mp3togo/filelist.py
@@ -19,21 +19,19 @@
"""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
+import sys
+import os
+import tempfile
+import threading
+import mp3togo.conf as setup
class FileList:
"""A list of verified music files."""
- def __init__(self):
+ def __init__(self, opts):
+ self._opts = opts
self._list = []
self._i = 0
self._lock = threading.Lock()
@@ -60,7 +58,11 @@ class FileList:
for name in names:
try:
- name = setup.checkfile(name)
+ if os.path.splitext(name)[1] in ('.m3u', '.pls'):
+ self._addplaylist(name)
+ continue
+ else:
+ name = setup.checkfile(name)
except:
continue
@@ -71,33 +73,41 @@ class FileList:
return True
def addplaylist(self, lst, block=True):
- if not os.path.exists(lst):
- raise setup.ErrorNoFile
-
+ """Add a playlist."""
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:
+ ret = self._addplaylist(lst)
+ except:
self._lock.release()
+ raise
+ self._lock.release()
+ return ret
+
+ def _addplaylist(self, lst):
+ if not os.path.exists(lst):
+ raise setup.ErrorNoFile
+ 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]
+ try:
+ name = setup.checkfile(name)
+ except:
+ continue
+ self._list.append(name)
return True
def addXMMS(self, session=0, block=True):
- try:
- import xmms.control
- except ImportError:
+ if self._opts.mod['xmms']:
+ import xmms
+ else:
raise setup.ErrorNoXMMSControl
if not xmms.control.is_running:
raise setup.ErrorXMMSNotRunning
@@ -108,6 +118,20 @@ class FileList:
# locks self in addfiles
self.addfiles(res, block)
+ def addAmarok(self, session=0, block=True):
+ # Thanks Justus Pendleton
+ if self._opts.bin['dcop']:
+ m3u = tempfile.NamedTemporaryFile(suffix='.m3u',
+ dir=self._opts['tempdir'])
+ # dcop amarok playlist saveM3u(string path, bool relativePaths)
+ exit_code = os.spawnl(os.P_WAIT, self._opts.bin['dcop'],
+ 'dcop', 'amarok', 'playlist',
+ 'saveM3u', m3u.name, '0')
+ if exit_code != 0:
+ raise setup.ErrorAmarokNotRunning
+ self.addplaylist(m3u.name)
+ #os.unlink(m3u.name) <- cleaned up automaticaly by tempfile
+
def removefile(self, name, block=True):
if not self._lock.acquire(block) and not block:
return False
file:843492b28bcd01439c3894b34971dff830a95a9b -> file:595bbc43354d24c0acd060e265f20786801b61fa
--- a/mp3togo/main.py
+++ b/mp3togo/main.py
@@ -38,9 +38,10 @@ import mp3togo.conf as conf
import mp3togo.track as track
import mp3togo.task as task
import mp3togo.pool as pool
+import mp3togo.cache as cache
-def fail(mesg, code=1):
+def fail(mesg='', code=1):
if mesg:
print >>sys.stderr, mesg
sys.exit(code)
@@ -50,13 +51,34 @@ def main(argv):
# Read in configuration
try:
opts = conf.Options(argv)
- except conf.Fail, msg:
- print setup.Options().usage()
+ except mp3togo.options.Fail, msg:
+ print conf.Options().usage()
fail(str(msg))
+ # Are we creating a cache?
+ if opts['makecache']:
+ if not opts['cachesize']:
+ opts.log(1, "Must specify a --cache-size to create a cache")
+ fail()
+ if cache.create(opts['makecache'], opts):
+ opts.log(1, "Cache successfully created at %s" % opts['makecache'])
+ return 0
+ else:
+ opts.log(1, "Create cache at %s failed" % opts['makecache'])
+ fail()
+
+ # Are we using a cache?
+ if opts['usecache']:
+ try:
+ cooked = cache.Cache(opts['usecache'], opts)
+ except:
+ opts.log(1, "Error reading cache at %s" % opts['usecache'])
+ else:
+ cooked = None
+
# Check for sanity
if not opts.bin[opts['encoder']]:
- opts.log(1, "Encoder binary '%s' not found in PATH! Quiting." % opts['encoder'])
+ opts.log(1, "Encoder binary '%s' not found! Quiting." % opts['encoder'])
fail('')
if not opts.bin['normalize-audio'] and not opts['nonormal']:
opts.log(1, "'normalize-audio' binary not found. Normalization disabled.")
@@ -66,8 +88,10 @@ def main(argv):
fail('')
# Build a playlist
- playlist = filelist.FileList()
+ playlist = filelist.FileList(opts)
+ # We could move these into FileList,
+ # but, we may want interactive import buttons in a GUI later.
if opts['playlist']:
playlist.addplaylist(opts['playlist'])
@@ -75,7 +99,22 @@ def main(argv):
playlist.addfiles(opts['arg_files'])
if opts['readxmms']:
- playlist.addXMMS()
+ try:
+ try:
+ playlist.addXMMS()
+ except conf.ErrorNoXMMSControl:
+ opts.log(1, "python-xmms is not installed. Can't open XMMS.")
+ except conf.ErrorXMMSNotRunning:
+ opts.log(1, "XMMS Does not seem to be running.")
+
+ if opts['readamarok']:
+ if opts.bin['dcop']:
+ try:
+ playlist.addAmarok()
+ except conf.ErrorAmarokNotRunning:
+ opts.log(1, "Amarok Does not seem to be running.")
+ else:
+ opts.log(1, "dcop is not installed. Can't open Amarok.")
# Offer a hint
if len(playlist) == 0:
@@ -84,10 +123,10 @@ def main(argv):
fail('')
# Start work
- execute_sequential(playlist, opts)
+ execute_sequential(playlist, opts, cooked)
-def execute_sequential(playlist, opts):
+def execute_sequential(playlist, opts, cooked=None):
"""Run the conversions one at a time"""
print "mp3togo %s\n" % mp3togo.version
print "<space> or 'p' to pause, <esc> or 'q' to quit\n"
@@ -111,7 +150,7 @@ def execute_sequential(playlist, opts):
for name in playlist:
track_start = time.time()
print "(%d/%d) %s: " % (playlist.cur_file() + 1, len(playlist), name)
- trk = track.Track(name, opts)
+ trk = track.Track(name, opts, cooked)
pl = pool.Pool(opts)
if pl.add_track(trk):
tsk = trk.getfirst()
file:f6b6c6f29866a7d4747163b508760c8f59f8f1c2 -> file:93405b2ba1c2d000d8db249d8741f300d30681dd
--- a/mp3togo/options.py
+++ b/mp3togo/options.py
@@ -39,7 +39,6 @@ import sys
import os
import UserDict
import getopt
-#import dummy_threading as threading
import threading
class Fail(Exception):
@@ -317,7 +316,7 @@ interface.
if i[4]:
try:
self._lock()
- apply(i[4], [value])
+ apply(i[4], [name, value])
finally:
self._unlock()
else:
@@ -430,28 +429,28 @@ interface.
- # These are stubs to be overridden in subclasses:
+ # These are callback stubs to be overridden in subclasses:
def _post_hook(self):
pass
def _pre_hook(self):
pass
- def _arg_files(self, value):
+ def _arg_files(self, name, value):
self._d['arg_files'] = value
- def _conffile(self, value):
+ def _conffile(self, name, value):
self._d['conffile'] = value
- def _saveconffile(self, value):
+ def _saveconffile(self, name, value):
self._d['saveconffile'] = value
- def _noconffile(self, value):
+ def _noconffile(self, name, value):
self._d['noconffile'] = value
- def _verbosity(self, value):
+ def _verbosity(self, name, value):
self._d['verbosity'] = value
- def _help(self, value):
+ def _help(self, name, value):
self._d['help'] = value
file:e40b93b5517c48b8e25bd4f63930cbfbd2f9abdc -> file:15de92be29e48d5b651aa6943bc37e73bbca3c48
--- a/mp3togo/pool.py
+++ b/mp3togo/pool.py
@@ -17,16 +17,12 @@
# 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 threading
+import mp3togo.conf as setup
import mp3togo.track as track
file:a1b41b0259758ad3a299277590f7eb458c21da07(deleted)
--- a/mp3togo/setup.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# - 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 options
-import sys, os
-
-class Fail(options.Fail):
- 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', None,
- '''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, ''),
- ('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 wave, lame or oggenc.'''),
- ('encopts', 'E', 'encoder-options', '', None,
- '''Compression options for the encoder.'''),
- ('compfactor', 'z', 'compression-factor', 10.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['wave'] = 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._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)
-
- def _post_hook(self):
- #Set up default encoder options if not specified:
- if not self['encopts']:
- self.reset_encoder_options()
-
- def _conffile(self, value):
- self._d['conffile'] = absfile(value)
-
- def _playerdir(self, dir):
- self._d['playerdir'] = 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 _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['encoder'] == 'lame':
- self['encopts'] = '--abr 96'
- elif self['encoder'] == 'oggenc':
- self['encopts'] = '-m 96 -M 225 -b 100'
- elif self['encoder'] == 'wave':
- self['encopts'] = ''
-
-
-def absfile(name):
- name = name.strip()
- name = os.path.expanduser(name)
- name = os.path.abspath(name)
- return name
file:ed553e0368bc74a27ba03b45d8ad4f065dc72728 -> file:3c33f5cf55eebb032caa678ae816d7fb4c12c729
--- a/mp3togo/tags.py
+++ b/mp3togo/tags.py
@@ -17,16 +17,16 @@
# 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 threading
+
+import mp3togo.conf as setup
+# This mess needs to be fixed,
+# Tags() is just going to have to
+# take an Options() instance.
try:
import ogg.vorbis
except ImportError:
file:8e70d6d565988e5da484dbe64e50c0eb73f674b0 -> file:3cb5dbb7afcf56f147a375a387b6a8270a19cde3
--- a/mp3togo/task.py
+++ b/mp3togo/task.py
@@ -17,12 +17,6 @@
# 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
@@ -31,6 +25,9 @@ import select
import pty
import fcntl
import signal
+import threading
+
+import mp3togo.conf as setup
READY = 'ready'
file:63ddcf60c23fabb7572882d97c67449f984b1bd6 -> file:871ce345d2d3e00dead1e2b331941d96cb5727ad
--- a/mp3togo/track.py
+++ b/mp3togo/track.py
@@ -17,18 +17,15 @@
# GNU General Public Licence version 2
# Available in this package or at http://www.fsf.org
+
import os
import sys
+import threading
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.cache as cache
import mp3togo.helpers
helpers = mp3togo.helpers.helpers
@@ -43,20 +40,18 @@ 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):
+ def __init__(self, filename, opts, cooked=None):
self.bytes = 0
self._filename = filename
self._queue = ()
self._state = FAILED
+ self._cache = cooked
+ self._hash = False
def undo_decode():
if os.path.exists(self._wavname):
@@ -125,60 +120,81 @@ class Track:
tasks.append(job)
del job
+ # Check the cache - if there is one
+ if self._cache:
+ self._hash = self._cache.search(self._filename)
+ if self._hash:
+ def recover_cache():
+ return self._cache.recover(self._hash, self._outname)
+ def undo_recover_cache():
+ if os.path.exists(self._outname):
+ os.unlink(self._outname)
+ return True
+ outreq = os.stat(self._cache.file(self._hash)).st_size
+ job = task.SimpleTask(self, recover_cache, None,
+ undo_recover_cache,
+ outsize=outreq, name="Hitting Cache")
+ 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")
+ if not self._hash:
+ 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
- else:
- if not opts['nonormal']:
- opts.log(2, "'normalize-audio' binary not found, skipping.")
+
+ # Normalize
+ if not self._hash:
+ 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]
- 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
+ if not self._hash:
+ encoder = opts['encoder']
+ prog = helpers[encoder]
+ 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
+ if not self._hash:
+ 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)
@@ -199,13 +215,24 @@ class Track:
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
+ if not self._hash:
+ 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
+
+ # Cache the output if we had to go to the trouble of producing it
+ if not self._hash and self._cache:
+ self._hash = cache.checksum(self._filename)
+ def cache_final():
+ self._cache.stash(self._outname, self._hash)
+ return True
+ job = task.SimpleTask(self, cache_final, name="Caching result")
+ tasks.append(job)
+ del job
# Completion sentinel
job = task.SimpleTask(self, finish, None, start, name="Done")
@@ -216,6 +243,8 @@ class Track:
if os.path.exists(self._outname) and not opts['force']:
opts.log(1, "Skipping existing file: %s\n" % self._outname)
self._queue = tuple(tasks)
+ if self._hash and self._cache:
+ self._cache.release(self._hash)
for tsk in self._queue:
tsk._status = task.DONE
self._state = DONE