From 2ccd8de4cdd9b3701220b02f33c3dc1832cda276 Mon Sep 17 00:00:00 2001 From: Max Goltzsche Date: Thu, 26 Dec 2024 19:25:23 +0100 Subject: [PATCH] smartplaylist: change encoding of additional field URL-encode additional item fields within generated EXTM3U playlists instead of JSON-encoding them. This is because JSON-encoding additional fields/attributes made it difficult to parse the `EXTINF` line but using URL-encoding for these values makes parsing easy (because URL-encoded values cannot contain quotation marks and spaces). I introduced the generation of additional EXTM3U item fields earlier this year and I want to make it right now. **Design/definition background:** Unfortunately, I didn't find a clear definition of how additional playlist item attributes should be encoded - apparently there is none. Given that item URIs within an M3U playlist can be URL-encoded already, defining the values of additional attributes to be URL-encoded is consistent design. I didn't find examples of additional M3U item attributes in the web where the attribute value contains a space or quotation mark but examples that specified numeric IDs and URLs as attribute values. Because the URL attribute examples I found didn't contain URL-encoded characters and because it is more readable and unproblematic for parsing, I've let the attribute URL encoding treat `:` and `/` as safe characters. **Breaking change:** While this is a breaking change in theory, in practice it is not since afaik all integrations of the smartplaylist plugin's additional EXTM3U item attribute generation feature (beets-webm3u) work with simple attribute values such as the item ID (numeric) whose formatting/encoding is not affected when changing from JSON to URL-encoding. In other words the change is backward-compatible with the beets-webm3u plugin (which I'll adjust correspondingly after this beets PR was merged). --- beetsplug/smartplaylist.py | 4 ++-- test/plugins/test_smartplaylist.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/beetsplug/smartplaylist.py b/beetsplug/smartplaylist.py index a3b24b5693..7ade5e7fcd 100644 --- a/beetsplug/smartplaylist.py +++ b/beetsplug/smartplaylist.py @@ -14,9 +14,9 @@ """Generates smart playlists based on beets queries.""" -import json import os from urllib.request import pathname2url +from urllib.parse import quote from beets import ui from beets.dbcore import OrQuery @@ -327,7 +327,7 @@ def update_playlists(self, lib, pretend=False): if extm3u: attr = [(k, entry.item[k]) for k in keys] al = [ - f" {a[0]}={json.dumps(str(a[1]))}" for a in attr + f" {a[0]}={quote(str(a[1]), safe='/:')}" for a in attr ] attrs = "".join(al) comment = "#EXTINF:{}{},{} - {}\n".format( diff --git a/test/plugins/test_smartplaylist.py b/test/plugins/test_smartplaylist.py index a50f3e6227..ade745c17d 100644 --- a/test/plugins/test_smartplaylist.py +++ b/test/plugins/test_smartplaylist.py @@ -283,7 +283,7 @@ def test_playlist_update_output_extm3u_fields(self): assert ( content == b"#EXTM3U\n" - + b'#EXTINF:300 id="456" genre="Fake Genre",Fake Artist - fake Title\n' + + b'#EXTINF:300 id="456" genre="Fake%20Genre",Fake Artist - fake Title\n' + b"/tagada.mp3\n" )