diff --git a/beetsplug/echonest_tempo.py b/beetsplug/echonest_tempo.py new file mode 100644 index 000000000..500981ad8 --- /dev/null +++ b/beetsplug/echonest_tempo.py @@ -0,0 +1,108 @@ +# This file is part of beets. +# Copyright 2012, David Brenner +# +# 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. + +"""Gets tempo (bpm) for imported music from the EchoNest API. Requires +the pyechonest library (https://github.com/echonest/pyechonest). +""" +import logging +from beets.plugins import BeetsPlugin +from beets import ui +from beets.ui import commands +import pyechonest.config +import pyechonest.song + +# Global logger. + +log = logging.getLogger('beets') + +# The user's EchoNest API key, if provided +_echonest_apikey = None + +def fetch_item_tempo(lib, loglevel, item, write): + """Fetch and store tempo for a single item. If ``write``, then the + tempo will also be written to the file itself in the bpm field. The + ``loglevel`` parameter controls the visibility of the function's + status log messages. + """ + # Skip if the item already has the tempo field. + if item.bpm: + log.log(loglevel, u'bpm already present: %s - %s' % + (item.artist, item.title)) + return + + # Fetch tempo. + tempo = get_tempo(item.artist, item.title) + if not tempo: + log.log(loglevel, u'tempo not found: %s - %s' % + (item.artist, item.title)) + return + + log.log(loglevel, u'fetched tempo: %s - %s' % + (item.artist, item.title)) + item.bpm = tempo + if write: + item.write() + lib.store(item) + +def get_tempo(artist, title): + "gets the tempo for a song" + + # Unfortunately, all we can do is search by artist and title. EchoNest + # supports foreign ids from MusicBrainz, but currently only for artists, + # not individual tracks/recordings. + results = pyechonest.song.search(artist=artist, title=title, results=1, buckets=['audio_summary']) + if len(results) > 0: + return results[0].audio_summary['tempo'] + else: + return None + +AUTOFETCH = True +class EchoNestTempoPlugin(BeetsPlugin): + def __init__(self): + super(EchoNestTempoPlugin, self).__init__() + self.import_stages = [self.imported] + + def commands(self): + cmd = ui.Subcommand('tempo', help='fetch song tempo (bpm)') + cmd.parser.add_option('-p', '--print', dest='printlyr', + action='store_true', default=False, + help='print tempo (bpm) to console') + def func(lib, config, opts, args): + # The "write to files" option corresponds to the + # import_write config value. + if not _echonest_apikey: + raise ui.UserError('no EchoNest user API key provided') + + write = ui.config_val(config, 'beets', 'import_write', + commands.DEFAULT_IMPORT_WRITE, bool) + + for item in lib.items(ui.decargs(args)): + fetch_item_tempo(lib, logging.INFO, item, write) + if opts.printlyr and item.bpm: + ui.print_(item.bpm) + cmd.func = func + return [cmd] + + def configure(self, config): + global AUTOFETCH, _echonest_apikey + AUTOFETCH = ui.config_val(config, 'echonest_tempo', 'autofetch', True, bool) + _echonest_apikey = ui.config_val(config, 'echonest_tempo', 'apikey', + None) + pyechonest.config.ECHO_NEST_API_KEY = _echonest_apikey + + # Auto-fetch tempo on import. + def imported(self, config, task): + if AUTOFETCH: + for item in task.imported_items(): + fetch_item_tempo(config.lib, logging.DEBUG, item, False) diff --git a/docs/plugins/echonest_tempo.rst b/docs/plugins/echonest_tempo.rst new file mode 100644 index 000000000..5bb0fb14b --- /dev/null +++ b/docs/plugins/echonest_tempo.rst @@ -0,0 +1,69 @@ +EchoNest Tempo Plugin +============= + +The ``echonest_tempo`` plugin fetches and stores a track's tempo (bpm field) + from the `EchoNest API`_ + +.. _EchoNest API: http://developer.echonest.com/ + +Installing Dependencies +----------------------- + +This plugin requires the pyechonest library in order to talk to the EchoNest +API. + +There are packages for most major linux distributions, you can download the +library from the EchoNest, or you can install the library from `pip`_, +like so:: + + $ pip install pyacoustid + +.. _pip: http://pip.openplans.org/ + +Configuring +----------- + +The plugin requires an EchoNest API key in order to function. To do this, +first `apply for an API key`_ from the EchoNest. Then, add the key to +your :doc:`/reference/config` as the value ``apikey`` in a section called +``echonest_tempo`` like so:: + + [echonest_tempo] + apikey=YOUR_API_KEY + +In addition, this plugin has one configuration option, ``autofetch``, which +lets you disable automatic tempo fetching during import. To do so, add this +to your ``~/.beetsconfig``:: + + [echonest_tempo] + apikey=YOUR_API_KEY + autofetch: no + +.. _apply for an API key: http://developer.echonest.com/account/register + +Fetch Tempo During Import +-------------------------- + +To automatically fetch the tempo for songs you import, just enable the plugin +by putting ``echonest_tempo`` on your config file's ``plugins`` line (see +:doc:`/plugins/index`), along with adding your EchoNest API key to your +``~/.beetsconfig``. When importing new files, beets will now fetch the +tempo for files that don't already have them. The bpm field will be stored in +the beets database. If the ``import_write`` config option is on, then the +tempo will also be written to the files' tags. + +This behavior can be disabled with the ``autofetch`` config option (see below). + +Fetching Tempo Manually +------------------------ + +The ``echonest_tempo`` command provided by this plugin fetches tempos for +items that match a query (see :doc:`/reference/query`). For example, +``beet tempo magnetic fields absolutely cuckoo`` will get the tempo for the +appropriate Magnetic Fields song, ``beet tempo magnetic fields`` will get +tempos for all my tracks by that band, and ``beet tempo`` will get tempos for +my entire library. The tempos will be added to the beets database and, if +``import_write`` is on, embedded into files' metadata. + +The ``-p`` option to the ``tempo`` command makes it print tempos out to the +console so you can view the fetched (or previously-stored) tempos. diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index b608245ba..1132de456 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -37,6 +37,7 @@ disabled by default, but you can turn them on as described above. chroma lyrics + echonest_tempo bpd mpdupdate fetchart @@ -67,6 +68,7 @@ Metadata '''''''' * :doc:`lyrics`: Automatically fetch song lyrics. +* :doc:`echonest_tempo`: Automatically fetch song tempos (bpm). * :doc:`lastgenre`: Fetch genres based on Last.fm tags. * :doc:`fetchart`: Fetch album cover art from various sources. * :doc:`embedart`: Embed album art images into files' metadata.