diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 7dcc925c2f..72fe5a854a 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/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.TryParseImdbId(str, out var imdbId);
+ return match ? imdbId : null;
}
return null;
diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs
new file mode 100644
index 0000000000..56e0112ddc
--- /dev/null
+++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs
@@ -0,0 +1,119 @@
+#nullable enable
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace MediaBrowser.Common.Providers
+{
+ ///
+ /// Parsers for provider ids.
+ ///
+ public static class ProviderIdParsers
+ {
+ ///
+ /// Parses an IMDb id from a string.
+ ///
+ /// The text to parse.
+ /// The parsed IMDb id.
+ /// True if parsing was successful, false otherwise.
+ public static bool TryParseImdbId(string text, [NotNullWhen(true)] out string? imdbId)
+ {
+ var span = text.AsSpan();
+ var tt = "tt".AsSpan();
+
+ while (true)
+ {
+ var ttPos = span.IndexOf(tt);
+ if (ttPos == -1)
+ {
+ imdbId = default;
+ return false;
+ }
+
+ span = span.Slice(ttPos + tt.Length);
+
+ int i = 0;
+ // IMDb id has a maximum of 8 digits
+ int max = span.Length > 8 ? 8 : span.Length;
+ for (; i < max; i++)
+ {
+ var c = span[i];
+
+ if (c < '0' || c > '9')
+ {
+ break;
+ }
+ }
+
+ // IMDb id has a minimum of 7 digits
+ if (i >= 7)
+ {
+ imdbId = string.Concat(tt, span.Slice(0, i));
+ return true;
+ }
+ }
+ }
+
+ ///
+ /// Parses an TMDb id from a movie url.
+ ///
+ /// The text with the url to parse.
+ /// The parsed TMDb id.
+ /// True if parsing was successful, false otherwise.
+ public static bool TryParseTmdbMovieId(string text, [NotNullWhen(true)] out string? tmdbId)
+ => TryParseProviderId(text, "themoviedb.org/movie/", out tmdbId);
+
+ ///
+ /// Parses an TMDb id from a series url.
+ ///
+ /// The text with the url to parse.
+ /// The parsed TMDb id.
+ /// True if parsing was successful, false otherwise.
+ public static bool TryParseTmdbSeriesId(string text, [NotNullWhen(true)] out string? tmdbId)
+ => TryParseProviderId(text, "themoviedb.org/tv/", out tmdbId);
+
+ ///
+ /// Parses an TVDb id from a url.
+ ///
+ /// The text with the url to parse.
+ /// The parsed TVDb id.
+ /// True if parsing was successful, false otherwise.
+ public static bool TryParseTvdbId(string text, [NotNullWhen(true)] out string? tvdbId)
+ => TryParseProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId);
+
+ private static bool TryParseProviderId(string text, string searchString, [NotNullWhen(true)] out string? providerId)
+ {
+ var span = text.AsSpan();
+ var searchSpan = searchString.AsSpan();
+
+ while (true)
+ {
+ var searchPos = span.IndexOf(searchSpan);
+ if (searchPos == -1)
+ {
+ providerId = default;
+ return false;
+ }
+
+ span = span.Slice(searchPos + searchSpan.Length);
+
+ int i = 0;
+ for (; i < span.Length; i++)
+ {
+ var c = span[i];
+
+ if (c < '0' || c > '9')
+ {
+ break;
+ }
+ }
+
+ if (i >= 1)
+ {
+ providerId = span.Slice(0, i).ToString();
+ return true;
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index 3129c131d0..d2fa120e8c 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/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;
@@ -63,8 +64,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
protected virtual bool SupportsUrlAfterClosingXmlTag => false;
- protected virtual string TmdbRegex => "themoviedb\\.org\\/movie\\/([0-9]+)";
-
///
/// Fetches metadata for an item from one xml file.
///
@@ -220,31 +219,29 @@ namespace MediaBrowser.XbmcMetadata.Parsers
protected void ParseProviderLinks(T item, string xml)
{
- // IMDB:
- // https://www.imdb.com/title/tt4154796
- var imdbRegex = Regex.Match(xml, "tt([0-9]{7,8})", RegexOptions.Compiled);
- if (imdbRegex.Success)
+ if (ProviderIdParsers.TryParseImdbId(xml, out var imdbId))
{
- item.SetProviderId(MetadataProvider.Imdb, imdbRegex.Value);
+ item.SetProviderId(MetadataProvider.Imdb, imdbId);
}
- // TMDB:
- // https://www.themoviedb.org/movie/30287-fallo (movie)
- // https://www.themoviedb.org/tv/1668-friends (tv)
- var tmdbRegex = Regex.Match(xml, TmdbRegex, RegexOptions.Compiled);
- if (tmdbRegex.Success)
+ if (item is Movie)
{
- item.SetProviderId(MetadataProvider.Tmdb, tmdbRegex.Groups[1].Value);
+ if (ProviderIdParsers.TryParseTmdbMovieId(xml, out var tmdbId))
+ {
+ item.SetProviderId(MetadataProvider.Tmdb, tmdbId);
+ }
}
- // TVDB:
- // https://www.thetvdb.com/?tab=series&id=121361
if (item is Series)
{
- var tvdbRegex = Regex.Match(xml, "thetvdb\\.com\\/\\?tab=series\\&id=([0-9]+)", RegexOptions.Compiled);
- if (tvdbRegex.Success)
+ if (ProviderIdParsers.TryParseTmdbSeriesId(xml, out var tmdbId))
+ {
+ item.SetProviderId(MetadataProvider.Tmdb, tmdbId);
+ }
+
+ if (ProviderIdParsers.TryParseTvdbId(xml, out var tvdbId))
{
- item.SetProviderId(MetadataProvider.Tvdb, tvdbRegex.Groups[1].Value);
+ item.SetProviderId(MetadataProvider.Tvdb, tvdbId);
}
}
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
index c09781b1ae..1dce378dca 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
@@ -35,9 +35,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
///
protected override bool SupportsUrlAfterClosingXmlTag => true;
- ///
- protected override string TmdbRegex => "themoviedb\\.org\\/tv\\/([0-9]+)";
-
///
protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult)
{
diff --git a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs
new file mode 100644
index 0000000000..a493dda64f
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs
@@ -0,0 +1,63 @@
+using MediaBrowser.Common.Providers;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Providers
+{
+ public class ProviderIdParserTests
+ {
+ [Theory]
+ [InlineData("tt123456", false, null)]
+ [InlineData("tt1234567", true, "tt1234567")]
+ [InlineData("tt12345678", true, "tt12345678")]
+ [InlineData("https://www.imdb.com/title/tt123456", false, null)]
+ [InlineData("https://www.imdb.com/title/tt1234567", true, "tt1234567")]
+ [InlineData("https://www.imdb.com/title/tt12345678", true, "tt12345678")]
+ [InlineData(@"multiline\nhttps://www.imdb.com/title/tt1234567", true, "tt1234567")]
+ [InlineData(@"multiline\nhttps://www.imdb.com/title/tt12345678", true, "tt12345678")]
+ [InlineData("Jellyfin", false, null)]
+ [InlineData("tt1234567tt7654321", true, "tt1234567")]
+ [InlineData("tt12345678tt7654321", true, "tt12345678")]
+ public void Parse_Imdb(string text, bool shouldSucceed, string? imdbId)
+ {
+ var succeeded = ProviderIdParsers.TryParseImdbId(text, out string? parsedId);
+ Assert.Equal(shouldSucceed, succeeded);
+ Assert.Equal(imdbId, parsedId);
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/movie/30287-fallo", true, "30287")]
+ [InlineData("themoviedb.org/movie/30287", true, "30287")]
+ [InlineData("https://www.themoviedb.org/movie/fallo-30287", false, null)]
+ [InlineData("https://www.themoviedb.org/tv/1668-friends", false, null)]
+ public void Parse_TmdbMovie(string text, bool shouldSucceed, string? tmdbId)
+ {
+ var succeeded = ProviderIdParsers.TryParseTmdbMovieId(text, out string? parsedId);
+ Assert.Equal(shouldSucceed, succeeded);
+ Assert.Equal(tmdbId, parsedId);
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/tv/1668-friends", true, "1668")]
+ [InlineData("themoviedb.org/tv/1668", true, "1668")]
+ [InlineData("https://www.themoviedb.org/tv/friends-1668", false, null)]
+ [InlineData("https://www.themoviedb.org/movie/30287-fallo", false, null)]
+ public void Parse_TmdbSeries(string text, bool shouldSucceed, string? tmdbId)
+ {
+ var succeeded = ProviderIdParsers.TryParseTmdbSeriesId(text, out string? parsedId);
+ Assert.Equal(shouldSucceed, succeeded);
+ Assert.Equal(tmdbId, parsedId);
+ }
+
+ [Theory]
+ [InlineData("https://www.thetvdb.com/?tab=series&id=121361", true, "121361")]
+ [InlineData("thetvdb.com/?tab=series&id=121361", true, "121361")]
+ [InlineData("thetvdb.com/?tab=series&id=Jellyfin121361", false, null)]
+ [InlineData("https://www.themoviedb.org/tv/1668-friends", false, null)]
+ public void Parse_Tvdb(string text, bool shouldSucceed, string? tvdbId)
+ {
+ var succeeded = ProviderIdParsers.TryParseTvdbId(text, out string? parsedId);
+ Assert.Equal(shouldSucceed, succeeded);
+ Assert.Equal(tvdbId, parsedId);
+ }
+ }
+}