mirror of
https://github.com/beetbox/beets.git
synced 2025-12-29 20:12:33 +01:00
Plugins can now extend MediaField
This commit is contained in:
parent
4b1a1e3d65
commit
eb4c323bcb
6 changed files with 76 additions and 34 deletions
|
|
@ -913,12 +913,14 @@ class MediaField(object):
|
|||
def __init__(self, *styles, **kwargs):
|
||||
"""Creates a new MediaField.
|
||||
|
||||
- `styles`: `StorageStyle` instances that describe the strategy
|
||||
for reading and writing the field in particular formats.
|
||||
There must be at least one style for each possible file
|
||||
format.
|
||||
- `styles`: `StorageStyle` instances that describe the strategy
|
||||
for reading and writing the field in particular formats.
|
||||
There must be at least one style for each possible file
|
||||
format.
|
||||
|
||||
- `out_type`: the type of the value that should be returned when
|
||||
getting this property.
|
||||
getting this property.
|
||||
|
||||
"""
|
||||
self.out_type = kwargs.get('out_type', unicode)
|
||||
self._styles = styles
|
||||
|
|
|
|||
|
|
@ -106,6 +106,20 @@ class BeetsPlugin(object):
|
|||
"""
|
||||
return None
|
||||
|
||||
def add_media_field(self, name, descriptor):
|
||||
"""Add a field that is synchronized between media files and items.
|
||||
|
||||
When a media field is added ``item.write()`` will set the name
|
||||
property of the item's MediaFile to ``item[name]`` and save the
|
||||
changes. Similarly ``item.read()`` will set ``item[name]`` to
|
||||
the value of the name property of the media file.
|
||||
|
||||
``descriptor`` must be an instance of ``mediafile.MediaField``.
|
||||
"""
|
||||
# Defer impor to prevent circular dependency
|
||||
from beets import library
|
||||
mediafile.MediaFile.add_field(name, descriptor)
|
||||
library.Item.media_fields.add(name)
|
||||
|
||||
listeners = None
|
||||
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ in hacking beets itself or creating plugins for it.
|
|||
|
||||
plugins
|
||||
api
|
||||
media_file
|
||||
|
|
|
|||
21
docs/dev/media_file.rst
Normal file
21
docs/dev/media_file.rst
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
.. _mediafile:
|
||||
|
||||
MediaFile
|
||||
---------
|
||||
|
||||
.. currentmodule:: beets.mediafile
|
||||
|
||||
.. autoclass:: MediaFile
|
||||
|
||||
.. automethod:: __init__
|
||||
.. automethod:: fields
|
||||
.. automethod:: readable_fields
|
||||
.. automethod:: save
|
||||
.. automethod:: update
|
||||
|
||||
.. autoclass:: MediaField
|
||||
|
||||
.. automethod:: __init__
|
||||
|
||||
.. autoclass:: StorageStyle
|
||||
:members:
|
||||
|
|
@ -300,38 +300,37 @@ template fields by adding a function accepting an ``Album`` argument to the
|
|||
Extend MediaFile
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
`MediaFile`_ is the file tag abstraction layer that beets uses to make
|
||||
:ref:`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"::
|
||||
The ``MediaFile`` class uses ``MediaField`` descriptors to provide
|
||||
access to file tags. Have a look at the ``beets.mediafile`` source code
|
||||
to learn how to use this descriptor class. If you have created a
|
||||
descriptor you can add it through your plugins ``add_media_field()``
|
||||
method.
|
||||
|
||||
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')
|
||||
),
|
||||
}
|
||||
.. automethod:: beets.plugins.BeetsPlugin.add_media_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.
|
||||
Here's an example plugin that provides a meaningless new field "foo"::
|
||||
|
||||
class FooPlugin(BeetsPlugin):
|
||||
def __init__(self):
|
||||
field = mediafile.MediaField(
|
||||
mediafile.MP3DescStorageStyle(u'foo')
|
||||
mediafile.StorageStyle(u'foo')
|
||||
)
|
||||
self.add_media_field('foo', field)
|
||||
|
||||
FooPlugin()
|
||||
item = Item.from_path('/path/to/foo/tag.mp3')
|
||||
assert item['foo'] == 'spam'
|
||||
|
||||
item['foo'] == 'ham'
|
||||
item.write()
|
||||
# The "foo" tag of the file is now "ham"
|
||||
|
||||
.. _MediaFile: https://github.com/sampsyo/beets/wiki/MediaFile
|
||||
|
||||
Add Import Pipeline Stages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from beets.mediafile import MediaFile, MediaField, Image, \
|
|||
MP3StorageStyle, StorageStyle, \
|
||||
MP4StorageStyle, ASFStorageStyle
|
||||
from beets.library import Item
|
||||
from beets.plugins import BeetsPlugin
|
||||
|
||||
|
||||
class ArtTestMixin(object):
|
||||
|
|
@ -263,7 +264,8 @@ field_extension = MediaField(
|
|||
class ExtendedFieldTestMixin(object):
|
||||
|
||||
def test_extended_field_write(self):
|
||||
MediaFile.add_field('initialkey', field_extension)
|
||||
plugin = BeetsPlugin()
|
||||
plugin.add_media_field('initialkey', field_extension)
|
||||
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.initialkey = 'F#'
|
||||
|
|
@ -272,9 +274,11 @@ class ExtendedFieldTestMixin(object):
|
|||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.initialkey, 'F#')
|
||||
delattr(MediaFile, 'initialkey')
|
||||
Item.media_fields.remove('initialkey')
|
||||
|
||||
def test_write_extended_tag_from_item(self):
|
||||
MediaFile.add_field('initialkey', field_extension)
|
||||
plugin = BeetsPlugin()
|
||||
plugin.add_media_field('initialkey', field_extension)
|
||||
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
self.assertEqual(mediafile.initialkey, '')
|
||||
|
|
@ -285,10 +289,11 @@ class ExtendedFieldTestMixin(object):
|
|||
self.assertEqual(mediafile.initialkey, 'Gb')
|
||||
|
||||
delattr(MediaFile, 'initialkey')
|
||||
Item.media_fields.remove('initialkey')
|
||||
|
||||
def test_read_flexible_attribute_from_file(self):
|
||||
MediaFile.add_field('initialkey', field_extension)
|
||||
Item.media_fields.add('initialkey')
|
||||
plugin = BeetsPlugin()
|
||||
plugin.add_media_field('initialkey', field_extension)
|
||||
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.update({'initialkey': 'F#'})
|
||||
|
|
|
|||
Loading…
Reference in a new issue