This commit is contained in:
xarph 2016-12-03 14:53:02 -08:00
commit fcc6c6bc80
68 changed files with 282 additions and 220 deletions

View file

@ -56,15 +56,13 @@ import imghdr
import os
import traceback
import enum
from beets import logging
from beets.util import displayable_path, syspath, as_string
import logging
import six
__all__ = ['UnreadableFileError', 'FileTypeError', 'MediaFile']
log = logging.getLogger('beets')
log = logging.getLogger(__name__)
# Human-readable type names.
TYPES = {
@ -90,7 +88,7 @@ class UnreadableFileError(Exception):
"""Mutagen is not able to extract information from the file.
"""
def __init__(self, path):
Exception.__init__(self, displayable_path(path))
Exception.__init__(self, repr(path))
class FileTypeError(UnreadableFileError):
@ -100,11 +98,10 @@ class FileTypeError(UnreadableFileError):
mutagen type is not supported by `Mediafile`.
"""
def __init__(self, path, mutagen_type=None):
path = displayable_path(path)
if mutagen_type is None:
msg = path
msg = repr(path)
else:
msg = u'{0}: of mutagen type {1}'.format(path, mutagen_type)
msg = u'{0}: of mutagen type {1}'.format(repr(path), mutagen_type)
Exception.__init__(self, msg)
@ -112,7 +109,7 @@ class MutagenError(UnreadableFileError):
"""Raised when Mutagen fails unexpectedly---probably due to a bug.
"""
def __init__(self, path, mutagen_exc):
msg = u'{0}: {1}'.format(displayable_path(path), mutagen_exc)
msg = u'{0}: {1}'.format(repr(path), mutagen_exc)
Exception.__init__(self, msg)
@ -134,12 +131,12 @@ def mutagen_call(action, path, func, *args, **kwargs):
try:
return func(*args, **kwargs)
except mutagen.MutagenError as exc:
log.debug(u'{} failed: {}', action, six.text_type(exc))
log.debug(u'%s failed: %s', action, six.text_type(exc))
raise UnreadableFileError(path)
except Exception as exc:
# Isolate bugs in Mutagen.
log.debug(u'{}', traceback.format_exc())
log.error(u'uncaught Mutagen exception in {}: {}', action, exc)
log.debug(u'%s', traceback.format_exc())
log.error(u'uncaught Mutagen exception in %s: %s', action, exc)
raise MutagenError(path, exc)
@ -405,7 +402,7 @@ class Image(object):
try:
type = list(ImageType)[type]
except IndexError:
log.debug(u"ignoring unknown image type index {0}", type)
log.debug(u"ignoring unknown image type index %s", type)
type = ImageType.other
self.type = type
@ -1412,7 +1409,6 @@ class MediaFile(object):
By default, MP3 files are saved with ID3v2.4 tags. You can use
the older ID3v2.3 standard by specifying the `id3v23` option.
"""
path = syspath(path)
self.path = path
self.mgfile = mutagen_call('open', path, mutagen.File, path)
@ -1488,7 +1484,12 @@ class MediaFile(object):
"""
for property, descriptor in cls.__dict__.items():
if isinstance(descriptor, MediaField):
yield as_string(property)
if isinstance(property, bytes):
# On Python 2, class field names are bytes. This method
# produces text strings.
yield property.decode('utf8', 'ignore')
else:
yield property
@classmethod
def _field_sort_name(cls, name):

View file

@ -105,7 +105,7 @@ class BadFiles(BeetsPlugin):
ui.print_(u"{}: checker exited withs status {}"
.format(ui.colorize('text_error', dpath), status))
for line in output:
ui.print_(" {}".format(displayable_path(line)))
ui.print_(u" {}".format(displayable_path(line)))
elif errors > 0:
ui.print_(u"{}: checker found {} errors or warnings"
.format(ui.colorize('text_warning', dpath), errors))

View file

@ -4,7 +4,9 @@ Changelog
1.4.2 (in development)
----------------------
Changelog goes here!
Fixes:
* :doc:`/plugins/badfiles`: Fix a crash on non-ASCII filenames. :bug:`2299`
1.4.1 (November 25, 2016)

View file

@ -60,7 +60,7 @@ file. The available options are:
Default: None.
- **google_engine**: The custom search engine to use.
Default: The `beets custom search engine`_, which searches the entire web.
**fanarttv_key**: The personal API key for requesting art from
- **fanarttv_key**: The personal API key for requesting art from
fanart.tv. See below.
- **store_source**: If enabled, fetchart stores the artwork's source in a
flexible tag named ``art_source``. See below for the rationale behind this.

View file

@ -72,7 +72,7 @@ The Web backend is built using a simple REST+JSON API with the excellent
`Backbone.js`_. This allows future non-Web clients to use the same backend API.
.. _Flask: http://flask.pocoo.org/
.. _Backbone.js: http://documentcloud.github.com/backbone/
.. _Backbone.js: http://backbonejs.org
Eventually, to make the Web player really viable, we should use a Flash fallback
for unsupported formats/browsers. There are a number of options for this:

View file

@ -333,6 +333,29 @@ class Bag(object):
return self.fields.get(key)
# Convenience methods for setting up a temporary sandbox directory for tests
# that need to interact with the filesystem.
class TempDirMixin(object):
"""Text mixin for creating and deleting a temporary directory.
"""
def create_temp_dir(self):
"""Create a temporary directory and assign it into `self.temp_dir`.
Call `remove_temp_dir` later to delete it.
"""
path = tempfile.mkdtemp()
if not isinstance(path, bytes):
path = path.encode('utf8')
self.temp_dir = path
def remove_temp_dir(self):
"""Delete the temporary directory created by `create_temp_dir`.
"""
if os.path.isdir(self.temp_dir):
shutil.rmtree(self.temp_dir)
# Platform mocking.
@contextmanager

View file

@ -16,12 +16,13 @@
"""Tests for the 'acousticbrainz' plugin.
"""
from __future__ import absolute_import, print_function
from __future__ import division, absolute_import, print_function
import json
import os.path
import unittest
from test._common import unittest, RSRC
from test._common import RSRC
from beetsplug.acousticbrainz import AcousticPlugin, ABSCHEME

View file

@ -19,12 +19,12 @@ from __future__ import division, absolute_import, print_function
import os
import shutil
import unittest
import responses
from mock import patch
from test import _common
from test._common import unittest
from beetsplug import fetchart
from beets.autotag import AlbumInfo, AlbumMatch
from beets import config

View file

@ -19,9 +19,9 @@ from __future__ import division, absolute_import, print_function
import re
import copy
import unittest
from test import _common
from test._common import unittest
from beets import autotag
from beets.autotag import match
from beets.autotag.hooks import Distance, string_dist

View file

@ -17,7 +17,7 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
from beetsplug import bucket
from beets import config, ui

View file

@ -7,11 +7,11 @@ import yaml
from mock import patch
from tempfile import mkdtemp
from shutil import rmtree
import unittest
from beets import ui
from beets import config
from test._common import unittest
from test.helper import TestHelper
from beets.library import Library
import six

View file

@ -17,8 +17,9 @@ from __future__ import division, absolute_import, print_function
import re
import os.path
import unittest
from test import _common
from test._common import unittest
from test import helper
from test.helper import control_stdin, capture_log

View file

@ -18,8 +18,8 @@
from __future__ import division, absolute_import, print_function
from test import _common
from test._common import unittest
from datetime import datetime
import unittest
import time
from beets.dbcore.query import _parse_periods, DateInterval, DateQuery

View file

@ -20,10 +20,10 @@ from __future__ import division, absolute_import, print_function
import os
import shutil
import sqlite3
import unittest
from six import assertRaisesRegex
from test import _common
from test._common import unittest
from beets import dbcore
from tempfile import mkstemp
import six

View file

@ -17,8 +17,9 @@
"""
from __future__ import division, absolute_import, print_function
import unittest
from test import _common
from test._common import unittest, Bag
from test._common import Bag
from beetsplug.discogs import DiscogsPlugin

View file

@ -15,10 +15,10 @@
from __future__ import division, absolute_import, print_function
import codecs
import unittest
from mock import patch
from test import _common
from test._common import unittest
from test.helper import TestHelper, control_stdin
from test.test_ui_importer import TerminalImportSessionSetup
from test.test_importer import ImportHelper, AutotagStub

View file

@ -19,9 +19,9 @@ import os.path
import shutil
from mock import patch, MagicMock
import tempfile
import unittest
from test import _common
from test._common import unittest
from test.helper import TestHelper
from beets.mediafile import MediaFile

View file

@ -2,9 +2,9 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
from test.helper import TestHelper
from beetsplug import embyupdate
import unittest
import responses

View file

@ -16,7 +16,7 @@
from __future__ import division, absolute_import, print_function
import os
from test._common import unittest
import unittest
from test.helper import TestHelper
from beets import util

View file

@ -20,9 +20,9 @@ from __future__ import division, absolute_import, print_function
import os
import shutil
import unittest
from test import _common
from test._common import unittest
from test.helper import capture_log
from test.test_importer import ImportHelper
from beets import config

View file

@ -21,9 +21,9 @@ import shutil
import os
import stat
from os.path import join
import unittest
from test import _common
from test._common import unittest
from test._common import item, touch
import beets.library
from beets import util

View file

@ -17,7 +17,7 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
from test.helper import TestHelper
from beetsplug import ftintitle

View file

@ -17,7 +17,7 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
import sys
import tempfile
from beets.util import hidden

View file

@ -17,9 +17,9 @@ from __future__ import division, absolute_import, print_function
import os.path
import tempfile
import unittest
from test import _common
from test._common import unittest
from test.helper import TestHelper
from beets import config

View file

@ -4,7 +4,7 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
from beets import importer
from beets.library import Item
from beetsplug.ihate import IHatePlugin

View file

@ -18,8 +18,8 @@ from __future__ import division, absolute_import, print_function
"""Tests for the `importadded` plugin."""
import os
import unittest
from test._common import unittest
from test.test_importer import ImportHelper, AutotagStub
from beets import importer
from beets import util

View file

@ -27,9 +27,9 @@ from tempfile import mkstemp
from zipfile import ZipFile
from tarfile import TarFile
from mock import patch, Mock
import unittest
from test import _common
from test._common import unittest
from beets.util import displayable_path, bytestring_path, py3_path
from test.helper import TestImportSession, TestHelper, has_program, capture_log
from beets import importer

View file

@ -6,8 +6,8 @@ import os
import os.path
import tempfile
import shutil
import unittest
from test._common import unittest
from beets import config
from beets.library import Item, Album, Library
from beetsplug.importfeeds import ImportFeedsPlugin

View file

@ -15,7 +15,7 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
from test.helper import TestHelper
from beets.mediafile import MediaFile

View file

@ -20,11 +20,12 @@ from beets import library
from beets.util import bytestring_path, _fsencoding
from beetsplug.ipfs import IPFSPlugin
from test import _common
from test._common import unittest
from test.helper import TestHelper
import unittest
import os
from test import _common
from test.helper import TestHelper
@patch('beets.util.command_output', Mock())
class IPFSPluginTest(unittest.TestCase, TestHelper):

View file

@ -16,7 +16,7 @@
from __future__ import division, absolute_import, print_function
from mock import patch
from test._common import unittest
import unittest
from test.helper import TestHelper
from beets.library import Item

View file

@ -17,10 +17,10 @@
from __future__ import division, absolute_import, print_function
import unittest
from mock import Mock
from test import _common
from test._common import unittest
from beetsplug import lastgenre
from beets import config

View file

@ -25,9 +25,9 @@ import re
import unicodedata
import sys
import time
import unittest
from test import _common
from test._common import unittest
from test._common import item
import beets.library
import beets.mediafile

View file

@ -7,12 +7,13 @@ import sys
import threading
import logging as log
from six import StringIO
import unittest
import beets.logging as blog
from beets import plugins, ui
import beetsplug
from test import _common
from test._common import unittest, TestCase
from test._common import TestCase
from test import helper
import six

View file

@ -18,13 +18,13 @@
from __future__ import division, absolute_import, print_function
import os
from test import _common
import sys
import re
import unittest
from test import _common
from mock import MagicMock
from test._common import unittest
from beetsplug import lyrics
from beets.library import Item
from beets.util import confit, bytestring_path

View file

@ -18,9 +18,10 @@
from __future__ import division, absolute_import, print_function
from test import _common
from test._common import unittest
from beets.autotag import mb
from beets import config
import unittest
import mock

View file

@ -15,7 +15,7 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
from test.helper import capture_stdout, control_stdin, TestHelper
from test.test_importer import ImportHelper, AutotagStub
from test.test_ui_importer import TerminalImportSessionSetup

View file

@ -15,9 +15,9 @@
from __future__ import division, absolute_import, print_function
import unittest
from mock import patch
from test._common import unittest
from test.helper import TestHelper,\
generate_album_info, \
generate_track_info, \

View file

@ -20,20 +20,14 @@ from __future__ import division, absolute_import, print_function
import os
import shutil
import tempfile
import datetime
import time
import unittest
from six import assertCountEqual
from test import _common
from test._common import unittest
from beets.mediafile import MediaFile, MediaField, Image, \
MP3DescStorageStyle, StorageStyle, MP4StorageStyle, \
ASFStorageStyle, ImageType, CoverArtField, UnreadableFileError
from beets.library import Item
from beets.plugins import BeetsPlugin
from beets.util import bytestring_path
import six
from beets.mediafile import MediaFile, Image, \
ImageType, CoverArtField, UnreadableFileError
class ArtTestMixin(object):
@ -298,80 +292,8 @@ class GenreListTestMixin(object):
assertCountEqual(self, mediafile.genres, [u'the genre', u'another'])
field_extension = MediaField(
MP3DescStorageStyle(u'customtag'),
MP4StorageStyle('----:com.apple.iTunes:customtag'),
StorageStyle('customtag'),
ASFStorageStyle('customtag'),
)
class ExtendedFieldTestMixin(object):
def test_extended_field_write(self):
plugin = BeetsPlugin()
plugin.add_media_field('customtag', field_extension)
try:
mediafile = self._mediafile_fixture('empty')
mediafile.customtag = u'F#'
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertEqual(mediafile.customtag, u'F#')
finally:
delattr(MediaFile, 'customtag')
Item._media_fields.remove('customtag')
def test_write_extended_tag_from_item(self):
plugin = BeetsPlugin()
plugin.add_media_field('customtag', field_extension)
try:
mediafile = self._mediafile_fixture('empty')
self.assertIsNone(mediafile.customtag)
item = Item(path=mediafile.path, customtag=u'Gb')
item.write()
mediafile = MediaFile(mediafile.path)
self.assertEqual(mediafile.customtag, u'Gb')
finally:
delattr(MediaFile, 'customtag')
Item._media_fields.remove('customtag')
def test_read_flexible_attribute_from_file(self):
plugin = BeetsPlugin()
plugin.add_media_field('customtag', field_extension)
try:
mediafile = self._mediafile_fixture('empty')
mediafile.update({'customtag': u'F#'})
mediafile.save()
item = Item.from_path(mediafile.path)
self.assertEqual(item['customtag'], u'F#')
finally:
delattr(MediaFile, 'customtag')
Item._media_fields.remove('customtag')
def test_invalid_descriptor(self):
with self.assertRaises(ValueError) as cm:
MediaFile.add_field('somekey', True)
self.assertIn(u'must be an instance of MediaField',
six.text_type(cm.exception))
def test_overwrite_property(self):
with self.assertRaises(ValueError) as cm:
MediaFile.add_field('artist', MediaField())
self.assertIn(u'property "artist" already exists',
six.text_type(cm.exception))
class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
ExtendedFieldTestMixin):
_common.TempDirMixin):
"""Test writing and reading tags. Subclasses must set ``extension`` and
``audio_properties``.
"""
@ -456,11 +378,10 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
]
def setUp(self):
self.temp_dir = bytestring_path(tempfile.mkdtemp())
self.create_temp_dir()
def tearDown(self):
if os.path.isdir(self.temp_dir):
shutil.rmtree(self.temp_dir)
self.remove_temp_dir()
def test_read_nonexisting(self):
mediafile = self._mediafile_fixture('full')
@ -709,7 +630,9 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
self.fail('\n '.join(errors))
def _mediafile_fixture(self, name):
name = bytestring_path(name + '.' + self.extension)
name = name + '.' + self.extension
if not isinstance(name, bytes):
name = name.encode('utf8')
src = os.path.join(_common.RSRC, name)
target = os.path.join(self.temp_dir, name)
shutil.copy(src, target)

View file

@ -19,18 +19,16 @@ from __future__ import division, absolute_import, print_function
import os
import shutil
import unittest
import mutagen.id3
from test import _common
from test._common import unittest
from test.helper import TestHelper
from beets.util import bytestring_path
import beets.mediafile
from beets import mediafile
import six
_sc = beets.mediafile._safe_cast
_sc = mediafile._safe_cast
class EdgeTest(unittest.TestCase):
@ -38,7 +36,7 @@ class EdgeTest(unittest.TestCase):
# Some files have an ID3 frame that has a list with no elements.
# This is very hard to produce, so this is just the first 8192
# bytes of a file found "in the wild".
emptylist = beets.mediafile.MediaFile(
emptylist = mediafile.MediaFile(
os.path.join(_common.RSRC, b'emptylist.mp3')
)
genre = emptylist.genre
@ -47,7 +45,7 @@ class EdgeTest(unittest.TestCase):
def test_release_time_with_space(self):
# Ensures that release times delimited by spaces are ignored.
# Amie Street produces such files.
space_time = beets.mediafile.MediaFile(
space_time = mediafile.MediaFile(
os.path.join(_common.RSRC, b'space_time.mp3')
)
self.assertEqual(space_time.year, 2009)
@ -57,7 +55,7 @@ class EdgeTest(unittest.TestCase):
def test_release_time_with_t(self):
# Ensures that release times delimited by Ts are ignored.
# The iTunes Store produces such files.
t_time = beets.mediafile.MediaFile(
t_time = mediafile.MediaFile(
os.path.join(_common.RSRC, b't_time.m4a')
)
self.assertEqual(t_time.year, 1987)
@ -67,20 +65,20 @@ class EdgeTest(unittest.TestCase):
def test_tempo_with_bpm(self):
# Some files have a string like "128 BPM" in the tempo field
# rather than just a number.
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, b'bpm.mp3'))
f = mediafile.MediaFile(os.path.join(_common.RSRC, b'bpm.mp3'))
self.assertEqual(f.bpm, 128)
def test_discc_alternate_field(self):
# Different taggers use different vorbis comments to reflect
# the disc and disc count fields: ensure that the alternative
# style works.
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, b'discc.ogg'))
f = mediafile.MediaFile(os.path.join(_common.RSRC, b'discc.ogg'))
self.assertEqual(f.disc, 4)
self.assertEqual(f.disctotal, 5)
def test_old_ape_version_bitrate(self):
media_file = os.path.join(_common.RSRC, b'oldape.ape')
f = beets.mediafile.MediaFile(media_file)
f = mediafile.MediaFile(media_file)
self.assertEqual(f.bitrate, 0)
def test_only_magic_bytes_jpeg(self):
@ -92,13 +90,13 @@ class EdgeTest(unittest.TestCase):
with open(magic_bytes_file, 'rb') as f:
jpg_data = f.read()
self.assertEqual(
beets.mediafile._imghdr_what_wrapper(jpg_data), 'jpeg')
mediafile._imghdr_what_wrapper(jpg_data), 'jpeg')
def test_soundcheck_non_ascii(self):
# Make sure we don't crash when the iTunes SoundCheck field contains
# non-ASCII binary data.
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC,
b'soundcheck-nonascii.m4a'))
f = mediafile.MediaFile(os.path.join(_common.RSRC,
b'soundcheck-nonascii.m4a'))
self.assertEqual(f.rg_track_gain, 0.0)
@ -146,7 +144,7 @@ class InvalidValueToleranceTest(unittest.TestCase):
self.assertEqual(v, 1.0)
class SafetyTest(unittest.TestCase, TestHelper):
class SafetyTest(unittest.TestCase, _common.TempDirMixin):
def setUp(self):
self.create_temp_dir()
@ -158,35 +156,35 @@ class SafetyTest(unittest.TestCase, TestHelper):
with open(fn, 'w') as f:
f.write(data)
try:
self.assertRaises(exc, beets.mediafile.MediaFile, fn)
self.assertRaises(exc, mediafile.MediaFile, fn)
finally:
os.unlink(fn) # delete the temporary file
def test_corrupt_mp3_raises_unreadablefileerror(self):
# Make sure we catch Mutagen reading errors appropriately.
self._exccheck(b'corrupt.mp3', beets.mediafile.UnreadableFileError)
self._exccheck(b'corrupt.mp3', mediafile.UnreadableFileError)
def test_corrupt_mp4_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.m4a', beets.mediafile.UnreadableFileError)
self._exccheck(b'corrupt.m4a', mediafile.UnreadableFileError)
def test_corrupt_flac_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.flac', beets.mediafile.UnreadableFileError)
self._exccheck(b'corrupt.flac', mediafile.UnreadableFileError)
def test_corrupt_ogg_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.ogg', beets.mediafile.UnreadableFileError)
self._exccheck(b'corrupt.ogg', mediafile.UnreadableFileError)
def test_invalid_ogg_header_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.ogg', beets.mediafile.UnreadableFileError,
self._exccheck(b'corrupt.ogg', mediafile.UnreadableFileError,
'OggS\x01vorbis')
def test_corrupt_monkeys_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.ape', beets.mediafile.UnreadableFileError)
self._exccheck(b'corrupt.ape', mediafile.UnreadableFileError)
def test_invalid_extension_raises_filetypeerror(self):
self._exccheck(b'something.unknown', beets.mediafile.FileTypeError)
self._exccheck(b'something.unknown', mediafile.FileTypeError)
def test_magic_xml_raises_unreadablefileerror(self):
self._exccheck(b'nothing.xml', beets.mediafile.UnreadableFileError,
self._exccheck(b'nothing.xml', mediafile.UnreadableFileError,
"ftyp")
@unittest.skipUnless(_common.HAVE_SYMLINK, u'platform lacks symlink')
@ -194,8 +192,8 @@ class SafetyTest(unittest.TestCase, TestHelper):
fn = os.path.join(_common.RSRC, b'brokenlink')
os.symlink('does_not_exist', fn)
try:
self.assertRaises(beets.mediafile.UnreadableFileError,
beets.mediafile.MediaFile, fn)
self.assertRaises(mediafile.UnreadableFileError,
mediafile.MediaFile, fn)
finally:
os.unlink(fn)
@ -206,19 +204,19 @@ class SideEffectsTest(unittest.TestCase):
def test_opening_tagless_file_leaves_untouched(self):
old_mtime = os.stat(self.empty).st_mtime
beets.mediafile.MediaFile(self.empty)
mediafile.MediaFile(self.empty)
new_mtime = os.stat(self.empty).st_mtime
self.assertEqual(old_mtime, new_mtime)
class MP4EncodingTest(unittest.TestCase, TestHelper):
class MP4EncodingTest(unittest.TestCase, _common.TempDirMixin):
def setUp(self):
self.create_temp_dir()
src = os.path.join(_common.RSRC, b'full.m4a')
self.path = os.path.join(self.temp_dir, b'test.m4a')
shutil.copy(src, self.path)
self.mf = beets.mediafile.MediaFile(self.path)
self.mf = mediafile.MediaFile(self.path)
def tearDown(self):
self.remove_temp_dir()
@ -226,18 +224,18 @@ class MP4EncodingTest(unittest.TestCase, TestHelper):
def test_unicode_label_in_m4a(self):
self.mf.label = u'foo\xe8bar'
self.mf.save()
new_mf = beets.mediafile.MediaFile(self.path)
new_mf = mediafile.MediaFile(self.path)
self.assertEqual(new_mf.label, u'foo\xe8bar')
class MP3EncodingTest(unittest.TestCase, TestHelper):
class MP3EncodingTest(unittest.TestCase, _common.TempDirMixin):
def setUp(self):
self.create_temp_dir()
src = os.path.join(_common.RSRC, b'full.mp3')
self.path = os.path.join(self.temp_dir, b'test.mp3')
shutil.copy(src, self.path)
self.mf = beets.mediafile.MediaFile(self.path)
self.mf = mediafile.MediaFile(self.path)
def test_comment_with_latin1_encoding(self):
# Set up the test file with a Latin1-encoded COMM frame. The encoding
@ -250,7 +248,7 @@ class MP3EncodingTest(unittest.TestCase, TestHelper):
self.mf.save()
class ZeroLengthMediaFile(beets.mediafile.MediaFile):
class ZeroLengthMediaFile(mediafile.MediaFile):
@property
def length(self):
return 0.0
@ -271,7 +269,7 @@ class TypeTest(unittest.TestCase):
def setUp(self):
super(TypeTest, self).setUp()
path = os.path.join(_common.RSRC, b'full.mp3')
self.mf = beets.mediafile.MediaFile(path)
self.mf = mediafile.MediaFile(path)
def test_year_integer_in_string(self):
self.mf.year = u'2009'
@ -303,45 +301,45 @@ class TypeTest(unittest.TestCase):
class SoundCheckTest(unittest.TestCase):
def test_round_trip(self):
data = beets.mediafile._sc_encode(1.0, 1.0)
gain, peak = beets.mediafile._sc_decode(data)
data = mediafile._sc_encode(1.0, 1.0)
gain, peak = mediafile._sc_decode(data)
self.assertEqual(gain, 1.0)
self.assertEqual(peak, 1.0)
def test_decode_zero(self):
data = b' 80000000 80000000 00000000 00000000 00000000 00000000 ' \
b'00000000 00000000 00000000 00000000'
gain, peak = beets.mediafile._sc_decode(data)
gain, peak = mediafile._sc_decode(data)
self.assertEqual(gain, 0.0)
self.assertEqual(peak, 0.0)
def test_malformatted(self):
gain, peak = beets.mediafile._sc_decode(b'foo')
gain, peak = mediafile._sc_decode(b'foo')
self.assertEqual(gain, 0.0)
self.assertEqual(peak, 0.0)
def test_special_characters(self):
gain, peak = beets.mediafile._sc_decode(u'caf\xe9'.encode('utf-8'))
gain, peak = mediafile._sc_decode(u'caf\xe9'.encode('utf-8'))
self.assertEqual(gain, 0.0)
self.assertEqual(peak, 0.0)
def test_decode_handles_unicode(self):
# Most of the time, we expect to decode the raw bytes. But some formats
# might give us text strings, which we need to handle.
gain, peak = beets.mediafile._sc_decode(u'caf\xe9')
gain, peak = mediafile._sc_decode(u'caf\xe9')
self.assertEqual(gain, 0.0)
self.assertEqual(peak, 0.0)
class ID3v23Test(unittest.TestCase, TestHelper):
def _make_test(self, ext='mp3', id3v23=False):
class ID3v23Test(unittest.TestCase, _common.TempDirMixin):
def _make_test(self, ext=b'mp3', id3v23=False):
self.create_temp_dir()
src = os.path.join(_common.RSRC,
bytestring_path('full.{0}'.format(ext)))
b'full.' + ext)
self.path = os.path.join(self.temp_dir,
bytestring_path('test.{0}'.format(ext)))
b'test.' + ext)
shutil.copy(src, self.path)
return beets.mediafile.MediaFile(self.path, id3v23=id3v23)
return mediafile.MediaFile(self.path, id3v23=id3v23)
def _delete_test(self):
self.remove_temp_dir()
@ -369,7 +367,7 @@ class ID3v23Test(unittest.TestCase, TestHelper):
self._delete_test()
def test_v23_on_non_mp3_is_noop(self):
mf = self._make_test('m4a', id3v23=True)
mf = self._make_test(b'm4a', id3v23=True)
try:
mf.year = 2013
mf.save()
@ -386,9 +384,9 @@ class ID3v23Test(unittest.TestCase, TestHelper):
mf = self._make_test(id3v23=v23)
try:
mf.images = [
beets.mediafile.Image(b'data', desc=u""),
beets.mediafile.Image(b'data', desc=u"foo"),
beets.mediafile.Image(b'data', desc=u"\u0185"),
mediafile.Image(b'data', desc=u""),
mediafile.Image(b'data', desc=u"foo"),
mediafile.Image(b'data', desc=u"\u0185"),
]
mf.save()
apic_frames = mf.mgfile.tags.getall('APIC')

View file

@ -21,9 +21,9 @@ import time
from datetime import datetime
from beets.library import Item
from beets.util import py3_path
import unittest
from test import _common
from test._common import unittest
from test.helper import TestHelper

View file

@ -15,8 +15,8 @@
from __future__ import division, absolute_import, print_function
import unittest
from mock import Mock, patch, call, ANY
from test._common import unittest
from test.helper import TestHelper
from beets.library import Item

View file

@ -6,9 +6,9 @@ from __future__ import division, absolute_import, print_function
import os
import platform
import unittest
from mock import patch, Mock
from test._common import unittest
from test.helper import TestHelper
from beets.util import displayable_path
from beetsplug.permissions import (check_permissions,

View file

@ -18,8 +18,8 @@
from __future__ import division, absolute_import, print_function
import six
import unittest
from test._common import unittest
from beets.util import pipeline

View file

@ -19,9 +19,9 @@ from __future__ import division, absolute_import, print_function
import os
import unittest
from mock import patch, ANY
from test._common import unittest
from test.helper import TestHelper, control_stdin
from beets.ui import UserError

View file

@ -17,7 +17,7 @@
"""
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
from beetsplug import bpd

View file

@ -2,9 +2,9 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
from test.helper import TestHelper
from beetsplug.plexupdate import get_music_section, update_plex
import unittest
import responses

View file

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# This file is part of beets.
# Copyright 2016, Adrian Sampson.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
"""Tests the facility that lets plugins add custom field to MediaFile.
"""
from __future__ import division, absolute_import, print_function
import os
import six
import shutil
from test import _common
from beets.library import Item
from beets import mediafile
from beets.plugins import BeetsPlugin
from beets.util import bytestring_path
field_extension = mediafile.MediaField(
mediafile.MP3DescStorageStyle(u'customtag'),
mediafile.MP4StorageStyle('----:com.apple.iTunes:customtag'),
mediafile.StorageStyle('customtag'),
mediafile.ASFStorageStyle('customtag'),
)
class ExtendedFieldTestMixin(_common.TestCase):
def _mediafile_fixture(self, name, extension='mp3'):
name = bytestring_path(name + '.' + extension)
src = os.path.join(_common.RSRC, name)
target = os.path.join(self.temp_dir, name)
shutil.copy(src, target)
return mediafile.MediaFile(target)
def test_extended_field_write(self):
plugin = BeetsPlugin()
plugin.add_media_field('customtag', field_extension)
try:
mf = self._mediafile_fixture('empty')
mf.customtag = u'F#'
mf.save()
mf = mediafile.MediaFile(mf.path)
self.assertEqual(mf.customtag, u'F#')
finally:
delattr(mediafile.MediaFile, 'customtag')
Item._media_fields.remove('customtag')
def test_write_extended_tag_from_item(self):
plugin = BeetsPlugin()
plugin.add_media_field('customtag', field_extension)
try:
mf = self._mediafile_fixture('empty')
self.assertIsNone(mf.customtag)
item = Item(path=mf.path, customtag=u'Gb')
item.write()
mf = mediafile.MediaFile(mf.path)
self.assertEqual(mf.customtag, u'Gb')
finally:
delattr(mediafile.MediaFile, 'customtag')
Item._media_fields.remove('customtag')
def test_read_flexible_attribute_from_file(self):
plugin = BeetsPlugin()
plugin.add_media_field('customtag', field_extension)
try:
mf = self._mediafile_fixture('empty')
mf.update({'customtag': u'F#'})
mf.save()
item = Item.from_path(mf.path)
self.assertEqual(item['customtag'], u'F#')
finally:
delattr(mediafile.MediaFile, 'customtag')
Item._media_fields.remove('customtag')
def test_invalid_descriptor(self):
with self.assertRaises(ValueError) as cm:
mediafile.MediaFile.add_field('somekey', True)
self.assertIn(u'must be an instance of MediaField',
six.text_type(cm.exception))
def test_overwrite_property(self):
with self.assertRaises(ValueError) as cm:
mediafile.MediaFile.add_field('artist', mediafile.MediaField())
self.assertIn(u'property "artist" already exists',
six.text_type(cm.exception))

View file

@ -19,6 +19,7 @@ import os
from mock import patch, Mock, ANY
import shutil
import itertools
import unittest
from beets.importer import SingletonImportTask, SentinelImportTask, \
ArchiveImportTask, action
@ -30,7 +31,7 @@ from beets.util import displayable_path, bytestring_path
from test.test_importer import ImportHelper, AutotagStub
from test.test_ui_importer import TerminalImportSessionSetup
from test._common import unittest, RSRC
from test._common import RSRC
from test import helper

View file

@ -21,9 +21,9 @@ from functools import partial
from mock import patch
import os
import sys
import unittest
from test import _common
from test._common import unittest
from test import helper
import beets.library

View file

@ -16,14 +16,15 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
import six
from test.helper import TestHelper, has_program
from beets import config
from beets.mediafile import MediaFile
from beetsplug.replaygain import (FatalGstreamerPluginReplayGainError,
GStreamerBackend)
import six
try:
import gi

View file

@ -18,6 +18,7 @@ from __future__ import division, absolute_import, print_function
from os import path, remove
from tempfile import mkdtemp
from shutil import rmtree
import unittest
from mock import Mock, MagicMock
@ -29,7 +30,6 @@ from beets.util import syspath, bytestring_path, py3_path, CHAR_REPLACE
from beets.ui import UserError
from beets import config
from test._common import unittest
from test.helper import TestHelper

View file

@ -17,8 +17,8 @@
"""
from __future__ import division, absolute_import, print_function
import unittest
from test import _common
from test._common import unittest
import beets.library
from beets import dbcore
from beets import config

View file

@ -6,9 +6,9 @@ from __future__ import division, absolute_import, print_function
import os
import responses
import unittest
from test import _common
from test._common import unittest
from beets import config
from beets.library import Item
from beetsplug import spotify

View file

@ -17,9 +17,9 @@
"""
from __future__ import division, absolute_import, print_function
from test._common import unittest
from beets.util import functemplate
import unittest
import six
from beets.util import functemplate
def _normexpr(expr):

View file

@ -4,7 +4,7 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
from test import _common
from beets import config
from beetsplug.the import ThePlugin, PATTERN_A, PATTERN_THE, FORMAT

View file

@ -19,8 +19,8 @@ import os.path
from mock import Mock, patch, call
from tempfile import mkdtemp
from shutil import rmtree
import unittest
from test._common import unittest
from test.helper import TestHelper
from beets.util import bytestring_path

View file

@ -17,8 +17,8 @@ from __future__ import division, absolute_import, print_function
import time
from datetime import datetime
import unittest
from test._common import unittest
from test.helper import TestHelper
from beets.util.confit import ConfigValueError

View file

@ -24,10 +24,10 @@ import subprocess
import platform
from copy import deepcopy
import six
import unittest
from mock import patch, Mock
from test import _common
from test._common import unittest
from test.helper import capture_stdout, has_program, TestHelper, control_stdin
from beets import library

View file

@ -20,9 +20,9 @@ from __future__ import division, absolute_import, print_function
import os
import shutil
import unittest
from test import _common
from test._common import unittest
from beets import library
from beets import ui

View file

@ -20,8 +20,9 @@ test_importer module. But here the test importer inherits from
"""
from __future__ import division, absolute_import, print_function
import unittest
from test._common import unittest, DummyIO
from test._common import DummyIO
from test import test_importer
from beets.ui.commands import TerminalImportSession
from beets import importer

View file

@ -18,8 +18,8 @@
from __future__ import division, absolute_import, print_function
import unittest
from test import _common
from test._common import unittest
from beets import ui

View file

@ -20,10 +20,10 @@ import sys
import re
import os
import subprocess
import unittest
from mock import patch, Mock
from test._common import unittest
from test import _common
from beets import util
import six

View file

@ -16,8 +16,8 @@
"""Tests for the virtual filesystem builder.."""
from __future__ import division, absolute_import, print_function
import unittest
from test import _common
from test._common import unittest
from beets import library
from beets import vfs

View file

@ -4,9 +4,9 @@
from __future__ import division, absolute_import, print_function
import unittest
from six import assertCountEqual
from test._common import unittest
from test import _common
import json
from beets.library import Item, Album

View file

@ -4,7 +4,7 @@
from __future__ import division, absolute_import, print_function
from test._common import unittest
import unittest
from test.helper import TestHelper
from beets.library import Item

View file

@ -20,8 +20,7 @@ from __future__ import division, absolute_import, print_function
import os
import re
import sys
from test._common import unittest
import unittest
pkgpath = os.path.dirname(__file__) or '.'
sys.path.append(pkgpath)

View file

@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
envlist = py27-test, py27-flake8, docs
envlist = py27-test, py35-test, py27-flake8, docs
# The exhaustive list of environments is:
# envlist = py{27,34,35}-{test,cov}, py{27,34,35}-flake8, docs