Repository: beetbox/beets

Issue:
In %asciify, asciify_paths, scrub path separators
Songs starting with "½" should be renamed like the following:

$artist/$album/01 - 1_2 song title.mp3

Actually they will be renamed like this:

$artist/$album/01 -1/2 sing title.mp3

So a new folder starting with "01" is created.

Suggestion:
We will probably need to special-case this to re-scrub path separators after asciification.

Also see:
https://groups.google.com/forum/#!topic/beets-users/cpY7zKRUGbA

Regards,
Matthias



Discussion:
Thanks. Note that this bug is related to #323.


Resolving patch:
diff --git a/beets/library.py b/beets/library.py
--- a/beets/library.py
+++ b/beets/library.py
@@ -23,7 +23,6 @@
 import time
 import re
 import six
-from unidecode import unidecode
 
 from beets import logging
 from beets.mediafile import MediaFile, UnreadableFileError
@@ -824,7 +823,10 @@ def destination(self, fragment=False, basedir=None, platform=None,
             subpath = unicodedata.normalize('NFC', subpath)
 
         if beets.config['asciify_paths']:
-            subpath = unidecode(subpath)
+            subpath = util.asciify_path(
+                subpath,
+                beets.config['path_sep_replace'].as_str()
+            )
 
         maxlen = beets.config['max_filename_length'].get(int)
         if not maxlen:
@@ -1076,7 +1078,10 @@ def art_destination(self, image, item_dir=None):
             beets.config['art_filename'].as_str())
         subpath = self.evaluate_template(filename_tmpl, True)
         if beets.config['asciify_paths']:
-            subpath = unidecode(subpath)
+            subpath = util.asciify_path(
+                subpath,
+                beets.config['path_sep_replace'].as_str()
+            )
         subpath = util.sanitize_path(subpath,
                                      replacements=self._db.replacements)
         subpath = bytestring_path(subpath)
@@ -1428,7 +1433,7 @@ def tmpl_if(condition, trueval, falseval=u''):
     def tmpl_asciify(s):
         """Translate non-ASCII characters to their ASCII equivalents.
         """
-        return unidecode(s)
+        return util.asciify_path(s, beets.config['path_sep_replace'].as_str())
 
     @staticmethod
     def tmpl_time(s, fmt):
diff --git a/beets/util/__init__.py b/beets/util/__init__.py
--- a/beets/util/__init__.py
+++ b/beets/util/__init__.py
@@ -29,6 +29,7 @@
 import shlex
 from beets.util import hidden
 import six
+from unidecode import unidecode
 
 
 MAX_FILENAME_LENGTH = 200
@@ -941,3 +942,27 @@ def raw_seconds_short(string):
         raise ValueError(u'String not in M:SS format')
     minutes, seconds = map(int, match.groups())
     return float(minutes * 60 + seconds)
+
+
+def asciify_path(path, sep_replace):
+    """Decodes all unicode characters in a path into ASCII equivalents.
+
+    Substitutions are provided by the unidecode module. Path separators in the
+    input are preserved.
+
+    Keyword arguments:
+    path -- The path to be asciified.
+    sep_replace -- the string to be used to replace extraneous path separators.
+    """
+    # if this platform has an os.altsep, change it to os.sep.
+    if os.altsep:
+        path = path.replace(os.altsep, os.sep)
+    path_components = path.split(os.sep)
+    for index, item in enumerate(path_components):
+        path_components[index] = unidecode(item).replace(os.sep, sep_replace)
+        if os.altsep:
+            path_components[index] = unidecode(item).replace(
+                os.altsep,
+                sep_replace
+            )
+    return os.sep.join(path_components)


Test patch:
diff --git a/test/test_library.py b/test/test_library.py
--- a/test/test_library.py
+++ b/test/test_library.py
@@ -422,6 +422,13 @@ def test_asciify_and_replace(self):
         self.i.title = u'\u201c\u00f6\u2014\u00cf\u201d'
         self.assertEqual(self.i.destination(), np('lib/qo--Iq'))
 
+    def test_asciify_character_expanding_to_slash(self):
+        config['asciify_paths'] = True
+        self.lib.directory = b'lib'
+        self.lib.path_formats = [(u'default', u'$title')]
+        self.i.title = u'ab\xa2\xbdd'
+        self.assertEqual(self.i.destination(), np('lib/abC_1_2d'))
+
     def test_destination_with_replacements(self):
         self.lib.directory = b'base'
         self.lib.replacements = [(re.compile(r'a'), u'e')]
@@ -582,6 +589,10 @@ def test_title_case_variable(self):
         self._setf(u'%title{$title}')
         self._assert_dest(b'/base/The Title')
 
+    def test_asciify_variable(self):
+        self._setf(u'%asciify{ab\xa2\xbdd}')
+        self._assert_dest(b'/base/abC_1_2d')
+
     def test_left_variable(self):
         self._setf(u'%left{$title, 3}')
         self._assert_dest(b'/base/the')