Merge remote-tracking branch 'upstream/master'

This commit is contained in:
kerobaros 2014-10-29 15:14:04 -05:00
commit f414fa0e10
8 changed files with 103 additions and 8 deletions

View file

@ -868,7 +868,7 @@ class VorbisImageStorageStyle(ListStorageStyle):
base64-encoded. Values are `Image` objects.
"""
formats = ['OggOpus', 'OggTheora', 'OggSpeex', 'OggVorbis',
'OggFlac', 'APEv2File', 'WavPack', 'Musepack', 'MonkeysAudio']
'OggFlac']
def __init__(self):
super(VorbisImageStorageStyle, self).__init__(
@ -950,6 +950,74 @@ class FlacImageStorageStyle(ListStorageStyle):
mutagen_file.clear_pictures()
class APEv2ImageStorageStyle(ListStorageStyle):
"""Store images in APEv2 tags. Values are `Image` objects.
"""
formats = ['APEv2File', 'WavPack', 'Musepack', 'MonkeysAudio', 'OptimFROG']
TAG_NAMES = {
ImageType.other: 'Cover Art (other)',
ImageType.icon: 'Cover Art (icon)',
ImageType.other_icon: 'Cover Art (other icon)',
ImageType.front: 'Cover Art (front)',
ImageType.back: 'Cover Art (back)',
ImageType.leaflet: 'Cover Art (leaflet)',
ImageType.media: 'Cover Art (media)',
ImageType.lead_artist: 'Cover Art (lead)',
ImageType.artist: 'Cover Art (artist)',
ImageType.conductor: 'Cover Art (conductor)',
ImageType.group: 'Cover Art (band)',
ImageType.composer: 'Cover Art (composer)',
ImageType.lyricist: 'Cover Art (lyricist)',
ImageType.recording_location: 'Cover Art (studio)',
ImageType.recording_session: 'Cover Art (recording)',
ImageType.performance: 'Cover Art (performance)',
ImageType.screen_capture: 'Cover Art (movie scene)',
ImageType.fish: 'Cover Art (colored fish)',
ImageType.illustration: 'Cover Art (illustration)',
ImageType.artist_logo: 'Cover Art (band logo)',
ImageType.publisher_logo: 'Cover Art (publisher logo)',
}
def __init__(self):
super(APEv2ImageStorageStyle, self).__init__(key='')
def fetch(self, mutagen_file):
images = []
for cover_type, cover_tag in self.TAG_NAMES.items():
try:
frame = mutagen_file[cover_tag]
text_delimiter_index = frame.value.find('\x00')
comment = frame.value[0:text_delimiter_index] \
if text_delimiter_index > 0 else None
image_data = frame.value[text_delimiter_index + 1:]
images.append(Image(data=image_data, type=cover_type,
desc=comment))
except KeyError:
pass
return images
def set_list(self, mutagen_file, values):
self.delete(mutagen_file)
for image in values:
image_type = image.type or ImageType.other
comment = image.desc or ''
image_data = comment + "\x00" + image.data
cover_tag = self.TAG_NAMES[image_type]
mutagen_file[cover_tag] = image_data
def delete(self, mutagen_file):
"""Remove all images from the file.
"""
for cover_tag in self.TAG_NAMES.values():
try:
del mutagen_file[cover_tag]
except KeyError:
pass
# MediaField is a descriptor that represents a single logical field. It
# aggregates several StorageStyles describing how to access the data for
# each file type.
@ -1088,11 +1156,11 @@ class DateField(MediaField):
year, month, and day number. Each number is either an integer or
None.
"""
# Get the underlying data and split on hyphens.
# Get the underlying data and split on hyphens and slashes.
datestring = super(DateField, self).__get__(mediafile, None)
if isinstance(datestring, basestring):
datestring = re.sub(r'[Tt ].*$', '', unicode(datestring))
items = unicode(datestring).split('-')
items = re.split('[-/]', unicode(datestring))
else:
items = []
@ -1206,6 +1274,7 @@ class ImageListField(MediaField):
ASFImageStorageStyle(),
VorbisImageStorageStyle(),
FlacImageStorageStyle(),
APEv2ImageStorageStyle(),
)
def __get__(self, mediafile, _):
@ -1485,6 +1554,7 @@ class MediaFile(object):
StorageStyle('DESCRIPTION'),
StorageStyle('COMMENT'),
ASFStorageStyle('WM/Comments'),
ASFStorageStyle('Description')
)
bpm = MediaField(
MP3StorageStyle('TBPM'),

View file

@ -110,7 +110,7 @@ def album_imported(lib, album):
def embed_item(item, imagepath, maxwidth=None, itempath=None,
compare_threshold=0, ifempty=False, asalbum=False):
compare_threshold=0, ifempty=False, as_album=False):
"""Embed an image into the item's media file.
"""
if compare_threshold:
@ -126,7 +126,7 @@ def embed_item(item, imagepath, maxwidth=None, itempath=None,
displayable_path(imagepath)
))
return
if maxwidth and not asalbum:
if maxwidth and not as_album:
imagepath = resize_image(imagepath, maxwidth)
try:
@ -165,7 +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),
config['embedart']['ifempty'].get(bool), asalbum=True)
config['embedart']['ifempty'].get(bool), as_album=True)
def resize_image(imagepath, maxwidth):

View file

@ -146,7 +146,8 @@ var BeetsRouter = Backbone.Router.extend({
"item/query/:query": "itemQuery",
},
itemQuery: function(query) {
$.getJSON('/item/query/' + query, function(data) {
var queryURL = query.split(/\s+/).map(encodeURIComponent).join('/');
$.getJSON('/item/query/' + queryURL, function(data) {
var models = _.map(
data['results'],
function(d) { return new Item(d); }

View file

@ -22,6 +22,8 @@ Features:
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.
* Standard cover art in APEv2 metadata is now supported. Thanks to Matthias
Kiefer. :bug:`1042`
Fixes:
@ -55,6 +57,15 @@ Fixes:
:user:`multikatt`. :bug:`1027`, :bug:`1040`
* :doc:`/plugins/play`: Fix a potential crash when the command outputs special
characters. :bug:`1041`
* :doc:`/plugins/web`: Typed queries are now treated as separate query
components. :bug:`1045`
* Date tags that use slashes instead of dashes as separators are now
interpreted correctly. And WMA (ASF) files now map the ``comments`` field to
the "Description" tag (in addition to "WM/Comments"). Thanks to Matthias
Kiefer. :bug:`1043`
* :doc:`/plugins/embedart`: Avoid resizing the image multiple times when
embedding into an album. Thanks to :user:`kerobaros`. :bug:`1028`,
:bug:`1036`
1.3.8 (September 17, 2014)

Binary file not shown.

BIN
test/rsrc/image.ape Normal file

Binary file not shown.

BIN
test/rsrc/pure.wma Normal file

Binary file not shown.

View file

@ -770,6 +770,12 @@ class WMATest(ReadWriteTestBase, ExtendedImageStructureTestMixin,
mediafile = MediaFile(mediafile.path)
self.assertIn(mediafile.genre, [u'one', u'two'])
def test_read_pure_tags(self):
mediafile = self._mediafile_fixture('pure')
self.assertEqual(mediafile.comments, 'the comments')
self.assertEqual(mediafile.title, 'the title')
self.assertEqual(mediafile.artist, 'the artist')
class OggTest(ReadWriteTestBase, ExtendedImageStructureTestMixin,
unittest.TestCase):
@ -807,6 +813,12 @@ class OggTest(ReadWriteTestBase, ExtendedImageStructureTestMixin,
mediafile = MediaFile(mediafile.path)
self.assertFalse('coverart' in mediafile.mgfile)
def test_date_tag_with_slashes(self):
mediafile = self._mediafile_fixture('date_with_slashes')
self.assertEqual(mediafile.year, 2005)
self.assertEqual(mediafile.month, 6)
self.assertEqual(mediafile.day, 5)
class FlacTest(ReadWriteTestBase, PartialTestMixin,
ExtendedImageStructureTestMixin,
@ -822,7 +834,8 @@ class FlacTest(ReadWriteTestBase, PartialTestMixin,
}
class ApeTest(ReadWriteTestBase, unittest.TestCase):
class ApeTest(ReadWriteTestBase, ExtendedImageStructureTestMixin,
unittest.TestCase):
extension = 'ape'
audio_properties = {
'length': 1.0,