Commit graph

13267 commits

Author SHA1 Message Date
Sebastian Mohr
f7ddcdeb59 Ruff format after rebase. 2025-12-02 13:39:19 +01:00
Sebastian Mohr
9ca95bf721 Added album_id index to speed up queries against album items. 2025-12-02 13:39:19 +01:00
Sebastian Mohr
91c8fccacb Added tests for index class. 2025-12-02 13:39:19 +01:00
Sebastian Mohr
1531c8f227 Added sql db indices as ORM model class. 2025-12-02 13:39:19 +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