|
15 | 15 | """Adds pseudo-releases from MusicBrainz as candidates during import."""
|
16 | 16 |
|
17 | 17 | import itertools
|
18 |
| -from typing import Iterable, Sequence |
| 18 | +from typing import Any, Iterable, Sequence |
19 | 19 |
|
20 | 20 | from typing_extensions import override
|
21 | 21 |
|
22 | 22 | import beetsplug.musicbrainz as mbplugin # avoid implicit loading of main plugin
|
23 | 23 | from beets.autotag import AlbumInfo, Distance
|
24 | 24 | from beets.autotag.distance import distance
|
25 |
| -from beets.autotag.hooks import V, TrackInfo |
| 25 | +from beets.autotag.hooks import TrackInfo |
26 | 26 | from beets.autotag.match import assign_items
|
27 | 27 | from beets.library import Item
|
28 | 28 | from beets.metadata_plugins import MetadataSourcePlugin
|
@@ -78,12 +78,10 @@ def _intercept_mb_releases(self, data: JSONDict):
|
78 | 78 | ):
|
79 | 79 | return None
|
80 | 80 |
|
81 |
| - pseudo_release_ids = ( |
82 |
| - self._wanted_pseudo_release_id(rel) |
83 |
| - for rel in data.get("release-relation-list", []) |
84 |
| - ) |
85 | 81 | pseudo_release_ids = [
|
86 |
| - rel for rel in pseudo_release_ids if rel is not None |
| 82 | + pr_id |
| 83 | + for rel in data.get("release-relation-list", []) |
| 84 | + if (pr_id := self._wanted_pseudo_release_id(rel)) is not None |
87 | 85 | ]
|
88 | 86 |
|
89 | 87 | if len(pseudo_release_ids) > 0:
|
@@ -139,10 +137,12 @@ def candidates(
|
139 | 137 | album: str,
|
140 | 138 | va_likely: bool,
|
141 | 139 | ) -> Iterable[AlbumInfo]:
|
142 |
| - """Even though a candidate might have extra and/or missing tracks, the set of paths from the items that |
143 |
| - were actually matched (which are stored in the corresponding ``mapping``) must be a subset of the set of |
144 |
| - paths from the input items. This helps us figure out which intercepted candidate might be relevant for |
145 |
| - the items we get in this call even if other candidates have been concurrently intercepted as well. |
| 140 | + """Even though a candidate might have extra and/or missing tracks, the set of |
| 141 | + paths from the items that were actually matched (which are stored in the |
| 142 | + corresponding ``mapping``) must be a subset of the set of paths from the input |
| 143 | + items. This helps us figure out which intercepted candidate might be relevant |
| 144 | + for the items we get in this call even if other candidates have been |
| 145 | + concurrently intercepted as well. |
146 | 146 | """
|
147 | 147 |
|
148 | 148 | if len(self._scripts) == 0:
|
@@ -256,19 +256,26 @@ def _mb_plugin_simulation_matched(
|
256 | 256 | items: Sequence[Item],
|
257 | 257 | official_candidates: list[AlbumInfo],
|
258 | 258 | ) -> bool:
|
259 |
| - """Simulate how we would have been called if the MusicBrainz plugin had actually executed. |
| 259 | + """Simulate how we would have been called if the MusicBrainz plugin had actually |
| 260 | + executed. |
260 | 261 |
|
261 | 262 | At this point we already called ``self._mb.candidates()``,
|
262 | 263 | which emits the ``mb_album_extract`` events,
|
263 | 264 | so now we simulate:
|
264 | 265 |
|
265 |
| - 1. Intercepting the ``AlbumInfo`` candidate that would have come in the ``albuminfo_received`` event. |
266 |
| - 2. Intercepting the distance calculation of the aforementioned candidate to store its mapping. |
| 266 | + 1. Intercepting the ``AlbumInfo`` candidate that would have come in the |
| 267 | + ``albuminfo_received`` event. |
| 268 | + 2. Intercepting the distance calculation of the aforementioned candidate to |
| 269 | + store its mapping. |
| 270 | +
|
| 271 | + If the official candidate is already a pseudo-release, we clean up internal |
| 272 | + state. This is needed because the MusicBrainz plugin emits official releases |
| 273 | + even if it receives a pseudo-release as input, so the chain would actually be: |
267 | 274 |
|
268 |
| - If the official candidate is already a pseudo-release, we clean up internal state. |
269 |
| - This is needed because the MusicBrainz plugin emits official releases even if |
270 |
| - it receives a pseudo-release as input, so the chain would actually be: |
271 |
| - pseudo-release input -> official release with pseudo emitted -> intercepted -> pseudo-release resolved (again) |
| 275 | + pseudo-release input -> |
| 276 | + official release with pseudo emitted -> |
| 277 | + intercepted -> |
| 278 | + pseudo-release resolved (again) |
272 | 279 |
|
273 | 280 | To avoid resolving again in the last step, we remove the pseudo-release's id.
|
274 | 281 | """
|
@@ -313,28 +320,30 @@ def album_distance(
|
313 | 320 | album_info: AlbumInfo,
|
314 | 321 | mapping: dict[Item, TrackInfo],
|
315 | 322 | ) -> Distance:
|
316 |
| - """We use this function more like a listener for the extra details we are injecting. |
| 323 | + """We use this function more like a listener for the extra details we are |
| 324 | + injecting. |
317 | 325 |
|
318 | 326 | For instances of ``PseudoAlbumInfo`` whose corresponding ``mapping`` is _not_ an
|
319 |
| - instance of ``ImmutableMapping``, we know at this point that all penalties from the |
320 |
| - normal auto-tagging flow have been applied, so we can switch to the metadata from |
321 |
| - the pseudo-release for the final proposal. |
| 327 | + instance of ``ImmutableMapping``, we know at this point that all penalties from |
| 328 | + the normal auto-tagging flow have been applied, so we can switch to the metadata |
| 329 | + from the pseudo-release for the final proposal. |
322 | 330 |
|
323 |
| - Other instances of ``AlbumInfo`` must come from other plugins, so we just check if |
324 |
| - we intercepted them as candidates with pseudo-releases and store their ``mapping``. |
325 |
| - This is needed because the real listeners we use never expose information from the |
326 |
| - input ``Item``s, so we intercept that here. |
| 331 | + Other instances of ``AlbumInfo`` must come from other plugins, so we just check |
| 332 | + if we intercepted them as candidates with pseudo-releases and store their |
| 333 | + ``mapping``. This is needed because the real listeners we use never expose |
| 334 | + information from the input ``Item``s, so we intercept that here. |
327 | 335 |
|
328 | 336 | The paths from the items are used to figure out which pseudo-releases should be
|
329 | 337 | provided for them, which is specially important for concurrent stage execution
|
330 |
| - where we might have intercepted releases from different import tasks when we run. |
| 338 | + where we might have already intercepted releases from different import tasks |
| 339 | + when we run. |
331 | 340 | """
|
332 | 341 |
|
333 | 342 | if isinstance(album_info, PseudoAlbumInfo):
|
334 | 343 | if not isinstance(mapping, ImmutableMapping):
|
335 | 344 | self._log.debug(
|
336 |
| - "Switching {0.album_id} to pseudo-release source for final proposal", |
337 |
| - album_info, |
| 345 | + "Switching {0} to pseudo-release source for final proposal", |
| 346 | + album_info.album_id, |
338 | 347 | )
|
339 | 348 | album_info.use_pseudo_as_ref()
|
340 | 349 | new_mappings, _, _ = assign_items(items, album_info.tracks)
|
@@ -364,14 +373,16 @@ def item_candidates(
|
364 | 373 | class PseudoAlbumInfo(AlbumInfo):
|
365 | 374 | """This is a not-so-ugly hack.
|
366 | 375 |
|
367 |
| - We want the pseudo-release to result in a distance that is lower or equal to that of the official release, |
368 |
| - otherwise it won't qualify as a good candidate. However, if the input is in a script that's different from |
369 |
| - the pseudo-release (and we want to translate/transliterate it in the library), it will receive unwanted penalties. |
| 376 | + We want the pseudo-release to result in a distance that is lower or equal to that of |
| 377 | + the official release, otherwise it won't qualify as a good candidate. However, if |
| 378 | + the input is in a script that's different from the pseudo-release (and we want to |
| 379 | + translate/transliterate it in the library), it will receive unwanted penalties. |
370 | 380 |
|
371 |
| - This class is essentially a view of the ``AlbumInfo`` of both official and pseudo-releases, |
372 |
| - where it's possible to change the details that are exposed to other parts of the auto-tagger, |
373 |
| - enabling a "fair" distance calculation based on the current input's script but still preferring |
374 |
| - the translation/transliteration in the final proposal. |
| 381 | + This class is essentially a view of the ``AlbumInfo`` of both official and |
| 382 | + pseudo-releases, where it's possible to change the details that are exposed to other |
| 383 | + parts of the auto-tagger, enabling a "fair" distance calculation based on the |
| 384 | + current input's script but still preferring the translation/transliteration in the |
| 385 | + final proposal. |
375 | 386 | """
|
376 | 387 |
|
377 | 388 | def __init__(
|
@@ -411,8 +422,8 @@ def use_pseudo_as_ref(self):
|
411 | 422 | def use_official_as_ref(self):
|
412 | 423 | self.__dict__["_pseudo_source"] = False
|
413 | 424 |
|
414 |
| - def __getattr__(self, attr: str) -> V: |
415 |
| - # ensure we don't duplicate an official release's id by always returning pseudo's |
| 425 | + def __getattr__(self, attr: str) -> Any: |
| 426 | + # ensure we don't duplicate an official release's id, always return pseudo's |
416 | 427 | if self.__dict__["_pseudo_source"] or attr == "album_id":
|
417 | 428 | return super().__getattr__(attr)
|
418 | 429 | else:
|
|
0 commit comments