Compare commits
6 Commits
d873570d84
...
apiv2
| Author | SHA1 | Date | |
|---|---|---|---|
|
f790196ac5
|
|||
|
e1d44659b8
|
|||
|
e3abb64135
|
|||
|
7f6964a5f4
|
|||
|
49ae56b3d3
|
|||
|
27e46330cd
|
105
__init__.py
105
__init__.py
@@ -2,67 +2,102 @@ from picard import config, log
|
|||||||
from picard.ui.options import OptionsPage, register_options_page
|
from picard.ui.options import OptionsPage, register_options_page
|
||||||
from picard.metadata import register_track_metadata_processor
|
from picard.metadata import register_track_metadata_processor
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
|
import re
|
||||||
|
|
||||||
from .constants import *
|
from .constants import *
|
||||||
|
|
||||||
def process_track(_, metadata, track, __):
|
def process_track(_, metadata, track, __):
|
||||||
disambiguation = track["recording"]["disambiguation"] if "disambiguation" in track["recording"] else ""
|
disambiguation = track["recording"]["disambiguation"] if "recording" in track and "disambiguation" in track["recording"] else track["disambiguation"] if "disambiguation" in track else ""
|
||||||
album_disambiguation = metadata["~releasecomment"] if "~releasecomment" in metadata else ""
|
album_disambiguation = metadata["~releasecomment"] if "~releasecomment" in metadata else ""
|
||||||
|
|
||||||
explicit_keywords = config.setting["ecd2itat_explicit_keywords"] or DEFAULT_EXPLICIT_KEYWORDS
|
explicit_keywords = config.setting["ecd2itat_explicit_keywords"] or DEFAULT_EXPLICIT_KEYWORDS
|
||||||
if isinstance(explicit_keywords, str):
|
if isinstance(explicit_keywords, str):
|
||||||
explicit_keywords = [kw.strip() for kw in explicit_keywords.split(",")]
|
explicit_keywords = [kw.strip() for kw in explicit_keywords.split(",")]
|
||||||
|
explicit_keywords = sorted((kw for kw in explicit_keywords if kw), key=len, reverse=True)
|
||||||
|
|
||||||
clean_keywords = config.setting["ecd2itat_clean_keywords"] or DEFAULT_CLEAN_KEYWORDS
|
clean_keywords = config.setting["ecd2itat_clean_keywords"] or DEFAULT_CLEAN_KEYWORDS
|
||||||
if isinstance(clean_keywords, str):
|
if isinstance(clean_keywords, str):
|
||||||
clean_keywords = [kw.strip() for kw in clean_keywords.split(",")]
|
clean_keywords = [kw.strip() for kw in clean_keywords.split(",")]
|
||||||
|
clean_keywords = sorted((kw for kw in clean_keywords if kw), key=len, reverse=True)
|
||||||
|
|
||||||
explicit_match = next((kw for kw in explicit_keywords if kw.lower() in disambiguation.strip().lower()), None)
|
disambiguation_lc = disambiguation.strip().lower()
|
||||||
clean_match = next((kw for kw in clean_keywords if kw.lower() in disambiguation.strip().lower()), None)
|
album_disambiguation_lc = album_disambiguation.strip().lower()
|
||||||
|
|
||||||
|
explicit_match = next((kw for kw in explicit_keywords if kw.lower() in disambiguation_lc), None)
|
||||||
|
clean_match = next((kw for kw in clean_keywords if kw.lower() in disambiguation_lc), None)
|
||||||
|
album_explicit_match = next((kw for kw in explicit_keywords if kw.lower() in album_disambiguation_lc), None)
|
||||||
|
album_clean_match = next((kw for kw in clean_keywords if kw.lower() in album_disambiguation_lc), None)
|
||||||
|
|
||||||
|
stripped_disambiguation = disambiguation
|
||||||
|
stripped_album_disambiguation = album_disambiguation
|
||||||
|
|
||||||
if explicit_match:
|
if explicit_match:
|
||||||
metadata["itunesadvisory"] = iTunesAdvisory.EXPLICIT.value
|
metadata["itunesadvisory"] = iTunesAdvisory.EXPLICIT.value
|
||||||
|
|
||||||
if config.setting["ecd2itat_save_rtng"]:
|
if config.setting["ecd2itat_save_rtng"]:
|
||||||
metadata["rtng"] = rtng.EXPLICIT.value
|
metadata["rtng"] = rtng.EXPLICIT.value
|
||||||
|
|
||||||
if config.setting["ecd2itat_strip_keyword_from_disambiguation"]:
|
|
||||||
metadata["subtitle"] = strip_keyword_from_disambiguation(disambiguation, explicit_match)
|
|
||||||
metadata["musicbrainz_albumcomment"] = strip_keyword_from_disambiguation(album_disambiguation, explicit_match)
|
|
||||||
elif clean_match:
|
elif clean_match:
|
||||||
metadata["itunesadvisory"] = iTunesAdvisory.CLEAN.value
|
metadata["itunesadvisory"] = iTunesAdvisory.CLEAN.value
|
||||||
|
if config.setting["ecd2itat_save_rtng"]:
|
||||||
if (config.setting["ecd2itat_save_rtng"]):
|
metadata["rtng"] = rtng.CLEAN.value
|
||||||
|
elif album_explicit_match:
|
||||||
|
metadata["itunesadvisory"] = iTunesAdvisory.EXPLICIT.value
|
||||||
|
if config.setting["ecd2itat_save_rtng"]:
|
||||||
|
metadata["rtng"] = rtng.EXPLICIT.value
|
||||||
|
elif album_clean_match:
|
||||||
|
metadata["itunesadvisory"] = iTunesAdvisory.CLEAN.value
|
||||||
|
if config.setting["ecd2itat_save_rtng"]:
|
||||||
metadata["rtng"] = rtng.CLEAN.value
|
metadata["rtng"] = rtng.CLEAN.value
|
||||||
|
|
||||||
if config.setting["ecd2itat_strip_keyword_from_disambiguation"]:
|
if config.setting["ecd2itat_strip_keyword_from_disambiguation"]:
|
||||||
metadata["subtitle"] = strip_keyword_from_disambiguation(disambiguation, clean_match)
|
if explicit_match:
|
||||||
metadata["musicbrainz_albumcomment"] = strip_keyword_from_disambiguation(album_disambiguation, clean_match)
|
stripped_disambiguation = strip_keyword_from_disambiguation(disambiguation, explicit_match)
|
||||||
|
elif clean_match:
|
||||||
|
stripped_disambiguation = strip_keyword_from_disambiguation(disambiguation, clean_match)
|
||||||
|
|
||||||
|
if album_explicit_match:
|
||||||
|
stripped_album_disambiguation = strip_keyword_from_disambiguation(album_disambiguation, album_explicit_match)
|
||||||
|
elif album_clean_match:
|
||||||
|
stripped_album_disambiguation = strip_keyword_from_disambiguation(album_disambiguation, album_clean_match)
|
||||||
|
|
||||||
|
metadata["~recordingcomment"] = stripped_disambiguation
|
||||||
|
metadata["~releasecomment"] = stripped_album_disambiguation
|
||||||
|
|
||||||
def strip_keyword_from_disambiguation(disambiguation, keyword):
|
def strip_keyword_from_disambiguation(disambiguation, keyword):
|
||||||
# If the keyword is the entire disambiguation, return an empty string (e,g. "explicit" becomes "")
|
disambiguation_stripped = disambiguation.strip()
|
||||||
if disambiguation.strip().lower() == keyword.lower():
|
keyword_stripped = keyword.strip()
|
||||||
|
|
||||||
|
if not disambiguation_stripped or not keyword_stripped:
|
||||||
|
return disambiguation
|
||||||
|
|
||||||
|
keyword_pattern = re.compile(rf"(?<!\\w){re.escape(keyword_stripped)}(?!\\w)", re.IGNORECASE)
|
||||||
|
if not keyword_pattern.search(disambiguation_stripped):
|
||||||
|
log.debug(f"Keyword '{keyword}' not found in disambiguation '{disambiguation}' for stripping")
|
||||||
|
return disambiguation
|
||||||
|
|
||||||
|
parts = re.split(r"\s*(,|\||-)\s*", disambiguation_stripped)
|
||||||
|
terms = parts[::2]
|
||||||
|
separators = parts[1::2]
|
||||||
|
|
||||||
|
cleaned_terms = []
|
||||||
|
for term in terms:
|
||||||
|
cleaned_term = keyword_pattern.sub("", term)
|
||||||
|
cleaned_term = re.sub(r"\(\s*\)", "", cleaned_term)
|
||||||
|
cleaned_term = re.sub(r"\s{2,}", " ", cleaned_term).strip()
|
||||||
|
cleaned_terms.append(cleaned_term)
|
||||||
|
|
||||||
|
non_empty_indexes = [i for i, term in enumerate(cleaned_terms) if term]
|
||||||
|
if not non_empty_indexes:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# keyword is at the start of the disambiguation (e.g. "explicit, original mix" becomes "original mix")
|
result = cleaned_terms[non_empty_indexes[0]]
|
||||||
if disambiguation.strip().lower().startswith(keyword.lower() + ","):
|
for index in non_empty_indexes[1:]:
|
||||||
return disambiguation[len(keyword)+1:].strip()
|
separator = separators[index - 1] if index - 1 < len(separators) else ","
|
||||||
|
if separator == ",":
|
||||||
|
result += f", {cleaned_terms[index]}"
|
||||||
|
else:
|
||||||
|
result += f" {separator} {cleaned_terms[index]}"
|
||||||
|
|
||||||
# keyword is at the end of the disambiguation (e.g. "original mix,explicit" becomes "original mix")
|
return result
|
||||||
if disambiguation.strip().lower().endswith("," + keyword.lower()):
|
|
||||||
return disambiguation[:-len(keyword)-1].strip()
|
|
||||||
|
|
||||||
# keyword is at the end with a preceding comma and space (e.g. "original mix, explicit" becomes "original mix")
|
|
||||||
if disambiguation.strip().lower().endswith(", " + keyword.lower()):
|
|
||||||
return disambiguation[:-len(keyword)-2].strip()
|
|
||||||
|
|
||||||
# keyword is in the middle of the disambiguation (e.g. "album version, explicit, remix" becomes "album version, remix")
|
|
||||||
if "," + keyword.lower() + "," in disambiguation.strip().lower():
|
|
||||||
return disambiguation.replace("," + keyword + ",", ",").strip()
|
|
||||||
|
|
||||||
# Return the disambiguation unchanged if the keyword is not found or cannot be stripped
|
|
||||||
log.debug(f"Keyword '{keyword}' not found in disambiguation '{disambiguation}' for stripping")
|
|
||||||
return disambiguation
|
|
||||||
|
|
||||||
class ECD2ITatOptionsPage(OptionsPage):
|
class ECD2ITatOptionsPage(OptionsPage):
|
||||||
NAME = "ecd2itat"
|
NAME = "ecd2itat"
|
||||||
@@ -86,7 +121,7 @@ class ECD2ITatOptionsPage(OptionsPage):
|
|||||||
self.save_rtng_checkbox.setToolTip("Save the rtng tag")
|
self.save_rtng_checkbox.setToolTip("Save the rtng tag")
|
||||||
|
|
||||||
self.ecd2itat_strip_keyword_from_disambiguation_checkbox = QtWidgets.QCheckBox("Strip keywords from disambiguation", self)
|
self.ecd2itat_strip_keyword_from_disambiguation_checkbox = QtWidgets.QCheckBox("Strip keywords from disambiguation", self)
|
||||||
self.ecd2itat_strip_keyword_from_disambiguation_checkbox.setToolTip("Try to remove the keyword from the disambiguation after processing, enable this if you don't want the keywords to be visible in the disambiguation and save it under \"subtitle\" (applies to albums too under \"musicbrainz_albumcomment\")")
|
self.ecd2itat_strip_keyword_from_disambiguation_checkbox.setToolTip("Try to remove the keyword from the disambiguation after processing, enable this if you don't want the keywords to be visible in the disambiguation (applies to albums too)")
|
||||||
|
|
||||||
options_layout.addWidget(self.save_rtng_checkbox)
|
options_layout.addWidget(self.save_rtng_checkbox)
|
||||||
options_layout.addWidget(self.ecd2itat_strip_keyword_from_disambiguation_checkbox)
|
options_layout.addWidget(self.ecd2itat_strip_keyword_from_disambiguation_checkbox)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from picard.config import BoolOption, TextOption, Option
|
|||||||
PLUGIN_NAME = "ecd2iTat"
|
PLUGIN_NAME = "ecd2iTat"
|
||||||
PLUGIN_AUTHOR = "cy1der"
|
PLUGIN_AUTHOR = "cy1der"
|
||||||
PLUGIN_DESCRIPTION = "Convert disambiguations containing \"explicit\"/\"clean\" (and others) keywords to proper tags so clients can display the 🅴/🅲 symbol"
|
PLUGIN_DESCRIPTION = "Convert disambiguations containing \"explicit\"/\"clean\" (and others) keywords to proper tags so clients can display the 🅴/🅲 symbol"
|
||||||
PLUGIN_VERSION = "1.0.0"
|
PLUGIN_VERSION = "1.0.3"
|
||||||
PLUGIN_API_VERSIONS = ["2.7", "2.8", "2.9", "2.10", "2.11", "2.12", "2.13"]
|
PLUGIN_API_VERSIONS = ["2.7", "2.8", "2.9", "2.10", "2.11", "2.12", "2.13"]
|
||||||
PLUGIN_LICENSE = "GPL-2.0-or-later"
|
PLUGIN_LICENSE = "GPL-2.0-or-later"
|
||||||
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html"
|
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html"
|
||||||
|
|||||||
81
tests.py
Normal file
81
tests.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import ast
|
||||||
|
from pathlib import Path
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from typing import Callable, cast
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def _load_strip_keyword_function():
|
||||||
|
module_path = Path(__file__).resolve().parent / "__init__.py"
|
||||||
|
source = module_path.read_text(encoding="utf-8")
|
||||||
|
tree = ast.parse(source, filename=str(module_path))
|
||||||
|
|
||||||
|
function_node = next(
|
||||||
|
node
|
||||||
|
for node in tree.body
|
||||||
|
if isinstance(node, ast.FunctionDef) and node.name == "strip_keyword_from_disambiguation"
|
||||||
|
)
|
||||||
|
|
||||||
|
isolated_module = ast.Module(
|
||||||
|
body=[
|
||||||
|
ast.Import(names=[ast.alias(name="re")]),
|
||||||
|
function_node,
|
||||||
|
],
|
||||||
|
type_ignores=[],
|
||||||
|
)
|
||||||
|
ast.fix_missing_locations(isolated_module)
|
||||||
|
|
||||||
|
namespace: dict[str, object] = {
|
||||||
|
"log": SimpleNamespace(debug=lambda *args, **kwargs: None),
|
||||||
|
}
|
||||||
|
exec(compile(isolated_module, str(module_path), "exec"), namespace)
|
||||||
|
return cast(Callable[[str, str], str], namespace["strip_keyword_from_disambiguation"])
|
||||||
|
|
||||||
|
|
||||||
|
strip_keyword_from_disambiguation = _load_strip_keyword_function()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"disambiguation, keyword, expected",
|
||||||
|
[
|
||||||
|
("explicit", "explicit", ""),
|
||||||
|
("original mix, explicit", "explicit", "original mix"),
|
||||||
|
("original mix explicit", "explicit", "original mix"),
|
||||||
|
("explicit, original mix", "explicit", "original mix"),
|
||||||
|
("explicit album version", "explicit", "album version"),
|
||||||
|
("explicit - original mix", "explicit", "original mix"),
|
||||||
|
("original mix,explicit", "explicit", "original mix"),
|
||||||
|
("original mix - explicit", "explicit", "original mix"),
|
||||||
|
("original mix (explicit)", "explicit", "original mix"),
|
||||||
|
("original mix ( explicit )", "explicit", "original mix"),
|
||||||
|
("original explicit mix", "explicit", "original mix"),
|
||||||
|
("album version, explicit, remix", "explicit", "album version, remix"),
|
||||||
|
("album version - explicit - remix", "explicit", "album version - remix"),
|
||||||
|
("album version | explicit | remix", "explicit", "album version | remix"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_strip_keyword_supported_patterns(disambiguation, keyword, expected):
|
||||||
|
assert strip_keyword_from_disambiguation(disambiguation, keyword) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_strip_keyword_is_case_insensitive():
|
||||||
|
result = strip_keyword_from_disambiguation("Original Mix, EXPLICIT", "explicit")
|
||||||
|
assert result == "Original Mix"
|
||||||
|
|
||||||
|
|
||||||
|
def test_strip_keyword_returns_original_when_not_found():
|
||||||
|
original = "original mix, clean"
|
||||||
|
assert strip_keyword_from_disambiguation(original, "explicit") == original
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"disambiguation, keyword",
|
||||||
|
[
|
||||||
|
("", "explicit"),
|
||||||
|
("original mix", ""),
|
||||||
|
("", ""),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_strip_keyword_handles_empty_inputs(disambiguation, keyword):
|
||||||
|
assert strip_keyword_from_disambiguation(disambiguation, keyword) == disambiguation
|
||||||
Reference in New Issue
Block a user