From a80a07f093c3a5f77446bd1b6e34bbc4d6d581e3 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Tue, 10 Jan 2017 14:42:15 +0000 Subject: [PATCH 01/14] playlist: Add playlist plugin Adds M3U playlist support as a query to beets and thus partially resolves issue #123. The implementation is heavily based on #2380 by Robin McCorkell. It supports referencing playlists by absolute path: $ beet ls playlist:/path/to/someplaylist.m3u It also supports referencing playlists by name. The playlist is then seached in the playlist_dir and the ".m3u" extension is appended to the name: $ beet ls playlist:anotherplaylist The configuration for the plugin looks like this: playlist: relative_to: library playlist_dir: /path/to/playlists The relative_to option specifies how relative paths in playlists are handled. By default, paths are relative to the "library" directory. It also possible to make them relative to the "playlist" or set the option or set it to a fixed path. --- beetsplug/playlist.py | 72 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 beetsplug/playlist.py diff --git a/beetsplug/playlist.py b/beetsplug/playlist.py new file mode 100644 index 000000000..624791ee4 --- /dev/null +++ b/beetsplug/playlist.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# 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. + +import os +import beets + + +class PlaylistQuery(beets.dbcore.FieldQuery): + """Matches files listed by a playlist file. + """ + def __init__(self, field, pattern, fast=False): + super(PlaylistQuery, self).__init__(field, pattern, fast) + config = beets.config['playlist'] + + # Get the full path to the playlist + if os.path.isabs(beets.util.syspath(pattern)): + playlist_path = pattern + else: + playlist_path = os.path.abspath(os.path.join( + config['playlist_dir'].as_filename(), + '{0}.m3u'.format(pattern), + )) + + if config['relative_to'].get() == 'library': + relative_to = beets.config['directory'].as_filename() + elif config['relative_to'].get() == 'playlist': + relative_to = os.path.dirname(playlist_path) + else: + relative_to = config['relative_to'].as_filename() + relative_to = beets.util.bytestring_path(relative_to) + + self.paths = [] + with open(beets.util.syspath(playlist_path), 'rb') as f: + for line in f: + if line[0] == '#': + # ignore comments, and extm3u extension + continue + + self.paths.append(beets.util.normpath( + os.path.join(relative_to, line.rstrip()) + )) + + def match(self, item): + return item.path in self.paths + + +class PlaylistType(beets.dbcore.types.String): + """Custom type for playlist query. + """ + query = PlaylistQuery + + +class PlaylistPlugin(beets.plugins.BeetsPlugin): + item_types = {'playlist': PlaylistType()} + + def __init__(self): + super(PlaylistPlugin, self).__init__() + self.config.add({ + 'playlist_dir': '.', + 'relative_to': 'library', + }) From 19b92e1199439017e05bf79408a863a91c34972a Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 15 Feb 2019 18:44:58 +0100 Subject: [PATCH 02/14] playlist: Improve speed in PlaylistQuery class Implement the col_clause method for faster, sqlite-based querying. This will only make a difference if the "fast" kwarg is set to True. --- beetsplug/playlist.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/beetsplug/playlist.py b/beetsplug/playlist.py index 624791ee4..654e3e1d0 100644 --- a/beetsplug/playlist.py +++ b/beetsplug/playlist.py @@ -19,7 +19,7 @@ import beets class PlaylistQuery(beets.dbcore.FieldQuery): """Matches files listed by a playlist file. """ - def __init__(self, field, pattern, fast=False): + def __init__(self, field, pattern, fast=True): super(PlaylistQuery, self).__init__(field, pattern, fast) config = beets.config['playlist'] @@ -51,6 +51,14 @@ class PlaylistQuery(beets.dbcore.FieldQuery): os.path.join(relative_to, line.rstrip()) )) + def col_clause(self): + if not self.paths: + # Playlist is empty + return '0', () + clause = 'BYTELOWER(path) IN ({0})'.format( + ', '.join('BYTELOWER(?)' for path in self.paths)) + return clause, (beets.library.BLOB_TYPE(p) for p in self.paths) + def match(self, item): return item.path in self.paths From cc501be2d9c9442eeaffb7e9681b11b58671abc7 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 15 Feb 2019 23:06:36 +0100 Subject: [PATCH 03/14] docs: Add documentation for the playlist plugin --- docs/plugins/index.rst | 2 ++ docs/plugins/playlist.rst | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 docs/plugins/playlist.rst diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 6bf50e227..e51354dac 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -81,6 +81,7 @@ like this:: mpdupdate permissions play + playlist plexupdate random replaygain @@ -158,6 +159,7 @@ Interoperability * :doc:`mpdupdate`: Automatically notifies `MPD`_ whenever the beets library changes. * :doc:`play`: Play beets queries in your music player. +* :doc:`playlist`: Use M3U playlists tp query the beets library. * :doc:`plexupdate`: Automatically notifies `Plex`_ whenever the beets library changes. * :doc:`smartplaylist`: Generate smart playlists based on beets queries. diff --git a/docs/plugins/playlist.rst b/docs/plugins/playlist.rst new file mode 100644 index 000000000..0a4e797c3 --- /dev/null +++ b/docs/plugins/playlist.rst @@ -0,0 +1,37 @@ +Smart Playlist Plugin +===================== + +``playlist`` is a plugin to use playlists in m3u format. + +To use it, enable the ``playlist`` plugin in your configuration +(see :ref:`using-plugins`). +Then configure your playlists like this:: + + playlist: + relative_to: ~/Music + playlist_dir: ~/.mpd/playlists + +It is possible to query the library based on a playlist by speicifying its +absolute path:: + + $ beet ls playlist:/path/to/someplaylist.m3u + +The plugin also supports referencing playlists by name. The playlist is then +seached in the playlist_dir and the ".m3u" extension is appended to the +name:: + + $ beet ls playlist:anotherplaylist + +Configuration +------------- + +To configure the plugin, make a ``smartplaylist:`` section in your +configuration file. In addition to the ``playlists`` described above, the +other configuration options are: + +- **playlist_dir**: Where to read playlist files from. + Default: The current working directory (i.e., ``'.'``). +- **relative_to**: Interpret paths in the playlist files relative to a base + directory. It is also possible to set it to ``playlist`` to use the + playlist's parent directory as base directory. + Default: ``library`` From d78bade30cfe39dcc0207330a31fa34195ad11b8 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 15 Feb 2019 23:07:19 +0100 Subject: [PATCH 04/14] docs: Add playlist plugin to the changelog --- docs/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2e9b751fe..a3b50af05 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,6 +14,10 @@ New features: issues with foobar2000 and Winamp. Thanks to :user:`mz2212`. :bug:`2944` +* :doc:`/plugins/playlist`: Add a plugin that can query the beets library using + M3U playlists. + Thanks to :user:`Holzhaus` and :user:`Xenopathic`. + :bug:`123` * Added whitespace padding to missing tracks dialog to improve readability. Thanks to :user:`jams2`. :bug:`2962` From 0988a2a18688a8b8e07d94e1609405c17bbe717d Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 15 Feb 2019 19:51:00 +0100 Subject: [PATCH 05/14] test: Add test suite for the playlist plugin --- test/test_playlist.py | 90 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 test/test_playlist.py diff --git a/test/test_playlist.py b/test/test_playlist.py new file mode 100644 index 000000000..176249134 --- /dev/null +++ b/test/test_playlist.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# This file is part of beets. +# Copyright 2016, Thomas Scholtes. +# +# 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. + +from __future__ import division, absolute_import, print_function + +import os +import tempfile +import unittest + +from test import _common +from test import helper + +import beets + + +class PlaylistTest(unittest.TestCase, helper.TestHelper): + def setUp(self): + self.setup_beets() + self.lib = beets.library.Library(':memory:') + + i1 = _common.item() + i1.path = beets.util.normpath('/a/b/c.mp3') + i1.title = u'some item' + i1.album = u'some album' + self.lib.add(i1) + self.lib.add_album([i1]) + + i2 = _common.item() + i2.path = beets.util.normpath('/d/e/f.mp3') + i2.title = 'another item' + i2.album = 'another album' + self.lib.add(i2) + self.lib.add_album([i2]) + + i3 = _common.item() + i3.path = beets.util.normpath('/x/y/z.mp3') + i3.title = 'yet another item' + i3.album = 'yet another album' + self.lib.add(i3) + self.lib.add_album([i3]) + + self.playlist_dir = tempfile.TemporaryDirectory() + with open(os.path.join(self.playlist_dir.name, 'test.m3u'), 'w') as f: + f.write('{0}\n'.format(beets.util.displayable_path(i1.path))) + f.write('{0}\n'.format(beets.util.displayable_path(i2.path))) + + self.config['directory'] = '/' + self.config['playlist']['relative_to'] = 'library' + self.config['playlist']['playlist_dir'] = self.playlist_dir.name + self.load_plugins('playlist') + + def tearDown(self): + self.unload_plugins() + self.playlist_dir.cleanup() + self.teardown_beets() + + def test_query_name(self): + q = u'playlist:test' + results = self.lib.items(q) + self.assertEqual(set([i.title for i in results]), set([ + u'some item', + u'another item', + ])) + + def test_query_path(self): + q = u'playlist:{0}/test.m3u'.format(self.playlist_dir.name) + results = self.lib.items(q) + self.assertEqual(set([i.title for i in results]), set([ + u'some item', + u'another item', + ])) + + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) + +if __name__ == '__main__': + unittest.main(defaultTest='suite') From f9f2fa0e266adb146b79542195c0761f59f0292f Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 17 Feb 2019 15:13:55 +0100 Subject: [PATCH 06/14] playlist: Restructure playlist reading code and add error handling --- beetsplug/playlist.py | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/beetsplug/playlist.py b/beetsplug/playlist.py index 654e3e1d0..a6ab8d18b 100644 --- a/beetsplug/playlist.py +++ b/beetsplug/playlist.py @@ -13,6 +13,7 @@ # included in all copies or substantial portions of the Software. import os +import fnmatch import beets @@ -24,24 +25,33 @@ class PlaylistQuery(beets.dbcore.FieldQuery): config = beets.config['playlist'] # Get the full path to the playlist - if os.path.isabs(beets.util.syspath(pattern)): - playlist_path = pattern - else: - playlist_path = os.path.abspath(os.path.join( + playlist_paths = ( + pattern, + os.path.abspath(os.path.join( config['playlist_dir'].as_filename(), '{0}.m3u'.format(pattern), - )) - - if config['relative_to'].get() == 'library': - relative_to = beets.config['directory'].as_filename() - elif config['relative_to'].get() == 'playlist': - relative_to = os.path.dirname(playlist_path) - else: - relative_to = config['relative_to'].as_filename() - relative_to = beets.util.bytestring_path(relative_to) + )), + ) self.paths = [] - with open(beets.util.syspath(playlist_path), 'rb') as f: + for playlist_path in playlist_paths: + if not fnmatch.fnmatch(playlist_path, '*.[mM]3[uU]'): + # This is not am M3U playlist, skip this candidate + continue + + try: + f = open(beets.util.syspath(playlist_path), mode='rb') + except OSError: + continue + + if config['relative_to'].get() == 'library': + relative_to = beets.config['directory'].as_filename() + elif config['relative_to'].get() == 'playlist': + relative_to = os.path.dirname(playlist_path) + else: + relative_to = config['relative_to'].as_filename() + relative_to = beets.util.bytestring_path(relative_to) + for line in f: if line[0] == '#': # ignore comments, and extm3u extension @@ -50,6 +60,8 @@ class PlaylistQuery(beets.dbcore.FieldQuery): self.paths.append(beets.util.normpath( os.path.join(relative_to, line.rstrip()) )) + f.close() + break def col_clause(self): if not self.paths: From d52dcdd48ff10422cf9734d2980156a2832c8d7f Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 17 Feb 2019 15:16:44 +0100 Subject: [PATCH 07/14] test: Add playlist testcases for nonexisting playlists --- test/test_playlist.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test_playlist.py b/test/test_playlist.py index 176249134..2c7c89805 100644 --- a/test/test_playlist.py +++ b/test/test_playlist.py @@ -82,6 +82,16 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): u'another item', ])) + def test_query_name_nonexisting(self): + q = u'playlist:nonexisting'.format(self.playlist_dir.name) + results = self.lib.items(q) + self.assertEqual(set(results), set()) + + def test_query_path_nonexisting(self): + q = u'playlist:{0}/nonexisting.m3u'.format(self.playlist_dir.name) + results = self.lib.items(q) + self.assertEqual(set(results), set()) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 34cdeeefb72b23dc00e291ecf1934030089417b6 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 17 Feb 2019 15:35:30 +0100 Subject: [PATCH 08/14] docs: Reword documentation of playlist plugin's relative_to option --- docs/plugins/playlist.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/plugins/playlist.rst b/docs/plugins/playlist.rst index 0a4e797c3..1156e7f77 100644 --- a/docs/plugins/playlist.rst +++ b/docs/plugins/playlist.rst @@ -32,6 +32,7 @@ other configuration options are: - **playlist_dir**: Where to read playlist files from. Default: The current working directory (i.e., ``'.'``). - **relative_to**: Interpret paths in the playlist files relative to a base - directory. It is also possible to set it to ``playlist`` to use the - playlist's parent directory as base directory. + directory. Instead of setting it to a fixed path, it is also possible to + set it to ``playlist`` to use the playlist's parent directory or to + ``library`` to use the library directory. Default: ``library`` From d4039be9c07b118b619d0353fe844b7ea867be01 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 17 Feb 2019 15:39:47 +0100 Subject: [PATCH 09/14] test: Get rid of TemporaryDirectory to restore Python 2.7 compatibility --- test/test_playlist.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/test_playlist.py b/test/test_playlist.py index 2c7c89805..abae5d969 100644 --- a/test/test_playlist.py +++ b/test/test_playlist.py @@ -16,6 +16,7 @@ from __future__ import division, absolute_import, print_function import os +import shutil import tempfile import unittest @@ -51,19 +52,19 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): self.lib.add(i3) self.lib.add_album([i3]) - self.playlist_dir = tempfile.TemporaryDirectory() - with open(os.path.join(self.playlist_dir.name, 'test.m3u'), 'w') as f: + self.playlist_dir = tempfile.mkdtemp() + with open(os.path.join(self.playlist_dir, 'test.m3u'), 'w') as f: f.write('{0}\n'.format(beets.util.displayable_path(i1.path))) f.write('{0}\n'.format(beets.util.displayable_path(i2.path))) self.config['directory'] = '/' self.config['playlist']['relative_to'] = 'library' - self.config['playlist']['playlist_dir'] = self.playlist_dir.name + self.config['playlist']['playlist_dir'] = self.playlist_dir self.load_plugins('playlist') def tearDown(self): self.unload_plugins() - self.playlist_dir.cleanup() + shutil.rmtree(self.playlist_dir) self.teardown_beets() def test_query_name(self): @@ -75,7 +76,7 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): ])) def test_query_path(self): - q = u'playlist:{0}/test.m3u'.format(self.playlist_dir.name) + q = u'playlist:{0}/test.m3u'.format(self.playlist_dir) results = self.lib.items(q) self.assertEqual(set([i.title for i in results]), set([ u'some item', @@ -83,12 +84,12 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): ])) def test_query_name_nonexisting(self): - q = u'playlist:nonexisting'.format(self.playlist_dir.name) + q = u'playlist:nonexisting'.format(self.playlist_dir) results = self.lib.items(q) self.assertEqual(set(results), set()) def test_query_path_nonexisting(self): - q = u'playlist:{0}/nonexisting.m3u'.format(self.playlist_dir.name) + q = u'playlist:{0}/nonexisting.m3u'.format(self.playlist_dir) results = self.lib.items(q) self.assertEqual(set(results), set()) From 32b6df046e242c437ddbebb71be1398b68c21293 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 17 Feb 2019 15:57:40 +0100 Subject: [PATCH 10/14] test: Don't use unix-only paths in playlist plugin testcase --- test/test_playlist.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/test/test_playlist.py b/test/test_playlist.py index abae5d969..f10076220 100644 --- a/test/test_playlist.py +++ b/test/test_playlist.py @@ -31,22 +31,33 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): self.setup_beets() self.lib = beets.library.Library(':memory:') + self.music_dir = os.path.expanduser('~/Music') + i1 = _common.item() - i1.path = beets.util.normpath('/a/b/c.mp3') + i1.path = beets.util.normpath(os.path.join( + self.music_dir, + 'a/b/c.mp3', + )) i1.title = u'some item' i1.album = u'some album' self.lib.add(i1) self.lib.add_album([i1]) i2 = _common.item() - i2.path = beets.util.normpath('/d/e/f.mp3') + i2.path = beets.util.normpath(os.path.join( + self.music_dir, + 'd/e/f.mp3', + )) i2.title = 'another item' i2.album = 'another album' self.lib.add(i2) self.lib.add_album([i2]) i3 = _common.item() - i3.path = beets.util.normpath('/x/y/z.mp3') + i3.path = beets.util.normpath(os.path.join( + self.music_dir, + 'x/y/z.mp3', + )) i3.title = 'yet another item' i3.album = 'yet another album' self.lib.add(i3) @@ -57,7 +68,7 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): f.write('{0}\n'.format(beets.util.displayable_path(i1.path))) f.write('{0}\n'.format(beets.util.displayable_path(i2.path))) - self.config['directory'] = '/' + self.config['directory'] = self.music_dir self.config['playlist']['relative_to'] = 'library' self.config['playlist']['playlist_dir'] = self.playlist_dir self.load_plugins('playlist') From 055f2d3702e40a62dff9ed9469af03e487b2d548 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 17 Feb 2019 16:00:04 +0100 Subject: [PATCH 11/14] playlist: Also catch IOErrors to restore Python 2.7 compatiblity --- beetsplug/playlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/playlist.py b/beetsplug/playlist.py index a6ab8d18b..759eaa51b 100644 --- a/beetsplug/playlist.py +++ b/beetsplug/playlist.py @@ -41,7 +41,7 @@ class PlaylistQuery(beets.dbcore.FieldQuery): try: f = open(beets.util.syspath(playlist_path), mode='rb') - except OSError: + except (OSError, IOError): continue if config['relative_to'].get() == 'library': From 31c687c853670ab5b58d51f372ae4b0bc2fbe74f Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 17 Feb 2019 16:17:47 +0100 Subject: [PATCH 12/14] test: Fix playlist plugin path handling for Windows compatibility --- test/test_playlist.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/test_playlist.py b/test/test_playlist.py index f10076220..62528dac1 100644 --- a/test/test_playlist.py +++ b/test/test_playlist.py @@ -87,7 +87,10 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): ])) def test_query_path(self): - q = u'playlist:{0}/test.m3u'.format(self.playlist_dir) + q = u'playlist:{0}'.format(os.path.join( + self.playlist_dir, + 'test.m3u', + )) results = self.lib.items(q) self.assertEqual(set([i.title for i in results]), set([ u'some item', @@ -100,7 +103,10 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): self.assertEqual(set(results), set()) def test_query_path_nonexisting(self): - q = u'playlist:{0}/nonexisting.m3u'.format(self.playlist_dir) + q = u'playlist:{0}'.format(os.path.join( + self.playlist_dir, + 'nonexisting.m3u', + )) results = self.lib.items(q) self.assertEqual(set(results), set()) From d6022e28d72fa1dc5521fbde34d22b307235ad8d Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 17 Feb 2019 16:43:36 +0100 Subject: [PATCH 13/14] test: Ensure path quoting in playlist tests --- test/test_playlist.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/test_playlist.py b/test/test_playlist.py index 62528dac1..529f3631c 100644 --- a/test/test_playlist.py +++ b/test/test_playlist.py @@ -14,6 +14,7 @@ # included in all copies or substantial portions of the Software. from __future__ import division, absolute_import, print_function +from six.moves import shlex_quote import os import shutil @@ -87,10 +88,10 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): ])) def test_query_path(self): - q = u'playlist:{0}'.format(os.path.join( + q = u'playlist:{0}'.format(shlex_quote(os.path.join( self.playlist_dir, 'test.m3u', - )) + ))) results = self.lib.items(q) self.assertEqual(set([i.title for i in results]), set([ u'some item', @@ -103,10 +104,11 @@ class PlaylistTest(unittest.TestCase, helper.TestHelper): self.assertEqual(set(results), set()) def test_query_path_nonexisting(self): - q = u'playlist:{0}'.format(os.path.join( + q = u'playlist:{0}'.format(shlex_quote(os.path.join( + self.playlist_dir, self.playlist_dir, 'nonexisting.m3u', - )) + ))) results = self.lib.items(q) self.assertEqual(set(results), set()) From 4f1a468aa944b37aadcef3c7164bf7f4576bb957 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 17 Feb 2019 17:34:36 +0100 Subject: [PATCH 14/14] playlist: Restore case sensitivity in col_clause method --- beetsplug/playlist.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/beetsplug/playlist.py b/beetsplug/playlist.py index 759eaa51b..e5c80f129 100644 --- a/beetsplug/playlist.py +++ b/beetsplug/playlist.py @@ -67,8 +67,7 @@ class PlaylistQuery(beets.dbcore.FieldQuery): if not self.paths: # Playlist is empty return '0', () - clause = 'BYTELOWER(path) IN ({0})'.format( - ', '.join('BYTELOWER(?)' for path in self.paths)) + clause = 'path IN ({0})'.format(', '.join('?' for path in self.paths)) return clause, (beets.library.BLOB_TYPE(p) for p in self.paths) def match(self, item):