Skip to content

Commit

Permalink
Merge branch 'master' into auto_formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
Serene-Arc authored Oct 3, 2023
2 parents 863cf20 + b822fe0 commit 3ba89e8
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 14 deletions.
80 changes: 80 additions & 0 deletions beetsplug/autobpm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# This file is part of beets.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

"""Uses Librosa to calculate the `bpm` field.
"""


from beets import ui
from beets import util
from beets.plugins import BeetsPlugin

from librosa import load, beat
from soundfile import LibsndfileError


class AutoBPMPlugin(BeetsPlugin):

def __init__(self):
super().__init__()
self.config.add({
'auto': True,
'overwrite': False,
})

if self.config['auto'].get(bool):
self.import_stages = [self.imported]

def commands(self):
cmd = ui.Subcommand('autobpm',
help='detect and add bpm from audio using Librosa')
cmd.func = self.command
return [cmd]

def command(self, lib, opts, args):
self.calculate_bpm(lib.items(ui.decargs(args)),
write=ui.should_write())

def imported(self, session, task):
self.calculate_bpm(task.imported_items())

def calculate_bpm(self, items, write=False):
overwrite = self.config['overwrite'].get(bool)

for item in items:
if item['bpm']:
self._log.info('found bpm {0} for {1}',
item['bpm'], util.displayable_path(item.path))
if not overwrite:
continue

try:
y, sr = load(util.syspath(item.path), res_type='kaiser_fast')
except LibsndfileError as exc:
self._log.error('LibsndfileError: failed to load {0} {1}',
util.displayable_path(item.path), exc)
continue
except ValueError as exc:
self._log.error('ValueError: failed to load {0} {1}',
util.displayable_path(item.path), exc)
continue

tempo, _ = beat.beat_track(y=y, sr=sr)
bpm = round(tempo)
item['bpm'] = bpm
self._log.info('added computed bpm {0} for {1}',
bpm, util.displayable_path(item.path))

if write:
item.try_write()
item.store()
2 changes: 1 addition & 1 deletion beetsplug/lastgenre/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@


def deduplicate(seq):
"""Remove duplicates from sequence wile preserving order.
"""Remove duplicates from sequence while preserving order.
"""
seen = set()
return [x for x in seq if x not in seen and not seen.add(x)]
Expand Down
2 changes: 1 addition & 1 deletion beetsplug/substitute.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
class Substitute(BeetsPlugin):
"""The substitute plugin class.
Create a template field function that subsitute the given field with the
Create a template field function that substitute the given field with the
given substitution rules. ``rules`` must be a list of (pattern,
replacement) pairs.
"""
Expand Down
6 changes: 3 additions & 3 deletions beetsplug/web/static/backbone.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@
},

// Fetch the model from the server. If the server's representation of the
// model differs from its current attributes, they will be overriden,
// model differs from its current attributes, they will be overridden,
// triggering a `"change"` event.
fetch : function(options) {
options || (options = {});
Expand Down Expand Up @@ -885,7 +885,7 @@
};

// Element lookup, scoped to DOM elements within the current view.
// This should be prefered to global lookups, if you're dealing with
// This should be preferred to global lookups, if you're dealing with
// a specific view.
var selectorDelegate = function(selector) {
return $(selector, this.el);
Expand Down Expand Up @@ -984,7 +984,7 @@
// Ensure that the View has a DOM element to render into.
// If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create
// an element from the `id`, `className` and `tagName` proeprties.
// an element from the `id`, `className` and `tagName` properties.
_ensureElement : function() {
if (!this.el) {
var attrs = this.attributes || {};
Expand Down
8 changes: 4 additions & 4 deletions beetsplug/web/static/jquery.js
Original file line number Diff line number Diff line change
Expand Up @@ -2278,7 +2278,7 @@ jQuery.fn.extend({
classNames = value.split( rspace );

while ( (className = classNames[ i++ ]) ) {
// check each className given, space seperated list
// check each className given, space separated list
state = isBool ? state : !self.hasClass( className );
self[ state ? "addClass" : "removeClass" ]( className );
}
Expand Down Expand Up @@ -3868,7 +3868,7 @@ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[
rNonWord = /\W/;

// Here we check if the JavaScript engine is using some sort of
// optimization where it does not always call our comparision
// optimization where it does not always call our comparison
// function. If that is the case, discard the hasDuplicate value.
// Thus far that includes Google Chrome.
[0, 0].sort(function() {
Expand Down Expand Up @@ -4180,7 +4180,7 @@ Sizzle.error = function( msg ) {
};

/**
* Utility function for retreiving the text value of an array of DOM nodes
* Utility function for retrieving the text value of an array of DOM nodes
* @param {Array|Element} elem
*/
var getText = Sizzle.getText = function( elem ) {
Expand Down Expand Up @@ -8111,7 +8111,7 @@ if ( jQuery.support.ajax ) {
xml;

// Firefox throws exceptions when accessing properties
// of an xhr when a network error occured
// of an xhr when a network error occurred
// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
try {

Expand Down
9 changes: 6 additions & 3 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,17 @@ New features:
:bug:`2786`
* Add support for ``artists`` and ``albumartists`` multi-valued tags.
:bug:`505`
* :doc:`/plugins/autobpm`: Add the `autobpm` plugin which uses Librosa to
calculate the BPM of the audio.
:bug:`3856`

Bug fixes:

* :doc:`/plugins/scrub`: Fixed the import behavior where scrubbed database tags
were restored to newly imported tracks with config settings ``scrub.auto: yes``
* :doc:`/plugins/scrub`: Fixed the import behavior where scrubbed database tags
were restored to newly imported tracks with config settings ``scrub.auto: yes``
and ``import.write: no``.
:bug:`4326`
* :doc:`/plugins/deezer`: Fixed the error where Deezer plugin would crash if non-Deezer id is passed during import.
* :doc:`/plugins/deezer`: Fixed the error where Deezer plugin would crash if non-Deezer id is passed during import.
* :doc:`/plugins/fetchart`: Fix fetching from Cover Art Archive when the
`maxwidth` option is set to one of the supported Cover Art Archive widths.
* :doc:`/plugins/discogs`: Fix "Discogs plugin replacing Feat. or Ft. with
Expand Down
25 changes: 25 additions & 0 deletions docs/plugins/autobpm.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
AutoBPM Plugin
==============

The `autobpm` plugin uses the `Librosa`_ library to calculate the BPM
of a track from its audio data and store it in the `bpm` field of your
database. It does so automatically when importing music or through
the ``beet autobpm [QUERY]`` command.

To use the ``autobpm`` plugin, enable it in your configuration (see
:ref:`using-plugins`).

Configuration
-------------

To configure the plugin, make a ``autobpm:`` section in your
configuration file. The available options are:

- **auto**: Analyze every file on import.
Otherwise, you need to use the ``beet autobpm`` command explicitly.
Default: ``yes``
- **overwrite**: Calculate a BPM even for files that already have a
`bpm` value.
Default: ``no``.

.. _Librosa: https://github.com/librosa/librosa/
5 changes: 5 additions & 0 deletions docs/plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ following to your configuration::
acousticbrainz
albumtypes
aura
autobpm
badfiles
bareasc
beatport
Expand Down Expand Up @@ -164,6 +165,9 @@ Metadata
:doc:`acousticbrainz <acousticbrainz>`
Fetch various AcousticBrainz metadata

:doc:`autobpm <autobpm>`
Use `Librosa`_ to calculate the BPM from the audio.

:doc:`bpm <bpm>`
Measure tempo using keystrokes.

Expand Down Expand Up @@ -222,6 +226,7 @@ Metadata
:doc:`zero <zero>`
Nullify fields by pattern or unconditionally.

.. _Librosa: https://github.com/librosa/librosa/
.. _KeyFinder: http://www.ibrahimshaath.co.uk/keyfinder/
.. _streaming_extractor_music: https://acousticbrainz.org/download

Expand Down
2 changes: 1 addition & 1 deletion docs/plugins/substitute.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ path formats. Specifically, it is intended to let you *canonicalize* names
such as artists: For example, perhaps you want albums from The Jimi Hendrix
Experience to be sorted into the same folder as solo Hendrix albums.

This plugin is intented as a replacement for the ``rewrite`` plugin. While
This plugin is intended as a replacement for the ``rewrite`` plugin. While
the ``rewrite`` plugin modifies the metadata, this plugin does not.

Enable the ``substitute`` plugin (see :ref:`using-plugins`), then make a ``substitute:`` section in your config file to contain your rules.
Expand Down
2 changes: 1 addition & 1 deletion extra/_beet
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ zstyle ":completion:${curcontext}:" cache-policy _beet_check_cache
_beet_check_cache () {
local cachefile="$(basename ${1})"
if [[ ! -a "${1}" ]] || [[ "${1}" -ot =beet ]]; then
# always update the cache if it doesnt exist, or if the beet executable changes
# always update the cache if it doesn't exist, or if the beet executable changes
return 0
fi
case cachefile; in
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ per-file-ignores =
./extra/release.py:D
./beetsplug/duplicates.py:D
./beetsplug/bpm.py:D
./beetsplug/autobpm.py:D
./beetsplug/convert.py:D
./beetsplug/info.py:D
./beetsplug/parentwork.py:D
Expand Down

0 comments on commit 3ba89e8

Please sign in to comment.