Skip to content

Tidal Metadata Plugin#6520

Merged
semohr merged 30 commits intomasterfrom
tidal
Apr 20, 2026
Merged

Tidal Metadata Plugin#6520
semohr merged 30 commits intomasterfrom
tidal

Conversation

@semohr
Copy link
Copy Markdown
Contributor

@semohr semohr commented Apr 10, 2026

Description

This PR introduces tidal as metadatasource. It add both an minimal api layer and the typical metadata source plugin capabilities.

Details

The implementation provides a small API layer consisting of TidalAPI for high-level album and track fetching, and TidalSession which extends requests.Session with token authentication, automatic rate limiting (~4 req/s via RateLimitAdapter), and pagination resolution following the JSON:API spec.

Authentication is handled through an OAuth2 PKCE flow accessible via beet tidal --auth, with automatic token refresh when the access token expires.

Metadata parsing handles Tidal's JSON:API response format, extracting album and track information including ISO 8601 duration conversion, artist relationships, and copyright/label data.

Input wanted

The API layer currently lacks comprehensive test coverage. Setting up proper tests would require either mocking all outgoing requests or creating a dedicated test token (which necessitates an account and might require read/write to github secrets).

Are we comfortable with the current approach of unit testing the plugin itself while mocking all requests?

TODOs

  • Documentation
  • candidate and item_candidates lookup
  • It should be possible to optimize batched lookups
  • Add tests for candidates and item_candidates
  • Implement batching for more than 20 filters

Refs

thanks to @jcjordyn130 for his initial implementations in #5637 and #4641

@github-actions

This comment was marked as resolved.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 65.86826% with 114 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.75%. Comparing base (c9cee0d) to head (aa17a50).
⚠️ Report is 31 commits behind head on master.

Files with missing lines Patch % Lines
beetsplug/tidal/api.py 32.53% 56 Missing ⚠️
beetsplug/tidal/session.py 40.00% 30 Missing ⚠️
beetsplug/tidal/__init__.py 85.32% 18 Missing and 9 partials ⚠️
beetsplug/_utils/requests.py 94.11% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6520      +/-   ##
==========================================
- Coverage   71.86%   71.75%   -0.11%     
==========================================
  Files         156      159       +3     
  Lines       20181    20515     +334     
  Branches     3211     3262      +51     
==========================================
+ Hits        14503    14721     +218     
- Misses       5000     5105     +105     
- Partials      678      689      +11     
Files with missing lines Coverage Δ
beets/util/id_extractors.py 78.57% <ø> (ø)
beetsplug/_utils/requests.py 97.72% <94.11%> (-0.87%) ⬇️
beetsplug/tidal/__init__.py 85.32% <85.32%> (ø)
beetsplug/tidal/session.py 40.00% <40.00%> (ø)
beetsplug/tidal/api.py 32.53% <32.53%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@semohr semohr force-pushed the tidal branch 3 times, most recently from 347624d to 452868d Compare April 11, 2026 13:04
@semohr semohr marked this pull request as ready for review April 11, 2026 13:35
@semohr semohr requested review from a team and snejus as code owners April 11, 2026 13:35
Copilot AI review requested due to automatic review settings April 11, 2026 13:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

grug see PR add new tidal metadata source plugin so beets autotagger can fetch album/track metadata from Tidal API, with auth flow + small API client layer.

Changes:

  • add Tidal plugin (TidalPlugin) with album/track lookup, search, and JSON:API parsing
  • add OAuth2 PKCE auth + token save/refresh + rate limited session + pagination merge
  • add docs + changelog + initial unit tests, plus shared RateLimitAdapter + tidal id extractor

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
beetsplug/tidal/__init__.py main metadata source plugin logic (lookup, search, parse to AlbumInfo/TrackInfo)
beetsplug/tidal/api.py TidalSession (auth, refresh, rate limit, pagination) + TidalAPI endpoints
beetsplug/tidal/authenticate.py PKCE browser/manual redirect auth flow + token serialization
beetsplug/tidal/api_types.py TypedDict models for Tidal JSON:API responses
beetsplug/_utils/requests.py add RateLimitAdapter for shared request throttling
beets/util/id_extractors.py add/update Tidal ID extraction regex
docs/plugins/tidal.rst new user docs for tidal plugin + config + auth usage
docs/plugins/index.rst add tidal to plugin docs index
docs/changelog.rst add changelog entry for new plugin
test/plugins/test_tidal.py add parsing + lookup unit tests with mocked API
.github/CODEOWNERS assign ownership for new tidal plugin folder

Comment thread beets/util/id_extractors.py Outdated
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/__init__.py
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/api.py Outdated
Comment thread beetsplug/tidal/api.py Outdated
Comment thread beetsplug/tidal/api.py Outdated
Comment thread beetsplug/tidal/api.py
Comment thread beetsplug/tidal/api.py Outdated
@jcjordyn130
Copy link
Copy Markdown
Contributor

Yes, I'm still around.

Sorry, life was pretty busy over the past year and I lost track even though I did see your original comment.

The code looks really good, and I never realized TIDAL allows for ISRC/UPC lookup (very useful).

I am wondering, why are we rolling our own API wrapper here?

@semohr
Copy link
Copy Markdown
Contributor Author

semohr commented Apr 11, 2026

No worries. I know how it goes. Really glad you’re still around!

I am wondering, why are we rolling our own API wrapper here?

We’ve run into issues before with API dependencies in beets not being well maintained, so this felt like a safer path. On top of that, we likely wouldn’t need most of the features a full-fledged API library would bring anyway.
Another key reason is that I want to keep the API layer decoupled from the plugin design. It does mean a bit more work on our side to maintain it, but in the long run I think the added flexibility and control will be well worth it.

I’m not completely set on this approach, but we recently did something similar with musicbrainz, so it feels like a reasonable direction for now.


I also noticed that your original PRs included quite a few more features (art, label regex, popularity, etc.). I’d definitely love to revisit those and would be happy to have you onboard for future enhancements/PRs build ontop of this one (ofc. only if you have the time and are interested in continuing to contribute).

@arsaboo
Copy link
Copy Markdown
Contributor

arsaboo commented Apr 13, 2026

It may be worthwhile to at least get album art and popularity in this iteration if it is not too much of a change. I understand if we want to leave it for a subsequent PR.

@jcjordyn130
Copy link
Copy Markdown
Contributor

Sounds great to me.

The reason I went with an external API was to avoid having to work on oauth authentication, but it does make more sense to maintain our own wrapper.

I'd be willing to help maintain this plugin.

The main thing I'm wondering is if we should implement album art here or in fetchart?

@arsaboo
Copy link
Copy Markdown
Contributor

arsaboo commented Apr 13, 2026

@jcjordyn130 it should probably be part of fetchart (where other sources are identified) as long as the wrapper provides the pipeline.

@semohr
Copy link
Copy Markdown
Contributor Author

semohr commented Apr 13, 2026

It may be worthwhile to at least get album art and popularity in this iteration if it is not too much of a change. I understand if we want to leave it for a subsequent PR.

I think reviewing this will already be challenging because of the size 😨 If we already want to work on these features tho, how about we create additional PRs and base them on this branch?
I'm was also hesitant to start on those features before we’re confident about the abstraction and architectural decisions made here.

Copy link
Copy Markdown
Member

@snejus snejus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏🏼 that's some good work here, added a couple of comments but nothing very significant

Comment thread beetsplug/tidal/__init__.py
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/__init__.py
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/api.py Outdated
Comment thread beetsplug/tidal/api_types.py Outdated
Comment thread beetsplug/tidal/api_types.py Outdated
Comment thread beetsplug/tidal/authenticate.py Outdated
Comment thread test/plugins/test_tidal.py
@semohr semohr force-pushed the tidal branch 2 times, most recently from 3f57edb to 1dfc8b7 Compare April 13, 2026 19:09
Comment thread beetsplug/_utils/requests.py
Copy link
Copy Markdown
Member

@snejus snejus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I can see requests-oauthlib slightly simplified the auth! A couple of comments

Comment thread beetsplug/tidal/__init__.py
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/__init__.py Outdated
Comment thread beetsplug/tidal/api.py Outdated
Comment thread beetsplug/tidal/api.py Outdated
Comment thread test/plugins/test_tidal.py
semohr added 26 commits April 20, 2026 20:55
amount of refactoring but im more happy with the abstraction now.
- too strict ids
- typos and docstring improvements
- Renamed id to _id
- Aligned line breaks for some comment with actual ruff line length
- Removed comment dividers
- Removed a number of unnecessary or duplicate comments
- Renamed _extract_* methods to _parse_*
- Renamed release to date_parts
- Using .get instead of .request("GET"...)
@semohr semohr enabled auto-merge April 20, 2026 18:57
@semohr semohr merged commit b13a4fa into master Apr 20, 2026
20 checks passed
@semohr semohr deleted the tidal branch April 20, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants