Skip to content

Commit 40d1d99

Browse files
Add alternatives.update_item event (#148)
1 parent a856c3f commit 40d1d99

File tree

7 files changed

+253
-56
lines changed

7 files changed

+253
-56
lines changed

CHANGELOG.md

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,95 @@
1-
Change Log
2-
==========
1+
# Change Log
32

43
## Unreleased
5-
* Promote `typing-extensions` to runtime dependency
6-
* Improved disk write performance when encoding items
4+
5+
- Promote `typing-extensions` to runtime dependency
6+
- Improved disk write performance when encoding items
7+
- You can now hook into collection updates and run your own logic using the
8+
`alternatives.item_updated` event.
79

810
## v0.13.3 - 2025-05-12
9-
* Add support for beets v2.3.x
11+
12+
- Add support for beets v2.3.x
1013

1114
## v0.13.2 - 2025-02-28
12-
* Make builds more reliable for packagers
15+
16+
- Make builds more reliable for packagers
1317

1418
## v0.13.1 - 2024-11-17
15-
* Resize embedded art in alternative files with `albumart_maxwidth` option.
19+
20+
- Resize embedded art in alternative files with `albumart_maxwidth` option.
1621

1722
## v0.13.0 - 2024-08-17
18-
* Consistently use Unicode paths to alternative items. This may result and
23+
24+
- Consistently use Unicode paths to alternative items. This may result and
1925
collection updates and orphaned files in alternatives. It may also improve
2026
usability on non-standard file systems (see [#74]).
21-
* Require Python >= 3.10
27+
- Require Python >= 3.10
2228

2329
[#74]: https://github.com/geigerzaehler/beets-alternatives/issues/74
2430

2531
## v0.12.0 - 2024-06-25
26-
* Fix an issue where items in a symlink collection with relative links were
32+
33+
- Fix an issue where items in a symlink collection with relative links were
2734
always unnecessarily updated.
28-
* Support [Beets v2](https://beets.readthedocs.io/en/latest/changelog.html#may-30-2024)
35+
- Support [Beets v2](https://beets.readthedocs.io/en/latest/changelog.html#may-30-2024)
2936

3037
## v0.11.1 - 2024-04-24
31-
* Add `--all` flag to update command which will update all configured
38+
39+
- Add `--all` flag to update command which will update all configured
3240
collections.
3341

3442
## v0.11.0 - 2023-06-06
35-
* Use the convert’s plugin [`thread` configuration][convert-config] when
43+
44+
- Use the convert’s plugin [`thread` configuration][convert-config] when
3645
transcoding files. ([@johnyerhot](https://github.com/johnyerhot))
37-
* Drop support for Python 2. Require Python >= 3.8
38-
* Require beets >= 1.6.0
46+
- Drop support for Python 2. Require Python >= 3.8
47+
- Require beets >= 1.6.0
3948

4049
[convert-config]: https://beets.readthedocs.io/en/latest/plugins/convert.html#configuration
4150

4251
## v0.10.2 - 2020-07-15
43-
* Add `beet alt list-tracks` command
44-
* SymlinkView: Fix stale symlinks not being removed when files are moved in the
52+
53+
- Add `beet alt list-tracks` command
54+
- SymlinkView: Fix stale symlinks not being removed when files are moved in the
4555
main library [#47][]
4656

4757
[#47]: https://github.com/geigerzaehler/beets-alternatives/issues/47
4858

4959
## v0.10.1 - 2019-09-18
50-
* Running `beet completion` does not crash anymore [#38][]
60+
61+
- Running `beet completion` does not crash anymore [#38][]
5162

5263
[#38]: https://github.com/geigerzaehler/beets-alternatives/issues/38
5364

5465
## v0.10.0 - 2019-08-25
55-
* Symlink views now support relative symlinks (@daviddavo)
56-
* Running just `beet alt` does not throw an error anymore (@daviddavo)
66+
67+
- Symlink views now support relative symlinks (@daviddavo)
68+
- Running just `beet alt` does not throw an error anymore (@daviddavo)
5769

5870
## v0.9.0 - 2018-11-24
59-
* The package is now on PyPI
60-
* Require at least beets v1.4.7
61-
* Update album art in alternatives when it changes
62-
* Python 3 support (Python 2.7 continuous to be supported)
63-
* Support the format aliases defined by the convert plugin ('wma' and 'vorbis'
71+
72+
- The package is now on PyPI
73+
- Require at least beets v1.4.7
74+
- Update album art in alternatives when it changes
75+
- Python 3 support (Python 2.7 continuous to be supported)
76+
- Support the format aliases defined by the convert plugin ('wma' and 'vorbis'
6477
with current beets)
65-
* Bugfix: Explicitly write tags after encoding instead of relying on the
78+
- Bugfix: Explicitly write tags after encoding instead of relying on the
6679
encoder to do so
67-
* Bugfix: If the `formats` config option is modified, don't move files if the
80+
- Bugfix: If the `formats` config option is modified, don't move files if the
6881
extension would change, but re-encode
6982

7083
## v0.8.2 - 2015-05-31
71-
* Fix a bug that made the plugin crash when reading unicode strings
84+
85+
- Fix a bug that made the plugin crash when reading unicode strings
7286
from the configuration
7387

7488
## v0.8.1 - 2015-05-30
75-
* Require beets v1.3.13 and drop support for all older versions.
76-
* Embed cover art when converting items
89+
90+
- Require beets v1.3.13 and drop support for all older versions.
91+
- Embed cover art when converting items
7792

7893
## v0.8.0 - 2015-04-14
94+
7995
First proper release

README.md

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ alternatives:
168168
link_type: relative
169169
```
170170

171-
With this config, the `beet alt update by-year` command will create relative symlinks. E.g:
171+
With this config, the `beet alt update by-year` command will create relative
172+
symlinks. E.g:
172173

173174
```plain
174175
/music/by-year/1982/Thriller/Beat It.mp3
@@ -260,8 +261,7 @@ following settings.
260261
* **`albumart_maxwidth`** Downscale the embedded album art to a width
261262
of maximum `albumart_maxwidth` pixels. The aspect ratio of the image
262263
will be preserved. This is comparable to the setting with the same
263-
name of the [convert plugin][convert plugin].
264-
264+
name of the [convert plugin][convert plugin].
265265

266266
* **`removable`** If this is `true` (the default) and `directory` does
267267
not exist, the `update` command will ask you to confirm the creation
@@ -271,6 +271,37 @@ following settings.
271271
**`formats`** is `link`, it sets the type of links to create. For
272272
differences between link types and examples see [Symlink Views](#symlink-views).
273273

274+
Events
275+
------
276+
277+
The plugin emits the `alternatives.item_updated` event after an item (track) is
278+
added, removed or updated in a collection by the `update` command.You can use
279+
the [Hook plugin][] to run a shell command whenever an item is updated.
280+
281+
```yaml
282+
hook:
283+
hooks:
284+
- event: alternatives.item_updated
285+
command: "bash -c 'echo \"{collection}: {action} {item.path}\" >> events.log"
286+
```
287+
288+
Alternatively, you can [listen for events][] from a custom plugin.
289+
290+
The event listener receives the following arguments:
291+
292+
* `collection: str` — name of the collection
293+
* [`item: beets.Item`][Item] — library item the action is taken on
294+
* `path: str` — absolute path of the item in the collection
295+
* `action: str` — type of update action:
296+
* `ADD`: The item is added to the collection and was not present before
297+
* `REMOVE`: The item is removed from the collection
298+
* `MOVE`: The file for an item is moved to a different location in the collection
299+
* `WRITE`: Updated metadata is written to the file in the collection
300+
* `SYNC_ART`: Updated album art is written to the file in the collection
301+
302+
[hook plugin]: https://beets.readthedocs.io/en/stable/plugins/hook.html
303+
[listen for events]: https://beets.readthedocs.io/en/stable/dev/plugins.html#listen-for-events
304+
[Item]: https://beets.readthedocs.io/en/stable/dev/library.html#beets.library.Item
274305

275306
Feature Requests
276307
----------------
@@ -283,7 +314,6 @@ The following is a list of things I might add in the feature.
283314
* Symbolic links for each artist in a multiple artists release (see the
284315
[beets issue][beets-issue-split-symlinks])
285316

286-
287317
License
288318
-------
289319

@@ -307,7 +337,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
307337
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
308338
SOFTWARE.
309339

310-
311340
[beets-docs]: https://beets.readthedocs.io/en/latest/index.html
312341
[beets-issue-split-symlinks]: https://github.com/sampsyo/beets/issues/153
313342
[config-directory]: http://beets.readthedocs.org/en/latest/reference/config.html#directory

beetsplug/alternatives.py

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from typing import Literal, TypeVar
2323

2424
import beets
25+
import beets.plugins
2526
import beetsplug.convert as convert
2627
import confuse
2728
from beets import art, util
@@ -231,11 +232,22 @@ def __init__(self, collection_id: str, config: confuse.ConfigView, lib: Library)
231232

232233

233234
class Action(Enum):
234-
ADD = 1
235-
REMOVE = 2
236-
WRITE = 3
237-
MOVE = 4
238-
SYNC_ART = 5
235+
"""Action to take for a track when syncing a collection"""
236+
237+
#: Track was not present in the collection before and is added
238+
ADD = "ADD"
239+
240+
#: Remove the track from the collection
241+
REMOVE = "REMOVE"
242+
243+
#: Write tags
244+
WRITE = "WRITE"
245+
246+
#: Move the file for an existing track in a collection to a different path.
247+
MOVE = "MOVE"
248+
249+
#: Write album art to the track’s metadata
250+
SYNC_ART = "SYNC_ART"
239251

240252

241253
class External:
@@ -323,13 +335,20 @@ def finalize_converted_item(item: Item, dest: Path):
323335
self._sync_art(item, dest)
324336
self._set_stored_path(item, dest)
325337
item.store()
338+
_send_item_updated(
339+
collection=self._config.collection_id,
340+
path=dest,
341+
item=item,
342+
action=action,
343+
)
326344

327345
converter, converting_done = self._converter()
328346
with converter as converter:
329347
for item, actions in self._items_actions():
330348
dest = self.destination(item)
331349
path = self._get_stored_path(item)
332350
for action in actions:
351+
delay_finalize = False
333352
if action == Action.MOVE:
334353
assert (
335354
path is not None
@@ -358,6 +377,7 @@ def finalize_converted_item(item: Item, dest: Path):
358377
print_(f"+{dest}")
359378
dest.parent.mkdir(exist_ok=True, parents=True)
360379
if self._should_transcode(item):
380+
delay_finalize = True
361381
converter.run(item, dest)
362382
else:
363383
self._log.debug(f"copying {dest}")
@@ -375,6 +395,14 @@ def finalize_converted_item(item: Item, dest: Path):
375395
self._remove_file(item)
376396
item.store()
377397

398+
if not delay_finalize:
399+
_send_item_updated(
400+
collection=self._config.collection_id,
401+
path=dest,
402+
item=item,
403+
action=action,
404+
)
405+
378406
for item, dest in _get_queue_available(converting_done):
379407
finalize_converted_item(item, dest)
380408

@@ -514,17 +542,24 @@ def update(self, create: bool | None = None):
514542
self._remove_file(item)
515543
self._create_symlink(item)
516544
self._set_stored_path(item, dest)
545+
item.store()
517546
elif action == Action.ADD:
518547
print_(f"+{dest}")
519548
self._create_symlink(item)
520549
self._set_stored_path(item, dest)
550+
item.store()
521551
elif action == Action.REMOVE:
522552
assert path is not None # action guarantees that `path` is not none
523553
print_(f"-{path}")
524554
self._remove_file(item)
525-
else:
526-
continue
527-
item.store()
555+
item.store()
556+
557+
_send_item_updated(
558+
collection=self._config.collection_id,
559+
path=dest,
560+
item=item,
561+
action=action,
562+
)
528563

529564
def _create_symlink(self, item: Item):
530565
dest = self.destination(item)
@@ -573,6 +608,16 @@ def _get_queue_available(q: queue.Queue[_T] | queue.SimpleQueue[_T]):
573608
yield item
574609

575610

611+
def _send_item_updated(*, collection: str, path: Path, item: Item, action: Action):
612+
beets.plugins.send(
613+
"alternatives.item_updated",
614+
collection=collection,
615+
path=path,
616+
item=item,
617+
action=action.value,
618+
)
619+
620+
576621
_beets_version = tuple(map(int, beets.__version__.split(".")[0:2]))
577622

578623
if _beets_version >= (2, 3):

poetry.lock

Lines changed: 60 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)