LrcLyricParser.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text.RegularExpressions;
  6. using Jellyfin.Extensions;
  7. using LrcParser.Model;
  8. using LrcParser.Parser;
  9. using MediaBrowser.Controller.Lyrics;
  10. using MediaBrowser.Controller.Resolvers;
  11. using MediaBrowser.Model.Lyrics;
  12. namespace MediaBrowser.Providers.Lyric;
  13. /// <summary>
  14. /// LRC Lyric Parser.
  15. /// </summary>
  16. public partial class LrcLyricParser : ILyricParser
  17. {
  18. private readonly LyricParser _lrcLyricParser;
  19. private static readonly string[] _supportedMediaTypes = [".lrc", ".elrc"];
  20. /// <summary>
  21. /// Initializes a new instance of the <see cref="LrcLyricParser"/> class.
  22. /// </summary>
  23. public LrcLyricParser()
  24. {
  25. _lrcLyricParser = new LrcParser.Parser.Lrc.LrcParser();
  26. }
  27. /// <inheritdoc />
  28. public string Name => "LrcLyricProvider";
  29. /// <summary>
  30. /// Gets the priority.
  31. /// </summary>
  32. /// <value>The priority.</value>
  33. public ResolverPriority Priority => ResolverPriority.Fourth;
  34. /// <inheritdoc />
  35. public LyricDto? ParseLyrics(LyricFile lyrics)
  36. {
  37. if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan()), StringComparison.OrdinalIgnoreCase))
  38. {
  39. return null;
  40. }
  41. Song lyricData;
  42. try
  43. {
  44. lyricData = _lrcLyricParser.Decode(lyrics.Content);
  45. }
  46. catch (Exception)
  47. {
  48. // Failed to parse, return null so the next parser will be tried
  49. return null;
  50. }
  51. List<LrcParser.Model.Lyric> sortedLyricData = lyricData.Lyrics.OrderBy(x => x.StartTime).ToList();
  52. if (sortedLyricData.Count == 0)
  53. {
  54. return null;
  55. }
  56. List<LyricLine> lyricList = [];
  57. for (var l = 0; l < sortedLyricData.Count; l++)
  58. {
  59. var cues = new List<LyricLineCue>();
  60. var lyric = sortedLyricData[l];
  61. if (lyric.TimeTags.Count != 0)
  62. {
  63. var keys = lyric.TimeTags.Keys.ToList();
  64. int current = 0, next = 1;
  65. while (next < keys.Count)
  66. {
  67. var currentKey = keys[current];
  68. var currentMs = lyric.TimeTags[currentKey] ?? 0;
  69. var nextMs = lyric.TimeTags[keys[next]] ?? 0;
  70. cues.Add(new LyricLineCue(
  71. position: Math.Max(currentKey.Index, 0),
  72. start: TimeSpan.FromMilliseconds(currentMs).Ticks,
  73. end: TimeSpan.FromMilliseconds(nextMs).Ticks));
  74. current++;
  75. next++;
  76. }
  77. var lastKey = keys[current];
  78. var lastMs = lyric.TimeTags[lastKey] ?? 0;
  79. cues.Add(new LyricLineCue(
  80. position: Math.Max(lastKey.Index, 0),
  81. start: TimeSpan.FromMilliseconds(lastMs).Ticks,
  82. end: l + 1 < sortedLyricData.Count ? TimeSpan.FromMilliseconds(sortedLyricData[l + 1].StartTime).Ticks : null));
  83. }
  84. long lyricStartTicks = TimeSpan.FromMilliseconds(lyric.StartTime).Ticks;
  85. lyricList.Add(new LyricLine(WhitespaceRegex().Replace(lyric.Text.Trim(), " "), lyricStartTicks, cues));
  86. }
  87. return new LyricDto { Lyrics = lyricList };
  88. }
  89. // Replacement is required until https://github.com/karaoke-dev/LrcParser/issues/83 is resolved.
  90. [GeneratedRegex(@"\s+")]
  91. private static partial Regex WhitespaceRegex();
  92. }