99
1010import abc
1111import re
12- import warnings
12+ from functools import cache , cached_property
1313from typing import TYPE_CHECKING , Generic , Literal , Sequence , TypedDict , TypeVar
1414
1515import unidecode
16+ from confuse import NotFoundError
1617from typing_extensions import NotRequired
1718
1819from beets .util import cached_classproperty
2324if TYPE_CHECKING :
2425 from collections .abc import Iterable
2526
26- from confuse import ConfigView
27-
28- from .autotag import Distance
2927 from .autotag .hooks import AlbumInfo , Item , TrackInfo
3028
3129
30+ @cache
3231def find_metadata_source_plugins () -> list [MetadataSourcePlugin ]:
33- """Returns a list of MetadataSourcePlugin subclass instances
34-
35- Resolved from all currently loaded beets plugins.
36- """
37-
38- all_plugins = find_plugins ()
39- metadata_plugins : list [MetadataSourcePlugin | BeetsPlugin ] = []
40- for plugin in all_plugins :
41- if isinstance (plugin , MetadataSourcePlugin ):
42- metadata_plugins .append (plugin )
43- elif hasattr (plugin , "data_source" ):
44- # TODO: Remove this in the future major release, v3.0.0
45- warnings .warn (
46- f"{ plugin .__class__ .__name__ } is used as a legacy metadata source. "
47- "It should extend MetadataSourcePlugin instead of BeetsPlugin. "
48- "Support for this will be removed in the v3.0.0 release!" ,
49- DeprecationWarning ,
50- stacklevel = 2 ,
51- )
52- metadata_plugins .append (plugin )
53-
54- # typeignore: BeetsPlugin is not a MetadataSourcePlugin (legacy support)
55- return metadata_plugins # type: ignore[return-value]
32+ """Return a list of all loaded metadata source plugins."""
33+ # TODO: Make this an isinstance(MetadataSourcePlugin, ...) check in v3.0.0
34+ return [p for p in find_plugins () if hasattr (p , "data_source" )] # type: ignore[misc]
5635
5736
5837@notify_info_yielded ("albuminfo_received" )
@@ -95,46 +74,17 @@ def track_for_id(_id: str) -> TrackInfo | None:
9574 return None
9675
9776
98- def track_distance (item : Item , info : TrackInfo ) -> Distance :
99- """Returns the track distance for an item and trackinfo.
100-
101- Returns a Distance object is populated by all metadata source plugins
102- that implement the :py:meth:`MetadataSourcePlugin.track_distance` method.
103- """
104- from beets .autotag .distance import Distance
105-
106- dist = Distance ()
107- for plugin in find_metadata_source_plugins ():
108- dist .update (plugin .track_distance (item , info ))
109- return dist
110-
111-
112- def album_distance (
113- items : Sequence [Item ],
114- album_info : AlbumInfo ,
115- mapping : dict [Item , TrackInfo ],
116- ) -> Distance :
117- """Returns the album distance calculated by plugins."""
118- from beets .autotag .distance import Distance
119-
120- dist = Distance ()
121- for plugin in find_metadata_source_plugins ():
122- dist .update (plugin .album_distance (items , album_info , mapping ))
123- return dist
124-
125-
126- def _get_distance (
127- config : ConfigView , data_source : str , info : AlbumInfo | TrackInfo
128- ) -> Distance :
129- """Returns the ``data_source`` weight and the maximum source weight
130- for albums or individual tracks.
131- """
132- from beets .autotag .distance import Distance
133-
134- dist = Distance ()
135- if info .data_source == data_source :
136- dist .add ("source" , config ["source_weight" ].as_number ())
137- return dist
77+ @cache
78+ def get_penalty (data_source : str | None ) -> float :
79+ """Get the penalty value for the given data source."""
80+ return next (
81+ (
82+ p .data_source_mismatch_penalty
83+ for p in find_metadata_source_plugins ()
84+ if p .data_source == data_source
85+ ),
86+ MetadataSourcePlugin .DEFAULT_DATA_SOURCE_MISMATCH_PENALTY ,
87+ )
13888
13989
14090class MetadataSourcePlugin (BeetsPlugin , metaclass = abc .ABCMeta ):
@@ -145,12 +95,29 @@ class MetadataSourcePlugin(BeetsPlugin, metaclass=abc.ABCMeta):
14595 and tracks, and to retrieve album and track information by ID.
14696 """
14797
98+ DEFAULT_DATA_SOURCE_MISMATCH_PENALTY = 0.5
99+
100+ @cached_classproperty
101+ def data_source (cls ) -> str :
102+ """The data source name for this plugin.
103+
104+ This is inferred from the plugin name.
105+ """
106+ return cls .__name__ .replace ("Plugin" , "" ) # type: ignore[attr-defined]
107+
108+ @cached_property
109+ def data_source_mismatch_penalty (self ) -> float :
110+ try :
111+ return self .config ["source_weight" ].as_number ()
112+ except NotFoundError :
113+ return self .config ["data_source_mismatch_penalty" ].as_number ()
114+
148115 def __init__ (self , * args , ** kwargs ) -> None :
149116 super ().__init__ (* args , ** kwargs )
150117 self .config .add (
151118 {
152119 "search_limit" : 5 ,
153- "source_weight " : 0.5 ,
120+ "data_source_mismatch_penalty " : self . DEFAULT_DATA_SOURCE_MISMATCH_PENALTY , # noqa: E501
154121 }
155122 )
156123
@@ -224,35 +191,6 @@ def tracks_for_ids(self, ids: Sequence[str]) -> Iterable[TrackInfo | None]:
224191
225192 return (self .track_for_id (id ) for id in ids )
226193
227- def album_distance (
228- self ,
229- items : Sequence [Item ],
230- album_info : AlbumInfo ,
231- mapping : dict [Item , TrackInfo ],
232- ) -> Distance :
233- """Calculate the distance for an album based on its items and album info."""
234- return _get_distance (
235- data_source = self .data_source , info = album_info , config = self .config
236- )
237-
238- def track_distance (
239- self ,
240- item : Item ,
241- info : TrackInfo ,
242- ) -> Distance :
243- """Calculate the distance for a track based on its item and track info."""
244- return _get_distance (
245- data_source = self .data_source , info = info , config = self .config
246- )
247-
248- @cached_classproperty
249- def data_source (cls ) -> str :
250- """The data source name for this plugin.
251-
252- This is inferred from the plugin name.
253- """
254- return cls .__name__ .replace ("Plugin" , "" ) # type: ignore[attr-defined]
255-
256194 def _extract_id (self , url : str ) -> str | None :
257195 """Extract an ID from a URL for this metadata source plugin.
258196
0 commit comments