Skip to content

Commit

Permalink
Merge pull request beetbox#5046 from mgoltzsche/add-extinf-support
Browse files Browse the repository at this point in the history
smartplaylist: add extm3u/extinf/m3u8 support
  • Loading branch information
Serene-Arc authored Dec 14, 2023
2 parents c1a232e + b07a2e4 commit 60ad1ba
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 6 deletions.
32 changes: 27 additions & 5 deletions beetsplug/smartplaylist.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def __init__(self):
"prefix": "",
"urlencode": False,
"pretend_paths": False,
"extm3u": False,
}
)

Expand All @@ -71,6 +72,17 @@ def commands(self):
action="store_true",
help="display query results but don't write playlist files.",
)
spl_update.parser.add_option(
"--extm3u",
action="store_true",
help="add artist/title as m3u8 comments to playlists.",
)
spl_update.parser.add_option(
"--no-extm3u",
action="store_false",
dest="extm3u",
help="do not add artist/title as extm3u comments to playlists.",
)
spl_update.func = self.update_cmd
return [spl_update]

Expand Down Expand Up @@ -99,7 +111,7 @@ def update_cmd(self, lib, opts, args):
else:
self._matched_playlists = self._unmatched_playlists

self.update_playlists(lib, opts.pretend)
self.update_playlists(lib, opts.extm3u, opts.pretend)

def build_queries(self):
"""
Expand Down Expand Up @@ -185,7 +197,7 @@ def db_change(self, lib, model):

self._unmatched_playlists -= self._matched_playlists

def update_playlists(self, lib, pretend=False):
def update_playlists(self, lib, extm3u=None, pretend=False):
if pretend:
self._log.info(
"Showing query results for {0} smart playlists...",
Expand Down Expand Up @@ -230,7 +242,7 @@ def update_playlists(self, lib, pretend=False):
if relative_to:
item_path = os.path.relpath(item.path, relative_to)
if item_path not in m3us[m3u_name]:
m3us[m3u_name].append(item_path)
m3us[m3u_name].append({"item": item, "path": item_path})
if pretend and self.config["pretend_paths"]:
print(displayable_path(item_path))
elif pretend:
Expand All @@ -244,13 +256,23 @@ def update_playlists(self, lib, pretend=False):
os.path.join(playlist_dir, bytestring_path(m3u))
)
mkdirall(m3u_path)
extm3u = extm3u is None and self.config["extm3u"] or extm3u
with open(syspath(m3u_path), "wb") as f:
for path in m3us[m3u]:
if extm3u:
f.write(b"#EXTM3U\n")
for entry in m3us[m3u]:
path = entry["path"]
item = entry["item"]
if self.config["forward_slash"].get():
path = path_as_posix(path)
if self.config["urlencode"]:
path = bytestring_path(pathname2url(path))
f.write(prefix + path + b"\n")
comment = ""
if extm3u:
comment = "#EXTINF:{},{} - {}\n".format(
int(item.length), item.artist, item.title
)
f.write(comment.encode("utf-8") + prefix + path + b"\n")
# Send an event when playlists were updated.
send_event("smartplaylist_update")

Expand Down
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ New features:
`synced` option to prefer synced lyrics over plain lyrics.
* :ref:`import-cmd`: Expose import.quiet_fallback as CLI option.
* :ref:`import-cmd`: Expose `import.incremental_skip_later` as CLI option.
* :doc:`/plugins/smartplaylist`: Add new config option `smartplaylist.extm3u`.

Bug fixes:

Expand Down
1 change: 1 addition & 0 deletions docs/plugins/smartplaylist.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,4 @@ other configuration options are:
- **urlencoded**: URL-encode all paths. Default: ``no``.
- **pretend_paths**: When running with ``--pretend``, show the actual file
paths that will be written to the m3u file. Default: ``false``.
- **extm3u**: Generate extm3u/m3u8 playlists. Default ``ǹo``.
52 changes: 51 additions & 1 deletion test/plugins/test_smartplaylist.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from tempfile import mkdtemp
from test import _common
from test.helper import TestHelper
from unittest.mock import MagicMock, Mock
from unittest.mock import MagicMock, Mock, PropertyMock

from beets import config
from beets.dbcore import OrQuery
Expand Down Expand Up @@ -191,6 +191,56 @@ def test_playlist_update(self):

self.assertEqual(content, b"/tagada.mp3\n")

def test_playlist_update_extm3u(self):
spl = SmartPlaylistPlugin()

i = MagicMock()
type(i).artist = PropertyMock(return_value="fake artist")
type(i).title = PropertyMock(return_value="fake title")
type(i).length = PropertyMock(return_value=300.123)
type(i).path = PropertyMock(return_value=b"/tagada.mp3")
i.evaluate_template.side_effect = lambda pl, _: pl.replace(
b"$title",
b"ta:ga:da",
).decode()

lib = Mock()
lib.replacements = CHAR_REPLACE
lib.items.return_value = [i]
lib.albums.return_value = []

q = Mock()
a_q = Mock()
pl = b"$title-my<playlist>.m3u", (q, None), (a_q, None)
spl._matched_playlists = [pl]

dir = bytestring_path(mkdtemp())
config["smartplaylist"]["extm3u"] = True
config["smartplaylist"]["prefix"] = "http://beets:8337/files"
config["smartplaylist"]["relative_to"] = False
config["smartplaylist"]["playlist_dir"] = py3_path(dir)
try:
spl.update_playlists(lib)
except Exception:
rmtree(syspath(dir))
raise

lib.items.assert_called_once_with(q, None)
lib.albums.assert_called_once_with(a_q, None)

m3u_filepath = path.join(dir, b"ta_ga_da-my_playlist_.m3u")
self.assertExists(m3u_filepath)
with open(syspath(m3u_filepath), "rb") as f:
content = f.read()
rmtree(syspath(dir))

self.assertEqual(
content,
b"#EXTM3U\n"
+ b"#EXTINF:300,fake artist - fake title\n"
+ b"http://beets:8337/files/tagada.mp3\n",
)


class SmartPlaylistCLITest(_common.TestCase, TestHelper):
def setUp(self):
Expand Down

0 comments on commit 60ad1ba

Please sign in to comment.