From f554e2e4a0f3c01c7fc3ae17e2fa4d77074f4b84 Mon Sep 17 00:00:00 2001 From: Dietrich Daroch Date: Mon, 28 Jul 2014 20:13:15 -0400 Subject: [PATCH 1/2] [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. --- beets/util/__init__.py | 35 ++++++++++++++++++++++++++++++----- beetsplug/convert.py | 41 +++++++++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 428de312a..6b8cf6647 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -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', diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 9892cc001..8fce6d19f 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -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', From ea4832e212c97f4d9c763767ca2ea402d6da00f3 Mon Sep 17 00:00:00 2001 From: Dietrich Daroch Date: Wed, 30 Jul 2014 14:35:19 -0400 Subject: [PATCH 2/2] [PEP8] I didn't had a pep8 checker on vim :c --- beets/util/__init__.py | 13 ++++++++----- beetsplug/convert.py | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 6b8cf6647..e91a12f91 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -202,21 +202,24 @@ def sorted_walk(path, ignore=(), logger=None): yield res -#We don't create directories on dry-runs, but we must pretend they exist +# 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 = 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) ) + # 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)): diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 8fce6d19f..8bf048039 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -178,8 +178,8 @@ def convert_item(dest_dir, keep_new, path_formats, pretend=False): continue if pretend: - #Should we add support for tagging and after_convert plugins? - continue #A yield is used at the start of the loop + # 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)