Version 0.4.1
Bug fixes:
Added test for failed write to stdout in mainloop.
Various small bugfixes.
Added a version field to the cache pickle.
Added a human readable info file to cache.
Added md5sum prefix to file names in cache.

New feature:
Merged Justus Pendleton's --format patch. See --help.

--
sim

file:980addefe24712d0e8924f4be5d4839de5a7cabf -> file:02ce5ed53d246604b0b1ec451e7b0397f30ce075
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,14 @@
+mp3togo (0.4.1) unstable; urgency=low
+
+ * Merged Justus Pendleton's --format patch. See --help.
+ * Added test for failed write to stdout in mainloop.
+ * Various small bugfixes.
+ * Added a version field to the cache pickle.
+ * Added a human readable info file to cache.
+ * Added md5sum prefix to file names in cache.
+
+ -- Simeon Veldstra <reallifesim@gmail.com> Sat, 20 May 2006 02:29:59 -0700
+
mp3togo (0.4.0) unstable; urgency=low
* Add caching support for processed files.
file:ccd5f78cd6cd38860fe726c9f4575a8596f1c492 -> file:51c7f4564da784f43c5d273308ac7c542b433fb1
--- 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, Thu 18 May 2006, 13:05
+.\" created by instant / docbook-to-man, Sat 20 May 2006, 02:36
file:ddfa839de3211786fc4719e303d9724ce94dc6b6 -> file:5d4c0685887ada0e901ad59dc82738e6b6ac3ba3
--- a/mp3togo/__init__.py
+++ b/mp3togo/__init__.py
@@ -21,5 +21,5 @@
# __all__ = ('converter', 'main', 'options', 'setup')
-version = '0.4.0'
+version = '0.4.1'
file:ff3930856d100b6b2fd2ceae5862626b1da619d7 -> file:54b9bca30c72a82aae575ac0abc4a40091738196
--- a/mp3togo/cache.py
+++ b/mp3togo/cache.py
@@ -39,9 +39,13 @@ import fcntl
# The man page says flock won't work over NFS
# We could have multiple processes accessing the
# same cache over NFS.
+import time
+
+import mp3togo
PICKLEPROTOCOL = 2
+save_opts = ('cachesize', 'encoder', 'encopts', 'compfactor', 'nonormal')
class Cache:
@@ -54,19 +58,16 @@ class Cache:
raise conf.ErrorNoCache
self.path = path
self._lock = threading.Lock()
+ self._opts = opts
try:
head = self._read_pickle()
except:
opts.log(1, "Error reading from file cache control pickle")
raise
- # We might print a warning about changing these values
- opts['cachesize'] = head.cachesize
- opts['encoder'] = head.encoder
- opts['encopts'] = head.encopts
- opts['compfactor'] = head.compfactor
- opts['brwarning'] = head.brwarning
- opts['nonormal'] = head.nonormal
+ for name in save_opts:
+ opts[name] = getattr(head, name)
+ opts.log(3, "Setting %s to cached value: %s" % (name, opts[name]))
self._write_pickle(head)
@@ -101,6 +102,7 @@ class Cache:
hold = int(not bool(justlook))
d = head.d
if sum in d.keys():
+ self._opts.log(1, "Prior cache requests: %d" % d[sum][1])
if d[sum][0]:
d[sum] = (d[sum][0], d[sum][1] + 1, d[sum][2] + hold)
ret = sum
@@ -131,7 +133,7 @@ class Cache:
fits = False
free = head.cachesize - head.bytes
# Should use blocks * blocksize to get accurate space requirement
- # unfortunately, the result of st_blksize * st_blocks makes no sense.
+ # unfortunately, the result of st_blksize * st_blocks seems to make no sense.
size = os.stat(filename).st_size
if size < free:
fits = True
@@ -157,6 +159,7 @@ class Cache:
if fits:
cachename = os.path.basename(filename)
+ cachename = '_' + sum + '_' + cachename
cachename = os.path.join(self.path, cachename)
shutil.copyfile(filename, cachename)
if d.has_key(sum):
@@ -212,6 +215,7 @@ def create(path, opts):
if not os.path.exists(path):
os.makedirs(path)
cuke = os.path.join(path, '2go.cache')
+ note = os.path.join(path, '2go.readme')
if os.path.exists(cuke):
opts.log(1, "Cache exists, remove first")
raise Exception
@@ -219,6 +223,22 @@ def create(path, opts):
fp = file(cuke, 'w')
cPickle.dump(data, fp, PICKLEPROTOCOL)
fp.close()
+ readme = """This directory is a file cache created by mp3togo
+
+The cache stores copies of processed files for reuse later.
+It is safe to delete this directory.
+This cache was created with these options:\n"""
+ for key in save_opts:
+ readme += " %s: %s" % (key, opts[key])
+ if key == 'cachesize':
+ readme += ' (' + opts['cacheunits'] + ')\n'
+ else:
+ readme += '\n'
+ readme += "on %s\n" % time.ctime()
+ readme += "by mp3togo version %s\n" % mp3togo.version
+ fp = file(note, 'w')
+ fp.write(readme)
+ fp.close()
return True
except:
opts.log(1, "Error Creating Cache control pickle")
@@ -235,12 +255,9 @@ class CacheHead:
"""Create a new cache"""
self.d = {}
self.bytes = 0
- self.cachesize = opts['cachesize']
- self.encoder = opts['encoder']
- self.encopts = opts['encopts']
- self.compfactor = opts['compfactor']
- self.brwarning = opts['brwarning']
- self.nonormal = opts['nonormal']
+ self.version = mp3togo.version
+ for name in save_opts:
+ setattr(self, name, opts[name])
def checksum(filename):
file:a7031634a952c7241167419d0a8e1491edefb7d9 -> file:8580d89dd10e30e44d83e45767bbaa5c816fbcee
--- a/mp3togo/conf.py
+++ b/mp3togo/conf.py
@@ -78,6 +78,8 @@ class Options(options.Options):
'''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).'''),
+ ('treestructure', '', 'format', '', None,
+ '''A string specifying the format for the output files. The format string can contain the following escapes:<li>%a - Artist<li>%l - Album<li>%t - Title<li>%y - Year<li>%g - Genre<li>%% - Literal '%'<br>Overrides --tree-depth.'''),
('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, ''),
@@ -237,3 +239,12 @@ def checkfile(name):
getfiletype(name) # Throws exception if unknown
return name
+def try_print(msg):
+ """Try to print a message to a nonblocking stdout"""
+ for i in range(5):
+ try:
+ sys.stdout.write(msg)
+ return
+ except:
+ time.sleep(0.01 ** (1.0 / i))
+
file:595bbc43354d24c0acd060e265f20786801b61fa -> file:5a2bbd43c5dd19e0c05b0ec8a0bc54845b336497
--- a/mp3togo/main.py
+++ b/mp3togo/main.py
@@ -147,9 +147,11 @@ def execute_sequential(playlist, opts, c
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
+ tryp = conf.try_print
+
for name in playlist:
track_start = time.time()
- print "(%d/%d) %s: " % (playlist.cur_file() + 1, len(playlist), name)
+ tryp("(%d/%d) %s: \n" % (playlist.cur_file() + 1, len(playlist), name))
trk = track.Track(name, opts, cooked)
pl = pool.Pool(opts)
if pl.add_track(trk):
@@ -163,14 +165,14 @@ def execute_sequential(playlist, opts, c
continue
while tsk.status() == task.RUNNING:
#parse output
- sys.stdout.write("\r")
+ tryp("\r")
pcent = tsk.output()
if 0 < pcent < 101:
bar = '#' * int(pcent/5.0) + ('_' * (20 - int(pcent/5.0)))
else:
bar = ""
pcent = 0
- sys.stdout.write("%s: %3.d%% %s" % (tsk.name, int(pcent), bar))
+ tryp("%s: %3.d%% %s" % (tsk.name, int(pcent), bar))
# Get keyboard input
time.sleep(0.01)
for busy in range(5):
@@ -182,7 +184,7 @@ def execute_sequential(playlist, opts, c
raise KeyboardInterrupt
elif c in (' ', 'p', 'P'):
if tsk.pause():
- sys.stdout.write("\r [PAUSED] hit <enter> to resume ")
+ tryp("\r [PAUSED] hit <enter> to resume ")
while 1:
try:
c = sys.stdin.read(1)
@@ -192,23 +194,24 @@ def execute_sequential(playlist, opts, c
tm = tsk.elapsed_time()
ts = format_time(tm)
tsk.wait()
- sys.stdout.write("\r ")
+ tryp("\r ")
if tsk.status() == task.DONE:
if ts:
- print "\r%s: Done. %s elapsed." % (tsk.name, ts)
+ tryp("\r%s: Done. %s elapsed.\n" % (tsk.name, ts))
else:
- print "\r%s: Done." % tsk.name
+ tryp("\r%s: Done.\n" % tsk.name)
tsk = tsk.next()
else:
- print "\r%s: Failed. Cleaning up." % tsk.name
+ tryp("\r%s: Failed. Cleaning up.\n" % tsk.name)
while tsk:
- print "Undo: %s" % tsk.name
+ tryp("Undo: %s\n" % tsk.name)
tsk = tsk.prev()
except KeyboardInterrupt:
tsk.stop()
- print "Stopping..."
+ tryp("Stopping...\n")
while tsk:
- print "Undo: %s" % tsk.name
+ tryp("Undo: %s\n" % tsk.name)
+ tsk.undo()
tsk = tsk.prev()
sys.exit(1)
pass
@@ -227,9 +230,9 @@ def execute_sequential(playlist, opts, c
tt = time.time() - track_start
ts = format_time(tt)
if ts:
- print "Total time this track: %s\n" % ts
+ tryp("Total time this track: %s\n\n" % ts)
else:
- print
+ tryp('\n')
tm = time.time() - start_time
ts = format_time(tm)
@@ -241,8 +244,8 @@ def execute_sequential(playlist, opts, c
else:
bad_str = "."
#bytes = format_bytes(pl.produced_bytes)
- print "\n%d tracks done%s %s total time elapsed." % \
- (good_ones, bad_str, ts)
+ tryp("\n%d tracks done%s %s total time elapsed.\n" %
+ (good_ones, bad_str, ts))
finally:
# Fry some fish:
@@ -254,12 +257,12 @@ def format_time(tm):
tm = int(tm)
if tm:
hr, min, sec = (tm/3600, (tm%3600)/60, (tm%3600)%60)
- ts = "%ds" % sec
+ ts = "%ds " % sec
if min > 0:
- ts = ("%dm" % min) + ts
+ ts = ("%dm " % min) + ts
if hr > 0:
- ts = ("%dh" % hr) + ts
- return ts
+ ts = ("%dh " % hr) + ts
+ return ts[:-1]
else:
return ''
@@ -276,5 +279,6 @@ def format_bytes(bytes):
else:
ret = str(bytes) + " Bytes"
+
if __name__ == "__main__": main(sys.argv)
file:93405b2ba1c2d000d8db249d8741f300d30681dd -> file:0fad56322b9afc00704febab120b804509a83bdb
--- a/mp3togo/options.py
+++ b/mp3togo/options.py
@@ -62,6 +62,9 @@ interface.
self.name = ''
self.version = ''
+ # The log
+ self._log = []
+
# The option dictionary
self._d = {}
@@ -341,11 +344,15 @@ interface.
def log(self, level, msg):
"""Print a log message. Level must be at least 1. If the level
is below the current verbosity level, nothing happens."""
+ self._log.append((level, msg))
# Don't lock
if level < 1:
raise Fail, "log() called with insufficiently high level."
if level <= self._d['verbosity']:
- print >>sys.stderr, msg
+ try:
+ print >>sys.stderr, msg
+ except:
+ pass
def usage(self):
"""Returns a pretty printed usage message with the available
@@ -374,11 +381,18 @@ interface.
msg += line + "\n"
line = ""
if option[5]:
- desc = option[5].replace('\n', ' ').split()
+ desc = option[5].replace('<br>', ' <br> ')
+ desc = desc.replace('<li>', ' <li> ')
+ desc = desc.replace('\n', ' ').split()
while len(desc):
line = " "
+ if desc[0] == '<li>':
+ line += " "
+ del desc[0]
+ if desc[0] == '<br>':
+ del desc[0]
while 1:
- if len(line) + len(desc[0]) > 60:
+ if len(line) + len(desc[0]) > 60 or desc[0] in ('<br>', '<li>'):
msg += line + "\n"
line = ""
break
file:3c33f5cf55eebb032caa678ae816d7fb4c12c729 -> file:00d55ca2761f055d9853fa6655b03b4f4f0476d4
--- a/mp3togo/tags.py
+++ b/mp3togo/tags.py
@@ -221,7 +221,7 @@ class Tags(UserDict.DictMixin):
if code == 'z':
fmt[0] = '%' + fmt[0][1:]
elif code in esc.keys():
- fmt[0] = self._tags[esc[code]][0] + fmt[0][1:]
+ fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
else:
self._lock.release()
raise setup.ErrorBadFormat
file:3cb5dbb7afcf56f147a375a387b6a8270a19cde3 -> file:a7154720b5b97da59663a749aabea56d3f1432de
--- a/mp3togo/task.py
+++ b/mp3togo/task.py
@@ -26,6 +26,7 @@ import pty
import fcntl
import signal
import threading
+import types
import mp3togo.conf as setup
@@ -189,7 +190,7 @@ class Task(SimpleTask):
if type(action) not in (list, tuple):
raise TypeError
for element in action:
- if type(element) is not type(''):
+ if type(element) not in types.StringTypes:
raise TypeError
self._paused = threading.Lock()
self._pid = None
file:871ce345d2d3e00dead1e2b331941d96cb5727ad -> file:84905a1b15c4ed5819c11f5cd254370ee8e26fa9
--- a/mp3togo/track.py
+++ b/mp3togo/track.py
@@ -80,22 +80,37 @@ class Track:
tasks = [job]
del job
+ # Read the tags
+ # Do this early so that 'treestructure' can access them
+ self.tags = tags.Tags(filename)
+ self.tags.read()
+
# 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)
+ if opts['treestructure'] != '':
+ # Standard format string
+ # See tags.Tags.format.__doc__
+ fmt = opts['treestructure']
+ dest = self.tags.format(fmt)
+
+ self._outdir = os.path.dirname(dest)
+ self._outdir = os.path.join(opts['playerdir'], self._outdir)
+ self._outname = os.path.join(opts['playerdir'], dest)
else:
- dir = ''
- self._outdir = os.path.join(opts['playerdir'], dir)
- self._outname = os.path.join(self._outdir, 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._outname += '.' + helpers[opts['encoder']]['type']
self._wavname = os.path.join(opts['tempdir'], base) + '.wav'
@@ -196,10 +211,6 @@ class Track:
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')
@@ -214,15 +225,15 @@ class Track:
tasks.append(job)
del job
- # Tag
- 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
+ # Tag
+ # tag files from the cache as well, brwarning may have changed
+ 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: