Skip to content
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

Add autobpm plugin #4923

Merged
merged 1 commit into from
Oct 2, 2023
Merged

Add autobpm plugin #4923

merged 1 commit into from
Oct 2, 2023

Conversation

tandy-1000
Copy link
Contributor

@tandy-1000 tandy-1000 commented Sep 24, 2023

This plugin uses librosa to automatically calculate the BPM for a track. It is based on the keyfinder plugin, and rounds the BPM to an int.

It's quite slow, but should be useful for DJs :).

Description

Fixes #3856

To Do

  • Documentation. (If you've add a new command-line flag, for example, find the appropriate page under docs/ to describe it.)
  • Changelog. (Add an entry to docs/changelog.rst near the top of the document.)
  • Tests. (Encouraged but not strictly required.)

@tandy-1000
Copy link
Contributor Author

Tested it:

$ beet autobpm
autobpm: added computed bpm 112 for /music/Compilations/Wormholes Part.2/04 Natrium.flac
autobpm: added computed bpm 185 for /music/2Pac featuring K‐Ci & JoJo/How Do U Want It/02 California Love (long radio edit).flac
autobpm: added computed bpm 92 for /music/2Pac featuring K‐Ci & JoJo/How Do U Want It/01 How Do U Want It (LP version).flac

I wonder if there's a smarter way to set the initial tempo that librosa uses for estimations...

@tandy-1000
Copy link
Contributor Author

tandy-1000 commented Sep 24, 2023

Another issue:

soundfile.LibsndfileError: Error opening b'/music/Compilations/Teklife VIP 2020/04 Don\xe2\x80\x99t Like.mp3': File contains data in an unimplemented format.

edit: this is only happening on MP3s, I've committed something to handle the error.

@tandy-1000
Copy link
Contributor Author

tandy-1000 commented Sep 25, 2023

Another error:

Traceback (most recent call last):
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/bin/.beet-wrapped", line 9, in <module>
    sys.exit(main())
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/lib/python3.10/site-packages/beets/ui/__init__.py", line 1304, in main
    _raw_main(args)
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/lib/python3.10/site-packages/beets/ui/__init__.py", line 1291, in _raw_main
    subcommand.func(lib, suboptions, subargs)
  File "/etc/beets/plugins/beetsplug/autobpm.py", line 46, in command
    self.calculate_bpm(lib.items(ui.decargs(args)), write=ui.should_write())
  File "/etc/beets/plugins/beetsplug/autobpm.py", line 59, in calculate_bpm
    y, sr = load(util.syspath(item.path), res_type="kaiser_fast")
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/util/decorators.py", line 88, in inner_f
    return f(*args, **kwargs)
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/core/audio.py", line 164, in load
    y, sr_native = __soundfile_load(path, offset, duration, dtype)
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/core/audio.py", line 208, in __soundfile_load
    y = sf_desc.read(frames=frame_duration, dtype=dtype, always_2d=False).T
  File "/nix/store/n9k6xw05vgf3xda9pj2k2b1i51dd0pfp-python3.10-soundfile-0.12.1/lib/python3.10/site-packages/soundfile.py", line 891, in read
    out = self._create_empty_array(frames, always_2d, dtype)
  File "/nix/store/n9k6xw05vgf3xda9pj2k2b1i51dd0pfp-python3.10-soundfile-0.12.1/lib/python3.10/site-packages/soundfile.py", line 1323, in _create_empty_array
    return np.empty(shape, dtype, order='C')
ValueError: array is too big; `arr.size * arr.dtype.itemsize` is larger than the maximum possible size.

Edit: the track that failed this is only 1:25.. I was expecting it to be a long one...

Copy link
Member

@sampsyo sampsyo left a comment

Choose a reason for hiding this comment

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

Nice!! This looks really great! It's a simple plugin but looks very useful.

I have just a couple of low-level suggestions; let us know if you have any questions.

beetsplug/autobpm.py Outdated Show resolved Hide resolved
docs/plugins/autobpm.rst Outdated Show resolved Hide resolved
docs/plugins/autobpm.rst Outdated Show resolved Hide resolved
@wisp3rwind
Copy link
Member

Another error:

Traceback (most recent call last):
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/bin/.beet-wrapped", line 9, in <module>
    sys.exit(main())
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/lib/python3.10/site-packages/beets/ui/__init__.py", line 1304, in main
    _raw_main(args)
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/lib/python3.10/site-packages/beets/ui/__init__.py", line 1291, in _raw_main
    subcommand.func(lib, suboptions, subargs)
  File "/etc/beets/plugins/beetsplug/autobpm.py", line 46, in command
    self.calculate_bpm(lib.items(ui.decargs(args)), write=ui.should_write())
  File "/etc/beets/plugins/beetsplug/autobpm.py", line 59, in calculate_bpm
    y, sr = load(util.syspath(item.path), res_type="kaiser_fast")
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/util/decorators.py", line 88, in inner_f
    return f(*args, **kwargs)
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/core/audio.py", line 164, in load
    y, sr_native = __soundfile_load(path, offset, duration, dtype)
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/core/audio.py", line 208, in __soundfile_load
    y = sf_desc.read(frames=frame_duration, dtype=dtype, always_2d=False).T
  File "/nix/store/n9k6xw05vgf3xda9pj2k2b1i51dd0pfp-python3.10-soundfile-0.12.1/lib/python3.10/site-packages/soundfile.py", line 891, in read
    out = self._create_empty_array(frames, always_2d, dtype)
  File "/nix/store/n9k6xw05vgf3xda9pj2k2b1i51dd0pfp-python3.10-soundfile-0.12.1/lib/python3.10/site-packages/soundfile.py", line 1323, in _create_empty_array
    return np.empty(shape, dtype, order='C')
ValueError: array is too big; `arr.size * arr.dtype.itemsize` is larger than the maximum possible size.

Edit: the track that failed this is only 1:25.. I was expecting it to be a long one...

From the traceback, this very much looks like a librosa bug, not one in the plugin. Maybe the file is in fact malformed and librosa doesn't properly validate (or check for plausibility) some header data? Or some parser bug?

@tandy-1000
Copy link
Contributor Author

Another error:

Traceback (most recent call last):
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/bin/.beet-wrapped", line 9, in <module>
    sys.exit(main())
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/lib/python3.10/site-packages/beets/ui/__init__.py", line 1304, in main
    _raw_main(args)
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/lib/python3.10/site-packages/beets/ui/__init__.py", line 1291, in _raw_main
    subcommand.func(lib, suboptions, subargs)
  File "/etc/beets/plugins/beetsplug/autobpm.py", line 46, in command
    self.calculate_bpm(lib.items(ui.decargs(args)), write=ui.should_write())
  File "/etc/beets/plugins/beetsplug/autobpm.py", line 59, in calculate_bpm
    y, sr = load(util.syspath(item.path), res_type="kaiser_fast")
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/util/decorators.py", line 88, in inner_f
    return f(*args, **kwargs)
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/core/audio.py", line 164, in load
    y, sr_native = __soundfile_load(path, offset, duration, dtype)
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/core/audio.py", line 208, in __soundfile_load
    y = sf_desc.read(frames=frame_duration, dtype=dtype, always_2d=False).T
  File "/nix/store/n9k6xw05vgf3xda9pj2k2b1i51dd0pfp-python3.10-soundfile-0.12.1/lib/python3.10/site-packages/soundfile.py", line 891, in read
    out = self._create_empty_array(frames, always_2d, dtype)
  File "/nix/store/n9k6xw05vgf3xda9pj2k2b1i51dd0pfp-python3.10-soundfile-0.12.1/lib/python3.10/site-packages/soundfile.py", line 1323, in _create_empty_array
    return np.empty(shape, dtype, order='C')
ValueError: array is too big; `arr.size * arr.dtype.itemsize` is larger than the maximum possible size.

Edit: the track that failed this is only 1:25.. I was expecting it to be a long one...

From the traceback, this very much looks like a librosa bug, not one in the plugin. Maybe the file is in fact malformed and librosa doesn't properly validate (or check for plausibility) some header data? Or some parser bug?

That's very possible, what do you recommend I do?

@wisp3rwind
Copy link
Member

wisp3rwind commented Sep 30, 2023

Another error:

Traceback (most recent call last):
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/bin/.beet-wrapped", line 9, in <module>
    sys.exit(main())
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/lib/python3.10/site-packages/beets/ui/__init__.py", line 1304, in main
    _raw_main(args)
  File "/nix/store/q4idnbq0gs5367jx5gbjp93iqz7qm9jd-beets-unstable-2022-08-27/lib/python3.10/site-packages/beets/ui/__init__.py", line 1291, in _raw_main
    subcommand.func(lib, suboptions, subargs)
  File "/etc/beets/plugins/beetsplug/autobpm.py", line 46, in command
    self.calculate_bpm(lib.items(ui.decargs(args)), write=ui.should_write())
  File "/etc/beets/plugins/beetsplug/autobpm.py", line 59, in calculate_bpm
    y, sr = load(util.syspath(item.path), res_type="kaiser_fast")
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/util/decorators.py", line 88, in inner_f
    return f(*args, **kwargs)
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/core/audio.py", line 164, in load
    y, sr_native = __soundfile_load(path, offset, duration, dtype)
  File "/nix/store/3zhwp829706gszrxy447j4rxifyqj3ld-python3.10-librosa-0.9.2/lib/python3.10/site-packages/librosa/core/audio.py", line 208, in __soundfile_load
    y = sf_desc.read(frames=frame_duration, dtype=dtype, always_2d=False).T
  File "/nix/store/n9k6xw05vgf3xda9pj2k2b1i51dd0pfp-python3.10-soundfile-0.12.1/lib/python3.10/site-packages/soundfile.py", line 891, in read
    out = self._create_empty_array(frames, always_2d, dtype)
  File "/nix/store/n9k6xw05vgf3xda9pj2k2b1i51dd0pfp-python3.10-soundfile-0.12.1/lib/python3.10/site-packages/soundfile.py", line 1323, in _create_empty_array
    return np.empty(shape, dtype, order='C')
ValueError: array is too big; `arr.size * arr.dtype.itemsize` is larger than the maximum possible size.

Edit: the track that failed this is only 1:25.. I was expecting it to be a long one...

From the traceback, this very much looks like a librosa bug, not one in the plugin. Maybe the file is in fact malformed and librosa doesn't properly validate (or check for plausibility) some header data? Or some parser bug?

That's very possible, what do you recommend I do?

I'd have suggested to report at librosa or soundfile, but there's already bastibe/python-soundfile#361 which didn't seem to garner much attention (which is unfortunate, since this really looks like soundfile blindly trusting some audio metadata, and I'd argue that audiofiles should really be treated as untrusted input here).

From beets' side, it might be prudent to have a quite generic try ... catch Exception around the librosa call, and then instead of crashing, log an error message that the bpm calculation failed, and also log the original exception.

EDIT: Nevermind, this is pretty much what @sampsyo already brought up in the discussion around the ValueError handler.

@tandy-1000
Copy link
Contributor Author

tandy-1000 commented Sep 30, 2023

If we are all good now, please let me know and I can rebase and squash before merging :)

EDIT: nevermind, all squashed!

This plugin uses librosa to automatically calculate the BPM for a track.
It is based on the keyfinder plugin, and rounds the BPM to an int.

Co-authored-by: Adrian Sampson <adrian@radbox.org>
@sampsyo
Copy link
Member

sampsyo commented Oct 2, 2023

All looks good with the new exception logging; let's call this good!

@sampsyo sampsyo merged commit b822fe0 into beetbox:master Oct 2, 2023
14 checks passed
@tandy-1000
Copy link
Contributor Author

All looks good with the new exception logging; let's call this good!

awesome, thanks everyone for the review!

@tandy-1000 tandy-1000 deleted the autobpm branch October 2, 2023 21:54
@JOJ0
Copy link
Member

JOJ0 commented Oct 3, 2023

@tandy-1000 wow! Just here to say: Many many thanks! I am a DJ and often what other existing bpm beets plugins give me is not satisfactory! Especially with "off-beaty" Drum And Bass I get a lot of wrong values (100-110-ish results LOL). Anyway, hope to be able to test this awesome new feature soon!

@snejus snejus mentioned this pull request Aug 19, 2024
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement an automatic bmp computation
4 participants