@@ -156,7 +156,7 @@ def setup(self):
156156 if bl_filename :
157157 bl_filename = normpath (bl_filename )
158158 try :
159- with codecs .open (bl_filename , 'r' , encoding = ' utf-8' ) as f :
159+ with codecs .open (bl_filename , "r" , encoding = " utf-8" ) as f :
160160 self .blacklist = yaml .safe_load (f )
161161 self ._log .debug ("Loaded genre blacklist from {0}" , bl_filename )
162162 except Exception as exc :
@@ -265,10 +265,19 @@ def fetch_genre(self, lastfm_obj):
265265 min_weight = self .config ["min_weight" ].get (int )
266266 return self ._tags_for (lastfm_obj , min_weight )
267267
268- def _filter_valid_genres (self , genres : list [str ]) -> list [str ]:
269- """Filter list of genres, only keep valid."""
268+ def _filter_valid_genres (
269+ self , genres : list [str ], artist : str = None
270+ ) -> list [str ]:
271+ """Filter list of genres, only keep valid.
272+ First applies blacklist filtering if enabled, then whitelist filtering."""
270273 if not genres :
271274 return []
275+
276+ # First remove forbidden genres if we have an artist
277+ if artist :
278+ genres = [g for g in genres if not self ._is_forbidden (g , artist )]
279+
280+ # Then apply whitelist filter
272281 return [x for x in genres if self ._is_valid (x )]
273282
274283 def _is_valid (self , genre : str ) -> bool :
@@ -281,6 +290,33 @@ def _is_valid(self, genre: str) -> bool:
281290 return True
282291 return False
283292
293+ def _is_forbidden (self , genre : str , artist : str ) -> bool :
294+ """Return True if the genre is forbidden for the artist.
295+
296+ Supports a special '*' key in the blacklist YAML file for
297+ global forbidden genres.
298+
299+ Example:
300+ "Artist Name":
301+ - "pop"
302+ "*":
303+ - "spoken word"
304+ """
305+ if not self .blacklist :
306+ return False
307+
308+ forbidden = set ()
309+ # Add global forbidden genres
310+ if "*" in self .blacklist :
311+ forbidden .update (g .lower () for g in self .blacklist ["*" ] or [])
312+ # Add artist-specific forbidden genres
313+ if artist :
314+ for bl_artist , blocked_genres in self .blacklist .items ():
315+ if bl_artist != "*" and bl_artist .lower () == artist .lower ():
316+ forbidden .update (g .lower () for g in blocked_genres or [])
317+
318+ return genre .lower () in forbidden
319+
284320 # Cached last.fm entity lookups.
285321
286322 def _last_lookup (self , entity , method , * args ):
@@ -311,25 +347,29 @@ def fetch_album_genre(self, obj):
311347 return self ._filter_valid_genres (
312348 self ._last_lookup (
313349 "album" , LASTFM .get_album , obj .albumartist , obj .album
314- )
350+ ),
351+ obj .albumartist ,
315352 )
316353
317354 def fetch_album_artist_genre (self , obj ):
318355 """Return the album artist genre for this Item or Album."""
319356 return self ._filter_valid_genres (
320- self ._last_lookup ("artist" , LASTFM .get_artist , obj .albumartist )
357+ self ._last_lookup ("artist" , LASTFM .get_artist , obj .albumartist ),
358+ obj .albumartist ,
321359 )
322360
323361 def fetch_artist_genre (self , item ):
324362 """Returns the track artist genre for this Item."""
325363 return self ._filter_valid_genres (
326- self ._last_lookup ("artist" , LASTFM .get_artist , item .artist )
364+ self ._last_lookup ("artist" , LASTFM .get_artist , item .artist ),
365+ item .artist ,
327366 )
328367
329368 def fetch_track_genre (self , obj ):
330369 """Returns the track genre for this Item."""
331370 return self ._filter_valid_genres (
332- self ._last_lookup ("track" , LASTFM .get_track , obj .artist , obj .title )
371+ self ._last_lookup ("track" , LASTFM .get_track , obj .artist , obj .title ),
372+ obj .artist ,
333373 )
334374
335375 # Main processing: _get_genre() and helpers.
0 commit comments