Merge pull request #2088 from lazka/mutagen-cleanup

Mutagen cleanup
This commit is contained in:
Adrian Sampson 2016-06-28 10:17:16 -07:00
commit 3a700eb2a4
5 changed files with 69 additions and 59 deletions

View file

@ -26,7 +26,7 @@ import six
from unidecode import unidecode
from beets import logging
from beets.mediafile import MediaFile, MutagenError, UnreadableFileError
from beets.mediafile import MediaFile, UnreadableFileError
from beets import plugins
from beets import util
from beets.util import bytestring_path, syspath, normpath, samefile
@ -568,7 +568,7 @@ class Item(LibModel):
read_path = normpath(read_path)
try:
mediafile = MediaFile(syspath(read_path))
except (OSError, IOError, UnreadableFileError) as exc:
except UnreadableFileError as exc:
raise ReadError(read_path, exc)
for key in self._media_fields:
@ -615,14 +615,14 @@ class Item(LibModel):
try:
mediafile = MediaFile(syspath(path),
id3v23=beets.config['id3v23'].get(bool))
except (OSError, IOError, UnreadableFileError) as exc:
except UnreadableFileError as exc:
raise ReadError(self.path, exc)
# Write the tags to the file.
mediafile.update(item_tags)
try:
mediafile.save()
except (OSError, IOError, MutagenError) as exc:
except UnreadableFileError as exc:
raise WriteError(self.path, exc)
# The file has a new mtime.

View file

@ -1346,32 +1346,12 @@ class MediaFile(object):
path = syspath(path)
self.path = path
unreadable_exc = (
mutagen.mp3.error,
mutagen.id3.error,
mutagen.flac.error,
mutagen.monkeysaudio.MonkeysAudioHeaderError,
mutagen.mp4.error,
mutagen.oggopus.error,
mutagen.oggvorbis.error,
mutagen.ogg.error,
mutagen.asf.error,
mutagen.apev2.error,
mutagen.aiff.error,
)
try:
self.mgfile = mutagen.File(path)
except unreadable_exc as exc:
log.debug(u'header parsing failed: {0}', six.text_type(exc))
except (mutagen.MutagenError, IOError) as exc:
# Mutagen <1.33 could raise IOError
log.debug(u'parsing failed: {0}', six.text_type(exc))
raise UnreadableFileError(path)
except IOError as exc:
if type(exc) == IOError:
# This is a base IOError, not a subclass from Mutagen or
# anywhere else.
raise
else:
log.debug(u'{}', traceback.format_exc())
raise MutagenError(path, exc)
except Exception as exc:
# Isolate bugs in Mutagen.
log.debug(u'{}', traceback.format_exc())
@ -1384,20 +1364,10 @@ class MediaFile(object):
elif (type(self.mgfile).__name__ == 'M4A' or
type(self.mgfile).__name__ == 'MP4'):
info = self.mgfile.info
if hasattr(info, 'codec'):
if info.codec and info.codec.startswith('alac'):
self.type = 'alac'
else:
self.type = 'aac'
if info.codec and info.codec.startswith('alac'):
self.type = 'alac'
else:
# This hack differentiates AAC and ALAC on versions of
# Mutagen < 1.26. Once Mutagen > 1.26 is out and
# required by beets, we can remove this.
if hasattr(self.mgfile.info, 'bitrate') and \
self.mgfile.info.bitrate > 0:
self.type = 'aac'
else:
self.type = 'alac'
self.type = 'aac'
elif (type(self.mgfile).__name__ == 'ID3' or
type(self.mgfile).__name__ == 'MP3'):
self.type = 'mp3'
@ -1428,7 +1398,8 @@ class MediaFile(object):
self.id3v23 = id3v23 and self.type == 'mp3'
def save(self):
"""Write the object's tags back to the file.
"""Write the object's tags back to the file. May
throw `UnreadableFileError`.
"""
# Possibly save the tags to ID3v2.3.
kwargs = {}
@ -1440,27 +1411,41 @@ class MediaFile(object):
id3.update_to_v23()
kwargs['v2_version'] = 3
# Isolate bugs in Mutagen.
try:
self.mgfile.save(**kwargs)
except (IOError, OSError):
# Propagate these through: they don't represent Mutagen bugs.
raise
except (mutagen.MutagenError, IOError) as exc:
# Mutagen <1.33 could raise IOError
log.debug(u'saving failed: {0}', six.text_type(exc))
raise UnreadableFileError(self.path)
except Exception as exc:
# Isolate bugs in Mutagen.
log.debug(u'{}', traceback.format_exc())
log.error(u'uncaught Mutagen exception in save: {0}', exc)
raise MutagenError(self.path, exc)
def delete(self):
"""Remove the current metadata tag from the file.
"""Remove the current metadata tag from the file. May
throw `UnreadableFileError`.
"""
try:
self.mgfile.delete()
except NotImplementedError:
# For Mutagen types that don't support deletion (notably,
# ASF), just delete each tag individually.
for tag in self.mgfile.keys():
del self.mgfile[tag]
try:
self.mgfile.delete()
except NotImplementedError:
# FIXME: This is fixed in mutagen >=1.31
# For Mutagen types that don't support deletion (notably,
# ASF), just delete each tag individually.
for tag in self.mgfile.keys():
del self.mgfile[tag]
except (mutagen.MutagenError, IOError) as exc:
# Mutagen <1.33 could raise IOError
log.debug(u'deleting failed: {0}', six.text_type(exc))
raise UnreadableFileError(self.path)
except Exception as exc:
# Isolate bugs in Mutagen.
log.debug(u'{}', traceback.format_exc())
log.error(u'uncaught Mutagen exception in save: {0}', exc)
raise MutagenError(self.path, exc)
# Convenient access to the set of available fields.
@ -1969,6 +1954,7 @@ class MediaFile(object):
def channels(self):
"""The number of channels in the audio (an int)."""
if isinstance(self.mgfile.info, mutagen.mp3.MPEGInfo):
# FIXME: MPEGInfo.channels was added in mutagen 1.30
return {
mutagen.mp3.STEREO: 2,
mutagen.mp3.JOINTSTEREO: 2,

View file

@ -119,7 +119,7 @@ class ScrubPlugin(BeetsPlugin):
try:
mf = mediafile.MediaFile(util.syspath(item.path),
config['id3v23'].get(bool))
except IOError as exc:
except mediafile.UnreadableFileError as exc:
self._log.error(u'could not open file to scrub: {0}',
exc)
art = mf.art
@ -133,10 +133,13 @@ class ScrubPlugin(BeetsPlugin):
item.try_write()
if art:
self._log.debug(u'restoring art')
mf = mediafile.MediaFile(util.syspath(item.path),
config['id3v23'].get(bool))
mf.art = art
mf.save()
try:
mf = mediafile.MediaFile(util.syspath(item.path),
config['id3v23'].get(bool))
mf.art = art
mf.save()
except mediafile.UnreadableFileError as exc:
self._log.error(u'could not write tags: {0}', exc)
def import_task_files(self, session, task):
"""Automatically scrub imported files."""

View file

@ -29,7 +29,7 @@ from test import _common
from test._common import unittest
from beets.mediafile import MediaFile, MediaField, Image, \
MP3DescStorageStyle, StorageStyle, MP4StorageStyle, \
ASFStorageStyle, ImageType, CoverArtField
ASFStorageStyle, ImageType, CoverArtField, UnreadableFileError
from beets.library import Item
from beets.plugins import BeetsPlugin
from beets.util import bytestring_path
@ -455,6 +455,27 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
if os.path.isdir(self.temp_dir):
shutil.rmtree(self.temp_dir)
def test_read_nonexisting(self):
mediafile = self._mediafile_fixture('full')
os.remove(mediafile.path)
self.assertRaises(UnreadableFileError, MediaFile, mediafile.path)
def test_save_nonexisting(self):
mediafile = self._mediafile_fixture('full')
os.remove(mediafile.path)
try:
mediafile.save()
except UnreadableFileError:
pass
def test_delete_nonexisting(self):
mediafile = self._mediafile_fixture('full')
os.remove(mediafile.path)
try:
mediafile.delete()
except UnreadableFileError:
pass
def test_read_audio_properties(self):
mediafile = self._mediafile_fixture('full')
for key, value in self.audio_properties.items():

View file

@ -193,7 +193,7 @@ class SafetyTest(unittest.TestCase, TestHelper):
fn = os.path.join(_common.RSRC, b'brokenlink')
os.symlink('does_not_exist', fn)
try:
self.assertRaises(IOError,
self.assertRaises(beets.mediafile.UnreadableFileError,
beets.mediafile.MediaFile, fn)
finally:
os.unlink(fn)