mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-27 17:01:16 -04:00
Adapt LrcLyricParser to new LrcParser version (#14263)
This commit is contained in:
parent
ba0eb87371
commit
9b8c12d433
4 changed files with 59 additions and 36 deletions
|
@ -24,7 +24,7 @@
|
||||||
<PackageVersion Include="Ignore" Version="0.2.1" />
|
<PackageVersion Include="Ignore" Version="0.2.1" />
|
||||||
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||||
<PackageVersion Include="libse" Version="4.0.12" />
|
<PackageVersion Include="libse" Version="4.0.12" />
|
||||||
<PackageVersion Include="LrcParser" Version="2025.228.1" />
|
<PackageVersion Include="LrcParser" Version="2025.623.0" />
|
||||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
|
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.6" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.6" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.6" />
|
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.6" />
|
||||||
|
@ -92,4 +92,4 @@
|
||||||
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
|
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
|
||||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -8,21 +8,28 @@ public class LyricLineCue
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LyricLineCue"/> class.
|
/// Initializes a new instance of the <see cref="LyricLineCue"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="position">The start of the character index of the lyric.</param>
|
/// <param name="position">The start character index of the cue.</param>
|
||||||
|
/// <param name="endPosition">The end character index of the cue.</param>
|
||||||
/// <param name="start">The start of the timestamp the lyric is synced to in ticks.</param>
|
/// <param name="start">The start of the timestamp the lyric is synced to in ticks.</param>
|
||||||
/// <param name="end">The end of the timestamp the lyric is synced to in ticks.</param>
|
/// <param name="end">The end of the timestamp the lyric is synced to in ticks.</param>
|
||||||
public LyricLineCue(int position, long start, long? end)
|
public LyricLineCue(int position, int endPosition, long start, long? end)
|
||||||
{
|
{
|
||||||
Position = position;
|
Position = position;
|
||||||
|
EndPosition = endPosition;
|
||||||
Start = start;
|
Start = start;
|
||||||
End = end;
|
End = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the character index of the lyric.
|
/// Gets the start character index of the cue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Position { get; }
|
public int Position { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the end character index of the cue.
|
||||||
|
/// </summary>
|
||||||
|
public int EndPosition { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the timestamp the lyric is synced to in ticks.
|
/// Gets the timestamp the lyric is synced to in ticks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using LrcParser.Model;
|
using LrcParser.Model;
|
||||||
|
@ -66,47 +67,56 @@ public partial class LrcLyricParser : ILyricParser
|
||||||
}
|
}
|
||||||
|
|
||||||
List<LyricLine> lyricList = [];
|
List<LyricLine> lyricList = [];
|
||||||
for (var l = 0; l < sortedLyricData.Count; l++)
|
for (var lineIndex = 0; lineIndex < sortedLyricData.Count; lineIndex++)
|
||||||
{
|
{
|
||||||
var cues = new List<LyricLineCue>();
|
var lyric = sortedLyricData[lineIndex];
|
||||||
var lyric = sortedLyricData[l];
|
|
||||||
|
|
||||||
if (lyric.TimeTags.Count != 0)
|
// Extract cues from time tags
|
||||||
|
var cues = new List<LyricLineCue>();
|
||||||
|
if (lyric.TimeTags.Count > 0)
|
||||||
{
|
{
|
||||||
var keys = lyric.TimeTags.Keys.ToList();
|
var keys = lyric.TimeTags.Keys.ToList();
|
||||||
int current = 0, next = 1;
|
for (var tagIndex = 0; tagIndex < keys.Count - 1; tagIndex++)
|
||||||
while (next < keys.Count)
|
|
||||||
{
|
{
|
||||||
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 currentMs = lyric.TimeTags[currentKey] ?? 0;
|
||||||
var nextMs = lyric.TimeTags[keys[next]] ?? 0;
|
var nextMs = lyric.TimeTags[keys[tagIndex + 1]] ?? 0;
|
||||||
|
var currentSlice = lyric.Text[currentPos..nextPos];
|
||||||
cues.Add(new LyricLineCue(
|
var currentSliceTrimmed = currentSlice.Trim();
|
||||||
position: Math.Max(currentKey.Index, 0),
|
if (currentSliceTrimmed.Length > 0)
|
||||||
start: TimeSpan.FromMilliseconds(currentMs).Ticks,
|
{
|
||||||
end: TimeSpan.FromMilliseconds(nextMs).Ticks));
|
cues.Add(new LyricLineCue(
|
||||||
|
position: currentPos,
|
||||||
current++;
|
endPosition: nextPos,
|
||||||
next++;
|
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 lastMs = lyric.TimeTags[lastKey] ?? 0;
|
||||||
|
var lastSlice = lyric.Text[lastPos..];
|
||||||
|
var lastSliceTrimmed = lastSlice.Trim();
|
||||||
|
|
||||||
cues.Add(new LyricLineCue(
|
if (lastSliceTrimmed.Length > 0)
|
||||||
position: Math.Max(lastKey.Index, 0),
|
{
|
||||||
start: TimeSpan.FromMilliseconds(lastMs).Ticks,
|
cues.Add(new LyricLineCue(
|
||||||
end: l + 1 < sortedLyricData.Count ? TimeSpan.FromMilliseconds(sortedLyricData[l + 1].StartTime).Ticks : null));
|
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;
|
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 };
|
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();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,22 +20,28 @@ public static class LrcLyricParserTests
|
||||||
var line1 = parsed.Lyrics[0];
|
var line1 = parsed.Lyrics[0];
|
||||||
Assert.Equal("Every night that goes between", line1.Text);
|
Assert.Equal("Every night that goes between", line1.Text);
|
||||||
Assert.NotNull(line1.Cues);
|
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(68400000, line1.Cues[0].Start);
|
||||||
Assert.Equal(72000000, line1.Cues[0].End);
|
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];
|
var line5 = parsed.Lyrics[4];
|
||||||
Assert.Equal("Every night you do not come", line5.Text);
|
Assert.Equal("Every night you do not come", line5.Text);
|
||||||
Assert.NotNull(line5.Cues);
|
Assert.NotNull(line5.Cues);
|
||||||
Assert.Equal(11, line5.Cues.Count);
|
Assert.Equal(6, line5.Cues.Count);
|
||||||
Assert.Equal(377300000, line5.Cues[5].Start);
|
Assert.Equal(375200000, line5.Cues[2].Start);
|
||||||
Assert.Equal(380000000, line5.Cues[5].End);
|
Assert.Equal(377300000, line5.Cues[2].End);
|
||||||
|
|
||||||
var lastLine = parsed.Lyrics[^1];
|
var lastLine = parsed.Lyrics[^1];
|
||||||
Assert.Equal("I have always been a storm", lastLine.Text);
|
Assert.Equal("I have always been a storm", lastLine.Text);
|
||||||
Assert.NotNull(lastLine.Cues);
|
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(2358000000, lastLine.Cues[^1].Start);
|
||||||
|
Assert.Equal(26, lastLine.Cues[^1].EndPosition);
|
||||||
Assert.Null(lastLine.Cues[^1].End);
|
Assert.Null(lastLine.Cues[^1].End);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue