Commit graph

13458 commits

Author SHA1 Message Date
Serene
0e5c64a766
Embedart plugin: clearart improvements (#6156) 2026-01-20 08:48:23 +10:00
Serene
39f65f6b11
Merge branch 'master' into embedart-clear-improvements 2026-01-20 08:43:30 +10:00
Henry
9b1bd5df7a Adjust type annotation, rebase. 2026-01-19 12:46:22 -08:00
Henry
ff95ce5d20 Remove utils, rework from_plugin method in ArtistState to from_config 2026-01-19 12:43:30 -08:00
Henry Oberholtzer
2cfd1df3c1 Split discogs.py into smaller and more workable modules. 2026-01-19 12:43:30 -08:00
Šarūnas Nejus
5523ca94a2 Document ArtistState 2026-01-19 12:43:30 -08:00
Šarūnas Nejus
7d83a68bdd Ensure all fields in artist dicts in tests 2026-01-19 12:43:30 -08:00
Šarūnas Nejus
b3183a73e0 Simplify building artist 2026-01-19 12:43:30 -08:00
Šarūnas Nejus
59e7c59172 Move building logic to dataclasses 2026-01-19 12:43:30 -08:00
Henry Oberholtzer
0e48c65171 Clarify variable in _process_clean_tracklist 2026-01-19 12:43:30 -08:00
Henry Oberholtzer
2d406a3ca5 Add comments, clean up types. 2026-01-19 12:43:30 -08:00
Henry
459fd39768 Fix behavior when ANV does not exist 2026-01-19 12:43:30 -08:00
Henry Oberholtzer
08a2c248b9 Fix handling of commas and semicolons in artist join 2026-01-19 12:43:30 -08:00
Henry
f0aef6e213 Cleanup for #6177, #6068 2026-01-19 12:43:27 -08:00
Henry
1d6e05709e Fix #6068 - Multivalue fields are now supported & tested. 2026-01-19 12:41:36 -08:00
Henry
9efe87101c Fix #6177, remove derived types, refactor coalesce tracks 2026-01-19 12:40:42 -08:00
Šarūnas Nejus
dbe97c231f
Add mp3rgain support to ReplayGain command backend (#6289)
This PR adds support for
[mp3rgain](https://github.com/M-Igashi/mp3rgain) as an alternative to
mp3gain/aacgain in the ReplayGain plugin's command backend.

## What is mp3rgain?

mp3rgain is a modern Rust rewrite of mp3gain that provides:

- **CLI-compatible drop-in replacement** for mp3gain - same command-line
interface and output format
- **Support for both MP3 and AAC/M4A formats** (like aacgain)
- **Security fixes** for vulnerabilities in the original mp3gain:
- CVE-2021-34085 (Critical, CVSS 9.8) - buffer over-read in
`WriteMP3GainAPETag`
- CVE-2019-18359 (Medium, CVSS 5.5) - buffer over-read in
`ReadMP3APETag`
- **Memory-safe implementation** in Rust
- **Works on modern systems** including Windows 11 and macOS with Apple
Silicon
- **Active maintenance** (original mp3gain has not been updated since
2009)

## Changes

1. **beetsplug/replaygain.py**:
- Add `mp3rgain` to the command search list (prioritized first since
it's more secure)
- Update `format_supported()` to correctly handle mp3rgain's AAC/M4A
support
   - Update error message to mention mp3rgain

2. **docs/plugins/replaygain.rst**:
   - Add mp3rgain to the backend documentation
   - Include installation instructions for various platforms
   - Document the security benefits

## Testing

mp3rgain has been tested to be fully compatible with the existing
command backend:
- Supports all the same command-line flags (`-o`, `-s s`, `-k`, `-c`,
`-d`, etc.)
- Produces identical tab-delimited output format
- Returns the same exit codes

## Why prioritize mp3rgain in the search order?

The original mp3gain has known security vulnerabilities that have not
been fixed (the project has been inactive since 2009). By searching for
mp3rgain first, users who have it installed will automatically benefit
from the security improvements, while maintaining full backward
compatibility for users who only have mp3gain/aacgain installed.
2026-01-19 20:01:09 +00:00
Jesse Pinkman
7002cbdda2
Merge branch 'master' into add-mp3rgain-support 2026-01-18 11:00:38 +01:00
m_igashi
545e7eb0b6 refactor: simplify CommandBackend and improve documentation
- Remove auto-detection of command tools, require explicit command config
- Simplify __init__ method by removing redundant else branch
- Reorganize docs with separate sections for mp3gain, aacgain, mp3rgain
- Fix CVE reference (CVE-2021-34085 is fixed in mp3gain 1.6.2)
- Update command option description per review feedback
2026-01-18 10:52:41 +01:00
Šarūnas Nejus
ef59cfa522
Handle potential OSError when unlinking temporary files in ArtResizer (#5615)
## Description

was getting permission error because after png is converted to jpg beets
want to delete the png but somehow it is still being used causing the
import to fail. this temporarily fixes the import but still needs a
proper way to know what is using the file and how to delete it.

```
  File "C:\Users\DELL\projects\_myForks\beets\beetsplug\fetchart.py", line 1321, in fetch_art
    candidate = self.art_for_album(task.album, task.paths, local)
  File "C:\Users\DELL\projects\_myForks\beets\beetsplug\fetchart.py", line 1413, in art_for_album
    out.resize(self)
    ~~~~~~~~~~^^^^^^
  File "C:\Users\DELL\projects\_myForks\beets\beetsplug\fetchart.py", line 218, in resize
    self._resize(plugin, current_check)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\DELL\projects\_myForks\beets\beetsplug\fetchart.py", line 246, in _resize
    self.path = ArtResizer.shared.reformat(
                ~~~~~~~~~~~~~~~~~~~~~~~~~~^
        self.path,
        ^^^^^^^^^^
        plugin.cover_format,
        ^^^^^^^^^^^^^^^^^^^^
        deinterlaced=plugin.deinterlace,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\DELL\projects\_myForks\beets\beets\util\artresizer.py", line 658, in reformat
    os.unlink(path_in)
    ~~~~~~~~~^^^^^^^^^
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: b'C:\\Users\\DELL\\AppData\\Local\\Temp\\beets\\beetsplug_fetchart\\4kqx2um2.png'
```
when importing
https://musicbrainz.org/release/5744ddb7-e9b6-4b46-a55c-38e75aa95460

beet config

```yaml
fetchart:
    minwidth: 500
    maxwidth: 3000
    max_filesize: 3500000
    sources:
    -   coverart: release
    -   coverart: releasegroup
    - itunes
    - amazon
    - filesystem
    - albumart
    - '*'
    cautious: yes
    cover_names: cover front art artwork folder album
    store_source: yes
    cover_format: JPEG
    auto: yes
    quality: 0
    enforce_ratio: no
    high_resolution: no
    deinterlace: no
```
2026-01-17 14:35:44 +00:00
Dr.Blank
52284ff7ed
fix: changelog entry 2026-01-17 14:30:22 +05:30
m_igashi
29e1c283eb fix: sort imports alphabetically 2026-01-17 02:15:43 +01:00
m_igashi
5ea41b3fbb refactor: simplify CommandBackend with SUPPORTED_FORMATS_BY_TOOL
- Add Tool type alias and SUPPORTED_FORMATS_BY_TOOL class variable
- Refactor __init__ to use shutil.which() and set cmd_name early
- Simplify format_supported() to use dictionary lookup
2026-01-17 02:10:29 +01:00
m_igashi
683da049a0 style: format replaygain.rst with docstrfmt 2026-01-16 16:20:45 +01:00
m_igashi
179dc7d070 style: remove trailing whitespace from blank line 2026-01-16 16:06:17 +01:00
Jesse Pinkman
dc117046a9
Merge branch 'master' into add-mp3rgain-support 2026-01-16 15:55:23 +01:00
Dr.Blank
4ad5871ef0
fix: sort imports 2026-01-16 15:53:34 +05:30
Dr.Blank
b0bce80518
remove changelog not related to pr 2026-01-16 15:50:09 +05:30
Dr.Blank
8482733034
Merge branch 'master' into fix-permission-error 2026-01-16 15:48:19 +05:30
Dr.Blank
e85f67ac7b
refactor: suppress OSError when unlinking temporary files in ArtResizer 2026-01-16 15:43:01 +05:30
Šarūnas Nejus
6c2c46091b
Respect no_convert and never_convert_lossy_files in convert plugin (#6286)
Given that @frigginbrownie's #5556 PR received some thumb ups but they
haven't responded since a while ago, I'm creating this PR to merge their
fix (I could not commit in the PR branch since I have no permissions to
push to their fork).

Supersedes: #5556 

Copying @frigginbrownie description from #5556:

According to the docs, the auto_keep function will "Convert your files
automatically on import to dest but import the non transcoded version."
This is true but not 100% accurate. In cases where no conversion is
required (say, importing lossy files where there's no need to convert),
auto_keep will copy the files to dest.

This behavior results in duplicate files being created on import when
the auto_keep function is set to yes - a lossy file will be imported
into the default directory (say /music) and then copied to the dest
location (say /transcodes).

This is ideal if you wish to have all music formats in your default
directory (lossy and lossless) and all lossy files (original imports and
transcodes) in a secondary directory (say /lossy).

But what if you want a separate directory of all music you've
transcoded? auto_keep won't provide that, as it copies lossy files to
the dest location. In addition, if the dest is set to the same location
as default directory, auto_keep will copy lossy files into the same
directory that beets previously imported files into, resulting in the
directory having two files for each file in an album. If you use paths
(say to have singletons imported into /music/singles), auto_keep will
import the file into the path location, then copy the file to the dest,
creating directories to match the path.

Unlike with the auto option or using "beet convert", auto_keep does not
follow the never_convert_lossy_files or no_convert options and will not
validate whether files need to be converted or copied on import to dest
- it transcodes or it copies, no questions asked.

This change updates the auto_convert_keep function to filter items using
should_transcode. This way, if the user sets never_convert_lossy_files
to no or no_convert: 'format:mp3', lossy files will not be copied to the
dest, while lossless files will be converted to the dest (perfect for a
seperate /transcodes directory). If the user sets
never_convert_lossy_files to yes, lossy files will to be copied to the
dest and lossless files will be converted to the dest (perfect for a
/lossy directory). In turn, this change makes behavior consistent with
"beet convert" and the auto option.
2026-01-15 15:58:35 +00:00
Šarūnas Nejus
bfb24da51c
Add note to the changelog 2026-01-15 15:53:06 +00:00
frigginbrownie
1ff254215a
Update convert.py 2026-01-15 15:46:01 +00:00
Šarūnas Nejus
679cfc93ed
Fix 'from_scratch': delete all tags before writing new tags to file (#5828)
Fixes #3706.

### Issue
Comment tags are written to file even if option 'from_scratch' is used.
The same tags are not written to the file if imported together with
other files as album. Therefore 'from_scratch' is not working as
described in the documentation.

### Solution
1. Add test: Adapt the function from the 'regular' import class and
insert it in the class for the singleton import test.
2. Fix bug : Add check for 'from_scratch' option. If used, clear
metadata before applying 'new' metadata with autotag.
3. No documentation change needed. Option now works as described in the
documentation.
4. Add changelog.
2026-01-15 15:43:19 +00:00
rdy2go
fdfeb35076 add changelog for and to resolve PR #5828 2026-01-15 16:07:54 +01:00
rdy2go
445ad02399
Merge branch 'beetbox:master' into master 2026-01-15 16:04:46 +01:00
m_igashi
ebd0e70012 Add mp3rgain support to ReplayGain command backend
mp3rgain is a modern Rust rewrite of mp3gain that provides:
- CLI-compatible drop-in replacement for mp3gain
- Support for both MP3 and AAC/M4A formats (like aacgain)
- Fixes for CVE-2021-34085 (Critical, CVSS 9.8) and CVE-2019-18359 (Medium)
- Memory-safe implementation in Rust
- Works on modern systems (Windows 11, macOS Apple Silicon)

Changes:
- Add mp3rgain to the command search list (prioritized first)
- Update format_supported() with more robust command name detection
  using os.path.basename() and startswith() instead of substring matching
- Update documentation with installation instructions

See: https://github.com/M-Igashi/mp3rgain
2026-01-14 01:47:45 +01:00
Šarūnas Nejus
b3c42a3350
Enable ruff's future-annotations and RUF* rules (#6245)
## Summary

This PR updates typing and linting across the codebase and enables
stricter `ruff` checks for Python 3.10:

1. Enable `tool.ruff.lint.future-annotations`
Very handy feature released in `0.13.0`: if required, it _automatically_
adds `from __future__ import annotations` and moves relevant imports
under `if TYPE_CHECKING`:

   ```py
   # before (runtime import)
   from beets.library import Library

   # after
   from __future__ import annotations

   from typing import TYPE_CHECKING

   if TYPE_CHECKING:
       from beets.library import Library
   ```

2. Set `tool.ruff.target-version = "py310"`

   This enforced PEP 604 unions in the codebase:

   ```py
   # before
   SQLiteType = Union[str, bytes, float, int, memoryview, None]

   # after
   SQLiteType = str | bytes | float | int | memoryview | None
   ```

3. Enable `RUF*` family of checks

   - Remove unused `# noqa`s
   - Ignore unused unpacked variables

     ```py
     # before
     likelies, consensus = util.get_most_common_tags(self.items)

     # after
     likelies, _ = util.get_most_common_tags(self.items)
     ```
   - Avoid list materialization
     ```py
     # before
     for part in parts + [","]:

     # after
     for part in [*parts, ","]:
     ```

- And, most importantly, **RUF012**: use `ClassVar` for mutable class
attributes

- This underlined our messy `BeetsPlugin.template_*` attributes design,
where I have now defined `BeetsPluginMeta` to make a clear distinction
between class and instance attributes. @semohr and @asardaes I saw you
had a discussion regarding these earlier - we will need to revisit this
at some point to sort it out for good.
- It also revealed a legitimate issue in `metasync.MetaSource` where
`item_types` were initialised as an instance attribute (but luckily
never used).
2026-01-13 21:02:32 +00:00
Šarūnas Nejus
c9625f8fb3
Update git blame ignore revs 2026-01-13 20:55:40 +00:00
Šarūnas Nejus
1c20e4bd4e
Address RUF012 2026-01-13 20:55:40 +00:00
Šarūnas Nejus
c52656fb0a
Enable RUF rules 2026-01-13 20:55:40 +00:00
Šarūnas Nejus
078ffc1c57
Configure ruff for py310 2026-01-13 20:55:40 +00:00
Šarūnas Nejus
b964d8b7eb
Configure future-annotations 2026-01-13 20:55:40 +00:00
Šarūnas Nejus
bd319c2c43
db: disable DQS on Python >= 3.12 (#5235)
cf. https://github.com/beetbox/beets/issues/4709, let's see how badly
this breaks CI
2026-01-13 13:53:45 +00:00
wisp3rwind
7685e9439a db: disable DQS on Python >= 3.12 2026-01-13 13:48:23 +00:00
Šarūnas Nejus
265d513251
Change link for beets-usertag (#5756)
The beets-usertag plugin was originally created by igordetigor, but has
been unmaintained for some time now. Recently, I decided to add some
features to it and, with Ingo's blessing, publish on PyPI. He's also
okay with me replacing the link in the plugins list with my fork.
2026-01-13 13:44:42 +00:00
Šarūnas Nejus
0efce4a86b
Merge branch 'master' into usertag 2026-01-13 13:38:56 +00:00
Šarūnas Nejus
4ff6b39ecb
Fix bug in fetching preferred release event. (#6279)
With the changes to how data is fetched from MusicBrainz, empty releases
are now `None` instead of an empty dict.
2026-01-13 13:21:24 +00:00
David Logie
3ea4bb7941 Fix bug in fetching preferred release event.
With the changes to how data is fetched from MusicBrainz, empty releases
are now `None` instead of an empty dict.
2026-01-13 13:16:09 +00:00
Šarūnas Nejus
f63585fe31
Remove expired Spotify credentials and log error for missing configuration (#6271)
Looks like our hard-coded API credentials expired. This PR removes
expired credentials and makes it clear to the user that they must
provide their credentials.

Fixes #6270
2026-01-11 19:02:37 +00:00