Fix M3U parser truncating EXTINF duration to single character (#3152)
authorDavid Bishop <teancom@users.noreply.github.com>
Fri, 13 Feb 2026 09:48:06 +0000 (01:48 -0800)
committerGitHub <noreply@github.com>
Fri, 13 Feb 2026 09:48:06 +0000 (09:48 +0000)
The EXTINF duration parser used `info[0].strip()[0]` which takes only
the first character of the duration string. For multi-digit durations
like "120", this truncates to "1". Also, the `-1` check on line 79
compared against "-1" but could only ever see "-" (a single char),
so negative/unknown durations were never properly detected.

Remove the erroneous `[0]` index so the full duration string is used
and the `-1` sentinel is correctly recognized.

Co-authored-by: David Bishop <git@gnuconsulting.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
music_assistant/helpers/playlists.py
tests/core/test_playlists.py [new file with mode: 0644]

index c66448c636a8af941ab7cf315827f321045fbcf3..81e501a3b2c28459104a971d7fd170d151ca0eb2 100644 (file)
@@ -75,7 +75,7 @@ def parse_m3u(m3u_data: str) -> list[PlaylistItem]:
             info = line.split("#EXTINF:")[1].split(",", 1)
             if len(info) != 2:
                 continue
-            length = info[0].strip()[0]
+            length = info[0].strip()
             if length == "-1":
                 length = None
             title = info[1].strip()
diff --git a/tests/core/test_playlists.py b/tests/core/test_playlists.py
new file mode 100644 (file)
index 0000000..720074e
--- /dev/null
@@ -0,0 +1,29 @@
+"""Tests for playlist parsing helpers."""
+
+from music_assistant.helpers.playlists import parse_m3u
+
+
+def test_m3u_extinf_duration_not_truncated() -> None:
+    """Test that EXTINF duration is parsed as full string, not truncated to first char."""
+    m3u_data = "#EXTM3U\n#EXTINF:120,Test Song\nhttp://example.com/song.mp3\n"
+    result = parse_m3u(m3u_data)
+    assert len(result) == 1
+    assert result[0].length == "120"
+    assert result[0].title == "Test Song"
+
+
+def test_m3u_extinf_negative_duration() -> None:
+    """Test that EXTINF with -1 duration is treated as None (unknown length)."""
+    m3u_data = "#EXTM3U\n#EXTINF:-1,Live Stream\nhttp://example.com/stream\n"
+    result = parse_m3u(m3u_data)
+    assert len(result) == 1
+    assert result[0].length is None
+    assert result[0].title == "Live Stream"
+
+
+def test_m3u_extinf_single_digit_duration() -> None:
+    """Test that single-digit durations still work correctly."""
+    m3u_data = "#EXTM3U\n#EXTINF:5,Short Clip\nhttp://example.com/clip.mp3\n"
+    result = parse_m3u(m3u_data)
+    assert len(result) == 1
+    assert result[0].length == "5"