Переглянути джерело

Merge pull request #5467 from Ullmie02/nfo-fixes

Bond-009 4 роки тому
батько
коміт
b49d50e634

+ 3 - 2
Emby.Server.Implementations/Library/PathExtensions.cs

@@ -4,6 +4,7 @@ using System;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Text.RegularExpressions;
+using MediaBrowser.Common.Providers;
 
 namespace Emby.Server.Implementations.Library
 {
@@ -43,8 +44,8 @@ namespace Emby.Server.Implementations.Library
             // for imdbid we also accept pattern matching
             if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
             {
-                var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
-                return m.Success ? m.Value : null;
+                var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
+                return match ? imdbId.ToString() : null;
             }
 
             return null;

+ 125 - 0
MediaBrowser.Common/Providers/ProviderIdParsers.cs

@@ -0,0 +1,125 @@
+#nullable enable
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace MediaBrowser.Common.Providers
+{
+    /// <summary>
+    /// Parsers for provider ids.
+    /// </summary>
+    public static class ProviderIdParsers
+    {
+        private const int ImdbMinNumbers = 7;
+        private const int ImdbMaxNumbers = 8;
+        private const string ImdbPrefix = "tt";
+
+        /// <summary>
+        /// Parses an IMDb id from a string.
+        /// </summary>
+        /// <param name="text">The text to parse.</param>
+        /// <param name="imdbId">The parsed IMDb id.</param>
+        /// <returns>True if parsing was successful, false otherwise.</returns>
+        public static bool TryFindImdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> imdbId)
+        {
+            // imdb id is at least 9 chars (tt + 7 numbers)
+            while (text.Length >= 2 + ImdbMinNumbers)
+            {
+                var ttPos = text.IndexOf(ImdbPrefix);
+                if (ttPos == -1)
+                {
+                    imdbId = default;
+                    return false;
+                }
+
+                text = text.Slice(ttPos);
+                var i = 2;
+                var limit = Math.Min(text.Length, ImdbMaxNumbers + 2);
+                for (; i < limit; i++)
+                {
+                    var c = text[i];
+                    if (!IsDigit(c))
+                    {
+                        break;
+                    }
+                }
+
+                // skip if more than 8 digits + 2 chars for tt
+                if (i <= ImdbMaxNumbers + 2 && i >= ImdbMinNumbers + 2)
+                {
+                    imdbId = text.Slice(0, i);
+                    return true;
+                }
+
+                text = text.Slice(i);
+            }
+
+            imdbId = default;
+            return false;
+        }
+
+        /// <summary>
+        /// Parses an TMDb id from a movie url.
+        /// </summary>
+        /// <param name="text">The text with the url to parse.</param>
+        /// <param name="tmdbId">The parsed TMDb id.</param>
+        /// <returns>True if parsing was successful, false otherwise.</returns>
+        public static bool TryFindTmdbMovieId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId)
+            => TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId);
+
+        /// <summary>
+        /// Parses an TMDb id from a series url.
+        /// </summary>
+        /// <param name="text">The text with the url to parse.</param>
+        /// <param name="tmdbId">The parsed TMDb id.</param>
+        /// <returns>True if parsing was successful, false otherwise.</returns>
+        public static bool TryFindTmdbSeriesId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId)
+            => TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId);
+
+        /// <summary>
+        /// Parses an TVDb id from a url.
+        /// </summary>
+        /// <param name="text">The text with the url to parse.</param>
+        /// <param name="tvdbId">The parsed TVDb id.</param>
+        /// <returns>True if parsing was successful, false otherwise.</returns>
+        public static bool TryFindTvdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tvdbId)
+            => TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId);
+
+        private static bool TryFindProviderId(ReadOnlySpan<char> text, ReadOnlySpan<char> searchString, [NotNullWhen(true)] out ReadOnlySpan<char> providerId)
+        {
+            var searchPos = text.IndexOf(searchString);
+            if (searchPos == -1)
+            {
+                providerId = default;
+                return false;
+            }
+
+            text = text.Slice(searchPos + searchString.Length);
+
+            int i = 0;
+            for (; i < text.Length; i++)
+            {
+                var c = text[i];
+
+                if (!IsDigit(c))
+                {
+                    break;
+                }
+            }
+
+            if (i >= 1)
+            {
+                providerId = text.Slice(0, i);
+                return true;
+            }
+
+            providerId = default;
+            return false;
+        }
+
+        private static bool IsDigit(char c)
+        {
+            return c >= '0' && c <= '9';
+        }
+    }
+}

+ 14 - 37
MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs

@@ -6,11 +6,12 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Text;
-using System.Text.RegularExpressions;
 using System.Threading;
 using System.Xml;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Providers;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
@@ -67,8 +68,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
 
         protected virtual bool SupportsUrlAfterClosingXmlTag => false;
 
-        protected virtual string MovieDbParserSearchString => "themoviedb.org/movie/";
-
         /// <summary>
         /// Fetches metadata for an item from one xml file.
         /// </summary>
@@ -185,8 +184,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                 }
                 else
                 {
-                    // If the file is just an Imdb url, handle that
-
+                    // If the file is just provider urls, handle that
                     ParseProviderLinks(item.Item, xml);
 
                     return;
@@ -225,50 +223,29 @@ namespace MediaBrowser.XbmcMetadata.Parsers
 
         protected void ParseProviderLinks(T item, string xml)
         {
-            // Look for a match for the Regex pattern "tt" followed by 7 or 8 digits
-            var m = Regex.Match(xml, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
-            if (m.Success)
+            if (ProviderIdParsers.TryFindImdbId(xml, out var imdbId))
             {
-                item.SetProviderId(MetadataProvider.Imdb, m.Value);
+                item.SetProviderId(MetadataProvider.Imdb, imdbId.ToString());
             }
 
-            // Support Tmdb
-            // https://www.themoviedb.org/movie/30287-fallo
-            var srch = MovieDbParserSearchString;
-            var index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
-
-            if (index != -1)
+            if (item is Movie)
             {
-                var tmdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/');
-                index = tmdbId.IndexOf('-');
-                if (index != -1)
-                {
-                    tmdbId = tmdbId.Slice(0, index);
-                }
-
-                if (!tmdbId.IsEmpty
-                    && !tmdbId.IsWhiteSpace()
-                    && int.TryParse(tmdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
+                if (ProviderIdParsers.TryFindTmdbMovieId(xml, out var tmdbId))
                 {
-                    item.SetProviderId(MetadataProvider.Tmdb, value.ToString(UsCulture));
+                    item.SetProviderId(MetadataProvider.Tmdb, tmdbId.ToString());
                 }
             }
 
             if (item is Series)
             {
-                srch = "thetvdb.com/?tab=series&id=";
-
-                index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
+                if (ProviderIdParsers.TryFindTmdbSeriesId(xml, out var tmdbId))
+                {
+                    item.SetProviderId(MetadataProvider.Tmdb, tmdbId.ToString());
+                }
 
-                if (index != -1)
+                if (ProviderIdParsers.TryFindTvdbId(xml, out var tvdbId))
                 {
-                    var tvdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/');
-                    if (!tvdbId.IsEmpty
-                        && !tvdbId.IsWhiteSpace()
-                        && int.TryParse(tvdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
-                    {
-                        item.SetProviderId(MetadataProvider.Tvdb, value.ToString(UsCulture));
-                    }
+                    item.SetProviderId(MetadataProvider.Tvdb, tvdbId.ToString());
                 }
             }
         }

+ 9 - 2
MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs

@@ -49,12 +49,19 @@ namespace MediaBrowser.XbmcMetadata.Parsers
             {
                 case "id":
                     {
+                        // get ids from attributes
                         string? imdbId = reader.GetAttribute("IMDB");
                         string? tmdbId = reader.GetAttribute("TMDB");
 
-                        if (string.IsNullOrWhiteSpace(imdbId))
+                        // read id from content
+                        var contentId = reader.ReadElementContentAsString();
+                        if (contentId.Contains("tt", StringComparison.Ordinal) && string.IsNullOrEmpty(imdbId))
                         {
-                            imdbId = reader.ReadElementContentAsString();
+                            imdbId = contentId;
+                        }
+                        else if (string.IsNullOrEmpty(tmdbId))
+                        {
+                            tmdbId = contentId;
                         }
 
                         if (!string.IsNullOrWhiteSpace(imdbId))

+ 0 - 3
MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs

@@ -37,9 +37,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
         /// <inheritdoc />
         protected override bool SupportsUrlAfterClosingXmlTag => true;
 
-        /// <inheritdoc />
-        protected override string MovieDbParserSearchString => "themoviedb.org/tv/";
-
         /// <inheritdoc />
         protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Series> itemResult)
         {

+ 85 - 0
tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs

@@ -0,0 +1,85 @@
+using System;
+using MediaBrowser.Common.Providers;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Providers
+{
+    public class ProviderIdParserTests
+    {
+        [Theory]
+        [InlineData("tt1234567", "tt1234567")]
+        [InlineData("tt12345678", "tt12345678")]
+        [InlineData("https://www.imdb.com/title/tt1234567", "tt1234567")]
+        [InlineData("https://www.imdb.com/title/tt12345678", "tt12345678")]
+        [InlineData(@"multiline\nhttps://www.imdb.com/title/tt1234567", "tt1234567")]
+        [InlineData(@"multiline\nhttps://www.imdb.com/title/tt12345678", "tt12345678")]
+        [InlineData("tt1234567tt7654321", "tt1234567")]
+        [InlineData("tt12345678tt7654321", "tt12345678")]
+        [InlineData("tt123456789", "tt12345678")]
+        public void FindImdbId_Valid_Success(string text, string expected)
+        {
+            Assert.True(ProviderIdParsers.TryFindImdbId(text, out ReadOnlySpan<char> parsedId));
+            Assert.Equal(expected, parsedId.ToString());
+        }
+
+        [Theory]
+        [InlineData("tt123456")]
+        [InlineData("https://www.imdb.com/title/tt123456")]
+        [InlineData("Jellyfin")]
+        public void FindImdbId_Invalid_Success(string text)
+        {
+            Assert.False(ProviderIdParsers.TryFindImdbId(text, out _));
+        }
+
+        [Theory]
+        [InlineData("https://www.themoviedb.org/movie/30287-fallo", "30287")]
+        [InlineData("themoviedb.org/movie/30287", "30287")]
+        public void FindTmdbMovieId_Valid_Success(string text, string expected)
+        {
+            Assert.True(ProviderIdParsers.TryFindTmdbMovieId(text, out ReadOnlySpan<char> parsedId));
+            Assert.Equal(expected, parsedId.ToString());
+        }
+
+        [Theory]
+        [InlineData("https://www.themoviedb.org/movie/fallo-30287")]
+        [InlineData("https://www.themoviedb.org/tv/1668-friends")]
+        public void FindTmdbMovieId_Invalid_Success(string text)
+        {
+            Assert.False(ProviderIdParsers.TryFindTmdbMovieId(text, out _));
+        }
+
+        [Theory]
+        [InlineData("https://www.themoviedb.org/tv/1668-friends", "1668")]
+        [InlineData("themoviedb.org/tv/1668", "1668")]
+        public void FindTmdbSeriesId_Valid_Success(string text, string expected)
+        {
+            Assert.True(ProviderIdParsers.TryFindTmdbSeriesId(text, out ReadOnlySpan<char> parsedId));
+            Assert.Equal(expected, parsedId.ToString());
+        }
+
+        [Theory]
+        [InlineData("https://www.themoviedb.org/tv/friends-1668")]
+        [InlineData("https://www.themoviedb.org/movie/30287-fallo")]
+        public void FindTmdbSeriesId_Invalid_Success(string text)
+        {
+            Assert.False(ProviderIdParsers.TryFindTmdbSeriesId(text, out _));
+        }
+
+        [Theory]
+        [InlineData("https://www.thetvdb.com/?tab=series&id=121361", "121361")]
+        [InlineData("thetvdb.com/?tab=series&id=121361", "121361")]
+        public void FindTvdbId_Valid_Success(string text, string expected)
+        {
+            Assert.True(ProviderIdParsers.TryFindTvdbId(text, out ReadOnlySpan<char> parsedId));
+            Assert.Equal(expected, parsedId.ToString());
+        }
+
+        [Theory]
+        [InlineData("thetvdb.com/?tab=series&id=Jellyfin121361")]
+        [InlineData("https://www.themoviedb.org/tv/1668-friends")]
+        public void FindTvdbId_Invalid_Success(string text)
+        {
+            Assert.False(ProviderIdParsers.TryFindTvdbId(text, out _));
+        }
+    }
+}

+ 15 - 0
tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs

@@ -203,6 +203,21 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
             Assert.Equal(id, item.ProviderIds[provider]);
         }
 
+        [Fact]
+        public void Parse_RadarrUrlFile_Success()
+        {
+            var result = new MetadataResult<Video>()
+            {
+                Item = new Movie()
+            };
+
+            _parser.Fetch(result, "Test Data/Radarr.nfo", CancellationToken.None);
+            var item = (Movie)result.Item;
+
+            Assert.Equal("583689", item.ProviderIds[MetadataProvider.Tmdb.ToString()]);
+            Assert.Equal("tt4154796", item.ProviderIds[MetadataProvider.Imdb.ToString()]);
+        }
+
         [Fact]
         public void Fetch_WithNullItem_ThrowsArgumentException()
         {

+ 2 - 1
tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo

@@ -93,7 +93,8 @@
     </fanart>
     <mpaa>Australia:M</mpaa>
     <id>tt0974015</id>
-    <uniqueid type="imdb" default="true">tt0974015</uniqueid>
+    <uniqueid type="imdb">tt0974015</uniqueid>
+    <uniqueid type="tmdb">141052</uniqueid>
     <genre>Action</genre>
     <genre>Adventure</genre>
     <genre>Fantasy</genre>

+ 2 - 0
tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo

@@ -0,0 +1,2 @@
+https://www.themoviedb.org/movie/583689
+https://www.imdb.com/title/tt4154796