[project] name = "beets" version = "2.12.0" description = "music tagger and library organizer" authors = [{ name = "Adrian Sampson", email = "adrian@radbox.org" }] maintainers = [{ name = "Serene-Arc" }] license = { text = "MIT" } readme = "README.rst" requires-python = ">=3.10,<3.15" 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 :: 3.14", "Programming Language :: Python :: Implementation :: CPython", ] dependencies = [ "colorama ; sys_platform == 'win32'", "confuse>=2.2.0", "jellyfish", "lap>=0.5.12", "mediafile>=0.17.0", "numpy>=2.0.2 ; python_version < '3.13'", "numpy>=2.3.5 ; python_version >= '3.13'", "packaging>=24.0", "platformdirs>=3.5.0", "pyyaml", "requests>=2.32.5", "requests-ratelimiter>=0.7.0", "typing_extensions", "unidecode>=1.3.6", ] [dependency-groups] docs = [ "docutils>=0.20.1", "pydata-sphinx-theme", "sphinx<9", "sphinx-lint>=1.0.0", "sphinx-design>=0.6.1", "sphinx-copybutton>=0.5.2", "sphinx-toolbox>=4.1.0", "titlecase>=2.4.1,<3", ] test = [ "beautifulsoup4", "codecov>=2.1.13", "flask", "langdetect", "pylast", "pytest", "pytest-cov", "pytest-factoryboy>=2.8.1", "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,<3", ] lint = [ "docstrfmt>=2.0.2", "ruff>=0.13.0", "sphinx-lint>=1.0.0", ] typing = [ "mypy", "types-beautifulsoup4", "types-docutils>=0.22.2.20251006", "types-Flask-Cors", "types-Pillow", "types-PyYAML", "types-requests", "types-urllib3", ] release = [ "click>=8.1.7", "tomli>=2.0.1", ] dev = [ { include-group = "docs" }, { include-group = "lint" }, { include-group = "test" }, { include-group = "typing" }, ] [project.optional-dependencies] # inline comments note required external / non-python dependencies absubmit = ["requests"] # extractor binary from https://acousticbrainz.org/download aura = ["flask", "flask-cors", "Pillow"] autobpm = ["librosa>=0.11", "resampy>=0.4.3"] # badfiles # mp3val and flac beatport = ["requests-oauthlib>=0.6.1"] bpd = [ "PyGObject ; sys_platform != 'win32'", ] # gobject-introspection, gstreamer1.0-plugins-base, python3-gst-1.0 chroma = ["pyacoustid>=1.3.1,<2"] # chromaprint or fpcalc # convert # ffmpeg discogs = ["python3-discogs-client>=2.3.15"] 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 ; sys_platform != 'win32'"] mpdstats = ["python-mpd2>=0.4.2"] plexupdate = ["requests"] reflink = ["reflink"] replaygain = [ "PyGObject ; sys_platform != 'win32'", ] # python-gi and GStreamer 1.0+ or mp3gain/aacgain or Python Audio Tools or ffmpeg scrub = ["mutagen>=1.33"] sonosupdate = ["soco"] tidal = ["requests-oauthlib>=0.6.1"] titlecase = ["titlecase"] thumbnails = ["Pillow", "pyxdg"] web = ["flask", "flask-cors"] [project.scripts] beet = "beets.ui:main" [project.urls] Homepage = "https://beets.io/" Repository = "https://github.com/beetbox/beets" Documentation = "https://beets.readthedocs.io/en/stable/" Changelog = "https://github.com/beetbox/beets/blob/master/docs/changelog.rst" "Bug Tracker" = "https://github.com/beetbox/beets/issues" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["beets", "beetsplug"] [tool.hatch.build.targets.sdist] include = [ "beets/", "beetsplug/", "docs/", "extra/", "man/", "test/", ] exclude = [ "docs/_build/", "docs/modd.conf", "docs/**/*.css", ] [tool.poe.tasks.build] help = "Build the package" shell = """ make -C docs man rm -rf man mv docs/_build/man . uv 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 --config=pyproject.toml" [tool.poe.tasks.format-docs] help = "Format the documentation" cmd = "docstrfmt --preserve-adornments docs *.rst" [tool.poe.tasks.install] help = "Install project dependencies" cmd = "uv sync --locked" [tool.poe.tasks.lint] help = "Check the code for linting issues. Accepts ruff options." cmd = "ruff check --config=pyproject.toml" [tool.poe.tasks.lint-docs] help = "Lint the documentation" interpreter = "bash" shell = """ set -o pipefail files=$(git ls-files '*.rst') grep -Eno ' `[^`][^`]+`[^_]' $files | sed 's/ .*/ Use double backticks for inline literal (double-backticks-required)/' && failed=1 sphinx-lint --enable all --disable default-role $files || failed=1 exit ${failed:-0} """ [tool.poe.tasks.update-dependencies] help = "Update dependencies to their latest versions." cmd = "uv sync --upgrade" [tool.poe.tasks.test] help = "Run tests with pytest" cmd = "uv run 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=. --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 = "py310" line-length = 80 [tool.ruff.format] skip-magic-trailing-comma = true [tool.ruff.lint] future-annotations = true select = [ "A", # flake8-builtins # "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 "PIE", # flake8-pie "PT", # flake8-pytest-style "RET", # flake8-return "RUF", # ruff "UP", # pyupgrade "TC", # 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/plugins/test_ftintitle.py" = ["E501"] "test/test_util.py" = ["E501"] "test/util/test_diff.py" = ["E501"] "test/util/test_id_extractors.py" = ["E501"] "test/**" = ["RUF001"] # we use Unicode characters in tests [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"]