fix: write playlists as .m3u8 so Rockbox reads them as UTF-8
Build and publish container / build (pull_request) Successful in 2m42s

Rockbox's is_m3u8_name() treats every playlist extension as UTF-8 except an
explicit ".m3u", which it instead decodes through the user's configured
codepage:

    /* Default to M3U8 unless explicitly told otherwise. */
    return (!dot || strcasecmp(dot, ".m3u") != 0);

The one extension being used was therefore the only one that mangles accented
filenames, and this library holds Motley Crue, Beyonce and Sigur Ros. Renaming
the output is the whole fix.

No byte order mark is written. One would promote a .m3u file to UTF-8 as well,
but it is unnecessary at this extension and upsets players that do not expect
to find one.

Kept with the pruning change rather than raised separately, because the rename
depends on it: without pruning, seventeen dead .m3u files would sit on the
device for ever, and every one of them full of paths that still resolve.
This commit is contained in:
Emma Thorpe
2026-08-25 10:43:43 +01:00
parent 9fdd61b648
commit 35d5e98642
3 changed files with 91 additions and 31 deletions
+13 -5
View File
@@ -69,6 +69,12 @@ BACKOFF_CEILING_SECONDS = 60.0
MIRROR_SUFFIX = ".mp3"
PLAYLIST_DIRECTORY = "_playlists"
# .m3u8, not .m3u. Rockbox's is_m3u8_name() treats every extension as UTF-8
# except an explicit ".m3u", which it decodes through the user's configured
# codepage instead -- so a plain .m3u mangles every accented filename, and this
# library holds Mötley Crüe, Beyoncé and Sigur Rós.
PLAYLIST_SUFFIX = ".m3u8"
# Tags are re-fetched this often. They move slowly, and the first pass over a
# library already costs one request per artist.
TAG_REFRESH_SECONDS = 90 * 86400
@@ -1552,8 +1558,10 @@ def build_vibe_playlists(store, vibes, mirror_root, library_root, limit, now):
continue
entries.append({**dict(row), "mirror": mirror})
write_playlist(directory / f"{vibe['name']}.m3u", entries, Path(mirror_root))
produced.append(f"{vibe['name']}.m3u")
write_playlist(
directory / f"{vibe['name']}{PLAYLIST_SUFFIX}", entries, Path(mirror_root)
)
produced.append(f"{vibe['name']}{PLAYLIST_SUFFIX}")
total += len(entries)
logger.info("playlist %-20s %4d tracks -- by tag", vibe["name"], len(entries))
@@ -1641,7 +1649,7 @@ def write_playlist(path, entries, reference=None):
if fresh and reference is not None:
match_ownership(path.parent, reference)
handle, temporary = tempfile.mkstemp(dir=path.parent, suffix=".m3u.part")
handle, temporary = tempfile.mkstemp(dir=path.parent, suffix=".m3u8.part")
os.close(handle)
temporary = Path(temporary)
try:
@@ -1702,8 +1710,8 @@ def build_playlists(store, mirror_root, library_root, limit, now):
missing += 1
continue
entries.append({**dict(row), "mirror": mirror})
write_playlist(directory / f"{name}.m3u", entries, Path(mirror_root))
produced.append(f"{name}.m3u")
write_playlist(directory / f"{name}{PLAYLIST_SUFFIX}", entries, Path(mirror_root))
produced.append(f"{name}{PLAYLIST_SUFFIX}")
total += len(entries)
logger.info("playlist %-20s %4d tracks -- %s", name, len(entries), description)