Manual fallback

This commit is contained in:
2026-03-13 20:27:50 -04:00
parent cbd4a8ee30
commit 05654a8203
2 changed files with 118 additions and 21 deletions

View File

@@ -2,12 +2,13 @@ import re
import urllib.parse
import functools
import unicodedata
from PyQt5 import QtWidgets
from PyQt5 import QtWidgets, QtCore
from picard import log, config
from picard.webservice import ratecontrol
from picard.metadata import register_album_metadata_processor
from picard.ui.options import register_options_page, OptionsPage
from picard.ui.itemviews import register_album_action, BaseAction
from picard.album import Album
from picard.metadata import Metadata
@@ -75,6 +76,26 @@ class DiscogsGenreProcessor:
def __init__(self):
self.host = "api.discogs.com"
def _add_discogs_tags(self, metadata: Metadata, genres: list[str], styles: list[str]) -> None:
metadata.delete('genre')
for genre in genres:
metadata.add('genre', genre)
style_tag = config.setting["discogs_style_tag"]
if style_tag is not None and style_tag != "":
tag_name = style_tag or "grouping"
metadata.delete(tag_name)
for style in styles:
metadata.add(tag_name, style)
def _propagate_tags_to_album_tracks(self, album: Album, genres: list[str], styles: list[str]) -> None:
for track in album.tracks:
self._add_discogs_tags(track.metadata, genres, styles)
for file in track.files:
self._add_discogs_tags(file.metadata, genres, styles)
file.update()
track.update()
def _clean_search_text(self, text: str) -> str:
if not text:
return ""
@@ -257,7 +278,7 @@ class DiscogsGenreProcessor:
attempts: list[dict[str, str]],
attempt_index: int,
response,
reply,
_reply,
error,
):
try:
@@ -285,13 +306,8 @@ class DiscogsGenreProcessor:
if valid_result:
genres = valid_result.get('genre', [])
styles = valid_result.get('style', [])
for genre in genres:
metadata.add('genre', genre)
if config.setting["discogs_style_tag"] is not None and config.setting["discogs_style_tag"] != "":
for style in styles:
metadata.add(config.setting["discogs_style_tag"] or "grouping", style)
self._add_discogs_tags(metadata, genres, styles)
finally:
album._requests -= 1
if album._requests == 0:
@@ -328,7 +344,15 @@ class DiscogsGenreProcessor:
return None
def fetch_discogs_tags(self, album: Album, metadata: Metadata, entity_type: str, entity_id: str, token: str):
def fetch_discogs_tags(
self,
album: Album,
metadata: Metadata,
entity_type: str,
entity_id: str,
token: str,
use_album_requests: bool = True,
):
path = f"/{entity_type}s/{entity_id}"
if token:
path += f"?token={token}"
@@ -339,10 +363,10 @@ class DiscogsGenreProcessor:
url=full_url,
parse_response_type="json",
priority=True,
handler=functools.partial(self.handle_tags_response, album, metadata)
handler=functools.partial(self.handle_tags_response, album, metadata, use_album_requests)
)
def handle_tags_response(self, album: Album, metadata: Metadata, response, reply, error):
def handle_tags_response(self, album: Album, metadata: Metadata, use_album_requests: bool, response, reply, error):
try:
if error or not response:
log.error(f"Discogs Tags API failed: {error}")
@@ -351,17 +375,90 @@ class DiscogsGenreProcessor:
genres = response.get('genres', [])
styles = response.get('styles', [])
for genre in genres:
metadata.add('genre', genre)
if config.setting["discogs_style_tag"] is not None and config.setting["discogs_style_tag"] != "":
for style in styles:
metadata.add(config.setting["discogs_style_tag"] or "grouping", style)
self._add_discogs_tags(metadata, genres, styles)
if not use_album_requests:
self._propagate_tags_to_album_tracks(album, genres, styles)
finally:
album._requests -= 1
if album._requests == 0:
if use_album_requests:
if album._requests > 0:
album._requests -= 1
else:
log.warning("Discogs tags response received with no pending album requests")
if use_album_requests and not album.loaded and album._requests == 0:
album._finalize_loading(None)
elif (use_album_requests and album.loaded and album._requests == 0) or not use_album_requests:
album.update()
class DiscogsManualSearchAction(BaseAction):
NAME = "[Discogs] Manual Search"
def __init__(self):
super().__init__()
def callback(self, objs):
albums = [a for a in objs if isinstance(a, Album)]
if not albums:
return
for album in albums:
title = album.metadata.get('album', '') if getattr(album, 'metadata', None) else ''
prompt = "Enter Discogs master/release code"
if title:
prompt = f"{prompt} for \"{title}\""
prompt = f"{prompt}\nFound in the top right corner of the page, example: [m123456] or [r123456]"
parent = QtWidgets.QApplication.activeWindow()
input_dialog = QtWidgets.QInputDialog(parent)
input_dialog.setWindowTitle("Discogs Manual Search")
input_dialog.setLabelText(prompt)
input_dialog.setInputMode(QtWidgets.QInputDialog.TextInput)
input_dialog.setTextValue("")
def focus_input() -> None:
line_edit = input_dialog.findChild(QtWidgets.QLineEdit)
if line_edit:
line_edit.setFocus()
QtCore.QTimer.singleShot(0, focus_input)
ok = input_dialog.exec_()
value = input_dialog.textValue()
if not ok or not value.strip():
continue
value = value.strip()
match = re.match(r'^\[[mMrR](\d+)\]$', value)
if match:
value = value[1:-1]
if not match:
QtWidgets.QMessageBox.warning(
parent,
"Invalid code",
"Please enter a valid Discogs master/release code, example: [m123456] or [r123456]",
)
continue
entity_id = match.group(1)
entity_type = 'master' if value.strip().lower().startswith('m') else 'release'
token = (config.setting["discogs_personal_access_token"] or "").strip()
DiscogsGenreProcessor().fetch_discogs_tags(
album,
album.metadata,
entity_type,
entity_id,
token,
use_album_requests=False,
)
register_options_page(DiscogsGenreOptionsPage)
register_album_action(DiscogsManualSearchAction())
register_album_metadata_processor(DiscogsGenreProcessor().process_album)

View File

@@ -4,7 +4,7 @@ from picard.config import TextOption, IntOption, Option
PLUGIN_NAME = "Discogs Genre & Style"
PLUGIN_AUTHOR = "cy1der"
PLUGIN_DESCRIPTION = "Fetches genres and styles from Discogs"
PLUGIN_VERSION = "1.0.2"
PLUGIN_VERSION = "1.0.3"
PLUGIN_API_VERSIONS = ["2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "2.8", "2.9", "2.10", "2.11", "2.12", "2.13"]
PLUGIN_LICENSE = "GPL-2.0-or-later"
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html"