diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py
index 159d623d5..d063f6278 100644
--- a/beets/autotag/mb.py
+++ b/beets/autotag/mb.py
@@ -310,9 +310,9 @@ def match_album(artist, album, tracks=None, limit=SEARCH_LIMIT):
optionally, a number of tracks on the album.
"""
# Build search criteria.
- criteria = {'release': album.lower()}
+ criteria = {'release': album.lower().strip()}
if artist is not None:
- criteria['artist'] = artist.lower()
+ criteria['artist'] = artist.lower().strip()
else:
# Various Artists search.
criteria['arid'] = VARIOUS_ARTISTS_ID
@@ -341,8 +341,8 @@ def match_track(artist, title, limit=SEARCH_LIMIT):
objects. May raise a MusicBrainzAPIError.
"""
criteria = {
- 'artist': artist.lower(),
- 'recording': title.lower(),
+ 'artist': artist.lower().strip(),
+ 'recording': title.lower().strip(),
}
if not any(criteria.itervalues()):
diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py
index 9e1fa986f..257b88c07 100644
--- a/beetsplug/embedart.py
+++ b/beetsplug/embedart.py
@@ -41,7 +41,7 @@ class EmbedCoverArtPlugin(BeetsPlugin):
'maxwidth': 0,
'auto': True,
'compare_threshold': 0,
- 'ifempty': False
+ 'ifempty': False,
})
if self.config['maxwidth'].get(int) and not ArtResizer.shared.local:
@@ -64,7 +64,7 @@ class EmbedCoverArtPlugin(BeetsPlugin):
)
maxwidth = config['embedart']['maxwidth'].get(int)
compare_threshold = config['embedart']['compare_threshold'].get(int)
- ifempty = config['embedart']['ifempty'].get()
+ ifempty = config['embedart']['ifempty'].get(bool)
def embed_func(lib, opts, args):
if opts.file:
@@ -165,6 +165,7 @@ def embed_album(album, maxwidth=None, quiet=False):
for item in album.items():
embed_item(item, imagepath, maxwidth, None,
config['embedart']['compare_threshold'].get(int),
+<<<<<<< HEAD
config['embedart']['ifempty'], asalbum=True)
@@ -175,6 +176,9 @@ def resize_image(imagepath, maxwidth):
.format(maxwidth))
imagepath = ArtResizer.shared.resize(maxwidth, syspath(imagepath))
return imagepath
+=======
+ config['embedart']['ifempty'].get(bool))
+>>>>>>> upstream/master
def check_art_similarity(item, imagepath, compare_threshold):
diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py
index 1474a7b09..81c984b60 100644
--- a/beetsplug/fetchart.py
+++ b/beetsplug/fetchart.py
@@ -1,5 +1,5 @@
# This file is part of beets.
-# Copyright 2013, Adrian Sampson.
+# Copyright 2014, Adrian Sampson.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/beetsplug/ftintitle.py b/beetsplug/ftintitle.py
index 831236b3c..8a92d20df 100644
--- a/beetsplug/ftintitle.py
+++ b/beetsplug/ftintitle.py
@@ -18,8 +18,11 @@ from beets.plugins import BeetsPlugin
from beets import ui
from beets.util import displayable_path
from beets import config
+import logging
import re
+log = logging.getLogger('beets')
+
def split_on_feat(artist):
"""Given an artist string, split the "main" artist from any artist
@@ -69,7 +72,7 @@ def update_metadata(item, feat_part, drop_feat):
item.title = new_title
-def ft_in_title(item, drop_feat):
+def ft_in_title(item, drop_feat, write):
"""Look for featured artists in the item's artist fields and move
them to the title.
"""
@@ -118,6 +121,7 @@ class FtInTitlePlugin(BeetsPlugin):
super(FtInTitlePlugin, self).__init__()
self.config.add({
+ 'auto': True,
'drop': False
})
@@ -130,6 +134,9 @@ class FtInTitlePlugin(BeetsPlugin):
action='store_true', default=False,
help='drop featuring from artists and ignore title update')
+ if self.config['auto']:
+ self.import_stages = [self.imported]
+
def commands(self):
def func(lib, opts, args):
@@ -145,3 +152,11 @@ class FtInTitlePlugin(BeetsPlugin):
self._command.func = func
return [self._command]
+
+ def imported(self, session, task):
+ """Import hook for moving featuring artist automatically.
+ """
+ drop_feat = self.config['drop'].get(bool)
+
+ for item in task.imported_items():
+ ft_in_title(item, drop_feat)
diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py
index 47e299f82..7dea01a32 100644
--- a/beetsplug/lyrics.py
+++ b/beetsplug/lyrics.py
@@ -329,10 +329,10 @@ def _scrape_strip_cruft(html, plain_text_out=False):
"""
html = unescape(html)
- # Normalize EOL
- html = html.replace('\r', '\n')
+ html = html.replace('\r', '\n') # Normalize EOL.
html = re.sub(r' +', ' ', html) # Whitespaces collapse.
- html = BREAK_RE.sub('\n', html) #
eats up surrounding '\n'
+ html = BREAK_RE.sub('\n', html) #
eats up surrounding '\n'.
+ html = re.sub(r'<(script).*?\1>(?s)', '', html) # Strip script tags.
if plain_text_out: # Strip remaining HTML tags
html = TAG_RE.sub('', html)
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 38f99638d..c05d08bce 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -18,6 +18,10 @@ Features:
in its album-level incarnation, it could not represent heterogeneous
releases---for example, an album consisting of a CD and a DVD. Now, tracks
accurately indicate the media they appear on. Thanks to Heinz Wiesinger.
+* :doc:`/plugins/embedart`: A new ``ifempty`` config option lets you only
+ embed album art when no album art is present. Thanks to kerobaros.
+* :doc:`/plugins/ftintitle`: The plugin now runs automatically on import. To
+ disable this, unset the ``auto`` config flag.
Fixes:
@@ -44,6 +48,8 @@ Fixes:
the Discogs servers. Thanks to Dustin Rodriguez.
* :doc:`/plugins/embedart`: Do not log "embedding album art into..." messages
during the import process.
+* Fix a crash in the autotagger when files had only whitespace in their
+ metadata.
1.3.8 (September 17, 2014)
diff --git a/docs/plugins/embedart.rst b/docs/plugins/embedart.rst
index 5abc24a04..387d6a3ff 100644
--- a/docs/plugins/embedart.rst
+++ b/docs/plugins/embedart.rst
@@ -28,7 +28,7 @@ When importing a lot of files with the ``auto`` option, one may be reluctant to
overwrite existing embedded art for all of them.
You can tell beets to avoid embedding images that are too different from the
-existing ones.
+existing ones.
This works by computing the perceptual hashes (`PHASH`_) of the two images and
checking that the difference between the two does not exceed a
threshold. You can set the threshold with the ``compare_threshold`` option.
@@ -81,6 +81,9 @@ regarding to embedded art to be written to the file (see
:ref:`image-similarity-check`). The default is 0 (no similarity check).
Requires `ImageMagick`_.
+To avoid embedding album art for files that already have album art, set the
+``ifempty`` config option to ``yes``.
+
.. _PIL: http://www.pythonware.com/products/pil/
.. _ImageMagick: http://www.imagemagick.org/
.. _PHASH: http://www.fmwconcepts.com/misc_tests/perceptual_hash_test_results_510/
diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst
index 37d1b9056..1aa171dac 100644
--- a/docs/plugins/fetchart.rst
+++ b/docs/plugins/fetchart.rst
@@ -111,7 +111,6 @@ your config file::
fetchart:
google_search: true
-
Embedding Album Art
-------------------
diff --git a/docs/plugins/ftintitle.rst b/docs/plugins/ftintitle.rst
index ed13cb840..c8bf48f46 100644
--- a/docs/plugins/ftintitle.rst
+++ b/docs/plugins/ftintitle.rst
@@ -21,4 +21,6 @@ If you prefer to remove featured artists entirely instead of adding them to
the title field, either use the ``-d`` flag to the command or set the
``ftintitle.drop`` config option.
+To disable this plugin on import, set the ``auto`` config option to false.
+
.. _MusicBrainz style: http://musicbrainz.org/doc/Style
diff --git a/test/test_importer.py b/test/test_importer.py
index 6d1b65dab..feafad923 100644
--- a/test/test_importer.py
+++ b/test/test_importer.py
@@ -65,7 +65,7 @@ class AutotagStub(object):
def restore(self):
autotag.mb.match_album = self.mb_match_album
- autotag.mb.match_track = self.mb_match_album
+ autotag.mb.match_track = self.mb_match_track
autotag.mb.album_for_id = self.mb_album_for_id
autotag.mb.track_for_id = self.mb_track_for_id
diff --git a/test/test_lyrics.py b/test/test_lyrics.py
index 6b2929565..c4876c003 100644
--- a/test/test_lyrics.py
+++ b/test/test_lyrics.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# This file is part of beets.
# Copyright 2014, Fabrice Laporte.
#
@@ -131,7 +130,7 @@ class LyricsPluginTest(unittest.TestCase):
self.assertFalse(lyrics.is_lyrics(t))
def test_slugify(self):
- text = u"http://site.com/çafe-au_lait(boisson)"
+ text = u"http://site.com/\xe7afe-au_lait(boisson)"
self.assertEqual(lyrics.slugify(text), 'http://site.com/cafe_au_lait')
def test_scrape_strip_cruft(self):
@@ -144,6 +143,11 @@ class LyricsPluginTest(unittest.TestCase):
self.assertEqual(lyrics._scrape_strip_cruft(text, True),
"one\ntwo !\n\nfour")
+ def test_scrape_strip_scripts(self):
+ text = u"""foobaz"""
+ self.assertEqual(lyrics._scrape_strip_cruft(text, True),
+ "foobaz")
+
def test_scrape_merge_paragraphs(self):
text = u"one
two
three" self.assertEqual(lyrics._scrape_merge_paragraphs(text), @@ -263,7 +267,7 @@ class LyricsGooglePluginTest(unittest.TestCase): except ImportError: self.skipTest('Beautiful Soup 4 not available') if sys.version_info[:3] < (2, 7, 3): - self.skipTest("Python’s built-in HTML parser is not good enough") + self.skipTest("Python's built-in HTML parser is not good enough") lyrics.LyricsPlugin() lyrics.fetch_url = MockFetchUrl() diff --git a/test/test_mb.py b/test/test_mb.py index c8ffceefb..f41ec5510 100644 --- a/test/test_mb.py +++ b/test/test_mb.py @@ -18,6 +18,7 @@ import _common from _common import unittest from beets.autotag import mb from beets import config +import mock class MBAlbumInfoTest(_common.TestCase): @@ -407,6 +408,78 @@ class ArtistFlatteningTest(_common.TestCase): self.assertEqual(flat, ('ALIASfr_P', 'ALIASSORTfr_P', 'CREDIT')) +class MBLibraryTest(unittest.TestCase): + def test_match_track(self): + with mock.patch('musicbrainzngs.search_recordings') as p: + p.return_value = { + 'recording-list': [{ + 'title': 'foo', + 'id': 'bar', + 'length': 42, + }], + } + ti = list(mb.match_track('hello', 'there'))[0] + + p.assert_called_with(artist='hello', recording='there', limit=5) + self.assertEqual(ti.title, 'foo') + self.assertEqual(ti.track_id, 'bar') + + def test_match_album(self): + mbid = 'd2a6f856-b553-40a0-ac54-a321e8e2da99' + with mock.patch('musicbrainzngs.search_releases') as sp: + sp.return_value = { + 'release-list': [{ + 'id': mbid, + }], + } + with mock.patch('musicbrainzngs.get_release_by_id') as gp: + gp.return_value = { + 'release': { + 'title': 'hi', + 'id': mbid, + 'medium-list': [{ + 'track-list': [{ + 'recording': { + 'title': 'foo', + 'id': 'bar', + 'length': 42, + }, + 'position': 9, + }], + 'position': 5, + }], + 'artist-credit': [{ + 'artist': { + 'name': 'some-artist', + 'id': 'some-id', + }, + }], + 'release-group': { + 'id': 'another-id', + } + } + } + + ai = list(mb.match_album('hello', 'there'))[0] + + sp.assert_called_with(artist='hello', release='there', limit=5) + gp.assert_calledwith(mbid) + self.assertEqual(ai.tracks[0].title, 'foo') + self.assertEqual(ai.album, 'hi') + + def test_match_track_empty(self): + with mock.patch('musicbrainzngs.search_recordings') as p: + til = list(mb.match_track(' ', ' ')) + self.assertFalse(p.called) + self.assertEqual(til, []) + + def test_match_album_empty(self): + with mock.patch('musicbrainzngs.search_releases') as p: + ail = list(mb.match_album(' ', ' ')) + self.assertFalse(p.called) + self.assertEqual(ail, []) + + def suite(): return unittest.TestLoader().loadTestsFromName(__name__)