mirror of
https://github.com/beetbox/beets.git
synced 2025-12-28 03:22:39 +01:00
[Improvement] --pretend option for the convert plugin
Partially resolves #877 showing: - Directory creation - Copies - Deletes - Moves - Encodings Information about tagging and plugins on _after_convert_ is not currently shown. That requires changing the plugins to support the pretend option, so a lot of work may be needed and it doesn't seem to be helpful enough for me.
This commit is contained in:
parent
51123d901b
commit
f554e2e4a0
2 changed files with 57 additions and 19 deletions
|
|
@ -24,6 +24,7 @@ from collections import defaultdict
|
|||
import traceback
|
||||
import subprocess
|
||||
import platform
|
||||
from sets import Set
|
||||
|
||||
|
||||
MAX_FILENAME_LENGTH = 200
|
||||
|
|
@ -201,10 +202,22 @@ def sorted_walk(path, ignore=(), logger=None):
|
|||
yield res
|
||||
|
||||
|
||||
def mkdirall(path):
|
||||
#We don't create directories on dry-runs, but we must pretend they exist
|
||||
directories_created = Set()
|
||||
def mkdirall(path, pretend=False):
|
||||
"""Make all the enclosing directories of path (like mkdir -p on the
|
||||
parent).
|
||||
"""
|
||||
|
||||
if pretend:
|
||||
#directory = syspath(ancestry(path)[-1]) # "dirname"
|
||||
# This seems cleaner but MAY have differences on symlinks (leading to an equivalent result)
|
||||
directory = os.path.dirname(path)
|
||||
if directory not in directories_created:
|
||||
directories_created.add(directory)
|
||||
#This is not a "raw" translation, but it's brief one
|
||||
print("mkdir -p '%s'" % (directory) )
|
||||
return
|
||||
for ancestor in ancestry(path):
|
||||
if not os.path.isdir(syspath(ancestor)):
|
||||
try:
|
||||
|
|
@ -388,10 +401,13 @@ def samefile(p1, p2):
|
|||
return shutil._samefile(syspath(p1), syspath(p2))
|
||||
|
||||
|
||||
def remove(path, soft=True):
|
||||
def remove(path, soft=True, pretend=False):
|
||||
"""Remove the file. If `soft`, then no error will be raised if the
|
||||
file does not exist.
|
||||
"""
|
||||
if pretend:
|
||||
print("rm '%s'" % (path))
|
||||
return
|
||||
path = syspath(path)
|
||||
if soft and not os.path.exists(path):
|
||||
return
|
||||
|
|
@ -401,12 +417,15 @@ def remove(path, soft=True):
|
|||
raise FilesystemError(exc, 'delete', (path,), traceback.format_exc())
|
||||
|
||||
|
||||
def copy(path, dest, replace=False):
|
||||
def copy(path, dest, replace=False, pretend=False):
|
||||
"""Copy a plain file. Permissions are not copied. If `dest` already
|
||||
exists, raises a FilesystemError unless `replace` is True. Has no
|
||||
effect if `path` is the same as `dest`. Paths are translated to
|
||||
system paths before the syscall.
|
||||
"""
|
||||
if pretend:
|
||||
print("cp '%s' '%s'" % (path, dest))
|
||||
return
|
||||
if samefile(path, dest):
|
||||
return
|
||||
path = syspath(path)
|
||||
|
|
@ -420,7 +439,7 @@ def copy(path, dest, replace=False):
|
|||
traceback.format_exc())
|
||||
|
||||
|
||||
def move(path, dest, replace=False):
|
||||
def move(path, dest, replace=False, pretend=False):
|
||||
"""Rename a file. `dest` may not be a directory. If `dest` already
|
||||
exists, raises an OSError unless `replace` is True. Has no effect if
|
||||
`path` is the same as `dest`. If the paths are on different
|
||||
|
|
@ -428,6 +447,9 @@ def move(path, dest, replace=False):
|
|||
instead, in which case metadata will *not* be preserved. Paths are
|
||||
translated to system paths.
|
||||
"""
|
||||
if pretend:
|
||||
print("mv '%s' '%s'" % (path, dest))
|
||||
return
|
||||
if samefile(path, dest):
|
||||
return
|
||||
path = syspath(path)
|
||||
|
|
@ -618,7 +640,7 @@ def cpu_count():
|
|||
return 1
|
||||
|
||||
|
||||
def command_output(cmd, shell=False):
|
||||
def command_output(cmd, shell=False, pretend=False):
|
||||
"""Runs the command and returns its output after it has exited.
|
||||
|
||||
``cmd`` is a list of arguments starting with the command names. If
|
||||
|
|
@ -633,6 +655,9 @@ def command_output(cmd, shell=False):
|
|||
Python 2.6 and which can have problems if lots of output is sent to
|
||||
stderr.
|
||||
"""
|
||||
if pretend:
|
||||
print(cmd)
|
||||
return
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=devnull,
|
||||
close_fds=platform.system() != 'Windows',
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ def get_format():
|
|||
)
|
||||
|
||||
|
||||
def encode(source, dest):
|
||||
def encode(source, dest, pretend=False):
|
||||
"""Encode ``source`` to ``dest`` using the command from ``get_format()``.
|
||||
|
||||
Raises an ``ui.UserError`` if the command was not found and a
|
||||
|
|
@ -92,7 +92,7 @@ def encode(source, dest):
|
|||
"""
|
||||
quiet = config['convert']['quiet'].get()
|
||||
|
||||
if not quiet:
|
||||
if not quiet and not pretend:
|
||||
log.info(u'Started encoding {0}'.format(util.displayable_path(source)))
|
||||
|
||||
command, _ = get_format()
|
||||
|
|
@ -105,7 +105,9 @@ def encode(source, dest):
|
|||
.format(util.displayable_path(command)))
|
||||
|
||||
try:
|
||||
util.command_output(command, shell=True)
|
||||
util.command_output(command, shell=True, pretend=pretend)
|
||||
if pretend:
|
||||
return
|
||||
except subprocess.CalledProcessError:
|
||||
# Something went wrong (probably Ctrl+C), remove temporary files
|
||||
log.info(u'Encoding {0} failed. Cleaning up...'
|
||||
|
|
@ -118,7 +120,7 @@ def encode(source, dest):
|
|||
u'convert: could invoke ffmpeg: {0}'.format(exc)
|
||||
)
|
||||
|
||||
if not quiet:
|
||||
if not quiet and not pretend:
|
||||
log.info(u'Finished encoding {0}'.format(
|
||||
util.displayable_path(source))
|
||||
)
|
||||
|
|
@ -134,7 +136,7 @@ def should_transcode(item):
|
|||
item.bitrate >= 1000 * maxbr
|
||||
|
||||
|
||||
def convert_item(dest_dir, keep_new, path_formats):
|
||||
def convert_item(dest_dir, keep_new, path_formats, pretend=False):
|
||||
while True:
|
||||
item = yield
|
||||
dest = _destination(dest_dir, item, keep_new, path_formats)
|
||||
|
|
@ -149,7 +151,7 @@ def convert_item(dest_dir, keep_new, path_formats):
|
|||
# time. (The existence check is not atomic with the directory
|
||||
# creation inside this function.)
|
||||
with _fs_lock:
|
||||
util.mkdirall(dest)
|
||||
util.mkdirall(dest, pretend)
|
||||
|
||||
# When keeping the new file in the library, we first move the
|
||||
# current (pristine) file to the destination. We'll then copy it
|
||||
|
|
@ -157,7 +159,7 @@ def convert_item(dest_dir, keep_new, path_formats):
|
|||
if keep_new:
|
||||
log.info(u'Moving to {0}'.
|
||||
format(util.displayable_path(dest)))
|
||||
util.move(item.path, dest)
|
||||
util.move(item.path, dest, pretend)
|
||||
original = dest
|
||||
_, ext = get_format()
|
||||
converted = os.path.splitext(item.path)[0] + ext
|
||||
|
|
@ -168,13 +170,17 @@ def convert_item(dest_dir, keep_new, path_formats):
|
|||
if not should_transcode(item):
|
||||
# No transcoding necessary.
|
||||
log.info(u'Copying {0}'.format(util.displayable_path(item.path)))
|
||||
util.copy(original, converted)
|
||||
util.copy(original, converted, pretend)
|
||||
else:
|
||||
try:
|
||||
encode(original, converted)
|
||||
encode(original, converted, pretend)
|
||||
except subprocess.CalledProcessError:
|
||||
continue
|
||||
|
||||
if pretend:
|
||||
#Should we add support for tagging and after_convert plugins?
|
||||
continue #A yield is used at the start of the loop
|
||||
|
||||
# Write tags from the database to the converted file.
|
||||
item.write(path=converted)
|
||||
|
||||
|
|
@ -229,17 +235,21 @@ def convert_func(lib, opts, args):
|
|||
else:
|
||||
path_formats = ui.get_path_formats(config['convert']['paths'])
|
||||
|
||||
ui.commands.list_items(lib, ui.decargs(args), opts.album, None)
|
||||
pretend = opts.pretend if opts.pretend is not None else \
|
||||
config['convert']['pretend'].get()
|
||||
|
||||
if not ui.input_yn("Convert? (Y/n)"):
|
||||
return
|
||||
if not pretend:
|
||||
ui.commands.list_items(lib, ui.decargs(args), opts.album, None)
|
||||
|
||||
if not ui.input_yn("Convert? (Y/n)"):
|
||||
return
|
||||
|
||||
if opts.album:
|
||||
items = (i for a in lib.albums(ui.decargs(args)) for i in a.items())
|
||||
else:
|
||||
items = iter(lib.items(ui.decargs(args)))
|
||||
convert = [convert_item(dest, keep_new, path_formats)
|
||||
for i in range(threads)]
|
||||
convert = [convert_item(dest, keep_new, path_formats, pretend)
|
||||
for _ in range(threads)]
|
||||
pipe = util.pipeline.Pipeline([items, convert])
|
||||
pipe.run_parallel()
|
||||
|
||||
|
|
@ -249,6 +259,7 @@ class ConvertPlugin(BeetsPlugin):
|
|||
super(ConvertPlugin, self).__init__()
|
||||
self.config.add({
|
||||
u'dest': None,
|
||||
u'pretend': False,
|
||||
u'threads': util.cpu_count(),
|
||||
u'format': u'mp3',
|
||||
u'formats': {
|
||||
|
|
@ -295,6 +306,8 @@ class ConvertPlugin(BeetsPlugin):
|
|||
|
||||
def commands(self):
|
||||
cmd = ui.Subcommand('convert', help='convert to external location')
|
||||
cmd.parser.add_option('-p', '--pretend', action='store_true',
|
||||
help='only show what would happen')
|
||||
cmd.parser.add_option('-a', '--album', action='store_true',
|
||||
help='choose albums instead of tracks')
|
||||
cmd.parser.add_option('-t', '--threads', action='store', type='int',
|
||||
|
|
|
|||
Loading…
Reference in a new issue