changelog/cleanup/fixes for #209

The major functional change here is how files move around when in keep_new
mode. Now, files are first moved to the destination directory and then
copied/transcoded back into the library.

This avoids problems where naming conflicts could occur when transcoding from
MP3 to MP3 (and thus not changing the filename).
This commit is contained in:
Adrian Sampson 2013-03-06 18:21:42 -08:00
parent 5a94cfe5d6
commit 87d71abc28
3 changed files with 48 additions and 42 deletions

View file

@ -22,7 +22,6 @@ from subprocess import Popen
from beets.plugins import BeetsPlugin
from beets import ui, util
from beetsplug.embedart import _embed
from beets import library
from beets import config
log = logging.getLogger('beets')
@ -30,25 +29,19 @@ DEVNULL = open(os.devnull, 'wb')
_fs_lock = threading.Lock()
def _dest_out(lib, dest_dir, item, keep_new):
"""Path to the files outside the directory"""
def _destination(lib, dest_dir, item, keep_new):
"""Return the path under `dest_dir` where the file should be placed
(possibly after conversion).
"""
dest = lib.destination(item, basedir=dest_dir)
if keep_new:
return os.path.join(dest_dir, lib.destination(item, fragment=True))
dest = os.path.join(dest_dir, lib.destination(item, fragment=True))
return os.path.splitext(dest)[0] + '.mp3'
def _dest_converted(lib, dest_dir, item, keep_new):
"""Path to the newly converted files"""
if keep_new:
dest = lib.destination(item)
# When we're keeping the converted file, no extension munging
# occurs.
return dest
else:
# Otherwise, replace the extension with .mp3.
return os.path.splitext(dest)[0] + '.mp3'
return _dest_out(lib, dest_dir, item, keep_new)
def encode(source, dest):
log.info(u'Started encoding {0}'.format(util.displayable_path(source)))
@ -71,11 +64,9 @@ def encode(source, dest):
def convert_item(lib, dest_dir, keep_new):
while True:
item = yield
dest = _destination(lib, dest_dir, item, keep_new)
dest_converted = _dest_converted(lib, dest_dir, item, keep_new)
dest_out = _dest_out(lib, dest_dir, item, keep_new)
if os.path.exists(util.syspath(dest_out)):
if os.path.exists(util.syspath(dest)):
log.info(u'Skipping {0} (target file exists)'.format(
util.displayable_path(item.path)
))
@ -85,21 +76,36 @@ def convert_item(lib, dest_dir, keep_new):
# time. (The existence check is not atomic with the directory
# creation inside this function.)
with _fs_lock:
util.mkdirall(dest_out)
util.mkdirall(dest)
# When keeping the new file in the library, we first move the
# current (pristine) file to the destination. We'll then copy it
# back to its old path or transcode it to a new path.
if keep_new:
log.info(u'Moving to {0}'.
format(util.displayable_path(dest)))
util.move(item.path, dest)
maxbr = config['convert']['max_bitrate'].get(int)
if item.format == 'MP3' and item.bitrate < 1000 * maxbr:
# No transcoding necessary.
log.info(u'Copying {0}'.format(util.displayable_path(item.path)))
util.copy(item.path, dest_out)
else:
encode(item.path, dest_converted)
if keep_new:
log.info(u'Moving to destination {0}'.
format(util.displayable_path(dest_out)))
util.move(item.path, dest_out)
util.copy(dest, item.path)
else:
util.copy(item.path, dest)
item.path = dest_converted
else:
if keep_new:
item.path = os.path.splitext(item.path)[0] + '.mp3'
encode(dest, item.path)
lib.store(item)
else:
encode(item.path, dest)
# Write tags from the database to the converted file.
if not keep_new:
item.path = dest
item.write()
if config['convert']['embed']:
@ -109,21 +115,14 @@ def convert_item(lib, dest_dir, keep_new):
if artpath:
_embed(artpath, [item])
if keep_new:
item.read()
log.info(u'Updating new format {0}'.format(item.format))
item.write()
lib.store(item)
def convert_func(lib, opts, args):
dest = opts.dest if opts.dest is not None else \
config['convert']['dest'].get()
config['convert']['dest'].get()
if not dest:
raise ui.UserError('no convert destination set')
threads = opts.threads if opts.threads is not None else \
config['convert']['threads'].get(int)
config['convert']['threads'].get(int)
keep_new = opts.keep_new
ui.commands.list_items(lib, ui.decargs(args), opts.album, None)

View file

@ -22,6 +22,9 @@ Other stuff:
track in MusicBrainz and updates your library to reflect it. This can help
you easily correct errors that have been fixed in the MB database. Thanks to
Jakob Schnitzer.
* :doc:`/plugins/convert`: A new ``--keep-new`` option lets you store
transcoded files in your library while backing up the originals (instead of
vice-versa). Thanks to Lucas Duailibe.
* :doc:`/plugins/echonest_tempo`: API errors now issue a warning instead of
exiting with an exception. We also avoid an error when track metadata
contains newlines.

View file

@ -26,13 +26,17 @@ Usage
To convert a part of your collection, run ``beet convert QUERY``. This
will display all items matching ``QUERY`` and ask you for confirmation before
starting the conversion. The ``-a`` (or ``--album``) option causes the command
to match albums instead of tracks. The ``-k`` (or ``--keep-new``) allows you to
keep the new, converted, files in your library and move the origin files to the
destination directory.
to match albums instead of tracks.
The ``-t`` (``--threads``) and ``-d`` (``--dest``) options allow you to specify
or overwrite the respective configuration options.
By default, the command places converted files into the destination directory
and leaves your library pristine. To instead back up your original files into
the destination directory and keep converted files in your library, use the
``-k`` (or ``--keep-new``) option.
Configuration
-------------