Commit graph

13265 commits

Author SHA1 Message Date
J0J0 Todos
4f95a2f25a importsource: Catch importer crash when skipping
Catches a crash when "skip" is selected in the importer and
task.imported_items() runs into a condition branch that supposedly
should never be reached:

  File "beets/beets/importer/tasks.py", line 254, in imported_items
    assert False
2025-12-05 16:59:28 +01:00
Guy Bloom
2bd77b9895
Fix convert --format with never_convert_lossy_files (#6171)
## Description

Fixes #5625 

When `convert.never_convert_lossy_files` is enabled, `beet convert` was
ignoring the explicit `--format` option and just copying the lossy files
without
transcoding them. For example:

- `beet convert format:mp3 --format opus`

would still produce MP3 files instead of OPUS.

Change:

- Allows to override options `never_convert_lossy_files`, `max_bitrate`
or `no_convert` for `beet convert` as well as trying to convert to the
same format as existing already with a new option `--force`. That way,
for example lossy files selected by the query are transcoded to the
requested format anyway.
- Keeps existing behavior for automatic conversion on import (no CLI
override there).
- Adds tests to cover checking whether `--force` correctly overrides
settings or CLI options.
- Documents the behavior in the convert plugin docs

Co-authored-by: J0J0 Todos <jojo@peek-a-boo.at>
2025-12-03 22:48:41 +01:00
Sebastian Mohr
53931279a3
docs: fix link to plugin development docs (#6198)
Fixes the link to the plugin development documentation.
2025-12-02 11:45:05 +01:00
Robin Bowes
fdaebc653a docs: Fix link to plugin development docs 2025-12-02 11:40:18 +01:00
Sebastian Mohr
ca7e959f5b
Sanitize log messages by removing control characters (#6199)
This pull request addresses an issue where control characters in log
messages could halt beets execution entirely. The fix implements
sanitization of log messages by removing C0 and C1 control characters
before they reach the terminal.
2025-12-02 11:32:00 +01:00
Anton Bobov
67e668d81f
fix: Sanitize log messages by removing control characters
Added regex pattern to strip C0/C1 control characters (excluding useful
whitespace) from log messages before terminal output. This prevents
disruptive/malicious control sequences from affecting terminal
rendering.
2025-12-02 15:27:24 +05:00
Šarūnas Nejus
6abb901b6b
Add deprecation warning for musicbrainz.enabled but use it to load the plugin, centralise deprecations handling (#6127)
Fixes: #6121

This PR introduces a centralized deprecation system and adjusts
`musicbrainz` plugin loading to properly handle the deprecated
`musicbrainz.enabled` configuration option.

#### MusicBrainz

- Added deprecation warnings for the `musicbrainz.enabled` configuration
option:
- When set to `true`, warns users to explicitly add `musicbrainz` to
their `plugins` configuration and adds it if not already present
- When set to `false`, warns users and adds the plugin to
`disabled_plugins` (list
    received by the `--disable-plugins` flag)

#### Deprecations

- Created new `beets/util/deprecation.py` module with standardized
deprecation helpers:
  - `deprecate_for_user()` - logs warnings visible to end users
- `deprecate_for_maintainers()` - emits `DeprecationWarning` for
developers
- `deprecate_imports()` - handles deprecated module imports with
automatic version calculation
- `_format_message()` - generates consistent deprecation messages that
auto-calculate next major version

- Migrated all deprecation handling to use the new centralized
functions:
  - Replaced inline `warnings.warn()` calls throughout codebase
- Updated `deprecate_imports()` signature to remove explicit `version`
parameter
- Converted user-facing deprecation warnings in plugins to use
logger-based `deprecate_for_user()`
2025-12-02 01:56:00 +00:00
Šarūnas Nejus
05430f312c
Move PromptChoice to beets.util module
And update imports that have been raising the deprecation warning.
2025-12-02 01:51:14 +00:00
Šarūnas Nejus
dd72704d3d
Do not force load musicbrainz, add a test to show the behaviour 2025-11-30 07:42:21 +00:00
Šarūnas Nejus
b643fc4ce5
Do not show a warning to users that have musicbrainz disabled 2025-11-30 07:42:19 +00:00
Šarūnas Nejus
3bb068a675
Warn users of deprecated musicbrainz.enabled option 2025-11-30 07:02:46 +00:00
Šarūnas Nejus
9f7cb8dbe4
Load musicbrainz implicitly and supply a deprecation warning 2025-11-30 07:02:46 +00:00
Šarūnas Nejus
5a3ecf6842
Add deprecate_for_user function 2025-11-30 07:02:46 +00:00
Šarūnas Nejus
39288637b9
Centralise warnings for maintainers into deprecate_for_maintainers 2025-11-30 07:02:46 +00:00
Šarūnas Nejus
c79cad4ed1
Move deprecate_imports to beets.util.deprecation 2025-11-30 07:02:46 +00:00
Šarūnas Nejus
95b3364361
reflink() doesn't take Path parameters (#6186)
Fix `test_successful_reflink`, by passing the right kinds of parameters.

This was failing inside the reflink package:

```
/usr/lib/python3/dist-packages/reflink/reflink.py:34: in reflink
    backend.clone(oldpath, newpath)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

oldpath = PosixPath('/tmp/tmpx3jirmhp/testfile')
newpath = PosixPath('/tmp/tmpx3jirmhp/testfile.dest')

    def clone(oldpath, newpath):
        if isinstance(oldpath, unicode):
            oldpath = oldpath.encode(sys.getfilesystemencoding())
        if isinstance(newpath, unicode):
            newpath = newpath.encode(sys.getfilesystemencoding())

>       newpath_c = ffi.new('char[]', newpath)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: expected new array length or list/tuple/str, not PosixPath
```
2025-11-30 05:42:38 +00:00
Šarūnas Nejus
26fde1ebf0
Merge branch 'master' into fix-reflink 2025-11-30 05:37:48 +00:00
henry
cb0d15ff84
Remove gmusic plugin (#6192)
Sometimes it is time to let go of old things:
This PR removes the old gmusic plugin and all related docs. 

---

The google play music service was shutdown in 2020 and already
deprecated in beets 1.6.0.
2025-11-28 12:19:10 -08:00
Sebastian Mohr
5cc7dcfce7 Sometimes it is time to let go of old things:
This removes old references and docs for the old gmusic plugin.
2025-11-27 21:58:29 +01:00
henry
b4f0dbf53b
Fix recursion in inline plugin when item_fields shadow DB fields (#6115) (#6174)
## Description

Fixes [#6115](https://github.com/beetbox/beets/issues/6115).

When an inline field definition shadows a built-in database field (e.g.,
redefining `track_no` in `item_fields`), the inline plugin evaluates the
field template by constructing a dictionary of all item values.
Previously, this triggered unbounded recursion because `_dict_for(obj)`
re-entered `__getitem__` for the same key while evaluating the computed
field.

This PR adds a per-object, per-key evaluation guard to prevent re-entry
when the same inline field is accessed during expression evaluation.
This resolves the recursion error while preserving normal computed-field
behavior.

A regression test
(`TestInlineRecursion.test_no_recursion_when_inline_shadows_fixed_field`)
verifies that `$track_no` evaluates correctly (`'01'`) when shadowed.

## To Do

- [x] ~Documentation.~
- [x] ~Changelog.~
- [x] Tests.
2025-11-25 16:25:25 -08:00
Gabriel Push
cd8e466a46 Updated changelog documentation 2025-11-25 19:18:10 -05:00
Gabriel Push
51164024c0 Fixed unit tests import 2025-11-25 18:41:31 -05:00
Gabriel Push
c59134bdb6 Fixed unit tests import 2025-11-25 18:38:09 -05:00
Gabriel Push
e827d43213 Fixed unit tests 2025-11-25 18:35:03 -05:00
Gabriel Push
eb11537328
Merge branch 'master' into gabepush-test-fix 2025-11-25 18:16:08 -05:00
Gabriel Push
13f95dcf3a Added documentation header 2025-11-25 18:15:18 -05:00
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
Stefano Rivera
4a17901c1d reflink() doesn't take Path parameters
Fix `test_successful_reflink`, by passing the right kinds of parameters.

This was failing inside the reflink package:

```
/usr/lib/python3/dist-packages/reflink/reflink.py:34: in reflink
    backend.clone(oldpath, newpath)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

oldpath = PosixPath('/tmp/tmpx3jirmhp/testfile')
newpath = PosixPath('/tmp/tmpx3jirmhp/testfile.dest')

    def clone(oldpath, newpath):
        if isinstance(oldpath, unicode):
            oldpath = oldpath.encode(sys.getfilesystemencoding())
        if isinstance(newpath, unicode):
            newpath = newpath.encode(sys.getfilesystemencoding())

>       newpath_c = ffi.new('char[]', newpath)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: expected new array length or list/tuple/str, not PosixPath
```
2025-11-23 13:50:57 -04:00
Sebastian Mohr
d446e10fb0
Add album template value in ftintitle plugin (#6164)
## Description

I was hoping to use the functionality from `ftintitle` to set the path's
album artist as the main artist, but that wasn't possible, so I added a
template value `album_artist_no_feat`.
2025-11-21 18:42:06 +01:00
Sebastian Mohr
ba18ee2f14 Added comment for deprecation in 3.0.0. 2025-11-21 18:31:59 +01:00
asardaes
be0b71043c Revert "Remove class variables for template fields and funcs"
This reverts commit a7033fe63b.
2025-11-21 18:31:59 +01:00
asardaes
23a19e9409 Remove class variables for template fields and funcs 2025-11-21 18:31:59 +01:00
asardaes
2eff2d25f5 Improve typing for template fields and funcs 2025-11-21 18:31:59 +01:00
asardaes
9c37f94171 Add album template value in ftintitle plugin 2025-11-21 18:31:59 +01:00
Gabriel Push
c0ca045c20
Merge branch 'master' into gabepush-test-fix 2025-11-20 16:15:59 -05:00
Gabriel Push
ba45fedde5 Fix inline recursion test formatting 2025-11-20 16:09:01 -05:00
Gabriel Push
aced802c56 Fix recursion in inline plugin when item_fields shadow DB fields (#6115) 2025-11-20 15:57:22 -05:00
Sebastian Mohr
f79c125d15
Catch ValueError when beetsplug.bpd cannot be imported (#6170)
Catch ValueError when setting gst required version

`pytest.importorskip` is used to catch the case when beetsplug.bpd cannot
be imported. On macOS, the `gi` module was able to be imported, but when
trying to specify `gi.require_version`, a `ValueError` is raised about
Gst being unavailable. pytest does not catch this `ValueError` during
`importskip` as it is not an `ImportError`, and thus the test suite
errors during the test collection phase.

With this change, we catch the ValueError, and re-raise it as an
`ImportError` and pytest gracefully skips those tests.

Fixes #3324
2025-11-19 12:49:07 +01:00
Ognyan Moore
aa2dc9005f
Catch ValueError when setting gst required version
pytest.importskip is used to catch the case when beetsplug.bpd cannot be
imported. On macOS, the gi module was able to be imported, but when
trying to specify `gi.require_version`, a ValueError is raised about
Gst being unavailable. pytest does not catch this ValueError during
importskip as it is not an ImportError, and thus the test suite errors
during the test collection phase.

With this change, we catch the ValueError, and re-raise it as an
ImportError and pytest gracefully skips those tests.
2025-11-19 14:43:30 +03:00
Sebastian Mohr
88ca0ce1fb
Plugins/web: fix endpoints /…/values/… (#6158)
Following #4709 and #5447, the web plugin used single-quotes (ie. string
litteral) in the SQL query for table columns.

Thus, for instance, the query `GET /item/values/albumartist` would
return the litteral "albumartist" instead of a list of unique album
artists.

This prevents the Mopidy beets integration from working, returning the
single artist "albumartist".
2025-11-17 10:21:31 +01:00
Théophile Bastian
189fedb008 Web plugin: add type hint for g.lib 2025-11-15 21:02:43 +01:00
Théophile Bastian
666c412b0e plugins/web: fix endpoints /…/values/…
Following #4709 and #5447, the web plugin used single-quotes (ie. string
litteral) in the SQL query for table columns. Thus, for instance, the query
`GET /item/values/albumartist` would return the litteral "albumartist"
instead of a list of unique album artists.
2025-11-15 21:02:38 +01:00
Sebastian Mohr
07445fdd07
Fix import --from-logfile (#6161)
Fixes "none of the paths are importable" error that was accidentally
introduced in 4260162d4

4260162d44 (diff-e324b20657a7d6b43b8b7aeb5754b96774f5062294b5ba7f1e3062845e9e7044R1382-R1390)
2025-11-13 19:32:19 +01:00
J0J0 Todos
97bc0b3b8c Changelog for #6161 2025-11-13 19:26:18 +01:00
J0J0 Todos
2ef77852b7 Fix import --from-logfile
Fixes "none of the paths are importable" error with any valid import log
file that was accidentally introduced in commit 4260162d4
2025-11-13 19:26:18 +01:00
Šarūnas Nejus
e326aafac0
Allow selecting either tags or genres in the includes, defaulting to genres (#5874)
Genres is a filtered list based on what musicbrainz considers a genre,
tags are all the user-submitted tags. [1]

1.
https://musicbrainz.org/doc/MusicBrainz_API#:~:text=Since%20genres%20are,!).
2025-11-12 21:40:02 +00:00
Aidan Epstein
672bf0bf41 Add tests. 2025-11-11 17:08:46 -08:00
Aidan Epstein
d7636fb0c3 Apply suggestions from code review
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2025-11-11 13:18:51 -08:00
Aidan Epstein
9e7d5debdc Allow selecting either tags or genres in the includes, defaulting to genres
Genres is a filtered list based on what musicbrainz considers a genre,
tags are all the user-submitted tags. [1]

1. https://musicbrainz.org/doc/MusicBrainz_API#:~:text=Since%20genres%20are,!).

Also apply suggestions from code review

Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2025-11-11 20:01:37 +00:00
Sebastian Mohr
f3da80e512
FIX: Dereference symlinks before hardlinking (#5684)
When creating a hardlink, either during import or `beet convert`, if the
origin of the hardlink was a symlink, that symlink used to be directly
copied. This could create broken symlinks if the origin symlink was
relative, and in either case, probably wasn't the user's desired
behavior.

This change de-references all symlinks before creating a hardlink, such
that the end result is a normal file with the same inode as the original
file. See #5676 for more discussion about the original issue.

Fixes #5676
2025-11-11 17:07:47 +01:00