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).*?(?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__)