mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-04-18 19:25:00 -04:00
* Add API support for ELRC word-based lyrics Adds support for word-based timestamps from within ELRC files. * Create TimeTags object * redo TimeTag implementation Change TimeTag to long, redo TimeTag implementation Make timestamp not nullable Update MediaBrowser.Model/Lyrics/LyricLine.cs Make TimeTag list IReadOnlyList Remove nullable Timestamp Update TimeTag description Co-Authored-By: Cody Robibero <cody@robibe.ro> * Changes to LyricLineTimeTag Moved TimeTag to LyricLineTimeTag Change "timestamp" to "start" for consistency Change plural "TimeTags" to "Cues" Change comments * Change LyricLineTimeTag to LyricLineCue, include info about end times * Remove width * Remove width tag * Rewrite cue parser and add tests --------- Co-authored-by: Cody Robibero <cody@robibe.ro>
112 lines
3.5 KiB
C#
112 lines
3.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using Jellyfin.Extensions;
|
|
using LrcParser.Model;
|
|
using LrcParser.Parser;
|
|
using MediaBrowser.Controller.Lyrics;
|
|
using MediaBrowser.Controller.Resolvers;
|
|
using MediaBrowser.Model.Lyrics;
|
|
|
|
namespace MediaBrowser.Providers.Lyric;
|
|
|
|
/// <summary>
|
|
/// LRC Lyric Parser.
|
|
/// </summary>
|
|
public partial class LrcLyricParser : ILyricParser
|
|
{
|
|
private readonly LyricParser _lrcLyricParser;
|
|
|
|
private static readonly string[] _supportedMediaTypes = [".lrc", ".elrc"];
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="LrcLyricParser"/> class.
|
|
/// </summary>
|
|
public LrcLyricParser()
|
|
{
|
|
_lrcLyricParser = new LrcParser.Parser.Lrc.LrcParser();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string Name => "LrcLyricProvider";
|
|
|
|
/// <summary>
|
|
/// Gets the priority.
|
|
/// </summary>
|
|
/// <value>The priority.</value>
|
|
public ResolverPriority Priority => ResolverPriority.Fourth;
|
|
|
|
/// <inheritdoc />
|
|
public LyricDto? ParseLyrics(LyricFile lyrics)
|
|
{
|
|
if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan()), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Song lyricData;
|
|
|
|
try
|
|
{
|
|
lyricData = _lrcLyricParser.Decode(lyrics.Content);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Failed to parse, return null so the next parser will be tried
|
|
return null;
|
|
}
|
|
|
|
List<LrcParser.Model.Lyric> sortedLyricData = lyricData.Lyrics.OrderBy(x => x.StartTime).ToList();
|
|
|
|
if (sortedLyricData.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
List<LyricLine> lyricList = [];
|
|
for (var l = 0; l < sortedLyricData.Count; l++)
|
|
{
|
|
var cues = new List<LyricLineCue>();
|
|
var lyric = sortedLyricData[l];
|
|
|
|
if (lyric.TimeTags.Count != 0)
|
|
{
|
|
var keys = lyric.TimeTags.Keys.ToList();
|
|
int current = 0, next = 1;
|
|
while (next < keys.Count)
|
|
{
|
|
var currentKey = keys[current];
|
|
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 lastKey = keys[current];
|
|
var lastMs = lyric.TimeTags[lastKey] ?? 0;
|
|
|
|
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));
|
|
}
|
|
|
|
long lyricStartTicks = TimeSpan.FromMilliseconds(lyric.StartTime).Ticks;
|
|
lyricList.Add(new LyricLine(WhitespaceRegex().Replace(lyric.Text.Trim(), " "), 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();
|
|
}
|