diff --git a/docs/changelog.rst b/docs/changelog.rst index 30c2f7c1..da6e6bd7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -126,6 +126,11 @@ v1.0.0 (UNRELEASED) - Sort local playlists by name. (Fixes: :issue:`1026`, PR: :issue:`1028`) +- Moved playlist support out to a new extension, :ref:`ext-m3u`. + +- *Deprecated:* The config value :confval:`local/playlists_dir` is no longer in + use and can be removed from your config. + **Local library API** - Implementors of :meth:`mopidy.local.Library.lookup` should now return a list @@ -139,6 +144,12 @@ v1.0.0 (UNRELEASED) - Add :meth:`mopidy.local.Library.get_images` for looking up images for local URIs. (Fixes: :issue:`1031`, PR: :issue:`1032` and :issue:`1037`) +**M3U backend** + +- Split the M3U playlist handling out of the local backend. See + :ref:`m3u-migration` for how to migrate your local playlists. (Fixes: + :issue:`1054`, PR: :issue:`1066`) + **MPD frontend** - In stored playlist names, replace "/", which are illegal, with "|" instead of diff --git a/docs/ext/m3u.rst b/docs/ext/m3u.rst new file mode 100644 index 00000000..d05f88f1 --- /dev/null +++ b/docs/ext/m3u.rst @@ -0,0 +1,55 @@ +.. _ext-m3u: + +********** +Mopidy-M3U +********** + +Mopidy-M3U is an extension for reading and writing M3U playlists stored +on disk. It is bundled with Mopidy and enabled by default. + +This backend handles URIs starting with ``m3u:``. + + +.. _m3u-migration: + +Migrating from Mopidy-Local playlists +===================================== + +Mopidy-M3U was split out of the Mopidy-Local extension in Mopidy 1.0. To +migrate your playlists from Mopidy-Local, simply move them from the +:confval:`local/playlists_dir` directory to the :confval:`m3u/playlists_dir` +directory. Assuming you have not changed the default config, run the following +commands to migrate:: + + mkdir -p ~/.local/share/mopidy/m3u/ + mv ~/.local/share/mopidy/local/playlists/* ~/.local/share/mopidy/m3u/ + + +Editing playlists +================= + +There is a core playlist API in place for editing playlists. This is supported +by a few Mopidy clients, but not through Mopidy's MPD server yet. + +It is possible to edit playlists by editing the M3U files located in the +:confval:`m3u/playlists_dir` directory, usually +:file:`~/.local/share/mopidy/m3u/`, by hand with a text editor. See `Wikipedia +`__ for a short description of the quite +simple M3U playlist format. + + +Configuration +============= + +See :ref:`config` for general help on configuring Mopidy. + +.. literalinclude:: ../../mopidy/m3u/ext.conf + :language: ini + +.. confval:: m3u/enabled + + If the M3U extension should be enabled or not. + +.. confval:: m3u/playlists_dir + + Path to directory with M3U files. diff --git a/docs/index.rst b/docs/index.rst index e91c491c..e9775030 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -94,6 +94,7 @@ Extensions :maxdepth: 2 ext/local + ext/m3u ext/stream ext/http ext/mpd diff --git a/mopidy/local/__init__.py b/mopidy/local/__init__.py index 542d99f3..dedb8632 100644 --- a/mopidy/local/__init__.py +++ b/mopidy/local/__init__.py @@ -24,7 +24,7 @@ class Extension(ext.Extension): schema['library'] = config.String() schema['media_dir'] = config.Path() schema['data_dir'] = config.Path() - schema['playlists_dir'] = config.Path() + schema['playlists_dir'] = config.Deprecated() schema['tag_cache_file'] = config.Deprecated() schema['scan_timeout'] = config.Integer( minimum=1000, maximum=1000 * 60 * 60) diff --git a/mopidy/local/actor.py b/mopidy/local/actor.py index f315607a..435d19a5 100644 --- a/mopidy/local/actor.py +++ b/mopidy/local/actor.py @@ -8,7 +8,6 @@ from mopidy import backend from mopidy.local import storage from mopidy.local.library import LocalLibraryProvider from mopidy.local.playback import LocalPlaybackProvider -from mopidy.local.playlists import LocalPlaylistsProvider logger = logging.getLogger(__name__) @@ -36,5 +35,4 @@ class LocalBackend(pykka.ThreadingActor, backend.Backend): logger.warning('Local library %s not found', library_name) self.playback = LocalPlaybackProvider(audio=audio, backend=self) - self.playlists = LocalPlaylistsProvider(backend=self) self.library = LocalLibraryProvider(backend=self, library=library) diff --git a/mopidy/local/ext.conf b/mopidy/local/ext.conf index 535f4806..ebd7962f 100644 --- a/mopidy/local/ext.conf +++ b/mopidy/local/ext.conf @@ -3,7 +3,6 @@ enabled = true library = json media_dir = $XDG_MUSIC_DIR data_dir = $XDG_DATA_DIR/mopidy/local -playlists_dir = $XDG_DATA_DIR/mopidy/local/playlists scan_timeout = 1000 scan_flush_threshold = 1000 scan_follow_symlinks = false diff --git a/mopidy/local/storage.py b/mopidy/local/storage.py index 9cdcd12e..21d278e5 100644 --- a/mopidy/local/storage.py +++ b/mopidy/local/storage.py @@ -20,11 +20,3 @@ def check_dirs_and_files(config): logger.warning( 'Could not create local data dir: %s', encoding.locale_decode(error)) - - # TODO: replace with data dir? - try: - path.get_or_create_dir(config['local']['playlists_dir']) - except EnvironmentError as error: - logger.warning( - 'Could not create local playlists dir: %s', - encoding.locale_decode(error)) diff --git a/mopidy/local/translator.py b/mopidy/local/translator.py index 6800c478..92b20a7b 100644 --- a/mopidy/local/translator.py +++ b/mopidy/local/translator.py @@ -2,18 +2,12 @@ from __future__ import absolute_import, unicode_literals import logging import os -import re import urllib -import urlparse from mopidy import compat -from mopidy.models import Track -from mopidy.utils.encoding import locale_decode from mopidy.utils.path import path_to_uri, uri_to_path -M3U_EXTINF_RE = re.compile(r'#EXTINF:(-1|\d+),(.*)') - logger = logging.getLogger(__name__) @@ -28,13 +22,6 @@ def local_track_uri_to_path(uri, media_dir): return os.path.join(media_dir, file_path) -def local_playlist_uri_to_path(uri, playlists_dir): - if not uri.startswith('local:playlist:'): - raise ValueError('Invalid URI %s' % uri) - file_path = uri_to_path(uri).split(b':', 1)[1] - return os.path.join(playlists_dir, file_path) - - def path_to_local_track_uri(relpath): """Convert path relative to media_dir to local track URI.""" if isinstance(relpath, compat.text_type): @@ -47,89 +34,3 @@ def path_to_local_directory_uri(relpath): if isinstance(relpath, compat.text_type): relpath = relpath.encode('utf-8') return b'local:directory:%s' % urllib.quote(relpath) - - -def path_to_local_playlist_uri(relpath): - """Convert path relative to playlists_dir to local playlist URI.""" - if isinstance(relpath, compat.text_type): - relpath = relpath.encode('utf-8') - return b'local:playlist:%s' % urllib.quote(relpath) - - -def m3u_extinf_to_track(line): - """Convert extended M3U directive to track template.""" - m = M3U_EXTINF_RE.match(line) - if not m: - logger.warning('Invalid extended M3U directive: %s', line) - return Track() - (runtime, title) = m.groups() - if int(runtime) > 0: - return Track(name=title, length=1000 * int(runtime)) - else: - return Track(name=title) - - -def parse_m3u(file_path, media_dir): - r""" - Convert M3U file list to list of tracks - - Example M3U data:: - - # This is a comment - Alternative\Band - Song.mp3 - Classical\Other Band - New Song.mp3 - Stuff.mp3 - D:\More Music\Foo.mp3 - http://www.example.com:8000/Listen.pls - http://www.example.com/~user/Mine.mp3 - - Example extended M3U data:: - - #EXTM3U - #EXTINF:123, Sample artist - Sample title - Sample.mp3 - #EXTINF:321,Example Artist - Example title - Greatest Hits\Example.ogg - #EXTINF:-1,Radio XMP - http://mp3stream.example.com:8000/ - - - Relative paths of songs should be with respect to location of M3U. - - Paths are normally platform specific. - - Lines starting with # are ignored, except for extended M3U directives. - - Track.name and Track.length are set from extended M3U directives. - - m3u files are latin-1. - """ - # TODO: uris as bytes - tracks = [] - try: - with open(file_path) as m3u: - contents = m3u.readlines() - except IOError as error: - logger.warning('Couldn\'t open m3u: %s', locale_decode(error)) - return tracks - - if not contents: - return tracks - - extended = contents[0].decode('latin1').startswith('#EXTM3U') - - track = Track() - for line in contents: - line = line.strip().decode('latin1') - - if line.startswith('#'): - if extended and line.startswith('#EXTINF'): - track = m3u_extinf_to_track(line) - continue - - if urlparse.urlsplit(line).scheme: - tracks.append(track.copy(uri=line)) - elif os.path.normpath(line) == os.path.abspath(line): - path = path_to_uri(line) - tracks.append(track.copy(uri=path)) - else: - path = path_to_uri(os.path.join(media_dir, line)) - tracks.append(track.copy(uri=path)) - - track = Track() - return tracks diff --git a/mopidy/m3u/__init__.py b/mopidy/m3u/__init__.py new file mode 100644 index 00000000..e0fcf305 --- /dev/null +++ b/mopidy/m3u/__init__.py @@ -0,0 +1,30 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import os + +import mopidy +from mopidy import config, ext + +logger = logging.getLogger(__name__) + + +class Extension(ext.Extension): + + dist_name = 'Mopidy-M3U' + ext_name = 'm3u' + version = mopidy.__version__ + + def get_default_config(self): + conf_file = os.path.join(os.path.dirname(__file__), 'ext.conf') + return config.read(conf_file) + + def get_config_schema(self): + schema = super(Extension, self).get_config_schema() + schema['playlists_dir'] = config.Path() + return schema + + def setup(self, registry): + from .actor import M3UBackend + + registry.add('backend', M3UBackend) diff --git a/mopidy/m3u/actor.py b/mopidy/m3u/actor.py new file mode 100644 index 00000000..3908d938 --- /dev/null +++ b/mopidy/m3u/actor.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import, unicode_literals + +import logging + +import pykka + +from mopidy import backend +from mopidy.m3u.library import M3ULibraryProvider +from mopidy.m3u.playlists import M3UPlaylistsProvider +from mopidy.utils import encoding, path + + +logger = logging.getLogger(__name__) + + +class M3UBackend(pykka.ThreadingActor, backend.Backend): + uri_schemes = ['m3u'] + + def __init__(self, config, audio): + super(M3UBackend, self).__init__() + + self._config = config + + try: + path.get_or_create_dir(config['m3u']['playlists_dir']) + except EnvironmentError as error: + logger.warning( + 'Could not create M3U playlists dir: %s', + encoding.locale_decode(error)) + + self.playlists = M3UPlaylistsProvider(backend=self) + self.library = M3ULibraryProvider(backend=self) diff --git a/mopidy/m3u/ext.conf b/mopidy/m3u/ext.conf new file mode 100644 index 00000000..0e828b1b --- /dev/null +++ b/mopidy/m3u/ext.conf @@ -0,0 +1,3 @@ +[m3u] +enabled = true +playlists_dir = $XDG_DATA_DIR/mopidy/m3u diff --git a/mopidy/m3u/library.py b/mopidy/m3u/library.py new file mode 100644 index 00000000..3b5bded1 --- /dev/null +++ b/mopidy/m3u/library.py @@ -0,0 +1,18 @@ +from __future__ import absolute_import, unicode_literals + +import logging + +from mopidy import backend + +logger = logging.getLogger(__name__) + + +class M3ULibraryProvider(backend.LibraryProvider): + """Library for looking up M3U playlists.""" + + def __init__(self, backend): + super(M3ULibraryProvider, self).__init__(backend) + + def lookup(self, uri): + # TODO Lookup tracks in M3U playlist + return [] diff --git a/mopidy/local/playlists.py b/mopidy/m3u/playlists.py similarity index 83% rename from mopidy/local/playlists.py rename to mopidy/m3u/playlists.py index f2b712c5..2dc11628 100644 --- a/mopidy/local/playlists.py +++ b/mopidy/m3u/playlists.py @@ -8,19 +8,18 @@ import os import sys from mopidy import backend +from mopidy.m3u import translator from mopidy.models import Playlist -from .translator import local_playlist_uri_to_path, path_to_local_playlist_uri -from .translator import parse_m3u logger = logging.getLogger(__name__) -class LocalPlaylistsProvider(backend.PlaylistsProvider): +class M3UPlaylistsProvider(backend.PlaylistsProvider): def __init__(self, *args, **kwargs): - super(LocalPlaylistsProvider, self).__init__(*args, **kwargs) - self._media_dir = self.backend.config['local']['media_dir'] - self._playlists_dir = self.backend.config['local']['playlists_dir'] + super(M3UPlaylistsProvider, self).__init__(*args, **kwargs) + + self._playlists_dir = self.backend._config['m3u']['playlists_dir'] self._playlists = [] self.refresh() @@ -49,7 +48,7 @@ class LocalPlaylistsProvider(backend.PlaylistsProvider): if not playlist: logger.warn('Trying to delete unknown playlist %s', uri) return - path = local_playlist_uri_to_path(uri, self._playlists_dir) + path = translator.playlist_uri_to_path(uri, self._playlists_dir) if os.path.exists(path): os.remove(path) else: @@ -70,10 +69,10 @@ class LocalPlaylistsProvider(backend.PlaylistsProvider): for path in glob.glob(os.path.join(self._playlists_dir, b'*.m3u')): relpath = os.path.basename(path) name = os.path.splitext(relpath)[0].decode(encoding) - uri = path_to_local_playlist_uri(relpath) + uri = translator.path_to_playlist_uri(relpath) tracks = [] - for track in parse_m3u(path, self._media_dir): + for track in translator.parse_m3u(path): tracks.append(track) playlist = Playlist(uri=uri, name=name, tracks=tracks) @@ -82,7 +81,7 @@ class LocalPlaylistsProvider(backend.PlaylistsProvider): self.playlists = sorted(playlists, key=operator.attrgetter('name')) logger.info( - 'Loaded %d local playlists from %s', + 'Loaded %d M3U playlists from %s', len(playlists), self._playlists_dir) def save(self, playlist): @@ -99,7 +98,7 @@ class LocalPlaylistsProvider(backend.PlaylistsProvider): playlist = self._save_m3u(playlist) if index >= 0 and uri != playlist.uri: - path = local_playlist_uri_to_path(uri, self._playlists_dir) + path = translator.playlist_uri_to_path(uri, self._playlists_dir) if os.path.exists(path): os.remove(path) else: @@ -125,11 +124,12 @@ class LocalPlaylistsProvider(backend.PlaylistsProvider): def _save_m3u(self, playlist, encoding=sys.getfilesystemencoding()): if playlist.name: name = self._sanitize_m3u_name(playlist.name, encoding) - uri = path_to_local_playlist_uri(name.encode(encoding) + b'.m3u') - path = local_playlist_uri_to_path(uri, self._playlists_dir) + uri = translator.path_to_playlist_uri( + name.encode(encoding) + b'.m3u') + path = translator.playlist_uri_to_path(uri, self._playlists_dir) elif playlist.uri: uri = playlist.uri - path = local_playlist_uri_to_path(uri, self._playlists_dir) + path = translator.playlist_uri_to_path(uri, self._playlists_dir) name, _ = os.path.splitext(os.path.basename(path).decode(encoding)) else: raise ValueError('M3U playlist needs name or URI') diff --git a/mopidy/m3u/translator.py b/mopidy/m3u/translator.py new file mode 100644 index 00000000..4eefce9d --- /dev/null +++ b/mopidy/m3u/translator.py @@ -0,0 +1,110 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import os +import re +import urllib +import urlparse + +from mopidy import compat +from mopidy.models import Track +from mopidy.utils.encoding import locale_decode +from mopidy.utils.path import path_to_uri, uri_to_path + + +M3U_EXTINF_RE = re.compile(r'#EXTINF:(-1|\d+),(.*)') + +logger = logging.getLogger(__name__) + + +def playlist_uri_to_path(uri, playlists_dir): + if not uri.startswith('m3u:'): + raise ValueError('Invalid URI %s' % uri) + file_path = uri_to_path(uri) + return os.path.join(playlists_dir, file_path) + + +def path_to_playlist_uri(relpath): + """Convert path relative to playlists_dir to M3U URI.""" + if isinstance(relpath, compat.text_type): + relpath = relpath.encode('utf-8') + return b'm3u:%s' % urllib.quote(relpath) + + +def m3u_extinf_to_track(line): + """Convert extended M3U directive to track template.""" + m = M3U_EXTINF_RE.match(line) + if not m: + logger.warning('Invalid extended M3U directive: %s', line) + return Track() + (runtime, title) = m.groups() + if int(runtime) > 0: + return Track(name=title, length=1000 * int(runtime)) + else: + return Track(name=title) + + +def parse_m3u(file_path, media_dir=None): + r""" + Convert M3U file list to list of tracks + + Example M3U data:: + + # This is a comment + Alternative\Band - Song.mp3 + Classical\Other Band - New Song.mp3 + Stuff.mp3 + D:\More Music\Foo.mp3 + http://www.example.com:8000/Listen.pls + http://www.example.com/~user/Mine.mp3 + + Example extended M3U data:: + + #EXTM3U + #EXTINF:123, Sample artist - Sample title + Sample.mp3 + #EXTINF:321,Example Artist - Example title + Greatest Hits\Example.ogg + #EXTINF:-1,Radio XMP + http://mp3stream.example.com:8000/ + + - Relative paths of songs should be with respect to location of M3U. + - Paths are normally platform specific. + - Lines starting with # are ignored, except for extended M3U directives. + - Track.name and Track.length are set from extended M3U directives. + - m3u files are latin-1. + """ + # TODO: uris as bytes + tracks = [] + try: + with open(file_path) as m3u: + contents = m3u.readlines() + except IOError as error: + logger.warning('Couldn\'t open m3u: %s', locale_decode(error)) + return tracks + + if not contents: + return tracks + + extended = contents[0].decode('latin1').startswith('#EXTM3U') + + track = Track() + for line in contents: + line = line.strip().decode('latin1') + + if line.startswith('#'): + if extended and line.startswith('#EXTINF'): + track = m3u_extinf_to_track(line) + continue + + if urlparse.urlsplit(line).scheme: + tracks.append(track.copy(uri=line)) + elif os.path.normpath(line) == os.path.abspath(line): + path = path_to_uri(line) + tracks.append(track.copy(uri=path)) + elif media_dir is not None: + path = path_to_uri(os.path.join(media_dir, line)) + tracks.append(track.copy(uri=path)) + + track = Track() + return tracks diff --git a/setup.py b/setup.py index 49940c15..a6f3050e 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ setup( 'mopidy.ext': [ 'http = mopidy.http:Extension', 'local = mopidy.local:Extension', + 'm3u = mopidy.m3u:Extension', 'mpd = mopidy.mpd:Extension', 'softwaremixer = mopidy.softwaremixer:Extension', 'stream = mopidy.stream:Extension', diff --git a/tests/m3u/__init__.py b/tests/m3u/__init__.py new file mode 100644 index 00000000..702deac5 --- /dev/null +++ b/tests/m3u/__init__.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import, unicode_literals + + +def generate_song(i): + return 'dummy:track:song%s' % i diff --git a/tests/local/test_playlists.py b/tests/m3u/test_playlists.py similarity index 84% rename from tests/local/test_playlists.py rename to tests/m3u/test_playlists.py index 5af0debe..8c2187dc 100644 --- a/tests/local/test_playlists.py +++ b/tests/m3u/test_playlists.py @@ -8,30 +8,28 @@ import unittest import pykka from mopidy import core -from mopidy.local import actor -from mopidy.local.translator import local_playlist_uri_to_path +from mopidy.m3u import actor +from mopidy.m3u.translator import playlist_uri_to_path from mopidy.models import Playlist, Track from tests import dummy_audio, path_to_data_dir -from tests.local import generate_song +from tests.m3u import generate_song -class LocalPlaylistsProviderTest(unittest.TestCase): - backend_class = actor.LocalBackend +class M3UPlaylistsProviderTest(unittest.TestCase): + backend_class = actor.M3UBackend config = { - 'local': { - 'media_dir': path_to_data_dir(''), - 'data_dir': path_to_data_dir(''), - 'library': 'json', + 'm3u': { + 'playlists_dir': path_to_data_dir(''), } } def setUp(self): # noqa: N802 - self.config['local']['playlists_dir'] = tempfile.mkdtemp() - self.playlists_dir = self.config['local']['playlists_dir'] + self.config['m3u']['playlists_dir'] = tempfile.mkdtemp() + self.playlists_dir = self.config['m3u']['playlists_dir'] self.audio = dummy_audio.create_proxy() - self.backend = actor.LocalBackend.start( + self.backend = actor.M3UBackend.start( config=self.config, audio=self.audio).proxy() self.core = core.Core(backends=[self.backend]) @@ -42,8 +40,8 @@ class LocalPlaylistsProviderTest(unittest.TestCase): shutil.rmtree(self.playlists_dir) def test_created_playlist_is_persisted(self): - uri = 'local:playlist:test.m3u' - path = local_playlist_uri_to_path(uri, self.playlists_dir) + uri = 'm3u:test.m3u' + path = playlist_uri_to_path(uri, self.playlists_dir) self.assertFalse(os.path.exists(path)) playlist = self.core.playlists.create('test') @@ -54,16 +52,16 @@ class LocalPlaylistsProviderTest(unittest.TestCase): def test_create_sanitizes_playlist_name(self): playlist = self.core.playlists.create('../../test FOO baR') self.assertEqual('test FOO baR', playlist.name) - path = local_playlist_uri_to_path(playlist.uri, self.playlists_dir) + path = playlist_uri_to_path(playlist.uri, self.playlists_dir) self.assertEqual(self.playlists_dir, os.path.dirname(path)) self.assertTrue(os.path.exists(path)) def test_saved_playlist_is_persisted(self): - uri1 = 'local:playlist:test1.m3u' - uri2 = 'local:playlist:test2.m3u' + uri1 = 'm3u:test1.m3u' + uri2 = 'm3u:test2.m3u' - path1 = local_playlist_uri_to_path(uri1, self.playlists_dir) - path2 = local_playlist_uri_to_path(uri2, self.playlists_dir) + path1 = playlist_uri_to_path(uri1, self.playlists_dir) + path2 = playlist_uri_to_path(uri2, self.playlists_dir) playlist = self.core.playlists.create('test1') self.assertEqual('test1', playlist.name) @@ -78,8 +76,8 @@ class LocalPlaylistsProviderTest(unittest.TestCase): self.assertTrue(os.path.exists(path2)) def test_deleted_playlist_is_removed(self): - uri = 'local:playlist:test.m3u' - path = local_playlist_uri_to_path(uri, self.playlists_dir) + uri = 'm3u:test.m3u' + path = playlist_uri_to_path(uri, self.playlists_dir) self.assertFalse(os.path.exists(path)) @@ -95,7 +93,7 @@ class LocalPlaylistsProviderTest(unittest.TestCase): track = Track(uri=generate_song(1)) playlist = self.core.playlists.create('test') playlist = self.core.playlists.save(playlist.copy(tracks=[track])) - path = local_playlist_uri_to_path(playlist.uri, self.playlists_dir) + path = playlist_uri_to_path(playlist.uri, self.playlists_dir) with open(path) as f: contents = f.read() @@ -106,7 +104,7 @@ class LocalPlaylistsProviderTest(unittest.TestCase): track = Track(uri=generate_song(1), name='Test', length=60000) playlist = self.core.playlists.create('test') playlist = self.core.playlists.save(playlist.copy(tracks=[track])) - path = local_playlist_uri_to_path(playlist.uri, self.playlists_dir) + path = playlist_uri_to_path(playlist.uri, self.playlists_dir) with open(path) as f: contents = f.read().splitlines() @@ -114,7 +112,7 @@ class LocalPlaylistsProviderTest(unittest.TestCase): self.assertEqual(contents, ['#EXTM3U', '#EXTINF:60,Test', track.uri]) def test_playlists_are_loaded_at_startup(self): - track = Track(uri='local:track:path2') + track = Track(uri='dummy:track:path2') playlist = self.core.playlists.create('test') playlist = playlist.copy(tracks=[track]) playlist = self.core.playlists.save(playlist) @@ -134,7 +132,7 @@ class LocalPlaylistsProviderTest(unittest.TestCase): pass @unittest.SkipTest - def test_playlist_dir_is_created(self): + def test_playlists_dir_is_created(self): pass def test_create_returns_playlist_with_name_set(self): @@ -154,7 +152,7 @@ class LocalPlaylistsProviderTest(unittest.TestCase): self.assert_(not self.core.playlists.playlists) def test_delete_non_existant_playlist(self): - self.core.playlists.delete('local:playlist:unknown') + self.core.playlists.delete('m3u:unknown') def test_delete_playlist_removes_it_from_the_collection(self): playlist = self.core.playlists.create('test') @@ -168,7 +166,7 @@ class LocalPlaylistsProviderTest(unittest.TestCase): playlist = self.core.playlists.create('test') self.assertIn(playlist, self.core.playlists.playlists) - path = local_playlist_uri_to_path(playlist.uri, self.playlists_dir) + path = playlist_uri_to_path(playlist.uri, self.playlists_dir) self.assertTrue(os.path.exists(path)) os.remove(path) @@ -244,12 +242,12 @@ class LocalPlaylistsProviderTest(unittest.TestCase): def test_save_playlist_with_new_uri(self): # you *should* not do this - uri = 'local:playlist:test.m3u' + uri = 'm3u:test.m3u' playlist = self.core.playlists.save(Playlist(uri=uri)) self.assertIn(playlist, self.core.playlists.playlists) self.assertEqual(uri, playlist.uri) self.assertEqual('test', playlist.name) - path = local_playlist_uri_to_path(playlist.uri, self.playlists_dir) + path = playlist_uri_to_path(playlist.uri, self.playlists_dir) self.assertTrue(os.path.exists(path)) def test_playlist_with_unknown_track(self): @@ -261,8 +259,7 @@ class LocalPlaylistsProviderTest(unittest.TestCase): backend = self.backend_class(config=self.config, audio=self.audio) self.assert_(backend.playlists.playlists) - self.assertEqual( - 'local:playlist:test.m3u', backend.playlists.playlists[0].uri) + self.assertEqual('m3u:test.m3u', backend.playlists.playlists[0].uri) self.assertEqual( playlist.name, backend.playlists.playlists[0].name) self.assertEqual( @@ -282,12 +279,12 @@ class LocalPlaylistsProviderTest(unittest.TestCase): check_order(self.core.playlists.playlists, ['a', 'b', 'c']) - playlist = self.core.playlists.lookup('local:playlist:a.m3u') + playlist = self.core.playlists.lookup('m3u:a.m3u') playlist = playlist.copy(name='d') playlist = self.core.playlists.save(playlist) check_order(self.core.playlists.playlists, ['b', 'c', 'd']) - self.core.playlists.delete('local:playlist:c.m3u') + self.core.playlists.delete('m3u:c.m3u') check_order(self.core.playlists.playlists, ['b', 'd']) diff --git a/tests/local/test_translator.py b/tests/m3u/test_translator.py similarity index 99% rename from tests/local/test_translator.py rename to tests/m3u/test_translator.py index d3ba9e68..fc7fc958 100644 --- a/tests/local/test_translator.py +++ b/tests/m3u/test_translator.py @@ -6,7 +6,7 @@ import os import tempfile import unittest -from mopidy.local import translator +from mopidy.m3u import translator from mopidy.models import Track from mopidy.utils import path