beets/pyproject.toml
henry b902352139
New Plugin: Titlecase (#6133)
This plugin aims to address the shortcomings of the %title function, as
brought up in issues #152, #3298 and an initial look to improvement with
#3411. It supplies a new string format command, `%titlecase` which
doesn't interfere with any prior expected behavior of the `%title`
format command.

It also adds the ability to apply titlecase logic to metadata fields
that a user selects, which is useful if you, like me, are looking for
stylistic consistency and the minor stylistic differences between
Musizbrainz, Discogs, Deezer etc, with title case are slightly
infuriating.

This will add an optional dependency of
[titlecase](https://pypi.org/project/titlecase/), which allows the
titlecase core logic to be externally maintained.

If there's not enough draw to have this as a core plugin, I can also
spin this into an independent one, but it seemed like a recurring theme
that the %title string format didn't really behave as expected, and I
wanted my metadata to match too.

- [x] Documentation. (If you've added a new command-line flag, for
example, find the appropriate page under `docs/` to describe it.)
- [x] Changelog. (Add an entry to `docs/changelog.rst` to the bottom of
one of the lists near the top of the document.)
- [x] Tests. - Not 100% coverage, but didn't see a lot of other plugins
with testing for import stages.
2025-11-23 10:34:05 -08:00

337 lines
9.4 KiB
TOML

[tool.poetry]
name = "beets"
version = "2.5.1"
description = "music tagger and library organizer"
authors = ["Adrian Sampson <adrian@radbox.org>"]
maintainers = ["Serene-Arc"]
license = "MIT"
readme = "README.rst"
homepage = "https://beets.io/"
repository = "https://github.com/beetbox/beets"
documentation = "https://beets.readthedocs.io/en/stable/"
classifiers = [
"Topic :: Multimedia :: Sound/Audio",
"Topic :: Multimedia :: Sound/Audio :: Players :: MP3",
"License :: OSI Approved :: MIT License",
"Environment :: Console",
"Environment :: Web Environment",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
]
packages = [
{ include = "beets" },
{ include = "beetsplug" },
]
include = [ # extra files to include in the sdist
{ path = "docs", format = "sdist" },
{ path = "extra", format = "sdist" },
{ path = "man/**/*", format = "sdist" },
{ path = "test/*.py", format = "sdist" },
{ path = "test/rsrc/**/*", format = "sdist" },
]
exclude = ["docs/_build", "docs/modd.conf", "docs/**/*.css"]
[tool.poetry.urls]
Changelog = "https://github.com/beetbox/beets/blob/master/docs/changelog.rst"
"Bug Tracker" = "https://github.com/beetbox/beets/issues"
[tool.poetry.dependencies]
python = ">=3.10,<4"
colorama = { version = "*", markers = "sys_platform == 'win32'" }
confuse = ">=2.1.0"
jellyfish = "*"
lap = ">=0.5.12"
mediafile = ">=0.12.0"
musicbrainzngs = ">=0.4"
numpy = [
{ python = "<3.13", version = ">=2.0.2" },
{ python = ">=3.13", version = ">=2.3.4" },
]
platformdirs = ">=3.5.0"
pyyaml = "*"
typing_extensions = "*"
unidecode = ">=1.3.6"
beautifulsoup4 = { version = "*", optional = true }
dbus-python = { version = "*", optional = true }
flask = { version = "*", optional = true }
flask-cors = { version = "*", optional = true }
langdetect = { version = "*", optional = true }
librosa = { version = ">=0.11", optional = true }
scipy = [ # for librosa
{ python = "<3.13", version = ">=1.13.1", optional = true },
{ python = ">=3.13", version = ">=1.16.1", optional = true },
]
numba = [ # for librosa
{ python = "<3.13", version = ">=0.60", optional = true },
{ python = ">=3.13", version = ">=0.62.1", optional = true },
]
mutagen = { version = ">=1.33", optional = true }
Pillow = { version = "*", optional = true }
py7zr = { version = "*", optional = true }
pyacoustid = { version = "*", optional = true }
PyGObject = { version = "*", optional = true }
pylast = { version = "*", optional = true }
python-mpd2 = { version = ">=0.4.2", optional = true }
python3-discogs-client = { version = ">=2.3.15", optional = true }
pyxdg = { version = "*", optional = true }
rarfile = { version = "*", optional = true }
reflink = { version = "*", optional = true }
requests = { version = "*", optional = true }
resampy = { version = ">=0.4.3", optional = true }
requests-oauthlib = { version = ">=0.6.1", optional = true }
soco = { version = "*", optional = true }
docutils = { version = ">=0.20.1", optional = true }
pydata-sphinx-theme = { version = "*", optional = true }
sphinx = { version = "*", optional = true }
sphinx-design = { version = ">=0.6.1", optional = true }
sphinx-copybutton = { version = ">=0.5.2", optional = true }
titlecase = {version = "^2.4.1", optional = true}
[tool.poetry.group.test.dependencies]
beautifulsoup4 = "*"
codecov = ">=2.1.13"
flask = "*"
langdetect = "*"
mock = "*"
pylast = "*"
pytest = "*"
pytest-cov = "*"
pytest-flask = "*"
python-mpd2 = "*"
python3-discogs-client = ">=2.3.15"
py7zr = "*"
pyxdg = "*"
rarfile = "*"
requests-mock = ">=1.12.1"
requests_oauthlib = "*"
responses = ">=0.3.0"
titlecase = "^2.4.1"
[tool.poetry.group.lint.dependencies]
docstrfmt = ">=1.11.1"
ruff = ">=0.6.4"
sphinx-lint = ">=1.0.0"
[tool.poetry.group.typing.dependencies]
mypy = "*"
types-beautifulsoup4 = "*"
types-docutils = ">=0.22.2.20251006"
types-mock = "*"
types-Flask-Cors = "*"
types-Pillow = "*"
types-PyYAML = "*"
types-requests = "*"
types-urllib3 = "*"
[tool.poetry.group.release.dependencies]
click = ">=8.1.7"
packaging = ">=24.0"
tomli = ">=2.0.1"
[tool.poetry.extras]
# inline comments note required external / non-python dependencies
absubmit = ["requests"] # extractor binary from https://acousticbrainz.org/download
aura = ["flask", "flask-cors", "Pillow"]
autobpm = ["librosa", "resampy"]
# badfiles # mp3val and flac
beatport = ["requests-oauthlib"]
bpd = ["PyGObject"] # gobject-introspection, gstreamer1.0-plugins-base, python3-gst-1.0
chroma = ["pyacoustid"] # chromaprint or fpcalc
# convert # ffmpeg
docs = [
"docutils",
"pydata-sphinx-theme",
"sphinx",
"sphinx-lint",
"sphinx-design",
"sphinx-copybutton",
]
discogs = ["python3-discogs-client"]
embedart = ["Pillow"] # ImageMagick
embyupdate = ["requests"]
fetchart = ["beautifulsoup4", "langdetect", "Pillow", "requests"]
import = ["py7zr", "rarfile"]
# ipfs # go-ipfs
# keyfinder # KeyFinder
kodiupdate = ["requests"]
lastgenre = ["pylast"]
lastimport = ["pylast"]
lyrics = ["beautifulsoup4", "langdetect", "requests"]
metasync = ["dbus-python"]
mpdstats = ["python-mpd2"]
plexupdate = ["requests"]
reflink = ["reflink"]
replaygain = [
"PyGObject",
] # python-gi and GStreamer 1.0+ or mp3gain/aacgain or Python Audio Tools or ffmpeg
scrub = ["mutagen"]
sonosupdate = ["soco"]
titlecase = ["titlecase"]
thumbnails = ["Pillow", "pyxdg"]
web = ["flask", "flask-cors"]
[tool.poetry.scripts]
beet = "beets.ui:main"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.pipx-install]
poethepoet = ">=0.26"
poetry = ">=1.8,<2"
[tool.poe.tasks.build]
help = "Build the package"
shell = """
make -C docs man
rm -rf man
mv docs/_build/man .
poetry build
"""
[tool.poe.tasks.bump]
help = "Bump project version and update relevant files"
cmd = "python ./extra/release.py bump $version"
args = { version = { help = "The new version to set", positional = true, required = true } }
[tool.poe.tasks.changelog]
help = "Print the latest version's changelog in Markdown"
cmd = "python ./extra/release.py changelog"
[tool.poe.tasks.check-docs-links]
help = "Check the documentation for broken URLs"
cmd = "make -C docs linkcheck"
[tool.poe.tasks.check-format]
help = "Check the code for style issues"
cmd = "ruff format --check --diff"
[tool.poe.tasks.check-types]
help = "Check the code for typing issues. Accepts mypy options."
cmd = "mypy"
[tool.poe.tasks.docs]
help = "Build documentation"
args = [{ name = "COMMANDS", positional = true, multiple = true, default = "html" }]
cmd = "make -C docs $COMMANDS"
[tool.poe.tasks.format]
help = "Format the codebase"
cmd = "ruff format"
[tool.poe.tasks.format-docs]
help = "Format the documentation"
cmd = "docstrfmt docs *.rst"
[tool.poe.tasks.lint]
help = "Check the code for linting issues. Accepts ruff options."
cmd = "ruff check"
[tool.poe.tasks.lint-docs]
help = "Lint the documentation"
shell = "sphinx-lint --enable all --disable default-role $(git ls-files '*.rst')"
[tool.poe.tasks.update-dependencies]
help = "Update dependencies to their latest versions."
cmd = "poetry update -vv"
[tool.poe.tasks.test]
help = "Run tests with pytest"
cmd = "pytest $OPTS"
env.OPTS.default = "-p no:cov"
[tool.poe.tasks.test-with-coverage]
help = "Run tests and record coverage"
ref = "test"
# record coverage in beets and beetsplug packages
# save xml for coverage upload to coveralls
# save html report for local dev use
# measure coverage across logical branches
# show which tests cover specific lines in the code (see the HTML report)
env.OPTS = """
--cov=beets
--cov=beetsplug
--cov-report=xml:.reports/coverage.xml
--cov-report=html:.reports/html
--cov-branch
--cov-context=test
"""
[tool.poe.tasks.check-temp-files]
help = "Run each test module one by one and check for leftover temp files"
shell = """
setopt nullglob
for file in test/**/*.py; do
print Temp files created by $file && poe test $file &>/dev/null
tempfiles=(/tmp/**/tmp* /tmp/beets/**/*)
if (( $#tempfiles )); then
print -l $'\t'$^tempfiles
rm -r --interactive=never $tempfiles &>/dev/null
fi
done
"""
interpreter = "zsh"
[tool.docstrfmt]
line-length = 80
extend-exclude = [
"docs/_templates/**/*",
"docs/api/**/*",
"README_kr.rst",
]
[tool.ruff]
target-version = "py39"
line-length = 80
[tool.ruff.lint]
select = [
# "ARG", # flake8-unused-arguments
# "C4", # flake8-comprehensions
"E", # pycodestyle
"F", # pyflakes
# "B", # flake8-bugbear
"G", # flake8-logging-format
"I", # isort
"ISC", # flake8-implicit-str-concat
"N", # pep8-naming
"PT", # flake8-pytest-style
# "RUF", # ruff
"UP", # pyupgrade
"TCH", # flake8-type-checking
"W", # pycodestyle
]
ignore = [
"TC006", # no need to quote 'cast's since we use 'from __future__ import annotations'
]
[tool.ruff.lint.per-file-ignores]
"beets/**" = ["PT"]
"test/test_util.py" = ["E501"]
[tool.ruff.lint.isort]
split-on-trailing-comma = false
[tool.ruff.lint.pycodestyle]
max-line-length = 88
[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false
parametrize-names-type = "csv"
[tool.ruff.lint.flake8-unused-arguments]
ignore-variadic-names = true
[tool.ruff.lint.pep8-naming]
classmethod-decorators = ["cached_classproperty"]
extend-ignore-names = ["assert*", "cached_classproperty"]