aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs175
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs77
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs38
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj9
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj4
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs6
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs22
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj12
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json105
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj4
-rw-r--r--tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs66
-rw-r--r--tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs39
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs61
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs69
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs393
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs127
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs (renamed from tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs)22
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs56
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs105
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs112
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs305
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs95
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs13
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs59
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs45
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs77
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs78
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs437
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/StackTests.cs452
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/StubTests.cs53
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs457
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs275
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs42
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj22
-rw-r--r--tests/coverletArgs.runsettings17
35 files changed, 3902 insertions, 27 deletions
diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
new file mode 100644
index 000000000..a2f5c2501
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Linq;
+using System.Security.Claims;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Jellyfin.Api.Auth;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Net;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Api.Tests.Auth
+{
+ public class CustomAuthenticationHandlerTests
+ {
+ private readonly IFixture _fixture;
+
+ private readonly Mock<IAuthService> _jellyfinAuthServiceMock;
+ private readonly Mock<IOptionsMonitor<AuthenticationSchemeOptions>> _optionsMonitorMock;
+ private readonly Mock<ISystemClock> _clockMock;
+ private readonly Mock<IServiceProvider> _serviceProviderMock;
+ private readonly Mock<IAuthenticationService> _authenticationServiceMock;
+ private readonly UrlEncoder _urlEncoder;
+ private readonly HttpContext _context;
+
+ private readonly CustomAuthenticationHandler _sut;
+ private readonly AuthenticationScheme _scheme;
+
+ public CustomAuthenticationHandlerTests()
+ {
+ var fixtureCustomizations = new AutoMoqCustomization
+ {
+ ConfigureMembers = true
+ };
+
+ _fixture = new Fixture().Customize(fixtureCustomizations);
+ AllowFixtureCircularDependencies();
+
+ _jellyfinAuthServiceMock = _fixture.Freeze<Mock<IAuthService>>();
+ _optionsMonitorMock = _fixture.Freeze<Mock<IOptionsMonitor<AuthenticationSchemeOptions>>>();
+ _clockMock = _fixture.Freeze<Mock<ISystemClock>>();
+ _serviceProviderMock = _fixture.Freeze<Mock<IServiceProvider>>();
+ _authenticationServiceMock = _fixture.Freeze<Mock<IAuthenticationService>>();
+ _fixture.Register<ILoggerFactory>(() => new NullLoggerFactory());
+
+ _urlEncoder = UrlEncoder.Default;
+
+ _serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService)))
+ .Returns(_authenticationServiceMock.Object);
+
+ _optionsMonitorMock.Setup(o => o.Get(It.IsAny<string>()))
+ .Returns(new AuthenticationSchemeOptions
+ {
+ ForwardAuthenticate = null
+ });
+
+ _context = new DefaultHttpContext
+ {
+ RequestServices = _serviceProviderMock.Object
+ };
+
+ _scheme = new AuthenticationScheme(
+ _fixture.Create<string>(),
+ null,
+ typeof(CustomAuthenticationHandler));
+
+ _sut = _fixture.Create<CustomAuthenticationHandler>();
+ _sut.InitializeAsync(_scheme, _context).Wait();
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldFailWithNullUser()
+ {
+ _jellyfinAuthServiceMock.Setup(
+ a => a.Authenticate(
+ It.IsAny<HttpRequest>(),
+ It.IsAny<AuthenticatedAttribute>()))
+ .Returns((User)null);
+
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ Assert.False(authenticateResult.Succeeded);
+ Assert.Equal("Invalid user", authenticateResult.Failure.Message);
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldFailOnSecurityException()
+ {
+ var errorMessage = _fixture.Create<string>();
+
+ _jellyfinAuthServiceMock.Setup(
+ a => a.Authenticate(
+ It.IsAny<HttpRequest>(),
+ It.IsAny<AuthenticatedAttribute>()))
+ .Throws(new SecurityException(errorMessage));
+
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ Assert.False(authenticateResult.Succeeded);
+ Assert.Equal(errorMessage, authenticateResult.Failure.Message);
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldSucceedWithUser()
+ {
+ SetupUser();
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ Assert.True(authenticateResult.Succeeded);
+ Assert.Null(authenticateResult.Failure);
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldAssignNameClaim()
+ {
+ var user = SetupUser();
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, user.Name));
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task HandleAuthenticateAsyncShouldAssignRoleClaim(bool isAdmin)
+ {
+ var user = SetupUser(isAdmin);
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ var expectedRole = user.Policy.IsAdministrator ? UserRoles.Administrator : UserRoles.User;
+ Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole));
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldAssignTicketCorrectScheme()
+ {
+ SetupUser();
+ var authenticatedResult = await _sut.AuthenticateAsync();
+
+ Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme);
+ }
+
+ private User SetupUser(bool isAdmin = false)
+ {
+ var user = _fixture.Create<User>();
+ user.Policy.IsAdministrator = isAdmin;
+
+ _jellyfinAuthServiceMock.Setup(
+ a => a.Authenticate(
+ It.IsAny<HttpRequest>(),
+ It.IsAny<AuthenticatedAttribute>()))
+ .Returns(user);
+
+ return user;
+ }
+
+ private void AllowFixtureCircularDependencies()
+ {
+ // A circular dependency exists in the User entity around parent folders,
+ // this allows Autofixture to generate a User regardless, rather than throw
+ // an error.
+ _fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
+ .ForEach(b => _fixture.Behaviors.Remove(b));
+ _fixture.Behaviors.Add(new OmitOnRecursionBehavior());
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs
new file mode 100644
index 000000000..84cdbe360
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using Microsoft.AspNetCore.Authorization;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupOrElevatedPolicy
+{
+ public class FirstTimeSetupOrElevatedHandlerTests
+ {
+ private readonly Mock<IConfigurationManager> _configurationManagerMock;
+ private readonly List<IAuthorizationRequirement> _requirements;
+ private readonly FirstTimeSetupOrElevatedHandler _sut;
+
+ public FirstTimeSetupOrElevatedHandlerTests()
+ {
+ var fixture = new Fixture().Customize(new AutoMoqCustomization());
+ _configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>();
+ _requirements = new List<IAuthorizationRequirement> {new FirstTimeSetupOrElevatedRequirement()};
+
+ _sut = fixture.Create<FirstTimeSetupOrElevatedHandler>();
+ }
+
+ [Theory]
+ [InlineData(UserRoles.Administrator)]
+ [InlineData(UserRoles.Guest)]
+ [InlineData(UserRoles.User)]
+ public async Task ShouldSucceedIfStartupWizardIncomplete(string userRole)
+ {
+ SetupConfigurationManager(false);
+ var user = SetupUser(userRole);
+ var context = new AuthorizationHandlerContext(_requirements, user, null);
+
+ await _sut.HandleAsync(context);
+ Assert.True(context.HasSucceeded);
+ }
+
+ [Theory]
+ [InlineData(UserRoles.Administrator, true)]
+ [InlineData(UserRoles.Guest, false)]
+ [InlineData(UserRoles.User, false)]
+ public async Task ShouldRequireAdministratorIfStartupWizardComplete(string userRole, bool shouldSucceed)
+ {
+ SetupConfigurationManager(true);
+ var user = SetupUser(userRole);
+ var context = new AuthorizationHandlerContext(_requirements, user, null);
+
+ await _sut.HandleAsync(context);
+ Assert.Equal(shouldSucceed, context.HasSucceeded);
+ }
+
+ private static ClaimsPrincipal SetupUser(string role)
+ {
+ var claims = new[] {new Claim(ClaimTypes.Role, role)};
+ var identity = new ClaimsIdentity(claims);
+ return new ClaimsPrincipal(identity);
+ }
+
+ private void SetupConfigurationManager(bool startupWizardCompleted)
+ {
+ var commonConfiguration = new BaseApplicationConfiguration
+ {
+ IsStartupWizardCompleted = startupWizardCompleted
+ };
+
+ _configurationManagerMock.Setup(c => c.CommonConfiguration)
+ .Returns(commonConfiguration);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs
new file mode 100644
index 000000000..e2beea1ad
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Jellyfin.Api.Auth.RequiresElevationPolicy;
+using Jellyfin.Api.Constants;
+using Microsoft.AspNetCore.Authorization;
+using Xunit;
+
+namespace Jellyfin.Api.Tests.Auth.RequiresElevationPolicy
+{
+ public class RequiresElevationHandlerTests
+ {
+ private readonly RequiresElevationHandler _sut;
+
+ public RequiresElevationHandlerTests()
+ {
+ _sut = new RequiresElevationHandler();
+ }
+
+ [Theory]
+ [InlineData(UserRoles.Administrator, true)]
+ [InlineData(UserRoles.User, false)]
+ [InlineData(UserRoles.Guest, false)]
+ public async Task ShouldHandleRolesCorrectly(string role, bool shouldSucceed)
+ {
+ var requirements = new List<IAuthorizationRequirement> {new RequiresElevationRequirement()};
+
+ var claims = new[] {new Claim(ClaimTypes.Role, role)};
+ var identity = new ClaimsIdentity(claims);
+ var user = new ClaimsPrincipal(identity);
+
+ var context = new AuthorizationHandlerContext(requirements, user, null);
+
+ await _sut.HandleAsync(context);
+ Assert.Equal(shouldSucceed, context.HasSucceeded);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 1671b8d79..0e8ef135e 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -1,20 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
+ <PackageReference Include="AutoFixture" Version="4.11.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.11.0" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.1.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.0" />
<PackageReference Include="Moq" Version="4.13.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../MediaBrowser.Api/MediaBrowser.Api.csproj" />
+ <ProjectReference Include="../../Jellyfin.Api/Jellyfin.Api.csproj" />
</ItemGroup>
</Project>
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index aa005b31d..da5e6576d 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
@@ -9,7 +9,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.1.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
index 12fde0770..c46c9578b 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
@@ -26,7 +26,7 @@ libswscale 5. 5.100 / 5. 5.100
libswresample 3. 5.100 / 3. 5.100
libpostproc 55. 5.100 / 55. 5.100";
- public const string FFmpegV414Output = @"ffmpeg version 4.1.4-1~deb10u1 Copyright (c) 2000-2019 the FFmpeg developers
+ public const string FFmpegV414Output = @"ffmpeg version 4.1.4-1~deb10u1 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 8 (Raspbian 8.3.0-6+rpi1)
configuration: --prefix=/usr --extra-version='1~deb10u1' --toolchain=hardened --libdir=/usr/lib/arm-linux-gnueabihf --incdir=/usr/include/arm-linux-gnueabihf --arch=arm --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
libavutil 56. 22.100 / 56. 22.100
@@ -39,7 +39,7 @@ libswscale 5. 3.100 / 5. 3.100
libswresample 3. 3.100 / 3. 3.100
libpostproc 55. 3.100 / 55. 3.100";
- public const string FFmpegV404Output = @"ffmpeg version 4.0.4 Copyright (c) 2000-2019 the FFmpeg developers
+ public const string FFmpegV404Output = @"ffmpeg version 4.0.4 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 8 (Debian 8.3.0-6)
configuration: --toolchain=hardened --prefix=/usr --target-os=linux --enable-cross-compile --extra-cflags=--static --enable-gpl --enable-static --disable-doc --disable-ffplay --disable-shared --disable-libxcb --disable-sdl2 --disable-xlib --enable-libfontconfig --enable-fontconfig --enable-gmp --enable-gnutls --enable-libass --enable-libbluray --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libx264 --enable-libx265 --enable-libzvbi --enable-omx --enable-omx-rpi --enable-version3 --enable-vaapi --enable-vdpau --arch=amd64 --enable-nvenc --enable-nvdec
libavutil 56. 14.100 / 56. 14.100
@@ -51,7 +51,7 @@ libswscale 5. 1.100 / 5. 1.100
libswresample 3. 1.100 / 3. 1.100
libpostproc 55. 1.100 / 55. 1.100";
- public const string FFmpegGitUnknownOutput = @"ffmpeg version N-94303-g7cb4f8c962 Copyright (c) 2000-2019 the FFmpeg developers
+ public const string FFmpegGitUnknownOutput = @"ffmpeg version N-94303-g7cb4f8c962 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 9.1.1 (GCC) 20190716
configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
libavutil 56. 30.100 / 56. 30.100
diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
new file mode 100644
index 000000000..2032f6cec
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
@@ -0,0 +1,22 @@
+using System.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+using MediaBrowser.MediaEncoding.Probing;
+using Xunit;
+
+namespace Jellyfin.MediaEncoding.Tests
+{
+ public class FFprobeParserTests
+ {
+ [Theory]
+ [InlineData("ffprobe1.json")]
+ public async Task Test(string fileName)
+ {
+ var path = Path.Join("Test Data", fileName);
+ using (var stream = File.OpenRead(path))
+ {
+ await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream).ConfigureAwait(false);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 70e2d1851..c01edd9fe 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -1,15 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
+ <None Include="Test Data\**\*.*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.1.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json
new file mode 100644
index 000000000..cdad5df50
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json
@@ -0,0 +1,105 @@
+{
+ "streams": [
+ {
+ "index": 0,
+ "codec_name": "h264",
+ "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
+ "profile": "Main",
+ "codec_type": "video",
+ "codec_time_base": "1/50",
+ "codec_tag_string": "[27][0][0][0]",
+ "codec_tag": "0x001b",
+ "width": 1920,
+ "height": 1080,
+ "coded_width": 1920,
+ "coded_height": 1080,
+ "has_b_frames": 0,
+ "sample_aspect_ratio": "0:1",
+ "display_aspect_ratio": "0:1",
+ "pix_fmt": "yuvj420p",
+ "level": 42,
+ "color_range": "pc",
+ "color_space": "bt709",
+ "color_transfer": "bt709",
+ "color_primaries": "bt709",
+ "chroma_location": "left",
+ "field_order": "progressive",
+ "refs": 1,
+ "is_avc": "false",
+ "nal_length_size": "0",
+ "id": "0x1",
+ "r_frame_rate": "25/1",
+ "avg_frame_rate": "25/1",
+ "time_base": "1/90000",
+ "start_pts": 8570867078,
+ "start_time": "95231.856422",
+ "duration_ts": 31694552,
+ "duration": "352.161689",
+ "bits_per_raw_sample": "8",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ }
+ },
+ {
+ "index": 1,
+ "codec_name": "aac",
+ "codec_long_name": "AAC (Advanced Audio Coding)",
+ "profile": "LC",
+ "codec_type": "audio",
+ "codec_time_base": "1/44100",
+ "codec_tag_string": "[15][0][0][0]",
+ "codec_tag": "0x000f",
+ "sample_fmt": "fltp",
+ "sample_rate": "44100",
+ "channels": 2,
+ "channel_layout": "stereo",
+ "bits_per_sample": 0,
+ "id": "0x2",
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 8570867697,
+ "start_time": "95231.863300",
+ "duration_ts": 31695687,
+ "duration": "352.174300",
+ "bit_rate": "98191",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ }
+ }
+ ],
+ "format": {
+ "filename": "TS Test record.ts",
+ "nb_streams": 2,
+ "nb_programs": 1,
+ "format_name": "mpegts",
+ "format_long_name": "MPEG-TS (MPEG-2 Transport Stream)",
+ "start_time": "95231.856422",
+ "duration": "352.181178",
+ "size": "179003772",
+ "bit_rate": "4066174",
+ "probe_score": 50
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index fe1518131..f246d459b 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
@@ -9,7 +9,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.1.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
new file mode 100644
index 000000000..a79e2cf61
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
@@ -0,0 +1,66 @@
+using Emby.Naming.Audio;
+using Emby.Naming.Common;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Music
+{
+ public class MultiDiscAlbumTests
+ {
+ [Fact]
+ public void TestMultiDiscAlbums()
+ {
+ Assert.False(IsMultiDiscAlbumFolder(@"blah blah"));
+ Assert.False(IsMultiDiscAlbumFolder(@"D:/music/weezer/03 Pinkerton"));
+ Assert.False(IsMultiDiscAlbumFolder(@"D:/music/michael jackson/Bad (2012 Remaster)"));
+
+ Assert.True(IsMultiDiscAlbumFolder(@"cd1"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disc18"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disk10"));
+ Assert.True(IsMultiDiscAlbumFolder(@"vol7"));
+ Assert.True(IsMultiDiscAlbumFolder(@"volume1"));
+
+ Assert.True(IsMultiDiscAlbumFolder(@"cd 1"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disc 1"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disk 1"));
+
+ Assert.False(IsMultiDiscAlbumFolder(@"disk"));
+ Assert.False(IsMultiDiscAlbumFolder(@"disk ·"));
+ Assert.False(IsMultiDiscAlbumFolder(@"disk a"));
+
+ Assert.False(IsMultiDiscAlbumFolder(@"disk volume"));
+ Assert.False(IsMultiDiscAlbumFolder(@"disc disc"));
+ Assert.False(IsMultiDiscAlbumFolder(@"disk disc 6"));
+
+ Assert.True(IsMultiDiscAlbumFolder(@"cd - 1"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disc- 1"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disk - 1"));
+
+ Assert.True(IsMultiDiscAlbumFolder(@"Disc 01 (Hugo Wolf · 24 Lieder)"));
+ Assert.True(IsMultiDiscAlbumFolder(@"Disc 04 (Encores and Folk Songs)"));
+ Assert.True(IsMultiDiscAlbumFolder(@"Disc04 (Encores and Folk Songs)"));
+ Assert.True(IsMultiDiscAlbumFolder(@"Disc 04(Encores and Folk Songs)"));
+ Assert.True(IsMultiDiscAlbumFolder(@"Disc04(Encores and Folk Songs)"));
+
+ Assert.True(IsMultiDiscAlbumFolder(@"D:/Video/MBTestLibrary/VideoTest/music/.38 special/anth/Disc 2"));
+ }
+
+ [Fact]
+ public void TestMultiDiscAlbums1()
+ {
+ Assert.False(IsMultiDiscAlbumFolder(@"[1985] Opportunities (Let's make lots of money) (1985)"));
+ }
+
+ [Fact]
+ public void TestMultiDiscAlbums2()
+ {
+ Assert.False(IsMultiDiscAlbumFolder(@"Blah 04(Encores and Folk Songs)"));
+ }
+
+ private bool IsMultiDiscAlbumFolder(string path)
+ {
+ var parser = new AlbumParser(new NamingOptions());
+
+ return parser.ParseMultiPart(path).IsMultiPart;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
new file mode 100644
index 000000000..41da889c2
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
@@ -0,0 +1,39 @@
+using Emby.Naming.Common;
+using Emby.Naming.Subtitles;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Subtitles
+{
+ public class SubtitleParserTests
+ {
+ private SubtitleParser GetParser()
+ {
+ var options = new NamingOptions();
+
+ return new SubtitleParser(options);
+ }
+
+ [Fact]
+ public void TestSubtitles()
+ {
+ Test("The Skin I Live In (2011).srt", null, false, false);
+ Test("The Skin I Live In (2011).eng.srt", "eng", false, false);
+ Test("The Skin I Live In (2011).eng.default.srt", "eng", true, false);
+ Test("The Skin I Live In (2011).eng.forced.srt", "eng", false, true);
+ Test("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true);
+ Test("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true);
+ Test("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true);
+ }
+
+ private void Test(string input, string language, bool isDefault, bool isForced)
+ {
+ var parser = GetParser();
+
+ var result = parser.ParseFile(input);
+
+ Assert.Equal(language, result.Language, true);
+ Assert.Equal(isDefault, result.IsDefault);
+ Assert.Equal(isForced, result.IsForced);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs
new file mode 100644
index 000000000..9abbcc7bf
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs
@@ -0,0 +1,61 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class AbsoluteEpisodeNumberTests
+ {
+ [Fact]
+ public void TestAbsoluteEpisodeNumber1()
+ {
+ Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/12.avi"));
+ }
+
+ [Fact]
+ public void TestAbsoluteEpisodeNumber2()
+ {
+ Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 12.avi"));
+ }
+
+ [Fact]
+ public void TestAbsoluteEpisodeNumber3()
+ {
+ Assert.Equal(82, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 82.avi"));
+ }
+
+ [Fact]
+ public void TestAbsoluteEpisodeNumber4()
+ {
+ Assert.Equal(112, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 112.avi"));
+ }
+
+ [Fact]
+ public void TestAbsoluteEpisodeNumber5()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/Foo_ep_02.avi"));
+ }
+
+ [Fact]
+ public void TestAbsoluteEpisodeNumber6()
+ {
+ Assert.Equal(889, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 889.avi"));
+ }
+
+ [Fact]
+ public void TestAbsoluteEpisodeNumber7()
+ {
+ Assert.Equal(101, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 101.avi"));
+ }
+
+ private int? GetEpisodeNumberFromFile(string path)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodeResolver(options)
+ .Resolve(path, false, null, null, true);
+
+ return result.EpisodeNumber;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs
new file mode 100644
index 000000000..29daf8cc3
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs
@@ -0,0 +1,69 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class DailyEpisodeTests
+ {
+ [Fact]
+ public void TestDailyEpisode1()
+ {
+ Test(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14);
+ }
+
+ [Fact]
+ public void TestDailyEpisode2()
+ {
+ Test(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestDailyEpisode3()
+ {
+ Test(@"/server/anything_14.11.1996.mp4", "anything", 1996, 11, 14);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestDailyEpisode4()
+ {
+ Test(@"/server/A Daily Show - (2015-01-15) - Episode Name - [720p].mkv", "A Daily Show", 2015, 01, 15);
+ }
+
+ [Fact]
+ public void TestDailyEpisode5()
+ {
+ Test(@"/server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv", "james.corden", 2017, 04, 20);
+ }
+
+ [Fact]
+ public void TestDailyEpisode6()
+ {
+ Test(@"/server/ABC News 2018_03_24_19_00_00.mkv", "ABC News", 2018, 03, 24);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestDailyEpisode7()
+ {
+ Test(@"/server/Last Man Standing_KTLADT_2018_05_25_01_28_00.wtv", "Last Man Standing", 2018, 05, 25);
+ }
+
+ private void Test(string path, string seriesName, int? year, int? month, int? day)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodeResolver(options)
+ .Resolve(path, false);
+
+ Assert.Null(result.SeasonNumber);
+ Assert.Null(result.EpisodeNumber);
+ Assert.Equal(year, result.Year);
+ Assert.Equal(month, result.Month);
+ Assert.Equal(day, result.Day);
+ Assert.Equal(seriesName, result.SeriesName, true);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
new file mode 100644
index 000000000..93c59c9ca
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
@@ -0,0 +1,393 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class EpisodeNumberTests
+ {
+ [Fact]
+ public void TestEpisodeNumber1()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/S02E03 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber40()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber41()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/01x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber42()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber43()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber44()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber45()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber46()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber47()
+ {
+ Assert.Equal(36, GetEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber52()
+ {
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode - 16.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber53()
+ {
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber54()
+ {
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16 - Some Title.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber57()
+ {
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 Some Title.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber58()
+ {
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 - 12 Some Title.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber59()
+ {
+ Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 - 12 Angry Men.avi"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumber60()
+ {
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 12 Some Title.avi"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumber61()
+ {
+ Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 12 Angry Men.avi"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumber62()
+ {
+ // This is not supported. Expected to fail, although it would be a good one to add support for.
+ Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.E03.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber63()
+ {
+ Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.S04E03.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber64()
+ {
+ Assert.Equal(368, GetEpisodeNumberFromFile(@"Running Man/Running Man S2017E368.mkv"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumber65()
+ {
+ // Not supported yet
+ Assert.Equal(7, GetEpisodeNumberFromFile(@"/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber30()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumber31()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber32()
+ {
+ Assert.Equal(9, GetEpisodeNumberFromFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber33()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber34()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber35()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber36()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber37()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber38()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber39()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber20()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber21()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber22()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber23()
+ {
+ Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber24()
+ {
+ Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber25()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber26()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber27()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumber28()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber29()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber11()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber12()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber13()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber14()
+ {
+ Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber15()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber16()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber17()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber18()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber19()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber2()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber3()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber4()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber5()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber6()
+ {
+ Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber7()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber8()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber9()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber10()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber48()
+ {
+ Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/2. Infestation.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber49()
+ {
+ Assert.Equal(7, GetEpisodeNumberFromFile(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi"));
+ }
+
+ private int? GetEpisodeNumberFromFile(string path)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodePathParser(options)
+ .Parse(path, false);
+
+ return result.EpisodeNumber;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs
new file mode 100644
index 000000000..00aa9ee7c
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs
@@ -0,0 +1,127 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class EpisodeNumberWithoutSeasonTests
+ {
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason1()
+ {
+ Assert.Equal(8, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason2()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 - Ep Name.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason3()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason4()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02 - Ep Name.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason5()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02-Ep Name.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason6()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.EpName.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason7()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason8()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 Ep Name.avi"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumberWithoutSeason9()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 - Ep Name.avi"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumberWithoutSeason10()
+ {
+ Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 Ep Name.avi"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumberWithoutSeason11()
+ {
+ Assert.Equal(7, GetEpisodeNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi"));
+ Assert.Equal(8, GetSeasonNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason12()
+ {
+ Assert.Equal(7, GetEpisodeNumberFromFile(@"GJ Club (2013)/GJ Club - 07.mkv"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumberWithoutSeason13()
+ {
+ // This is not supported anymore after removing the episode number 365+ hack from EpisodePathParser
+ Assert.Equal(13, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 13.mkv"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason14()
+ {
+ Assert.Equal(3, GetSeasonNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv"));
+ Assert.Equal(17, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumberWithoutSeason15()
+ {
+ Assert.Equal(2017, GetSeasonNumberFromFile(@"Running Man/Running Man S2017E368.mkv"));
+ }
+
+ private int? GetEpisodeNumberFromFile(string path)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodeResolver(options)
+ .Resolve(path, false);
+
+ return result.EpisodeNumber;
+ }
+
+ private int? GetSeasonNumberFromFile(string path)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodeResolver(options)
+ .Resolve(path, false);
+
+ return result.SeasonNumber;
+ }
+
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs
index dd1e04215..da6e99310 100644
--- a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs
@@ -2,7 +2,7 @@ using Emby.Naming.Common;
using Emby.Naming.TV;
using Xunit;
-namespace Jellyfin.Naming.Tests
+namespace Jellyfin.Naming.Tests.TV
{
public class EpisodePathParserTest
{
@@ -11,6 +11,10 @@ namespace Jellyfin.Naming.Tests
[InlineData("/media/Foo - S04E011", "Foo", 4, 11)]
[InlineData("/media/Foo/Foo s01x01", "Foo", 1, 1)]
[InlineData("/media/Foo (2019)/Season 4/Foo (2019).S04E03", "Foo (2019)", 4, 3)]
+ [InlineData("D:\\media\\Foo\\Foo-S01E01", "Foo", 1, 1)]
+ [InlineData("D:\\media\\Foo - S04E011", "Foo", 4, 11)]
+ [InlineData("D:\\media\\Foo\\Foo s01x01", "Foo", 1, 1)]
+ [InlineData("D:\\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();
@@ -21,18 +25,13 @@ namespace Jellyfin.Naming.Tests
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", "Foo", 889)]
[InlineData("/media/Foo/[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)]
+ [InlineData("D:\\media\\Foo\\Foo 889", "Foo", 889)]
+ [InlineData("D:\\media\\Foo\\[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)]
public void ParseEpisodeWithoutSeason(string path, string name, int episode)
{
NamingOptions o = new NamingOptions();
@@ -43,13 +42,6 @@ namespace Jellyfin.Naming.Tests
Assert.Equal(name, res.SeriesName);
Assert.Null(res.SeasonNumber);
Assert.Equal(episode, res.EpisodeNumber);
-
- // testing other paths delimeter
- var res2 = p.Parse(path.Replace('/', '\\'), false, fillExtendedInfo: false);
- Assert.True(res2.Success);
- Assert.Equal(name, res2.SeriesName);
- Assert.Null(res2.SeasonNumber);
- Assert.Equal(episode, res2.EpisodeNumber);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs
new file mode 100644
index 000000000..c2851ccdb
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs
@@ -0,0 +1,56 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class EpisodeWithoutSeasonTests
+ {
+ // FIXME
+ // [Fact]
+ public void TestWithoutSeason1()
+ {
+ Test(@"/server/anything_ep02.mp4", "anything", null, 2);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestWithoutSeason2()
+ {
+ Test(@"/server/anything_ep_02.mp4", "anything", null, 2);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestWithoutSeason3()
+ {
+ Test(@"/server/anything_part.II.mp4", "anything", null, null);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestWithoutSeason4()
+ {
+ Test(@"/server/anything_pt.II.mp4", "anything", null, null);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestWithoutSeason5()
+ {
+ Test(@"/server/anything_pt_II.mp4", "anything", null, null);
+ }
+
+ private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodeResolver(options)
+ .Resolve(path, false);
+
+ Assert.Equal(seasonNumber, result.SeasonNumber);
+ Assert.Equal(episodeNumber, result.EpisodeNumber);
+ Assert.Equal(seriesName, result.SeriesName, true);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
new file mode 100644
index 000000000..b15dd6b74
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
@@ -0,0 +1,105 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class MultiEpisodeTests
+ {
+ [Fact]
+ public void TestGetEndingEpisodeNumberFromFile()
+ {
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/4x01 – 20 Hours in America (1).mkv"));
+
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/01x02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 04 Ep Name.mp4"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/My show name 02x03 - 04 Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4"));
+ Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4"));
+ Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4"));
+
+
+ // Four Digits seasons
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4"));
+ Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4"));
+ Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4"));
+ Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4"));
+
+ // Without season number
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02.avi"));
+
+ Assert.Equal(3, GetEndingEpisodeNumberFromFile(@"Season 1/02-03 - blah.avi"));
+ Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04 - blah 14 blah.avi"));
+ Assert.Equal(5, GetEndingEpisodeNumberFromFile(@"Season 1/02-05 - blah-02 a.avi"));
+ Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04.avi"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv"));
+
+ // With format specification that must not be detected as ending episode number
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-1080p.mkv"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720p.mkv"));
+ Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720i.mkv"));
+ Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 1/MOONLIGHTING_s01e01-e04.mkv"));
+ }
+
+ [Fact]
+ public void TestGetEndingEpisodeNumberFromFolder()
+ {
+ Assert.Equal(4, GetEndingEpisodeNumberFromFolder(@"Season 1/MOONLIGHTING_s01e01-e04"));
+ }
+
+ private int? GetEndingEpisodeNumberFromFolder(string path)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodePathParser(options)
+ .Parse(path, true);
+
+ return result.EndingEpsiodeNumber;
+ }
+
+ private int? GetEndingEpisodeNumberFromFile(string path)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodePathParser(options)
+ .Parse(path, false);
+
+ return result.EndingEpsiodeNumber;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs
new file mode 100644
index 000000000..ffa8d3483
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs
@@ -0,0 +1,112 @@
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class SeasonFolderTests
+ {
+ [Fact]
+ public void TestGetSeasonNumberFromPath1()
+ {
+ Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath2()
+ {
+ Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 2"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath3()
+ {
+ Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 02"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath4()
+ {
+ Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath5()
+ {
+ Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/S02"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath6()
+ {
+ Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/2"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath7()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromPath(@"/Drive/Season 2009"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath8()
+ {
+ Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season1"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath9()
+ {
+ Assert.Equal(4, GetSeasonNumberFromPath(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath10()
+ {
+ Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Season 7 (2016)"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath11()
+ {
+ Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Staffel 7 (2016)"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath12()
+ {
+ Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Stagione 7 (2016)"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath14()
+ {
+ Assert.Null(GetSeasonNumberFromPath(@"/Drive/Season (8)"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath13()
+ {
+ Assert.Equal(3, GetSeasonNumberFromPath(@"/Drive/3.Staffel"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath15()
+ {
+ Assert.Null(GetSeasonNumberFromPath(@"/Drive/s06e05"));
+ }
+
+ [Fact]
+ public void TestGetSeasonNumberFromPath16()
+ {
+ Assert.Null(GetSeasonNumberFromPath(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv"));
+ }
+
+ private int? GetSeasonNumberFromPath(string path)
+ {
+ var result = new SeasonPathParser()
+ .Parse(path, true, true);
+
+ return result.SeasonNumber;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs
new file mode 100644
index 000000000..ba3c5ecac
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs
@@ -0,0 +1,305 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class SeasonNumberTests
+ {
+ private int? GetSeasonNumberFromEpisodeFile(string path)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodeResolver(options)
+ .Resolve(path, false);
+
+ return result.SeasonNumber;
+ }
+
+ [Fact]
+ public void TestSeasonNumber1()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"/Show/Season 02/S02E03 blah.avi"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber2()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber3()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber4()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01xE02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber5()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/01x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber6()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber7()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01xE02 blah.avi"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestSeasonNumber8()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname 01x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber9()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber10()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01E02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber11()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber12()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber13()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03-04-15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber14()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber15()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03-E15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber16()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber17()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber18()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber19()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber20()
+ {
+ Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber21()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber22()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber23()
+ {
+ Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber24()
+ {
+ Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"The Simpsons/The Simpsons.S25E09.Steal this episode.mp4"));
+ }
+
+ [Fact]
+ public void TestSeasonNumber25()
+ {
+ Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season s2016e1.mp4"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestSeasonNumber26()
+ {
+ // This convention is not currently supported, just adding in case we want to look at it in the future
+ Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season 2016x1.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber1()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber2()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber3()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber4()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009xE02 blah.avi"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestFourDigitSeasonNumber5()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname 2009x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber6()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009x02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber7()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009E02 blah.avi"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber8()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber9()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber10()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber11()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber12()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber13()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber14()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber15()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber16()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber17()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber18()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber19()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4"));
+ }
+
+ [Fact]
+ public void TestFourDigitSeasonNumber20()
+ {
+ Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4"));
+ }
+
+ [Fact]
+ public void TestNoSeriesFolder()
+ {
+ Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Series/1-12 - The Woman.mp4"));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
new file mode 100644
index 000000000..c9323c218
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
@@ -0,0 +1,95 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class SimpleEpisodeTests
+ {
+ [Fact]
+ public void TestSimpleEpisodePath1()
+ {
+ Test(@"/server/anything_s01e02.mp4", "anything", 1, 2);
+ }
+
+ [Fact]
+ public void TestSimpleEpisodePath2()
+ {
+ Test(@"/server/anything_s1e2.mp4", "anything", 1, 2);
+ }
+
+ [Fact]
+ public void TestSimpleEpisodePath3()
+ {
+ Test(@"/server/anything_s01.e02.mp4", "anything", 1, 2);
+ }
+
+ [Fact]
+ public void TestSimpleEpisodePath4()
+ {
+ Test(@"/server/anything_s01_e02.mp4", "anything", 1, 2);
+ }
+
+ [Fact]
+ public void TestSimpleEpisodePath5()
+ {
+ Test(@"/server/anything_102.mp4", "anything", 1, 2);
+ }
+
+ [Fact]
+ public void TestSimpleEpisodePath6()
+ {
+ Test(@"/server/anything_1x02.mp4", "anything", 1, 2);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestSimpleEpisodePath7()
+ {
+ Test(@"/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1);
+ }
+
+ [Fact]
+ public void TestSimpleEpisodePath8()
+ {
+ Test(@"/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1);
+ }
+
+
+ [Fact]
+ public void TestSimpleEpisodePath9()
+ {
+ Test(@"/server/Temp/S01E02 foo.mp4", string.Empty, 1, 2);
+ }
+
+ [Fact]
+ public void TestSimpleEpisodePath10()
+ {
+ Test(@"Series/4-12 - The Woman.mp4", string.Empty, 4, 12);
+ }
+
+ [Fact]
+ public void TestSimpleEpisodePath11()
+ {
+ Test(@"Series/4x12 - The Woman.mp4", string.Empty, 4, 12);
+ }
+
+ [Fact]
+ public void TestSimpleEpisodePath12()
+ {
+ Test(@"Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32);
+ }
+
+ private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber)
+ {
+ var options = new NamingOptions();
+
+ var result = new EpisodeResolver(options)
+ .Resolve(path, false);
+
+ Assert.Equal(seasonNumber, result.SeasonNumber);
+ Assert.Equal(episodeNumber, result.EpisodeNumber);
+ Assert.Equal(seriesName, result.SeriesName, true);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs
new file mode 100644
index 000000000..0c2978aca
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs
@@ -0,0 +1,13 @@
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public abstract class BaseVideoTest
+ {
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ protected VideoResolver GetParser()
+ => new VideoResolver(_namingOptions);
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
new file mode 100644
index 000000000..a2ef2dcd6
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
@@ -0,0 +1,59 @@
+using System.IO;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public sealed class CleanDateTimeTests
+ {
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData(@"The Wolf of Wall Street (2013).mkv", "The Wolf of Wall Street", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2 (2013).mkv", "The Wolf of Wall Street 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street - 2 (2013).mkv", "The Wolf of Wall Street - 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2001 (2013).mkv", "The Wolf of Wall Street 2001", 2013)]
+ [InlineData(@"300 (2006).mkv", "300", 2006)]
+ [InlineData(@"d:/movies/300 (2006).mkv", "300", 2006)]
+ [InlineData(@"300 2 (2006).mkv", "300 2", 2006)]
+ [InlineData(@"300 - 2 (2006).mkv", "300 - 2", 2006)]
+ [InlineData(@"300 2001 (2006).mkv", "300 2001", 2006)]
+ [InlineData(@"curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", "curse.of.chucky", 2013)]
+ [InlineData(@"curse.of.chucky.2013.stv.unrated.multi.2160p.bluray.x264-rough", "curse.of.chucky", 2013)]
+ [InlineData(@"/server/Movies/300 (2007)/300 (2006).bluray.disc", "300", 2006)]
+ [InlineData(@"Arrival.2016.2160p.Blu-Ray.HEVC.mkv", "Arrival", 2016)]
+ [InlineData(@"The Wolf of Wall Street (2013)", "The Wolf of Wall Street", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2 (2013)", "The Wolf of Wall Street 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street - 2 (2013)", "The Wolf of Wall Street - 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2001 (2013)", "The Wolf of Wall Street 2001", 2013)]
+ [InlineData(@"300 (2006)", "300", 2006)]
+ [InlineData(@"d:/movies/300 (2006)", "300", 2006)]
+ [InlineData(@"300 2 (2006)", "300 2", 2006)]
+ [InlineData(@"300 - 2 (2006)", "300 - 2", 2006)]
+ [InlineData(@"300 2001 (2006)", "300 2001", 2006)]
+ [InlineData(@"/server/Movies/300 (2007)/300 (2006)", "300", 2006)]
+ [InlineData(@"/server/Movies/300 (2007)/300 (2006).mkv", "300", 2006)]
+ [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv", null)]
+ [InlineData(@"American Psycho.mkv", "American Psycho.mkv", null)]
+ [InlineData(@"[rec].mkv", "[rec].mkv", null)]
+ [InlineData(@"St. Vincent (2014)", "St. Vincent", 2014)]
+ [InlineData("Super movie(2009).mp4", "Super movie", 2009)]
+ // FIXME: [InlineData("Drug War 2013.mp4", "Drug War", 2013)]
+ [InlineData("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997)]
+ // FIXME: [InlineData("First Man 2018 1080p.mkv", "First Man", 2018)]
+ [InlineData("First Man (2018) 1080p.mkv", "First Man", 2018)]
+ // FIXME: [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
+ // FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
+ [InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
+ public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
+ {
+ input = Path.GetFileName(input);
+
+ var result = new VideoResolver(_namingOptions).CleanDateTime(input);
+
+ Assert.Equal(expectedName, result.Name, true);
+ Assert.Equal(expectedYear, result.Year);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
new file mode 100644
index 000000000..fde06c5a1
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
@@ -0,0 +1,45 @@
+using System;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public sealed class CleanStringTests
+ {
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData("Super movie 480p.mp4", "Super movie")]
+ [InlineData("Super movie 480p 2001.mp4", "Super movie")]
+ [InlineData("Super movie [480p].mp4", "Super movie")]
+ [InlineData("480 Super movie [tmdbid=12345].mp4", "480 Super movie")]
+ [InlineData("Super movie(2009).mp4", "Super movie(2009).mp4")]
+ [InlineData("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4")]
+ [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv")]
+ [InlineData(@"American Psycho.mkv", "American Psycho.mkv")]
+ [InlineData(@"[rec].mkv", "[rec].mkv")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.HDR.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ // FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")]
+ public void CleanStringTest(string input, string expectedName)
+ {
+ if (new VideoResolver(_namingOptions).TryCleanString(input, out ReadOnlySpan<char> newName))
+ {
+ // TODO: compare spans when XUnit supports it
+ Assert.Equal(expectedName, newName.ToString());
+ }
+ else
+ {
+ Assert.Equal(expectedName, input);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
new file mode 100644
index 000000000..1646237a0
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
@@ -0,0 +1,77 @@
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using MediaBrowser.Model.Entities;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public class ExtraTests : BaseVideoTest
+ {
+ // Requirements
+ // movie-deleted = ExtraType deletedscene
+
+ // All of the above rules should be configurable through the options objects (ideally, even the ExtraTypes)
+
+ [Fact]
+ public void TestKodiExtras()
+ {
+ var videoOptions = new NamingOptions();
+
+ Test("trailer.mp4", ExtraType.Trailer, videoOptions);
+ Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
+
+ Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
+ }
+
+ [Fact]
+ public void TestExpandedExtras()
+ {
+ var videoOptions = new NamingOptions();
+
+ Test("trailer.mp4", ExtraType.Trailer, videoOptions);
+ Test("trailer.mp3", null, videoOptions);
+ Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
+
+ Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
+ Test("theme.mkv", null, videoOptions);
+
+ Test("300-scene.mp4", ExtraType.Scene, videoOptions);
+ Test("300-scene2.mp4", ExtraType.Scene, videoOptions);
+ Test("300-clip.mp4", ExtraType.Clip, videoOptions);
+
+ Test("300-deleted.mp4", ExtraType.DeletedScene, videoOptions);
+ Test("300-deletedscene.mp4", ExtraType.DeletedScene, videoOptions);
+ Test("300-interview.mp4", ExtraType.Interview, videoOptions);
+ Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, videoOptions);
+ }
+
+ [Fact]
+ public void TestSample()
+ {
+ var videoOptions = new NamingOptions();
+
+ Test("300-sample.mp4", ExtraType.Sample, videoOptions);
+ }
+
+ private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions)
+ {
+ var parser = GetExtraTypeParser(videoOptions);
+
+ var extraType = parser.GetExtraInfo(input).ExtraType;
+
+ if (expectedType == null)
+ {
+ Assert.Null(extraType);
+ }
+ else
+ {
+ Assert.Equal(expectedType, extraType);
+ }
+ }
+
+ private ExtraResolver GetExtraTypeParser(NamingOptions videoOptions)
+ {
+ return new ExtraResolver(videoOptions);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs
new file mode 100644
index 000000000..ed3112936
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs
@@ -0,0 +1,78 @@
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public class Format3DTests : BaseVideoTest
+ {
+ [Fact]
+ public void TestKodiFormat3D()
+ {
+ var options = new NamingOptions();
+
+ Test("Super movie.3d.mp4", false, null, options);
+ Test("Super movie.3d.hsbs.mp4", true, "hsbs", options);
+ Test("Super movie.3d.sbs.mp4", true, "sbs", options);
+ Test("Super movie.3d.htab.mp4", true, "htab", options);
+ Test("Super movie.3d.tab.mp4", true, "tab", options);
+ Test("Super movie 3d hsbs.mp4", true, "hsbs", options);
+ }
+
+ [Fact]
+ public void Test3DName()
+ {
+ var result =
+ GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.3d.hsbs.mkv");
+
+ Assert.Equal("hsbs", result.Format3D);
+ Assert.Equal("Oblivion", result.Name);
+ }
+
+ [Fact]
+ public void TestExpandedFormat3D()
+ {
+ // These were introduced for Media Browser 3
+ // Kodi conventions are preferred but these still need to be supported
+ var options = new NamingOptions();
+
+ Test("Super movie.3d.mp4", false, null, options);
+ Test("Super movie.3d.hsbs.mp4", true, "hsbs", options);
+ Test("Super movie.3d.sbs.mp4", true, "sbs", options);
+ Test("Super movie.3d.htab.mp4", true, "htab", options);
+ Test("Super movie.3d.tab.mp4", true, "tab", options);
+
+ Test("Super movie.hsbs.mp4", true, "hsbs", options);
+ Test("Super movie.sbs.mp4", true, "sbs", options);
+ Test("Super movie.htab.mp4", true, "htab", options);
+ Test("Super movie.tab.mp4", true, "tab", options);
+ Test("Super movie.sbs3d.mp4", true, "sbs3d", options);
+ Test("Super movie.3d.mvc.mp4", true, "mvc", options);
+
+ Test("Super movie [3d].mp4", false, null, options);
+ Test("Super movie [hsbs].mp4", true, "hsbs", options);
+ Test("Super movie [fsbs].mp4", true, "fsbs", options);
+ Test("Super movie [ftab].mp4", true, "ftab", options);
+ Test("Super movie [htab].mp4", true, "htab", options);
+ Test("Super movie [sbs3d].mp4", true, "sbs3d", options);
+ }
+
+ private void Test(string input, bool is3D, string format3D, NamingOptions options)
+ {
+ var parser = new Format3DParser(options);
+
+ var result = parser.Parse(input);
+
+ Assert.Equal(is3D, result.Is3D);
+
+ if (format3D == null)
+ {
+ Assert.Null(result.Format3D);
+ }
+ else
+ {
+ Assert.Equal(format3D, result.Format3D, true);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
new file mode 100644
index 000000000..b8fbb2cb2
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
@@ -0,0 +1,437 @@
+using System.Linq;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using MediaBrowser.Model.IO;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public class MultiVersionTests
+ {
+ // FIXME
+ // [Fact]
+ public void TestMultiEdition1()
+ {
+ var files = new[]
+ {
+ @"/movies/X-Men Days of Future Past/X-Men Days of Future Past - 1080p.mkv",
+ @"/movies/X-Men Days of Future Past/X-Men Days of Future Past-trailer.mp4",
+ @"/movies/X-Men Days of Future Past/X-Men Days of Future Past - [hsbs].mkv",
+ @"/movies/X-Men Days of Future Past/X-Men Days of Future Past [hsbs].mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Single(result[0].Extras);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiEdition2()
+ {
+ var files = new[]
+ {
+ @"/movies/X-Men Days of Future Past/X-Men Days of Future Past - apple.mkv",
+ @"/movies/X-Men Days of Future Past/X-Men Days of Future Past-trailer.mp4",
+ @"/movies/X-Men Days of Future Past/X-Men Days of Future Past - banana.mkv",
+ @"/movies/X-Men Days of Future Past/X-Men Days of Future Past [banana].mp4"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Single(result[0].Extras);
+ Assert.Equal(2, result[0].AlternateVersions.Count);
+ }
+
+ [Fact]
+ public void TestMultiEdition3()
+ {
+ var files = new[]
+ {
+ @"/movies/The Phantom of the Opera (1925)/The Phantom of the Opera (1925) - 1925 version.mkv",
+ @"/movies/The Phantom of the Opera (1925)/The Phantom of the Opera (1925) - 1929 version.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Single(result[0].AlternateVersions);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestLetterFolders()
+ {
+ var files = new[]
+ {
+ @"/movies/M/Movie 1.mkv",
+ @"/movies/M/Movie 2.mkv",
+ @"/movies/M/Movie 3.mkv",
+ @"/movies/M/Movie 4.mkv",
+ @"/movies/M/Movie 5.mkv",
+ @"/movies/M/Movie 6.mkv",
+ @"/movies/M/Movie 7.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(7, result.Count);
+ Assert.Empty(result[0].Extras);
+ Assert.Empty(result[0].AlternateVersions);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersionLimit()
+ {
+ var files = new[]
+ {
+ @"/movies/Movie/Movie.mkv",
+ @"/movies/Movie/Movie-2.mkv",
+ @"/movies/Movie/Movie-3.mkv",
+ @"/movies/Movie/Movie-4.mkv",
+ @"/movies/Movie/Movie-5.mkv",
+ @"/movies/Movie/Movie-6.mkv",
+ @"/movies/Movie/Movie-7.mkv",
+ @"/movies/Movie/Movie-8.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Empty(result[0].Extras);
+ Assert.Equal(7, result[0].AlternateVersions.Count);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersionLimit2()
+ {
+ var files = new[]
+ {
+ @"/movies/Mo/Movie 1.mkv",
+ @"/movies/Mo/Movie 2.mkv",
+ @"/movies/Mo/Movie 3.mkv",
+ @"/movies/Mo/Movie 4.mkv",
+ @"/movies/Mo/Movie 5.mkv",
+ @"/movies/Mo/Movie 6.mkv",
+ @"/movies/Mo/Movie 7.mkv",
+ @"/movies/Mo/Movie 8.mkv",
+ @"/movies/Mo/Movie 9.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(9, result.Count);
+ Assert.Empty(result[0].Extras);
+ Assert.Empty(result[0].AlternateVersions);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersion3()
+ {
+ var files = new[]
+ {
+ @"/movies/Movie/Movie 1.mkv",
+ @"/movies/Movie/Movie 2.mkv",
+ @"/movies/Movie/Movie 3.mkv",
+ @"/movies/Movie/Movie 4.mkv",
+ @"/movies/Movie/Movie 5.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(5, result.Count);
+ Assert.Empty(result[0].Extras);
+ Assert.Empty(result[0].AlternateVersions);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersion4()
+ {
+ // Test for false positive
+
+ var files = new[]
+ {
+ @"/movies/Iron Man/Iron Man.mkv",
+ @"/movies/Iron Man/Iron Man (2008).mkv",
+ @"/movies/Iron Man/Iron Man (2009).mkv",
+ @"/movies/Iron Man/Iron Man (2010).mkv",
+ @"/movies/Iron Man/Iron Man (2011).mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(5, result.Count);
+ Assert.Empty(result[0].Extras);
+ Assert.Empty(result[0].AlternateVersions);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersion5()
+ {
+ var files = new[]
+ {
+ @"/movies/Iron Man/Iron Man.mkv",
+ @"/movies/Iron Man/Iron Man-720p.mkv",
+ @"/movies/Iron Man/Iron Man-test.mkv",
+ @"/movies/Iron Man/Iron Man-bluray.mkv",
+ @"/movies/Iron Man/Iron Man-3d.mkv",
+ @"/movies/Iron Man/Iron Man-3d-hsbs.mkv",
+ @"/movies/Iron Man/Iron Man-3d.hsbs.mkv",
+ @"/movies/Iron Man/Iron Man[test].mkv",
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Empty(result[0].Extras);
+ Assert.Equal(7, result[0].AlternateVersions.Count);
+ Assert.False(result[0].AlternateVersions[2].Is3D);
+ Assert.True(result[0].AlternateVersions[3].Is3D);
+ Assert.True(result[0].AlternateVersions[4].Is3D);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersion6()
+ {
+ var files = new[]
+ {
+ @"/movies/Iron Man/Iron Man.mkv",
+ @"/movies/Iron Man/Iron Man - 720p.mkv",
+ @"/movies/Iron Man/Iron Man - test.mkv",
+ @"/movies/Iron Man/Iron Man - bluray.mkv",
+ @"/movies/Iron Man/Iron Man - 3d.mkv",
+ @"/movies/Iron Man/Iron Man - 3d-hsbs.mkv",
+ @"/movies/Iron Man/Iron Man - 3d.hsbs.mkv",
+ @"/movies/Iron Man/Iron Man [test].mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Empty(result[0].Extras);
+ Assert.Equal(7, result[0].AlternateVersions.Count);
+ Assert.False(result[0].AlternateVersions[3].Is3D);
+ Assert.True(result[0].AlternateVersions[4].Is3D);
+ Assert.True(result[0].AlternateVersions[5].Is3D);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersion7()
+ {
+ var files = new[]
+ {
+ @"/movies/Iron Man/Iron Man - B (2006).mkv",
+ @"/movies/Iron Man/Iron Man - C (2007).mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(2, result.Count);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersion8()
+ {
+ // This is not actually supported yet
+
+ var files = new[]
+ {
+ @"/movies/Iron Man/Iron Man.mkv",
+ @"/movies/Iron Man/Iron Man_720p.mkv",
+ @"/movies/Iron Man/Iron Man_test.mkv",
+ @"/movies/Iron Man/Iron Man_bluray.mkv",
+ @"/movies/Iron Man/Iron Man_3d.mkv",
+ @"/movies/Iron Man/Iron Man_3d-hsbs.mkv",
+ @"/movies/Iron Man/Iron Man_3d.hsbs.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Empty(result[0].Extras);
+ Assert.Equal(6, result[0].AlternateVersions.Count);
+ Assert.False(result[0].AlternateVersions[2].Is3D);
+ Assert.True(result[0].AlternateVersions[3].Is3D);
+ Assert.True(result[0].AlternateVersions[4].Is3D);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersion9()
+ {
+ // Test for false positive
+
+ var files = new[]
+ {
+ @"/movies/Iron Man/Iron Man (2007).mkv",
+ @"/movies/Iron Man/Iron Man (2008).mkv",
+ @"/movies/Iron Man/Iron Man (2009).mkv",
+ @"/movies/Iron Man/Iron Man (2010).mkv",
+ @"/movies/Iron Man/Iron Man (2011).mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(5, result.Count);
+ Assert.Empty(result[0].Extras);
+ Assert.Empty(result[0].AlternateVersions);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersion10()
+ {
+ var files = new[]
+ {
+ @"/movies/Blade Runner (1982)/Blade Runner (1982) [Final Cut] [1080p HEVC AAC].mkv",
+ @"/movies/Blade Runner (1982)/Blade Runner (1982) [EE by ADM] [480p HEVC AAC,AAC,AAC].mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Empty(result[0].Extras);
+ Assert.Single(result[0].AlternateVersions);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMultiVersion11()
+ {
+ // Currently not supported but we should probably handle this.
+
+ var files = new[]
+ {
+ @"/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016) [1080p] Blu-ray.x264.DTS.mkv",
+ @"/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016) [2160p] Blu-ray.x265.AAC.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Empty(result[0].Extras);
+ Assert.Single(result[0].AlternateVersions);
+ }
+
+ private VideoListResolver GetResolver()
+ {
+ var options = new NamingOptions();
+ return new VideoListResolver(options);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
new file mode 100644
index 000000000..5c121d738
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
@@ -0,0 +1,452 @@
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using MediaBrowser.Model.IO;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public class StackTests : BaseVideoTest
+ {
+ [Fact]
+ public void TestSimpleStack()
+ {
+ var files = new[]
+ {
+ "Bad Boys (2006) part1.mkv",
+ "Bad Boys (2006) part2.mkv",
+ "Bad Boys (2006) part3.mkv",
+ "Bad Boys (2006) part4.mkv",
+ "Bad Boys (2006)-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Single(result.Stacks);
+ TestStackInfo(result.Stacks[0], "Bad Boys (2006)", 4);
+ }
+
+ [Fact]
+ public void TestFalsePositives()
+ {
+ var files = new[]
+ {
+ "Bad Boys (2006).mkv",
+ "Bad Boys (2007).mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Empty(result.Stacks);
+ }
+
+ [Fact]
+ public void TestFalsePositives2()
+ {
+ var files = new[]
+ {
+ "Bad Boys 2006.mkv",
+ "Bad Boys 2007.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Empty(result.Stacks);
+ }
+
+ [Fact]
+ public void TestFalsePositives3()
+ {
+ var files = new[]
+ {
+ "300 (2006).mkv",
+ "300 (2007).mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Empty(result.Stacks);
+ }
+
+ [Fact]
+ public void TestFalsePositives4()
+ {
+ var files = new[]
+ {
+ "300 2006.mkv",
+ "300 2007.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Empty(result.Stacks);
+ }
+
+ [Fact]
+ public void TestFalsePositives5()
+ {
+ var files = new[]
+ {
+ "Star Trek 1 - The motion picture.mkv",
+ "Star Trek 2- The wrath of khan.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Empty(result.Stacks);
+ }
+
+ [Fact]
+ public void TestFalsePositives6()
+ {
+ var files = new[]
+ {
+ "Red Riding in the Year of Our Lord 1983 (2009).mkv",
+ "Red Riding in the Year of Our Lord 1980 (2009).mkv",
+ "Red Riding in the Year of Our Lord 1974 (2009).mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Empty(result.Stacks);
+ }
+
+ [Fact]
+ public void TestStackName()
+ {
+ var files = new[]
+ {
+ "d:/movies/300 2006 part1.mkv",
+ "d:/movies/300 2006 part2.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Single(result.Stacks);
+ TestStackInfo(result.Stacks[0], "300 2006", 2);
+ }
+
+ [Fact]
+ public void TestDirtyNames()
+ {
+ var files = new[]
+ {
+ "Bad Boys (2006).part1.stv.unrated.multi.1080p.bluray.x264-rough.mkv",
+ "Bad Boys (2006).part2.stv.unrated.multi.1080p.bluray.x264-rough.mkv",
+ "Bad Boys (2006).part3.stv.unrated.multi.1080p.bluray.x264-rough.mkv",
+ "Bad Boys (2006).part4.stv.unrated.multi.1080p.bluray.x264-rough.mkv",
+ "Bad Boys (2006)-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Single(result.Stacks);
+ TestStackInfo(result.Stacks[0], "Bad Boys (2006).stv.unrated.multi.1080p.bluray.x264-rough", 4);
+ }
+
+ [Fact]
+ public void TestNumberedFiles()
+ {
+ var files = new[]
+ {
+ "Bad Boys (2006).mkv",
+ "Bad Boys (2006) 1.mkv",
+ "Bad Boys (2006) 2.mkv",
+ "Bad Boys (2006) 3.mkv",
+ "Bad Boys (2006)-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Empty(result.Stacks);
+ }
+
+ [Fact]
+ public void TestSimpleStackWithNumericName()
+ {
+ var files = new[]
+ {
+ "300 (2006) part1.mkv",
+ "300 (2006) part2.mkv",
+ "300 (2006) part3.mkv",
+ "300 (2006) part4.mkv",
+ "300 (2006)-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Single(result.Stacks);
+ TestStackInfo(result.Stacks[0], "300 (2006)", 4);
+ }
+
+ [Fact]
+ public void TestMixedExpressionsNotAllowed()
+ {
+ var files = new[]
+ {
+ "Bad Boys (2006) part1.mkv",
+ "Bad Boys (2006) part2.mkv",
+ "Bad Boys (2006) part3.mkv",
+ "Bad Boys (2006) parta.mkv",
+ "Bad Boys (2006)-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Single(result.Stacks);
+ TestStackInfo(result.Stacks[0], "Bad Boys (2006)", 3);
+ }
+
+ [Fact]
+ public void TestDualStacks()
+ {
+ var files = new[]
+ {
+ "Bad Boys (2006) part1.mkv",
+ "Bad Boys (2006) part2.mkv",
+ "Bad Boys (2006) part3.mkv",
+ "Bad Boys (2006) part4.mkv",
+ "Bad Boys (2006)-trailer.mkv",
+ "300 (2006) part1.mkv",
+ "300 (2006) part2.mkv",
+ "300 (2006) part3.mkv",
+ "300 (2006)-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Equal(2, result.Stacks.Count);
+ TestStackInfo(result.Stacks[1], "Bad Boys (2006)", 4);
+ TestStackInfo(result.Stacks[0], "300 (2006)", 3);
+ }
+
+ [Fact]
+ public void TestDirectories()
+ {
+ var files = new[]
+ {
+ "blah blah - cd 1",
+ "blah blah - cd 2"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveDirectories(files);
+
+ Assert.Single(result.Stacks);
+ TestStackInfo(result.Stacks[0], "blah blah", 2);
+ }
+
+ [Fact]
+ public void TestFalsePositive()
+ {
+ var files = new[]
+ {
+ "300a.mkv",
+ "300b.mkv",
+ "300c.mkv",
+ "300-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Single(result.Stacks);
+
+ TestStackInfo(result.Stacks[0], "300", 3);
+ }
+
+ [Fact]
+ public void TestFailSequence()
+ {
+ var files = new[]
+ {
+ "300 part1.mkv",
+ "300 part2.mkv",
+ "Avatar",
+ "Avengers part1.mkv",
+ "Avengers part2.mkv",
+ "Avengers part3.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Equal(2, result.Stacks.Count);
+
+ TestStackInfo(result.Stacks[0], "300", 2);
+ TestStackInfo(result.Stacks[1], "Avengers", 3);
+ }
+
+ [Fact]
+ public void TestMixedExpressions()
+ {
+ var files = new[]
+ {
+ "Bad Boys (2006) part1.mkv",
+ "Bad Boys (2006) part2.mkv",
+ "Bad Boys (2006) part3.mkv",
+ "Bad Boys (2006) part4.mkv",
+ "Bad Boys (2006)-trailer.mkv",
+ "300 (2006) parta.mkv",
+ "300 (2006) partb.mkv",
+ "300 (2006) partc.mkv",
+ "300 (2006) partd.mkv",
+ "300 (2006)-trailer.mkv",
+ "300a.mkv",
+ "300b.mkv",
+ "300c.mkv",
+ "300-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Equal(3, result.Stacks.Count);
+
+ TestStackInfo(result.Stacks[0], "300 (2006)", 4);
+ TestStackInfo(result.Stacks[1], "300", 3);
+ TestStackInfo(result.Stacks[2], "Bad Boys (2006)", 4);
+ }
+
+ [Fact]
+ public void TestAlphaLimitOfFour()
+ {
+ var files = new[]
+ {
+ "300 (2006) parta.mkv",
+ "300 (2006) partb.mkv",
+ "300 (2006) partc.mkv",
+ "300 (2006) partd.mkv",
+ "300 (2006) parte.mkv",
+ "300 (2006) partf.mkv",
+ "300 (2006) partg.mkv",
+ "300 (2006)-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Single(result.Stacks);
+
+ TestStackInfo(result.Stacks[0], "300 (2006)", 4);
+ }
+
+ [Fact]
+ public void TestMixed()
+ {
+ var files = new[]
+ {
+ new FileSystemMetadata{FullName = "Bad Boys (2006) part1.mkv", IsDirectory = false},
+ new FileSystemMetadata{FullName = "Bad Boys (2006) part2.mkv", IsDirectory = false},
+ new FileSystemMetadata{FullName = "300 (2006) part2", IsDirectory = true},
+ new FileSystemMetadata{FullName = "300 (2006) part3", IsDirectory = true},
+ new FileSystemMetadata{FullName = "300 (2006) part1", IsDirectory = true}
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files);
+
+ Assert.Equal(2, result.Stacks.Count);
+ TestStackInfo(result.Stacks[0], "300 (2006)", 3);
+ TestStackInfo(result.Stacks[1], "Bad Boys (2006)", 2);
+ }
+
+ [Fact]
+ public void TestNamesWithoutParts()
+ {
+ // No stacking here because there is no part/disc/etc
+ var files = new[]
+ {
+ "Harry Potter and the Deathly Hallows.mkv",
+ "Harry Potter and the Deathly Hallows 1.mkv",
+ "Harry Potter and the Deathly Hallows 2.mkv",
+ "Harry Potter and the Deathly Hallows 3.mkv",
+ "Harry Potter and the Deathly Hallows 4.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Empty(result.Stacks);
+ }
+
+ [Fact]
+ public void TestNumbersAppearingBeforePartNumber()
+ {
+ // No stacking here because there is no part/disc/etc
+ var files = new[]
+ {
+ "Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part1.mkv",
+ "Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part2.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveFiles(files);
+
+ Assert.Single(result.Stacks);
+ Assert.Equal(2, result.Stacks[0].Files.Count);
+ }
+
+ [Fact]
+ public void TestMultiDiscs()
+ {
+ // No stacking here because there is no part/disc/etc
+ var files = new[]
+ {
+ @"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 01)",
+ @"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 02)"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.ResolveDirectories(files);
+
+ Assert.Single(result.Stacks);
+ Assert.Equal(2, result.Stacks[0].Files.Count);
+ }
+
+ private void TestStackInfo(FileStack stack, string name, int fileCount)
+ {
+ Assert.Equal(fileCount, stack.Files.Count);
+ Assert.Equal(name, stack.Name);
+ }
+
+ private StackResolver GetResolver()
+ {
+ return new StackResolver(new NamingOptions());
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs
new file mode 100644
index 000000000..7b3a01bc0
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs
@@ -0,0 +1,53 @@
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public class StubTests : BaseVideoTest
+ {
+ [Fact]
+ public void TestStubs()
+ {
+ Test("video.mkv", false, null);
+ Test("video.disc", true, null);
+ Test("video.dvd.disc", true, "dvd");
+ Test("video.hddvd.disc", true, "hddvd");
+ Test("video.bluray.disc", true, "bluray");
+ Test("video.brrip.disc", true, "bluray");
+ Test("video.bd25.disc", true, "bluray");
+ Test("video.bd50.disc", true, "bluray");
+ Test("video.vhs.disc", true, "vhs");
+ Test("video.hdtv.disc", true, "tv");
+ Test("video.pdtv.disc", true, "tv");
+ Test("video.dsr.disc", true, "tv");
+ }
+
+ [Fact]
+ public void TestStubName()
+ {
+ var result =
+ GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.dvd.disc");
+
+ Assert.Equal("Oblivion", result.Name);
+ }
+
+ private void Test(string path, bool isStub, string stubType)
+ {
+ var options = new NamingOptions();
+
+ var resultStubType = StubResolver.ResolveFile(path, options);
+
+ Assert.Equal(isStub, resultStubType.IsStub);
+
+ if (stubType == null)
+ {
+ Assert.Null(resultStubType.StubType);
+ }
+ else
+ {
+ Assert.Equal(stubType, resultStubType.StubType, true);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs
new file mode 100644
index 000000000..ef8a17898
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs
@@ -0,0 +1,457 @@
+using System.Linq;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using MediaBrowser.Model.IO;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public class VideoListResolverTests
+ {
+ // FIXME
+ // [Fact]
+ public void TestStackAndExtras()
+ {
+ // No stacking here because there is no part/disc/etc
+ var files = new[]
+ {
+ "Harry Potter and the Deathly Hallows-trailer.mkv",
+ "Harry Potter and the Deathly Hallows.trailer.mkv",
+ "Harry Potter and the Deathly Hallows part1.mkv",
+ "Harry Potter and the Deathly Hallows part2.mkv",
+ "Harry Potter and the Deathly Hallows part3.mkv",
+ "Harry Potter and the Deathly Hallows part4.mkv",
+ "Batman-deleted.mkv",
+ "Batman-sample.mkv",
+ "Batman-trailer.mkv",
+ "Batman part1.mkv",
+ "Batman part2.mkv",
+ "Batman part3.mkv",
+ "Avengers.mkv",
+ "Avengers-trailer.mkv",
+
+ // Despite having a keyword in the name that will return an ExtraType, there's no original video to match it to
+ // So this is just a standalone video
+ "trailer.mkv",
+
+ // Same as above
+ "WillyWonka-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(5, result.Count);
+
+ Assert.Equal(3, result[1].Files.Count);
+ Assert.Equal(3, result[1].Extras.Count);
+ Assert.Equal("Batman", result[1].Name);
+
+ Assert.Equal(4, result[2].Files.Count);
+ Assert.Equal(2, result[2].Extras.Count);
+ Assert.Equal("Harry Potter and the Deathly Hallows", result[2].Name);
+ }
+
+ [Fact]
+ public void TestWithMetadata()
+ {
+ var files = new[]
+ {
+ "300.mkv",
+ "300.nfo"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestWithExtra()
+ {
+ var files = new[]
+ {
+ "300.mkv",
+ "300 trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestVariationWithFolderName()
+ {
+ var files = new[]
+ {
+ "X-Men Days of Future Past - 1080p.mkv",
+ "X-Men Days of Future Past-trailer.mp4"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestTrailer2()
+ {
+ var files = new[]
+ {
+ "X-Men Days of Future Past - 1080p.mkv",
+ "X-Men Days of Future Past-trailer.mp4",
+ "X-Men Days of Future Past-trailer2.mp4"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestDifferentNames()
+ {
+ var files = new[]
+ {
+ "Looper (2012)-trailer.mkv",
+ "Looper.2012.bluray.720p.x264.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestSeparateFiles()
+ {
+ // These should be considered separate, unrelated videos
+ var files = new[]
+ {
+ "My video 1.mkv",
+ "My video 2.mkv",
+ "My video 3.mkv",
+ "My video 4.mkv",
+ "My video 5.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(5, result.Count);
+ }
+
+ [Fact]
+ public void TestMultiDisc()
+ {
+ var files = new[]
+ {
+ @"M:/Movies (DVD)/Movies (Musical)/Sound of Music (1965)/Sound of Music Disc 1",
+ @"M:/Movies (DVD)/Movies (Musical)/Sound of Music (1965)/Sound of Music Disc 2"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = true,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestPoundSign()
+ {
+ // These should be considered separate, unrelated videos
+ var files = new[]
+ {
+ @"My movie #1.mp4",
+ @"My movie #2.mp4"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = true,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(2, result.Count);
+ }
+
+ [Fact]
+ public void TestStackedWithTrailer()
+ {
+ var files = new[]
+ {
+ @"No (2012) part1.mp4",
+ @"No (2012) part2.mp4",
+ @"No (2012) part1-trailer.mp4"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestStackedWithTrailer2()
+ {
+ var files = new[]
+ {
+ @"No (2012) part1.mp4",
+ @"No (2012) part2.mp4",
+ @"No (2012)-trailer.mp4"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestExtrasByFolderName()
+ {
+ var files = new[]
+ {
+ @"/Movies/Top Gun (1984)/movie.mp4",
+ @"/Movies/Top Gun (1984)/Top Gun (1984)-trailer.mp4",
+ @"/Movies/Top Gun (1984)/Top Gun (1984)-trailer2.mp4",
+ @"trailer.mp4"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestDoubleTags()
+ {
+ var files = new[]
+ {
+ @"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Counterfeit Racks (2011) Disc 1 cd1.avi",
+ @"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Counterfeit Racks (2011) Disc 1 cd2.avi",
+ @"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Disc 2 cd1.avi",
+ @"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Disc 2 cd2.avi"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(2, result.Count);
+ }
+
+ [Fact]
+ public void TestArgumentOutOfRangeException()
+ {
+ var files = new[]
+ {
+ @"/nas-markrobbo78/Videos/INDEX HTPC/Movies/Watched/3 - ACTION/Argo (2012)/movie.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestColony()
+ {
+ var files = new[]
+ {
+ @"The Colony.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestFourSisters()
+ {
+ var files = new[]
+ {
+ @"Four Sisters and a Wedding - A.avi",
+ @"Four Sisters and a Wedding - B.avi"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestMovieTrailer()
+ {
+ var files = new[]
+ {
+ @"/Server/Despicable Me/Despicable Me (2010).mkv",
+ @"/Server/Despicable Me/movie-trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void TestTrailerFalsePositives()
+ {
+ var files = new[]
+ {
+ @"/Server/Despicable Me/Skyscraper (2018) - Big Game Spot.mkv",
+ @"/Server/Despicable Me/Skyscraper (2018) - Trailer.mkv",
+ @"/Server/Despicable Me/Baywatch (2017) - Big Game Spot.mkv",
+ @"/Server/Despicable Me/Baywatch (2017) - Trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Equal(4, result.Count);
+ }
+
+ [Fact]
+ public void TestSubfolders()
+ {
+ var files = new[]
+ {
+ @"/Movies/Despicable Me/Despicable Me.mkv",
+ @"/Movies/Despicable Me/trailers/trailer.mkv"
+ };
+
+ var resolver = GetResolver();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ }
+
+ private VideoListResolver GetResolver()
+ {
+ var options = new NamingOptions();
+ return new VideoListResolver(options);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs
new file mode 100644
index 000000000..5a3ce8886
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs
@@ -0,0 +1,275 @@
+using MediaBrowser.Model.Entities;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public class VideoResolverTests : BaseVideoTest
+ {
+ // FIXME
+ // [Fact]
+ public void TestSimpleFile()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).mkv");
+
+ Assert.Equal(2006, result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Equal("Brave", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestSimpleFile2()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv");
+
+ Assert.Equal(1995, result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Equal("Bad Boys", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestSimpleFileWithNumericName()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).mkv");
+
+ Assert.Equal(2006, result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Equal("300", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestExtra()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv");
+
+ Assert.Equal(2006, result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Equal(ExtraType.Trailer, result.ExtraType);
+ Assert.Equal("Brave (2006)-trailer", result.Name);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestExtraWithNumericName()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.mkv");
+
+ Assert.Equal(2006, result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Equal("300 (2006)-trailer", result.Name);
+ Assert.Equal(ExtraType.Trailer, result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestStubFileWithNumericName()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).bluray.disc");
+
+ Assert.Equal(2006, result.Year);
+ Assert.True(result.IsStub);
+ Assert.Equal("bluray", result.StubType);
+ Assert.False(result.Is3D);
+ Assert.Equal("300", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestStubFile()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).bluray.disc");
+
+ Assert.Equal(2006, result.Year);
+ Assert.True(result.IsStub);
+ Assert.Equal("bluray", result.StubType);
+ Assert.False(result.Is3D);
+ Assert.Equal("Brave", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestExtraStubWithNumericNameNotSupported()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc");
+
+ Assert.Equal(2006, result.Year);
+ Assert.True(result.IsStub);
+ Assert.Equal("bluray", result.StubType);
+ Assert.False(result.Is3D);
+ Assert.Equal("300", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestExtraStubNotSupported()
+ {
+ // Using a stub for an extra is currently not supported
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc");
+
+ Assert.Equal(2006, result.Year);
+ Assert.True(result.IsStub);
+ Assert.Equal("bluray", result.StubType);
+ Assert.False(result.Is3D);
+ Assert.Equal("brave", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void Test3DFileWithNumericName()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv");
+
+ Assert.Equal(2006, result.Year);
+ Assert.False(result.IsStub);
+ Assert.True(result.Is3D);
+ Assert.Equal("sbs", result.Format3D);
+ Assert.Equal("300", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestBad3DFileWithNumericName()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv");
+
+ Assert.Equal(2006, result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Equal("300", result.Name);
+ Assert.Null(result.ExtraType);
+ Assert.Null(result.Format3D);
+ }
+
+ // FIXME
+ // [Fact]
+ public void Test3DFile()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv");
+
+ Assert.Equal(2006, result.Year);
+ Assert.False(result.IsStub);
+ Assert.True(result.Is3D);
+ Assert.Equal("sbs", result.Format3D);
+ Assert.Equal("brave", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ [Fact]
+ public void TestNameWithoutDate()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/American Psycho/American.Psycho.mkv");
+
+ Assert.Null(result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Null(result.Format3D);
+ Assert.Equal("American.Psycho", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateAndStringsSequence()
+ {
+ var parser = GetParser();
+
+ // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
+ var result =
+ parser.ResolveFile(@"/server/Movies/3.Days.to.Kill/3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv");
+
+ Assert.Equal(2014, result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Null(result.Format3D);
+ Assert.Equal("3.Days.to.Kill", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateAndStringsSequence1()
+ {
+ var parser = GetParser();
+
+ // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
+ var result =
+ parser.ResolveFile(@"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv");
+
+ Assert.Equal(2005, result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Null(result.Format3D);
+ Assert.Equal("3 days to kill", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+
+ [Fact]
+ public void TestFolderNameWithExtension()
+ {
+ var parser = GetParser();
+
+ var result =
+ parser.ResolveFile(@"/server/Movies/7 Psychos.mkv/7 Psychos.mkv");
+
+ Assert.Null(result.Year);
+ Assert.False(result.IsStub);
+ Assert.False(result.Is3D);
+ Assert.Equal("7 Psychos", result.Name);
+ Assert.Null(result.ExtraType);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
new file mode 100644
index 000000000..671c59b2e
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
@@ -0,0 +1,42 @@
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Server.Implementations.IO;
+using MediaBrowser.Model.System;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.IO
+{
+ public class ManagedFileSystemTests
+ {
+ private readonly IFixture _fixture;
+ private readonly ManagedFileSystem _sut;
+
+ public ManagedFileSystemTests()
+ {
+ _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
+ _sut = _fixture.Create<ManagedFileSystem>();
+ }
+
+ [Theory]
+ [InlineData("/Volumes/Library/Sample/Music/Playlists/", "../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Beethoven/Misc/Moonlight Sonata.mp3")]
+ [InlineData("/Volumes/Library/Sample/Music/Playlists/", "../../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Beethoven/Misc/Moonlight Sonata.mp3")]
+ [InlineData("/Volumes/Library/Sample/Music/Playlists/", "Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Playlists/Beethoven/Misc/Moonlight Sonata.mp3")]
+ public void MakeAbsolutePathCorrectlyHandlesRelativeFilePaths(
+ string folderPath,
+ string filePath,
+ string expectedAbsolutePath)
+ {
+ var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
+
+ if (MediaBrowser.Common.System.OperatingSystem.Id == OperatingSystemId.Windows)
+ {
+ var expectedWindowsPath = expectedAbsolutePath.Replace('/', '\\');
+ Assert.Equal(expectedWindowsPath, generatedPath.Split(':')[1]);
+ }
+ else
+ {
+ Assert.Equal(expectedAbsolutePath, generatedPath);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
new file mode 100644
index 000000000..c554bc937
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <RootNamespace>Jellyfin.Server.Implementations.Tests</RootNamespace>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="AutoFixture" Version="4.11.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
+ <PackageReference Include="Moq" Version="4.13.1" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="coverlet.collector" Version="1.2.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/coverletArgs.runsettings b/tests/coverletArgs.runsettings
new file mode 100644
index 000000000..3113957e0
--- /dev/null
+++ b/tests/coverletArgs.runsettings
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<RunSettings>
+ <DataCollectionRunSettings>
+ <DataCollectors>
+ <DataCollector friendlyName="XPlat code coverage">
+ <Configuration>
+ <Format>cobertura</Format>
+ <Exclude>[coverlet.*.tests?]*,[*]Coverlet.Core*,[*]Moq*</Exclude> <!-- [Assembly-Filter]Type-Filter -->
+ <ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute</ExcludeByAttribute>
+ <SingleHit>false</SingleHit>
+ <UseSourceLink>true</UseSourceLink>
+ <IncludeTestAssembly>false</IncludeTestAssembly>
+ </Configuration>
+ </DataCollector>
+ </DataCollectors>
+ </DataCollectionRunSettings>
+</RunSettings> \ No newline at end of file