mirror of
https://github.com/beetbox/beets.git
synced 2025-12-29 20:12:33 +01:00
Merge branch 'master' of github.com:sampsyo/beets
This commit is contained in:
commit
a6de671994
6 changed files with 36 additions and 63 deletions
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
Loading…
Reference in a new issue