Commit graph

13343 commits

Author SHA1 Message Date
dunkla
ea67c8efac Remove conditional logic from lastgenre tests (ref https://github.com/beetbox/beets/pull/6169#issuecomment-3716893013) 2026-01-10 15:37:55 +01:00
dunkla
c67dea2adb Use compact generator expression in Beatport (ref https://github.com/beetbox/beets/pull/6169#issuecomment-3716893013) 2026-01-10 15:37:45 +01:00
dunkla
7f15e1b8e2 Fix lastgenre migration separator logic (ref https://github.com/beetbox/beets/pull/6169#issuecomment-3716893013) 2026-01-10 15:37:33 +01:00
dunkla
9fbf0edd75 Remove manual migrate command
Migration now happens automatically when the database schema is
updated (in Library._make_table()), so the manual 'beet migrate'
command is no longer needed.

Addresses PR review comment.
2025-12-28 21:03:42 +01:00
dunkla
5e3e3cbc98 Simplify MusicBrainz genres assignment
Remove intermediate variable and assign directly to info.genres.
Addresses PR review comment.
2025-12-28 20:48:15 +01:00
dunkla
9fc90dd8f6 Implement automatic database-level genre migration
- Add Library._make_table() override to automatically migrate genres when database schema is updated
- Migration splits comma/semicolon/slash-separated genre strings into genres list
- Writes changes to both database and media files with progress reporting
- Remove lazy migration from correct_list_fields() - now handled at database level
- Remove migration-specific tests (migration is now automatic, not lazy)
- Update changelog to reflect automatic migration behavior

Related PR review comment changes:
- Replace _is_valid with _filter_valid method in lastgenre plugin
- Use unique_list and remove genre field from Beatport plugin
- Simplify LastGenre tests - remove separator logic
- Document separator deprecation in lastgenre plugin
- Add deprecation warning for genre parameter in Info.__init__()
2025-12-28 20:14:49 +01:00
dunkla
d565524965 simplify check for fallback in beetsplug/lastgenre/__init__.py
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2025-12-28 17:23:50 +01:00
dunkla
c735ffb670 simplify genre unpacking in beetsplug/lastgenre/__init__.py
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2025-12-28 17:23:50 +01:00
dunkla
4fec632d8c simplify return logic in beetsplug/lastgenre/__init__.py
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2025-12-28 17:23:50 +01:00
dunkla
b90cd5d57e better function description in beetsplug/lastgenre/__init__.py
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2025-12-28 17:23:50 +01:00
dunkla
fc7988dcc5 shorte test description in test/plugins/test_lastgenre.py
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2025-12-28 17:23:50 +01:00
dunkla
1a158b92fc remove noisy comment from test/plugins/test_beatport.py
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2025-12-28 17:23:50 +01:00
Johann Fot
306211a4c8 Add native support for multiple genres per album/track
Simplify multi-genre implementation based on maintainer feedback (PR #6169).

Changes:
- Remove multi_value_genres and genre_separator config options
- Replace complex sync_genre_fields() with ensure_first_value('genre', 'genres')
- Update all plugins (Beatport, MusicBrainz, LastGenre) to always write genres as lists
- Add automatic migration for comma/semicolon/slash-separated genre strings
- Add 'beet migrate genres' command for explicit batch migration with --pretend flag
- Update all tests to reflect simplified approach (44 tests passing)
- Update documentation

Implementation aligns with maintainer vision of always using multi-value genres
internally with automatic backward-compatible sync to the genre field via
ensure_first_value(), eliminating configuration complexity.

Migration strategy avoids problems from #5540:
- Automatic lazy migration on item access (no reimport/mbsync needed)
- Optional batch migration command for user control
- No endless rewrite loops due to proper field synchronization
2025-12-28 17:23:50 +01:00
Šarūnas Nejus
21e6a1f757
Improve model changes colour display and db / field diff typing (#6240)
## 1. **Refactored UI diffs**

Using the following command:

```sh
beet modify turn page data_source= 'play_count!' hello=hi comp=1 mb_albumid=https://bandcamp
```

### Before
<img width="613" height="260" alt="before"
src="https://github.com/user-attachments/assets/785c4b73-69e4-4c60-b4dd-d114ee3170a1"
/>

* New field additions have been shown in red
* No difference in formatting between
  - field _removal_ (`field!`)
  - and it being reset to an empty string (`field=`)
 
### After
<img width="640" height="256" alt="after"
src="https://github.com/user-attachments/assets/89b036d7-a074-494b-a5e1-b1bf5100d454"
/>


* Now, the field name is colored in red or green whenever it's added or
removed

## 2. Small improvements in `Model` types:
* Added `NotFoundError` and `Model.get_fresh_from_db` for those cases
where `Database._get` must return a non-optional instance of `Model`
   * Added cached `Model.db` property to dedupe `Model._check_db` calls.

## 3. Added a global autouse model-level `config` fixture.
2025-12-27 14:35:43 +00:00
Šarūnas Nejus
c807effeda
Define a shared fixture for config 2025-12-27 14:30:35 +00:00
Šarūnas Nejus
75baec611a
Improve and simplify show_model_changes 2025-12-27 14:30:35 +00:00
Šarūnas Nejus
e1e0d945f8
Add NotFoundError and Model.get_fresh_from_db; tidy DB getters
Introduce NotFoundError and a Model.get_fresh_from_db helper that reloads
an object from the database and raises when missing. Use it to simplify
Model.load and UI change detection.
2025-12-27 14:26:29 +00:00
Šarūnas Nejus
8ccb33e4bc
dbcore: add Model.db cached attribute 2025-12-27 14:26:15 +00:00
Šarūnas Nejus
a62f4fb817
Introduce Info.name property and add types to match details functions (#6142)
# Generalize some of common tagging functionality to `Info` and `Match`
base classes

This PR centralises some common tagging functionality between singletons
and albums allowing to simplify `ChangeRepresentation` and importing
functionality. This is prep work for a larger PR which refactors and
simplifies the entire tagging workflow.

## Changes

- **Core type changes**: Changed `mapping` parameter from `Mapping[Item,
TrackInfo]` to `list[tuple[Item, TrackInfo]]` in `apply_metadata()`,
`distance()`, and `assign_items()` functions
- **Match dataclasses**: Converted `AlbumMatch` and `TrackMatch` from
`NamedTuple` to `@dataclass`, introducing base `Match` class with common
functionality
- **New properties**: Added `name` cached property to `Info` class for
unified name access
- **ChangeRepresentation refactor**: Converted to `@dataclass` with lazy
property evaluation, replacing `cur_album`/`cur_title` with unified
`cur_name` field
- **UI improvements**: Simplified display logic by using
`match.info.name` instead of type-specific field checks
- **Parameter renaming**: Renamed `search_album`/`search_title`
parameters to `search_name` for consistency across singleton and album
workflows

The changes maintain backward compatibility in behavior while improving
type safety and code clarity.
2025-12-24 11:06:47 +00:00
Šarūnas Nejus
60b4a38c09
Add missing type defs in import_/display.py 2025-12-24 11:01:27 +00:00
Šarūnas Nejus
7873ae56f0
hooks: introduce Info.name property 2025-12-24 11:01:26 +00:00
Šarūnas Nejus
84f6ada739
hooks: Generalise AlbumMatch and TrackMatch into Match 2025-12-24 11:01:26 +00:00
Šarūnas Nejus
acc7c2aeac
matching: replace search_title, search_album with search_name 2025-12-24 11:01:26 +00:00
Šarūnas Nejus
ea157832fe
hooks: make AlbumMatch.mapping a tuple 2025-12-24 11:01:26 +00:00
Sebastian Mohr
b05821865f
Fix edit plugin cancel flow restoring in-memory tags (#6104) (#6200)
fixes #6104

## Description

When using the `fromfilename` and `edit` plugins together during import,
aborted edit sessions could silently discard the temporary tags injected
by `fromfilename` (e.g., track number and title derived from the
filename). This happened when using `eDit` or `edit Candidates` and then
cancelling: the edit plugin reverted objects by re-reading from disk,
which does not contain the `fromfilename`-generated metadata.

This PR changes the `edit` plugin so that cancel and “continue Editing”
both roll back objects to the original in-memory snapshot captured
before opening the editor, instead of reloading from the files. This
preserves temporary tags provided by other plugins (like `fromfilename`)
across aborted edit sessions, while still only writing to disk when the
user chooses Apply.

`importer_edit` is also updated to rely on this rollback behavior when
edits are cancelled, rather than re-reading from disk, so interactive
imports resume with the same in-memory metadata they started with.
2025-12-23 15:40:23 +01:00
Sebastian Mohr
53a42bf6f6
Merge branch 'master' into gabepush-test-fix 2025-12-23 15:34:47 +01:00
J0J0 Todos
5d1210ada5
importsource: Catch importer crash when skipping; Fix original changelog entry; Add new tests (#6203)
Prevents 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
           ^^^^^
AssertionError
```

- Since for items/albums that should be skipped, looping through
`task.imported_items()` is not required anyway, the fix here is to exit
early from the function that calls it.
- Additionally this PR fixes the original changelog entry which was
located at an older releases "new features list". Also now it briefly
explains to changelog readers what the plugin actually does.
- Two new tests were added that proof that "skip doesn't crash" and
reimports never "suggest removal of source files"

---------

Co-authored-by: Doron Behar <doron.behar@gmail.com>
2025-12-21 20:35:27 +01:00
J0J0 Todos
9ffae4bef1 importsource: Test skip, Test reimport-skip 2025-12-21 13:07:02 +01:00
J0J0 Todos
be3485b066 Fix initial importsource plugin #4748 changelog
- Fix position (wrong release)
- Elaborate wording
2025-12-21 13:07:02 +01:00
Doron Behar
0230352da1 importsource: fix potential prevent_suggest_removal crash 2025-12-21 13:07:02 +01:00
Šarūnas Nejus
c1904b1f69
Make musicbrainz plugin talk to musicbrainz directly (#6052)
This PR refactors the MusicBrainz plugin implementation by replacing the
`musicbrainzngs` library with direct HTTP API calls using `requests` and
`requests-ratelimiter`.

**Key Changes:**

- **New utilities module**: Added `beetsplug/_utils/requests.py` with
`TimeoutSession` class and HTTP error handling (`HTTPNotFoundError`,
`CaptchaError`)
- **MusicBrainz API rewrite**: Replaced `musicbrainzngs` dependency with
custom `MusicBrainzAPI` class using direct HTTP requests
- **Rate limiting**: Integrated `requests-ratelimiter` for API rate
limiting instead of `musicbrainzngs.set_rate_limit()`
- **Data structure updates**: Updated field names to match MusicBrainz
JSON API v2 format (e.g., `medium-list` → `media`, `track-list` →
`tracks`)
- **Dependency management**: 
- Made `musicbrainzngs` optional and added it to plugin-specific extras
(`listenbrainz`, `mbcollection`, `missing`, `parentwork`). Updated
plugin docs accordingly.
- Made `requests` a required dependency to ensure backwards
compatibility (ideally, we would make it an optional dependency under
`musicbrainz` extra).
- **Error handling**: Simplified error handling by removing
`MusicBrainzAPIError` wrapper class

**Benefits:**
- Direct control over HTTP requests
- Consistent rate limiting across all network requests
- Better alignment with modern MusicBrainz API responses

The changes maintain backward compatibility while modernizing the
underlying implementation.

Fixes #5553
Fixes #5095
2025-12-21 01:08:10 +00:00
Šarūnas Nejus
5785ce3a84
Ensure that inc are joined with a plus
See this line in https://musicbrainz.org/doc/MusicBrainz_API#Lookups

> To include more than one subquery in a single request, separate the arguments to inc= with a + (plus sign), like inc=recordings+labels.
2025-12-21 01:03:20 +00:00
Šarūnas Nejus
d1aa45a008
Add retries for connection errors 2025-12-21 01:03:20 +00:00
Šarūnas Nejus
9dad040977
Add Usage block to RequestHandler 2025-12-21 00:40:40 +00:00
Šarūnas Nejus
72f7d6ebe3
Refactor HTTP request handling with RequestHandler base class
Introduce a new RequestHandler base class to introduce a shared session,
centralize HTTP request management and error handling across plugins.

Key changes:
- Add RequestHandler base class with a shared/cached session
- Convert TimeoutSession to use SingletonMeta for proper resource
  management
- Create LyricsRequestHandler subclass with lyrics-specific error
  handling
- Update MusicBrainzAPI to inherit from RequestHandler
2025-12-21 00:40:40 +00:00
Šarūnas Nejus
041d4b8036
Make musicbrainzngs dependency optional and requests required 2025-12-20 01:35:52 +00:00
Šarūnas Nejus
10ebd98ca5
musicbrainz: remove error handling 2025-12-20 01:35:52 +00:00
Šarūnas Nejus
ca0b3171cc
musicbrainz: access the custom server directly, if configured 2025-12-20 01:35:51 +00:00
Šarūnas Nejus
6b034da147
musicbrainz: browse directly 2025-12-20 01:35:51 +00:00
Šarūnas Nejus
abad03c1cb
musicbrainz: search directly 2025-12-20 01:35:51 +00:00
Šarūnas Nejus
d70e591738
musicbrainz: lookup recordings directly 2025-12-20 01:35:51 +00:00
Šarūnas Nejus
2a63e13617
musicbrainz: lookup release directly 2025-12-20 01:35:51 +00:00
Šarūnas Nejus
7fdb458524
Move pseudo release lookup under the plugin 2025-12-20 01:35:51 +00:00
Šarūnas Nejus
69e3a8233d
Add missing blame ignore revs from musicbrainz plugin 2025-12-20 01:35:51 +00:00
Šarūnas Nejus
a866347345
Define MusicBrainzAPI class with rate limiting 2025-12-20 01:35:51 +00:00
Šarūnas Nejus
fda3bbaea5
Move TimeoutSession under beetsplug._utils 2025-12-20 01:35:51 +00:00
Henry Oberholtzer
ac0b6ec5e4 Merge branch 'Nedra1998-improved-multiartist' 2025-12-19 12:18:03 -08:00
Arden Rasmussen
a7170fae45 expand tests to include check for track artists 2025-12-18 16:23:58 -08:00
henry
09476bdad9
Titlecase Plugin Improvements (#6220)
- Add preserving strings that are all lowercase or all upper case
- Fix spelling of 'separator' in config, docs and code
- Move most of the logging for the plugin to debug to keep log cleaner.

Improvements I found a need for in my daily use with the plugin.

- [x] Documentation. (If you've added a new command-line flag, for
example, find the appropriate page under `docs/` to describe it.)
- [x] Changelog. (Skipping as the plugin has not been released yet)
- [x] Tests. (Very much encouraged but not strictly required.)
2025-12-17 16:10:16 -08:00
Arden Rasmussen
9cbbad19f8 remove changes for lastgenre as there was an existing PR for that work 2025-12-17 15:57:23 -08:00