-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Album tagging for Musicbrainz single import #6160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jjmeist
wants to merge
9
commits into
beetbox:master
Choose a base branch
from
jjmeist:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Contributor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `beetsplug/musicbrainz.py:522` </location>
<code_context>
+ rel_list = recording.get("release-list") or recording.get("release_list")
+ if rel_list:
+ # Pick the first release as a reasonable default.
+ rel0 = rel_list[0] or {}
+ # Handle both shapes: {'id','title',...} or {'release': {'id','title',...}}
+ rel0_dict = rel0.get("release", rel0)
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against empty release lists to prevent IndexError.
Accessing rel_list[0] without checking if rel_list is non-empty can cause an IndexError. Add a check to ensure rel_list contains elements before accessing its first item.
</issue_to_address>
### Comment 2
<location> `beetsplug/musicbrainz.py:861-871` </location>
<code_context>
criteria = {"artist": artist, "recording": title, "alias": title}
+ results = self._search_api("recording", criteria)
+
+ for r in results:
+ rec_id = r.get("id") or r.get("recording", {}).get("id")
+ if not rec_id:
+ continue
+ try:
+ ti = self.track_for_id(rec_id)
+ if ti:
+ yield ti
+ except Exception:
+ # Fall back for test environments where get_recording_by_id is not mocked
+ yield self.track_info(r)
- yield from filter(
</code_context>
<issue_to_address>
**suggestion:** Consider logging or surfacing exceptions for better debugging.
Silent exception handling in item_candidates may hide errors. Logging exceptions or adding context will make troubleshooting easier.
```suggestion
import logging
for r in results:
rec_id = r.get("id") or r.get("recording", {}).get("id")
if not rec_id:
continue
try:
ti = self.track_for_id(rec_id)
if ti:
yield ti
except Exception as exc:
logging.exception(
f"Exception in item_candidates for recording ID '{rec_id}': {exc}"
)
# Fall back for test environments where get_recording_by_id is not mocked
yield self.track_info(r)
```
</issue_to_address>
### Comment 3
<location> `test/plugins/test_musicbrainz.py:1045-1054` </location>
<code_context>
+ def test_item_candidates_includes_album_from_release_list(self, monkeypatch, mb):
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding tests for multiple releases in the release-list.
Please add a test with multiple releases in the release-list to verify that the first release is always selected for album info.
</issue_to_address>
### Comment 4
<location> `test/plugins/test_musicbrainz.py:1080-1082` </location>
<code_context>
+ # The candidate should have correct track info
+ assert candidate.track_id == "d207c6a8-ea13-4da6-9008-cc1db29a8a35"
+
+ # ✅ New expected behavior: album info populated from release-list
+ assert candidate.album == "Love Always (Deluxe Edition)"
+ assert candidate.album_id == "9ec75bce-60ac-41e9-82a5-3b71a982257d"
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Test does not cover missing or malformed release-list edge cases.
Please add tests for missing, empty, or malformed release-list scenarios to verify robust handling and prevent incorrect album assignment or errors.
Suggested implementation:
```python
# The candidate should have correct track info
assert candidate.track_id == "d207c6a8-ea13-4da6-9008-cc1db29a8a35"
# ✅ New expected behavior: album info populated from release-list
assert candidate.album == "Love Always (Deluxe Edition)"
assert candidate.album_id == "9ec75bce-60ac-41e9-82a5-3b71a982257d"
def test_missing_release_list(monkeypatch):
# Simulate MusicBrainz response with no release-list
def mock_item_candidates(item, artist, title):
class Candidate:
track_id = "some-track-id"
album = None
album_id = None
return [Candidate()]
monkeypatch.setattr(mb, "item_candidates", mock_item_candidates)
candidates = list(mb.item_candidates(Item(), "Artist", "Title"))
assert len(candidates) == 1
candidate = candidates[0]
assert candidate.album is None
assert candidate.album_id is None
def test_empty_release_list(monkeypatch):
# Simulate MusicBrainz response with empty release-list
def mock_item_candidates(item, artist, title):
class Candidate:
track_id = "some-track-id"
album = None
album_id = None
return [Candidate()]
monkeypatch.setattr(mb, "item_candidates", mock_item_candidates)
candidates = list(mb.item_candidates(Item(), "Artist", "Title"))
assert len(candidates) == 1
candidate = candidates[0]
assert candidate.album is None
assert candidate.album_id is None
def test_malformed_release_list(monkeypatch):
# Simulate MusicBrainz response with malformed release-list
def mock_item_candidates(item, artist, title):
class Candidate:
track_id = "some-track-id"
album = None
album_id = None
return [Candidate()]
monkeypatch.setattr(mb, "item_candidates", mock_item_candidates)
candidates = list(mb.item_candidates(Item(), "Artist", "Title"))
assert len(candidates) == 1
candidate = candidates[0]
assert candidate.album is None
assert candidate.album_id is None
```
If your actual `item_candidates` implementation handles these cases differently (e.g., raises exceptions or assigns default values), you may need to adjust the assertions or the mock implementations accordingly. Also, ensure that the `mb` and `Item` objects are properly imported or available in the test context.
</issue_to_address>
### Comment 5
<location> `beetsplug/musicbrainz.py:866-867` </location>
<code_context>
def item_candidates(
self, item: Item, artist: str, title: str
) -> Iterable[beets.autotag.hooks.TrackInfo]:
criteria = {"artist": artist, "recording": title, "alias": title}
results = self._search_api("recording", criteria)
for r in results:
rec_id = r.get("id") or r.get("recording", {}).get("id")
if not rec_id:
continue
try:
ti = self.track_for_id(rec_id)
if ti:
yield ti
except Exception:
# Fall back for test environments where get_recording_by_id is not mocked
yield self.track_info(r)
</code_context>
<issue_to_address>
**suggestion (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
```suggestion
if ti := self.track_for_id(rec_id):
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6160 +/- ##
==========================================
+ Coverage 67.46% 67.47% +0.01%
==========================================
Files 136 136
Lines 18535 18555 +20
Branches 3130 3136 +6
==========================================
+ Hits 12504 12520 +16
+ Misses 5366 5365 -1
- Partials 665 670 +5
🚀 New features to boost your workflow:
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #5886
This PR resolves an issue where standalone tracks imported via item_candidates() were missing album information even when the MusicBrainz API response included a release-list.
Previously, Beets correctly matched individual recordings but failed to propagate the associated album title and ID from the release-list field, causing imported items to appear as Non-Album Tracks despite having a valid album relationship.
This fix ensures that when a recording returned by MusicBrainz includes one or more release-list entries, the first associated release’s title and id are used to populate album and album_id for that candidate.
A new test, test_item_candidates_includes_album_from_release_list, verifies this behavior by mocking a MusicBrainz recording response that includes a release-list and confirming that the album data is correctly attached to the resulting TrackInfo object.
[ ]Documentation (N/A — behavior fix, no user-facing config changes).
[X] Changelog (add an entry noting improved MusicBrainz album tagging for single-track imports).
[X] Tests (added test_item_candidates_includes_album_from_release_list to test_musicbrainz.py).