diff --git a/.gitignore b/.gitignore
index c2ae76c1e3..3f80ffcf7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -281,3 +281,5 @@ apiclient/generated
# Omnisharp crash logs
mono_crash.*.json
+
+!LrcParser.dll
\ No newline at end of file
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index e45f9b58cd..89fb56744c 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -1,14 +1,18 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
+using Jellyfin.Api.Models.UserDtos;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
+using Kfstorm.LrcParser;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -381,5 +385,50 @@ namespace Jellyfin.Api.Controllers
return _userDataRepository.GetUserDataDto(item, user);
}
+
+ ///
+ /// Gets an item's lyrics.
+ ///
+ /// User id.
+ /// Item id.
+ /// Lyrics returned.
+ /// Something went wrong. No Lyrics will be returned.
+ /// An containing the intros to play.
+ [HttpGet("Users/{userId}/Items/{itemId}/Lyrics")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult> GetLyrics([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ if (user == null)
+ {
+ List lyricsList = new List
+ {
+ new Lyrics { Error = "User Not Found" }
+ };
+ return NotFound(new { Results = lyricsList.ToArray() });
+ }
+
+ var item = itemId.Equals(default)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(itemId);
+
+ if (item == null)
+ {
+ List lyricsList = new List
+ {
+ new Lyrics { Error = "Requested Item Not Found" }
+ };
+ return NotFound(new { Results = lyricsList.ToArray() });
+ }
+
+ List result = ItemHelper.GetLyricData(item);
+ if (string.IsNullOrEmpty(result.ElementAt(0).Error))
+ {
+ return Ok(new { Results = result });
+ }
+
+ return NotFound(new { Results = result.ToArray() });
+ }
}
}
diff --git a/Jellyfin.Api/Helpers/ItemHelper.cs b/Jellyfin.Api/Helpers/ItemHelper.cs
new file mode 100644
index 0000000000..43ef7aa093
--- /dev/null
+++ b/Jellyfin.Api/Helpers/ItemHelper.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.UserDtos;
+using Kfstorm.LrcParser;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Net;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Api.Helpers
+{
+ ///
+ /// Item helper.
+ ///
+ public static class ItemHelper
+ {
+ ///
+ /// Opens lyrics file, converts to a List of Lyrics, and returns it.
+ ///
+ /// Requested Item.
+ /// Collection of Lyrics.
+ internal static List GetLyricData(BaseItem item)
+ {
+ List lyricsList = new List();
+
+ string lrcFilePath = @Path.ChangeExtension(item.Path, "lrc");
+
+ // LRC File not found, fallback to TXT file
+ if (!System.IO.File.Exists(lrcFilePath))
+ {
+ string txtFilePath = @Path.ChangeExtension(item.Path, "txt");
+ if (!System.IO.File.Exists(txtFilePath))
+ {
+ lyricsList.Add(new Lyrics { Error = "Lyric File Not Found" });
+ return lyricsList;
+ }
+
+ var lyricTextData = System.IO.File.ReadAllText(txtFilePath);
+ string[] lyricTextLines = lyricTextData.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var lyricLine in lyricTextLines)
+ {
+ lyricsList.Add(new Lyrics { Text = lyricLine });
+ }
+
+ return lyricsList;
+ }
+
+ // Process LRC File
+ ILrcFile lyricData;
+ string lrcFileContent = System.IO.File.ReadAllText(lrcFilePath);
+ try
+ {
+ lrcFileContent = lrcFileContent.Replace('<', '[');
+ lrcFileContent = lrcFileContent.Replace('>', ']');
+ lyricData = Kfstorm.LrcParser.LrcFile.FromText(lrcFileContent);
+ }
+ catch
+ {
+ lyricsList.Add(new Lyrics { Error = "No Lyrics Data" });
+ return lyricsList;
+ }
+
+ if (lyricData == null)
+ {
+ lyricsList.Add(new Lyrics { Error = "No Lyrics Data" });
+ return lyricsList;
+ }
+
+ foreach (var lyricLine in lyricData.Lyrics)
+ {
+ double ticks = lyricLine.Timestamp.TotalSeconds * 10000000;
+ lyricsList.Add(new Lyrics { Start = Math.Ceiling(ticks), Text = lyricLine.Content });
+ }
+
+ return lyricsList;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 894d871383..1b78bb1074 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -46,4 +46,16 @@
+
+
+ Libraries\LrcParser.dll
+
+
+
+
+
+ Always
+
+
+
diff --git a/Jellyfin.Api/Libraries/LrcParser.dll b/Jellyfin.Api/Libraries/LrcParser.dll
new file mode 100644
index 0000000000..46e4fb7033
Binary files /dev/null and b/Jellyfin.Api/Libraries/LrcParser.dll differ
diff --git a/Jellyfin.Api/Models/UserDtos/Lyrics.cs b/Jellyfin.Api/Models/UserDtos/Lyrics.cs
new file mode 100644
index 0000000000..df1b5d08a2
--- /dev/null
+++ b/Jellyfin.Api/Models/UserDtos/Lyrics.cs
@@ -0,0 +1,23 @@
+namespace Jellyfin.Api.Models.UserDtos
+{
+ ///
+ /// Lyric dto.
+ ///
+ public class Lyrics
+ {
+ ///
+ /// Gets or sets the start.
+ ///
+ public double? Start { get; set; }
+
+ ///
+ /// Gets or sets the test.
+ ///
+ public string? Text { get; set; }
+
+ ///
+ /// Gets or sets the error.
+ ///
+ public string? Error { get; set; }
+ }
+}