mirror of
https://github.com/beetbox/beets.git
synced 2026-01-15 12:41:22 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
f414fa0e10
8 changed files with 103 additions and 8 deletions
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
BIN
test/rsrc/date_with_slashes.ogg
Normal file
BIN
test/rsrc/date_with_slashes.ogg
Normal file
Binary file not shown.
BIN
test/rsrc/image.ape
Normal file
BIN
test/rsrc/image.ape
Normal file
Binary file not shown.
BIN
test/rsrc/pure.wma
Normal file
BIN
test/rsrc/pure.wma
Normal file
Binary file not shown.
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue