diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index dd1434cea..2f1e036e0 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -175,6 +175,12 @@ namespace MediaBrowser.Controller.Entities.TV get { return _season ?? (_season = FindParent()); } } + /// + /// This is the ending episode number for double episodes. + /// + /// The index number. + public int? IndexNumberEnd { get; set; } + /// /// Creates the name of the sort. /// diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 6a220c6d7..9538e927a 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -44,19 +44,48 @@ namespace MediaBrowser.Controller.Library new Regex( @".*\\[s|S]?(?\d{1,2})[x|X](?\d{1,3})[^\\]*$", RegexOptions.Compiled), - // 01x02 blah.avi S01x01 balh.avi new Regex( - @".*\\[s|S](?\d{1,2})x?[e|E](?\d{1,3})[^\\]*$", + @".*\\[s|S](?\d{1,2})[x,X]?[e|E](?\d{1,3})[^\\]*$", RegexOptions.Compiled), - // S01E02 blah.avi, S01xE01 blah.avi new Regex( - @".*\\(?[^\\]*)[s|S]?(?\d{1,2})[x|X](?\d{1,3})[^\\]*$", + @".*\\(?((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?\d{1,2})[x|X](?\d{1,3}))[^\\]*$", RegexOptions.Compiled), - // 01x02 blah.avi S01x01 balh.avi new Regex( @".*\\(?[^\\]*)[s|S](?\d{1,2})[x|X|\.]?[e|E](?\d{1,3})[^\\]*$", RegexOptions.Compiled) - // S01E02 blah.avi, S01xE01 blah.avi + }; + private static readonly Regex[] MultipleEpisodeExpressions = new[] + { + new Regex( + @".*\\[s|S]?(?\d{1,2})[x|X](?\d{1,3})([ |-]{1,3}\d{1,2}[e|E|x|X](?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled), + new Regex( + @".*\\[s|S]?(?\d{1,2})[x|X](?\d{1,3})([ |-]{1,3}\d{1,2}[x|X][e|E](?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled), + new Regex( + @".*\\[s|S]?(?\d{1,2})[x|X](?\d{1,3})([ |-]{0,3}[x|X|e|E](?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled), + new Regex( + @".*\\[s|S]?(?\d{1,2})[x|X](?\d{1,3})([ |-]{1,3}[x|E]?[e|E]?(?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled), + new Regex( + @".*\\(?((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?\d{1,2})[x|X](?\d{1,3}))([ |-]{1,3}\d{1,2}[x|X|e|E](?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled), + new Regex( + @".*\\(?((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?\d{1,2})[x|X](?\d{1,3}))([ |-]{1,3}\d{1,2}[x|X][e|E](?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled), + new Regex( + @".*\\(?((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?\d{1,2})[x|X](?\d{1,3}))([ |-]{0,3}[x|X|e|E](?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled), + new Regex( + @".*\\(?((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?\d{1,2})[x|X](?\d{1,3}))([ |-]{1,3}[x|X]?[e|E]?(?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled), + new Regex( + @".*\\(?[^\\]*)[s|S](?\d{1,2})[x|X|\.]?[e|E](?\d{1,3})([ |-]{0,3}[x|X|e|E](?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled), + new Regex( + @".*\\(?[^\\]*)[s|S](?\d{1,2})[x|X|\.]?[e|E](?\d{1,3})([ |-]{1,3}[x|X]?[e|E]?(?\d{1,3}))+[^\\]*$", + RegexOptions.Compiled) }; /// @@ -224,6 +253,18 @@ namespace MediaBrowser.Controller.Library return null; } + public static int? GetEndingEpisodeNumberFromFile(string fullPath) + { + var fl = fullPath.ToLower(); + foreach (var r in MultipleEpisodeExpressions) + { + var m = r.Match(fl); + if (m.Success && !string.IsNullOrEmpty(m.Groups["endingepnumber"].Value)) + return ParseEpisodeNumber(m.Groups["endingepnumber"].Value); + } + return null; + } + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static int? ParseEpisodeNumber(string val) diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index d6fe5d456..cc9016190 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -53,6 +53,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV if (episode != null) { episode.IndexNumber = TVUtils.GetEpisodeNumberFromFile(args.Path, season != null); + episode.IndexNumberEnd = TVUtils.GetEndingEpisodeNumberFromFile(args.Path); if (season != null) { diff --git a/MediaBrowser.Specs/Controller/Library/TvUtilTests.cs b/MediaBrowser.Specs/Controller/Library/TvUtilTests.cs new file mode 100644 index 000000000..fd1e952b8 --- /dev/null +++ b/MediaBrowser.Specs/Controller/Library/TvUtilTests.cs @@ -0,0 +1,58 @@ +using MediaBrowser.Controller.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace MediaBrowser.Specs.Controller.Library +{ + [TestClass] + public class TvUtilTests + { + [TestMethod] + public void TestGetEpisodeNumberFromFile() + { + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\01x02 blah.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\S01x02 blah.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\S01E02 blah.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\S01xE02 blah.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\seriesname 01x02 blah.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\seriesname S01x02 blah.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\seriesname S01E02 blah.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\seriesname S01xE02 blah.avi", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 2\Elementary - 02x03 - 02x04 - 02x15 - Ep Name.ext", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 2\02x03 - 02x04 - 02x15 - Ep Name.ext", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 2\02x03-04-15 - Ep Name.ext", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 2\Elementary - 02x03-04-15 - Ep Name.ext", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 02\02x03-E15 - Ep Name.ext", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 02\Elementary - 02x03-E15 - Ep Name.ext", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 02\02x03 - x04 - x15 - Ep Name.ext", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 02\Elementary - 02x03 - x04 - x15 - Ep Name.ext", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 02\02x03x04x15 - Ep Name.ext", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 02\Elementary - 02x03x04x15 - Ep Name.ext", true)); + Assert.AreEqual(23, TVUtils.GetEpisodeNumberFromFile(@"Season 1\Elementary - S01E23-E24-E26 - The Woman.mp4", true)); + Assert.AreEqual(23, TVUtils.GetEpisodeNumberFromFile(@"Season 1\S01E23-E24-E26 - The Woman.mp4", true)); + } + + [TestMethod] + public void TestGetEndingEpisodeNumberFromFile() + { + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\01x02 blah.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\S01x02 blah.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\S01E02 blah.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\S01xE02 blah.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\seriesname 01x02 blah.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\seriesname S01x02 blah.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\seriesname S01E02 blah.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\seriesname S01xE02 blah.avi")); + Assert.AreEqual(15, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\Elementary - 02x03 - 02x04 - 02x15 - Ep Name.ext")); + Assert.AreEqual(15, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\02x03 - 02x04 - 02x15 - Ep Name.ext")); + Assert.AreEqual(15, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 02\02x03-E15 - Ep Name.ext")); + Assert.AreEqual(15, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 02\Elementary - 02x03-E15 - Ep Name.ext")); + Assert.AreEqual(15, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\02x03-04-15 - Ep Name.ext")); + Assert.AreEqual(15, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\Elementary - 02x03-04-15 - Ep Name.ext")); + Assert.AreEqual(26, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\Elementary - S01E23-E24-E26 - The Woman.mp4")); + Assert.AreEqual(26, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\S01E23-E24-E26 - The Woman.mp4")); + Assert.AreEqual(15, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 02\02x03x04x15 - Ep Name.ext")); + Assert.AreEqual(15, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 02\02x03 - x04 - x15 - Ep Name.ext")); + Assert.AreEqual(15, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 02\Elementary - 02x03 - x04 - x15 - Ep Name.ext")); + } + } +} diff --git a/MediaBrowser.Specs/MediaBrowser.Specs.csproj b/MediaBrowser.Specs/MediaBrowser.Specs.csproj new file mode 100644 index 000000000..a6f2cb6c0 --- /dev/null +++ b/MediaBrowser.Specs/MediaBrowser.Specs.csproj @@ -0,0 +1,89 @@ + + + + Debug + AnyCPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081} + Library + Properties + MediaBrowser.Specs + MediaBrowser.Specs + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + MediaBrowser.Controller + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.Specs/Properties/AssemblyInfo.cs b/MediaBrowser.Specs/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..6d835be88 --- /dev/null +++ b/MediaBrowser.Specs/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MediaBrowser.Specs")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MediaBrowser.Specs")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("42408c2b-9c5d-4541-ac3f-3fe389b760ed")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MediaBrowser.sln b/MediaBrowser.sln index a5612a287..70f3d65e7 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Impleme EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model.net35", "MediaBrowser.Model.net35\MediaBrowser.Model.net35.csproj", "{657B5410-7C3B-4806-9753-D254102CE537}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Specs", "MediaBrowser.Specs\MediaBrowser.Specs.csproj", "{E22BFD35-0FCD-4A85-978A-C22DCD73A081}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -185,6 +187,20 @@ Global {657B5410-7C3B-4806-9753-D254102CE537}.Release|Win32.ActiveCfg = Release|Any CPU {657B5410-7C3B-4806-9753-D254102CE537}.Release|x64.ActiveCfg = Release|Any CPU {657B5410-7C3B-4806-9753-D254102CE537}.Release|x86.ActiveCfg = Release|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Win32.ActiveCfg = Debug|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|x64.ActiveCfg = Debug|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|x86.ActiveCfg = Debug|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Any CPU.Build.0 = Release|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Win32.ActiveCfg = Release|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x64.ActiveCfg = Release|Any CPU + {E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE