diff --git a/.gitignore b/.gitignore index b6e4761..b314a90 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,9 @@ dmypy.json # Pyre type checker .pyre/ + +# pyright +pyrightconfig.json + +# typestubs +typings diff --git a/README.md b/README.md index 6c4e5fb..ce6e49f 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ # beets-vocadb -Plugin for beets to use VocaDB, UtaiteDB and TouhouDB as an autotagger source. +Plugins for beets to use VocaDB, UtaiteDB and TouhouDB as autotagger sources. ## Installation ```Shell -pip install git+https://github.com/prTopi/beets-vocadb +pip install git+https://github.com/amogus07/beets-vocadb ``` or, if you use [pipx](https://pipx.pypa.io): ```Shell -pipx inject beets git+https://github.com/prTopi/beets-vocadb +pipx inject beets git+https://github.com/amogus07/beets-vocadb ``` -This Plugin has 3 components: vocadb, utaitedb and touhoudb. +This repository contains 3 plugins: vocadb, utaitedb and touhoudb. To enable them, add them to the plugin section of your beets config file: ```yaml @@ -24,9 +24,15 @@ plugins: - touhoudb ``` +## Subcommands + +Each plugin adds a subcommand to beets that works similarly to the mbsync command. +vocadb adds `vdbsync`, utaitedb adds `udbsync` and touhoudb adds `tdbsync`. +For usage information run `beet [subcommand] -h`. + ## Configuration -The plugin uses beets default language list to determine which language to use +The plugins use beets default language list to determine which language to use for tags. ```yaml diff --git a/beetsplug/touhoudb.py b/beetsplug/touhoudb.py index ab225a3..91e0088 100644 --- a/beetsplug/touhoudb.py +++ b/beetsplug/touhoudb.py @@ -1,9 +1,11 @@ -from beetsplug.vocadb import VocaDBPlugin +from beetsplug.vocadb import VocaDBPlugin, VocaDBInstance class TouhouDBPlugin(VocaDBPlugin): def __init__(self): super().__init__() self.data_source = "TouhouDB" - self.base_url = "https://touhoudb.com/" - self.api_url = "https://touhoudb.com/api/" - self.subcommand = "tdbsync" + self.instance = VocaDBInstance( + base_url = "https://touhoudb.com/", + api_url = "https://touhoudb.com/api/", + subcommand = "tdbsync" + ) diff --git a/beetsplug/utaitedb.py b/beetsplug/utaitedb.py index 4e62141..9733f5c 100644 --- a/beetsplug/utaitedb.py +++ b/beetsplug/utaitedb.py @@ -1,9 +1,11 @@ -from beetsplug.vocadb import VocaDBPlugin +from beetsplug.vocadb import VocaDBPlugin, VocaDBInstance class UtaiteDBPlugin(VocaDBPlugin): def __init__(self): super().__init__() self.data_source = "UtaiteDB" - self.base_url = "https://utaiteadb.net/" - self.api_url = "https://utaitedb.net/api/" - self.subcommand = "udbsync" + self.instance = VocaDBInstance( + base_url = "https://utaiteadb.net/", + api_url = "https://utaitedb.net/api/", + subcommand = "udbsync" + ) diff --git a/beetsplug/vocadb.py b/beetsplug/vocadb.py index 48c214d..087b9f9 100644 --- a/beetsplug/vocadb.py +++ b/beetsplug/vocadb.py @@ -1,6 +1,7 @@ from datetime import datetime from json import load from re import match, search +from typing import NamedTuple from urllib.error import HTTPError from urllib.parse import quote, urljoin from urllib.request import Request, urlopen @@ -10,16 +11,25 @@ from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.plugins import BeetsPlugin, apply_item_changes, get_distance +USER_AGENT = f"beets/{beets.__version__} +https://beets.io/" +HEADERS = {"accept": "application/json", "User-Agent": USER_AGENT} + + +class VocaDBInstance(NamedTuple): + base_url: str + api_url: str + subcommand: str + class VocaDBPlugin(BeetsPlugin): def __init__(self): super().__init__() self.data_source = "VocaDB" - self.base_url = "https://vocadb.net/" - self.api_url = "https://vocadb.net/api/" - self.subcommand = "vdbsync" - self.user_agent = f"beets/{beets.__version__} +https://beets.io/" - self.headers = {"accept": "application/json", "User-Agent": self.user_agent} + self.instance = VocaDBInstance( + base_url = "https://vocadb.net/", + api_url = "https://vocadb.net/api/", + subcommand = "vdbsync" + ) self.config.add( { "source_weight": 0.5, @@ -29,7 +39,7 @@ def __init__(self): ) def commands(self): - cmd = ui.Subcommand(self.subcommand, help=f"update metadata from {self.data_source}") + cmd = ui.Subcommand(self.instance.subcommand, help=f"update metadata from {self.data_source}") cmd.parser.add_option( "-p", "--pretend", @@ -146,14 +156,14 @@ def albums(self, lib, query, move, pretend, write): track_id, album_formatted ) - + if missing_tracks: self._log.warning( "The following track IDs were missing in the VocaDB album info for {0}: {1}", album_formatted, ', '.join(missing_tracks) ) - + self._log.debug("applying changes to {}", album_formatted) with lib.transaction(): autotag.apply_metadata(album_info, mapping) @@ -194,10 +204,10 @@ def album_distance(self, items, album_info, mapping): def candidates(self, items, artist, album, va_likely, extra_tags=None): self._log.debug("Searching for album {0}", album) url = urljoin( - self.api_url, + self.instance.api_url, f"albums/?query={quote(album)}&maxResults=5&nameMatchMode=Auto", ) - request = Request(url, headers=self.headers) + request = Request(url, headers=HEADERS) try: with urlopen(request) as result: if result: @@ -216,13 +226,13 @@ def item_candidates(self, item, artist, title): self._log.debug("Searching for track {0}", item) language = self.get_lang(config["import"]["languages"].as_str_seq()) url = urljoin( - self.api_url, + self.instance.api_url, f"songs/?query={quote(title)}" + f"&fields={self.get_song_fields()}" + f"&lang={language}" + "&maxResults=5&sort=SongType&preferAccurateMatches=true&nameMatchMode=Auto", ) - request = Request(url, headers=self.headers) + request = Request(url, headers=HEADERS) try: with urlopen(request) as result: if result: @@ -243,13 +253,13 @@ def album_for_id(self, album_id): self._log.debug("Searching for album {0}", album_id) language = self.get_lang(config["import"]["languages"].as_str_seq()) url = urljoin( - self.api_url, + self.instance.api_url, f"albums/{album_id}" + "?fields=Artists,Discs,Tags,Tracks,WebLinks" + f"&songFields={self.get_song_fields()}" + f"&lang={language}", ) - request = Request(url, headers=self.headers) + request = Request(url, headers=HEADERS) try: with urlopen(request) as result: if result: @@ -266,12 +276,12 @@ def track_for_id(self, track_id): self._log.debug("Searching for track {0}", track_id) language = self.get_lang(config["import"]["languages"].as_str_seq()) url = urljoin( - self.api_url, + self.instance.api_url, f"songs/{track_id}" + f"?fields={self.get_song_fields()}" + f"&lang={language}", ) - request = Request(url, headers=self.headers) + request = Request(url, headers=HEADERS) try: with urlopen(request) as result: if result: @@ -360,7 +370,7 @@ def album_info(self, release, search_lang=None): media = release["discs"][0]["name"] except IndexError: media = None - data_url = urljoin(self.base_url, f"Al/{album_id}") + data_url = urljoin(self.instance.base_url, f"Al/{album_id}") return AlbumInfo( album=album, album_id=album_id, @@ -417,7 +427,7 @@ def track_info( composer = ", ".join(artist_categories["composers"]) lyricist = ", ".join(artist_categories["lyricists"]) length = recording.get("lengthSeconds", 0) - data_url = urljoin(self.base_url, f"S/{track_id}") + data_url = urljoin(self.instance.base_url, f"S/{track_id}") bpm = str(recording.get("maxMilliBpm", 0) // 1000) genre = self.get_genres(recording) script, language, lyrics = self.get_lyrics(