# 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. import os from shlex import quote import beets from beets.test import _common from beets.test.helper import PluginTestCase class PlaylistTestCase(PluginTestCase): plugin = "playlist" preload_plugin = False def setUp(self): super().setUp() self.music_dir = os.path.expanduser(os.path.join("~", "Music")) i1 = _common.item() i1.path = beets.util.normpath( os.path.join( self.music_dir, "a", "b", "c.mp3", ) ) i1.title = "some item" i1.album = "some album" self.lib.add(i1) self.lib.add_album([i1]) i2 = _common.item() 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( os.path.join( self.music_dir, "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 = self.temp_dir_path / "playlists" self.playlist_dir.mkdir(parents=True, exist_ok=True) self.config["directory"] = self.music_dir self.config["playlist"]["playlist_dir"] = str(self.playlist_dir) self.setup_test() self.load_plugins() def setup_test(self): raise NotImplementedError class PlaylistQueryTest: def test_name_query_with_absolute_paths_in_playlist(self): q = "playlist:absolute" results = self.lib.items(q) assert {i.title for i in results} == {"some item", "another item"} def test_path_query_with_absolute_paths_in_playlist(self): q = f"playlist:{quote(os.path.join(self.playlist_dir, 'absolute.m3u'))}" results = self.lib.items(q) assert {i.title for i in results} == {"some item", "another item"} def test_name_query_with_relative_paths_in_playlist(self): q = "playlist:relative" results = self.lib.items(q) assert {i.title for i in results} == {"some item", "another item"} def test_path_query_with_relative_paths_in_playlist(self): q = f"playlist:{quote(os.path.join(self.playlist_dir, 'relative.m3u'))}" results = self.lib.items(q) assert {i.title for i in results} == {"some item", "another item"} def test_name_query_with_nonexisting_playlist(self): q = "playlist:nonexisting" results = self.lib.items(q) assert set(results) == set() def test_path_query_with_nonexisting_playlist(self): q = f"playlist:{os.path.join(self.playlist_dir, 'nonexisting.m3u')!r}" results = self.lib.items(q) assert set(results) == set() class PlaylistTestRelativeToLib(PlaylistQueryTest, PlaylistTestCase): def setup_test(self): with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: f.writelines( [ os.path.join(self.music_dir, "a", "b", "c.mp3") + "\n", os.path.join(self.music_dir, "d", "e", "f.mp3") + "\n", os.path.join(self.music_dir, "nonexisting.mp3") + "\n", ] ) with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: f.writelines( [ os.path.join("a", "b", "c.mp3") + "\n", os.path.join("d", "e", "f.mp3") + "\n", "nonexisting.mp3" + "\n", ] ) self.config["playlist"]["relative_to"] = "library" class PlaylistTestRelativeToDir(PlaylistQueryTest, PlaylistTestCase): def setup_test(self): with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: f.writelines( [ os.path.join(self.music_dir, "a", "b", "c.mp3") + "\n", os.path.join(self.music_dir, "d", "e", "f.mp3") + "\n", os.path.join(self.music_dir, "nonexisting.mp3") + "\n", ] ) with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: f.writelines( [ os.path.join("a", "b", "c.mp3") + "\n", os.path.join("d", "e", "f.mp3") + "\n", "nonexisting.mp3" + "\n", ] ) self.config["playlist"]["relative_to"] = self.music_dir class PlaylistTestRelativeToPls(PlaylistQueryTest, PlaylistTestCase): def setup_test(self): with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: f.writelines( [ os.path.join(self.music_dir, "a", "b", "c.mp3") + "\n", os.path.join(self.music_dir, "d", "e", "f.mp3") + "\n", os.path.join(self.music_dir, "nonexisting.mp3") + "\n", ] ) with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: f.writelines( [ os.path.relpath( os.path.join(self.music_dir, "a", "b", "c.mp3"), start=self.playlist_dir, ) + "\n", os.path.relpath( os.path.join(self.music_dir, "d", "e", "f.mp3"), start=self.playlist_dir, ) + "\n", os.path.relpath( os.path.join(self.music_dir, "nonexisting.mp3"), start=self.playlist_dir, ) + "\n", ] ) self.config["playlist"]["relative_to"] = "playlist" self.config["playlist"]["playlist_dir"] = str(self.playlist_dir) class PlaylistUpdateTest: def setup_test(self): with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: f.writelines( [ os.path.join(self.music_dir, "a", "b", "c.mp3") + "\n", os.path.join(self.music_dir, "d", "e", "f.mp3") + "\n", os.path.join(self.music_dir, "nonexisting.mp3") + "\n", ] ) with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: f.writelines( [ os.path.join("a", "b", "c.mp3") + "\n", os.path.join("d", "e", "f.mp3") + "\n", "nonexisting.mp3" + "\n", ] ) self.config["playlist"]["auto"] = True self.config["playlist"]["relative_to"] = "library" class PlaylistTestItemMoved(PlaylistUpdateTest, PlaylistTestCase): def test_item_moved(self): # Emit item_moved event for an item that is in a playlist results = self.lib.items( f"path:{quote(os.path.join(self.music_dir, 'd', 'e', 'f.mp3'))}" ) item = results[0] beets.plugins.send( "item_moved", item=item, source=item.path, destination=beets.util.bytestring_path( os.path.join(self.music_dir, "g", "h", "i.mp3") ), ) # Emit item_moved event for an item that is not in a playlist results = self.lib.items( f"path:{quote(os.path.join(self.music_dir, 'x', 'y', 'z.mp3'))}" ) item = results[0] beets.plugins.send( "item_moved", item=item, source=item.path, destination=beets.util.bytestring_path( os.path.join(self.music_dir, "u", "v", "w.mp3") ), ) # Emit cli_exit event beets.plugins.send("cli_exit", lib=self.lib) # Check playlist with absolute paths playlist_path = os.path.join(self.playlist_dir, "absolute.m3u") with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] assert lines == [ os.path.join(self.music_dir, "a", "b", "c.mp3"), os.path.join(self.music_dir, "g", "h", "i.mp3"), os.path.join(self.music_dir, "nonexisting.mp3"), ] # Check playlist with relative paths playlist_path = os.path.join(self.playlist_dir, "relative.m3u") with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] assert lines == [ os.path.join("a", "b", "c.mp3"), os.path.join("g", "h", "i.mp3"), "nonexisting.mp3", ] class PlaylistTestItemRemoved(PlaylistUpdateTest, PlaylistTestCase): def test_item_removed(self): # Emit item_removed event for an item that is in a playlist results = self.lib.items( f"path:{quote(os.path.join(self.music_dir, 'd', 'e', 'f.mp3'))}" ) item = results[0] beets.plugins.send("item_removed", item=item) # Emit item_removed event for an item that is not in a playlist results = self.lib.items( f"path:{quote(os.path.join(self.music_dir, 'x', 'y', 'z.mp3'))}" ) item = results[0] beets.plugins.send("item_removed", item=item) # Emit cli_exit event beets.plugins.send("cli_exit", lib=self.lib) # Check playlist with absolute paths playlist_path = os.path.join(self.playlist_dir, "absolute.m3u") with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] assert lines == [ os.path.join(self.music_dir, "a", "b", "c.mp3"), os.path.join(self.music_dir, "nonexisting.mp3"), ] # Check playlist with relative paths playlist_path = os.path.join(self.playlist_dir, "relative.m3u") with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] assert lines == [os.path.join("a", "b", "c.mp3"), "nonexisting.mp3"]