From ff81bbf8f5b7ce008ea49dd2c979ad7ba5be8f50 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Wed, 30 Dec 2015 22:03:50 -0600 Subject: [PATCH 01/30] ABrainz: Added first pass of plugin. Only prints MBID and response status code to console. --- beetsplug/acoustic.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 beetsplug/acoustic.py diff --git a/beetsplug/acoustic.py b/beetsplug/acoustic.py new file mode 100644 index 000000000..4eb7ea2de --- /dev/null +++ b/beetsplug/acoustic.py @@ -0,0 +1,38 @@ +from __future__ import (division, absolute_import, print_function, + unicode_literals) + + +from beets import plugins, ui +import requests + +ACOUSTIC_URL = "http://acousticbrainz.org/" +LEVEL = "/high-level" +PLUGIN_DESCRIPTION = "Fetch metadata from AcousticBrainz" + + +class AcousticPlugin(plugins.BeetsPlugin): + def __init__(self): + super(AcousticPlugin, self).__init__() + + def commands(self): + cmd = ui.Subcommand('acoustic', help=PLUGIN_DESCRIPTION) + + def func(lib, opts, args): + fetch_info(lib) + + cmd.func = func + return [cmd] + + +# Currently outputs MBID and corresponding request status code +def fetch_info(lib): + for item in lib.items(): + if item.mb_trackid: + r = requests.get(generate_url(item.mb_trackid)) + print(item.mb_trackid) + print(r.status_code) + + +# Generates url of AcousticBrainz end point for given MBID +def generate_url(mbid): + return ACOUSTIC_URL + mbid + LEVEL From c0dde8da2848eb1fd884a2edb54eb67c0334b928 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 30 Dec 2015 20:23:29 -0800 Subject: [PATCH 02/30] echonest: Fix capitalization in help text --- beetsplug/echonest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/echonest.py b/beetsplug/echonest.py index 5e4522ff9..182c7f9a2 100644 --- a/beetsplug/echonest.py +++ b/beetsplug/echonest.py @@ -465,7 +465,7 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): def commands(self): fetch_cmd = ui.Subcommand('echonest', - help='Fetch metadata from the EchoNest') + help='fetch metadata from The Echo Nest') fetch_cmd.parser.add_option( '-f', '--force', dest='force', action='store_true', default=False, help='(re-)download information from the EchoNest' From 29e6cbedfe4fa3eb31ca033ef776c98eafa605c6 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Wed, 30 Dec 2015 22:27:53 -0600 Subject: [PATCH 03/30] ABrainz: Added copyright header, docstrings, and followed long line standards. --- beetsplug/acoustic.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/beetsplug/acoustic.py b/beetsplug/acoustic.py index 4eb7ea2de..99eb024a8 100644 --- a/beetsplug/acoustic.py +++ b/beetsplug/acoustic.py @@ -1,3 +1,20 @@ +# -*- coding: utf-8 -*- +# This file is part of beets. +# Copyright 2016, Adrian Sampson. +# +# 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. + +""" Fetch various AcousticBrainz metadata using MBID +""" from __future__ import (division, absolute_import, print_function, unicode_literals) @@ -7,7 +24,6 @@ import requests ACOUSTIC_URL = "http://acousticbrainz.org/" LEVEL = "/high-level" -PLUGIN_DESCRIPTION = "Fetch metadata from AcousticBrainz" class AcousticPlugin(plugins.BeetsPlugin): @@ -15,7 +31,8 @@ class AcousticPlugin(plugins.BeetsPlugin): super(AcousticPlugin, self).__init__() def commands(self): - cmd = ui.Subcommand('acoustic', help=PLUGIN_DESCRIPTION) + cmd = ui.Subcommand('acoustic', + help="fetch metadata from AcousticBrainz") def func(lib, opts, args): fetch_info(lib) @@ -24,8 +41,9 @@ class AcousticPlugin(plugins.BeetsPlugin): return [cmd] -# Currently outputs MBID and corresponding request status code def fetch_info(lib): + """Currently outputs MBID and corresponding request status code + """ for item in lib.items(): if item.mb_trackid: r = requests.get(generate_url(item.mb_trackid)) @@ -33,6 +51,7 @@ def fetch_info(lib): print(r.status_code) -# Generates url of AcousticBrainz end point for given MBID def generate_url(mbid): + """Generates url of AcousticBrainz end point for given MBID + """ return ACOUSTIC_URL + mbid + LEVEL From 5369a60a2adadce59d0ead54eaab76c84e55b168 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 30 Dec 2015 21:21:30 -0800 Subject: [PATCH 04/30] More details on log levels (fix #1781) --- docs/dev/plugins.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/dev/plugins.rst b/docs/dev/plugins.rst index 3b3ad80a2..b7c5d82f9 100644 --- a/docs/dev/plugins.rst +++ b/docs/dev/plugins.rst @@ -517,16 +517,18 @@ str.format-style string formatting. So you can write logging calls like this:: When beets is in verbose mode, plugin messages are prefixed with the plugin name to make them easier to see. -What messages will be logged depends on the logging level and the action +Which messages will be logged depends on the logging level and the action performed: -* On import stages and event handlers, the default is ``WARNING`` messages and - above. -* On direct actions, the default is ``INFO`` or above, as with the rest of - beets. +* Inside import stages and event handlers, the default is ``WARNING`` messages + and above. +* Everywhere else, the default is ``INFO`` or above. -The verbosity can be increased with ``--verbose`` flags: each flags lowers the -level by a notch. +The verbosity can be increased with ``--verbose`` (``-v``) flags: each flags +lowers the level by a notch. That means that, with a single ``-v`` flag, event +handlers won't have their ``DEBUG`` messages displayed, but command functions +(for example) will. With ``-vv`` on the command line, ``DEBUG`` messages will +be displayed everywhere. This addresses a common pattern where plugins need to use the same code for a command and an import stage, but the command needs to print more messages than From 247e35161ea134dbd86ba57962acf66012eb4a86 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 30 Dec 2015 21:23:07 -0800 Subject: [PATCH 05/30] CLI help: say that -v can appear twice (#1781) --- beets/ui/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index adc47d095..eaf79a0ce 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -1172,7 +1172,7 @@ def _raw_main(args, lib=None): parser.add_option('-d', '--directory', dest='directory', help="destination music directory") parser.add_option('-v', '--verbose', dest='verbose', action='count', - help='print debugging information') + help='log more details (use twice for even more)') parser.add_option('-c', '--config', dest='config', help='path to configuration file') parser.add_option('-h', '--help', dest='help', action='store_true', From 9daa02b5b0b9726dec5b85c51e639646b256b1d3 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Wed, 30 Dec 2015 23:34:56 -0600 Subject: [PATCH 06/30] ABrainz: Added write for three attributes to metadata and easier dictionary traversal format. --- beetsplug/acoustic.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/beetsplug/acoustic.py b/beetsplug/acoustic.py index 99eb024a8..62ea8aa6f 100644 --- a/beetsplug/acoustic.py +++ b/beetsplug/acoustic.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # This file is part of beets. -# Copyright 2016, Adrian Sampson. +# Copyright 2016, Ohm Patel. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -18,9 +18,9 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) +import requests from beets import plugins, ui -import requests ACOUSTIC_URL = "http://acousticbrainz.org/" LEVEL = "/high-level" @@ -46,12 +46,32 @@ def fetch_info(lib): """ for item in lib.items(): if item.mb_trackid: - r = requests.get(generate_url(item.mb_trackid)) - print(item.mb_trackid) - print(r.status_code) + rs = requests.get(generate_url(item.mb_trackid)).json() + + item.abrainz_danceable = get_value(rs, ["highlevel", + "danceability", + "all", + "danceable"]) + item.abrainz_happy = get_value(rs, ["highlevel", + "mood_happy", + "all", + "happy"]) + item.abrainz_party = get_value(rs, ["highlevel", + "mood_party", + "all", + "party"]) + + item.write() + item.store() def generate_url(mbid): """Generates url of AcousticBrainz end point for given MBID """ return ACOUSTIC_URL + mbid + LEVEL + + +def get_value(data, map_path): + """Allows traversal of dictionary with cleaner formatting + """ + return reduce(lambda d, k: d[k], map_path, data) From 59a1333732c12cf32cf2408103cd98f579c9f7e7 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Wed, 30 Dec 2015 23:38:19 -0600 Subject: [PATCH 07/30] ABrainz: Renamed danceable to dance to match five character attribute --- beetsplug/acoustic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beetsplug/acoustic.py b/beetsplug/acoustic.py index 62ea8aa6f..eab5d2c72 100644 --- a/beetsplug/acoustic.py +++ b/beetsplug/acoustic.py @@ -48,10 +48,10 @@ def fetch_info(lib): if item.mb_trackid: rs = requests.get(generate_url(item.mb_trackid)).json() - item.abrainz_danceable = get_value(rs, ["highlevel", - "danceability", - "all", - "danceable"]) + item.abrainz_dance = get_value(rs, ["highlevel", + "danceability", + "all", + "danceable"]) item.abrainz_happy = get_value(rs, ["highlevel", "mood_happy", "all", From ed6d9087a9ac60224ac0029cb5cce8a965dc2ba8 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 00:50:49 -0600 Subject: [PATCH 08/30] ABrainz: Added error catching and renamed to abrainz --- beetsplug/{acoustic.py => abrainz.py} | 44 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 18 deletions(-) rename beetsplug/{acoustic.py => abrainz.py} (54%) diff --git a/beetsplug/acoustic.py b/beetsplug/abrainz.py similarity index 54% rename from beetsplug/acoustic.py rename to beetsplug/abrainz.py index eab5d2c72..4ebc3d0d6 100644 --- a/beetsplug/acoustic.py +++ b/beetsplug/abrainz.py @@ -31,35 +31,40 @@ class AcousticPlugin(plugins.BeetsPlugin): super(AcousticPlugin, self).__init__() def commands(self): - cmd = ui.Subcommand('acoustic', + cmd = ui.Subcommand('abrainz', help="fetch metadata from AcousticBrainz") def func(lib, opts, args): - fetch_info(lib) + fetch_info(self, lib) cmd.func = func return [cmd] -def fetch_info(lib): +def fetch_info(self, lib): """Currently outputs MBID and corresponding request status code """ for item in lib.items(): if item.mb_trackid: - rs = requests.get(generate_url(item.mb_trackid)).json() + rs = requests.get(generate_url(item.mb_trackid)) - item.abrainz_dance = get_value(rs, ["highlevel", - "danceability", - "all", - "danceable"]) - item.abrainz_happy = get_value(rs, ["highlevel", - "mood_happy", - "all", - "happy"]) - item.abrainz_party = get_value(rs, ["highlevel", - "mood_party", - "all", - "party"]) + try: + rs.json() + except ValueError: + self._log.debug('abrainz: Invalid Response: {}', rs.text) + + item.danceable = get_value(self._log, rs.json(), ["highlevel", + "danceability", + "all", + "danceable"]) + item.mood_happy = get_value(self._log, rs.json(), ["highlevel", + "mood_happy", + "all", + "happy"]) + item.mood_party = get_value(self._log, rs.json(), ["highlevel", + "mood_party", + "all", + "party"]) item.write() item.store() @@ -71,7 +76,10 @@ def generate_url(mbid): return ACOUSTIC_URL + mbid + LEVEL -def get_value(data, map_path): +def get_value(log, data, map_path): """Allows traversal of dictionary with cleaner formatting """ - return reduce(lambda d, k: d[k], map_path, data) + try: + return reduce(lambda d, k: d[k], map_path, data) + except KeyError: + log.debug('Invalid Path: {}', map_path) From d15d7efbf4eda44b421e6f3a11e5cc86eb1b9acd Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 00:53:05 -0600 Subject: [PATCH 09/30] ABrainz: Removed repeated portion of error message --- beetsplug/abrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/abrainz.py b/beetsplug/abrainz.py index 4ebc3d0d6..615b2a2b8 100644 --- a/beetsplug/abrainz.py +++ b/beetsplug/abrainz.py @@ -51,7 +51,7 @@ def fetch_info(self, lib): try: rs.json() except ValueError: - self._log.debug('abrainz: Invalid Response: {}', rs.text) + self._log.debug('Invalid Response: {}', rs.text) item.danceable = get_value(self._log, rs.json(), ["highlevel", "danceability", From bcd4c6d556d6686af6d570afe9cb492df1e5e0a8 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 01:12:52 -0600 Subject: [PATCH 10/30] ABrainz: Added initial documentation --- docs/plugins/abrainz.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docs/plugins/abrainz.rst diff --git a/docs/plugins/abrainz.rst b/docs/plugins/abrainz.rst new file mode 100644 index 000000000..c53a1921b --- /dev/null +++ b/docs/plugins/abrainz.rst @@ -0,0 +1,16 @@ +ABrainz Plugin +=========== + +The ``abrainz`` plugin provides a command that traverses through a library and tags tracks with valid MusicBrainz IDs with additional metadata such as +* ```danceable``` + + Predicts how easy the track is danceable to +* ```mood_happy``` + + Predicts the probability this track is played to invoke happiness +* ```mood_party``` + + Predicts the probability this track is played in a party environment + +Enable the ``abrainz`` plugin in your configuration (see :ref:`using-plugins`) and run with: + + $ beet abrainz + +Additional command-line options coming soon. \ No newline at end of file From 61830958e692ec9a7e0b4786a2cc3ca98d250f05 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 01:14:30 -0600 Subject: [PATCH 11/30] ABrainz: Fix slight bugs on Doc page --- docs/plugins/abrainz.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/plugins/abrainz.rst b/docs/plugins/abrainz.rst index c53a1921b..3739f9d24 100644 --- a/docs/plugins/abrainz.rst +++ b/docs/plugins/abrainz.rst @@ -2,11 +2,12 @@ ABrainz Plugin =========== The ``abrainz`` plugin provides a command that traverses through a library and tags tracks with valid MusicBrainz IDs with additional metadata such as -* ```danceable``` + +* ``danceable`` + Predicts how easy the track is danceable to -* ```mood_happy``` +* ``mood_happy`` + Predicts the probability this track is played to invoke happiness -* ```mood_party``` +* ``mood_party`` + Predicts the probability this track is played in a party environment Enable the ``abrainz`` plugin in your configuration (see :ref:`using-plugins`) and run with: From fdd1534cf35dd803de72660a4e569113c7a227a2 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 01:19:28 -0600 Subject: [PATCH 12/30] ABrainz: Doc title underline too short --- docs/plugins/abrainz.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/abrainz.rst b/docs/plugins/abrainz.rst index 3739f9d24..471d31fcb 100644 --- a/docs/plugins/abrainz.rst +++ b/docs/plugins/abrainz.rst @@ -1,5 +1,5 @@ ABrainz Plugin -=========== +============== The ``abrainz`` plugin provides a command that traverses through a library and tags tracks with valid MusicBrainz IDs with additional metadata such as From ff89716a4d6eb3fb0bc1a72c71fd097b09198273 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 01:23:44 -0600 Subject: [PATCH 13/30] ABrainz: Add abrainz doc to toctree --- docs/plugins/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 5da5ee0be..4b10275e8 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -31,6 +31,7 @@ Each plugin has its own set of options that can be defined in a section bearing .. toctree:: :hidden: + abrainz badfiles bpd bpm @@ -94,6 +95,7 @@ Autotagger Extensions Metadata -------- +* :doc:`abrainz`: Fetch various AcousticBrainz metadata * :doc:`bpm`: Measure tempo using keystrokes. * :doc:`echonest`: Automatically fetch `acoustic attributes`_ from `the Echo Nest`_ (tempo, energy, danceability, ...). From f685a59e716eb6b888b418ad61c1df59af7b2e2c Mon Sep 17 00:00:00 2001 From: "nath@laptop" Date: Thu, 31 Dec 2015 13:40:48 +0100 Subject: [PATCH 14/30] play: remove dead code This piece of code won't ever execute because interactive open terminates python execution --- beetsplug/play.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/beetsplug/play.py b/beetsplug/play.py index 1d425e1c8..e366919fb 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -131,11 +131,6 @@ class PlayPlugin(BeetsPlugin): except OSError as exc: raise ui.UserError("Could not play the music playlist: " "{0}".format(exc)) - finally: - if not raw: - self._log.debug('Removing temporary playlist: {}', - open_args[0]) - util.remove(open_args[0]) def _create_tmp_playlist(self, paths_list): """Create a temporary .m3u file. Return the filename. From 5201e1cde742935170040815d6cbda6237d03b4d Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 08:56:55 -0600 Subject: [PATCH 15/30] Rename --- beetsplug/{abrainz.py => acousticbrainz.py} | 0 docs/plugins/{abrainz.rst => acousticbrainz.rst} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename beetsplug/{abrainz.py => acousticbrainz.py} (100%) rename docs/plugins/{abrainz.rst => acousticbrainz.rst} (100%) diff --git a/beetsplug/abrainz.py b/beetsplug/acousticbrainz.py similarity index 100% rename from beetsplug/abrainz.py rename to beetsplug/acousticbrainz.py diff --git a/docs/plugins/abrainz.rst b/docs/plugins/acousticbrainz.rst similarity index 100% rename from docs/plugins/abrainz.rst rename to docs/plugins/acousticbrainz.rst From 6337b7ff084fdaf186b5eabc3cffa554b09a74d0 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 08:57:19 -0600 Subject: [PATCH 16/30] Rename commands and files --- beetsplug/acousticbrainz.py | 2 +- docs/plugins/acousticbrainz.rst | 8 ++++---- docs/plugins/index.rst | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index 615b2a2b8..bb33e0515 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -31,7 +31,7 @@ class AcousticPlugin(plugins.BeetsPlugin): super(AcousticPlugin, self).__init__() def commands(self): - cmd = ui.Subcommand('abrainz', + cmd = ui.Subcommand('acousticbrainz', help="fetch metadata from AcousticBrainz") def func(lib, opts, args): diff --git a/docs/plugins/acousticbrainz.rst b/docs/plugins/acousticbrainz.rst index 471d31fcb..f48104766 100644 --- a/docs/plugins/acousticbrainz.rst +++ b/docs/plugins/acousticbrainz.rst @@ -1,7 +1,7 @@ -ABrainz Plugin +Acousticbrainz Plugin ============== -The ``abrainz`` plugin provides a command that traverses through a library and tags tracks with valid MusicBrainz IDs with additional metadata such as +The ``acoustricbrainz`` plugin provides a command that traverses through a library and tags tracks with valid MusicBrainz IDs with additional metadata such as * ``danceable`` + Predicts how easy the track is danceable to @@ -10,8 +10,8 @@ The ``abrainz`` plugin provides a command that traverses through a library and t * ``mood_party`` + Predicts the probability this track is played in a party environment -Enable the ``abrainz`` plugin in your configuration (see :ref:`using-plugins`) and run with: +Enable the ``acousticbrainz`` plugin in your configuration (see :ref:`using-plugins`) and run with: $ beet abrainz -Additional command-line options coming soon. \ No newline at end of file +Additional command-line options coming soon. diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 4b10275e8..0c1c4ce11 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -31,7 +31,7 @@ Each plugin has its own set of options that can be defined in a section bearing .. toctree:: :hidden: - abrainz + acousticbrainz badfiles bpd bpm @@ -95,7 +95,7 @@ Autotagger Extensions Metadata -------- -* :doc:`abrainz`: Fetch various AcousticBrainz metadata +* :doc:`acousticbrainz`: Fetch various AcousticBrainz metadata * :doc:`bpm`: Measure tempo using keystrokes. * :doc:`echonest`: Automatically fetch `acoustic attributes`_ from `the Echo Nest`_ (tempo, energy, danceability, ...). From fa9039381cdb91a8ecb97c306eef3a406fa938b3 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 09:01:13 -0600 Subject: [PATCH 17/30] Update docs --- docs/plugins/acousticbrainz.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/acousticbrainz.rst b/docs/plugins/acousticbrainz.rst index f48104766..7019671a2 100644 --- a/docs/plugins/acousticbrainz.rst +++ b/docs/plugins/acousticbrainz.rst @@ -1,5 +1,5 @@ Acousticbrainz Plugin -============== +===================== The ``acoustricbrainz`` plugin provides a command that traverses through a library and tags tracks with valid MusicBrainz IDs with additional metadata such as @@ -12,6 +12,6 @@ The ``acoustricbrainz`` plugin provides a command that traverses through a libra Enable the ``acousticbrainz`` plugin in your configuration (see :ref:`using-plugins`) and run with: - $ beet abrainz + $ beet acousticbrainz Additional command-line options coming soon. From ad57943819078d36843c2e0037edae4c9ebe2138 Mon Sep 17 00:00:00 2001 From: Ohm Patel Date: Thu, 31 Dec 2015 09:35:01 -0600 Subject: [PATCH 18/30] Updated Copyright Year --- beetsplug/acousticbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index bb33e0515..fa1d71b5f 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # This file is part of beets. -# Copyright 2016, Ohm Patel. +# Copyright 2015-2016, Ohm Patel. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the From 6f1e003a9bba7d88e305c2f2208e1ba1ed7a9a99 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 31 Dec 2015 11:19:29 -0800 Subject: [PATCH 19/30] Changelog for #1784 --- docs/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index dbcd7e399..e9c2befe9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,10 @@ New: * :doc:`/plugins/fetchart`: The Google Images backend has been restored. It now requires an API key from Google. Thanks to :user:`lcharlick`. :bug:`1778` +* A new :doc:`/plugins/acousticbrainz` fetches acoustic-analysis information + from the `AcousticBrainz`_ project. Thanks to :user:`opatel99`. :bug:`1784` + +.. _AcousticBrainz: http://acousticbrainz.org/ 1.3.16 (December 28, 2015) From b29e386a1510200c185ad3e9a1912bfb2a337a38 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 31 Dec 2015 11:24:32 -0800 Subject: [PATCH 20/30] Refine AcousticBrainz docs (#1784) --- docs/plugins/acousticbrainz.rst | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/plugins/acousticbrainz.rst b/docs/plugins/acousticbrainz.rst index 7019671a2..71dc462d8 100644 --- a/docs/plugins/acousticbrainz.rst +++ b/docs/plugins/acousticbrainz.rst @@ -1,17 +1,22 @@ -Acousticbrainz Plugin +AcousticBrainz Plugin ===================== -The ``acoustricbrainz`` plugin provides a command that traverses through a library and tags tracks with valid MusicBrainz IDs with additional metadata such as +The ``acoustricbrainz`` plugin gets acoustic-analysis information from the +`AcousticBrainz`_ project. The spirit is similar to the +:doc:`/plugins/echonest`. -* ``danceable`` - + Predicts how easy the track is danceable to -* ``mood_happy`` - + Predicts the probability this track is played to invoke happiness -* ``mood_party`` - + Predicts the probability this track is played in a party environment +.. _AcousticBrainz: http://acousticbrainz.org/ -Enable the ``acousticbrainz`` plugin in your configuration (see :ref:`using-plugins`) and run with: +Enable the ``acousticbrainz`` plugin in your configuration (see :ref:`using-plugins`) and run it by typing:: $ beet acousticbrainz -Additional command-line options coming soon. +For all tracks with a MusicBrainz recording ID, the plugin currently sets +these fields: + +* ``danceable``: Predicts how easy the track is to dance to. +* ``mood_happy``: Predicts the probability this track will evoke happiness. +* ``mood_party``: Predicts the probability this track should be played at a + party. + +These three fields are all numbers between 0.0 and 1.0. From eb2b081dfd985e57c80bd98c59395197976658b9 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 31 Dec 2015 11:28:28 -0800 Subject: [PATCH 21/30] acousticbrainz: A bit more logging This way, the command shows you that it's making progress. --- beetsplug/acousticbrainz.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index fa1d71b5f..65a8f7d71 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -35,36 +35,40 @@ class AcousticPlugin(plugins.BeetsPlugin): help="fetch metadata from AcousticBrainz") def func(lib, opts, args): - fetch_info(self, lib) + fetch_info(self._log, lib) cmd.func = func return [cmd] -def fetch_info(self, lib): +def fetch_info(log, lib): """Currently outputs MBID and corresponding request status code """ for item in lib.items(): if item.mb_trackid: - rs = requests.get(generate_url(item.mb_trackid)) + log.info('getting data for: {}', item) + + url = generate_url(item.mb_trackid) + log.debug('fetching URL: {}', url) + rs = requests.get(url) try: rs.json() except ValueError: - self._log.debug('Invalid Response: {}', rs.text) + log.debug('Invalid Response: {}', rs.text) - item.danceable = get_value(self._log, rs.json(), ["highlevel", - "danceability", - "all", - "danceable"]) - item.mood_happy = get_value(self._log, rs.json(), ["highlevel", - "mood_happy", - "all", - "happy"]) - item.mood_party = get_value(self._log, rs.json(), ["highlevel", - "mood_party", - "all", - "party"]) + item.danceable = get_value(log, rs.json(), ["highlevel", + "danceability", + "all", + "danceable"]) + item.mood_happy = get_value(log, rs.json(), ["highlevel", + "mood_happy", + "all", + "happy"]) + item.mood_party = get_value(log, rs.json(), ["highlevel", + "mood_party", + "all", + "party"]) item.write() item.store() From 18e5b30d19dbd3a10e1dabf27a9f795b0be9fe09 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 31 Dec 2015 11:29:57 -0800 Subject: [PATCH 22/30] acousticbrainz: Handle requests errors --- beetsplug/acousticbrainz.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index 65a8f7d71..9c0c18260 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -48,10 +48,16 @@ def fetch_info(log, lib): if item.mb_trackid: log.info('getting data for: {}', item) + # Fetch the data from the AB API. url = generate_url(item.mb_trackid) log.debug('fetching URL: {}', url) - rs = requests.get(url) + try: + rs = requests.get(url) + except requests.RequestException as exc: + log.info('request error: {}', exc) + continue + # Parse the JSON response. try: rs.json() except ValueError: From 878161bb4f4099526321cd21bc8a8848438ffb60 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 31 Dec 2015 11:31:32 -0800 Subject: [PATCH 23/30] acousticbrainz: Avoid re-parsing the JSON --- beetsplug/acousticbrainz.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index 9c0c18260..acc08488b 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -59,22 +59,25 @@ def fetch_info(log, lib): # Parse the JSON response. try: - rs.json() + data = rs.json() except ValueError: log.debug('Invalid Response: {}', rs.text) - item.danceable = get_value(log, rs.json(), ["highlevel", - "danceability", - "all", - "danceable"]) - item.mood_happy = get_value(log, rs.json(), ["highlevel", - "mood_happy", - "all", - "happy"]) - item.mood_party = get_value(log, rs.json(), ["highlevel", - "mood_party", - "all", - "party"]) + item.danceable = get_value( + log, + data, + ["highlevel", "danceability", "all", "danceable"], + ) + item.mood_happy = get_value( + log, + data, + ["highlevel", "mood_happy", "all", "happy"], + ) + item.mood_party = get_value( + log, + data, + ["highlevel", "mood_party", "all", "party"], + ) item.write() item.store() From 5d89b7da67ba08e55462d2a351368d0d39d3cde0 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 31 Dec 2015 11:33:58 -0800 Subject: [PATCH 24/30] acousticbrainz: Allow filtering on a query Like other, similar commands. --- beetsplug/acousticbrainz.py | 10 ++++++---- docs/plugins/acousticbrainz.rst | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index acc08488b..badb5728d 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -35,16 +35,17 @@ class AcousticPlugin(plugins.BeetsPlugin): help="fetch metadata from AcousticBrainz") def func(lib, opts, args): - fetch_info(self._log, lib) + items = lib.items(ui.decargs(args)) + fetch_info(self._log, items, ui.should_write()) cmd.func = func return [cmd] -def fetch_info(log, lib): +def fetch_info(log, items, write): """Currently outputs MBID and corresponding request status code """ - for item in lib.items(): + for item in items: if item.mb_trackid: log.info('getting data for: {}', item) @@ -79,7 +80,8 @@ def fetch_info(log, lib): ["highlevel", "mood_party", "all", "party"], ) - item.write() + if write: + item.try_write() item.store() diff --git a/docs/plugins/acousticbrainz.rst b/docs/plugins/acousticbrainz.rst index 71dc462d8..8e15716a5 100644 --- a/docs/plugins/acousticbrainz.rst +++ b/docs/plugins/acousticbrainz.rst @@ -9,7 +9,7 @@ The ``acoustricbrainz`` plugin gets acoustic-analysis information from the Enable the ``acousticbrainz`` plugin in your configuration (see :ref:`using-plugins`) and run it by typing:: - $ beet acousticbrainz + $ beet acousticbrainz [QUERY] For all tracks with a MusicBrainz recording ID, the plugin currently sets these fields: From 3a3dc8b3ae90189e87fa56566b9a545f3c6340a7 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 31 Dec 2015 11:35:14 -0800 Subject: [PATCH 25/30] acousticbrainz: Don't write tags Since we aren't updating any on-disk tags anyway. --- beetsplug/acousticbrainz.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index badb5728d..e4557106b 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -36,13 +36,13 @@ class AcousticPlugin(plugins.BeetsPlugin): def func(lib, opts, args): items = lib.items(ui.decargs(args)) - fetch_info(self._log, items, ui.should_write()) + fetch_info(self._log, items) cmd.func = func return [cmd] -def fetch_info(log, items, write): +def fetch_info(log, items): """Currently outputs MBID and corresponding request status code """ for item in items: @@ -64,6 +64,7 @@ def fetch_info(log, items, write): except ValueError: log.debug('Invalid Response: {}', rs.text) + # Get each field and assign it on the item. item.danceable = get_value( log, data, @@ -80,8 +81,8 @@ def fetch_info(log, items, write): ["highlevel", "mood_party", "all", "party"], ) - if write: - item.try_write() + # Store the data. We only update flexible attributes, so we + # don't call `item.try_write()` here. item.store() From aad2cf1feb193acb7d5001efce692e47a95eb8f4 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 31 Dec 2015 11:38:05 -0800 Subject: [PATCH 26/30] acousticbrainz: Log an error for missing data --- beetsplug/acousticbrainz.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index e4557106b..934531a3c 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -58,6 +58,11 @@ def fetch_info(log, items): log.info('request error: {}', exc) continue + # Check for missing tracks. + if rs.status_code == 404: + log.info('recording ID {} not found', item.mb_trackid) + continue + # Parse the JSON response. try: data = rs.json() From 775ac4ed8368758c77e5cfebcec885cdda60f300 Mon Sep 17 00:00:00 2001 From: "nath@laptop" Date: Thu, 31 Dec 2015 21:49:03 +0100 Subject: [PATCH 27/30] play: Log exception from os call more accurately Depending on raw or not, we do not necessarily play a 'playlist'. Use 'query' generically instead. --- beetsplug/play.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/play.py b/beetsplug/play.py index e366919fb..c943a5155 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -129,7 +129,7 @@ class PlayPlugin(BeetsPlugin): try: util.interactive_open(open_args, command_str) except OSError as exc: - raise ui.UserError("Could not play the music playlist: " + raise ui.UserError("Could not play the query: " "{0}".format(exc)) def _create_tmp_playlist(self, paths_list): From e9cfae9369fd628a1ca0679785ce025ec6b87163 Mon Sep 17 00:00:00 2001 From: "nath@laptop" Date: Fri, 1 Jan 2016 12:17:12 +0100 Subject: [PATCH 28/30] play: Document #1785 --- docs/changelog.rst | 10 ++++++++++ docs/plugins/play.rst | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index e9c2befe9..50699cadd 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,6 +14,16 @@ New: .. _AcousticBrainz: http://acousticbrainz.org/ +Fixes: + +* :doc:`/plugins/play`: Remove dead code. From this point on, beets isn't + supposed and won't try to delete the playlists generated by ``beet play`` + (Note that although it was supposed to, beet didn't actually remove the + generated ``.m3u`` files beforehand either.). If this is an issue for you, you + might want to take a look at the ``raw`` config option of the + :doc:`/plugins/play`. :bug:`1785`, :bug:`1600` + + 1.3.16 (December 28, 2015) -------------------------- diff --git a/docs/plugins/play.rst b/docs/plugins/play.rst index 9a7210b4e..5092ce846 100644 --- a/docs/plugins/play.rst +++ b/docs/plugins/play.rst @@ -82,3 +82,17 @@ example:: indicates that you need to insert extra arguments before specifying the playlist. + +Note on the Leakage of the Generated Playlists +_______________________________________________ + +Because the command that will open the generated ``.m3u`` files can be +arbitrarily configured by the user, beets won't try to delete those files. For +this reason, using this plugin will leave one or several playlist(s) in the +directory selected to create temporary files (Most likely ``/tmp/`` on Unix-like +systems. See `tempfile.tempdir`_.). Leaking those playlists until they are +externally wiped could be an issue for privacy or storage reasons. If this is +the case for you, you might want to use the ``raw`` config option described +above. + +.. _tempfile.tempdir: https://docs.python.org/2/library/tempfile.html#tempfile.tempdir From 50789e88fe6533e43e9d4593b3f91c3ac5b43522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20Thomas?= Date: Fri, 1 Jan 2016 17:19:10 +0100 Subject: [PATCH 29/30] Fix 1583: Move command only shows modified files --- beets/ui/commands.py | 7 ++++++- docs/changelog.rst | 2 ++ docs/reference/cli.rst | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 39501f361..4b756b5c8 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1427,11 +1427,16 @@ def move_items(lib, dest, query, copy, album, pretend): """ items, albums = _do_query(lib, query, album, False) objs = albums if album else items + isitemmoved = lambda item: item.path != item.destination(basedir=dest) + isalbummoved = lambda album: any(isitemmoved(i) for i in album.items()) + objs = [o for o in objs if (isalbummoved if album else isitemmoved)(o)] action = 'Copying' if copy else 'Moving' entity = 'album' if album else 'item' log.info(u'{0} {1} {2}{3}.', action, len(objs), entity, - 's' if len(objs) > 1 else '') + 's' if len(objs) != 1 else '') + if len(objs) == 0: + return if pretend: if album: show_path_changes([(item.path, item.destination(basedir=dest)) diff --git a/docs/changelog.rst b/docs/changelog.rst index 50699cadd..bc15bef29 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,8 @@ Fixes: generated ``.m3u`` files beforehand either.). If this is an issue for you, you might want to take a look at the ``raw`` config option of the :doc:`/plugins/play`. :bug:`1785`, :bug:`1600` +* The :ref:`move-cmd` command does not display files whose path does not change + anymore. :bug:`1583` diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index f39dad393..5eb440316 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -256,7 +256,7 @@ anywhere in your filesystem. The ``-c`` option copies files instead of moving them. As with other commands, the ``-a`` option matches albums instead of items. To perform a "dry run", just use the ``-p`` (for "pretend") flag. This will -show you all how the files would be moved but won't actually change anything +show you a list of files that would be moved but won't actually change anything on disk. .. _update-cmd: From f582e045e9a5ff987e1265082a3abd3dad86358e Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Fri, 1 Jan 2016 13:05:07 -0800 Subject: [PATCH 30/30] Add a comment for #1584 --- beets/ui/commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 4b756b5c8..25ed0425d 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1427,6 +1427,8 @@ def move_items(lib, dest, query, copy, album, pretend): """ items, albums = _do_query(lib, query, album, False) objs = albums if album else items + + # Filter out files that don't need to be moved. isitemmoved = lambda item: item.path != item.destination(basedir=dest) isalbummoved = lambda album: any(isitemmoved(i) for i in album.items()) objs = [o for o in objs if (isalbummoved if album else isitemmoved)(o)] @@ -1435,8 +1437,9 @@ def move_items(lib, dest, query, copy, album, pretend): entity = 'album' if album else 'item' log.info(u'{0} {1} {2}{3}.', action, len(objs), entity, 's' if len(objs) != 1 else '') - if len(objs) == 0: + if not objs: return + if pretend: if album: show_path_changes([(item.path, item.destination(basedir=dest))