Skip to content

Commit 0bf7c06

Browse files
author
Thomas Scholtes
committed
Media file tags can be customized with the write event
1 parent a38a6b2 commit 0bf7c06

File tree

4 files changed

+63
-13
lines changed

4 files changed

+63
-13
lines changed

beets/library.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,15 +473,16 @@ def write(self, path=None):
473473
else:
474474
path = normpath(path)
475475

476-
plugins.send('write', item=self, path=path)
476+
tags = dict(self)
477+
plugins.send('write', item=self, path=path, tags=tags)
477478

478479
try:
479480
mediafile = MediaFile(syspath(path),
480481
id3v23=beets.config['id3v23'].get(bool))
481482
except (OSError, IOError, UnreadableFileError) as exc:
482483
raise ReadError(self.path, exc)
483484

484-
mediafile.update(self)
485+
mediafile.update(tags)
485486
try:
486487
mediafile.save()
487488
except (OSError, IOError, MutagenError) as exc:

docs/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ Fixes:
6868
this case.
6969
* :doc:`/plugins/convert`: Fix filename extensions when converting
7070
automatically.
71+
* The ``write`` event allows plugins to change the tags that are
72+
written to a media file.
7173

7274
.. _discogs_client: https://github.com/discogs/discogs_client
7375

docs/dev/plugins.rst

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,14 @@ currently available are:
143143
or album's part) is removed from the library (even when its file is not
144144
deleted from disk).
145145

146-
* *write*: called with an ``Item`` object just before a file's metadata is
147-
written to disk (i.e., just before the file on disk is opened). Event
148-
handlers may raise a ``library.FileOperationError`` exception to abort
149-
the write operation. Beets will catch that exception, print an error
150-
message and continue.
146+
* *write*: called with an ``Item`` object, a ``path``, and a ``tags``
147+
dictionary just before a file's metadata is written to disk (i.e.,
148+
just before the file on disk is opened). Event handlers may change
149+
the ``tags`` dictionary to customize the tags that are written to the
150+
media file. Event handlers may also raise a
151+
``library.FileOperationError`` exception to abort the write
152+
operation. Beets will catch that exception, print an error message
153+
and continue.
151154

152155
* *after_write*: called with an ``Item`` object after a file's metadata is
153156
written to disk (i.e., just after the file on disk is closed).

test/test_plugins.py

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@
1414

1515
from mock import patch
1616
from _common import unittest
17-
from helper import TestHelper
17+
import helper
1818

1919
from beets import plugins
2020
from beets.library import Item
2121
from beets.dbcore import types
22+
from beets.mediafile import MediaFile
2223

2324

24-
class PluginTest(unittest.TestCase, TestHelper):
25+
class TestHelper(helper.TestHelper):
2526

26-
def setUp(self):
27+
def setup_plugin_loader(self):
2728
# FIXME the mocking code is horrific, but this is the lowest and
2829
# earliest level of the plugin mechanism we can hook into.
2930
self._plugin_loader_patch = patch('beets.plugins.load_plugins')
@@ -35,9 +36,22 @@ def myload(names=()):
3536
load_plugins.side_effect = myload
3637
self.setup_beets()
3738

38-
def tearDown(self):
39+
def teardown_plugin_loader(self):
3940
self._plugin_loader_patch.stop()
4041
self.unload_plugins()
42+
43+
def register_plugin(self, plugin_class):
44+
self._plugin_classes.add(plugin_class)
45+
46+
47+
class ItemTypesTest(unittest.TestCase, TestHelper):
48+
49+
def setUp(self):
50+
self.setup_plugin_loader()
51+
self.setup_beets()
52+
53+
def tearDown(self):
54+
self.teardown_plugin_loader()
4155
self.teardown_beets()
4256

4357
def test_flex_field_type(self):
@@ -64,8 +78,38 @@ class RatingPlugin(plugins.BeetsPlugin):
6478
out = self.run_with_output('ls', 'rating:3..5')
6579
self.assertNotIn('aaa', out)
6680

67-
def register_plugin(self, plugin_class):
68-
self._plugin_classes.add(plugin_class)
81+
82+
class ItemWriteTest(unittest.TestCase, TestHelper):
83+
84+
def setUp(self):
85+
self.setup_plugin_loader()
86+
self.setup_beets()
87+
88+
class EventListenerPlugin(plugins.BeetsPlugin):
89+
pass
90+
self.event_listener_plugin = EventListenerPlugin
91+
self.register_plugin(EventListenerPlugin)
92+
93+
def tearDown(self):
94+
self.teardown_plugin_loader()
95+
self.teardown_beets()
96+
97+
def test_change_tags(self):
98+
99+
def on_write(item=None, path=None, tags=None):
100+
if tags['artist'] == 'XXX':
101+
tags['artist'] = 'YYY'
102+
103+
self.register_listener('write', on_write)
104+
105+
item = self.add_item_fixture(artist='XXX')
106+
item.write()
107+
108+
mediafile = MediaFile(item.path)
109+
self.assertEqual(mediafile.artist, 'YYY')
110+
111+
def register_listener(self, event, func):
112+
self.event_listener_plugin.register_listener(event, func)
69113

70114

71115
def suite():

0 commit comments

Comments
 (0)