From 8da012c8c507b6a177cfff233cfdac3490f79847 Mon Sep 17 00:00:00 2001 From: Narfinger Date: Thu, 10 Oct 2019 12:09:16 +0900 Subject: [PATCH 1/4] add tests for Emby.Naming/TV/EpisodePathParser.cs This should in the future help to detect working and non working name matchings --- MediaBrowser.sln | 7 ++++++ .../EpisodePathParserTest.cs | 25 +++++++++++++++++++ .../Jellyfin.Naming.Tests.csproj | 20 +++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs create mode 100644 tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj diff --git a/MediaBrowser.sln b/MediaBrowser.sln index dd4e9f8a69..260b10eb91 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -59,6 +59,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Common.Tests", "te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -165,6 +167,10 @@ Global {28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Debug|Any CPU.Build.0 = Debug|Any CPU {28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Release|Any CPU.ActiveCfg = Release|Any CPU {28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Release|Any CPU.Build.0 = Release|Any CPU + {3998657B-1CCC-49DD-A19F-275DC8495F57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3998657B-1CCC-49DD-A19F-275DC8495F57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3998657B-1CCC-49DD-A19F-275DC8495F57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3998657B-1CCC-49DD-A19F-275DC8495F57}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -193,5 +199,6 @@ Global GlobalSection(NestedProjects) = preSolution {DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection EndGlobal diff --git a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs new file mode 100644 index 0000000000..596bb3253a --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs @@ -0,0 +1,25 @@ +namespace Emby.Naming.TV +{ + using Emby.Naming.Common; + using Xunit; + + public class EpisodePathParserTest + { + [Theory] + [InlineData("/media/Foo/Foo-S01E01", "Foo", 1, 1)] + [InlineData("/media/Foo - S04E011", "Foo", 4, 11)] + [InlineData("/media/Foo/Foo s01x01", "Foo", 1, 1)] + [InlineData("/media/Foo/Foo s01x03 - the bar of foo", "Foo", 1, 3)] + public void ParseEpisodesCorrectly(string path, string name, int season, int episode) + { + NamingOptions o = new NamingOptions(); + EpisodePathParser p = new EpisodePathParser(o); + var res = p.Parse(path, false); + + Assert.True(res.Success); + Assert.Equal(name, res.SeriesName); + Assert.Equal(season, res.SeasonNumber); + Assert.Equal(episode, res.EpisodeNumber); + } + } +} \ No newline at end of file diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj new file mode 100644 index 0000000000..f5e1513489 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.0 + + false + + + + + + + + + + + + + + From 45f906c5564d8ecbc3d6d1cabcd78e929a9fb7e1 Mon Sep 17 00:00:00 2001 From: Narfinger Date: Fri, 11 Oct 2019 11:46:51 +0900 Subject: [PATCH 2/4] added a couple more tests --- .../EpisodePathParserTest.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs index 596bb3253a..c4e1ff3541 100644 --- a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs @@ -9,7 +9,7 @@ namespace Emby.Naming.TV [InlineData("/media/Foo/Foo-S01E01", "Foo", 1, 1)] [InlineData("/media/Foo - S04E011", "Foo", 4, 11)] [InlineData("/media/Foo/Foo s01x01", "Foo", 1, 1)] - [InlineData("/media/Foo/Foo s01x03 - the bar of foo", "Foo", 1, 3)] + [InlineData("/media/Foo (2019)/Season 4/Foo (2019).S04E03", "Foo (2019)", 4, 3)] public void ParseEpisodesCorrectly(string path, string name, int season, int episode) { NamingOptions o = new NamingOptions(); @@ -20,6 +20,34 @@ namespace Emby.Naming.TV Assert.Equal(name, res.SeriesName); Assert.Equal(season, res.SeasonNumber); Assert.Equal(episode, res.EpisodeNumber); + + //testing other paths delimeter + var res2 = p.Parse(path.Replace("/", "\\"), false); + Assert.True(res2.Success); + Assert.Equal(name, res2.SeriesName); + Assert.Equal(season, res2.SeasonNumber); + Assert.Equal(episode, res2.EpisodeNumber); + } + + [Theory] + [InlineData("/media/Foo/Foo 889.avi", "Foo", 889)] + public void ParseEpisodeWithoutSeason(string path, string name, int episode) + { + NamingOptions o = new NamingOptions(); + EpisodePathParser p = new EpisodePathParser(o); + var res = p.Parse(path, false, null, null, true); + + Assert.True(res.Success); + Assert.Equal(name, res.SeriesName); + Assert.True(res.SeasonNumber == null); + Assert.Equal(episode, res.EpisodeNumber); + + //testing other paths delimeter + var res2 = p.Parse(path.Replace("/", "\\"), false, null, null, true); + Assert.True(res2.Success); + Assert.Equal(name, res2.SeriesName); + Assert.True(res2.SeasonNumber == null); + Assert.Equal(episode, res2.EpisodeNumber); } } } \ No newline at end of file From 4a20260a27e8ede4188609d8206a7313f7243e97 Mon Sep 17 00:00:00 2001 From: Narfinger Date: Fri, 11 Oct 2019 19:24:55 +0900 Subject: [PATCH 3/4] add another parser case and allow parsing of seasonless Add another parser case and we now allow parsing of seasonless series which hopefully should cover more cases of directory structure --- Emby.Naming/Common/NamingOptions.cs | 10 +++++++--- Emby.Server.Implementations/Library/LibraryManager.cs | 6 +++++- tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs | 5 +++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 88a9b46e63..e1f25fd2b1 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -328,6 +328,10 @@ namespace Emby.Naming.Common // *** End Kodi Standard Naming +                // [bar] Foo - 1 [baz] +                new EpisodeExpression(@".*?(\[.*?\])+.*?(?(\w+\s)+?)[-\s_]+(?\d{1,3}).*$"){ + IsNamed=false, + }, new EpisodeExpression(@".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})[^\\\/]*$") { IsNamed = true @@ -654,9 +658,9 @@ namespace Emby.Naming.Common @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$" }.Select(i => new EpisodeExpression(i) - { - IsNamed = true - }).ToArray(); + { + IsNamed = true + }).ToArray(); VideoFileExtensions = extensions .Distinct(StringComparer.OrdinalIgnoreCase) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 87e951f25d..90f373cc6a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1899,7 +1899,7 @@ namespace Emby.Server.Implementations.Library /// The cancellation token. public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - UpdateItems(new [] { item }, parent, updateReason, cancellationToken); + UpdateItems(new[] { item }, parent, updateReason, cancellationToken); } /// @@ -2487,6 +2487,10 @@ namespace Emby.Server.Implementations.Library { episode.ParentIndexNumber = season.IndexNumber; } + else + { + episode.ParentIndexNumber = 1; + } if (episode.ParentIndexNumber.HasValue) { diff --git a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs index c4e1ff3541..28ccd6df1d 100644 --- a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs @@ -30,12 +30,13 @@ namespace Emby.Naming.TV } [Theory] - [InlineData("/media/Foo/Foo 889.avi", "Foo", 889)] + [InlineData("/media/Foo/Foo 889", "Foo", 889)] + [InlineData("/media/Foo/[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)] public void ParseEpisodeWithoutSeason(string path, string name, int episode) { NamingOptions o = new NamingOptions(); EpisodePathParser p = new EpisodePathParser(o); - var res = p.Parse(path, false, null, null, true); + var res = p.Parse(path, true, null, null, true); Assert.True(res.Success); Assert.Equal(name, res.SeriesName); From 9cd62d661f3209ddf411faf5936e2483b532fadb Mon Sep 17 00:00:00 2001 From: Narfinger Date: Thu, 7 Nov 2019 10:50:02 +0900 Subject: [PATCH 4/4] removed restriction to 3 digits in episodenumber and 4 in season numbers --- Emby.Naming/Common/NamingOptions.cs | 12 ++++++------ .../Library/LibraryManager.cs | 5 +++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index e1f25fd2b1..9cf430daf7 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -329,31 +329,31 @@ namespace Emby.Naming.Common // *** End Kodi Standard Naming                 // [bar] Foo - 1 [baz] -                new EpisodeExpression(@".*?(\[.*?\])+.*?(?(\w+\s)+?)[-\s_]+(?\d{1,3}).*$"){ +                new EpisodeExpression(@".*?(\[.*?\])+.*?(?(\w+\s)+?)[-\s_]+(?\d+).*$"){ IsNamed=false, }, - new EpisodeExpression(@".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})[^\\\/]*$") + new EpisodeExpression(@".*(\\|\/)[sS]?(?\d+)[xX](?\d+)[^\\\/]*$") { IsNamed = true }, - new EpisodeExpression(@".*(\\|\/)[sS](?\d{1,4})[x,X]?[eE](?\d{1,3})[^\\\/]*$") + new EpisodeExpression(@".*(\\|\/)[sS](?\d+)[x,X]?[eE](?\d+)[^\\\/]*$") { IsNamed = true }, - new EpisodeExpression(@".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))[^\\\/]*$") + new EpisodeExpression(@".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d+))[^\\\/]*$") { IsNamed = true }, - new EpisodeExpression(@".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})[^\\\/]*$") + new EpisodeExpression(@".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d+)[^\\\/]*$") { IsNamed = true }, // "01.avi" - new EpisodeExpression(@".*[\\\/](?\d{1,3})(-(?\d{2,3}))*\.\w+$") + new EpisodeExpression(@".*[\\\/](?\d+)(-(?\d+))*\.\w+$") { IsOptimistic = true, IsNamed = true diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 90f373cc6a..dd74b1060e 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2489,6 +2489,11 @@ namespace Emby.Server.Implementations.Library } else { + /* + Anime series don't generally have a season in their file name, however, + tvdb needs a season to correctly get the metadata. + Hence, a null season needs to be filled with something. */ + //FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified episode.ParentIndexNumber = 1; }