diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 29b2a73e7..bb84aedc7 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -223,6 +223,13 @@ def sorted_walk(path, ignore=(), ignore_hidden=False, logger=None): yield res +def path_as_posix(path): + """Return the string representation of the path with forward (/) + slashes. + """ + return path.replace(b'\\', b'/') + + def mkdirall(path): """Make all the enclosing directories of path (like mkdir -p on the parent). diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index 01f3ac6ac..725e0d634 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -74,6 +74,9 @@ ABSCHEME = { 'sad': 'mood_sad' } }, + 'moods_mirex': { + 'value': 'moods_mirex' + }, 'ismir04_rhythm': { 'value': 'rhythm' }, @@ -82,6 +85,9 @@ ABSCHEME = { 'tonal': 'tonal' } }, + 'timbre': { + 'value': 'timbre' + }, 'voice_instrumental': { 'value': 'voice_instrumental' }, @@ -124,7 +130,9 @@ class AcousticPlugin(plugins.BeetsPlugin): 'mood_party': types.Float(6), 'mood_relaxed': types.Float(6), 'mood_sad': types.Float(6), + 'moods_mirex': types.STRING, 'rhythm': types.Float(6), + 'timbre': types.STRING, 'tonal': types.Float(6), 'voice_instrumental': types.STRING, } diff --git a/beetsplug/playlist.py b/beetsplug/playlist.py index 4ab02c6b7..302ddb56d 100644 --- a/beetsplug/playlist.py +++ b/beetsplug/playlist.py @@ -18,6 +18,7 @@ import os import fnmatch import tempfile import beets +from beets.util import path_as_posix class PlaylistQuery(beets.dbcore.Query): @@ -86,6 +87,7 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin): 'auto': False, 'playlist_dir': '.', 'relative_to': 'library', + 'forward_slash': False, }) self.playlist_dir = self.config['playlist_dir'].as_filename() @@ -160,6 +162,8 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin): try: new_path = self.changes[beets.util.normpath(lookup)] except KeyError: + if self.config['forward_slash']: + line = path_as_posix(line) tempfp.write(line) else: if new_path is None: @@ -170,8 +174,10 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin): changes += 1 if is_relative: new_path = os.path.relpath(new_path, base_dir) - - tempfp.write(line.replace(original_path, new_path)) + line = line.replace(original_path, new_path) + if self.config['forward_slash']: + line = path_as_posix(line) + tempfp.write(line) if changes or deletions: self._log.info( diff --git a/beetsplug/smartplaylist.py b/beetsplug/smartplaylist.py index a83fc4d19..4ffdd21e7 100644 --- a/beetsplug/smartplaylist.py +++ b/beetsplug/smartplaylist.py @@ -21,7 +21,7 @@ from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets import ui from beets.util import (mkdirall, normpath, sanitize_path, syspath, - bytestring_path) + bytestring_path, path_as_posix) from beets.library import Item, Album, parse_query_string from beets.dbcore import OrQuery from beets.dbcore.query import MultipleSort, ParsingError @@ -37,7 +37,8 @@ class SmartPlaylistPlugin(BeetsPlugin): 'relative_to': None, 'playlist_dir': u'.', 'auto': True, - 'playlists': [] + 'playlists': [], + 'forward_slash': False, }) self._matched_playlists = None @@ -206,6 +207,8 @@ class SmartPlaylistPlugin(BeetsPlugin): mkdirall(m3u_path) with open(syspath(m3u_path), 'wb') as f: for path in m3us[m3u]: + if self.config['forward_slash'].get(): + path = path_as_posix(path) f.write(path + b'\n') self._log.info(u"{0} playlists updated", len(self._matched_playlists)) diff --git a/docs/changelog.rst b/docs/changelog.rst index a13f46794..b5a500fda 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -25,6 +25,13 @@ New features: * :doc:`/plugins/replaygain`: The new ``ffmpeg`` ReplayGain backend supports ``R128_`` tags, just like the ``bs1770gain`` backend. :bug:`3056` +* :doc:`plugins/replaygain`: ``r128_targetlevel`` is a new configuration option + for the ReplayGain plugin: It defines the reference volume for files using + ``R128_`` tags. ``targetlevel`` only configures the reference volume for + ``REPLAYGAIN_`` files. + This also deprecates the ``bs1770gain`` ReplayGain backend's ``method`` + option. Use ``targetlevel`` and ``r128_targetlevel`` instead. + :bug:`3065` * A new :doc:`/plugins/parentwork` gets information about the original work, which is useful for classical music. Thanks to :user:`dosoe`. @@ -42,13 +49,15 @@ New features: new ``discogs_albumid`` field from the Discogs API. Thanks to :user:`thedevilisinthedetails`. :bug:`465` :bug:`3322` -* :doc:`plugins/replaygain`: ``r128_targetlevel`` is a new configuration option - for the ReplayGain plugin: It defines the reference volume for files using - ``R128_`` tags. ``targetlevel`` only configures the reference volume for - ``REPLAYGAIN_`` files. - This also deprecates the ``bs1770gain`` ReplayGain backend's ``method`` - option. Use ``targetlevel`` and ``r128_targetlevel`` instead. - :bug:`3065` +* :doc:`/plugins/acousticbrainz`: The plugin now fetches two more additional + fields: ``moods_mirex`` and ``timbre``. + Thanks to :user:`malcops`. + :bug:`2860` +* :doc:`/plugins/playlist` and :doc:`/plugins/smartplaylist`: A new + ``forward_slash`` config option facilitates compatibility with MPD on + Windows. + Thanks to :user:`MartyLake`. + :bug:`3331` :bug:`3334` Fixes: diff --git a/docs/plugins/acousticbrainz.rst b/docs/plugins/acousticbrainz.rst index 7c24ffe0d..7d7aed237 100644 --- a/docs/plugins/acousticbrainz.rst +++ b/docs/plugins/acousticbrainz.rst @@ -38,7 +38,9 @@ these fields: * ``mood_party`` * ``mood_relaxed`` * ``mood_sad`` +* ``moods_mirex`` * ``rhythm`` +* ``timbre`` * ``tonal`` * ``voice_instrumental`` diff --git a/docs/plugins/playlist.rst b/docs/plugins/playlist.rst index 3622581db..31609cb3c 100644 --- a/docs/plugins/playlist.rst +++ b/docs/plugins/playlist.rst @@ -11,6 +11,7 @@ Then configure your playlists like this:: auto: no relative_to: ~/Music playlist_dir: ~/.mpd/playlists + forward_slash: no It is possible to query the library based on a playlist by speicifying its absolute path:: @@ -45,3 +46,7 @@ other configuration options are: set it to ``playlist`` to use the playlist's parent directory or to ``library`` to use the library directory. Default: ``library`` +- **forward_slash**: Forces forward slashes in the generated playlist files. + If you intend to use this plugin to generate playlists for MPD on + Windows, set this to yes. + Default: Use system separator. diff --git a/docs/plugins/smartplaylist.rst b/docs/plugins/smartplaylist.rst index e68217657..dd3ee45ba 100644 --- a/docs/plugins/smartplaylist.rst +++ b/docs/plugins/smartplaylist.rst @@ -14,6 +14,7 @@ Then configure your smart playlists like the following example:: smartplaylist: relative_to: ~/Music playlist_dir: ~/.mpd/playlists + forward_slash: no playlists: - name: all.m3u query: '' @@ -96,3 +97,7 @@ other configuration options are: directory. If you intend to use this plugin to generate playlists for MPD, point this to your MPD music directory. Default: Use absolute paths. +- **forward_slash**: Forces forward slashes in the generated playlist files. + If you intend to use this plugin to generate playlists for MPD on + Windows, set this to yes. + Default: Use system separator. diff --git a/test/test_acousticbrainz.py b/test/test_acousticbrainz.py index 0b1407581..4c0b0137b 100644 --- a/test/test_acousticbrainz.py +++ b/test/test_acousticbrainz.py @@ -95,7 +95,9 @@ class MapDataToSchemeTest(unittest.TestCase): ('danceable', 0.143928021193), ('rhythm', 'VienneseWaltz'), ('mood_electronic', 0.339881360531), - ('mood_happy', 0.0894767045975) + ('mood_happy', 0.0894767045975), + ('moods_mirex', "Cluster3"), + ('timbre', "bright") } self.assertEqual(mapping, expected) diff --git a/test/test_files.py b/test/test_files.py index ff055ac6f..f31779672 100644 --- a/test/test_files.py +++ b/test/test_files.py @@ -195,6 +195,11 @@ class HelperTest(_common.TestCase): a = ['a', 'b', 'c'] self.assertEqual(util.components(p), a) + def test_forward_slash(self): + p = br'C:\a\b\c' + a = br'C:/a/b/c' + self.assertEqual(util.path_as_posix(p), a) + class AlbumFileTest(_common.TestCase): def setUp(self):