mirror of
https://github.com/beetbox/beets.git
synced 2025-12-15 21:14:19 +01:00
smartplaylist: allow exporting item fields
Allow generating extm3u playlists so that they contain additional item fields such as the `id`. The feature is required by the mgoltzsche/beets-webm3u plugin (M3U server) to transform playlists using a request based item URI template which may require additional fields such as the `id`, e.g. `beets:library:track;$id`.
This commit is contained in:
parent
cc941df025
commit
c0afd3eb3c
5 changed files with 83 additions and 5 deletions
|
|
@ -16,6 +16,7 @@
|
|||
"""
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
from urllib.request import pathname2url
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ class SmartPlaylistPlugin(BeetsPlugin):
|
|||
"auto": True,
|
||||
"playlists": [],
|
||||
"uri_format": None,
|
||||
"fields": [],
|
||||
"forward_slash": False,
|
||||
"prefix": "",
|
||||
"urlencode": False,
|
||||
|
|
@ -297,7 +299,7 @@ class SmartPlaylistPlugin(BeetsPlugin):
|
|||
item_uri = prefix + item_uri
|
||||
|
||||
if item_uri not in m3us[m3u_name]:
|
||||
m3us[m3u_name].append({"item": item, "uri": item_uri})
|
||||
m3us[m3u_name].append(PlaylistItem(item, item_uri))
|
||||
if pretend and self.config["pretend_paths"]:
|
||||
print(displayable_path(item_uri))
|
||||
elif pretend:
|
||||
|
|
@ -317,16 +319,23 @@ class SmartPlaylistPlugin(BeetsPlugin):
|
|||
raise Exception(msg.format(pl_format))
|
||||
m3u8 = pl_format == "m3u8"
|
||||
with open(syspath(m3u_path), "wb") as f:
|
||||
keys = []
|
||||
if m3u8:
|
||||
keys = self.config["fields"].get(list)
|
||||
f.write(b"#EXTM3U\n")
|
||||
for entry in m3us[m3u]:
|
||||
item = entry["item"]
|
||||
item = entry.item
|
||||
comment = ""
|
||||
if m3u8:
|
||||
comment = "#EXTINF:{},{} - {}\n".format(
|
||||
int(item.length), item.artist, item.title
|
||||
attr = [(k, entry.item[k]) for k in keys]
|
||||
al = [
|
||||
f" {a[0]}={json.dumps(str(a[1]))}" for a in attr
|
||||
]
|
||||
attrs = "".join(al)
|
||||
comment = "#EXTINF:{}{},{} - {}\n".format(
|
||||
int(item.length), attrs, item.artist, item.title
|
||||
)
|
||||
f.write(comment.encode("utf-8") + entry["uri"] + b"\n")
|
||||
f.write(comment.encode("utf-8") + entry.uri + b"\n")
|
||||
# Send an event when playlists were updated.
|
||||
send_event("smartplaylist_update")
|
||||
|
||||
|
|
@ -339,3 +348,9 @@ class SmartPlaylistPlugin(BeetsPlugin):
|
|||
self._log.info(
|
||||
"{0} playlists updated", len(self._matched_playlists)
|
||||
)
|
||||
|
||||
|
||||
class PlaylistItem:
|
||||
def __init__(self, item, uri):
|
||||
self.item = item
|
||||
self.uri = uri
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ New features:
|
|||
like the other lossless formats.
|
||||
* Add support for `barcode` field.
|
||||
:bug:`3172`
|
||||
* :doc:`/plugins/smartplaylist`: Add new config option `smartplaylist.fields`.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
|
|
|
|||
|
|
@ -313,6 +313,9 @@ Interoperability
|
|||
Automatically notifies `Subsonic`_ whenever the beets
|
||||
library changes.
|
||||
|
||||
`webm3u`_
|
||||
Serves the (:doc:`smartplaylist <smartplaylist>` plugin generated) M3U
|
||||
playlists via HTTP.
|
||||
|
||||
.. _AURA: https://auraspec.readthedocs.io
|
||||
.. _Emby: https://emby.media
|
||||
|
|
@ -321,6 +324,7 @@ Interoperability
|
|||
.. _Kodi: https://kodi.tv
|
||||
.. _Sonos: https://sonos.com
|
||||
.. _Subsonic: http://www.subsonic.org/
|
||||
.. _webm3u: https://github.com/mgoltzsche/beets-webm3u
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
|
|
|||
|
|
@ -123,6 +123,12 @@ other configuration options are:
|
|||
When this option is specified, the local path-related options ``prefix``,
|
||||
``relative_to``, ``forward_slash`` and ``urlencode`` are ignored.
|
||||
- **output**: Specify the playlist format: m3u|m3u8. Default ``m3u``.
|
||||
- **fields**: Specify the names of the additional item fields to export into
|
||||
the playlist. This allows using e.g. the ``id`` field within other tools such
|
||||
as the `webm3u`_ plugin.
|
||||
To use this option, you must set the ``output`` option to ``m3u8``.
|
||||
|
||||
.. _webm3u: https://github.com/mgoltzsche/beets-webm3u
|
||||
|
||||
For many configuration options, there is a corresponding CLI option, e.g.
|
||||
``--playlist-dir``, ``--relative-to``, ``--prefix``, ``--forward-slash``,
|
||||
|
|
|
|||
|
|
@ -241,6 +241,58 @@ class SmartPlaylistTest(_common.TestCase):
|
|||
+ b"http://beets:8337/files/tagada.mp3\n",
|
||||
)
|
||||
|
||||
def test_playlist_update_output_m3u8_fields(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")
|
||||
a = {"id": 456, "genre": "Fake Genre"}
|
||||
i.__getitem__.side_effect = a.__getitem__
|
||||
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"]["output"] = "m3u8"
|
||||
config["smartplaylist"]["relative_to"] = False
|
||||
config["smartplaylist"]["playlist_dir"] = py3_path(dir)
|
||||
config["smartplaylist"]["fields"] = ["id", "genre"]
|
||||
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 id="456" genre="Fake Genre",Fake Artist - fake Title\n'
|
||||
+ b"/tagada.mp3\n",
|
||||
)
|
||||
|
||||
def test_playlist_update_uri_format(self):
|
||||
spl = SmartPlaylistPlugin()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue