Bläddra i källkod

Create ILyricsProvider

1hitsong 2 år sedan
förälder
incheckning
9d5cf67dfe

+ 5 - 0
Emby.Server.Implementations/Dto/DtoService.cs

@@ -7,6 +7,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
 using Jellyfin.Extensions;
@@ -139,6 +140,10 @@ namespace Emby.Server.Implementations.Dto
             {
                 LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
             }
+            else if (item is Audio)
+            {
+                dto.HasLocalLyricsFile = ItemHelper.HasLyricFile(item.Path);
+            }
 
             if (item is IItemByName itemByName
                 && options.ContainsField(ItemFields.ItemCounts))

+ 68 - 49
Jellyfin.Api/Helpers/ItemHelper.cs

@@ -23,79 +23,98 @@ namespace Jellyfin.Api.Helpers
         /// <returns>Collection of Lyrics.</returns>
         internal static object? GetLyricData(BaseItem item)
         {
-            List<Lyrics> lyricsList = new List<Lyrics>();
+            List<ILyricsProvider> providerList = new List<ILyricsProvider>();
 
-            string lrcFilePath = @Path.ChangeExtension(item.Path, "lrc");
+            // Find all classes that implement ILyricsProvider Interface
+            var foundLyricProviders = System.Reflection.Assembly.GetExecutingAssembly()
+                .GetTypes()
+                .Where(type => typeof(ILyricsProvider).IsAssignableFrom(type) && !type.IsInterface);
 
-            // LRC File not found, fallback to TXT file
-            if (!System.IO.File.Exists(lrcFilePath))
+            if (!foundLyricProviders.Any())
             {
-                string txtFilePath = @Path.ChangeExtension(item.Path, "txt");
-                if (!System.IO.File.Exists(txtFilePath))
-                {
-                    return null;
-                }
+                return null;
+            }
 
-                var lyricTextData = System.IO.File.ReadAllText(txtFilePath);
-                string[] lyricTextLines = lyricTextData.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
+            foreach (var provider in foundLyricProviders)
+            {
+                providerList.Add((ILyricsProvider)Activator.CreateInstance(provider));
+            }
 
-                foreach (var lyricLine in lyricTextLines)
+            foreach (ILyricsProvider provider in providerList)
+            {
+                provider.Process(item);
+                if (provider.HasData)
                 {
-                    lyricsList.Add(new Lyrics { Text = lyricLine });
+                    return provider.Data;
                 }
-
-                return new { lyrics = lyricsList };
             }
 
-            // Process LRC File
-            Song lyricData;
-            List<Lyric> sortedLyricData = new List<Lyric>();
-            var metaData = new ExpandoObject() as IDictionary<string, object>;
-            string lrcFileContent = System.IO.File.ReadAllText(lrcFilePath);
-
-            try
-            {
-                LyricParser lrcLyricParser = new LrcParser.Parser.Lrc.LrcParser();
-                lyricData = lrcLyricParser.Decode(lrcFileContent);
-                var _metaData = lyricData.Lyrics
-                    .Where(x => x.TimeTags.Count == 0)
-                    .Where(x => x.Text.StartsWith("[", StringComparison.Ordinal) && x.Text.EndsWith("]", StringComparison.Ordinal))
-                    .Select(x => x.Text)
-                    .ToList();
-
-                foreach (string dataRow in _metaData)
-                {
-                    var data = dataRow.Split(":");
+            return null;
+        }
 
-                    string newPropertyName = data[0].Replace("[", string.Empty, StringComparison.Ordinal);
-                    string newPropertyValue = data[1].Replace("]", string.Empty, StringComparison.Ordinal);
+        /// <summary>
+        /// Checks if requested item has a matching lyric file.
+        /// </summary>
+        /// <param name="itemPath">Path of requested item.</param>
+        /// <returns>True if item has a matching lyrics file.</returns>
+        public static string? GetLyricFilePath(string itemPath)
+        {
+            List<string> supportedLyricFileExtensions = new List<string>();
 
-                    metaData.Add(newPropertyName, newPropertyValue);
-                }
+            // Find all classes that implement ILyricsProvider Interface
+            var foundLyricProviders = System.Reflection.Assembly.GetExecutingAssembly()
+                .GetTypes()
+                .Where(type => typeof(ILyricsProvider).IsAssignableFrom(type) && !type.IsInterface);
 
-                sortedLyricData = lyricData.Lyrics.Where(x => x.TimeTags.Count > 0).OrderBy(x => x.TimeTags.ToArray()[0].Value).ToList();
-            }
-            catch
+            if (!foundLyricProviders.Any())
             {
                 return null;
             }
 
-            if (lyricData == null)
+            // Iterate over all found lyric providers
+            foreach (var provider in foundLyricProviders)
             {
-                return null;
+                var foundProvider = (ILyricsProvider)Activator.CreateInstance(provider);
+                if (foundProvider?.FileExtensions is null)
+                {
+                    continue;
+                }
+
+                if (foundProvider.FileExtensions.Any())
+                {
+                    // Gather distinct list of handled file extentions
+                    foreach (string lyricFileExtension in foundProvider.FileExtensions)
+                    {
+                        if (!supportedLyricFileExtensions.Contains(lyricFileExtension))
+                        {
+                            supportedLyricFileExtensions.Add(lyricFileExtension);
+                        }
+                    }
+                }
             }
 
-            for (int i = 0; i < sortedLyricData.Count; i++)
+            foreach (string lyricFileExtension in supportedLyricFileExtensions)
             {
-                if (sortedLyricData[i].TimeTags.Count > 0)
+                string lyricFilePath = @Path.ChangeExtension(itemPath, lyricFileExtension);
+                if (System.IO.File.Exists(lyricFilePath))
                 {
-                    var timeData = sortedLyricData[i].TimeTags.ToArray()[0].Value;
-                    double ticks = Convert.ToDouble(timeData, new NumberFormatInfo()) * 10000;
-                    lyricsList.Add(new Lyrics { Start = Math.Ceiling(ticks), Text = sortedLyricData[i].Text });
+                    return lyricFilePath;
                 }
             }
 
-            return new { MetaData = metaData, lyrics = lyricsList };
+            return null;
+        }
+
+
+        /// <summary>
+        /// Checks if requested item has a matching local lyric file.
+        /// </summary>
+        /// <param name="itemPath">Path of requested item.</param>
+        /// <returns>True if item has a matching lyrics file; otherwise false.</returns>
+        public static bool HasLyricFile(string itemPath)
+        {
+            string? lyricFilePath = GetLyricFilePath(itemPath);
+            return !string.IsNullOrEmpty(lyricFilePath);
         }
     }
 }

+ 34 - 0
Jellyfin.Api/Models/UserDtos/ILyricsProvider.cs

@@ -0,0 +1,34 @@
+using System.Collections.ObjectModel;
+using MediaBrowser.Controller.Entities;
+
+namespace Jellyfin.Api.Models.UserDtos
+{
+    /// <summary>
+    /// Interface ILyricsProvider.
+    /// </summary>
+    public interface ILyricsProvider
+    {
+        /// <summary>
+        /// Gets a value indicating the File Extenstions this provider works with.
+        /// </summary>
+        public Collection<string>? FileExtensions { get; }
+
+        /// <summary>
+        /// Gets a value indicating whether Process() generated data.
+        /// </summary>
+        /// <returns><c>true</c> if data generated; otherwise, <c>false</c>.</returns>
+        bool HasData { get; }
+
+        /// <summary>
+        /// Gets Data object generated by Process() method.
+        /// </summary>
+        /// <returns><c>Object</c> with data if no error occured; otherwise, <c>null</c>.</returns>
+        object? Data { get; }
+
+        /// <summary>
+        /// Opens lyric file for [the specified item], and processes it for API return.
+        /// </summary>
+        /// <param name="item">The item to to process.</param>
+        void Process(BaseItem item);
+    }
+}

+ 117 - 0
Jellyfin.Api/Models/UserDtos/LrcLyricsProvider.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Dynamic;
+using System.Globalization;
+using System.Linq;
+using LrcParser.Model;
+using LrcParser.Parser;
+using MediaBrowser.Controller.Entities;
+
+namespace Jellyfin.Api.Models.UserDtos
+{
+    /// <summary>
+    /// LRC File Lyric Provider.
+    /// </summary>
+    public class LrcLyricsProvider : ILyricsProvider
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="LrcLyricsProvider"/> class.
+        /// </summary>
+        public LrcLyricsProvider()
+        {
+            FileExtensions = new Collection<string>
+            {
+                "lrc"
+            };
+        }
+
+        /// <summary>
+        /// Gets a value indicating the File Extenstions this provider works with.
+        /// </summary>
+        public Collection<string>? FileExtensions { get; }
+
+        /// <summary>
+        /// Gets or Sets a value indicating whether Process() generated data.
+        /// </summary>
+        /// <returns><c>true</c> if data generated; otherwise, <c>false</c>.</returns>
+        public bool HasData { get; set; }
+
+        /// <summary>
+        /// Gets or Sets Data object generated by Process() method.
+        /// </summary>
+        /// <returns><c>Object</c> with data if no error occured; otherwise, <c>null</c>.</returns>
+        public object? Data { get; set; }
+
+        /// <summary>
+        /// Opens lyric file for [the specified item], and processes it for API return.
+        /// </summary>
+        /// <param name="item">The item to to process.</param>
+        public void Process(BaseItem item)
+        {
+            string? lyricFilePath = Helpers.ItemHelper.GetLyricFilePath(item.Path);
+
+            if (string.IsNullOrEmpty(lyricFilePath))
+            {
+                return;
+            }
+
+            List<Lyric> lyricsList = new List<Lyric>();
+
+            List<LrcParser.Model.Lyric> sortedLyricData = new List<LrcParser.Model.Lyric>();
+            var metaData = new ExpandoObject() as IDictionary<string, object>;
+            string lrcFileContent = System.IO.File.ReadAllText(lyricFilePath);
+
+            try
+            {
+                // Parse and sort lyric rows
+                LyricParser lrcLyricParser = new LrcParser.Parser.Lrc.LrcParser();
+                Song lyricData = lrcLyricParser.Decode(lrcFileContent);
+                sortedLyricData = lyricData.Lyrics.Where(x => x.TimeTags.Count > 0).OrderBy(x => x.TimeTags.ToArray()[0].Value).ToList();
+
+                // Parse metadata rows
+                var metaDataRows = lyricData.Lyrics
+                    .Where(x => x.TimeTags.Count == 0)
+                    .Where(x => x.Text.StartsWith("[", StringComparison.Ordinal) && x.Text.EndsWith("]", StringComparison.Ordinal))
+                    .Select(x => x.Text)
+                    .ToList();
+
+                foreach (string metaDataRow in metaDataRows)
+                {
+                    var metaDataField = metaDataRow.Split(":");
+
+                    string metaDataFieldName = metaDataField[0].Replace("[", string.Empty, StringComparison.Ordinal);
+                    string metaDataFieldValue = metaDataField[1].Replace("]", string.Empty, StringComparison.Ordinal);
+
+                    metaData.Add(metaDataFieldName, metaDataFieldValue);
+                }
+            }
+            catch
+            {
+                return;
+            }
+
+            if (!sortedLyricData.Any())
+            {
+                return;
+            }
+
+            for (int i = 0; i < sortedLyricData.Count; i++)
+            {
+                var timeData = sortedLyricData[i].TimeTags.ToArray()[0].Value;
+                double ticks = Convert.ToDouble(timeData, new NumberFormatInfo()) * 10000;
+                lyricsList.Add(new Lyric { Start = Math.Ceiling(ticks), Text = sortedLyricData[i].Text });
+            }
+
+            this.HasData = true;
+            if (metaData.Any())
+            {
+                this.Data = new { MetaData = metaData, lyrics = lyricsList };
+            }
+            else
+            {
+                this.Data = new { lyrics = lyricsList };
+            }
+        }
+    }
+}

+ 18 - 0
Jellyfin.Api/Models/UserDtos/Lyric.cs

@@ -0,0 +1,18 @@
+namespace Jellyfin.Api.Models.UserDtos
+{
+    /// <summary>
+    /// Lyric dto.
+    /// </summary>
+    public class Lyric
+    {
+        /// <summary>
+        /// Gets or sets the start time (ticks).
+        /// </summary>
+        public double Start { get; set; }
+
+        /// <summary>
+        /// Gets or sets the text.
+        /// </summary>
+        public string Text { get; set; } = string.Empty;
+    }
+}

+ 0 - 23
Jellyfin.Api/Models/UserDtos/Lyrics.cs

@@ -1,23 +0,0 @@
-namespace Jellyfin.Api.Models.UserDtos
-{
-    /// <summary>
-    /// Lyric dto.
-    /// </summary>
-    public class Lyrics
-    {
-        /// <summary>
-        /// Gets or sets the start.
-        /// </summary>
-        public double? Start { get; set; }
-
-        /// <summary>
-        /// Gets or sets the text.
-        /// </summary>
-        public string? Text { get; set; }
-
-        /// <summary>
-        /// Gets or sets the error.
-        /// </summary>
-        public string? Error { get; set; }
-    }
-}

+ 81 - 0
Jellyfin.Api/Models/UserDtos/TxtLyricsProvider.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Dynamic;
+using System.Globalization;
+using System.Linq;
+using LrcParser.Model;
+using LrcParser.Parser;
+using MediaBrowser.Controller.Entities;
+
+namespace Jellyfin.Api.Models.UserDtos
+{
+    /// <summary>
+    /// TXT File Lyric Provider.
+    /// </summary>
+    public class TxtLyricsProvider : ILyricsProvider
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="TxtLyricsProvider"/> class.
+        /// </summary>
+        public TxtLyricsProvider()
+        {
+            FileExtensions = new Collection<string>
+            {
+                "lrc", "txt"
+            };
+        }
+
+        /// <summary>
+        /// Gets a value indicating the File Extenstions this provider works with.
+        /// </summary>
+        public Collection<string>? FileExtensions { get; }
+
+        /// <summary>
+        /// Gets or Sets a value indicating whether Process() generated data.
+        /// </summary>
+        /// <returns><c>true</c> if data generated; otherwise, <c>false</c>.</returns>
+        public bool HasData { get; set; }
+
+        /// <summary>
+        /// Gets or Sets Data object generated by Process() method.
+        /// </summary>
+        /// <returns><c>Object</c> with data if no error occured; otherwise, <c>null</c>.</returns>
+        public object? Data { get; set; }
+
+        /// <summary>
+        /// Opens lyric file for [the specified item], and processes it for API return.
+        /// </summary>
+        /// <param name="item">The item to to process.</param>
+        public void Process(BaseItem item)
+        {
+            string? lyricFilePath = Helpers.ItemHelper.GetLyricFilePath(item.Path);
+
+            if (string.IsNullOrEmpty(lyricFilePath))
+            {
+                return;
+            }
+
+            List<Lyric> lyricsList = new List<Lyric>();
+
+            string lyricData = System.IO.File.ReadAllText(lyricFilePath);
+
+            // Splitting on Environment.NewLine caused some new lines to be missed in Windows.
+            char[] newLinedelims = new[] { '\r', '\n' };
+            string[] lyricTextLines = lyricData.Split(newLinedelims, StringSplitOptions.RemoveEmptyEntries);
+
+            if (!lyricTextLines.Any())
+            {
+                return;
+            }
+
+            foreach (string lyricLine in lyricTextLines)
+            {
+                lyricsList.Add(new Lyric { Text = lyricLine });
+            }
+
+            this.HasData = true;
+            this.Data = new { lyrics = lyricsList };
+        }
+    }
+}

+ 2 - 0
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -76,6 +76,8 @@ namespace MediaBrowser.Model.Dto
 
         public bool? CanDownload { get; set; }
 
+        public bool? HasLocalLyricsFile { get; set; }
+
         public bool? HasSubtitles { get; set; }
 
         public string PreferredMetadataLanguage { get; set; }