lastgenre: File loaders module

This commit is contained in:
J0J0 Todos 2026-02-07 18:46:52 +01:00
parent 18b6d30b49
commit ae7e265fd8
2 changed files with 140 additions and 51 deletions

View file

@ -24,18 +24,16 @@ https://gist.github.com/1241307
from __future__ import annotations
import os
from functools import singledispatchmethod
from pathlib import Path
from typing import TYPE_CHECKING, Any
import yaml
from beets import config, library, plugins, ui
from beets.library import Album, Item
from beets.util import plurality, unique_list
from .client import LastFmClient
from .loaders import DataFileLoader
from .utils import make_tunelog
if TYPE_CHECKING:
@ -81,12 +79,6 @@ def find_parents(candidate: str, branches: list[list[str]]) -> list[str]:
return [candidate]
# Main plugin logic.
WHITELIST = os.path.join(os.path.dirname(__file__), "genres.txt")
C14N_TREE = os.path.join(os.path.dirname(__file__), "genres-tree.yaml")
class LastGenrePlugin(plugins.BeetsPlugin):
def __init__(self) -> None:
super().__init__()
@ -119,49 +111,13 @@ class LastGenrePlugin(plugins.BeetsPlugin):
self.client = LastFmClient(
self._log, self.config["min_weight"].get(int)
)
self.whitelist = self._load_whitelist()
self.c14n_branches, self.canonicalize = self._load_c14n_tree()
def _load_whitelist(self) -> set[str]:
"""Load the whitelist from a text file.
Default whitelist is used if config is True, empty string or set to "nothing".
"""
whitelist = set()
wl_filename = self.config["whitelist"].get()
if wl_filename in (True, "", None): # Indicates the default whitelist.
wl_filename = WHITELIST
if wl_filename:
self._log.debug("Loading whitelist {}", wl_filename)
text = Path(wl_filename).expanduser().read_text(encoding="utf-8")
for line in text.splitlines():
if (line := line.strip().lower()) and not line.startswith("#"):
whitelist.add(line)
return whitelist
def _load_c14n_tree(self) -> tuple[list[list[str]], bool]:
"""Load the canonicalization tree from a YAML file.
Default tree is used if config is True, empty string, set to "nothing"
or if prefer_specific is enabled.
"""
c14n_branches: list[list[str]] = []
c14n_filename = self.config["canonical"].get()
canonicalize = c14n_filename is not False
# Default tree
if c14n_filename in (True, "", None) or (
# prefer_specific requires a tree, load default tree
not canonicalize and self.config["prefer_specific"].get()
):
c14n_filename = C14N_TREE
# Read the tree
if c14n_filename:
self._log.debug("Loading canonicalization tree {}", c14n_filename)
with Path(c14n_filename).expanduser().open(encoding="utf-8") as f:
genres_tree = yaml.safe_load(f)
flatten_tree(genres_tree, [], c14n_branches)
return c14n_branches, canonicalize
loader = DataFileLoader.from_config(
self.config, self._log, Path(__file__).parent, flatten_tree
)
self.whitelist = loader.whitelist
self.c14n_branches = loader.c14n_branches
self.canonicalize = loader.canonicalize
@property
def sources(self) -> tuple[str, ...]:

View file

@ -0,0 +1,133 @@
# This file is part of beets.
# Copyright 2016, Adrian Sampson.
# Copyright 2026, J0J0 Todos.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
"""Data file loaders for the lastgenre plugin."""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
import yaml
if TYPE_CHECKING:
from confuse import ConfigView
from beets.logging import Logger
class DataFileLoader:
"""Loads genre-related data files for the lastgenre plugin."""
def __init__(
self,
log: Logger,
plugin_dir: Path,
whitelist: set[str],
c14n_branches: list[list[str]],
canonicalize: bool,
):
"""Initialize with pre-loaded data.
Use from_config() classmethod to construct from plugin config.
"""
self._log = log
self._plugin_dir = plugin_dir
self.whitelist = whitelist
self.c14n_branches = c14n_branches
self.canonicalize = canonicalize
@classmethod
def from_config(
cls,
config: ConfigView,
log: Logger,
plugin_dir: Path,
flatten_func: Callable,
) -> DataFileLoader:
"""Create a DataFileLoader from plugin configuration.
Reads config values and loads all data files during construction.
"""
# Default paths
default_whitelist = str(plugin_dir / "genres.txt")
default_tree = str(plugin_dir / "genres-tree.yaml")
# Load whitelist
whitelist = cls._load_whitelist(
log, config["whitelist"].get(), default_whitelist
)
# Load tree
c14n_branches, canonicalize = cls._load_tree(
log,
config["canonical"].get(),
default_tree,
config["prefer_specific"].get(),
flatten_func,
)
return cls(log, plugin_dir, whitelist, c14n_branches, canonicalize)
@staticmethod
def _load_whitelist(
log: Logger, config_value: str | bool | None, default_path: str
) -> set[str]:
"""Load the whitelist from a text file.
Returns set of valid genre names (lowercase).
"""
whitelist = set()
wl_filename = config_value
if wl_filename in (True, "", None): # Indicates the default whitelist.
wl_filename = default_path
if wl_filename:
log.debug("Loading whitelist {}", wl_filename)
text = Path(wl_filename).expanduser().read_text(encoding="utf-8")
for line in text.splitlines():
if (line := line.strip().lower()) and not line.startswith("#"):
whitelist.add(line)
return whitelist
@staticmethod
def _load_tree(
log: Logger,
config_value: str | bool | None,
default_path: str,
prefer_specific: bool,
flatten_func: Callable,
) -> tuple[list[list[str]], bool]:
"""Load the canonicalization tree from a YAML file.
Returns tuple of (branches, canonicalize_enabled).
"""
c14n_branches: list[list[str]] = []
c14n_filename = config_value
canonicalize = c14n_filename is not False
# Default tree
if c14n_filename in (True, "", None) or (
# prefer_specific requires a tree, load default tree
not canonicalize and prefer_specific
):
c14n_filename = default_path
# Read the tree
if c14n_filename:
log.debug("Loading canonicalization tree {}", c14n_filename)
with Path(c14n_filename).expanduser().open(encoding="utf-8") as f:
genres_tree = yaml.safe_load(f)
flatten_func(genres_tree, [], c14n_branches)
return c14n_branches, canonicalize