mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
Merge branch 'master' into ftintitle-continue-even-if-albumartist-and-artist-is-the-same
This commit is contained in:
commit
00e3da1a92
28 changed files with 627 additions and 358 deletions
2
.github/workflows/changelog_reminder.yaml
vendored
2
.github/workflows/changelog_reminder.yaml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
||||||
check_changes:
|
check_changes:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get all updated Python files
|
- name: Get all updated Python files
|
||||||
id: changed-python-files
|
id: changed-python-files
|
||||||
|
|
|
||||||
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
|
|
@ -25,12 +25,12 @@ jobs:
|
||||||
env:
|
env:
|
||||||
IS_MAIN_PYTHON: ${{ matrix.python-version == '3.9' && matrix.platform == 'ubuntu-latest' }}
|
IS_MAIN_PYTHON: ${{ matrix.python-version == '3.9' && matrix.platform == 'ubuntu-latest' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Install Python tools
|
- name: Install Python tools
|
||||||
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
||||||
- name: Setup Python with poetry caching
|
- name: Setup Python with poetry caching
|
||||||
# poetry cache requires poetry to already be installed, weirdly
|
# poetry cache requires poetry to already be installed, weirdly
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache: poetry
|
cache: poetry
|
||||||
|
|
@ -90,10 +90,10 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get the coverage report
|
- name: Get the coverage report
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: coverage-report
|
name: coverage-report
|
||||||
|
|
||||||
|
|
|
||||||
4
.github/workflows/integration_test.yaml
vendored
4
.github/workflows/integration_test.yaml
vendored
|
|
@ -7,10 +7,10 @@ jobs:
|
||||||
test_integration:
|
test_integration:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Install Python tools
|
- name: Install Python tools
|
||||||
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
cache: poetry
|
cache: poetry
|
||||||
|
|
|
||||||
18
.github/workflows/lint.yml
vendored
18
.github/workflows/lint.yml
vendored
|
|
@ -24,7 +24,7 @@ jobs:
|
||||||
changed_doc_files: ${{ steps.changed-doc-files.outputs.all_changed_files }}
|
changed_doc_files: ${{ steps.changed-doc-files.outputs.all_changed_files }}
|
||||||
changed_python_files: ${{ steps.changed-python-files.outputs.all_changed_files }}
|
changed_python_files: ${{ steps.changed-python-files.outputs.all_changed_files }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Get changed docs files
|
- name: Get changed docs files
|
||||||
id: changed-doc-files
|
id: changed-doc-files
|
||||||
uses: tj-actions/changed-files@v46
|
uses: tj-actions/changed-files@v46
|
||||||
|
|
@ -56,10 +56,10 @@ jobs:
|
||||||
name: Check formatting
|
name: Check formatting
|
||||||
needs: changed-files
|
needs: changed-files
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Install Python tools
|
- name: Install Python tools
|
||||||
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
cache: poetry
|
cache: poetry
|
||||||
|
|
@ -77,10 +77,10 @@ jobs:
|
||||||
name: Check linting
|
name: Check linting
|
||||||
needs: changed-files
|
needs: changed-files
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Install Python tools
|
- name: Install Python tools
|
||||||
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
cache: poetry
|
cache: poetry
|
||||||
|
|
@ -97,10 +97,10 @@ jobs:
|
||||||
name: Check types with mypy
|
name: Check types with mypy
|
||||||
needs: changed-files
|
needs: changed-files
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Install Python tools
|
- name: Install Python tools
|
||||||
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
cache: poetry
|
cache: poetry
|
||||||
|
|
@ -120,10 +120,10 @@ jobs:
|
||||||
name: Check docs
|
name: Check docs
|
||||||
needs: changed-files
|
needs: changed-files
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Install Python tools
|
- name: Install Python tools
|
||||||
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
cache: poetry
|
cache: poetry
|
||||||
|
|
|
||||||
12
.github/workflows/make_release.yaml
vendored
12
.github/workflows/make_release.yaml
vendored
|
|
@ -17,10 +17,10 @@ jobs:
|
||||||
name: Bump version, commit and create tag
|
name: Bump version, commit and create tag
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Install Python tools
|
- name: Install Python tools
|
||||||
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
cache: poetry
|
cache: poetry
|
||||||
|
|
@ -45,13 +45,13 @@ jobs:
|
||||||
outputs:
|
outputs:
|
||||||
changelog: ${{ steps.generate_changelog.outputs.changelog }}
|
changelog: ${{ steps.generate_changelog.outputs.changelog }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.NEW_TAG }}
|
ref: ${{ env.NEW_TAG }}
|
||||||
|
|
||||||
- name: Install Python tools
|
- name: Install Python tools
|
||||||
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
cache: poetry
|
cache: poetry
|
||||||
|
|
@ -92,7 +92,7 @@ jobs:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Download all the dists
|
- name: Download all the dists
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: python-package-distributions
|
name: python-package-distributions
|
||||||
path: dist/
|
path: dist/
|
||||||
|
|
@ -107,7 +107,7 @@ jobs:
|
||||||
CHANGELOG: ${{ needs.build.outputs.changelog }}
|
CHANGELOG: ${{ needs.build.outputs.changelog }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download all the dists
|
- name: Download all the dists
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: python-package-distributions
|
name: python-package-distributions
|
||||||
path: dist/
|
path: dist/
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -94,3 +94,6 @@ ENV/
|
||||||
|
|
||||||
# pyright
|
# pyright
|
||||||
pyrightconfig.json
|
pyrightconfig.json
|
||||||
|
|
||||||
|
# Pyrefly
|
||||||
|
pyrefly.toml
|
||||||
|
|
|
||||||
|
|
@ -940,10 +940,10 @@ class Transaction:
|
||||||
|
|
||||||
def __exit__(
|
def __exit__(
|
||||||
self,
|
self,
|
||||||
exc_type: type[Exception],
|
exc_type: type[BaseException] | None,
|
||||||
exc_value: Exception,
|
exc_value: BaseException | None,
|
||||||
traceback: TracebackType,
|
traceback: TracebackType | None,
|
||||||
):
|
) -> bool | None:
|
||||||
"""Complete a transaction. This must be the most recently
|
"""Complete a transaction. This must be the most recently
|
||||||
entered but not yet exited transaction. If it is the last active
|
entered but not yet exited transaction. If it is the last active
|
||||||
transaction, the database updates are committed.
|
transaction, the database updates are committed.
|
||||||
|
|
@ -965,6 +965,8 @@ class Transaction:
|
||||||
):
|
):
|
||||||
raise DBCustomFunctionError()
|
raise DBCustomFunctionError()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def query(
|
def query(
|
||||||
self, statement: str, subvals: Sequence[SQLiteType] = ()
|
self, statement: str, subvals: Sequence[SQLiteType] = ()
|
||||||
) -> list[sqlite3.Row]:
|
) -> list[sqlite3.Row]:
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ from typing import (
|
||||||
NamedTuple,
|
NamedTuple,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
from unidecode import unidecode
|
from unidecode import unidecode
|
||||||
|
|
@ -1052,7 +1053,7 @@ def par_map(transform: Callable[[T], Any], items: Sequence[T]) -> None:
|
||||||
pool.join()
|
pool.join()
|
||||||
|
|
||||||
|
|
||||||
class cached_classproperty:
|
class cached_classproperty(Generic[T]):
|
||||||
"""Descriptor implementing cached class properties.
|
"""Descriptor implementing cached class properties.
|
||||||
|
|
||||||
Provides class-level dynamic property behavior where the getter function is
|
Provides class-level dynamic property behavior where the getter function is
|
||||||
|
|
@ -1060,9 +1061,9 @@ class cached_classproperty:
|
||||||
instance properties, this operates on the class rather than instances.
|
instance properties, this operates on the class rather than instances.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cache: ClassVar[dict[tuple[Any, str], Any]] = {}
|
cache: ClassVar[dict[tuple[type[object], str], object]] = {}
|
||||||
|
|
||||||
name: str
|
name: str = ""
|
||||||
|
|
||||||
# Ideally, we would like to use `Callable[[type[T]], Any]` here,
|
# Ideally, we would like to use `Callable[[type[T]], Any]` here,
|
||||||
# however, `mypy` is unable to see this as a **class** property, and thinks
|
# however, `mypy` is unable to see this as a **class** property, and thinks
|
||||||
|
|
@ -1078,21 +1079,21 @@ class cached_classproperty:
|
||||||
# "Callable[[Album], ...]"; expected "Callable[[type[Album]], ...]"
|
# "Callable[[Album], ...]"; expected "Callable[[type[Album]], ...]"
|
||||||
#
|
#
|
||||||
# Therefore, we just use `Any` here, which is not ideal, but works.
|
# Therefore, we just use `Any` here, which is not ideal, but works.
|
||||||
def __init__(self, getter: Callable[[Any], Any]) -> None:
|
def __init__(self, getter: Callable[..., T]) -> None:
|
||||||
"""Initialize the descriptor with the property getter function."""
|
"""Initialize the descriptor with the property getter function."""
|
||||||
self.getter = getter
|
self.getter: Callable[..., T] = getter
|
||||||
|
|
||||||
def __set_name__(self, owner: Any, name: str) -> None:
|
def __set_name__(self, owner: object, name: str) -> None:
|
||||||
"""Capture the attribute name this descriptor is assigned to."""
|
"""Capture the attribute name this descriptor is assigned to."""
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def __get__(self, instance: Any, owner: type[Any]) -> Any:
|
def __get__(self, instance: object, owner: type[object]) -> T:
|
||||||
"""Compute and cache if needed, and return the property value."""
|
"""Compute and cache if needed, and return the property value."""
|
||||||
key = owner, self.name
|
key: tuple[type[object], str] = owner, self.name
|
||||||
if key not in self.cache:
|
if key not in self.cache:
|
||||||
self.cache[key] = self.getter(owner)
|
self.cache[key] = self.getter(owner)
|
||||||
|
|
||||||
return self.cache[key]
|
return cast(T, self.cache[key])
|
||||||
|
|
||||||
|
|
||||||
class LazySharedInstance(Generic[T]):
|
class LazySharedInstance(Generic[T]):
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,9 @@ class DiscogsPlugin(MetadataSourcePlugin):
|
||||||
"user_token": "",
|
"user_token": "",
|
||||||
"separator": ", ",
|
"separator": ", ",
|
||||||
"index_tracks": False,
|
"index_tracks": False,
|
||||||
"featured_string": "Feat.",
|
|
||||||
"append_style_genre": False,
|
"append_style_genre": False,
|
||||||
"strip_disambiguation": True,
|
"strip_disambiguation": True,
|
||||||
|
"featured_string": "Feat.",
|
||||||
"anv": {
|
"anv": {
|
||||||
"artist_credit": True,
|
"artist_credit": True,
|
||||||
"artist": False,
|
"artist": False,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,11 @@ from beets.util import get_temp_filename
|
||||||
# If this is missing, they're placed at the end.
|
# If this is missing, they're placed at the end.
|
||||||
ARGS_MARKER = "$args"
|
ARGS_MARKER = "$args"
|
||||||
|
|
||||||
|
# Indicate where the playlist file (with absolute path) should be inserted into
|
||||||
|
# the command string. If this is missing, its placed at the end, but before
|
||||||
|
# arguments.
|
||||||
|
PLS_MARKER = "$playlist"
|
||||||
|
|
||||||
|
|
||||||
def play(
|
def play(
|
||||||
command_str,
|
command_str,
|
||||||
|
|
@ -132,8 +137,23 @@ class PlayPlugin(BeetsPlugin):
|
||||||
return
|
return
|
||||||
|
|
||||||
open_args = self._playlist_or_paths(paths)
|
open_args = self._playlist_or_paths(paths)
|
||||||
|
open_args_str = [
|
||||||
|
p.decode("utf-8") for p in self._playlist_or_paths(paths)
|
||||||
|
]
|
||||||
command_str = self._command_str(opts.args)
|
command_str = self._command_str(opts.args)
|
||||||
|
|
||||||
|
if PLS_MARKER in command_str:
|
||||||
|
if not config["play"]["raw"]:
|
||||||
|
command_str = command_str.replace(
|
||||||
|
PLS_MARKER, "".join(open_args_str)
|
||||||
|
)
|
||||||
|
self._log.debug(
|
||||||
|
"command altered by PLS_MARKER to: {}", command_str
|
||||||
|
)
|
||||||
|
open_args = []
|
||||||
|
else:
|
||||||
|
command_str = command_str.replace(PLS_MARKER, " ")
|
||||||
|
|
||||||
# Check if the selection exceeds configured threshold. If True,
|
# Check if the selection exceeds configured threshold. If True,
|
||||||
# cancel, otherwise proceed with play command.
|
# cancel, otherwise proceed with play command.
|
||||||
if opts.yes or not self._exceeds_threshold(
|
if opts.yes or not self._exceeds_threshold(
|
||||||
|
|
@ -162,6 +182,7 @@ class PlayPlugin(BeetsPlugin):
|
||||||
return paths
|
return paths
|
||||||
else:
|
else:
|
||||||
return [self._create_tmp_playlist(paths)]
|
return [self._create_tmp_playlist(paths)]
|
||||||
|
return [shlex.quote(self._create_tmp_playlist(paths))]
|
||||||
|
|
||||||
def _exceeds_threshold(
|
def _exceeds_threshold(
|
||||||
self, selection, command_str, open_args, item_type="track"
|
self, selection, command_str, open_args, item_type="track"
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,11 @@ var AppView = Backbone.View.extend({
|
||||||
'pause': _.bind(this.audioPause, this),
|
'pause': _.bind(this.audioPause, this),
|
||||||
'ended': _.bind(this.audioEnded, this)
|
'ended': _.bind(this.audioEnded, this)
|
||||||
});
|
});
|
||||||
|
if ("mediaSession" in navigator) {
|
||||||
|
navigator.mediaSession.setActionHandler("nexttrack", () => {
|
||||||
|
this.playNext();
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showItems: function(items) {
|
showItems: function(items) {
|
||||||
this.shownItems = items;
|
this.shownItems = items;
|
||||||
|
|
@ -306,7 +311,9 @@ var AppView = Backbone.View.extend({
|
||||||
},
|
},
|
||||||
audioEnded: function() {
|
audioEnded: function() {
|
||||||
this.playingItem.entryView.setPlaying(false);
|
this.playingItem.entryView.setPlaying(false);
|
||||||
|
this.playNext();
|
||||||
|
},
|
||||||
|
playNext: function(){
|
||||||
// Try to play the next track.
|
// Try to play the next track.
|
||||||
var idx = this.shownItems.indexOf(this.playingItem);
|
var idx = this.shownItems.indexOf(this.playingItem);
|
||||||
if (idx == -1) {
|
if (idx == -1) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ New features:
|
||||||
- :doc:`plugins/ftintitle`: Added argument for custom feat. words in ftintitle.
|
- :doc:`plugins/ftintitle`: Added argument for custom feat. words in ftintitle.
|
||||||
- :doc:`plugins/ftintitle`: Added argument to skip the processing of artist and
|
- :doc:`plugins/ftintitle`: Added argument to skip the processing of artist and
|
||||||
album artist are the same in ftintitle.
|
album artist are the same in ftintitle.
|
||||||
|
- :doc:`plugins/play`: Added `$playlist` marker to precisely edit the playlist
|
||||||
|
filepath into the command calling the player program.
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
||||||
|
|
@ -48,6 +50,10 @@ Other changes:
|
||||||
- :doc:`guides/main`: Modernized the *Getting Started* guide with tabbed
|
- :doc:`guides/main`: Modernized the *Getting Started* guide with tabbed
|
||||||
sections and dropdown menus. Installation instructions have been streamlined,
|
sections and dropdown menus. Installation instructions have been streamlined,
|
||||||
and a new subpage now provides additional setup details.
|
and a new subpage now provides additional setup details.
|
||||||
|
- Documentation: introduced a new role ``conf`` for documenting configuration
|
||||||
|
options. This role provides consistent formatting and creates references
|
||||||
|
automatically. Applied it to :doc:`plugins/deezer`, :doc:`plugins/discogs`,
|
||||||
|
:doc:`plugins/musicbrainz` and :doc:`plugins/spotify` plugins documentation.
|
||||||
|
|
||||||
2.5.0 (October 11, 2025)
|
2.5.0 (October 11, 2025)
|
||||||
------------------------
|
------------------------
|
||||||
|
|
@ -58,16 +64,18 @@ New features:
|
||||||
without storing or writing them.
|
without storing or writing them.
|
||||||
- :doc:`plugins/convert`: Add a config option to disable writing metadata to
|
- :doc:`plugins/convert`: Add a config option to disable writing metadata to
|
||||||
converted files.
|
converted files.
|
||||||
- :doc:`plugins/discogs`: New config option `strip_disambiguation` to toggle
|
- :doc:`plugins/discogs`: New config option
|
||||||
stripping discogs numeric disambiguation on artist and label fields.
|
:conf:`plugins.discogs:strip_disambiguation` to toggle stripping discogs
|
||||||
|
numeric disambiguation on artist and label fields.
|
||||||
- :doc:`plugins/discogs` Added support for featured artists. :bug:`6038`
|
- :doc:`plugins/discogs` Added support for featured artists. :bug:`6038`
|
||||||
- :doc:`plugins/discogs` New configuration option `featured_string` to change
|
- :doc:`plugins/discogs` New configuration option
|
||||||
the default string used to join featured artists. The default string is
|
:conf:`plugins.discogs:featured_string` to change the default string used to
|
||||||
`Feat.`.
|
join featured artists. The default string is `Feat.`.
|
||||||
- :doc:`plugins/discogs` Support for `artist_credit` in Discogs tags.
|
- :doc:`plugins/discogs` Support for `artist_credit` in Discogs tags.
|
||||||
:bug:`3354`
|
:bug:`3354`
|
||||||
- :doc:`plugins/discogs` Support for name variations and config options to
|
- :doc:`plugins/discogs` Support for name variations and config options to
|
||||||
specify where the variations are written. :bug:`3354`
|
specify where the variations are written. :bug:`3354`
|
||||||
|
- :doc:`plugins/web` Support for `nexttrack` keyboard press
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
||||||
|
|
@ -91,9 +99,10 @@ Bug fixes:
|
||||||
- :doc:`/plugins/fromfilename`: Fix :bug:`5218`, improve the code (refactor
|
- :doc:`/plugins/fromfilename`: Fix :bug:`5218`, improve the code (refactor
|
||||||
regexps, allow for more cases, add some logging), add tests.
|
regexps, allow for more cases, add some logging), add tests.
|
||||||
- Metadata source plugins: Fixed data source penalty calculation that was
|
- Metadata source plugins: Fixed data source penalty calculation that was
|
||||||
incorrectly applied during import matching. The ``source_weight``
|
incorrectly applied during import matching. The
|
||||||
configuration option has been renamed to ``data_source_mismatch_penalty`` to
|
:conf:`plugins.index:source_weight` configuration option has been renamed to
|
||||||
better reflect its purpose. :bug:`6066`
|
:conf:`plugins.index:data_source_mismatch_penalty` to better reflect its
|
||||||
|
purpose. :bug:`6066`
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
|
|
||||||
|
|
@ -139,12 +148,13 @@ New features:
|
||||||
separate plugin. The default :ref:`plugins-config` includes ``musicbrainz``,
|
separate plugin. The default :ref:`plugins-config` includes ``musicbrainz``,
|
||||||
but if you've customized your ``plugins`` list in your configuration, you'll
|
but if you've customized your ``plugins`` list in your configuration, you'll
|
||||||
need to explicitly add ``musicbrainz`` to continue using this functionality.
|
need to explicitly add ``musicbrainz`` to continue using this functionality.
|
||||||
Configuration option ``musicbrainz.enabled`` has thus been deprecated.
|
Configuration option :conf:`plugins.musicbrainz:enabled` has thus been
|
||||||
:bug:`2686` :bug:`4605`
|
deprecated. :bug:`2686` :bug:`4605`
|
||||||
- :doc:`plugins/web`: Show notifications when a track plays. This uses the Media
|
- :doc:`plugins/web`: Show notifications when a track plays. This uses the Media
|
||||||
Session API to customize media notifications.
|
Session API to customize media notifications.
|
||||||
- :doc:`plugins/discogs`: Add configurable ``search_limit`` option to limit the
|
- :doc:`plugins/discogs`: Add configurable :conf:`plugins.discogs:search_limit`
|
||||||
number of results returned by the Discogs metadata search queries.
|
option to limit the number of results returned by the Discogs metadata search
|
||||||
|
queries.
|
||||||
- :doc:`plugins/discogs`: Implement ``track_for_id`` method to allow retrieving
|
- :doc:`plugins/discogs`: Implement ``track_for_id`` method to allow retrieving
|
||||||
singletons by their Discogs ID. :bug:`4661`
|
singletons by their Discogs ID. :bug:`4661`
|
||||||
- :doc:`plugins/replace`: Add new plugin.
|
- :doc:`plugins/replace`: Add new plugin.
|
||||||
|
|
@ -159,12 +169,13 @@ New features:
|
||||||
be played for it to be counted as played instead of skipped.
|
be played for it to be counted as played instead of skipped.
|
||||||
- :doc:`plugins/web`: Display artist and album as part of the search results.
|
- :doc:`plugins/web`: Display artist and album as part of the search results.
|
||||||
- :doc:`plugins/spotify` :doc:`plugins/deezer`: Add new configuration option
|
- :doc:`plugins/spotify` :doc:`plugins/deezer`: Add new configuration option
|
||||||
``search_limit`` to limit the number of results returned by search queries.
|
:conf:`plugins.index:search_limit` to limit the number of results returned by
|
||||||
|
search queries.
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
||||||
- :doc:`plugins/musicbrainz`: fix regression where user configured
|
- :doc:`plugins/musicbrainz`: fix regression where user configured
|
||||||
``extra_tags`` have been read incorrectly. :bug:`5788`
|
:conf:`plugins.musicbrainz:extra_tags` have been read incorrectly. :bug:`5788`
|
||||||
- tests: Fix library tests failing on Windows when run from outside ``D:/``.
|
- tests: Fix library tests failing on Windows when run from outside ``D:/``.
|
||||||
:bug:`5802`
|
:bug:`5802`
|
||||||
- Fix an issue where calling ``Library.add`` would cause the ``database_change``
|
- Fix an issue where calling ``Library.add`` would cause the ``database_change``
|
||||||
|
|
@ -196,9 +207,10 @@ Bug fixes:
|
||||||
|
|
||||||
For packagers:
|
For packagers:
|
||||||
|
|
||||||
- Optional ``extra_tags`` parameter has been removed from
|
- Optional :conf:`plugins.musicbrainz:extra_tags` parameter has been removed
|
||||||
``BeetsPlugin.candidates`` method signature since it is never passed in. If
|
from ``BeetsPlugin.candidates`` method signature since it is never passed in.
|
||||||
you override this method in your plugin, feel free to remove this parameter.
|
If you override this method in your plugin, feel free to remove this
|
||||||
|
parameter.
|
||||||
- Loosened ``typing_extensions`` dependency in pyproject.toml to apply to every
|
- Loosened ``typing_extensions`` dependency in pyproject.toml to apply to every
|
||||||
python version.
|
python version.
|
||||||
|
|
||||||
|
|
@ -554,8 +566,9 @@ New features:
|
||||||
:bug:`4348`
|
:bug:`4348`
|
||||||
- Create the parental directories for database if they do not exist. :bug:`3808`
|
- Create the parental directories for database if they do not exist. :bug:`3808`
|
||||||
:bug:`4327`
|
:bug:`4327`
|
||||||
- :ref:`musicbrainz-config`: a new :ref:`musicbrainz.enabled` option allows
|
- :ref:`musicbrainz-config`: a new :conf:`plugins.musicbrainz:enabled` option
|
||||||
disabling the MusicBrainz metadata source during the autotagging process
|
allows disabling the MusicBrainz metadata source during the autotagging
|
||||||
|
process
|
||||||
- :doc:`/plugins/kodiupdate`: Now supports multiple kodi instances :bug:`4101`
|
- :doc:`/plugins/kodiupdate`: Now supports multiple kodi instances :bug:`4101`
|
||||||
- Add the item fields ``bitrate_mode``, ``encoder_info`` and
|
- Add the item fields ``bitrate_mode``, ``encoder_info`` and
|
||||||
``encoder_settings``.
|
``encoder_settings``.
|
||||||
|
|
@ -588,8 +601,8 @@ New features:
|
||||||
:bug:`4561` :bug:`4600`
|
:bug:`4561` :bug:`4600`
|
||||||
- :ref:`musicbrainz-config`: MusicBrainz release pages often link to related
|
- :ref:`musicbrainz-config`: MusicBrainz release pages often link to related
|
||||||
metadata sources like Discogs, Bandcamp, Spotify, Deezer and Beatport. When
|
metadata sources like Discogs, Bandcamp, Spotify, Deezer and Beatport. When
|
||||||
enabled via the :ref:`musicbrainz.external_ids` options, release ID's will be
|
enabled via the :conf:`plugins.musicbrainz:external_ids` options, release ID's
|
||||||
extracted from those URL's and imported to the library. :bug:`4220`
|
will be extracted from those URL's and imported to the library. :bug:`4220`
|
||||||
- :doc:`/plugins/convert`: Add support for generating m3u8 playlists together
|
- :doc:`/plugins/convert`: Add support for generating m3u8 playlists together
|
||||||
with converted media files. :bug:`4373`
|
with converted media files. :bug:`4373`
|
||||||
- Fetch the ``release_group_title`` field from MusicBrainz. :bug:`4809`
|
- Fetch the ``release_group_title`` field from MusicBrainz. :bug:`4809`
|
||||||
|
|
@ -943,8 +956,9 @@ Other new things:
|
||||||
|
|
||||||
- ``beet remove`` now also allows interactive selection of items from the query,
|
- ``beet remove`` now also allows interactive selection of items from the query,
|
||||||
similar to ``beet modify``.
|
similar to ``beet modify``.
|
||||||
- Enable HTTPS for MusicBrainz by default and add configuration option ``https``
|
- Enable HTTPS for MusicBrainz by default and add configuration option
|
||||||
for custom servers. See :ref:`musicbrainz-config` for more details.
|
:conf:`plugins.musicbrainz:https` for custom servers. See
|
||||||
|
:ref:`musicbrainz-config` for more details.
|
||||||
- :doc:`/plugins/mpdstats`: Add a new ``strip_path`` option to help build the
|
- :doc:`/plugins/mpdstats`: Add a new ``strip_path`` option to help build the
|
||||||
right local path from MPD information.
|
right local path from MPD information.
|
||||||
- :doc:`/plugins/convert`: Conversion can now parallelize conversion jobs on
|
- :doc:`/plugins/convert`: Conversion can now parallelize conversion jobs on
|
||||||
|
|
@ -964,8 +978,8 @@ Other new things:
|
||||||
server.
|
server.
|
||||||
- :doc:`/plugins/subsonicupdate`: The plugin now automatically chooses between
|
- :doc:`/plugins/subsonicupdate`: The plugin now automatically chooses between
|
||||||
token- and password-based authentication based on the server version.
|
token- and password-based authentication based on the server version.
|
||||||
- A new :ref:`extra_tags` configuration option lets you use more metadata in
|
- A new :conf:`plugins.musicbrainz:extra_tags` configuration option lets you use
|
||||||
MusicBrainz queries to further narrow the search.
|
more metadata in MusicBrainz queries to further narrow the search.
|
||||||
- A new :doc:`/plugins/fish` adds `Fish shell`_ tab autocompletion to beets.
|
- A new :doc:`/plugins/fish` adds `Fish shell`_ tab autocompletion to beets.
|
||||||
- :doc:`plugins/fetchart` and :doc:`plugins/embedart`: Added a new ``quality``
|
- :doc:`plugins/fetchart` and :doc:`plugins/embedart`: Added a new ``quality``
|
||||||
option that controls the quality of the image output when the image is
|
option that controls the quality of the image output when the image is
|
||||||
|
|
@ -1019,9 +1033,9 @@ Other new things:
|
||||||
(and now deprecated) separate ``host``, ``port``, and ``contextpath`` config
|
(and now deprecated) separate ``host``, ``port``, and ``contextpath`` config
|
||||||
options. As a consequence, the plugin can now talk to Subsonic over HTTPS.
|
options. As a consequence, the plugin can now talk to Subsonic over HTTPS.
|
||||||
Thanks to :user:`jef`. :bug:`3449`
|
Thanks to :user:`jef`. :bug:`3449`
|
||||||
- :doc:`/plugins/discogs`: The new ``index_tracks`` option enables incorporation
|
- :doc:`/plugins/discogs`: The new :conf:`plugins.discogs:index_tracks` option
|
||||||
of work names and intra-work divisions into imported track titles. Thanks to
|
enables incorporation of work names and intra-work divisions into imported
|
||||||
:user:`cole-miller`. :bug:`3459`
|
track titles. Thanks to :user:`cole-miller`. :bug:`3459`
|
||||||
- :doc:`/plugins/web`: The query API now interprets backslashes as path
|
- :doc:`/plugins/web`: The query API now interprets backslashes as path
|
||||||
separators to support path queries. Thanks to :user:`nmeum`. :bug:`3567`
|
separators to support path queries. Thanks to :user:`nmeum`. :bug:`3567`
|
||||||
- ``beet import`` now handles tar archives with bzip2 or gzip compression.
|
- ``beet import`` now handles tar archives with bzip2 or gzip compression.
|
||||||
|
|
@ -1035,9 +1049,9 @@ Other new things:
|
||||||
:user:`logan-arens`. :bug:`2947`
|
:user:`logan-arens`. :bug:`2947`
|
||||||
- There is a new ``--plugins`` (or ``-p``) CLI flag to specify a list of plugins
|
- There is a new ``--plugins`` (or ``-p``) CLI flag to specify a list of plugins
|
||||||
to load.
|
to load.
|
||||||
- A new :ref:`genres` option fetches genre information from MusicBrainz. This
|
- A new :conf:`plugins.musicbrainz:genres` option fetches genre information from
|
||||||
functionality depends on functionality that is currently unreleased in the
|
MusicBrainz. This functionality depends on functionality that is currently
|
||||||
python-musicbrainzngs_ library: see PR `#266
|
unreleased in the python-musicbrainzngs_ library: see PR `#266
|
||||||
<https://github.com/alastair/python-musicbrainzngs/pull/266>`_. Thanks to
|
<https://github.com/alastair/python-musicbrainzngs/pull/266>`_. Thanks to
|
||||||
:user:`aereaux`.
|
:user:`aereaux`.
|
||||||
- :doc:`/plugins/replaygain`: Analysis now happens in parallel using the
|
- :doc:`/plugins/replaygain`: Analysis now happens in parallel using the
|
||||||
|
|
@ -1077,9 +1091,10 @@ Fixes:
|
||||||
:bug:`3867`
|
:bug:`3867`
|
||||||
- :doc:`/plugins/web`: Fixed a small bug that caused the album art path to be
|
- :doc:`/plugins/web`: Fixed a small bug that caused the album art path to be
|
||||||
redacted even when ``include_paths`` option is set. :bug:`3866`
|
redacted even when ``include_paths`` option is set. :bug:`3866`
|
||||||
- :doc:`/plugins/discogs`: Fixed a bug with the ``index_tracks`` option that
|
- :doc:`/plugins/discogs`: Fixed a bug with the
|
||||||
sometimes caused the index to be discarded. Also, remove the extra semicolon
|
:conf:`plugins.discogs:index_tracks` option that sometimes caused the index to
|
||||||
that was added when there is no index track.
|
be discarded. Also, remove the extra semicolon that was added when there is no
|
||||||
|
index track.
|
||||||
- :doc:`/plugins/subsonicupdate`: The API client was using the ``POST`` method
|
- :doc:`/plugins/subsonicupdate`: The API client was using the ``POST`` method
|
||||||
rather the ``GET`` method. Also includes better exception handling, response
|
rather the ``GET`` method. Also includes better exception handling, response
|
||||||
parsing, and tests.
|
parsing, and tests.
|
||||||
|
|
@ -2695,9 +2710,9 @@ Major new features and bigger changes:
|
||||||
analysis tool. Thanks to :user:`jmwatte`. :bug:`1343`
|
analysis tool. Thanks to :user:`jmwatte`. :bug:`1343`
|
||||||
- A new ``filesize`` field on items indicates the number of bytes in the file.
|
- A new ``filesize`` field on items indicates the number of bytes in the file.
|
||||||
:bug:`1291`
|
:bug:`1291`
|
||||||
- A new :ref:`search_limit` configuration option allows you to specify how many
|
- A new :conf:`plugins.index:search_limit` configuration option allows you to
|
||||||
search results you wish to see when looking up releases at MusicBrainz during
|
specify how many search results you wish to see when looking up releases at
|
||||||
import. :bug:`1245`
|
MusicBrainz during import. :bug:`1245`
|
||||||
- The importer now records the data source for a match in a new flexible
|
- The importer now records the data source for a match in a new flexible
|
||||||
attribute ``data_source`` on items and albums. :bug:`1311`
|
attribute ``data_source`` on items and albums. :bug:`1311`
|
||||||
- The colors used in the terminal interface are now configurable via the new
|
- The colors used in the terminal interface are now configurable via the new
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add custom extensions directory to path
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent / "extensions"))
|
||||||
|
|
||||||
project = "beets"
|
project = "beets"
|
||||||
AUTHOR = "Adrian Sampson"
|
AUTHOR = "Adrian Sampson"
|
||||||
|
|
@ -26,6 +31,7 @@ extensions = [
|
||||||
"sphinx.ext.viewcode",
|
"sphinx.ext.viewcode",
|
||||||
"sphinx_design",
|
"sphinx_design",
|
||||||
"sphinx_copybutton",
|
"sphinx_copybutton",
|
||||||
|
"conf",
|
||||||
]
|
]
|
||||||
|
|
||||||
autosummary_generate = True
|
autosummary_generate = True
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ str.format-style string formatting. So you can write logging calls like this:
|
||||||
|
|
||||||
.. _pep 3101: https://www.python.org/dev/peps/pep-3101/
|
.. _pep 3101: https://www.python.org/dev/peps/pep-3101/
|
||||||
|
|
||||||
.. _standard python logging module: https://docs.python.org/2/library/logging.html
|
.. _standard python logging module: https://docs.python.org/3/library/logging.html
|
||||||
|
|
||||||
When beets is in verbose mode, plugin messages are prefixed with the plugin name
|
When beets is in verbose mode, plugin messages are prefixed with the plugin name
|
||||||
to make them easier to see.
|
to make them easier to see.
|
||||||
|
|
|
||||||
142
docs/extensions/conf.py
Normal file
142
docs/extensions/conf.py
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
"""Sphinx extension for simple configuration value documentation."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, ClassVar
|
||||||
|
|
||||||
|
from docutils import nodes
|
||||||
|
from docutils.parsers.rst import directives
|
||||||
|
from sphinx import addnodes
|
||||||
|
from sphinx.directives import ObjectDescription
|
||||||
|
from sphinx.domains import Domain, ObjType
|
||||||
|
from sphinx.roles import XRefRole
|
||||||
|
from sphinx.util.nodes import make_refnode
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Iterable, Sequence
|
||||||
|
|
||||||
|
from docutils.nodes import Element
|
||||||
|
from docutils.parsers.rst.states import Inliner
|
||||||
|
from sphinx.addnodes import desc_signature, pending_xref
|
||||||
|
from sphinx.application import Sphinx
|
||||||
|
from sphinx.builders import Builder
|
||||||
|
from sphinx.environment import BuildEnvironment
|
||||||
|
from sphinx.util.typing import ExtensionMetadata, OptionSpec
|
||||||
|
|
||||||
|
|
||||||
|
class Conf(ObjectDescription[str]):
|
||||||
|
"""Directive for documenting a single configuration value."""
|
||||||
|
|
||||||
|
option_spec: ClassVar[OptionSpec] = {
|
||||||
|
"default": directives.unchanged,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_signature(self, sig: str, signode: desc_signature) -> str:
|
||||||
|
"""Process the directive signature (the config name)."""
|
||||||
|
signode += addnodes.desc_name(sig, sig)
|
||||||
|
|
||||||
|
# Add default value if provided
|
||||||
|
if "default" in self.options:
|
||||||
|
signode += nodes.Text(" ")
|
||||||
|
default_container = nodes.inline("", "")
|
||||||
|
default_container += nodes.Text("(default: ")
|
||||||
|
default_container += nodes.literal("", self.options["default"])
|
||||||
|
default_container += nodes.Text(")")
|
||||||
|
signode += default_container
|
||||||
|
|
||||||
|
return sig
|
||||||
|
|
||||||
|
def add_target_and_index(
|
||||||
|
self, name: str, sig: str, signode: desc_signature
|
||||||
|
) -> None:
|
||||||
|
"""Add cross-reference target and index entry."""
|
||||||
|
target = f"conf-{name}"
|
||||||
|
if target not in self.state.document.ids:
|
||||||
|
signode["ids"].append(target)
|
||||||
|
self.state.document.note_explicit_target(signode)
|
||||||
|
|
||||||
|
# A unique full name which includes the document name
|
||||||
|
index_name = f"{self.env.docname.replace('/', '.')}:{name}"
|
||||||
|
# Register with the conf domain
|
||||||
|
domain = self.env.get_domain("conf")
|
||||||
|
domain.data["objects"][index_name] = (self.env.docname, target)
|
||||||
|
|
||||||
|
# Add to index
|
||||||
|
self.indexnode["entries"].append(
|
||||||
|
("single", f"{name} (configuration value)", target, "", None)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfDomain(Domain):
|
||||||
|
"""Domain for simple configuration values."""
|
||||||
|
|
||||||
|
name = "conf"
|
||||||
|
label = "Simple Configuration"
|
||||||
|
object_types = {"conf": ObjType("conf", "conf")}
|
||||||
|
directives = {"conf": Conf}
|
||||||
|
roles = {"conf": XRefRole()}
|
||||||
|
initial_data: dict[str, Any] = {"objects": {}}
|
||||||
|
|
||||||
|
def get_objects(self) -> Iterable[tuple[str, str, str, str, str, int]]:
|
||||||
|
"""Return an iterable of object tuples for the inventory."""
|
||||||
|
for name, (docname, targetname) in self.data["objects"].items():
|
||||||
|
# Remove the document name prefix for display
|
||||||
|
display_name = name.split(":")[-1]
|
||||||
|
yield (name, display_name, "conf", docname, targetname, 1)
|
||||||
|
|
||||||
|
def resolve_xref(
|
||||||
|
self,
|
||||||
|
env: BuildEnvironment,
|
||||||
|
fromdocname: str,
|
||||||
|
builder: Builder,
|
||||||
|
typ: str,
|
||||||
|
target: str,
|
||||||
|
node: pending_xref,
|
||||||
|
contnode: Element,
|
||||||
|
) -> Element | None:
|
||||||
|
if entry := self.data["objects"].get(target):
|
||||||
|
docname, targetid = entry
|
||||||
|
return make_refnode(
|
||||||
|
builder, fromdocname, docname, targetid, contnode
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# sphinx.util.typing.RoleFunction
|
||||||
|
def conf_role(
|
||||||
|
name: str,
|
||||||
|
rawtext: str,
|
||||||
|
text: str,
|
||||||
|
lineno: int,
|
||||||
|
inliner: Inliner,
|
||||||
|
/,
|
||||||
|
options: dict[str, Any] | None = None,
|
||||||
|
content: Sequence[str] = (),
|
||||||
|
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
|
||||||
|
"""Role for referencing configuration values."""
|
||||||
|
node = addnodes.pending_xref(
|
||||||
|
"",
|
||||||
|
refdomain="conf",
|
||||||
|
reftype="conf",
|
||||||
|
reftarget=text,
|
||||||
|
refwarn=True,
|
||||||
|
**(options or {}),
|
||||||
|
)
|
||||||
|
node += nodes.literal(text, text.split(":")[-1])
|
||||||
|
return [node], []
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||||
|
app.add_domain(ConfDomain)
|
||||||
|
|
||||||
|
# register a top-level directive so users can use ".. conf:: ..."
|
||||||
|
app.add_directive("conf", Conf)
|
||||||
|
|
||||||
|
# Register role with short name
|
||||||
|
app.add_role("conf", conf_role)
|
||||||
|
return {
|
||||||
|
"version": "0.1",
|
||||||
|
"parallel_read_safe": True,
|
||||||
|
"parallel_write_safe": True,
|
||||||
|
}
|
||||||
|
|
@ -35,15 +35,23 @@ Default
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
deezer:
|
deezer:
|
||||||
|
search_query_ascii: no
|
||||||
data_source_mismatch_penalty: 0.5
|
data_source_mismatch_penalty: 0.5
|
||||||
search_limit: 5
|
search_limit: 5
|
||||||
search_query_ascii: no
|
|
||||||
|
|
||||||
- **search_query_ascii**: If set to ``yes``, the search query will be converted
|
.. conf:: search_query_ascii
|
||||||
to ASCII before being sent to Deezer. Converting searches to ASCII can enhance
|
:default: no
|
||||||
search results in some cases, but in general, it is not recommended. For
|
|
||||||
instance ``artist:deadmau5 album:4×4`` will be converted to ``artist:deadmau5
|
If enabled, the search query will be converted to ASCII before being sent to
|
||||||
album:4x4`` (notice ``×!=x``). Default: ``no``.
|
Deezer. Converting searches to ASCII can enhance search results in some cases,
|
||||||
|
but in general, it is not recommended. For instance, ``artist:deadmau5
|
||||||
|
album:4×4`` will be converted to ``artist:deadmau5 album:4x4`` (notice
|
||||||
|
``×!=x``).
|
||||||
|
|
||||||
|
.. include:: ./shared_metadata_source_config.rst
|
||||||
|
|
||||||
|
Commands
|
||||||
|
--------
|
||||||
|
|
||||||
The ``deezer`` plugin provides an additional command ``deezerupdate`` to update
|
The ``deezer`` plugin provides an additional command ``deezerupdate`` to update
|
||||||
the ``rank`` information from Deezer. The ``rank`` (ranges from 0 to 1M) is a
|
the ``rank`` information from Deezer. The ``rank`` (ranges from 0 to 1M) is a
|
||||||
|
|
|
||||||
|
|
@ -71,67 +71,93 @@ Default
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
discogs:
|
discogs:
|
||||||
data_source_mismatch_penalty: 0.5
|
|
||||||
search_limit: 5
|
|
||||||
apikey: REDACTED
|
apikey: REDACTED
|
||||||
apisecret: REDACTED
|
apisecret: REDACTED
|
||||||
tokenfile: discogs_token.json
|
tokenfile: discogs_token.json
|
||||||
user_token: REDACTED
|
user_token:
|
||||||
index_tracks: no
|
index_tracks: no
|
||||||
append_style_genre: no
|
append_style_genre: no
|
||||||
separator: ', '
|
separator: ', '
|
||||||
strip_disambiguation: yes
|
strip_disambiguation: yes
|
||||||
|
featured_string: Feat.
|
||||||
- **index_tracks**: Index tracks (see the `Discogs guidelines`_) along with
|
|
||||||
headers, mark divisions between distinct works on the same release or within
|
|
||||||
works. When enabled, beets will incorporate the names of the divisions
|
|
||||||
containing each track into the imported track's title. Default: ``no``.
|
|
||||||
|
|
||||||
For example, importing `divisions album`_ would result in track names like:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
Messiah, Part I: No.1: Sinfony
|
|
||||||
Messiah, Part II: No.22: Chorus- Behold The Lamb Of God
|
|
||||||
Athalia, Act I, Scene I: Sinfonia
|
|
||||||
|
|
||||||
whereas with ``index_tracks`` disabled you'd get:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
No.1: Sinfony
|
|
||||||
No.22: Chorus- Behold The Lamb Of God
|
|
||||||
Sinfonia
|
|
||||||
|
|
||||||
This option is useful when importing classical music.
|
|
||||||
|
|
||||||
- **append_style_genre**: Appends the Discogs style (if found) to the genre tag.
|
|
||||||
This can be useful if you want more granular genres to categorize your music.
|
|
||||||
For example, a release in Discogs might have a genre of "Electronic" and a
|
|
||||||
style of "Techno": enabling this setting would set the genre to be
|
|
||||||
"Electronic, Techno" (assuming default separator of ``", "``) instead of just
|
|
||||||
"Electronic". Default: ``False``
|
|
||||||
- **separator**: How to join multiple genre and style values from Discogs into a
|
|
||||||
string. Default: ``", "``
|
|
||||||
- **strip_disambiguation**: Discogs uses strings like ``"(4)"`` to mark distinct
|
|
||||||
artists and labels with the same name. If you'd like to use the discogs
|
|
||||||
disambiguation in your tags, you can disable it. Default: ``True``
|
|
||||||
- **featured_string**: Configure the string used for noting featured artists.
|
|
||||||
Useful if you prefer ``Featuring`` or ``ft.``. Default: ``Feat.``
|
|
||||||
- **anv**: These configuration option are dedicated to handling Artist Name
|
|
||||||
Variations (ANVs). Sometimes a release credits artists differently compared to
|
|
||||||
the majority of their work. For example, "Basement Jaxx" may be credited as
|
|
||||||
"Tha Jaxx" or "The Basement Jaxx".You can select any combination of these
|
|
||||||
config options to control where beets writes and stores the variation credit.
|
|
||||||
The default, shown below, writes variations to the artist_credit field.
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
discogs:
|
|
||||||
anv:
|
anv:
|
||||||
artist_credit: True
|
artist_credit: yes
|
||||||
artist: False
|
artist: no
|
||||||
album_artist: False
|
album_artist: no
|
||||||
|
data_source_mismatch_penalty: 0.5
|
||||||
|
search_limit: 5
|
||||||
|
|
||||||
|
.. conf:: index_tracks
|
||||||
|
:default: no
|
||||||
|
|
||||||
|
Index tracks (see the `Discogs guidelines`_) along with headers, mark divisions
|
||||||
|
between distinct works on the same release or within works. When enabled,
|
||||||
|
beets will incorporate the names of the divisions containing each track into the
|
||||||
|
imported track's title.
|
||||||
|
|
||||||
|
For example, importing `divisions album`_ would result in track names like:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
Messiah, Part I: No.1: Sinfony
|
||||||
|
Messiah, Part II: No.22: Chorus- Behold The Lamb Of God
|
||||||
|
Athalia, Act I, Scene I: Sinfonia
|
||||||
|
|
||||||
|
whereas with ``index_tracks`` disabled you'd get:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
No.1: Sinfony
|
||||||
|
No.22: Chorus- Behold The Lamb Of God
|
||||||
|
Sinfonia
|
||||||
|
|
||||||
|
This option is useful when importing classical music.
|
||||||
|
|
||||||
|
.. conf:: append_style_genre
|
||||||
|
:default: no
|
||||||
|
|
||||||
|
Appends the Discogs style (if found) to the genre tag. This can be useful if
|
||||||
|
you want more granular genres to categorize your music. For example,
|
||||||
|
a release in Discogs might have a genre of "Electronic" and a style of
|
||||||
|
"Techno": enabling this setting would set the genre to be "Electronic,
|
||||||
|
Techno" (assuming default separator of ``", "``) instead of just
|
||||||
|
"Electronic".
|
||||||
|
|
||||||
|
.. conf:: separator
|
||||||
|
:default: ", "
|
||||||
|
|
||||||
|
How to join multiple genre and style values from Discogs into a string.
|
||||||
|
|
||||||
|
.. conf:: strip_disambiguation
|
||||||
|
:default: yes
|
||||||
|
|
||||||
|
Discogs uses strings like ``"(4)"`` to mark distinct artists and labels with
|
||||||
|
the same name. If you'd like to use the Discogs disambiguation in your tags,
|
||||||
|
you can disable this option.
|
||||||
|
|
||||||
|
.. conf:: featured_string
|
||||||
|
:default: Feat.
|
||||||
|
|
||||||
|
Configure the string used for noting featured artists. Useful if you prefer ``Featuring`` or ``ft.``.
|
||||||
|
|
||||||
|
.. conf:: anv
|
||||||
|
|
||||||
|
This configuration option is dedicated to handling Artist Name
|
||||||
|
Variations (ANVs). Sometimes a release credits artists differently compared to
|
||||||
|
the majority of their work. For example, "Basement Jaxx" may be credited as
|
||||||
|
"Tha Jaxx" or "The Basement Jaxx". You can select any combination of these
|
||||||
|
config options to control where beets writes and stores the variation credit.
|
||||||
|
The default, shown below, writes variations to the artist_credit field.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
discogs:
|
||||||
|
anv:
|
||||||
|
artist_credit: yes
|
||||||
|
artist: no
|
||||||
|
album_artist: no
|
||||||
|
|
||||||
|
.. include:: ./shared_metadata_source_config.rst
|
||||||
|
|
||||||
.. _discogs guidelines: https://support.discogs.com/hc/en-us/articles/360005055373-Database-Guidelines-12-Tracklisting#Index_Tracks_And_Headings
|
.. _discogs guidelines: https://support.discogs.com/hc/en-us/articles/360005055373-Database-Guidelines-12-Tracklisting#Index_Tracks_And_Headings
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ These options match the options from the `Python csv module`_.
|
||||||
|
|
||||||
.. _python csv module: https://docs.python.org/3/library/csv.html#csv-fmt-params
|
.. _python csv module: https://docs.python.org/3/library/csv.html#csv-fmt-params
|
||||||
|
|
||||||
.. _python json module: https://docs.python.org/2/library/json.html#basic-usage
|
.. _python json module: https://docs.python.org/3/library/json.html#basic-usage
|
||||||
|
|
||||||
The default options look like this:
|
The default options look like this:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,65 +50,7 @@ Using Metadata Source Plugins
|
||||||
We provide several :ref:`autotagger_extensions` that fetch metadata from online
|
We provide several :ref:`autotagger_extensions` that fetch metadata from online
|
||||||
databases. They share the following configuration options:
|
databases. They share the following configuration options:
|
||||||
|
|
||||||
.. _data_source_mismatch_penalty:
|
.. include:: ./shared_metadata_source_config.rst
|
||||||
|
|
||||||
- **data_source_mismatch_penalty**: Penalty applied when the data source of a
|
|
||||||
match candidate differs from the original source of your existing tracks. Any
|
|
||||||
decimal number between 0.0 and 1.0. Default: ``0.5``.
|
|
||||||
|
|
||||||
This setting controls how much to penalize matches from different metadata
|
|
||||||
sources during import. The penalty is applied when beets detects that a match
|
|
||||||
candidate comes from a different data source than what appears to be the
|
|
||||||
original source of your music collection.
|
|
||||||
|
|
||||||
**Example configurations:**
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
# Prefer MusicBrainz over Discogs when sources don't match
|
|
||||||
plugins: musicbrainz discogs
|
|
||||||
|
|
||||||
musicbrainz:
|
|
||||||
data_source_mismatch_penalty: 0.3 # Lower penalty = preferred
|
|
||||||
discogs:
|
|
||||||
data_source_mismatch_penalty: 0.8 # Higher penalty = less preferred
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
# Do not penalise candidates from Discogs at all
|
|
||||||
plugins: musicbrainz discogs
|
|
||||||
|
|
||||||
musicbrainz:
|
|
||||||
data_source_mismatch_penalty: 0.5
|
|
||||||
discogs:
|
|
||||||
data_source_mismatch_penalty: 0.0
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
# Disable cross-source penalties entirely
|
|
||||||
plugins: musicbrainz discogs
|
|
||||||
|
|
||||||
musicbrainz:
|
|
||||||
data_source_mismatch_penalty: 0.0
|
|
||||||
discogs:
|
|
||||||
data_source_mismatch_penalty: 0.0
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
The last configuration is equivalent to setting:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
match:
|
|
||||||
distance_weights:
|
|
||||||
data_source: 0.0 # Disable data source matching
|
|
||||||
|
|
||||||
- **source_weight**
|
|
||||||
|
|
||||||
.. deprecated:: 2.5 Use `data_source_mismatch_penalty`_ instead.
|
|
||||||
|
|
||||||
- **search_limit**: Maximum number of search results to consider. Default:
|
|
||||||
``5``.
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:hidden:
|
:hidden:
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,6 @@ Default
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
musicbrainz:
|
musicbrainz:
|
||||||
data_source_mismatch_penalty: 0.5
|
|
||||||
search_limit: 5
|
|
||||||
host: musicbrainz.org
|
host: musicbrainz.org
|
||||||
https: no
|
https: no
|
||||||
ratelimit: 1
|
ratelimit: 1
|
||||||
|
|
@ -41,122 +39,107 @@ Default
|
||||||
deezer: no
|
deezer: no
|
||||||
beatport: no
|
beatport: no
|
||||||
tidal: no
|
tidal: no
|
||||||
|
data_source_mismatch_penalty: 0.5
|
||||||
|
search_limit: 5
|
||||||
|
|
||||||
You can instruct beets to use `your own MusicBrainz database
|
.. conf:: host
|
||||||
<https://musicbrainz.org/doc/MusicBrainz_Server/Setup>`__ instead of the
|
:default: musicbrainz.org
|
||||||
|
|
||||||
`main server`_. Use the ``host``, ``https`` and ``ratelimit`` options under a
|
The Web server hostname (and port, optionally) that will be contacted by beets.
|
||||||
``musicbrainz:`` header, like so
|
You can use this to configure beets to use `your own MusicBrainz database
|
||||||
|
<https://musicbrainz.org/doc/MusicBrainz_Server/Setup>`__ instead of the
|
||||||
|
`main server`_.
|
||||||
|
|
||||||
.. code-block:: yaml
|
The server must have search indices enabled (see `Building search indexes`_).
|
||||||
|
|
||||||
musicbrainz:
|
Example:
|
||||||
host: localhost:5000
|
|
||||||
https: no
|
|
||||||
ratelimit: 100
|
|
||||||
|
|
||||||
The ``host`` key, of course, controls the Web server hostname (and port,
|
.. code-block:: yaml
|
||||||
optionally) that will be contacted by beets (default: musicbrainz.org). The
|
|
||||||
``https`` key makes the client use HTTPS instead of HTTP. This setting applies
|
|
||||||
only to custom servers. The official MusicBrainz server always uses HTTPS.
|
|
||||||
(Default: no.) The server must have search indices enabled (see `Building search
|
|
||||||
indexes`_).
|
|
||||||
|
|
||||||
The ``ratelimit`` option, an integer, controls the number of Web service
|
musicbrainz:
|
||||||
requests per second (default: 1). **Do not change the rate limit setting** if
|
host: localhost:5000
|
||||||
you're using the main MusicBrainz server---on this public server, you're
|
|
||||||
limited_ to one request per second.
|
.. conf:: https
|
||||||
|
:default: no
|
||||||
|
|
||||||
|
Makes the client use HTTPS instead of HTTP. This setting applies only to custom
|
||||||
|
servers. The official MusicBrainz server always uses HTTPS.
|
||||||
|
|
||||||
|
.. conf:: ratelimit
|
||||||
|
:default: 1
|
||||||
|
|
||||||
|
Controls the number of Web service requests per second.
|
||||||
|
|
||||||
|
**Do not change the rate limit setting** if you're using the main MusicBrainz
|
||||||
|
server---on this public server, you're limited_ to one request per second.
|
||||||
|
|
||||||
|
.. conf:: ratelimit_interval
|
||||||
|
:default: 1.0
|
||||||
|
|
||||||
|
The time interval (in seconds) for the rate limit.
|
||||||
|
|
||||||
|
.. conf:: enabled
|
||||||
|
:default: yes
|
||||||
|
|
||||||
|
.. deprecated:: 2.4 Add ``musicbrainz`` to the ``plugins`` list instead.
|
||||||
|
|
||||||
|
.. conf:: extra_tags
|
||||||
|
:default: []
|
||||||
|
|
||||||
|
By default, beets will use only the artist, album, and track count to query
|
||||||
|
MusicBrainz. Additional tags to be queried can be supplied with the
|
||||||
|
``extra_tags`` setting.
|
||||||
|
|
||||||
|
This setting should improve the autotagger results if the metadata with the
|
||||||
|
given tags match the metadata returned by MusicBrainz.
|
||||||
|
|
||||||
|
Note that the only tags supported by this setting are: ``barcode``,
|
||||||
|
``catalognum``, ``country``, ``label``, ``media``, and ``year``.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
musicbrainz:
|
||||||
|
extra_tags: [barcode, catalognum, country, label, media, year]
|
||||||
|
|
||||||
|
.. conf:: genres
|
||||||
|
:default: no
|
||||||
|
|
||||||
|
Use MusicBrainz genre tags to populate (and replace if it's already set) the
|
||||||
|
``genre`` tag. This will make it a list of all the genres tagged for the release
|
||||||
|
and the release-group on MusicBrainz, separated by "; " and sorted by the total
|
||||||
|
number of votes.
|
||||||
|
|
||||||
|
.. conf:: external_ids
|
||||||
|
|
||||||
|
**Default**
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
musicbrainz:
|
||||||
|
external_ids:
|
||||||
|
discogs: no
|
||||||
|
spotify: no
|
||||||
|
bandcamp: no
|
||||||
|
beatport: no
|
||||||
|
deezer: no
|
||||||
|
tidal: no
|
||||||
|
|
||||||
|
Set any of the ``external_ids`` options to ``yes`` to enable the MusicBrainz
|
||||||
|
importer to look for links to related metadata sources. If such a link is
|
||||||
|
available the release ID will be extracted from the URL provided and imported to
|
||||||
|
the beets library.
|
||||||
|
|
||||||
|
The library fields of the corresponding :ref:`autotagger_extensions` are used to
|
||||||
|
save the data as flexible attributes (``discogs_album_id``, ``bandcamp_album_id``, ``spotify_album_id``,
|
||||||
|
``beatport_album_id``, ``deezer_album_id``, ``tidal_album_id``). On re-imports
|
||||||
|
existing data will be overwritten.
|
||||||
|
|
||||||
|
.. include:: ./shared_metadata_source_config.rst
|
||||||
|
|
||||||
.. _building search indexes: https://musicbrainz.org/doc/Development/Search_server_setup
|
.. _building search indexes: https://musicbrainz.org/doc/Development/Search_server_setup
|
||||||
|
|
||||||
.. _limited: https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
|
.. _limited: https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
|
||||||
|
|
||||||
.. _main server: https://musicbrainz.org/
|
.. _main server: https://musicbrainz.org/
|
||||||
|
|
||||||
.. _musicbrainz.enabled:
|
|
||||||
|
|
||||||
enabled
|
|
||||||
+++++++
|
|
||||||
|
|
||||||
.. deprecated:: 2.4 Add ``musicbrainz`` to the ``plugins`` list instead.
|
|
||||||
|
|
||||||
This option allows you to disable using MusicBrainz as a metadata source. This
|
|
||||||
applies if you use plugins that fetch data from alternative sources and should
|
|
||||||
make the import process quicker.
|
|
||||||
|
|
||||||
Default: ``yes``.
|
|
||||||
|
|
||||||
.. _search_limit:
|
|
||||||
|
|
||||||
search_limit
|
|
||||||
++++++++++++
|
|
||||||
|
|
||||||
The number of matches returned when sending search queries to the MusicBrainz
|
|
||||||
server.
|
|
||||||
|
|
||||||
Default: ``5``.
|
|
||||||
|
|
||||||
searchlimit
|
|
||||||
+++++++++++
|
|
||||||
|
|
||||||
.. deprecated:: 2.4 Use `search_limit`_.
|
|
||||||
|
|
||||||
.. _extra_tags:
|
|
||||||
|
|
||||||
extra_tags
|
|
||||||
++++++++++
|
|
||||||
|
|
||||||
By default, beets will use only the artist, album, and track count to query
|
|
||||||
MusicBrainz. Additional tags to be queried can be supplied with the
|
|
||||||
``extra_tags`` setting. For example
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
musicbrainz:
|
|
||||||
extra_tags: [barcode, catalognum, country, label, media, year]
|
|
||||||
|
|
||||||
This setting should improve the autotagger results if the metadata with the
|
|
||||||
given tags match the metadata returned by MusicBrainz.
|
|
||||||
|
|
||||||
Note that the only tags supported by this setting are the ones listed in the
|
|
||||||
above example.
|
|
||||||
|
|
||||||
Default: ``[]``
|
|
||||||
|
|
||||||
.. _genres:
|
|
||||||
|
|
||||||
genres
|
|
||||||
++++++
|
|
||||||
|
|
||||||
Use MusicBrainz genre tags to populate (and replace if it's already set) the
|
|
||||||
``genre`` tag. This will make it a list of all the genres tagged for the release
|
|
||||||
and the release-group on MusicBrainz, separated by "; " and sorted by the total
|
|
||||||
number of votes. Default: ``no``
|
|
||||||
|
|
||||||
.. _musicbrainz.external_ids:
|
|
||||||
|
|
||||||
external_ids
|
|
||||||
++++++++++++
|
|
||||||
|
|
||||||
Set any of the ``external_ids`` options to ``yes`` to enable the MusicBrainz
|
|
||||||
importer to look for links to related metadata sources. If such a link is
|
|
||||||
available the release ID will be extracted from the URL provided and imported to
|
|
||||||
the beets library
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
musicbrainz:
|
|
||||||
external_ids:
|
|
||||||
discogs: yes
|
|
||||||
spotify: yes
|
|
||||||
bandcamp: yes
|
|
||||||
beatport: yes
|
|
||||||
deezer: yes
|
|
||||||
tidal: yes
|
|
||||||
|
|
||||||
The library fields of the corresponding :ref:`autotagger_extensions` are used to
|
|
||||||
save the data (``discogs_albumid``, ``bandcamp_album_id``, ``spotify_album_id``,
|
|
||||||
``beatport_album_id``, ``deezer_album_id``, ``tidal_album_id``). On re-imports
|
|
||||||
existing data will be overwritten.
|
|
||||||
|
|
||||||
The default of all options is ``no``.
|
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,15 @@ string, use ``$args`` to indicate where to insert them. For example:
|
||||||
indicates that you need to insert extra arguments before specifying the
|
indicates that you need to insert extra arguments before specifying the
|
||||||
playlist.
|
playlist.
|
||||||
|
|
||||||
|
Some players require a different syntax. For example, with ``mpv`` the optional
|
||||||
|
``$playlist`` variable can be used to match the syntax of the ``--playlist``
|
||||||
|
option:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
play:
|
||||||
|
command: mpv $args --playlist=$playlist
|
||||||
|
|
||||||
The ``--yes`` (or ``-y``) flag to the ``play`` command will skip the warning
|
The ``--yes`` (or ``-y``) flag to the ``play`` command will skip the warning
|
||||||
message if you choose to play more items than the **warning_threshold** value
|
message if you choose to play more items than the **warning_threshold** value
|
||||||
usually allows.
|
usually allows.
|
||||||
|
|
@ -123,4 +132,4 @@ until they are externally wiped could be an issue for privacy or storage
|
||||||
reasons. If this is the case for you, you might want to use the ``raw`` config
|
reasons. If this is the case for you, you might want to use the ``raw`` config
|
||||||
option described above.
|
option described above.
|
||||||
|
|
||||||
.. _tempfile.tempdir: https://docs.python.org/2/library/tempfile.html#tempfile.tempdir
|
.. _tempfile.tempdir: https://docs.python.org/3/library/tempfile.html#tempfile.tempdir
|
||||||
|
|
|
||||||
65
docs/plugins/shared_metadata_source_config.rst
Normal file
65
docs/plugins/shared_metadata_source_config.rst
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
.. _data_source_mismatch_penalty:
|
||||||
|
|
||||||
|
.. conf:: data_source_mismatch_penalty
|
||||||
|
:default: 0.5
|
||||||
|
|
||||||
|
Penalty applied when the data source of a
|
||||||
|
match candidate differs from the original source of your existing tracks. Any
|
||||||
|
decimal number between 0.0 and 1.0
|
||||||
|
|
||||||
|
This setting controls how much to penalize matches from different metadata
|
||||||
|
sources during import. The penalty is applied when beets detects that a match
|
||||||
|
candidate comes from a different data source than what appears to be the
|
||||||
|
original source of your music collection.
|
||||||
|
|
||||||
|
**Example configurations:**
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# Prefer MusicBrainz over Discogs when sources don't match
|
||||||
|
plugins: musicbrainz discogs
|
||||||
|
|
||||||
|
musicbrainz:
|
||||||
|
data_source_mismatch_penalty: 0.3 # Lower penalty = preferred
|
||||||
|
discogs:
|
||||||
|
data_source_mismatch_penalty: 0.8 # Higher penalty = less preferred
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# Do not penalise candidates from Discogs at all
|
||||||
|
plugins: musicbrainz discogs
|
||||||
|
|
||||||
|
musicbrainz:
|
||||||
|
data_source_mismatch_penalty: 0.5
|
||||||
|
discogs:
|
||||||
|
data_source_mismatch_penalty: 0.0
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# Disable cross-source penalties entirely
|
||||||
|
plugins: musicbrainz discogs
|
||||||
|
|
||||||
|
musicbrainz:
|
||||||
|
data_source_mismatch_penalty: 0.0
|
||||||
|
discogs:
|
||||||
|
data_source_mismatch_penalty: 0.0
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
The last configuration is equivalent to setting:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
match:
|
||||||
|
distance_weights:
|
||||||
|
data_source: 0.0 # Disable data source matching
|
||||||
|
|
||||||
|
.. conf:: source_weight
|
||||||
|
:default: 0.5
|
||||||
|
|
||||||
|
.. deprecated:: 2.5 Use `data_source_mismatch_penalty`_ instead.
|
||||||
|
|
||||||
|
.. conf:: search_limit
|
||||||
|
:default: 5
|
||||||
|
|
||||||
|
Maximum number of search results to return.
|
||||||
|
|
@ -73,8 +73,6 @@ Default
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
spotify:
|
spotify:
|
||||||
data_source_mismatch_penalty: 0.5
|
|
||||||
search_limit: 5
|
|
||||||
mode: list
|
mode: list
|
||||||
region_filter:
|
region_filter:
|
||||||
show_failures: no
|
show_failures: no
|
||||||
|
|
@ -84,59 +82,67 @@ Default
|
||||||
client_id: REDACTED
|
client_id: REDACTED
|
||||||
client_secret: REDACTED
|
client_secret: REDACTED
|
||||||
tokenfile: spotify_token.json
|
tokenfile: spotify_token.json
|
||||||
|
data_source_mismatch_penalty: 0.5
|
||||||
|
search_limit: 5
|
||||||
|
|
||||||
- **mode**: One of the following:
|
.. conf:: mode
|
||||||
|
:default: list
|
||||||
|
|
||||||
- ``list``: Print out the playlist as a list of links. This list can then
|
Controls how the playlist is output:
|
||||||
be pasted in to a new or existing Spotify playlist.
|
|
||||||
- ``open``: This mode actually sends a link to your default browser with
|
|
||||||
instructions to open Spotify with the playlist you created. Until this
|
|
||||||
has been tested on all platforms, it will remain optional.
|
|
||||||
|
|
||||||
Default: ``list``.
|
- ``list``: Print out the playlist as a list of links. This list can then
|
||||||
|
be pasted in to a new or existing Spotify playlist.
|
||||||
|
- ``open``: This mode actually sends a link to your default browser with
|
||||||
|
instructions to open Spotify with the playlist you created. Until this
|
||||||
|
has been tested on all platforms, it will remain optional.
|
||||||
|
|
||||||
- **region_filter**: A two-character country abbreviation, to limit results to
|
.. conf:: region_filter
|
||||||
that market. Default: None.
|
:default:
|
||||||
- **show_failures**: List each lookup that does not return a Spotify ID (and
|
|
||||||
therefore cannot be added to a playlist). Default: ``no``.
|
|
||||||
- **tiebreak**: How to choose the track if there is more than one identical
|
|
||||||
result. For example, there might be multiple releases of the same album. The
|
|
||||||
options are ``popularity`` and ``first`` (to just choose the first match
|
|
||||||
returned). Default: ``popularity``.
|
|
||||||
- **regex**: An array of regex transformations to perform on the
|
|
||||||
track/album/artist fields before sending them to Spotify. Can be useful for
|
|
||||||
changing certain abbreviations, like ft. -> feat. See the examples below.
|
|
||||||
Default: None.
|
|
||||||
- **search_query_ascii**: If set to ``yes``, the search query will be converted
|
|
||||||
to ASCII before being sent to Spotify. Converting searches to ASCII can
|
|
||||||
enhance search results in some cases, but in general, it is not recommended.
|
|
||||||
For instance ``artist:deadmau5 album:4×4`` will be converted to
|
|
||||||
``artist:deadmau5 album:4x4`` (notice ``×!=x``). Default: ``no``.
|
|
||||||
|
|
||||||
Here's an example:
|
A two-character country abbreviation, to limit results to that market.
|
||||||
|
|
||||||
::
|
.. conf:: show_failures
|
||||||
|
:default: no
|
||||||
|
|
||||||
spotify:
|
List each lookup that does not return a Spotify ID (and therefore cannot be
|
||||||
data_source_mismatch_penalty: 0.7
|
added to a playlist).
|
||||||
mode: open
|
|
||||||
region_filter: US
|
|
||||||
show_failures: on
|
|
||||||
tiebreak: first
|
|
||||||
search_query_ascii: no
|
|
||||||
|
|
||||||
regex: [
|
.. conf:: tiebreak
|
||||||
{
|
:default: popularity
|
||||||
field: "albumartist", # Field in the item object to regex.
|
|
||||||
search: "Something", # String to look for.
|
How to choose the candidate if there is more than one identical result. For
|
||||||
replace: "Replaced" # Replacement value.
|
example, there might be multiple releases of the same album.
|
||||||
},
|
|
||||||
{
|
- ``popularity``: pick the more popular candidate
|
||||||
field: "title",
|
- ``first``: pick the first candidate
|
||||||
search: "Something Else",
|
|
||||||
replace: "AlsoReplaced"
|
.. conf:: regex
|
||||||
}
|
:default: []
|
||||||
]
|
|
||||||
|
An array of regex transformations to perform on the track/album/artist fields
|
||||||
|
before sending them to Spotify. Can be useful for changing certain
|
||||||
|
abbreviations, like ft. -> feat. For example:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
regex:
|
||||||
|
- field: albumartist
|
||||||
|
search: Something
|
||||||
|
replace: Replaced
|
||||||
|
- field: title
|
||||||
|
search: Something Else
|
||||||
|
replace: AlsoReplaced
|
||||||
|
|
||||||
|
.. conf:: search_query_ascii
|
||||||
|
:default: no
|
||||||
|
|
||||||
|
If enabled, the search query will be converted to ASCII before being sent to
|
||||||
|
Spotify. Converting searches to ASCII can enhance search results in some
|
||||||
|
cases, but in general, it is not recommended. For instance,
|
||||||
|
``artist:deadmau5 album:4×4`` will be converted to ``artist:deadmau5
|
||||||
|
album:4x4`` (notice ``×!=x``).
|
||||||
|
|
||||||
|
.. include:: ./shared_metadata_source_config.rst
|
||||||
|
|
||||||
Obtaining Track Popularity and Audio Features from Spotify
|
Obtaining Track Popularity and Audio Features from Spotify
|
||||||
----------------------------------------------------------
|
----------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,7 @@ terminal_encoding
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The text encoding, as `known to Python
|
The text encoding, as `known to Python
|
||||||
<https://docs.python.org/2/library/codecs.html#standard-encodings>`__, to use
|
<https://docs.python.org/3/library/codecs.html#standard-encodings>`__, to use
|
||||||
for messages printed to the standard output. It's also used to read messages
|
for messages printed to the standard output. It's also used to read messages
|
||||||
from the standard input. By default, this is determined automatically from the
|
from the standard input. By default, this is determined automatically from the
|
||||||
locale environment variables.
|
locale environment variables.
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ def create_rst_replacements() -> list[Replacement]:
|
||||||
# Replace Sphinx directives by documentation URLs, e.g.,
|
# Replace Sphinx directives by documentation URLs, e.g.,
|
||||||
# :ref:`/plugins/autobpm` -> [AutoBPM Plugin](DOCS/plugins/autobpm.html)
|
# :ref:`/plugins/autobpm` -> [AutoBPM Plugin](DOCS/plugins/autobpm.html)
|
||||||
(
|
(
|
||||||
r":(?:ref|doc|class):`+(?:([^`<]+)<)?/?([\w./_-]+)>?`+",
|
r":(?:ref|doc|class|conf):`+(?:([^`<]+)<)?/?([\w.:/_-]+)>?`+",
|
||||||
lambda m: make_ref_link(m[2], m[1]),
|
lambda m: make_ref_link(m[2], m[1]),
|
||||||
),
|
),
|
||||||
# Convert command references to documentation URLs
|
# Convert command references to documentation URLs
|
||||||
|
|
|
||||||
15
poetry.lock
generated
15
poetry.lock
generated
|
|
@ -3473,6 +3473,17 @@ files = [
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
types-html5lib = "*"
|
types-html5lib = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-docutils"
|
||||||
|
version = "0.22.2.20251006"
|
||||||
|
description = "Typing stubs for docutils"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
files = [
|
||||||
|
{file = "types_docutils-0.22.2.20251006-py3-none-any.whl", hash = "sha256:1e61afdeb4fab4ae802034deea3e853ced5c9b5e1d156179000cb68c85daf384"},
|
||||||
|
{file = "types_docutils-0.22.2.20251006.tar.gz", hash = "sha256:c36c0459106eda39e908e9147bcff9dbd88535975cde399433c428a517b9e3b2"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-flask-cors"
|
name = "types-flask-cors"
|
||||||
version = "6.0.0.20250520"
|
version = "6.0.0.20250520"
|
||||||
|
|
@ -3650,7 +3661,7 @@ beatport = ["requests-oauthlib"]
|
||||||
bpd = ["PyGObject"]
|
bpd = ["PyGObject"]
|
||||||
chroma = ["pyacoustid"]
|
chroma = ["pyacoustid"]
|
||||||
discogs = ["python3-discogs-client"]
|
discogs = ["python3-discogs-client"]
|
||||||
docs = ["pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinx-design"]
|
docs = ["docutils", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinx-design"]
|
||||||
embedart = ["Pillow"]
|
embedart = ["Pillow"]
|
||||||
embyupdate = ["requests"]
|
embyupdate = ["requests"]
|
||||||
fetchart = ["Pillow", "beautifulsoup4", "langdetect", "requests"]
|
fetchart = ["Pillow", "beautifulsoup4", "langdetect", "requests"]
|
||||||
|
|
@ -3672,4 +3683,4 @@ web = ["flask", "flask-cors"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.9,<4"
|
python-versions = ">=3.9,<4"
|
||||||
content-hash = "1db39186aca430ef6f1fd9e51b9dcc3ed91880a458bc21b22d950ed8589fdf5a"
|
content-hash = "aedfeb1ac78ae0120855c6a7d6f35963c63cc50a8750142c95dd07ffd213683f"
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,11 @@ resampy = { version = ">=0.4.3", optional = true }
|
||||||
requests-oauthlib = { version = ">=0.6.1", optional = true }
|
requests-oauthlib = { version = ">=0.6.1", optional = true }
|
||||||
soco = { version = "*", optional = true }
|
soco = { version = "*", optional = true }
|
||||||
|
|
||||||
|
docutils = { version = ">=0.20.1", optional = true }
|
||||||
pydata-sphinx-theme = { version = "*", optional = true }
|
pydata-sphinx-theme = { version = "*", optional = true }
|
||||||
sphinx = { version = "*", optional = true }
|
sphinx = { version = "*", optional = true }
|
||||||
sphinx-design = { version = "^0.6.1", optional = true }
|
sphinx-design = { version = ">=0.6.1", optional = true }
|
||||||
sphinx-copybutton = { version = "^0.5.2", optional = true }
|
sphinx-copybutton = { version = ">=0.5.2", optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.poetry.group.test.dependencies]
|
||||||
beautifulsoup4 = "*"
|
beautifulsoup4 = "*"
|
||||||
|
|
@ -109,6 +110,7 @@ sphinx-lint = ">=1.0.0"
|
||||||
[tool.poetry.group.typing.dependencies]
|
[tool.poetry.group.typing.dependencies]
|
||||||
mypy = "*"
|
mypy = "*"
|
||||||
types-beautifulsoup4 = "*"
|
types-beautifulsoup4 = "*"
|
||||||
|
types-docutils = ">=0.22.2.20251006"
|
||||||
types-mock = "*"
|
types-mock = "*"
|
||||||
types-Flask-Cors = "*"
|
types-Flask-Cors = "*"
|
||||||
types-Pillow = "*"
|
types-Pillow = "*"
|
||||||
|
|
@ -131,7 +133,14 @@ beatport = ["requests-oauthlib"]
|
||||||
bpd = ["PyGObject"] # gobject-introspection, gstreamer1.0-plugins-base, python3-gst-1.0
|
bpd = ["PyGObject"] # gobject-introspection, gstreamer1.0-plugins-base, python3-gst-1.0
|
||||||
chroma = ["pyacoustid"] # chromaprint or fpcalc
|
chroma = ["pyacoustid"] # chromaprint or fpcalc
|
||||||
# convert # ffmpeg
|
# convert # ffmpeg
|
||||||
docs = ["pydata-sphinx-theme", "sphinx", "sphinx-lint", "sphinx-design", "sphinx-copybutton"]
|
docs = [
|
||||||
|
"docutils",
|
||||||
|
"pydata-sphinx-theme",
|
||||||
|
"sphinx",
|
||||||
|
"sphinx-lint",
|
||||||
|
"sphinx-design",
|
||||||
|
"sphinx-copybutton",
|
||||||
|
]
|
||||||
discogs = ["python3-discogs-client"]
|
discogs = ["python3-discogs-client"]
|
||||||
embedart = ["Pillow"] # ImageMagick
|
embedart = ["Pillow"] # ImageMagick
|
||||||
embyupdate = ["requests"]
|
embyupdate = ["requests"]
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,19 @@ class PlayPluginTest(CleanupModulesMixin, PluginTestCase):
|
||||||
|
|
||||||
open_mock.assert_called_once_with([self.item.path], "echo")
|
open_mock.assert_called_once_with([self.item.path], "echo")
|
||||||
|
|
||||||
|
def test_pls_marker(self, open_mock):
|
||||||
|
self.config["play"]["command"] = (
|
||||||
|
"echo --some params --playlist=$playlist --some-more params"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.run_command("play", "nice")
|
||||||
|
|
||||||
|
open_mock.assert_called_once
|
||||||
|
|
||||||
|
commandstr = open_mock.call_args_list[0][0][1]
|
||||||
|
assert commandstr.startswith("echo --some params --playlist=")
|
||||||
|
assert commandstr.endswith(" --some-more params")
|
||||||
|
|
||||||
def test_not_found(self, open_mock):
|
def test_not_found(self, open_mock):
|
||||||
self.run_command("play", "not found")
|
self.run_command("play", "not found")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue