diff --git a/headphones/helpers.py b/headphones/helpers.py index ab5cd2eb..ff062d57 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -199,13 +199,15 @@ def replace_all(text, dic, normalize=False): if normalize: new_dic = {} for i, j in dic.iteritems(): - try: - if sys.platform == 'darwin': - j = unicodedata.normalize('NFD', j) - else: - j = unicodedata.normalize('NFC', j) - except TypeError: - j = unicodedata.normalize('NFC', j.decode(headphones.SYS_ENCODING, 'replace')) + if j is not None: + try: + if sys.platform == 'darwin': + j = unicodedata.normalize('NFD', j) + else: + j = unicodedata.normalize('NFC', j) + except TypeError: + j = unicodedata.normalize('NFC', + j.decode(headphones.SYS_ENCODING, 'replace')) new_dic[i] = j dic = new_dic return pathrender.render(text, dic)[0] diff --git a/headphones/metadata.py b/headphones/metadata.py index 3a423b32..f5ddf4bc 100644 --- a/headphones/metadata.py +++ b/headphones/metadata.py @@ -114,6 +114,7 @@ def _as_str(val): def _media_file_to_dict(mf, d): + # type: (MediaFile, MutableMapping[basestring,basestring])->None """ Populate dict with tags read from media file. """ @@ -158,6 +159,18 @@ def _date_year(release): return date, year +def _lower(s): + # type: basestring->basestring + """ + Return s.lower() if not None + :param s: + :return: + """ + if s: + return s.lower() + return None + + def file_metadata(path, release): # type: (str,sqlite3.Row)->Tuple[Mapping[str,str],bool] """ @@ -208,7 +221,7 @@ def file_metadata(path, release): else: artist_name = release['ArtistName'] - if artist_name.startswith('The '): + if artist_name and artist_name.startswith('The '): sort_name = artist_name[4:] + ", The" else: sort_name = artist_name @@ -224,10 +237,10 @@ def file_metadata(path, release): Vars.YEAR: year, Vars.DATE: date, Vars.EXTENSION: ext, - Vars.TITLE_LOWER: title.lower(), - Vars.ARTIST_LOWER: artist_name.lower(), - Vars.SORT_ARTIST_LOWER: sort_name.lower(), - Vars.ALBUM_LOWER: album_title.lower(), + Vars.TITLE_LOWER: _lower(title), + Vars.ARTIST_LOWER: _lower(artist_name), + Vars.SORT_ARTIST_LOWER: _lower(sort_name), + Vars.ALBUM_LOWER: _lower(album_title), } res.add_items(override_values.iteritems()) return res, from_metadata @@ -255,26 +268,34 @@ def album_metadata(path, release, common_tags): :return: metadata dictionary with substitution variables for rendering path. """ date, year = _date_year(release) - artist = release['ArtistName'].replace('/', '_') - album = release['AlbumTitle'].replace('/', '_') - release_type = release['Type'].replace('/', '_') + artist = release['ArtistName'] + if artist: + artist = artist.replace('/', '_') + album = release['AlbumTitle'] + if album: + album = album.replace('/', '_') + release_type = release['Type'] + if release_type: + release_type = release_type.replace('/', '_') - if release['ArtistName'].startswith('The '): - sort_name = release['ArtistName'][4:] + ", The" + if artist and artist.startswith('The '): + sort_name = artist[4:] + ", The" else: - sort_name = release['ArtistName'] + sort_name = artist - if sort_name[0].isdigit(): + if not sort_name or sort_name[0].isdigit(): first_char = u'0-9' else: first_char = sort_name[0] + orig_folder = u'' for r, d, f in os.walk(path): try: orig_folder = os.path.basename( os.path.normpath(r).decode(headphones.SYS_ENCODING, 'replace')) + break except: - orig_folder = u'' + pass override_values = { Vars.ARTIST: artist, @@ -285,12 +306,12 @@ def album_metadata(path, release, common_tags): Vars.TYPE: release_type, Vars.ORIGINAL_FOLDER: orig_folder, Vars.FIRST_LETTER: first_char.upper(), - Vars.ARTIST_LOWER: artist.lower(), - Vars.SORT_ARTIST_LOWER: sort_name.lower(), - Vars.ALBUM_LOWER: album.lower(), - Vars.TYPE_LOWER: release_type.lower(), - Vars.FIRST_LETTER_LOWER: first_char.lower(), - Vars.ORIGINAL_FOLDER_LOWER: orig_folder.lower() + Vars.ARTIST_LOWER: _lower(artist), + Vars.SORT_ARTIST_LOWER: _lower(sort_name), + Vars.ALBUM_LOWER: _lower(album), + Vars.TYPE_LOWER: _lower(release_type), + Vars.FIRST_LETTER_LOWER: _lower(first_char), + Vars.ORIGINAL_FOLDER_LOWER: _lower(orig_folder) } res = MetadataDict(common_tags) res.add_items(override_values.iteritems()) @@ -306,13 +327,16 @@ def albumart_metadata(release, common_tags): :return: metadata dictionary with substitution variables for rendering path. """ date, year = _date_year(release) + artist = release['ArtistName'] + album = release['AlbumTitle'] + override_values = { - Vars.ARTIST: release['ArtistName'], - Vars.ALBUM: release['AlbumTitle'], + Vars.ARTIST: artist, + Vars.ALBUM: album, Vars.YEAR: year, Vars.DATE: date, - Vars.ARTIST_LOWER: release['ArtistName'].lower(), - Vars.ALBUM_LOWER: release['AlbumTitle'].lower() + Vars.ARTIST_LOWER: _lower(artist), + Vars.ALBUM_LOWER: _lower(album) } res = MetadataDict(common_tags) res.add_items(override_values.iteritems()) diff --git a/headphones/metadata_test.py b/headphones/metadata_test.py index c4e6bb47..37d07c12 100644 --- a/headphones/metadata_test.py +++ b/headphones/metadata_test.py @@ -16,7 +16,9 @@ """ Test module for metadata. """ +import headphones as _h import headphones.metadata as _md +import headphones.helpers as _hp from headphones.metadata import MetadataDict import datetime @@ -146,3 +148,28 @@ class MetadataTest(TestCase): '$Variation': '5' } self.assertItemsEqual(expected, md, "check _row_to_dict() valid") + + def test_album_metadata_with_None(self): + """metadata: check handling of None metadata values""" + row = _MockDatabaseRow({ + 'ArtistName': 'artist', + 'AlbumTitle': 'Album', + 'Type': None, + 'ReleaseDate': None, + }) + mb = _md.AlbumMetadataBuilder() + f1 = _MockMediaFile('artist', None, None, None, None, None) + mb.add_media_file(f1) + f2 = _MockMediaFile('artist', None, None, 2, 'track2', None) + mb.add_media_file(f2) + md = _md.album_metadata("/music/Artist - Album [2002]", row, mb.build()) + + # tests don't undergo normal Headphones init, SYS_ENCODING is not set + if not _h.SYS_ENCODING: + _h.SYS_ENCODING = 'UTF-8' + + res = _hp.replace_all( + "/music/$First/$Artist/$Artist - $Album{ [$Year]}", md, True) + + self.assertEqual(res, u"/music/A/artist/artist - Album", + "check correct rendering of None via replace_all()")