From 6de56f05186b77042a611112d82208b8fa8675fb Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Tue, 20 Jun 2023 16:51:07 +0200 Subject: Add support for lyric provider plugins --- Jellyfin.Server/CoreAppHost.cs | 6 + MediaBrowser.Controller/Lyrics/ILyricParser.cs | 28 +++ MediaBrowser.Controller/Lyrics/ILyricProvider.cs | 36 ---- MediaBrowser.Controller/Lyrics/LyricFile.cs | 28 +++ MediaBrowser.Controller/Lyrics/LyricInfo.cs | 49 ----- .../Lyric/DefaultLyricProvider.cs | 66 +++++++ MediaBrowser.Providers/Lyric/ILyricProvider.cs | 36 ++++ MediaBrowser.Providers/Lyric/LrcLyricParser.cs | 197 ++++++++++++++++++ MediaBrowser.Providers/Lyric/LrcLyricProvider.cs | 220 --------------------- MediaBrowser.Providers/Lyric/LyricManager.cs | 22 ++- MediaBrowser.Providers/Lyric/TxtLyricParser.cs | 49 +++++ MediaBrowser.Providers/Lyric/TxtLyricProvider.cs | 60 ------ 12 files changed, 427 insertions(+), 370 deletions(-) create mode 100644 MediaBrowser.Controller/Lyrics/ILyricParser.cs delete mode 100644 MediaBrowser.Controller/Lyrics/ILyricProvider.cs create mode 100644 MediaBrowser.Controller/Lyrics/LyricFile.cs delete mode 100644 MediaBrowser.Controller/Lyrics/LyricInfo.cs create mode 100644 MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs create mode 100644 MediaBrowser.Providers/Lyric/ILyricProvider.cs create mode 100644 MediaBrowser.Providers/Lyric/LrcLyricParser.cs delete mode 100644 MediaBrowser.Providers/Lyric/LrcLyricProvider.cs create mode 100644 MediaBrowser.Providers/Lyric/TxtLyricParser.cs delete mode 100644 MediaBrowser.Providers/Lyric/TxtLyricProvider.cs diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 939376dd8..0c6315c66 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -22,6 +22,7 @@ using MediaBrowser.Controller.Lyrics; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Activity; +using MediaBrowser.Providers.Lyric; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -93,6 +94,11 @@ namespace Jellyfin.Server serviceCollection.AddSingleton(typeof(ILyricProvider), type); } + foreach (var type in GetExportTypes()) + { + serviceCollection.AddSingleton(typeof(ILyricParser), type); + } + base.RegisterServices(serviceCollection); } diff --git a/MediaBrowser.Controller/Lyrics/ILyricParser.cs b/MediaBrowser.Controller/Lyrics/ILyricParser.cs new file mode 100644 index 000000000..65a9471a3 --- /dev/null +++ b/MediaBrowser.Controller/Lyrics/ILyricParser.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Providers.Lyric; + +namespace MediaBrowser.Controller.Lyrics; + +/// +/// Interface ILyricParser. +/// +public interface ILyricParser +{ + /// + /// Gets a value indicating the provider name. + /// + string Name { get; } + + /// + /// Gets the priority. + /// + /// The priority. + ResolverPriority Priority { get; } + + /// + /// Parses the raw lyrics into a response. + /// + /// The raw lyrics content. + /// The parsed lyrics or null if invalid. + LyricResponse? ParseLyrics(LyricFile lyrics); +} diff --git a/MediaBrowser.Controller/Lyrics/ILyricProvider.cs b/MediaBrowser.Controller/Lyrics/ILyricProvider.cs deleted file mode 100644 index 2a04c6152..000000000 --- a/MediaBrowser.Controller/Lyrics/ILyricProvider.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Resolvers; - -namespace MediaBrowser.Controller.Lyrics; - -/// -/// Interface ILyricsProvider. -/// -public interface ILyricProvider -{ - /// - /// Gets a value indicating the provider name. - /// - string Name { get; } - - /// - /// Gets the priority. - /// - /// The priority. - ResolverPriority Priority { get; } - - /// - /// Gets the supported media types for this provider. - /// - /// The supported media types. - IReadOnlyCollection SupportedMediaTypes { get; } - - /// - /// Gets the lyrics. - /// - /// The media item. - /// A task representing found lyrics. - Task GetLyrics(BaseItem item); -} diff --git a/MediaBrowser.Controller/Lyrics/LyricFile.cs b/MediaBrowser.Controller/Lyrics/LyricFile.cs new file mode 100644 index 000000000..21096797a --- /dev/null +++ b/MediaBrowser.Controller/Lyrics/LyricFile.cs @@ -0,0 +1,28 @@ +namespace MediaBrowser.Providers.Lyric; + +/// +/// The information for a raw lyrics file before parsing. +/// +public class LyricFile +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The content. + public LyricFile(string name, string content) + { + Name = name; + Content = content; + } + + /// + /// Gets or sets the name of the lyrics file. This must include the file extension. + /// + public string Name { get; set; } + + /// + /// Gets or sets the contents of the file. + /// + public string Content { get; set; } +} diff --git a/MediaBrowser.Controller/Lyrics/LyricInfo.cs b/MediaBrowser.Controller/Lyrics/LyricInfo.cs deleted file mode 100644 index 6ec6df582..000000000 --- a/MediaBrowser.Controller/Lyrics/LyricInfo.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.IO; -using Jellyfin.Extensions; - -namespace MediaBrowser.Controller.Lyrics; - -/// -/// Lyric helper methods. -/// -public static class LyricInfo -{ - /// - /// Gets matching lyric file for a requested item. - /// - /// The lyricProvider interface to use. - /// Path of requested item. - /// Lyric file path if passed lyric provider's supported media type is found; otherwise, null. - public static string? GetLyricFilePath(this ILyricProvider lyricProvider, string itemPath) - { - // Ensure we have a provider - if (lyricProvider is null) - { - return null; - } - - // Ensure the path to the item is not null - string? itemDirectoryPath = Path.GetDirectoryName(itemPath); - if (itemDirectoryPath is null) - { - return null; - } - - // Ensure the directory path exists - if (!Directory.Exists(itemDirectoryPath)) - { - return null; - } - - foreach (var lyricFilePath in Directory.GetFiles(itemDirectoryPath, $"{Path.GetFileNameWithoutExtension(itemPath)}.*")) - { - if (lyricProvider.SupportedMediaTypes.Contains(Path.GetExtension(lyricFilePath.AsSpan())[1..], StringComparison.OrdinalIgnoreCase)) - { - return lyricFilePath; - } - } - - return null; - } -} diff --git a/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs b/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs new file mode 100644 index 000000000..f828ec26b --- /dev/null +++ b/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Resolvers; + +namespace MediaBrowser.Providers.Lyric; + +/// +public class DefaultLyricProvider : ILyricProvider +{ + private static readonly string[] _lyricExtensions = { "lrc", "elrc", "txt", "elrc" }; + + /// + public string Name => "DefaultLyricProvider"; + + /// + public ResolverPriority Priority => ResolverPriority.First; + + /// + public bool HasLyrics(BaseItem item) + { + var path = GetLyricsPath(item); + return path is not null; + } + + /// + public async Task GetLyrics(BaseItem item) + { + var path = GetLyricsPath(item); + if (path is not null) + { + var content = await File.ReadAllTextAsync(path).ConfigureAwait(false); + return new LyricFile(path, content); + } + + return null; + } + + private string? GetLyricsPath(BaseItem item) + { + // Ensure the path to the item is not null + string? itemDirectoryPath = Path.GetDirectoryName(item.Path); + if (itemDirectoryPath is null) + { + return null; + } + + // Ensure the directory path exists + if (!Directory.Exists(itemDirectoryPath)) + { + return null; + } + + foreach (var lyricFilePath in Directory.GetFiles(itemDirectoryPath, $"{Path.GetFileNameWithoutExtension(item.Path)}.*")) + { + if (_lyricExtensions.Contains(Path.GetExtension(lyricFilePath.AsSpan())[1..], StringComparison.OrdinalIgnoreCase)) + { + return lyricFilePath; + } + } + + return null; + } +} diff --git a/MediaBrowser.Providers/Lyric/ILyricProvider.cs b/MediaBrowser.Providers/Lyric/ILyricProvider.cs new file mode 100644 index 000000000..27ceba72b --- /dev/null +++ b/MediaBrowser.Providers/Lyric/ILyricProvider.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Resolvers; + +namespace MediaBrowser.Providers.Lyric; + +/// +/// Interface ILyricsProvider. +/// +public interface ILyricProvider +{ + /// + /// Gets a value indicating the provider name. + /// + string Name { get; } + + /// + /// Gets the priority. + /// + /// The priority. + ResolverPriority Priority { get; } + + /// + /// Checks if an item has lyrics available. + /// + /// The media item. + /// Whether lyrics where found or not. + bool HasLyrics(BaseItem item); + + /// + /// Gets the lyrics. + /// + /// The media item. + /// A task representing found lyrics. + Task GetLyrics(BaseItem item); +} diff --git a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs new file mode 100644 index 000000000..01a0dddf1 --- /dev/null +++ b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using Jellyfin.Extensions; +using LrcParser.Model; +using LrcParser.Parser; +using MediaBrowser.Controller.Lyrics; +using MediaBrowser.Controller.Resolvers; + +namespace MediaBrowser.Providers.Lyric; + +/// +/// LRC Lyric Parser. +/// +public class LrcLyricParser : ILyricParser +{ + private readonly LyricParser _lrcLyricParser; + + private static readonly string[] _supportedMediaTypes = { "lrc", "elrc" }; + private static readonly string[] _acceptedTimeFormats = { "HH:mm:ss", "H:mm:ss", "mm:ss", "m:ss" }; + + /// + /// Initializes a new instance of the class. + /// + public LrcLyricParser() + { + _lrcLyricParser = new LrcParser.Parser.Lrc.LrcParser(); + } + + /// + public string Name => "LrcLyricProvider"; + + /// + /// Gets the priority. + /// + /// The priority. + public ResolverPriority Priority => ResolverPriority.Fourth; + + /// + public LyricResponse? ParseLyrics(LyricFile lyrics) + { + if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan())[1..], 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 sortedLyricData = lyricData.Lyrics.Where(x => x.TimeTags.Count > 0).OrderBy(x => x.TimeTags.First().Value).ToList(); + + // Parse metadata rows + var metaDataRows = lyricData.Lyrics + .Where(x => x.TimeTags.Count == 0) + .Where(x => x.Text.StartsWith('[') && x.Text.EndsWith(']')) + .Select(x => x.Text) + .ToList(); + + var fileMetaData = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (string metaDataRow in metaDataRows) + { + var index = metaDataRow.IndexOf(':', StringComparison.OrdinalIgnoreCase); + if (index == -1) + { + continue; + } + + // Remove square bracket before field name, and after field value + // Example 1: [au: 1hitsong] + // Example 2: [ar: Calabrese] + var metaDataFieldName = GetMetadataFieldName(metaDataRow, index); + var metaDataFieldValue = GetMetadataValue(metaDataRow, index); + + if (string.IsNullOrEmpty(metaDataFieldName) || string.IsNullOrEmpty(metaDataFieldValue)) + { + continue; + } + + fileMetaData[metaDataFieldName] = metaDataFieldValue; + } + + if (sortedLyricData.Count == 0) + { + return null; + } + + List lyricList = new(); + + for (int i = 0; i < sortedLyricData.Count; i++) + { + var timeData = sortedLyricData[i].TimeTags.First().Value; + if (timeData is null) + { + continue; + } + + long ticks = TimeSpan.FromMilliseconds(timeData.Value).Ticks; + lyricList.Add(new LyricLine(sortedLyricData[i].Text, ticks)); + } + + if (fileMetaData.Count != 0) + { + // Map metaData values from LRC file to LyricMetadata properties + LyricMetadata lyricMetadata = MapMetadataValues(fileMetaData); + + return new LyricResponse { Metadata = lyricMetadata, Lyrics = lyricList }; + } + + return new LyricResponse { Lyrics = lyricList }; + } + + /// + /// Converts metadata from an LRC file to LyricMetadata properties. + /// + /// The metadata from the LRC file. + /// A lyricMetadata object with mapped property data. + private static LyricMetadata MapMetadataValues(IDictionary metaData) + { + LyricMetadata lyricMetadata = new(); + + if (metaData.TryGetValue("ar", out var artist) && !string.IsNullOrEmpty(artist)) + { + lyricMetadata.Artist = artist; + } + + if (metaData.TryGetValue("al", out var album) && !string.IsNullOrEmpty(album)) + { + lyricMetadata.Album = album; + } + + if (metaData.TryGetValue("ti", out var title) && !string.IsNullOrEmpty(title)) + { + lyricMetadata.Title = title; + } + + if (metaData.TryGetValue("au", out var author) && !string.IsNullOrEmpty(author)) + { + lyricMetadata.Author = author; + } + + if (metaData.TryGetValue("length", out var length) && !string.IsNullOrEmpty(length)) + { + if (DateTime.TryParseExact(length, _acceptedTimeFormats, null, DateTimeStyles.None, out var value)) + { + lyricMetadata.Length = value.TimeOfDay.Ticks; + } + } + + if (metaData.TryGetValue("by", out var by) && !string.IsNullOrEmpty(by)) + { + lyricMetadata.By = by; + } + + if (metaData.TryGetValue("offset", out var offset) && !string.IsNullOrEmpty(offset)) + { + if (int.TryParse(offset, out var value)) + { + lyricMetadata.Offset = TimeSpan.FromMilliseconds(value).Ticks; + } + } + + if (metaData.TryGetValue("re", out var creator) && !string.IsNullOrEmpty(creator)) + { + lyricMetadata.Creator = creator; + } + + if (metaData.TryGetValue("ve", out var version) && !string.IsNullOrEmpty(version)) + { + lyricMetadata.Version = version; + } + + return lyricMetadata; + } + + private static string GetMetadataFieldName(string metaDataRow, int index) + { + var metadataFieldName = metaDataRow.AsSpan(1, index - 1).Trim(); + return metadataFieldName.IsEmpty ? string.Empty : metadataFieldName.ToString(); + } + + private static string GetMetadataValue(string metaDataRow, int index) + { + var metadataValue = metaDataRow.AsSpan(index + 1, metaDataRow.Length - index - 2).Trim(); + return metadataValue.IsEmpty ? string.Empty : metadataValue.ToString(); + } +} diff --git a/MediaBrowser.Providers/Lyric/LrcLyricProvider.cs b/MediaBrowser.Providers/Lyric/LrcLyricProvider.cs deleted file mode 100644 index 7b108921b..000000000 --- a/MediaBrowser.Providers/Lyric/LrcLyricProvider.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using LrcParser.Model; -using LrcParser.Parser; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Lyrics; -using MediaBrowser.Controller.Resolvers; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.Lyric; - -/// -/// LRC Lyric Provider. -/// -public class LrcLyricProvider : ILyricProvider -{ - private readonly ILogger _logger; - - private readonly LyricParser _lrcLyricParser; - - private static readonly string[] _acceptedTimeFormats = { "HH:mm:ss", "H:mm:ss", "mm:ss", "m:ss" }; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - public LrcLyricProvider(ILogger logger) - { - _logger = logger; - _lrcLyricParser = new LrcParser.Parser.Lrc.LrcParser(); - } - - /// - public string Name => "LrcLyricProvider"; - - /// - /// Gets the priority. - /// - /// The priority. - public ResolverPriority Priority => ResolverPriority.First; - - /// - public IReadOnlyCollection SupportedMediaTypes { get; } = new[] { "lrc", "elrc" }; - - /// - /// Opens lyric file for the requested item, and processes it for API return. - /// - /// The item to to process. - /// If provider can determine lyrics, returns a with or without metadata; otherwise, null. - public async Task GetLyrics(BaseItem item) - { - string? lyricFilePath = this.GetLyricFilePath(item.Path); - - if (string.IsNullOrEmpty(lyricFilePath)) - { - return null; - } - - var fileMetaData = new Dictionary(StringComparer.OrdinalIgnoreCase); - string lrcFileContent = await File.ReadAllTextAsync(lyricFilePath).ConfigureAwait(false); - - Song lyricData; - - try - { - lyricData = _lrcLyricParser.Decode(lrcFileContent); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error parsing lyric file {LyricFilePath} from {Provider}", lyricFilePath, Name); - return null; - } - - List sortedLyricData = lyricData.Lyrics.Where(x => x.TimeTags.Count > 0).OrderBy(x => x.TimeTags.First().Value).ToList(); - - // Parse metadata rows - var metaDataRows = lyricData.Lyrics - .Where(x => x.TimeTags.Count == 0) - .Where(x => x.Text.StartsWith('[') && x.Text.EndsWith(']')) - .Select(x => x.Text) - .ToList(); - - foreach (string metaDataRow in metaDataRows) - { - var index = metaDataRow.IndexOf(':', StringComparison.OrdinalIgnoreCase); - if (index == -1) - { - continue; - } - - // Remove square bracket before field name, and after field value - // Example 1: [au: 1hitsong] - // Example 2: [ar: Calabrese] - var metaDataFieldName = GetMetadataFieldName(metaDataRow, index); - var metaDataFieldValue = GetMetadataValue(metaDataRow, index); - - if (string.IsNullOrEmpty(metaDataFieldName) || string.IsNullOrEmpty(metaDataFieldValue)) - { - continue; - } - - fileMetaData[metaDataFieldName] = metaDataFieldValue; - } - - if (sortedLyricData.Count == 0) - { - return null; - } - - List lyricList = new(); - - for (int i = 0; i < sortedLyricData.Count; i++) - { - var timeData = sortedLyricData[i].TimeTags.First().Value; - if (timeData is null) - { - continue; - } - - long ticks = TimeSpan.FromMilliseconds(timeData.Value).Ticks; - lyricList.Add(new LyricLine(sortedLyricData[i].Text, ticks)); - } - - if (fileMetaData.Count != 0) - { - // Map metaData values from LRC file to LyricMetadata properties - LyricMetadata lyricMetadata = MapMetadataValues(fileMetaData); - - return new LyricResponse - { - Metadata = lyricMetadata, - Lyrics = lyricList - }; - } - - return new LyricResponse - { - Lyrics = lyricList - }; - } - - /// - /// Converts metadata from an LRC file to LyricMetadata properties. - /// - /// The metadata from the LRC file. - /// A lyricMetadata object with mapped property data. - private static LyricMetadata MapMetadataValues(IDictionary metaData) - { - LyricMetadata lyricMetadata = new(); - - if (metaData.TryGetValue("ar", out var artist) && !string.IsNullOrEmpty(artist)) - { - lyricMetadata.Artist = artist; - } - - if (metaData.TryGetValue("al", out var album) && !string.IsNullOrEmpty(album)) - { - lyricMetadata.Album = album; - } - - if (metaData.TryGetValue("ti", out var title) && !string.IsNullOrEmpty(title)) - { - lyricMetadata.Title = title; - } - - if (metaData.TryGetValue("au", out var author) && !string.IsNullOrEmpty(author)) - { - lyricMetadata.Author = author; - } - - if (metaData.TryGetValue("length", out var length) && !string.IsNullOrEmpty(length)) - { - if (DateTime.TryParseExact(length, _acceptedTimeFormats, null, DateTimeStyles.None, out var value)) - { - lyricMetadata.Length = value.TimeOfDay.Ticks; - } - } - - if (metaData.TryGetValue("by", out var by) && !string.IsNullOrEmpty(by)) - { - lyricMetadata.By = by; - } - - if (metaData.TryGetValue("offset", out var offset) && !string.IsNullOrEmpty(offset)) - { - if (int.TryParse(offset, out var value)) - { - lyricMetadata.Offset = TimeSpan.FromMilliseconds(value).Ticks; - } - } - - if (metaData.TryGetValue("re", out var creator) && !string.IsNullOrEmpty(creator)) - { - lyricMetadata.Creator = creator; - } - - if (metaData.TryGetValue("ve", out var version) && !string.IsNullOrEmpty(version)) - { - lyricMetadata.Version = version; - } - - return lyricMetadata; - } - - private static string GetMetadataFieldName(string metaDataRow, int index) - { - var metadataFieldName = metaDataRow.AsSpan(1, index - 1).Trim(); - return metadataFieldName.IsEmpty ? string.Empty : metadataFieldName.ToString(); - } - - private static string GetMetadataValue(string metaDataRow, int index) - { - var metadataValue = metaDataRow.AsSpan(index + 1, metaDataRow.Length - index - 2).Trim(); - return metadataValue.IsEmpty ? string.Empty : metadataValue.ToString(); - } -} diff --git a/MediaBrowser.Providers/Lyric/LyricManager.cs b/MediaBrowser.Providers/Lyric/LyricManager.cs index f9547e0f0..6da811927 100644 --- a/MediaBrowser.Providers/Lyric/LyricManager.cs +++ b/MediaBrowser.Providers/Lyric/LyricManager.cs @@ -12,14 +12,17 @@ namespace MediaBrowser.Providers.Lyric; public class LyricManager : ILyricManager { private readonly ILyricProvider[] _lyricProviders; + private readonly ILyricParser[] _lyricParsers; /// /// Initializes a new instance of the class. /// /// All found lyricProviders. - public LyricManager(IEnumerable lyricProviders) + /// All found lyricParsers. + public LyricManager(IEnumerable lyricProviders, IEnumerable lyricParsers) { _lyricProviders = lyricProviders.OrderBy(i => i.Priority).ToArray(); + _lyricParsers = lyricParsers.OrderBy(i => i.Priority).ToArray(); } /// @@ -27,10 +30,19 @@ public class LyricManager : ILyricManager { foreach (ILyricProvider provider in _lyricProviders) { - var results = await provider.GetLyrics(item).ConfigureAwait(false); - if (results is not null) + var lyrics = await provider.GetLyrics(item).ConfigureAwait(false); + if (lyrics is null) { - return results; + continue; + } + + foreach (ILyricParser parser in _lyricParsers) + { + var result = parser.ParseLyrics(lyrics); + if (result is not null) + { + return result; + } } } @@ -47,7 +59,7 @@ public class LyricManager : ILyricManager continue; } - if (provider.GetLyricFilePath(item.Path) is not null) + if (provider.HasLyrics(item)) { return true; } diff --git a/MediaBrowser.Providers/Lyric/TxtLyricParser.cs b/MediaBrowser.Providers/Lyric/TxtLyricParser.cs new file mode 100644 index 000000000..2ed0a6d8a --- /dev/null +++ b/MediaBrowser.Providers/Lyric/TxtLyricParser.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Lyrics; +using MediaBrowser.Controller.Resolvers; + +namespace MediaBrowser.Providers.Lyric; + +/// +/// TXT Lyric Parser. +/// +public class TxtLyricParser : ILyricParser +{ + private static readonly string[] _supportedMediaTypes = { "lrc", "elrc", "txt" }; + + /// + public string Name => "TxtLyricProvider"; + + /// + /// Gets the priority. + /// + /// The priority. + public ResolverPriority Priority => ResolverPriority.Fifth; + + /// + public LyricResponse? ParseLyrics(LyricFile lyrics) + { + if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan())[1..], StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + string[] lyricTextLines = lyrics.Content.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + + if (lyricTextLines.Length == 0) + { + return null; + } + + LyricLine[] lyricList = new LyricLine[lyricTextLines.Length]; + + for (int lyricLineIndex = 0; lyricLineIndex < lyricTextLines.Length; lyricLineIndex++) + { + lyricList[lyricLineIndex] = new LyricLine(lyricTextLines[lyricLineIndex]); + } + + return new LyricResponse { Lyrics = lyricList }; + } +} diff --git a/MediaBrowser.Providers/Lyric/TxtLyricProvider.cs b/MediaBrowser.Providers/Lyric/TxtLyricProvider.cs deleted file mode 100644 index a9099d192..000000000 --- a/MediaBrowser.Providers/Lyric/TxtLyricProvider.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Lyrics; -using MediaBrowser.Controller.Resolvers; - -namespace MediaBrowser.Providers.Lyric; - -/// -/// TXT Lyric Provider. -/// -public class TxtLyricProvider : ILyricProvider -{ - /// - public string Name => "TxtLyricProvider"; - - /// - /// Gets the priority. - /// - /// The priority. - public ResolverPriority Priority => ResolverPriority.Second; - - /// - public IReadOnlyCollection SupportedMediaTypes { get; } = new[] { "lrc", "elrc", "txt" }; - - /// - /// Opens lyric file for the requested item, and processes it for API return. - /// - /// The item to to process. - /// If provider can determine lyrics, returns a ; otherwise, null. - public async Task GetLyrics(BaseItem item) - { - string? lyricFilePath = this.GetLyricFilePath(item.Path); - - if (string.IsNullOrEmpty(lyricFilePath)) - { - return null; - } - - string[] lyricTextLines = await File.ReadAllLinesAsync(lyricFilePath).ConfigureAwait(false); - - if (lyricTextLines.Length == 0) - { - return null; - } - - LyricLine[] lyricList = new LyricLine[lyricTextLines.Length]; - - for (int lyricLineIndex = 0; lyricLineIndex < lyricTextLines.Length; lyricLineIndex++) - { - lyricList[lyricLineIndex] = new LyricLine(lyricTextLines[lyricLineIndex]); - } - - return new LyricResponse - { - Lyrics = lyricList - }; - } -} -- cgit v1.2.3 From 1ed5f0a624abeebef16a960ed7a52942bce47502 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 24 Jun 2023 09:25:25 +0200 Subject: Move line break characters to static readonly string array in TxtLyricParser --- MediaBrowser.Providers/Lyric/TxtLyricParser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Lyric/TxtLyricParser.cs b/MediaBrowser.Providers/Lyric/TxtLyricParser.cs index 2ed0a6d8a..7e029ee42 100644 --- a/MediaBrowser.Providers/Lyric/TxtLyricParser.cs +++ b/MediaBrowser.Providers/Lyric/TxtLyricParser.cs @@ -12,6 +12,7 @@ namespace MediaBrowser.Providers.Lyric; public class TxtLyricParser : ILyricParser { private static readonly string[] _supportedMediaTypes = { "lrc", "elrc", "txt" }; + private static readonly string[] _lineBreakCharacters = { "\r\n", "\r", "\n" }; /// public string Name => "TxtLyricProvider"; @@ -30,7 +31,7 @@ public class TxtLyricParser : ILyricParser return null; } - string[] lyricTextLines = lyrics.Content.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + string[] lyricTextLines = lyrics.Content.Split(_lineBreakCharacters, StringSplitOptions.None); if (lyricTextLines.Length == 0) { -- cgit v1.2.3 From 6be45f73bc60d7b4646d7967d29634eea0b7e422 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Thu, 29 Jun 2023 21:16:29 +0200 Subject: Simplify file extension checks in lyrics parsers and provider --- MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs | 4 ++-- MediaBrowser.Providers/Lyric/LrcLyricParser.cs | 4 ++-- MediaBrowser.Providers/Lyric/TxtLyricParser.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs b/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs index f828ec26b..ae6350dd7 100644 --- a/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs +++ b/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Providers.Lyric; /// public class DefaultLyricProvider : ILyricProvider { - private static readonly string[] _lyricExtensions = { "lrc", "elrc", "txt", "elrc" }; + private static readonly string[] _lyricExtensions = { ".lrc", ".elrc", ".txt" }; /// public string Name => "DefaultLyricProvider"; @@ -55,7 +55,7 @@ public class DefaultLyricProvider : ILyricProvider foreach (var lyricFilePath in Directory.GetFiles(itemDirectoryPath, $"{Path.GetFileNameWithoutExtension(item.Path)}.*")) { - if (_lyricExtensions.Contains(Path.GetExtension(lyricFilePath.AsSpan())[1..], StringComparison.OrdinalIgnoreCase)) + if (_lyricExtensions.Contains(Path.GetExtension(lyricFilePath.AsSpan()), StringComparison.OrdinalIgnoreCase)) { return lyricFilePath; } diff --git a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs index 01a0dddf1..7f1ecd743 100644 --- a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs +++ b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs @@ -18,7 +18,7 @@ public class LrcLyricParser : ILyricParser { private readonly LyricParser _lrcLyricParser; - private static readonly string[] _supportedMediaTypes = { "lrc", "elrc" }; + private static readonly string[] _supportedMediaTypes = { ".lrc", ".elrc" }; private static readonly string[] _acceptedTimeFormats = { "HH:mm:ss", "H:mm:ss", "mm:ss", "m:ss" }; /// @@ -41,7 +41,7 @@ public class LrcLyricParser : ILyricParser /// public LyricResponse? ParseLyrics(LyricFile lyrics) { - if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan())[1..], StringComparison.OrdinalIgnoreCase)) + if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan()), StringComparison.OrdinalIgnoreCase)) { return null; } diff --git a/MediaBrowser.Providers/Lyric/TxtLyricParser.cs b/MediaBrowser.Providers/Lyric/TxtLyricParser.cs index 7e029ee42..ed4f2cb7a 100644 --- a/MediaBrowser.Providers/Lyric/TxtLyricParser.cs +++ b/MediaBrowser.Providers/Lyric/TxtLyricParser.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Providers.Lyric; /// public class TxtLyricParser : ILyricParser { - private static readonly string[] _supportedMediaTypes = { "lrc", "elrc", "txt" }; + private static readonly string[] _supportedMediaTypes = { ".lrc", ".elrc", ".txt" }; private static readonly string[] _lineBreakCharacters = { "\r\n", "\r", "\n" }; /// @@ -26,7 +26,7 @@ public class TxtLyricParser : ILyricParser /// public LyricResponse? ParseLyrics(LyricFile lyrics) { - if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan())[1..], StringComparison.OrdinalIgnoreCase)) + if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan()), StringComparison.OrdinalIgnoreCase)) { return null; } -- cgit v1.2.3 From 0ae4d175a184b86ec0267b1bdb89c8c59d40c325 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 1 Jul 2023 11:16:21 +0200 Subject: Check for empty string in DefaultLyricProvider --- MediaBrowser.Controller/Lyrics/LyricFile.cs | 2 +- MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs | 5 ++++- MediaBrowser.Providers/Lyric/TxtLyricParser.cs | 6 ------ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Controller/Lyrics/LyricFile.cs b/MediaBrowser.Controller/Lyrics/LyricFile.cs index 21096797a..ede89403c 100644 --- a/MediaBrowser.Controller/Lyrics/LyricFile.cs +++ b/MediaBrowser.Controller/Lyrics/LyricFile.cs @@ -9,7 +9,7 @@ public class LyricFile /// Initializes a new instance of the class. /// /// The name. - /// The content. + /// The content, must not be empty. public LyricFile(string name, string content) { Name = name; diff --git a/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs b/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs index ae6350dd7..a26f2babb 100644 --- a/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs +++ b/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs @@ -32,7 +32,10 @@ public class DefaultLyricProvider : ILyricProvider if (path is not null) { var content = await File.ReadAllTextAsync(path).ConfigureAwait(false); - return new LyricFile(path, content); + if (content.Length != 0) + { + return new LyricFile(path, content); + } } return null; diff --git a/MediaBrowser.Providers/Lyric/TxtLyricParser.cs b/MediaBrowser.Providers/Lyric/TxtLyricParser.cs index ed4f2cb7a..706f13dbc 100644 --- a/MediaBrowser.Providers/Lyric/TxtLyricParser.cs +++ b/MediaBrowser.Providers/Lyric/TxtLyricParser.cs @@ -32,12 +32,6 @@ public class TxtLyricParser : ILyricParser } string[] lyricTextLines = lyrics.Content.Split(_lineBreakCharacters, StringSplitOptions.None); - - if (lyricTextLines.Length == 0) - { - return null; - } - LyricLine[] lyricList = new LyricLine[lyricTextLines.Length]; for (int lyricLineIndex = 0; lyricLineIndex < lyricTextLines.Length; lyricLineIndex++) -- cgit v1.2.3 From 0af5373f6d9050e9bb5a7cb4ed19742a8893d074 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 1 Jul 2023 14:07:59 +0200 Subject: Use string.IsNullOrEmpty --- MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs b/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs index a26f2babb..ab09f278a 100644 --- a/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs +++ b/MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs @@ -32,7 +32,7 @@ public class DefaultLyricProvider : ILyricProvider if (path is not null) { var content = await File.ReadAllTextAsync(path).ConfigureAwait(false); - if (content.Length != 0) + if (!string.IsNullOrEmpty(content)) { return new LyricFile(path, content); } -- cgit v1.2.3