From 066db8ac7fcece0ab3420b6b6c03e420d22c7306 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 19 Jul 2022 21:28:04 +0200 Subject: Migrate NetworkManager and Tests to native .NET IP objects --- MediaBrowser.Controller/IServerApplicationHost.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 11afdc4ae..45ac5c3a8 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -4,7 +4,6 @@ using System.Net; using MediaBrowser.Common; -using MediaBrowser.Common.Net; using MediaBrowser.Model.System; using Microsoft.AspNetCore.Http; @@ -75,10 +74,10 @@ namespace MediaBrowser.Controller /// /// Gets an URL that can be used to access the API over LAN. /// - /// An optional hostname to use. + /// An optional IP address to use. /// A value indicating whether to allow HTTPS. /// The API URL. - string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true); + string GetApiUrlForLocalAccess(IPAddress ipAddress = null, bool allowHttps = true); /// /// Gets a local (LAN) URL that can be used to access the API. -- cgit v1.2.3 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 (limited to 'MediaBrowser.Controller') 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 b5f0760db8dba96e9edd67d4b9c914cf25c3d26a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 22 May 2023 22:48:09 +0200 Subject: Use RegexGenerator where possible --- Emby.Dlna/PlayTo/DlnaHttpClient.cs | 10 +++---- Emby.Naming/Audio/AlbumParser.cs | 13 +++++---- Emby.Naming/TV/SeriesResolver.cs | 7 ++--- Emby.Naming/Video/VideoListResolver.cs | 12 ++++++--- .../Library/Resolvers/Movies/MovieResolver.cs | 10 +++---- .../HdHomerun/LegacyHdHomerunChannelCommands.cs | 7 +++-- .../LiveTv/TunerHosts/M3uParser.cs | 9 ++++--- .../Users/UserManager.cs | 20 +++++++------- MediaBrowser.Common/Extensions/BaseExtensions.cs | 13 +++++---- .../MediaEncoding/EncodingHelper.cs | 7 +++-- .../Encoder/EncoderValidator.cs | 15 ++++++----- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 11 +++++--- MediaBrowser.MediaEncoding/Subtitles/AssWriter.cs | 7 +++-- MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs | 7 +++-- MediaBrowser.MediaEncoding/Subtitles/SsaWriter.cs | 7 +++-- MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs | 7 +++-- MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs | 7 +++-- MediaBrowser.Model/Dlna/SearchCriteria.cs | 31 +++++----------------- .../MediaInfo/AudioFileProber.cs | 7 +++-- MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs | 9 ++++--- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 15 ++++++----- jellyfin.ruleset | 2 ++ src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 3 +-- src/Jellyfin.Extensions/StringExtensions.cs | 10 ++++--- .../Manager/ItemImageProviderTests.cs | 7 +++-- 25 files changed, 137 insertions(+), 116 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Dlna/PlayTo/DlnaHttpClient.cs b/Emby.Dlna/PlayTo/DlnaHttpClient.cs index 8b983e9e3..8454c1afd 100644 --- a/Emby.Dlna/PlayTo/DlnaHttpClient.cs +++ b/Emby.Dlna/PlayTo/DlnaHttpClient.cs @@ -31,6 +31,9 @@ namespace Emby.Dlna.PlayTo _httpClientFactory = httpClientFactory; } + [GeneratedRegex("(&(?![a-z]*;))")] + private static partial Regex EscapeAmpersandRegex(); + private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) { // If it's already a complete url, don't stick anything onto the front of it @@ -128,12 +131,5 @@ namespace Emby.Dlna.PlayTo // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); } - - /// - /// Compile-time generated regular expression for escaping ampersands. - /// - /// Compiled regular expression. - [GeneratedRegex("(&(?![a-z]*;))")] - private static partial Regex EscapeAmpersandRegex(); } } diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs index 86a564153..73424a134 100644 --- a/Emby.Naming/Audio/AlbumParser.cs +++ b/Emby.Naming/Audio/AlbumParser.cs @@ -10,7 +10,7 @@ namespace Emby.Naming.Audio /// /// Helper class to determine if Album is multipart. /// - public class AlbumParser + public partial class AlbumParser { private readonly NamingOptions _options; @@ -23,6 +23,9 @@ namespace Emby.Naming.Audio _options = options; } + [GeneratedRegex(@"([-\.\(\)]|\s+)")] + private static partial Regex CleanRegex(); + /// /// Function that determines if album is multipart. /// @@ -42,13 +45,9 @@ namespace Emby.Naming.Audio // Normalize // Remove whitespace - filename = filename.Replace('-', ' '); - filename = filename.Replace('.', ' '); - filename = filename.Replace('(', ' '); - filename = filename.Replace(')', ' '); - filename = Regex.Replace(filename, @"\s+", " "); + filename = CleanRegex().Replace(filename, " "); - ReadOnlySpan trimmedFilename = filename.TrimStart(); + ReadOnlySpan trimmedFilename = filename.AsSpan().TrimStart(); foreach (var prefix in _options.AlbumStackingPrefixes) { diff --git a/Emby.Naming/TV/SeriesResolver.cs b/Emby.Naming/TV/SeriesResolver.cs index 307a84096..d8fa41743 100644 --- a/Emby.Naming/TV/SeriesResolver.cs +++ b/Emby.Naming/TV/SeriesResolver.cs @@ -7,14 +7,15 @@ namespace Emby.Naming.TV /// /// Used to resolve information about series from path. /// - public static class SeriesResolver + public static partial class SeriesResolver { /// /// Regex that matches strings of at least 2 characters separated by a dot or underscore. /// Used for removing separators between words, i.e turns "The_show" into "The show" while /// preserving namings like "S.H.O.W". /// - private static readonly Regex _seriesNameRegex = new Regex(@"((?[^\._]{2,})[\._]*)|([\._](?[^\._]{2,}))", RegexOptions.Compiled); + [GeneratedRegex(@"((?[^\._]{2,})[\._]*)|([\._](?[^\._]{2,}))")] + private static partial Regex SeriesNameRegex(); /// /// Resolve information about series from path. @@ -37,7 +38,7 @@ namespace Emby.Naming.TV if (!string.IsNullOrEmpty(seriesName)) { - seriesName = _seriesNameRegex.Replace(seriesName, "${a} ${b}").Trim(); + seriesName = SeriesNameRegex().Replace(seriesName, "${a} ${b}").Trim(); } return new SeriesInfo(path) diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 6209cd46f..51f29cf08 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -12,9 +12,13 @@ namespace Emby.Naming.Video /// /// Resolves alternative versions and extras from list of video files. /// - public static class VideoListResolver + public static partial class VideoListResolver { - private static readonly Regex _resolutionRegex = new Regex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase | RegexOptions.Compiled); + [GeneratedRegex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase)] + private static partial Regex ResolutionRegex(); + + [GeneratedRegex(@"^\[([^]]*)\]")] + private static partial Regex CheckMultiVersionRegex(); /// /// Resolves alternative versions and extras from list of video files. @@ -131,7 +135,7 @@ namespace Emby.Naming.Video if (videos.Count > 1) { - var groups = videos.GroupBy(x => _resolutionRegex.IsMatch(x.Files[0].FileNameWithoutExtension)).ToList(); + var groups = videos.GroupBy(x => ResolutionRegex().IsMatch(x.Files[0].FileNameWithoutExtension)).ToList(); videos.Clear(); foreach (var group in groups) { @@ -201,7 +205,7 @@ namespace Emby.Naming.Video // The CleanStringParser should have removed common keywords etc. return testFilename.IsEmpty || testFilename[0] == '-' - || Regex.IsMatch(testFilename, @"^\[([^]]*)\]", RegexOptions.Compiled); + || CheckMultiVersionRegex().IsMatch(testFilename); } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index ea980b992..0b65bf921 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies /// /// Class MovieResolver. /// - public class MovieResolver : BaseVideoResolver