mirror of
https://github.com/beetbox/beets.git
synced 2026-02-16 20:35:23 +01:00
Updating to master to form a new PR.
This commit is contained in:
commit
d06da6381f
12 changed files with 123 additions and 17 deletions
|
|
@ -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()):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) # <br> eats up surrounding '\n'
|
||||
html = BREAK_RE.sub('\n', html) # <br> 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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ your config file::
|
|||
|
||||
fetchart:
|
||||
google_search: true
|
||||
|
||||
|
||||
Embedding Album Art
|
||||
-------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"""foo<script>bar</script>baz"""
|
||||
self.assertEqual(lyrics._scrape_strip_cruft(text, True),
|
||||
"foobaz")
|
||||
|
||||
def test_scrape_merge_paragraphs(self):
|
||||
text = u"one</p> <p class='myclass'>two</p><p>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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue