From 9b8c12d4336fdf327d8fcdcd38cf9bb9c1e71716 Mon Sep 17 00:00:00 2001 From: Max Rumpf Date: Wed, 25 Jun 2025 00:59:09 +0200 Subject: [PATCH] Adapt LrcLyricParser to new LrcParser version (#14263) --- Directory.Packages.props | 4 +- MediaBrowser.Model/Lyrics/LyricLineCue.cs | 13 +++- .../Lyric/LrcLyricParser.cs | 62 +++++++++++-------- .../Lyrics/LrcLyricParserTests.cs | 16 +++-- 4 files changed, 59 insertions(+), 36 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 5322026344..61093ad992 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -24,7 +24,7 @@ - + @@ -92,4 +92,4 @@ - \ No newline at end of file + diff --git a/MediaBrowser.Model/Lyrics/LyricLineCue.cs b/MediaBrowser.Model/Lyrics/LyricLineCue.cs index 1172a0231d..2915533617 100644 --- a/MediaBrowser.Model/Lyrics/LyricLineCue.cs +++ b/MediaBrowser.Model/Lyrics/LyricLineCue.cs @@ -8,21 +8,28 @@ public class LyricLineCue /// /// Initializes a new instance of the class. /// - /// The start of the character index of the lyric. + /// The start character index of the cue. + /// The end character index of the cue. /// The start of the timestamp the lyric is synced to in ticks. /// The end of the timestamp the lyric is synced to in ticks. - public LyricLineCue(int position, long start, long? end) + public LyricLineCue(int position, int endPosition, long start, long? end) { Position = position; + EndPosition = endPosition; Start = start; End = end; } /// - /// Gets the character index of the lyric. + /// Gets the start character index of the cue. /// public int Position { get; } + /// + /// Gets the end character index of the cue. + /// + public int EndPosition { get; } + /// /// Gets the timestamp the lyric is synced to in ticks. /// diff --git a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs index 27d17b535c..fa711eb281 100644 --- a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs +++ b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using Jellyfin.Extensions; using LrcParser.Model; @@ -66,47 +67,56 @@ public partial class LrcLyricParser : ILyricParser } List lyricList = []; - for (var l = 0; l < sortedLyricData.Count; l++) + for (var lineIndex = 0; lineIndex < sortedLyricData.Count; lineIndex++) { - var cues = new List(); - var lyric = sortedLyricData[l]; + var lyric = sortedLyricData[lineIndex]; - if (lyric.TimeTags.Count != 0) + // Extract cues from time tags + var cues = new List(); + if (lyric.TimeTags.Count > 0) { var keys = lyric.TimeTags.Keys.ToList(); - int current = 0, next = 1; - while (next < keys.Count) + for (var tagIndex = 0; tagIndex < keys.Count - 1; tagIndex++) { - var currentKey = keys[current]; + var currentKey = keys[tagIndex]; + var nextKey = keys[tagIndex + 1]; + + var currentPos = currentKey.State == IndexState.End ? currentKey.Index + 1 : currentKey.Index; + var nextPos = nextKey.State == IndexState.End ? nextKey.Index + 1 : nextKey.Index; var currentMs = lyric.TimeTags[currentKey] ?? 0; - var nextMs = lyric.TimeTags[keys[next]] ?? 0; - - cues.Add(new LyricLineCue( - position: Math.Max(currentKey.Index, 0), - start: TimeSpan.FromMilliseconds(currentMs).Ticks, - end: TimeSpan.FromMilliseconds(nextMs).Ticks)); - - current++; - next++; + var nextMs = lyric.TimeTags[keys[tagIndex + 1]] ?? 0; + var currentSlice = lyric.Text[currentPos..nextPos]; + var currentSliceTrimmed = currentSlice.Trim(); + if (currentSliceTrimmed.Length > 0) + { + cues.Add(new LyricLineCue( + position: currentPos, + endPosition: nextPos, + start: TimeSpan.FromMilliseconds(currentMs).Ticks, + end: TimeSpan.FromMilliseconds(nextMs).Ticks)); + } } - var lastKey = keys[current]; + var lastKey = keys[^1]; + var lastPos = lastKey.State == IndexState.End ? lastKey.Index + 1 : lastKey.Index; var lastMs = lyric.TimeTags[lastKey] ?? 0; + var lastSlice = lyric.Text[lastPos..]; + var lastSliceTrimmed = lastSlice.Trim(); - cues.Add(new LyricLineCue( - position: Math.Max(lastKey.Index, 0), - start: TimeSpan.FromMilliseconds(lastMs).Ticks, - end: l + 1 < sortedLyricData.Count ? TimeSpan.FromMilliseconds(sortedLyricData[l + 1].StartTime).Ticks : null)); + if (lastSliceTrimmed.Length > 0) + { + cues.Add(new LyricLineCue( + position: lastPos, + endPosition: lyric.Text.Length, + start: TimeSpan.FromMilliseconds(lastMs).Ticks, + end: lineIndex + 1 < sortedLyricData.Count ? TimeSpan.FromMilliseconds(sortedLyricData[lineIndex + 1].StartTime).Ticks : null)); + } } long lyricStartTicks = TimeSpan.FromMilliseconds(lyric.StartTime).Ticks; - lyricList.Add(new LyricLine(WhitespaceRegex().Replace(lyric.Text.Trim(), " "), lyricStartTicks, cues)); + lyricList.Add(new LyricLine(lyric.Text, lyricStartTicks, cues)); } return new LyricDto { Lyrics = lyricList }; } - - // Replacement is required until https://github.com/karaoke-dev/LrcParser/issues/83 is resolved. - [GeneratedRegex(@"\s+")] - private static partial Regex WhitespaceRegex(); } diff --git a/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs b/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs index 756a688abe..a1fc067cc8 100644 --- a/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs +++ b/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs @@ -20,22 +20,28 @@ public static class LrcLyricParserTests var line1 = parsed.Lyrics[0]; Assert.Equal("Every night that goes between", line1.Text); Assert.NotNull(line1.Cues); - Assert.Equal(9, line1.Cues.Count); + Assert.Equal(5, line1.Cues.Count); Assert.Equal(68400000, line1.Cues[0].Start); Assert.Equal(72000000, line1.Cues[0].End); + Assert.Equal(0, line1.Cues[0].Position); + Assert.Equal(5, line1.Cues[0].EndPosition); + Assert.Equal(6, line1.Cues[1].Position); + Assert.Equal(11, line1.Cues[1].EndPosition); + Assert.Equal(12, line1.Cues[2].Position); var line5 = parsed.Lyrics[4]; Assert.Equal("Every night you do not come", line5.Text); Assert.NotNull(line5.Cues); - Assert.Equal(11, line5.Cues.Count); - Assert.Equal(377300000, line5.Cues[5].Start); - Assert.Equal(380000000, line5.Cues[5].End); + Assert.Equal(6, line5.Cues.Count); + Assert.Equal(375200000, line5.Cues[2].Start); + Assert.Equal(377300000, line5.Cues[2].End); var lastLine = parsed.Lyrics[^1]; Assert.Equal("I have always been a storm", lastLine.Text); Assert.NotNull(lastLine.Cues); - Assert.Equal(11, lastLine.Cues.Count); + Assert.Equal(6, lastLine.Cues.Count); Assert.Equal(2358000000, lastLine.Cues[^1].Start); + Assert.Equal(26, lastLine.Cues[^1].EndPosition); Assert.Null(lastLine.Cues[^1].End); } }