Merge branch 'master' of github.com:sampsyo/beets

This commit is contained in:
Adrian Sampson 2014-04-04 18:05:19 -07:00
commit a6de671994
6 changed files with 36 additions and 63 deletions

View file

@ -414,17 +414,23 @@ class Item(LibModel):
self.path = read_path
def write(self):
"""Write the item's metadata to the associated file.
def write(self, path=None):
"""Write the item's metadata to a media file.
``path`` defaults to the item's path property.
Can raise either a `ReadError` or a `WriteError`.
"""
if path is None:
path = self.path
else:
path = normpath(path)
try:
f = MediaFile(syspath(self.path))
f = MediaFile(syspath(path))
except (OSError, IOError) as exc:
raise ReadError(self.path, exc)
plugins.send('write', item=self)
plugins.send('write', item=self, path=path)
for key in ITEM_KEYS_WRITABLE:
setattr(f, key, self[key])

View file

@ -17,9 +17,9 @@
import logging
import traceback
from collections import defaultdict
import inspect
import beets
from beets import mediafile
PLUGIN_NAMESPACE = 'beetsplug'
@ -40,7 +40,6 @@ class BeetsPlugin(object):
def __init__(self, name=None):
"""Perform one-time plugin setup.
"""
_add_media_fields(self.item_fields())
self.import_stages = []
self.name = name or self.__module__.split('.')[-1]
self.config = beets.config[self.name]
@ -86,14 +85,6 @@ class BeetsPlugin(object):
"""
return ()
def item_fields(self):
"""Returns field descriptors to be added to the MediaFile class,
in the form of a dictionary whose keys are field names and whose
values are descriptor (e.g., MediaField) instances. The Library
database schema is not (currently) extended.
"""
return {}
def album_for_id(self, album_id):
"""Return an AlbumInfo object or None if no matching release was
found.
@ -297,13 +288,6 @@ def template_funcs():
funcs.update(plugin.template_funcs)
return funcs
def _add_media_fields(fields):
"""Adds a {name: descriptor} dictionary of fields to the MediaFile
class. Called during the plugin initialization.
"""
for key, value in fields.iteritems():
setattr(mediafile.MediaFile, key, value)
def import_stages():
"""Get a list of import stage functions defined by plugins."""
stages = []
@ -356,4 +340,8 @@ def send(event, **arguments):
Returns a list of return values from the handlers.
"""
log.debug('Sending event: %s' % event)
return [handler(**arguments) for handler in event_handlers()[event]]
for handler in event_handlers()[event]:
# Don't break legacy plugins if we want to pass more arguments
argspec = inspect.getargspec(handler).args
args = dict((k, v) for k, v in arguments.items() if k in argspec)
handler(**args)

View file

@ -174,13 +174,12 @@ def convert_item(dest_dir, keep_new, path_formats):
encode(item.path, dest)
# Write tags from the database to the converted file.
if not keep_new:
item.path = dest
item.write()
item.write(path=dest)
# If we're keeping the transcoded file, read it again (after
# writing) to get new bitrate, duration, etc.
if keep_new:
# If we're keeping the transcoded file, read it again (after
# writing) to get new bitrate, duration, etc.
item.path = dest
item.read()
item.store() # Store new path and audio data.

View file

@ -297,41 +297,6 @@ This field works for *item* templates. Similarly, you can register *album*
template fields by adding a function accepting an ``Album`` argument to the
``album_template_fields`` dict.
Extend MediaFile
^^^^^^^^^^^^^^^^
`MediaFile`_ is the file tag abstraction layer that beets uses to make
cross-format metadata manipulation simple. Plugins can add fields to MediaFile
to extend the kinds of metadata that they can easily manage.
The ``item_fields`` method on plugins should be overridden to return a
dictionary whose keys are field names and whose values are descriptor objects
that provide the field in question. The descriptors should probably be
``MediaField`` instances (defined in ``beets.mediafile``). Here's an example
plugin that provides a meaningless new field "foo"::
from beets import mediafile, plugins, ui
class FooPlugin(plugins.BeetsPlugin):
def item_fields(self):
return {
'foo': mediafile.MediaField(
mp3 = mediafile.StorageStyle(
'TXXX', id3_desc=u'Foo Field'),
mp4 = mediafile.StorageStyle(
'----:com.apple.iTunes:Foo Field'),
etc = mediafile.StorageStyle('FOO FIELD')
),
}
Later, the plugin can manipulate this new field by saying something like
``mf.foo = 'bar'`` where ``mf`` is a ``MediaFile`` instance.
Note that, currently, these additional fields are *only* applied to
``MediaFile`` itself. The beets library database schema and the ``Item`` class
are not extended, so the fields are second-class citizens. This may change
eventually.
.. _MediaFile: https://github.com/sampsyo/beets/wiki/MediaFile
Add Import Pipeline Stages
^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -361,7 +361,7 @@ Beets includes support for shell command completion. The command ``beet
completion`` prints out a `bash`_ 3.2 script; to enable completion put a line
like this into your ``.bashrc`` or similar file::
eval $(beet completion)
eval "$(beet completion)"
Or, to avoid slowing down your shell startup time, you can pipe the ``beet
completion`` output to a file and source that instead.

View file

@ -29,6 +29,7 @@ import beets.library
from beets import util
from beets import plugins
from beets import config
from beets.mediafile import MediaFile
TEMP_LIB = os.path.join(_common.RSRC, 'test_copy.blb')
@ -958,6 +959,20 @@ class WriteTest(_common.LibTestCase):
self.i.path = path
self.assertRaises(beets.library.WriteError, self.i.write)
def test_write_with_custom_path(self):
custom_path = os.path.join(self.temp_dir, 'file.mp3')
self.i.path = os.path.join(self.temp_dir, 'item_file.mp3')
shutil.copy(os.path.join(_common.RSRC, 'empty.mp3'), custom_path)
shutil.copy(os.path.join(_common.RSRC, 'empty.mp3'), self.i.path)
self.i['artist'] = 'new artist'
self.assertNotEqual(MediaFile(custom_path).artist, 'new artist')
self.assertNotEqual(MediaFile(self.i.path).artist, 'new artist')
self.i.write(custom_path)
self.assertEqual(MediaFile(custom_path).artist, 'new artist')
self.assertNotEqual(MediaFile(self.i.path).artist, 'new artist')
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)