From 9b42cab76b7e6aff08b2620a4a0e82ba7ab712e5 Mon Sep 17 00:00:00 2001 From: Uyanide Date: Wed, 8 Apr 2026 12:28:28 +0200 Subject: [PATCH] fix: preserve input order for equal-timestamp lyrics in normalize and to_plain --- pyproject.toml | 2 +- src/lrx_cli/lrc.py | 14 ++++++++++++-- tests/test_lrc.py | 32 ++++++++++++++++++++++++++++++++ uv.lock | 2 +- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0ceff0d..fbb66aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lrx-cli" -version = "0.6.3" +version = "0.6.4" description = "Fetch line-synced lyrics for your music player." readme = "README.md" requires-python = ">=3.13" diff --git a/src/lrx_cli/lrc.py b/src/lrx_cli/lrc.py index f263214..bbe0354 100644 --- a/src/lrx_cli/lrc.py +++ b/src/lrx_cli/lrc.py @@ -378,7 +378,12 @@ class LRCData: shifted = max(0, time_ms + offset_ms) lyric_entries.append((shifted, lyric_text)) - lyric_entries.sort(key=lambda item: item[0]) + # Sort by timestamp; original index as tiebreaker so equal-time entries + # retain the order they appeared in the input. + lyric_entries = [ + e + for _, e in sorted(enumerate(lyric_entries), key=lambda x: (x[1][0], x[0])) + ] out_lyrics: list[LyricLine] = [ LyricLine(line_times_ms=[time_ms], words=[LrcWordSegment(text=text)]) @@ -413,7 +418,12 @@ class LRCData: for line in self._lines: tagged_lines.extend(line.timed_plain_entries()) - sorted_lines = [lyric for _, lyric in sorted(tagged_lines, key=lambda x: x[0])] + sorted_lines = [ + lyric + for _, (_, lyric) in sorted( + enumerate(tagged_lines), key=lambda x: (x[1][0], x[0]) + ) + ] if deduplicate: # Remove consecutive duplicates diff --git a/tests/test_lrc.py b/tests/test_lrc.py index e9fe195..0475a9b 100644 --- a/tests/test_lrc.py +++ b/tests/test_lrc.py @@ -185,6 +185,23 @@ def test_normalize_expands_multi_time_tags_and_sorts_lyrics() -> None: assert normalized == "\n".join(["[00:01.00]x", "[00:02.00]x", "[00:03.00]c"]) +def test_normalize_preserves_input_order_for_equal_timestamps() -> None: + text = "\n".join( + [ + "[00:00.00]first", + "[00:00.00]second", + "[00:00.00]third", + "[00:01.00]later", + ] + ) + + normalized = LRCData(text).to_normalized_text() + + assert normalized == "\n".join( + ["[00:00.00]first", "[00:00.00]second", "[00:00.00]third", "[00:01.00]later"] + ) + + def test_normalize_converts_unsynced_lines_and_removes_word_sync_tags() -> None: text = "\n".join( [ @@ -257,6 +274,21 @@ def test_to_plain_sorts_lines_by_timestamp_across_lines() -> None: assert plain == "\n".join(["early", "middle", "late"]) +def test_to_plain_preserves_input_order_for_equal_timestamps() -> None: + text = "\n".join( + [ + "[00:00.00]first", + "[00:00.00]second", + "[00:00.00]third", + "[00:01.00]later", + ] + ) + + plain = LRCData(text).to_plain() + + assert plain == "\n".join(["first", "second", "third", "later"]) + + def test_to_plain_deduplicate_collapses_only_consecutive_equals() -> None: text = "\n".join( [ diff --git a/uv.lock b/uv.lock index cbc4707..e6ea960 100644 --- a/uv.lock +++ b/uv.lock @@ -153,7 +153,7 @@ wheels = [ [[package]] name = "lrx-cli" -version = "0.6.3" +version = "0.6.4" source = { editable = "." } dependencies = [ { name = "cyclopts" },