diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 248096730..0ae646a22 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -1108,3 +1108,25 @@ def lazy_property(func): return value return wrapper + + +def decode_commandline_path(path): + """Prepare a path for substitution into commandline template. + + On Python 3, we need to construct the subprocess commands to invoke as a + Unicode string. On Unix, this is a little unfortunate---the OS is + expecting bytes---so we use surrogate escaping and decode with the + argument encoding, which is the same encoding that will then be + *reversed* to recover the same bytes before invoking the OS. On + Windows, we want to preserve the Unicode filename "as is." + """ + if six.PY2: + # On Python 2, substitute the bytestring directly into the template. + return path + else: + # On Python 3, the template is a Unicode string, which only supports + # substitution of Unicode variables. + if platform.system() == 'Windows': + return path.decode(_fsencoding()) + else: + return path.decode(arg_encoding(), 'surrogateescape') diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 275703e97..ae91cd155 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -16,7 +16,7 @@ """Converts tracks or albums to external directory """ from __future__ import division, absolute_import, print_function -from beets.util import par_map +from beets.util import par_map, decode_commandline_path import os import threading @@ -25,7 +25,6 @@ import tempfile import shlex import six from string import Template -import platform from beets import ui, util, plugins, config from beets.plugins import BeetsPlugin @@ -205,20 +204,8 @@ class ConvertPlugin(BeetsPlugin): if not quiet and not pretend: self._log.info(u'Encoding {0}', util.displayable_path(source)) - # On Python 3, we need to construct the command to invoke as a - # Unicode string. On Unix, this is a little unfortunate---the OS is - # expecting bytes---so we use surrogate escaping and decode with the - # argument encoding, which is the same encoding that will then be - # *reversed* to recover the same bytes before invoking the OS. On - # Windows, we want to preserve the Unicode filename "as is." - if not six.PY2: - command = command.decode(util.arg_encoding(), 'surrogateescape') - if platform.system() == 'Windows': - source = source.decode(util._fsencoding()) - dest = dest.decode(util._fsencoding()) - else: - source = source.decode(util.arg_encoding(), 'surrogateescape') - dest = dest.decode(util.arg_encoding(), 'surrogateescape') + source = decode_commandline_path(source) + dest = decode_commandline_path(dest) # Substitute $source and $dest in the argument list. args = shlex.split(command) diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index 0492fea65..bc9e3ff3c 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -17,15 +17,13 @@ """ from __future__ import division, absolute_import, print_function -import platform import shlex import six -from beets import util from beets.plugins import BeetsPlugin from beets.ui import decargs, print_, Subcommand, UserError from beets.util import command_output, displayable_path, subprocess, \ - bytestring_path, MoveOperation + bytestring_path, MoveOperation, decode_commandline_path from beets.library import Item, Album @@ -200,20 +198,8 @@ class DuplicatesPlugin(BeetsPlugin): output as flexattr on a key that is the name of the program, and return the key, checksum tuple. """ - # On Python 3, we need to construct the command to invoke as a - # Unicode string. On Unix, this is a little unfortunate---the OS is - # expecting bytes---so we use surrogate escaping and decode with the - # argument encoding, which is the same encoding that will then be - # *reversed* to recover the same bytes before invoking the OS. On - # Windows, we want to preserve the Unicode filename "as is." - if not six.PY2: - if platform.system() == 'Windows': - args = [p.format(file=item.path.decode(util._fsencoding())) - for p in shlex.split(prog)] - else: - args = [p.format(file=item.path.decode(util.arg_encoding(), - 'surrogateescape')) for p in shlex.split(prog)] - + args = [p.format(file=decode_commandline_path(item.path)) + for p in shlex.split(prog)] key = args[0] checksum = getattr(item, key, False) if not checksum: