1.0.0
This commit is contained in:
175
.gitignore
vendored
175
.gitignore
vendored
@@ -1,176 +1 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# ecd2iTat
|
||||
|
||||
Convert disambiguations containing "explicit"/"clean" (and others) to proper tags so clients can display the 🅴/🅲 symbol
|
||||
Convert disambiguations containing "explicit"/"clean" (and others) keywords to proper tags so clients can display the 🅴/🅲 symbol
|
||||
|
||||
## What the hell is that name?
|
||||
|
||||
(E)xplicit/(c)lean (d)isambiguation [to] (iT)unes (a)dvisory (t)ag*
|
||||
(E)xplicit/(c)lean (d)isambiguation [to] (iT)unes (a)dvisory (t)ag\*
|
||||
|
||||
<small>* `itunesadvisory` (and `rtng` because why not)</small>
|
||||
<small>\* `itunesadvisory` (and `rtng` because why not)</small>
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
138
__init__.py
Normal file
138
__init__.py
Normal file
@@ -0,0 +1,138 @@
|
||||
from picard import config, log
|
||||
from picard.ui.options import OptionsPage, register_options_page
|
||||
from picard.metadata import register_track_metadata_processor
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from .constants import *
|
||||
|
||||
def process_track(_, metadata, track, __):
|
||||
disambiguation = track["recording"]["disambiguation"] if "disambiguation" in track["recording"] else ""
|
||||
album_disambiguation = metadata["~releasecomment"] if "~releasecomment" in metadata else ""
|
||||
|
||||
explicit_keywords = config.setting["ecd2itat_explicit_keywords"] or DEFAULT_EXPLICIT_KEYWORDS
|
||||
if isinstance(explicit_keywords, str):
|
||||
explicit_keywords = [kw.strip() for kw in explicit_keywords.split(",")]
|
||||
|
||||
clean_keywords = config.setting["ecd2itat_clean_keywords"] or DEFAULT_CLEAN_KEYWORDS
|
||||
if isinstance(clean_keywords, str):
|
||||
clean_keywords = [kw.strip() for kw in clean_keywords.split(",")]
|
||||
|
||||
explicit_match = next((kw for kw in explicit_keywords if kw.lower() in disambiguation.strip().lower()), None)
|
||||
clean_match = next((kw for kw in clean_keywords if kw.lower() in disambiguation.strip().lower()), None)
|
||||
|
||||
if explicit_match:
|
||||
metadata["itunesadvisory"] = iTunesAdvisory.EXPLICIT.value
|
||||
|
||||
if config.setting["ecd2itat_save_rtng"]:
|
||||
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:
|
||||
metadata["itunesadvisory"] = iTunesAdvisory.CLEAN.value
|
||||
|
||||
if (config.setting["ecd2itat_save_rtng"]):
|
||||
metadata["rtng"] = rtng.CLEAN.value
|
||||
|
||||
if config.setting["ecd2itat_strip_keyword_from_disambiguation"]:
|
||||
metadata["subtitle"] = strip_keyword_from_disambiguation(disambiguation, clean_match)
|
||||
metadata["musicbrainz_albumcomment"] = strip_keyword_from_disambiguation(album_disambiguation, clean_match)
|
||||
|
||||
def strip_keyword_from_disambiguation(disambiguation, keyword):
|
||||
# If the keyword is the entire disambiguation, return an empty string (e,g. "explicit" becomes "")
|
||||
if disambiguation.strip().lower() == keyword.lower():
|
||||
return ""
|
||||
|
||||
# keyword is at the start of the disambiguation (e.g. "explicit, original mix" becomes "original mix")
|
||||
if disambiguation.strip().lower().startswith(keyword.lower() + ","):
|
||||
return disambiguation[len(keyword)+1:].strip()
|
||||
|
||||
# keyword is at the end of the disambiguation (e.g. "original mix,explicit" becomes "original mix")
|
||||
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):
|
||||
NAME = "ecd2itat"
|
||||
TITLE = "ecd2iTat"
|
||||
PARENT = "plugins"
|
||||
|
||||
options = CONFIG_OPTIONS
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
options_group = QtWidgets.QGroupBox("Options", self)
|
||||
options_group.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
|
||||
options_layout = QtWidgets.QVBoxLayout(options_group)
|
||||
|
||||
self.save_rtng_checkbox = QtWidgets.QCheckBox("Save rtng", self)
|
||||
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.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\")")
|
||||
|
||||
options_layout.addWidget(self.save_rtng_checkbox)
|
||||
options_layout.addWidget(self.ecd2itat_strip_keyword_from_disambiguation_checkbox)
|
||||
|
||||
layout.addWidget(options_group)
|
||||
|
||||
keywords_group = QtWidgets.QGroupBox("Keywords", self)
|
||||
keywords_group.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
|
||||
keywords_layout = QtWidgets.QVBoxLayout(keywords_group)
|
||||
|
||||
explicit_keywords_layout = QtWidgets.QHBoxLayout()
|
||||
explicit_keywords_label = QtWidgets.QLabel("Explicit:", self)
|
||||
self.explicit_keywords_input = QtWidgets.QLineEdit(self)
|
||||
self.explicit_keywords_input.setPlaceholderText("Comma-separated list of keywords to consider as explicit")
|
||||
self.explicit_keywords_input.setToolTip("Comma-separated list of keywords to consider as explicit")
|
||||
|
||||
explicit_keywords_layout.addWidget(explicit_keywords_label)
|
||||
explicit_keywords_layout.addWidget(self.explicit_keywords_input)
|
||||
|
||||
clean_keywords_layout = QtWidgets.QHBoxLayout()
|
||||
clean_keywords_label = QtWidgets.QLabel("Clean:", self)
|
||||
self.clean_keywords_input = QtWidgets.QLineEdit(self)
|
||||
self.clean_keywords_input.setPlaceholderText("Comma-separated list of keywords to consider as clean")
|
||||
self.clean_keywords_input.setToolTip("Comma-separated list of keywords to consider as clean")
|
||||
|
||||
clean_keywords_layout.addWidget(clean_keywords_label)
|
||||
clean_keywords_layout.addWidget(self.clean_keywords_input)
|
||||
|
||||
keywords_layout.addLayout(explicit_keywords_layout)
|
||||
keywords_layout.addLayout(clean_keywords_layout)
|
||||
|
||||
layout.addWidget(keywords_group)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
def load(self):
|
||||
self.save_rtng_checkbox.setChecked(config.setting["ecd2itat_save_rtng"] or False)
|
||||
self.ecd2itat_strip_keyword_from_disambiguation_checkbox.setChecked(config.setting["ecd2itat_strip_keyword_from_disambiguation"] or False)
|
||||
self.explicit_keywords_input.setText(config.setting["ecd2itat_explicit_keywords"] or ", ".join(DEFAULT_EXPLICIT_KEYWORDS))
|
||||
self.clean_keywords_input.setText(config.setting["ecd2itat_clean_keywords"] or ", ".join(DEFAULT_CLEAN_KEYWORDS))
|
||||
|
||||
def save(self):
|
||||
config.setting["ecd2itat_save_rtng"] = self.save_rtng_checkbox.isChecked()
|
||||
config.setting["ecd2itat_strip_keyword_from_disambiguation"] = self.ecd2itat_strip_keyword_from_disambiguation_checkbox.isChecked()
|
||||
config.setting["ecd2itat_explicit_keywords"] = self.explicit_keywords_input.text()
|
||||
config.setting["ecd2itat_clean_keywords"] = self.clean_keywords_input.text()
|
||||
|
||||
register_options_page(ECD2ITatOptionsPage)
|
||||
register_track_metadata_processor(process_track)
|
||||
45
constants.py
Normal file
45
constants.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from typing import List
|
||||
from enum import Enum
|
||||
from picard.config import BoolOption, TextOption, Option
|
||||
|
||||
PLUGIN_NAME = "ecd2iTat"
|
||||
PLUGIN_AUTHOR = "cy1der"
|
||||
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_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_URL = "https://www.gnu.org/licenses/gpl-2.0.html"
|
||||
PLUGIN_USER_GUIDE_URL = "https://git.altaiar.dev/ahmed/ecd2iTat"
|
||||
|
||||
class iTunesAdvisory(Enum):
|
||||
EXPLICIT = 1
|
||||
CLEAN = 2
|
||||
|
||||
class rtng(Enum):
|
||||
EXPLICIT = 4
|
||||
CLEAN = 2
|
||||
|
||||
DEFAULT_EXPLICIT_KEYWORDS: List[str] = [
|
||||
"explicit release version",
|
||||
"dirty release version",
|
||||
"explicit version",
|
||||
"dirty version",
|
||||
"explicit",
|
||||
"dirty"
|
||||
]
|
||||
|
||||
DEFAULT_CLEAN_KEYWORDS: List[str] = [
|
||||
"censored release version",
|
||||
"clean release version",
|
||||
"censored version",
|
||||
"clean version",
|
||||
"censored",
|
||||
"clean"
|
||||
]
|
||||
|
||||
CONFIG_OPTIONS: List[Option] = [
|
||||
TextOption("setting", "ecd2itat_explicit_keywords", ", ".join(DEFAULT_EXPLICIT_KEYWORDS)),
|
||||
TextOption("setting", "ecd2itat_clean_keywords", ", ".join(DEFAULT_CLEAN_KEYWORDS)),
|
||||
BoolOption("setting", "ecd2itat_save_rtng", False),
|
||||
BoolOption("setting", "ecd2itat_strip_keyword_from_disambiguation", False),
|
||||
]
|
||||
Reference in New Issue
Block a user