aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines.yml8
-rw-r--r--Emby.Naming/Common/NamingOptions.cs39
-rw-r--r--Emby.Naming/Emby.Naming.csproj2
-rw-r--r--Emby.Naming/Video/CleanStringParser.cs2
-rw-r--r--Emby.Naming/Video/ExtraResolver.cs2
-rw-r--r--Emby.Naming/Video/ExtraResult.cs4
-rw-r--r--Emby.Naming/Video/ExtraRule.cs5
-rw-r--r--Emby.Naming/Video/StubResolver.cs4
-rw-r--r--Emby.Naming/Video/VideoFileInfo.cs6
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs9
-rw-r--r--Emby.Naming/Video/VideoResolver.cs2
-rw-r--r--Emby.Photos/Emby.Photos.csproj7
-rw-r--r--Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs1
-rw-r--r--Emby.Server.Implementations/Activity/ActivityManager.cs1
-rw-r--r--Emby.Server.Implementations/Activity/ActivityRepository.cs1
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs13
-rw-r--r--Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs1
-rw-r--r--Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs1
-rw-r--r--Emby.Server.Implementations/Channels/ChannelImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs1
-rw-r--r--Emby.Server.Implementations/Channels/ChannelPostScanTask.cs1
-rw-r--r--Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs1
-rw-r--r--Emby.Server.Implementations/Collections/CollectionImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs1
-rw-r--r--Emby.Server.Implementations/Cryptography/CryptographyProvider.cs2
-rw-r--r--Emby.Server.Implementations/Data/BaseSqliteRepository.cs1
-rw-r--r--Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs1
-rw-r--r--Emby.Server.Implementations/Data/ManagedConnection.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteExtensions.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserRepository.cs1
-rw-r--r--Emby.Server.Implementations/Devices/DeviceId.cs1
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs1
-rw-r--r--Emby.Server.Implementations/Diagnostics/CommonProcess.cs1
-rw-r--r--Emby.Server.Implementations/Diagnostics/ProcessFactory.cs1
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs1
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj14
-rw-r--r--Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs1
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs1
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs1
-rw-r--r--Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs1
-rw-r--r--Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/FileWriter.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs2
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/IHttpListener.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthService.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/SessionContext.cs1
-rw-r--r--Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs1
-rw-r--r--Emby.Server.Implementations/IO/FileRefresher.cs1
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs1
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs1
-rw-r--r--Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs1
-rw-r--r--Emby.Server.Implementations/IO/StreamHelper.cs1
-rw-r--r--Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs13
-rw-r--r--Emby.Server.Implementations/Library/ExclusiveLiveStream.cs1
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs32
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs1
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs5
-rw-r--r--Emby.Server.Implementations/Library/MediaStreamSelector.cs1
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs1
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs1
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs3
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs503
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs68
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/NfoConfigurationExtensions.cs19
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs5
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ms.json28
-rw-r--r--Emby.Server.Implementations/Localization/Core/sr.json1
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj2
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj12
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs2
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs93
-rw-r--r--MediaBrowser.Api/Playback/UniversalAudioService.cs9
-rw-r--r--MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs1
-rw-r--r--MediaBrowser.Common/Configuration/IConfigurationFactory.cs1
-rw-r--r--MediaBrowser.Common/Configuration/IConfigurationManager.cs1
-rw-r--r--MediaBrowser.Common/Cryptography/PasswordHash.cs25
-rw-r--r--MediaBrowser.Common/Events/EventHelper.cs8
-rw-r--r--MediaBrowser.Common/Extensions/BaseExtensions.cs7
-rw-r--r--MediaBrowser.Common/Extensions/CopyToExtensions.cs4
-rw-r--r--MediaBrowser.Common/Extensions/MethodNotAllowedException.cs26
-rw-r--r--MediaBrowser.Common/Extensions/RateLimitExceededException.cs26
-rw-r--r--MediaBrowser.Common/Extensions/ResourceNotFoundException.cs65
-rw-r--r--MediaBrowser.Common/Hex.cs12
-rw-r--r--MediaBrowser.Common/IApplicationHost.cs55
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs (renamed from MediaBrowser.Common/Json/Converters/GuidConverter.cs)2
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs53
-rw-r--r--MediaBrowser.Common/Json/JsonDefaults.cs2
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj17
-rw-r--r--MediaBrowser.Common/Net/CustomHeaderNames.cs1
-rw-r--r--MediaBrowser.Common/Net/HttpRequestOptions.cs28
-rw-r--r--MediaBrowser.Common/Net/HttpResponseInfo.cs32
-rw-r--r--MediaBrowser.Common/Net/IHttpClient.cs4
-rw-r--r--MediaBrowser.Common/Net/INetworkManager.cs11
-rw-r--r--MediaBrowser.Common/Plugins/BasePlugin.cs150
-rw-r--r--MediaBrowser.Common/Plugins/IPlugin.cs29
-rw-r--r--MediaBrowser.Common/Plugins/IPluginAssembly.cs14
-rw-r--r--MediaBrowser.Common/Progress/ActionableProgress.cs1
-rw-r--r--MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs1
-rw-r--r--MediaBrowser.Common/System/OperatingSystem.cs1
-rw-r--r--MediaBrowser.Common/Updates/IInstallationManager.cs10
-rw-r--r--MediaBrowser.Common/Updates/InstallationEventArgs.cs1
-rw-r--r--MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs1
-rw-r--r--MediaBrowser.Controller/Authentication/AuthenticationResult.cs7
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs1
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceManager.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs30
-rw-r--r--MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs21
-rw-r--r--MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs325
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaChapter.cs32
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs81
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs282
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs199
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs2
-rw-r--r--MediaBrowser.Model/Entities/MediaType.cs5
-rw-r--r--MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs12
-rw-r--r--jellyfin.ruleset12
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs57
-rw-r--r--tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs40
-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.cs424
-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.cs15
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs143
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs133
-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.cs438
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/StackTests.cs478
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/StubTests.cs55
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs457
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs275
157 files changed, 5036 insertions, 1167 deletions
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index 143873266..7bcaed70c 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -2,7 +2,7 @@ name: $(Date:yyyyMMdd)$(Rev:.r)
variables:
- name: TestProjects
- value: 'tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj'
+ value: 'tests/**/*Tests.csproj'
- name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
@@ -132,12 +132,12 @@ jobs:
inputs:
packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
-
- task: VSTest@2
inputs:
testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
testAssemblyVer2: | # Required when testSelector == TestAssemblies
- **\bin\$(BuildConfiguration)\**\*test*.dll
+ **\bin\$(BuildConfiguration)\**\*tests.dll
+ **\bin\$(BuildConfiguration)\**\*test.dll
!**\obj\**
!**\xunit.runner.visualstudio.testadapter.dll
!**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
@@ -147,6 +147,8 @@ jobs:
codeCoverageEnabled: True # Optional
configuration: 'Debug' # Optional
publishRunAttachments: true # Optional
+ testRunTitle: $(Agent.JobName)
+ otherConsoleOptions: '/platform:x64 /Framework:.NETCoreApp,Version=v3.1 /logger:console;verbosity="normal"'
- job: main_build_win
displayName: Publish Windows
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 69e68660d..a2105889b 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Video;
+using MediaBrowser.Model.Entities;
namespace Emby.Naming.Common
{
@@ -176,7 +177,7 @@ namespace Emby.Naming.Common
CleanDateTimes = new[]
{
- @"(.+[^ _\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9][0-9]|20[0-1][0-9])([ _\,\.\(\)\[\]\-][^0-9]|$)"
+ @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](\d{4})([ _\,\.\(\)\[\]\-][^\d]|).*(\d{4})*"
};
CleanStrings = new[]
@@ -423,126 +424,126 @@ namespace Emby.Naming.Common
{
new ExtraRule
{
- ExtraType = "trailer",
+ ExtraType = ExtraType.Trailer,
RuleType = ExtraRuleType.Filename,
Token = "trailer",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "trailer",
+ ExtraType = ExtraType.Trailer,
RuleType = ExtraRuleType.Suffix,
Token = "-trailer",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "trailer",
+ ExtraType = ExtraType.Trailer,
RuleType = ExtraRuleType.Suffix,
Token = ".trailer",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "trailer",
+ ExtraType = ExtraType.Trailer,
RuleType = ExtraRuleType.Suffix,
Token = "_trailer",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "trailer",
+ ExtraType = ExtraType.Trailer,
RuleType = ExtraRuleType.Suffix,
Token = " trailer",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "sample",
+ ExtraType = ExtraType.Sample,
RuleType = ExtraRuleType.Filename,
Token = "sample",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "sample",
+ ExtraType = ExtraType.Sample,
RuleType = ExtraRuleType.Suffix,
Token = "-sample",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "sample",
+ ExtraType = ExtraType.Sample,
RuleType = ExtraRuleType.Suffix,
Token = ".sample",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "sample",
+ ExtraType = ExtraType.Sample,
RuleType = ExtraRuleType.Suffix,
Token = "_sample",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "sample",
+ ExtraType = ExtraType.Sample,
RuleType = ExtraRuleType.Suffix,
Token = " sample",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "themesong",
+ ExtraType = ExtraType.ThemeSong,
RuleType = ExtraRuleType.Filename,
Token = "theme",
MediaType = MediaType.Audio
},
new ExtraRule
{
- ExtraType = "scene",
+ ExtraType = ExtraType.Scene,
RuleType = ExtraRuleType.Suffix,
Token = "-scene",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "clip",
+ ExtraType = ExtraType.Clip,
RuleType = ExtraRuleType.Suffix,
Token = "-clip",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "interview",
+ ExtraType = ExtraType.Interview,
RuleType = ExtraRuleType.Suffix,
Token = "-interview",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "behindthescenes",
+ ExtraType = ExtraType.BehindTheScenes,
RuleType = ExtraRuleType.Suffix,
Token = "-behindthescenes",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "deletedscene",
+ ExtraType = ExtraType.DeletedScene,
RuleType = ExtraRuleType.Suffix,
Token = "-deleted",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "featurette",
+ ExtraType = ExtraType.Clip,
RuleType = ExtraRuleType.Suffix,
Token = "-featurette",
MediaType = MediaType.Video
},
new ExtraRule
{
- ExtraType = "short",
+ ExtraType = ExtraType.Clip,
RuleType = ExtraRuleType.Suffix,
Token = "-short",
MediaType = MediaType.Video
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index ed1670adf..900b9694c 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -27,7 +27,7 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs
index be028c662..fcd4b65c7 100644
--- a/Emby.Naming/Video/CleanStringParser.cs
+++ b/Emby.Naming/Video/CleanStringParser.cs
@@ -7,7 +7,7 @@ using System.Text.RegularExpressions;
namespace Emby.Naming.Video
{
/// <summary>
- /// http://kodi.wiki/view/Advancedsettings.xml#video
+ /// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
/// </summary>
public class CleanStringParser
{
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index 989ede206..ea9a6d6c2 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -23,7 +23,7 @@ namespace Emby.Naming.Video
{
return _options.VideoExtraRules
.Select(i => GetExtraInfo(path, i))
- .FirstOrDefault(i => !string.IsNullOrEmpty(i.ExtraType)) ?? new ExtraResult();
+ .FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
}
private ExtraResult GetExtraInfo(string path, ExtraRule rule)
diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs
index 6081a4494..4e991d685 100644
--- a/Emby.Naming/Video/ExtraResult.cs
+++ b/Emby.Naming/Video/ExtraResult.cs
@@ -1,6 +1,8 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
+using MediaBrowser.Model.Entities;
+
namespace Emby.Naming.Video
{
public class ExtraResult
@@ -9,7 +11,7 @@ namespace Emby.Naming.Video
/// Gets or sets the type of the extra.
/// </summary>
/// <value>The type of the extra.</value>
- public string ExtraType { get; set; }
+ public ExtraType? ExtraType { get; set; }
/// <summary>
/// Gets or sets the rule.
diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs
index cfce79fd0..cfaa84ed6 100644
--- a/Emby.Naming/Video/ExtraRule.cs
+++ b/Emby.Naming/Video/ExtraRule.cs
@@ -1,7 +1,8 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
-using Emby.Naming.Common;
+using MediaBrowser.Model.Entities;
+using MediaType = Emby.Naming.Common.MediaType;
namespace Emby.Naming.Video
{
@@ -17,7 +18,7 @@ namespace Emby.Naming.Video
/// Gets or sets the type of the extra.
/// </summary>
/// <value>The type of the extra.</value>
- public string ExtraType { get; set; }
+ public ExtraType ExtraType { get; set; }
/// <summary>
/// Gets or sets the type of the rule.
diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs
index bbf399677..95868e89d 100644
--- a/Emby.Naming/Video/StubResolver.cs
+++ b/Emby.Naming/Video/StubResolver.cs
@@ -14,14 +14,14 @@ namespace Emby.Naming.Video
{
if (path == null)
{
- return default(StubResult);
+ return default;
}
var extension = Path.GetExtension(path);
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- return default(StubResult);
+ return default;
}
var result = new StubResult()
diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs
index 250a1ec45..90c798da1 100644
--- a/Emby.Naming/Video/VideoFileInfo.cs
+++ b/Emby.Naming/Video/VideoFileInfo.cs
@@ -1,3 +1,5 @@
+using MediaBrowser.Model.Entities;
+
namespace Emby.Naming.Video
{
/// <summary>
@@ -30,10 +32,10 @@ namespace Emby.Naming.Video
public int? Year { get; set; }
/// <summary>
- /// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc.
+ /// Gets or sets the type of the extra, e.g. trailer, theme song, behind the scenes, etc.
/// </summary>
/// <value>The type of the extra.</value>
- public string ExtraType { get; set; }
+ public ExtraType? ExtraType { get; set; }
/// <summary>
/// Gets or sets the extra rule.
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 5a32846bf..87498000c 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Naming.Video
@@ -32,7 +33,7 @@ namespace Emby.Naming.Video
// Filter out all extras, otherwise they could cause stacks to not be resolved
// See the unit test TestStackedWithTrailer
var nonExtras = videoInfos
- .Where(i => string.IsNullOrEmpty(i.ExtraType))
+ .Where(i => i.ExtraType == null)
.Select(i => new FileSystemMetadata
{
FullName = i.Path,
@@ -79,7 +80,7 @@ namespace Emby.Naming.Video
}
var standaloneMedia = remainingFiles
- .Where(i => string.IsNullOrEmpty(i.ExtraType))
+ .Where(i => i.ExtraType == null)
.ToList();
foreach (var media in standaloneMedia)
@@ -148,7 +149,7 @@ namespace Emby.Naming.Video
if (list.Count == 1)
{
var trailers = remainingFiles
- .Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase))
+ .Where(i => i.ExtraType == ExtraType.Trailer)
.ToList();
list[0].Extras.AddRange(trailers);
@@ -229,7 +230,7 @@ namespace Emby.Naming.Video
}
return remainingFiles
- .Where(i => !string.IsNullOrEmpty(i.ExtraType))
+ .Where(i => i.ExtraType == null)
.Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
.ToList();
}
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index 5a93e1eaf..41b79697c 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -94,7 +94,7 @@ namespace Emby.Naming.Video
{
var cleanDateTimeResult = CleanDateTime(name);
- if (string.IsNullOrEmpty(extraResult.ExtraType))
+ if (extraResult.ExtraType == null)
{
name = CleanString(cleanDateTimeResult.Name).Name;
}
diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index 23b7a819b..29ed3c5f7 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -26,9 +26,10 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
- <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index b622a3167..ac8af66a2 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs
index a30e93912..b03c4d182 100644
--- a/Emby.Server.Implementations/Activity/ActivityManager.cs
+++ b/Emby.Server.Implementations/Activity/ActivityManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Linq;
diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs
index 7be72319e..633343bb6 100644
--- a/Emby.Server.Implementations/Activity/ActivityRepository.cs
+++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index a179c1b15..0bb1d832f 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -177,11 +178,7 @@ namespace Emby.Server.Implementations
/// Gets the plugins.
/// </summary>
/// <value>The plugins.</value>
- public IPlugin[] Plugins
- {
- get => _plugins;
- protected set => _plugins = value;
- }
+ public IReadOnlyList<IPlugin> Plugins => _plugins;
/// <summary>
/// Gets or sets the logger factory.
@@ -1056,7 +1053,7 @@ namespace Emby.Server.Implementations
}
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
- Plugins = GetExports<IPlugin>()
+ _plugins = GetExports<IPlugin>()
.Select(LoadPlugin)
.Where(i => i != null)
.ToArray();
@@ -1705,9 +1702,9 @@ namespace Emby.Server.Implementations
/// <param name="plugin">The plugin.</param>
public void RemovePlugin(IPlugin plugin)
{
- var list = Plugins.ToList();
+ var list = _plugins.ToList();
list.Remove(plugin);
- Plugins = list.ToArray();
+ _plugins = list.ToArray();
}
/// <summary>
diff --git a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
index 93000ae12..15aee63a0 100644
--- a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
+++ b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
diff --git a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
index 6016fed07..aae416b37 100644
--- a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
index 62aeb9bcb..fe64f1b15 100644
--- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
+++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System.Collections.Generic;
using System.Linq;
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 6e1baddfe..de2e123af 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
index 2712fc8c5..36e0e5e26 100644
--- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
+++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Linq;
diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
index 5774c0415..039e2c138 100644
--- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
+++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
index 1fa556ec9..8006b8694 100644
--- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
+++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 1d7c11989..efdef8481 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
index 776074b72..de83b023d 100644
--- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
+++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
@@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Cryptography
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
- /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index 0654132f4..b7f643819 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
index 2a8f2d6b3..8a5387e9b 100644
--- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
+++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Threading;
diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs
index 5c094ddd2..2c2f19cd3 100644
--- a/Emby.Server.Implementations/Data/ManagedConnection.cs
+++ b/Emby.Server.Implementations/Data/ManagedConnection.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
index d474f1c6b..8087419ce 100644
--- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index c87793072..55c24ccc0 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index 22955850a..f6c37e4e5 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs
index a042320c9..c82c93ffc 100644
--- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs
index f0d43e665..ff75efa59 100644
--- a/Emby.Server.Implementations/Devices/DeviceId.cs
+++ b/Emby.Server.Implementations/Devices/DeviceId.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Globalization;
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index 2393f1f45..ef7317050 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
index bfa49ac5f..f8b754151 100644
--- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
+++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Diagnostics;
diff --git a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs b/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs
index 02ad3c1a8..219f73c78 100644
--- a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs
+++ b/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using MediaBrowser.Model.Diagnostics;
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 3d622b3fc..fcf0360c7 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 7ae6f38a1..03cbe00b7 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -29,9 +29,9 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
- <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.0" />
<PackageReference Include="Mono.Nat" Version="2.0.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
<PackageReference Include="sharpcompress" Version="0.24.0" />
@@ -51,10 +51,10 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
- <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
- <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
diff --git a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs
index d69b0909d..a6eb1152f 100644
--- a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Linq;
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index e290c62e1..4e4ef3be0 100644
--- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index 5f938e59a..f85d52dbc 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
index dbb3503c4..e0aa18e89 100644
--- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Linq;
diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
index e431da148..3e22080fc 100644
--- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
index c1c8c3eb3..1795651fd 100644
--- a/Emby.Server.Implementations/HttpServer/FileWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 2aefc9fe5..b0126f7fa 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -218,7 +219,6 @@ namespace Emby.Server.Implementations.HttpServer
case FileNotFoundException _:
case ResourceNotFoundException _: return 404;
case MethodNotAllowedException _: return 405;
- case RemoteServiceUnavailableException _: return 502;
default: return 500;
}
}
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index a62b4e7af..cefcaa835 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs
index 501593725..1c3496e5d 100644
--- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs
+++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Threading;
diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
index 8b9028f6b..7cb113a58 100644
--- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
index 58421aaf1..03b5b748d 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Linq;
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index 129faeaab..e8884bca0 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
index 166952c64..a6a0f5b03 100644
--- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using MediaBrowser.Controller.Entities;
diff --git a/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs
index 3150f3367..5be144452 100644
--- a/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs
+++ b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
namespace Emby.Server.Implementations.IO
{
diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs
index 4b5b11f01..cf92ddbcd 100644
--- a/Emby.Server.Implementations/IO/FileRefresher.cs
+++ b/Emby.Server.Implementations/IO/FileRefresher.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index b1fb8cc63..7777efc3b 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 9568f62df..e27081c45 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
index e6696b8c4..574b63ae6 100644
--- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
+++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs
index 40b397edc..c99018e40 100644
--- a/Emby.Server.Implementations/IO/StreamHelper.cs
+++ b/Emby.Server.Implementations/IO/StreamHelper.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Buffers;
diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index fd50f156a..acf3a3b23 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
index 94f60ea62..ab036eca7 100644
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
@@ -70,9 +70,9 @@ namespace Emby.Server.Implementations.Library
byte[] calculatedHash = _cryptographyProvider.ComputeHash(
readyHash.Id,
passwordbytes,
- readyHash.Salt);
+ readyHash.Salt.ToArray());
- if (calculatedHash.SequenceEqual(readyHash.Hash))
+ if (readyHash.Hash.SequenceEqual(calculatedHash))
{
success = true;
}
@@ -148,17 +148,18 @@ namespace Emby.Server.Implementations.Library
// TODO: make use of iterations parameter?
PasswordHash passwordHash = PasswordHash.Parse(user.Password);
+ var salt = passwordHash.Salt.ToArray();
return new PasswordHash(
passwordHash.Id,
_cryptographyProvider.ComputeHash(
passwordHash.Id,
Encoding.UTF8.GetBytes(str),
- passwordHash.Salt),
- passwordHash.Salt,
+ salt),
+ salt,
passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString();
}
- public byte[] GetHashed(User user, string str)
+ public ReadOnlySpan<byte> GetHashed(User user, string str)
{
if (string.IsNullOrEmpty(user.Password))
{
@@ -170,7 +171,7 @@ namespace Emby.Server.Implementations.Library
return _cryptographyProvider.ComputeHash(
passwordHash.Id,
Encoding.UTF8.GetBytes(str),
- passwordHash.Salt);
+ passwordHash.Salt.ToArray());
}
}
}
diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
index 9a7186898..3eb64c29c 100644
--- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
+++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Globalization;
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index cee51479e..ae3cdece9 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -2558,7 +2559,7 @@ namespace Emby.Server.Implementations.Library
if (currentVideo != null)
{
- files.AddRange(currentVideo.Extras.Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path)));
+ files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
}
var resolvers = new IItemResolver[]
@@ -2608,7 +2609,7 @@ namespace Emby.Server.Implementations.Library
if (currentVideo != null)
{
- files.AddRange(currentVideo.Extras.Where(i => !string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path)));
+ files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
}
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
@@ -2712,7 +2713,7 @@ namespace Emby.Server.Implementations.Library
if (!string.Equals(newPath, path, StringComparison.Ordinal))
{
- if (to.IndexOf('/') != -1)
+ if (to.IndexOf('/', StringComparison.Ordinal) != -1)
{
newPath = newPath.Replace('\\', '/');
}
@@ -2733,30 +2734,7 @@ namespace Emby.Server.Implementations.Library
var result = resolver.GetExtraInfo(item.Path);
- if (string.Equals(result.ExtraType, "deletedscene", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.DeletedScene;
- }
- else if (string.Equals(result.ExtraType, "behindthescenes", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.BehindTheScenes;
- }
- else if (string.Equals(result.ExtraType, "interview", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.Interview;
- }
- else if (string.Equals(result.ExtraType, "scene", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.Scene;
- }
- else if (string.Equals(result.ExtraType, "sample", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.Sample;
- }
- else
- {
- item.ExtraType = ExtraType.Clip;
- }
+ item.ExtraType = result.ExtraType;
}
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index ed7d8aa40..f28f4a538 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index ba1564d1f..e310065b2 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.Library
});
}
- public async Task<List<MediaSourceInfo>> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
+ public async Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
{
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
@@ -307,7 +308,7 @@ namespace Emby.Server.Implementations.Library
return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
}
- var sources = await GetPlayackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
+ var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
}
diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
index 6b9f4d052..1652ad974 100644
--- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs
+++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index 1ec578371..29af6670b 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index 9d4bd9e59..7e3b27a12 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index c4bb861b8..43302bb3f 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index 0b93ebeb8..1e2e0704c 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index a71ae8250..e1eb23652 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
index a68562fc2..5e672f221 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
index 1030ed39d..eca60b133 100644
--- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 7cc9eabc8..e39d85bc9 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
index 62268fce9..6404d6476 100644
--- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 11d6c737a..76ae14720 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index 071681b08..f1fb35d9a 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 1b9c317d8..656eeb145 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -485,7 +486,7 @@ namespace Emby.Server.Implementations.Library
var hash = _cryptoProvider.ComputeHash(
passwordHash.Id,
Encoding.UTF8.GetBytes(password),
- passwordHash.Salt);
+ passwordHash.Salt.ToArray());
success = passwordHash.Hash.SequenceEqual(hash);
}
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index 322819b05..935deb71c 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 3b6bfce6a..f4d6cd4d3 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -1,3 +1,6 @@
+#pragma warning disable SA1600
+#pragma warning disable CS1591
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -40,6 +43,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
public class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
{
+ public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
+
+ private const int TunerDiscoveryDurationMs = 3000;
+
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
@@ -57,19 +64,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IProviderManager _providerManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IProcessFactory _processFactory;
- private IMediaSourceManager _mediaSourceManager;
-
- public static EmbyTV Current;
-
- public event EventHandler<GenericEventArgs<TimerInfo>> TimerCreated;
- public event EventHandler<GenericEventArgs<string>> TimerCancelled;
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IStreamHelper _streamHelper;
private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
- private readonly IStreamHelper _streamHelper;
+ private readonly ConcurrentDictionary<string, EpgChannelData> _epgChannels =
+ new ConcurrentDictionary<string, EpgChannelData>(StringComparer.OrdinalIgnoreCase);
+
+ private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1);
- public EmbyTV(IServerApplicationHost appHost,
+ private bool _disposed = false;
+
+ public EmbyTV(
+ IServerApplicationHost appHost,
IStreamHelper streamHelper,
IMediaSourceManager mediaSourceManager,
ILogger logger,
@@ -103,12 +112,40 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers.json"));
_timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers.json"));
- _timerProvider.TimerFired += _timerProvider_TimerFired;
+ _timerProvider.TimerFired += OnTimerProviderTimerFired;
- _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
+ _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
}
- private void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
+ public event EventHandler<GenericEventArgs<TimerInfo>> TimerCreated;
+
+ public event EventHandler<GenericEventArgs<string>> TimerCancelled;
+
+ public static EmbyTV Current { get; private set; }
+
+ /// <inheritdoc />
+ public string Name => "Emby";
+
+ public string DataPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv");
+
+ /// <inheritdoc />
+ public string HomePageUrl => "https://github.com/jellyfin/jellyfin";
+
+ private string DefaultRecordingPath => Path.Combine(DataPath, "recordings");
+
+ private string RecordingPath
+ {
+ get
+ {
+ var path = GetConfiguration().RecordingPath;
+
+ return string.IsNullOrWhiteSpace(path)
+ ? DefaultRecordingPath
+ : path;
+ }
+ }
+
+ private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
if (string.Equals(e.Key, "livetv", StringComparison.OrdinalIgnoreCase))
{
@@ -116,11 +153,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- public async Task Start()
+ public Task Start()
{
_timerProvider.RestartTimers();
- await CreateRecordingFolders().ConfigureAwait(false);
+ return CreateRecordingFolders();
}
private async void OnRecordingFoldersChanged()
@@ -132,8 +169,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
try
{
- var recordingFolders = GetRecordingFolders();
-
+ var recordingFolders = GetRecordingFolders().ToArray();
var virtualFolders = _libraryManager.GetVirtualFolders()
.ToList();
@@ -241,26 +277,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- public string Name => "Emby";
-
- public string DataPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv");
-
- private string DefaultRecordingPath => Path.Combine(DataPath, "recordings");
-
- private string RecordingPath
- {
- get
- {
- var path = GetConfiguration().RecordingPath;
-
- return string.IsNullOrWhiteSpace(path)
- ? DefaultRecordingPath
- : path;
- }
- }
-
- public string HomePageUrl => "https://github.com/jellyfin/jellyfin";
-
public async Task RefreshSeriesTimers(CancellationToken cancellationToken)
{
var seriesTimers = await GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
@@ -339,7 +355,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
catch (NotSupportedException)
{
-
}
catch (Exception ex)
{
@@ -351,7 +366,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return list;
}
- private async Task AddMetadata(IListingsProvider provider, ListingsProviderInfo info, List<ChannelInfo> tunerChannels, bool enableCache, CancellationToken cancellationToken)
+ private async Task AddMetadata(
+ IListingsProvider provider,
+ ListingsProviderInfo info,
+ IEnumerable<ChannelInfo> tunerChannels,
+ bool enableCache,
+ CancellationToken cancellationToken)
{
var epgChannels = await GetEpgChannels(provider, info, enableCache, cancellationToken).ConfigureAwait(false);
@@ -363,8 +383,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (!string.IsNullOrWhiteSpace(epgChannel.Name))
{
- //tunerChannel.Name = epgChannel.Name;
+ // tunerChannel.Name = epgChannel.Name;
}
+
if (!string.IsNullOrWhiteSpace(epgChannel.ImageUrl))
{
tunerChannel.ImageUrl = epgChannel.ImageUrl;
@@ -373,10 +394,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private readonly ConcurrentDictionary<string, EpgChannelData> _epgChannels =
- new ConcurrentDictionary<string, EpgChannelData>(StringComparer.OrdinalIgnoreCase);
-
- private async Task<EpgChannelData> GetEpgChannels(IListingsProvider provider, ListingsProviderInfo info, bool enableCache, CancellationToken cancellationToken)
+ private async Task<EpgChannelData> GetEpgChannels(
+ IListingsProvider provider,
+ ListingsProviderInfo info,
+ bool enableCache,
+ CancellationToken cancellationToken)
{
if (!enableCache || !_epgChannels.TryGetValue(info.Id, out var result))
{
@@ -394,59 +416,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return result;
}
- private class EpgChannelData
- {
- public EpgChannelData(List<ChannelInfo> channels)
- {
- ChannelsById = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
- ChannelsByNumber = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
- ChannelsByName = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
-
- foreach (var channel in channels)
- {
- ChannelsById[channel.Id] = channel;
-
- if (!string.IsNullOrEmpty(channel.Number))
- {
- ChannelsByNumber[channel.Number] = channel;
- }
-
- var normalizedName = NormalizeName(channel.Name ?? string.Empty);
- if (!string.IsNullOrWhiteSpace(normalizedName))
- {
- ChannelsByName[normalizedName] = channel;
- }
- }
- }
-
- private Dictionary<string, ChannelInfo> ChannelsById { get; set; }
- private Dictionary<string, ChannelInfo> ChannelsByNumber { get; set; }
- private Dictionary<string, ChannelInfo> ChannelsByName { get; set; }
-
- public ChannelInfo GetChannelById(string id)
- {
- ChannelInfo result = null;
-
- ChannelsById.TryGetValue(id, out result);
-
- return result;
- }
-
- public ChannelInfo GetChannelByNumber(string number)
- {
- ChannelsByNumber.TryGetValue(number, out var result);
-
- return result;
- }
-
- public ChannelInfo GetChannelByName(string name)
- {
- ChannelsByName.TryGetValue(name, out var result);
-
- return result;
- }
- }
-
private async Task<ChannelInfo> GetEpgChannelFromTunerChannel(IListingsProvider provider, ListingsProviderInfo info, ChannelInfo tunerChannel, CancellationToken cancellationToken)
{
var epgChannels = await GetEpgChannels(provider, info, true, cancellationToken).ConfigureAwait(false);
@@ -463,6 +432,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return mapping.Value;
}
}
+
return channelId;
}
@@ -476,7 +446,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return GetEpgChannelFromTunerChannel(info.ChannelMappings, tunerChannel, epgChannels);
}
- private ChannelInfo GetEpgChannelFromTunerChannel(NameValuePair[] mappings, ChannelInfo tunerChannel, EpgChannelData epgChannelData)
+ private ChannelInfo GetEpgChannelFromTunerChannel(
+ NameValuePair[] mappings,
+ ChannelInfo tunerChannel,
+ EpgChannelData epgChannelData)
{
if (!string.IsNullOrWhiteSpace(tunerChannel.Id))
{
@@ -537,7 +510,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(tunerChannel.Name))
{
- var normalizedName = NormalizeName(tunerChannel.Name);
+ var normalizedName = EpgChannelData.NormalizeName(tunerChannel.Name);
var channel = epgChannelData.GetChannelByName(normalizedName);
@@ -550,11 +523,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return null;
}
- private static string NormalizeName(string value)
- {
- return value.Replace(" ", string.Empty).Replace("-", string.Empty);
- }
-
public async Task<List<ChannelInfo>> GetChannelsForListingsProvider(ListingsProviderInfo listingsProvider, CancellationToken cancellationToken)
{
var list = new List<ChannelInfo>();
@@ -600,6 +568,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
_seriesTimerProvider.Delete(remove);
}
+
return Task.CompletedTask;
}
@@ -689,6 +658,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
programInfo = GetProgramInfoFromCache(timer);
}
+
if (programInfo == null)
{
_logger.LogInformation("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
@@ -703,10 +673,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
timer.IsManual = true;
_timerProvider.Add(timer);
- if (TimerCreated != null)
- {
- TimerCreated(this, new GenericEventArgs<TimerInfo>(timer));
- }
+ TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
return Task.FromResult(timer.Id);
}
@@ -800,7 +767,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
// Only update if not currently active
- if (!_activeRecordings.TryGetValue(updatedTimer.Id, out var activeRecordingInfo))
+ if (!_activeRecordings.TryGetValue(updatedTimer.Id, out _))
{
existingTimer.PrePaddingSeconds = updatedTimer.PrePaddingSeconds;
existingTimer.PostPaddingSeconds = updatedTimer.PostPaddingSeconds;
@@ -846,6 +813,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return info.Path;
}
+
return null;
}
@@ -870,9 +838,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return null;
}
+
return recording;
}
}
+
return null;
}
@@ -1061,13 +1031,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id;
- //if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing)
- //{
- // var ticks = (DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value).Ticks - TimeSpan.FromSeconds(10).Ticks;
- // ticks = Math.Max(0, ticks);
- // mediaSource.Path += "?t=" + ticks.ToString(CultureInfo.InvariantCulture) + "&s=" + mediaSource.DateLiveStreamOpened.Value.Ticks.ToString(CultureInfo.InvariantCulture);
- //}
-
return mediaSource;
}
@@ -1091,7 +1054,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
catch (NotImplementedException)
{
-
}
}
@@ -1142,7 +1104,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return Task.CompletedTask;
}
- async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e)
+ private async void OnTimerProviderTimerFired(object sender, GenericEventArgs<TimerInfo> e)
{
var timer = e.Argument;
@@ -1177,7 +1139,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
catch (OperationCanceledException)
{
-
}
catch (Exception ex)
{
@@ -1221,7 +1182,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (timer.SeasonNumber.HasValue)
{
- folderName = string.Format("Season {0}", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
+ folderName = string.Format(
+ CultureInfo.InvariantCulture,
+ "Season {0}",
+ timer.SeasonNumber.Value);
recordPath = Path.Combine(recordPath, folderName);
}
}
@@ -1275,6 +1239,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
recordPath = Path.Combine(recordPath, "Sports");
}
+
recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim());
}
else
@@ -1283,6 +1248,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
recordPath = Path.Combine(recordPath, "Other");
}
+
recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim());
}
@@ -1304,6 +1270,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
programInfo = GetProgramInfoFromCache(timer);
}
+
if (programInfo == null)
{
_logger.LogInformation("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
@@ -1315,9 +1282,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
CopyProgramInfoToTimerInfo(programInfo, timer);
}
- string seriesPath = null;
var remoteMetadata = await FetchInternetMetadata(timer, CancellationToken.None).ConfigureAwait(false);
- var recordPath = GetRecordingPath(timer, remoteMetadata, out seriesPath);
+ var recordPath = GetRecordingPath(timer, remoteMetadata, out string seriesPath);
var recordingStatus = RecordingStatus.New;
string liveStreamId = null;
@@ -1326,19 +1292,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
- var allMediaSources = await _mediaSourceManager.GetPlayackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false);
+ var allMediaSources = await _mediaSourceManager.GetPlaybackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false);
var mediaStreamInfo = allMediaSources[0];
IDirectStreamProvider directStreamProvider = null;
if (mediaStreamInfo.RequiresOpening)
{
- var liveStreamResponse = await _mediaSourceManager.OpenLiveStreamInternal(new LiveStreamRequest
- {
- ItemId = channelItem.Id,
- OpenToken = mediaStreamInfo.OpenToken
-
- }, CancellationToken.None).ConfigureAwait(false);
+ var liveStreamResponse = await _mediaSourceManager.OpenLiveStreamInternal(
+ new LiveStreamRequest
+ {
+ ItemId = channelItem.Id,
+ OpenToken = mediaStreamInfo.OpenToken
+ },
+ CancellationToken.None).ConfigureAwait(false);
mediaStreamInfo = liveStreamResponse.Item1.MediaSource;
liveStreamId = mediaStreamInfo.LiveStreamId;
@@ -1412,12 +1379,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate && timer.RetryCount < 10)
{
- const int retryIntervalSeconds = 60;
- _logger.LogInformation("Retrying recording in {0} seconds.", retryIntervalSeconds);
+ const int RetryIntervalSeconds = 60;
+ _logger.LogInformation("Retrying recording in {0} seconds.", RetryIntervalSeconds);
timer.Status = RecordingStatus.New;
timer.PrePaddingSeconds = 0;
- timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds);
+ timer.StartDate = DateTime.UtcNow.AddSeconds(RetryIntervalSeconds);
timer.RetryCount++;
_timerProvider.AddOrUpdate(timer);
}
@@ -1538,6 +1505,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return;
}
+
if (string.IsNullOrWhiteSpace(seriesPath))
{
return;
@@ -1576,21 +1544,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
DeleteLibraryItemsForTimers(timersToDelete);
var librarySeries = _libraryManager.FindByPath(seriesPath, true) as Folder;
-
if (librarySeries == null)
{
return;
}
- var episodesToDelete = librarySeries.GetItemList(new InternalItemsQuery
- {
- OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) },
- IsVirtualItem = false,
- IsFolder = false,
- Recursive = true,
- DtoOptions = new DtoOptions(true)
+ var episodesToDelete = librarySeries.GetItemList(
+ new InternalItemsQuery
+ {
+ OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) },
+ IsVirtualItem = false,
+ IsFolder = false,
+ Recursive = true,
+ DtoOptions = new DtoOptions(true)
- })
+ })
.Where(i => i.IsFileProtocol && File.Exists(i.Path))
.Skip(seriesTimer.KeepUpTo - 1)
.ToList();
@@ -1599,11 +1567,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
try
{
- _libraryManager.DeleteItem(item, new DeleteOptions
- {
- DeleteFileLocation = true
-
- }, true);
+ _libraryManager.DeleteItem(
+ item,
+ new DeleteOptions
+ {
+ DeleteFileLocation = true
+ },
+ true);
}
catch (Exception ex)
{
@@ -1617,7 +1587,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1);
private void DeleteLibraryItemsForTimers(List<TimerInfo> timers)
{
foreach (var timer in timers)
@@ -1644,22 +1613,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (libraryItem != null)
{
- _libraryManager.DeleteItem(libraryItem, new DeleteOptions
- {
- DeleteFileLocation = true
-
- }, true);
+ _libraryManager.DeleteItem(
+ libraryItem,
+ new DeleteOptions
+ {
+ DeleteFileLocation = true
+ },
+ true);
}
- else
+ else if (File.Exists(timer.RecordingPath))
{
- try
- {
- _fileSystem.DeleteFile(timer.RecordingPath);
- }
- catch (IOException)
- {
-
- }
+ _fileSystem.DeleteFile(timer.RecordingPath);
}
_timerProvider.Delete(timer);
@@ -1690,16 +1654,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return true;
}
- var hasRecordingAtPath = _activeRecordings
+ return _activeRecordings
.Values
.ToList()
.Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
-
- if (hasRecordingAtPath)
- {
- return true;
- }
- return false;
}
private IRecorder GetRecorder(MediaSourceInfo mediaSource)
@@ -1756,17 +1714,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void Process_Exited(object sender, EventArgs e)
{
- var process = (IProcess)sender;
- try
+ using (var process = (IProcess)sender)
{
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
- }
- catch
- {
+ process.Dispose();
}
-
- process.Dispose();
}
private async Task SaveRecordingImage(string recordingPath, LiveTvProgram program, ItemImageInfo image)
@@ -1776,44 +1729,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
image = await _libraryManager.ConvertImageToLocal(program, image, 0).ConfigureAwait(false);
}
- string imageSaveFilenameWithoutExtension = null;
-
- switch (image.Type)
+ string imageSaveFilenameWithoutExtension = image.Type switch
{
- case ImageType.Primary:
-
- if (program.IsSeries)
- {
- imageSaveFilenameWithoutExtension = Path.GetFileNameWithoutExtension(recordingPath) + "-thumb";
- }
- else
- {
- imageSaveFilenameWithoutExtension = "poster";
- }
-
- break;
- case ImageType.Logo:
- imageSaveFilenameWithoutExtension = "logo";
- break;
- case ImageType.Thumb:
- if (program.IsSeries)
- {
- imageSaveFilenameWithoutExtension = Path.GetFileNameWithoutExtension(recordingPath) + "-thumb";
- }
- else
- {
- imageSaveFilenameWithoutExtension = "landscape";
- }
-
- break;
- case ImageType.Backdrop:
- imageSaveFilenameWithoutExtension = "fanart";
- break;
- default:
- break;
- }
+ ImageType.Primary => program.IsSeries ? Path.GetFileNameWithoutExtension(recordingPath) + "-thumb" : "poster",
+ ImageType.Logo => "logo",
+ ImageType.Thumb => program.IsSeries ? Path.GetFileNameWithoutExtension(recordingPath) + "-thumb" : "landscape",
+ ImageType.Backdrop => "fanart",
+ _ => null
+ };
- if (string.IsNullOrWhiteSpace(imageSaveFilenameWithoutExtension))
+ if (imageSaveFilenameWithoutExtension == null)
{
return;
}
@@ -1897,7 +1822,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Limit = 1,
ExternalId = timer.ProgramId,
DtoOptions = new DtoOptions(true)
-
}).FirstOrDefault() as LiveTvProgram;
// dummy this up
@@ -1921,11 +1845,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
program.AddGenre("Sports");
}
+
if (timer.IsKids)
{
program.AddGenre("Kids");
program.AddGenre("Children");
}
+
if (timer.IsNews)
{
program.AddGenre("News");
@@ -1980,14 +1906,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
writer.WriteElementString("id", id);
}
+
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id))
{
writer.WriteElementString("imdb_id", id);
}
+
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out id))
{
writer.WriteElementString("tmdbid", id);
}
+
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out id))
{
writer.WriteElementString("zap2itid", id);
@@ -2014,7 +1943,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
private void SaveVideoNfo(TimerInfo timer, string recordingPath, BaseItem item, bool lockData)
{
var nfoPath = Path.ChangeExtension(recordingPath, ".nfo");
@@ -2056,7 +1984,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var formatString = options.ReleaseDateFormat;
- writer.WriteElementString("aired", premiereDate.Value.ToLocalTime().ToString(formatString));
+ writer.WriteElementString(
+ "aired",
+ premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
}
if (item.IndexNumber.HasValue)
@@ -2087,12 +2017,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var formatString = options.ReleaseDateFormat;
- writer.WriteElementString("premiered", item.PremiereDate.Value.ToLocalTime().ToString(formatString));
- writer.WriteElementString("releasedate", item.PremiereDate.Value.ToLocalTime().ToString(formatString));
+ writer.WriteElementString(
+ "premiered",
+ item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
+ writer.WriteElementString(
+ "releasedate",
+ item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
}
}
- writer.WriteElementString("dateadded", DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat));
+ writer.WriteElementString(
+ "dateadded",
+ DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat, CultureInfo.InvariantCulture));
if (item.ProductionYear.HasValue)
{
@@ -2106,7 +2042,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var overview = (item.Overview ?? string.Empty)
.StripHtml()
- .Replace("&quot;", "'");
+ .Replace("&quot;", "'", StringComparison.Ordinal);
writer.WriteElementString("plot", overview);
@@ -2214,17 +2150,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
private static bool IsPersonType(PersonInfo person, string type)
- {
- return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
- }
-
- private void AddGenre(List<string> genres, string genre)
- {
- if (!genres.Contains(genre, StringComparer.OrdinalIgnoreCase))
- {
- genres.Add(genre);
- }
- }
+ => string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase)
+ || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
private LiveTvProgram GetProgramInfoFromCache(string programId)
{
@@ -2283,25 +2210,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return false;
}
- if (!seriesTimer.RecordAnyTime)
+ if (!seriesTimer.RecordAnyTime
+ && Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(10).Ticks)
{
- if (Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(10).Ticks)
- {
- return true;
- }
+ return true;
}
- //if (!seriesTimer.Days.Contains(timer.StartDate.ToLocalTime().DayOfWeek))
- //{
- // return true;
- //}
-
if (seriesTimer.RecordNewOnly && timer.IsRepeat)
{
return true;
}
- if (!seriesTimer.RecordAnyChannel && !string.Equals(timer.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase))
+ if (!seriesTimer.RecordAnyChannel
+ && !string.Equals(timer.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase))
{
return true;
}
@@ -2346,7 +2267,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var allTimers = GetTimersForSeries(seriesTimer).ToList();
-
var enabledTimersForSeries = new List<TimerInfo>();
foreach (var timer in allTimers)
{
@@ -2369,10 +2289,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
enabledTimersForSeries.Add(timer);
}
+
_timerProvider.Add(timer);
TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
}
+
// Only update if not currently active - test both new timer and existing in case Id's are different
// Id's could be different if the timer was created manually prior to series timer creation
else if (!_activeRecordings.TryGetValue(timer.Id, out _) && !_activeRecordings.TryGetValue(existingTimer.Id, out _))
@@ -2508,13 +2430,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (!tempChannelCache.TryGetValue(parent.ChannelId, out LiveTvChannel channel))
{
- channel = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
- ItemIds = new[] { parent.ChannelId },
- DtoOptions = new DtoOptions()
-
- }).Cast<LiveTvChannel>().FirstOrDefault();
+ channel = _libraryManager.GetItemList(
+ new InternalItemsQuery
+ {
+ IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
+ ItemIds = new[] { parent.ChannelId },
+ DtoOptions = new DtoOptions()
+ }).FirstOrDefault() as LiveTvChannel;
if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId))
{
@@ -2567,13 +2489,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (!tempChannelCache.TryGetValue(programInfo.ChannelId, out LiveTvChannel channel))
{
- channel = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
- ItemIds = new[] { programInfo.ChannelId },
- DtoOptions = new DtoOptions()
-
- }).Cast<LiveTvChannel>().FirstOrDefault();
+ channel = _libraryManager.GetItemList(
+ new InternalItemsQuery
+ {
+ IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
+ ItemIds = new[] { programInfo.ChannelId },
+ DtoOptions = new DtoOptions()
+ }).FirstOrDefault() as LiveTvChannel;
if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId))
{
@@ -2618,10 +2540,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var providerId in timerInfo.ProviderIds)
{
- var srch = "Series";
- if (providerId.Key.StartsWith(srch, StringComparison.OrdinalIgnoreCase))
+ const string Search = "Series";
+ if (providerId.Key.StartsWith(Search, StringComparison.OrdinalIgnoreCase))
{
- seriesProviderIds[providerId.Key.Substring(srch.Length)] = providerId.Value;
+ seriesProviderIds[providerId.Key.Substring(Search.Length)] = providerId.Value;
}
}
@@ -2632,12 +2554,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if ((program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) || !string.IsNullOrWhiteSpace(program.EpisodeTitle))
{
- var seriesIds = _libraryManager.GetItemIds(new InternalItemsQuery
- {
- IncludeItemTypes = new[] { typeof(Series).Name },
- Name = program.Name
-
- }).ToArray();
+ var seriesIds = _libraryManager.GetItemIds(
+ new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Name = program.Name
+ }).ToArray();
if (seriesIds.Length == 0)
{
@@ -2666,59 +2588,70 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return false;
}
- private bool _disposed;
+ /// <inheritdoc />
public void Dispose()
{
- _disposed = true;
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ _recordingDeleteSemaphore.Dispose();
+ }
+
foreach (var pair in _activeRecordings.ToList())
{
pair.Value.CancellationTokenSource.Cancel();
}
+
+ _disposed = true;
}
- public List<VirtualFolderInfo> GetRecordingFolders()
+ public IEnumerable<VirtualFolderInfo> GetRecordingFolders()
{
- var list = new List<VirtualFolderInfo>();
-
var defaultFolder = RecordingPath;
var defaultName = "Recordings";
if (Directory.Exists(defaultFolder))
{
- list.Add(new VirtualFolderInfo
+ yield return new VirtualFolderInfo
{
Locations = new string[] { defaultFolder },
Name = defaultName
- });
+ };
}
var customPath = GetConfiguration().MovieRecordingPath;
- if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
+ if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath))
{
- list.Add(new VirtualFolderInfo
+ yield return new VirtualFolderInfo
{
Locations = new string[] { customPath },
Name = "Recorded Movies",
CollectionType = CollectionType.Movies
- });
+ };
}
customPath = GetConfiguration().SeriesRecordingPath;
- if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
+ if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath))
{
- list.Add(new VirtualFolderInfo
+ yield return new VirtualFolderInfo
{
Locations = new string[] { customPath },
Name = "Recorded Shows",
CollectionType = CollectionType.TvShows
- });
+ };
}
-
- return list;
}
- private const int TunerDiscoveryDurationMs = 3000;
-
public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
{
var list = new List<TunerHostInfo>();
@@ -2737,6 +2670,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase))
.ToList();
}
+
list.AddRange(discoveredDevices);
}
@@ -2773,11 +2707,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDuationMs, CancellationToken cancellationToken)
+ private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken)
{
try
{
- var discoveredDevices = await host.DiscoverDevices(discoveryDuationMs, cancellationToken).ConfigureAwait(false);
+ var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false);
foreach (var device in discoveredDevices)
{
@@ -2794,11 +2728,4 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
}
- public static class ConfigurationExtension
- {
- public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager manager)
- {
- return manager.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
- }
- }
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
new file mode 100644
index 000000000..498aa3c26
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
@@ -0,0 +1,68 @@
+#pragma warning disable SA1600
+#pragma warning disable CS1591
+
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Controller.LiveTv;
+
+namespace Emby.Server.Implementations.LiveTv.EmbyTV
+{
+
+ internal class EpgChannelData
+ {
+ public EpgChannelData(IEnumerable<ChannelInfo> channels)
+ {
+ ChannelsById = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
+ ChannelsByNumber = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
+ ChannelsByName = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var channel in channels)
+ {
+ ChannelsById[channel.Id] = channel;
+
+ if (!string.IsNullOrEmpty(channel.Number))
+ {
+ ChannelsByNumber[channel.Number] = channel;
+ }
+
+ var normalizedName = NormalizeName(channel.Name ?? string.Empty);
+ if (!string.IsNullOrWhiteSpace(normalizedName))
+ {
+ ChannelsByName[normalizedName] = channel;
+ }
+ }
+ }
+
+ private Dictionary<string, ChannelInfo> ChannelsById { get; set; }
+
+ private Dictionary<string, ChannelInfo> ChannelsByNumber { get; set; }
+
+ private Dictionary<string, ChannelInfo> ChannelsByName { get; set; }
+
+ public ChannelInfo GetChannelById(string id)
+ {
+ ChannelsById.TryGetValue(id, out var result);
+
+ return result;
+ }
+
+ public ChannelInfo GetChannelByNumber(string number)
+ {
+ ChannelsByNumber.TryGetValue(number, out var result);
+
+ return result;
+ }
+
+ public ChannelInfo GetChannelByName(string name)
+ {
+ ChannelsByName.TryGetValue(name, out var result);
+
+ return result;
+ }
+
+ public static string NormalizeName(string value)
+ {
+ return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/NfoConfigurationExtensions.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/NfoConfigurationExtensions.cs
new file mode 100644
index 000000000..83f5e8413
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/NfoConfigurationExtensions.cs
@@ -0,0 +1,19 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+
+namespace Emby.Server.Implementations.LiveTv.EmbyTV
+{
+ /// <summary>
+ /// Class containing extension methods for working with the nfo configuration.
+ /// </summary>
+ public static class NfoConfigurationExtensions
+ {
+ /// <summary>
+ /// Gets the nfo configuration.
+ /// </summary>
+ /// <param name="configurationManager">The configuration manager.</param>
+ /// <returns>The nfo configuration.</returns>
+ public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager configurationManager)
+ => configurationManager.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index ee7db1413..e3f9df35a 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -1,3 +1,6 @@
+#pragma warning disable SA1600
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -35,7 +38,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv
{
/// <summary>
- /// Class LiveTvManager
+ /// Class LiveTvManager.
/// </summary>
public class LiveTvManager : ILiveTvManager, IDisposable
{
diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index e1dce82ff..6b25e3df0 100644
--- a/Emby.Server.Implementations/Localization/Core/fr.json
+++ b/Emby.Server.Implementations/Localization/Core/fr.json
@@ -5,7 +5,7 @@
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès",
"Books": "Livres",
- "CameraImageUploadedFrom": "Une nouvelle image de caméra a été chargée depuis {0}",
+ "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}",
"Channels": "Chaînes",
"ChapterNameValue": "Chapitre {0}",
"Collections": "Collections",
diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json
index c10fbe58f..b91c98ca0 100644
--- a/Emby.Server.Implementations/Localization/Core/ms.json
+++ b/Emby.Server.Implementations/Localization/Core/ms.json
@@ -1,23 +1,23 @@
{
"Albums": "Album-album",
"AppDeviceValues": "App: {0}, Device: {1}",
- "Application": "Application",
+ "Application": "Aplikasi",
"Artists": "Artis-artis",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
"Books": "Buku-buku",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
- "Channels": "Channels",
+ "Channels": "Saluran",
"ChapterNameValue": "Chapter {0}",
- "Collections": "Collections",
+ "Collections": "Koleksi",
"DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected",
- "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
+ "FailedLoginAttemptWithUserName": "Cubaan log masuk gagal dari {0}",
"Favorites": "Favorites",
"Folders": "Folders",
- "Genres": "Genres",
+ "Genres": "Genre-genre",
"HeaderAlbumArtists": "Album Artists",
- "HeaderCameraUploads": "Camera Uploads",
- "HeaderContinueWatching": "Continue Watching",
+ "HeaderCameraUploads": "Muatnaik Kamera",
+ "HeaderContinueWatching": "Terus Menonton",
"HeaderFavoriteAlbums": "Favorite Albums",
"HeaderFavoriteArtists": "Favorite Artists",
"HeaderFavoriteEpisodes": "Favorite Episodes",
@@ -39,8 +39,8 @@
"MessageServerConfigurationUpdated": "Server configuration has been updated",
"MixedContent": "Mixed content",
"Movies": "Movies",
- "Music": "Music",
- "MusicVideos": "Music videos",
+ "Music": "Muzik",
+ "MusicVideos": "Video muzik",
"NameInstallFailed": "{0} installation failed",
"NameSeasonNumber": "Season {0}",
"NameSeasonUnknown": "Season Unknown",
@@ -68,8 +68,8 @@
"PluginUninstalledWithName": "{0} was uninstalled",
"PluginUpdatedWithName": "{0} was updated",
"ProviderValue": "Provider: {0}",
- "ScheduledTaskFailedWithName": "{0} failed",
- "ScheduledTaskStartedWithName": "{0} started",
+ "ScheduledTaskFailedWithName": "{0} gagal",
+ "ScheduledTaskStartedWithName": "{0} bermula",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
"Shows": "Series",
"Songs": "Songs",
@@ -78,7 +78,7 @@
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
"Sync": "Sync",
- "System": "System",
+ "System": "Sistem",
"TvShows": "TV Shows",
"User": "User",
"UserCreatedWithName": "User {0} has been created",
@@ -92,6 +92,6 @@
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
- "ValueSpecialEpisodeName": "Special - {0}",
- "VersionNumber": "Version {0}"
+ "ValueSpecialEpisodeName": "Khas - {0}",
+ "VersionNumber": "Versi {0}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/sr.json
@@ -0,0 +1 @@
+{}
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 73ffaa53d..38cdb0998 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -19,7 +19,7 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index a41112191..166e22909 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -27,10 +27,10 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
- <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
- <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@@ -39,8 +39,8 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.6.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.0.1" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.1" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 5881e22a7..1f7dc0d71 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -759,7 +759,7 @@ namespace MediaBrowser.Api.Playback
if (mediaSource == null)
{
- var mediaSources = await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false);
+ var mediaSources = await MediaSourceManager.GetPlaybackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false);
mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? mediaSources[0]
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 7375e1509..4b9bb8010 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -1,6 +1,13 @@
+#pragma warning disable CS1591
+#pragma warning disable SA1402
+#pragma warning disable SA1600
+#pragma warning disable SA1649
+
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
+using System.Text.Json;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -54,7 +61,7 @@ namespace MediaBrowser.Api.Playback
public class GetBitrateTestBytes
{
[ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")]
- public long Size { get; set; }
+ public int Size { get; set; }
public GetBitrateTestBytes()
{
@@ -72,7 +79,6 @@ namespace MediaBrowser.Api.Playback
private readonly INetworkManager _networkManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IUserManager _userManager;
- private readonly IJsonSerializer _json;
private readonly IAuthorizationContext _authContext;
public MediaInfoService(
@@ -85,7 +91,6 @@ namespace MediaBrowser.Api.Playback
INetworkManager networkManager,
IMediaEncoder mediaEncoder,
IUserManager userManager,
- IJsonSerializer json,
IAuthorizationContext authContext)
: base(logger, serverConfigurationManager, httpResultFactory)
{
@@ -95,20 +100,35 @@ namespace MediaBrowser.Api.Playback
_networkManager = networkManager;
_mediaEncoder = mediaEncoder;
_userManager = userManager;
- _json = json;
_authContext = authContext;
}
public object Get(GetBitrateTestBytes request)
{
- var bytes = new byte[request.Size];
+ const int MaxSize = 10_000_000;
+
+ var size = request.Size;
+
+ if (size <= 0)
+ {
+ throw new ArgumentException($"The requested size ({size}) is equal to or smaller than 0.", nameof(request));
+ }
- for (var i = 0; i < bytes.Length; i++)
+ if (size > MaxSize)
{
- bytes[i] = 0;
+ throw new ArgumentException($"The requested size ({size}) is larger than the max allowed value ({MaxSize}).", nameof(request));
}
- return ResultFactory.GetResult(null, bytes, "application/octet-stream");
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
+ try
+ {
+ new Random().NextBytes(buffer);
+ return ResultFactory.GetResult(null, buffer, "application/octet-stream");
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
+ }
}
public async Task<object> Get(GetPlaybackInfo request)
@@ -166,8 +186,7 @@ namespace MediaBrowser.Api.Playback
public void Post(CloseMediaSource request)
{
- var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId);
- Task.WaitAll(task);
+ _mediaSourceManager.CloseLiveStream(request.LiveStreamId).GetAwaiter().GetResult();
}
public async Task<PlaybackInfoResponse> GetPlaybackInfo(GetPostedPlaybackInfo request)
@@ -176,7 +195,7 @@ namespace MediaBrowser.Api.Playback
var profile = request.DeviceProfile;
- //Logger.LogInformation("GetPostedPlaybackInfo profile: {profile}", _json.SerializeToString(profile));
+ Logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
if (profile == null)
{
@@ -215,9 +234,7 @@ namespace MediaBrowser.Api.Playback
StartTimeTicks = request.StartTimeTicks,
SubtitleStreamIndex = request.SubtitleStreamIndex,
UserId = request.UserId,
- OpenToken = mediaSource.OpenToken,
- //EnableMediaProbe = request.EnableMediaProbe
-
+ OpenToken = mediaSource.OpenToken
}).ConfigureAwait(false);
info.MediaSources = new MediaSourceInfo[] { openStreamResult.MediaSource };
@@ -251,9 +268,8 @@ namespace MediaBrowser.Api.Playback
{
// Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
// Should we move this directly into MediaSourceManager?
-
- var json = _json.SerializeToString(obj);
- return _json.DeserializeFromString<T>(json);
+ var json = JsonSerializer.SerializeToUtf8Bytes(obj);
+ return JsonSerializer.Deserialize<T>(json);
}
private async Task<PlaybackInfoResponse> GetPlaybackInfo(Guid id, Guid userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null)
@@ -268,7 +284,7 @@ namespace MediaBrowser.Api.Playback
try
{
// TODO handle supportedLiveMediaTypes ?
- mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, user, true, false, CancellationToken.None).ConfigureAwait(false);
+ mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, false, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -294,7 +310,7 @@ namespace MediaBrowser.Api.Playback
result.MediaSources = new MediaSourceInfo[] { mediaSource };
}
- if (result.MediaSources.Length == 0)
+ if (result.MediaSources.Count == 0)
{
if (!result.ErrorCode.HasValue)
{
@@ -311,7 +327,8 @@ namespace MediaBrowser.Api.Playback
return result;
}
- private void SetDeviceSpecificData(Guid itemId,
+ private void SetDeviceSpecificData(
+ Guid itemId,
PlaybackInfoResponse result,
DeviceProfile profile,
AuthorizationInfo auth,
@@ -339,7 +356,8 @@ namespace MediaBrowser.Api.Playback
SortMediaSources(result, maxBitrate);
}
- private void SetDeviceSpecificData(BaseItem item,
+ private void SetDeviceSpecificData(
+ BaseItem item,
MediaSourceInfo mediaSource,
DeviceProfile profile,
AuthorizationInfo auth,
@@ -383,10 +401,12 @@ namespace MediaBrowser.Api.Playback
{
mediaSource.SupportsDirectPlay = false;
}
+
if (!enableDirectStream)
{
mediaSource.SupportsDirectStream = false;
}
+
if (!enableTranscoding)
{
mediaSource.SupportsTranscoding = false;
@@ -434,9 +454,9 @@ namespace MediaBrowser.Api.Playback
}
// The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+ ? streamBuilder.BuildAudioItem(options)
+ : streamBuilder.BuildVideoItem(options);
if (streamInfo == null || !streamInfo.IsDirectStream)
{
@@ -473,9 +493,9 @@ namespace MediaBrowser.Api.Playback
}
// The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+ ? streamBuilder.BuildAudioItem(options)
+ : streamBuilder.BuildVideoItem(options);
if (streamInfo == null || !streamInfo.IsDirectStream)
{
@@ -493,9 +513,9 @@ namespace MediaBrowser.Api.Playback
options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
// The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+ ? streamBuilder.BuildAudioItem(options)
+ : streamBuilder.BuildVideoItem(options);
if (streamInfo != null)
{
@@ -510,10 +530,12 @@ namespace MediaBrowser.Api.Playback
{
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
}
+
if (!allowAudioStreamCopy)
{
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
}
+
mediaSource.TranscodingContainer = streamInfo.Container;
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
}
@@ -609,14 +631,11 @@ namespace MediaBrowser.Api.Playback
}).ThenBy(i =>
{
- switch (i.Protocol)
+ return i.Protocol switch
{
- case MediaProtocol.File:
- return 0;
- default:
- return 1;
- }
-
+ MediaProtocol.File => 0,
+ _ => 1,
+ };
}).ThenBy(i =>
{
if (maxBitrate.HasValue)
diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs
index 9cba9df13..cbf981dfe 100644
--- a/MediaBrowser.Api/Playback/UniversalAudioService.cs
+++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs
@@ -74,7 +74,6 @@ namespace MediaBrowser.Api.Playback
[Authenticated]
public class UniversalAudioService : BaseApiService
{
- private readonly ILoggerFactory _loggerFactory;
private readonly EncodingHelper _encodingHelper;
public UniversalAudioService(
@@ -243,7 +242,6 @@ namespace MediaBrowser.Api.Playback
NetworkManager,
MediaEncoder,
UserManager,
- JsonSerializer,
AuthorizationContext)
{
Request = Request
@@ -300,6 +298,10 @@ namespace MediaBrowser.Api.Playback
var transcodingProfile = deviceProfile.TranscodingProfiles[0];
+ // hls segment container can only be mpegts or fmp4 per ffmpeg documentation
+ // TODO: remove this when we switch back to the segment muxer
+ var supportedHLSContainers = new string[] { "mpegts", "fmp4" };
+
var newRequest = new GetMasterHlsAudioPlaylist
{
AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)),
@@ -312,7 +314,8 @@ namespace MediaBrowser.Api.Playback
PlaySessionId = playbackInfoResult.PlaySessionId,
StartTimeTicks = request.StartTimeTicks,
Static = isStatic,
- SegmentContainer = request.TranscodingContainer,
+ // fallback to mpegts if device reports some weird value unsupported by hls
+ SegmentContainer = Array.Exists(supportedHLSContainers, element => element == request.TranscodingContainer) ? request.TranscodingContainer : "mpegts",
AudioSampleRate = request.MaxAudioSampleRate,
MaxAudioBitDepth = request.MaxAudioBitDepth,
BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames,
diff --git a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
index 344aecf53..828415c18 100644
--- a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
+++ b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
diff --git a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs
index 4c4060096..9b4ed772d 100644
--- a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs
+++ b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs
index caf2edd83..7773596af 100644
--- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs
+++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs
index 19b8be47a..3477c1c04 100644
--- a/MediaBrowser.Common/Cryptography/PasswordHash.cs
+++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -15,24 +16,24 @@ namespace MediaBrowser.Common.Cryptography
public class PasswordHash
{
private readonly Dictionary<string, string> _parameters;
+ private readonly byte[] _salt;
+ private readonly byte[] _hash;
public PasswordHash(string id, byte[] hash)
: this(id, hash, Array.Empty<byte>())
{
-
}
public PasswordHash(string id, byte[] hash, byte[] salt)
: this(id, hash, salt, new Dictionary<string, string>())
{
-
}
public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters)
{
Id = id;
- Hash = hash;
- Salt = salt;
+ _hash = hash;
+ _salt = salt;
_parameters = parameters;
}
@@ -45,25 +46,24 @@ namespace MediaBrowser.Common.Cryptography
/// <summary>
/// Gets the additional parameters used by the hash function.
/// </summary>
- /// <value></value>
public IReadOnlyDictionary<string, string> Parameters => _parameters;
/// <summary>
/// Gets the salt used for hashing the password.
/// </summary>
/// <value>Returns the salt used for hashing the password.</value>
- public byte[] Salt { get; }
+ public ReadOnlySpan<byte> Salt => _salt;
/// <summary>
/// Gets the hashed password.
/// </summary>
/// <value>Return the hashed password.</value>
- public byte[] Hash { get; }
+ public ReadOnlySpan<byte> Hash => _hash;
public static PasswordHash Parse(string hashString)
{
- string[] splitted = hashString.Split('$');
// The string should at least contain the hash function and the hash itself
+ string[] splitted = hashString.Split('$');
if (splitted.Length < 3)
{
throw new ArgumentException("String doesn't contain enough segments", nameof(hashString));
@@ -77,7 +77,7 @@ namespace MediaBrowser.Common.Cryptography
// Optional parameters
Dictionary<string, string> parameters = new Dictionary<string, string>();
- if (splitted[index].IndexOf('=') != -1)
+ if (splitted[index].IndexOf('=', StringComparison.Ordinal) != -1)
{
foreach (string paramset in splitted[index++].Split(','))
{
@@ -98,6 +98,7 @@ namespace MediaBrowser.Common.Cryptography
byte[] hash;
byte[] salt;
+
// Check if the string also contains a salt
if (splitted.Length - index == 2)
{
@@ -141,14 +142,14 @@ namespace MediaBrowser.Common.Cryptography
.Append(Id);
SerializeParameters(str);
- if (Salt.Length != 0)
+ if (_salt.Length != 0)
{
str.Append('$')
- .Append(Hex.Encode(Salt, false));
+ .Append(Hex.Encode(_salt, false));
}
return str.Append('$')
- .Append(Hex.Encode(Hash, false)).ToString();
+ .Append(Hex.Encode(_hash, false)).ToString();
}
}
}
diff --git a/MediaBrowser.Common/Events/EventHelper.cs b/MediaBrowser.Common/Events/EventHelper.cs
index b67315df6..c9d3226ac 100644
--- a/MediaBrowser.Common/Events/EventHelper.cs
+++ b/MediaBrowser.Common/Events/EventHelper.cs
@@ -1,15 +1,13 @@
-#pragma warning disable CS1591
-
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Common.Events
{
- // TODO: @bond Remove
/// <summary>
- /// Class EventHelper
+ /// Class EventHelper.
/// </summary>
+ // TODO: @bond Remove
public static class EventHelper
{
/// <summary>
@@ -40,7 +38,7 @@ namespace MediaBrowser.Common.Events
/// <summary>
/// Queues the event.
/// </summary>
- /// <typeparam name="T"></typeparam>
+ /// <typeparam name="T">Argument type for the <c>handler</c>.</typeparam>
/// <param name="handler">The handler.</param>
/// <param name="sender">The sender.</param>
/// <param name="args">The args.</param>
diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs
index 33473c2be..08964420e 100644
--- a/MediaBrowser.Common/Extensions/BaseExtensions.cs
+++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs
@@ -1,12 +1,12 @@
using System;
+using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
-using System.Security.Cryptography;
namespace MediaBrowser.Common.Extensions
{
/// <summary>
- /// Class BaseExtensions
+ /// Class BaseExtensions.
/// </summary>
public static class BaseExtensions
{
@@ -30,10 +30,13 @@ namespace MediaBrowser.Common.Extensions
/// <returns><see cref="Guid" />.</returns>
public static Guid GetMD5(this string str)
{
+#pragma warning disable CA5351
using (var provider = MD5.Create())
{
return new Guid(provider.ComputeHash(Encoding.Unicode.GetBytes(str)));
}
+
+#pragma warning restore CA5351
}
}
}
diff --git a/MediaBrowser.Common/Extensions/CopyToExtensions.cs b/MediaBrowser.Common/Extensions/CopyToExtensions.cs
index 78a73f07e..2ecbc6539 100644
--- a/MediaBrowser.Common/Extensions/CopyToExtensions.cs
+++ b/MediaBrowser.Common/Extensions/CopyToExtensions.cs
@@ -5,7 +5,7 @@ namespace MediaBrowser.Common.Extensions
/// <summary>
/// Provides <c>CopyTo</c> extensions methods for <see cref="IReadOnlyList{T}" />.
/// </summary>
- public static class CollectionExtensions
+ public static class CopyToExtensions
{
/// <summary>
/// Copies all the elements of the current collection to the specified list
@@ -14,7 +14,7 @@ namespace MediaBrowser.Common.Extensions
/// <param name="source">The current collection that is the source of the elements.</param>
/// <param name="destination">The list that is the destination of the elements copied from the current collection.</param>
/// <param name="index">A 32-bit integer that represents the index in <c>destination</c> at which copying begins.</param>
- /// <typeparam name="T"></typeparam>
+ /// <typeparam name="T">The type of the array.</typeparam>
public static void CopyTo<T>(this IReadOnlyList<T> source, IList<T> destination, int index = 0)
{
for (int i = 0; i < source.Count; i++)
diff --git a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs
new file mode 100644
index 000000000..48e758ee4
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace MediaBrowser.Common.Extensions
+{
+ /// <summary>
+ /// Class MethodNotAllowedException.
+ /// </summary>
+ public class MethodNotAllowedException : Exception
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class.
+ /// </summary>
+ public MethodNotAllowedException()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ public MethodNotAllowedException(string message)
+ : base(message)
+ {
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs
new file mode 100644
index 000000000..4e5d4e9ca
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs
@@ -0,0 +1,26 @@
+#pragma warning disable CS1591
+#pragma warning disable SA1600
+
+using System;
+
+namespace MediaBrowser.Common.Extensions
+{
+ public class RateLimitExceededException : Exception
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RateLimitExceededException" /> class.
+ /// </summary>
+ public RateLimitExceededException()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RateLimitExceededException" /> class.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ public RateLimitExceededException(string message)
+ : base(message)
+ {
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs
index 9b064a40d..22130c5a1 100644
--- a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs
+++ b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs
@@ -1,11 +1,9 @@
-#pragma warning disable CS1591
-
using System;
namespace MediaBrowser.Common.Extensions
{
/// <summary>
- /// Class ResourceNotFoundException
+ /// Class ResourceNotFoundException.
/// </summary>
public class ResourceNotFoundException : Exception
{
@@ -14,7 +12,6 @@ namespace MediaBrowser.Common.Extensions
/// </summary>
public ResourceNotFoundException()
{
-
}
/// <summary>
@@ -24,66 +21,6 @@ namespace MediaBrowser.Common.Extensions
public ResourceNotFoundException(string message)
: base(message)
{
-
- }
- }
-
- /// <summary>
- /// Class MethodNotAllowedException
- /// </summary>
- public class MethodNotAllowedException : Exception
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class.
- /// </summary>
- public MethodNotAllowedException()
- {
-
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class.
- /// </summary>
- /// <param name="message">The message.</param>
- public MethodNotAllowedException(string message)
- : base(message)
- {
-
- }
- }
-
- public class RemoteServiceUnavailableException : Exception
- {
- public RemoteServiceUnavailableException()
- {
-
- }
-
- public RemoteServiceUnavailableException(string message)
- : base(message)
- {
-
- }
- }
-
- public class RateLimitExceededException : Exception
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="RateLimitExceededException" /> class.
- /// </summary>
- public RateLimitExceededException()
- {
-
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="RateLimitExceededException" /> class.
- /// </summary>
- /// <param name="message">The message.</param>
- public RateLimitExceededException(string message)
- : base(message)
- {
-
}
}
}
diff --git a/MediaBrowser.Common/Hex.cs b/MediaBrowser.Common/Hex.cs
index b2d9aea3a..559109f74 100644
--- a/MediaBrowser.Common/Hex.cs
+++ b/MediaBrowser.Common/Hex.cs
@@ -14,11 +14,11 @@ namespace MediaBrowser.Common
internal const int LastHexSymbol = 0x66; // 102: f
/// <summary>
- /// Map from an ASCII char to its hex value shifted,
+ /// Gets a map from an ASCII char to its hex value shifted,
/// e.g. <c>b</c> -> 11. 0xFF means it's not a hex symbol.
/// </summary>
- /// <value></value>
- internal static ReadOnlySpan<byte> HexLookup => new byte[] {
+ internal static ReadOnlySpan<byte> HexLookup => new byte[]
+ {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
@@ -29,10 +29,10 @@ namespace MediaBrowser.Common
};
/// <summary>
- /// Encodes <c>bytes</c> as a hex string.
+ /// Encodes each element of the specified bytes as its hexadecimal string representation.
/// </summary>
- /// <param name="bytes"></param>
- /// <param name="lowercase"></param>
+ /// <param name="bytes">An array of bytes.</param>
+ /// <param name="lowercase"><c>true</c> to use lowercase hexadecimal characters; otherwise <c>false</c>.</param>
/// <returns><c>bytes</c> as a hex string.</returns>
public static string Encode(ReadOnlySpan<byte> bytes, bool lowercase = true)
{
diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs
index 6668e98aa..68a24aaba 100644
--- a/MediaBrowser.Common/IApplicationHost.cs
+++ b/MediaBrowser.Common/IApplicationHost.cs
@@ -8,11 +8,16 @@ using Microsoft.Extensions.DependencyInjection;
namespace MediaBrowser.Common
{
/// <summary>
- /// An interface to be implemented by the applications hosting a kernel
+ /// An interface to be implemented by the applications hosting a kernel.
/// </summary>
public interface IApplicationHost
{
/// <summary>
+ /// Occurs when [has pending restart changed].
+ /// </summary>
+ event EventHandler HasPendingRestartChanged;
+
+ /// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
@@ -25,13 +30,13 @@ namespace MediaBrowser.Common
string SystemId { get; }
/// <summary>
- /// Gets or sets a value indicating whether this instance has pending kernel reload.
+ /// Gets a value indicating whether this instance has pending kernel reload.
/// </summary>
/// <value><c>true</c> if this instance has pending kernel reload; otherwise, <c>false</c>.</value>
bool HasPendingRestart { get; }
/// <summary>
- /// Gets or sets a value indicating whether this instance is currently shutting down.
+ /// Gets a value indicating whether this instance is currently shutting down.
/// </summary>
/// <value><c>true</c> if this instance is shutting down; otherwise, <c>false</c>.</value>
bool IsShuttingDown { get; }
@@ -43,27 +48,12 @@ namespace MediaBrowser.Common
bool CanSelfRestart { get; }
/// <summary>
- /// Get the version class of the system.
+ /// Gets the version class of the system.
/// </summary>
/// <value><see cref="PackageVersionClass.Release" /> or <see cref="PackageVersionClass.Beta" />.</value>
PackageVersionClass SystemUpdateLevel { get; }
/// <summary>
- /// Occurs when [has pending restart changed].
- /// </summary>
- event EventHandler HasPendingRestartChanged;
-
- /// <summary>
- /// Notifies the pending restart.
- /// </summary>
- void NotifyPendingRestart();
-
- /// <summary>
- /// Restarts this instance.
- /// </summary>
- void Restart();
-
- /// <summary>
/// Gets the application version.
/// </summary>
/// <value>The application version.</value>
@@ -88,6 +78,22 @@ namespace MediaBrowser.Common
string ApplicationUserAgentAddress { get; }
/// <summary>
+ /// Gets the plugins.
+ /// </summary>
+ /// <value>The plugins.</value>
+ IReadOnlyList<IPlugin> Plugins { get; }
+
+ /// <summary>
+ /// Notifies the pending restart.
+ /// </summary>
+ void NotifyPendingRestart();
+
+ /// <summary>
+ /// Restarts this instance.
+ /// </summary>
+ void Restart();
+
+ /// <summary>
/// Gets the exports.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
@@ -98,22 +104,17 @@ namespace MediaBrowser.Common
/// <summary>
/// Resolves this instance.
/// </summary>
- /// <typeparam name="T"></typeparam>
+ /// <typeparam name="T">The <c>Type</c>.</typeparam>
/// <returns>``0.</returns>
T Resolve<T>();
/// <summary>
/// Shuts down.
/// </summary>
+ /// <returns>A task.</returns>
Task Shutdown();
/// <summary>
- /// Gets the plugins.
- /// </summary>
- /// <value>The plugins.</value>
- IPlugin[] Plugins { get; }
-
- /// <summary>
/// Removes the plugin.
/// </summary>
/// <param name="plugin">The plugin.</param>
@@ -122,6 +123,8 @@ namespace MediaBrowser.Common
/// <summary>
/// Inits this instance.
/// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <returns>A task.</returns>
Task InitAsync(IServiceCollection serviceCollection);
/// <summary>
diff --git a/MediaBrowser.Common/Json/Converters/GuidConverter.cs b/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs
index 3081e12ee..d35a761f3 100644
--- a/MediaBrowser.Common/Json/Converters/GuidConverter.cs
+++ b/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Common.Json.Converters
/// <summary>
/// Converts a GUID object or value to/from JSON.
/// </summary>
- public class GuidConverter : JsonConverter<Guid>
+ public class JsonGuidConverter : JsonConverter<Guid>
{
/// <inheritdoc />
public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
diff --git a/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs b/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs
new file mode 100644
index 000000000..fe5dd6cd4
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Buffers;
+using System.Buffers.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ /// <summary>
+ /// Converts a GUID object or value to/from JSON.
+ /// </summary>
+ public class JsonInt32Converter : JsonConverter<int>
+ {
+ /// <inheritdoc />
+ public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ static void ThrowFormatException() => throw new FormatException("Invalid format for an integer.");
+ ReadOnlySpan<byte> span = stackalloc byte[0];
+
+ if (reader.HasValueSequence)
+ {
+ long sequenceLength = reader.ValueSequence.Length;
+ Span<byte> stackSpan = stackalloc byte[(int)sequenceLength];
+ reader.ValueSequence.CopyTo(stackSpan);
+ span = stackSpan;
+ }
+ else
+ {
+ span = reader.ValueSpan;
+ }
+
+ if (!Utf8Parser.TryParse(span, out int number, out _))
+ {
+ ThrowFormatException();
+ }
+
+ return number;
+ }
+
+ /// <inheritdoc />
+ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
+ {
+ static void ThrowInvalidOperationException() => throw new InvalidOperationException();
+ Span<byte> span = stackalloc byte[16];
+ if (Utf8Formatter.TryFormat(value, span, out int bytesWritten))
+ {
+ writer.WriteStringValue(span.Slice(0, bytesWritten));
+ }
+
+ ThrowInvalidOperationException();
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs
index 4ba0d5a1a..4a6ee0a79 100644
--- a/MediaBrowser.Common/Json/JsonDefaults.cs
+++ b/MediaBrowser.Common/Json/JsonDefaults.cs
@@ -21,7 +21,7 @@ namespace MediaBrowser.Common.Json
WriteIndented = false
};
- options.Converters.Add(new GuidConverter());
+ options.Converters.Add(new JsonGuidConverter());
options.Converters.Add(new JsonStringEnumConverter());
return options;
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 889fbfa5a..567fcdda1 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -12,8 +12,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.0.0" />
- <PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.0" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.0" />
+ <PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
</ItemGroup>
<ItemGroup>
@@ -27,9 +27,16 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
- <PropertyGroup>
- <!-- We need at least C# 7.1 for the "default literal" feature-->
- <LangVersion>latest</LangVersion>
+ <!-- Code analyzers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <!-- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <!-- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> -->
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
diff --git a/MediaBrowser.Common/Net/CustomHeaderNames.cs b/MediaBrowser.Common/Net/CustomHeaderNames.cs
index 5ca9897eb..8cc48c55f 100644
--- a/MediaBrowser.Common/Net/CustomHeaderNames.cs
+++ b/MediaBrowser.Common/Net/CustomHeaderNames.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
namespace MediaBrowser.Common.Net
{
diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs
index 18c4b181f..8207a45f3 100644
--- a/MediaBrowser.Common/Net/HttpRequestOptions.cs
+++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -8,11 +9,22 @@ using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Common.Net
{
/// <summary>
- /// Class HttpRequestOptions
+ /// Class HttpRequestOptions.
/// </summary>
public class HttpRequestOptions
{
/// <summary>
+ /// Initializes a new instance of the <see cref="HttpRequestOptions"/> class.
+ /// </summary>
+ public HttpRequestOptions()
+ {
+ RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ CacheMode = CacheMode.None;
+ DecompressionMethod = CompressionMethod.Deflate;
+ }
+
+ /// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>The URL.</value>
@@ -71,14 +83,17 @@ namespace MediaBrowser.Common.Net
public string RequestContentType { get; set; }
public string RequestContent { get; set; }
+
public byte[] RequestContentBytes { get; set; }
public bool BufferContent { get; set; }
public bool LogErrorResponseBody { get; set; }
+
public bool EnableKeepAlive { get; set; }
public CacheMode CacheMode { get; set; }
+
public TimeSpan CacheLength { get; set; }
public bool EnableDefaultUserAgent { get; set; }
@@ -89,17 +104,6 @@ namespace MediaBrowser.Common.Net
return value;
}
-
- /// <summary>
- /// Initializes a new instance of the <see cref="HttpRequestOptions"/> class.
- /// </summary>
- public HttpRequestOptions()
- {
- RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- CacheMode = CacheMode.None;
- DecompressionMethod = CompressionMethod.Deflate;
- }
}
public enum CacheMode
diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs
index 0de034b0e..d711ad64a 100644
--- a/MediaBrowser.Common/Net/HttpResponseInfo.cs
+++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.IO;
using System.Net;
@@ -8,10 +6,25 @@ using System.Net.Http.Headers;
namespace MediaBrowser.Common.Net
{
/// <summary>
- /// Class HttpResponseInfo
+ /// Class HttpResponseInfo.
/// </summary>
public class HttpResponseInfo : IDisposable
{
+#pragma warning disable CS1591
+#pragma warning disable SA1600
+ public HttpResponseInfo()
+ {
+ }
+
+ public HttpResponseInfo(HttpResponseHeaders headers, HttpContentHeaders contentHeader)
+ {
+ Headers = headers;
+ ContentHeaders = contentHeader;
+ }
+
+#pragma warning restore CS1591
+#pragma warning restore SA1600
+
/// <summary>
/// Gets or sets the type of the content.
/// </summary>
@@ -60,21 +73,10 @@ namespace MediaBrowser.Common.Net
/// <value>The content headers.</value>
public HttpContentHeaders ContentHeaders { get; set; }
- public HttpResponseInfo()
- {
-
- }
-
- public HttpResponseInfo(HttpResponseHeaders headers, HttpContentHeaders contentHeader)
- {
- Headers = headers;
- ContentHeaders = contentHeader;
- }
-
/// <inheritdoc />
public void Dispose()
{
- // Only IDisposable for backwards compatibility
+ // backwards compatibility
}
}
}
diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs
index 23ba34173..534e22edd 100644
--- a/MediaBrowser.Common/Net/IHttpClient.cs
+++ b/MediaBrowser.Common/Net/IHttpClient.cs
@@ -1,12 +1,12 @@
using System;
using System.IO;
-using System.Threading.Tasks;
using System.Net.Http;
+using System.Threading.Tasks;
namespace MediaBrowser.Common.Net
{
/// <summary>
- /// Interface IHttpClient
+ /// Interface IHttpClient.
/// </summary>
public interface IHttpClient
{
diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs
index 0b99dc910..6bd7dd1d6 100644
--- a/MediaBrowser.Common/Net/INetworkManager.cs
+++ b/MediaBrowser.Common/Net/INetworkManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -11,20 +12,20 @@ namespace MediaBrowser.Common.Net
{
event EventHandler NetworkChanged;
+ Func<string[]> LocalSubnetsFn { get; set; }
+
/// <summary>
- /// Gets a random port number that is currently available
+ /// Gets a random port number that is currently available.
/// </summary>
/// <returns>System.Int32.</returns>
int GetRandomUnusedTcpPort();
int GetRandomUnusedUdpPort();
- Func<string[]> LocalSubnetsFn { get; set; }
-
/// <summary>
- /// Returns MAC Address from first Network Card in Computer
+ /// Returns the MAC Address from first Network Card in Computer.
/// </summary>
- /// <returns>[string] MAC Address</returns>
+ /// <returns>The MAC Address.</returns>
List<PhysicalAddress> GetMacAddresses();
/// <summary>
diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs
index 6ef891d66..b24d10ff1 100644
--- a/MediaBrowser.Common/Plugins/BasePlugin.cs
+++ b/MediaBrowser.Common/Plugins/BasePlugin.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable SA1402
using System;
using System.IO;
@@ -8,10 +8,13 @@ using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Common.Plugins
{
+ /// <summary>
+ /// Provides a common base class for all plugins.
+ /// </summary>
public abstract class BasePlugin : IPlugin, IPluginAssembly
{
/// <summary>
- /// Gets the name of the plugin
+ /// Gets the name of the plugin.
/// </summary>
/// <value>The name.</value>
public abstract string Name { get; }
@@ -29,18 +32,24 @@ namespace MediaBrowser.Common.Plugins
public virtual Guid Id { get; private set; }
/// <summary>
- /// Gets the plugin version
+ /// Gets the plugin version.
/// </summary>
/// <value>The version.</value>
public Version Version { get; private set; }
/// <summary>
- /// Gets the path to the assembly file
+ /// Gets the path to the assembly file.
/// </summary>
/// <value>The assembly file path.</value>
public string AssemblyFilePath { get; private set; }
/// <summary>
+ /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed.
+ /// </summary>
+ /// <value>The data folder path.</value>
+ public string DataFolderPath { get; private set; }
+
+ /// <summary>
/// Gets the plugin info.
/// </summary>
/// <returns>PluginInfo.</returns>
@@ -62,9 +71,9 @@ namespace MediaBrowser.Common.Plugins
/// </summary>
public virtual void OnUninstalling()
{
-
}
+ /// <inheritdoc />
public void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion)
{
AssemblyFilePath = assemblyFilePath;
@@ -72,26 +81,49 @@ namespace MediaBrowser.Common.Plugins
Version = assemblyVersion;
}
+ /// <inheritdoc />
public void SetId(Guid assemblyId)
{
Id = assemblyId;
}
-
- /// <summary>
- /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed
- /// </summary>
- /// <value>The data folder path.</value>
- public string DataFolderPath { get; private set; }
}
/// <summary>
- /// Provides a common base class for all plugins
+ /// Provides a common base class for all plugins.
/// </summary>
/// <typeparam name="TConfigurationType">The type of the T configuration type.</typeparam>
public abstract class BasePlugin<TConfigurationType> : BasePlugin, IHasPluginConfiguration
where TConfigurationType : BasePluginConfiguration
{
/// <summary>
+ /// The configuration sync lock.
+ /// </summary>
+ private readonly object _configurationSyncLock = new object();
+
+ /// <summary>
+ /// The save lock.
+ /// </summary>
+ private readonly object _configurationSaveLock = new object();
+
+ private Action<string> _directoryCreateFn;
+
+ /// <summary>
+ /// The configuration.
+ /// </summary>
+ private TConfigurationType _configuration;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BasePlugin{TConfigurationType}" /> class.
+ /// </summary>
+ /// <param name="applicationPaths">The application paths.</param>
+ /// <param name="xmlSerializer">The XML serializer.</param>
+ protected BasePlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ {
+ ApplicationPaths = applicationPaths;
+ XmlSerializer = xmlSerializer;
+ }
+
+ /// <summary>
/// Gets the application paths.
/// </summary>
/// <value>The application paths.</value>
@@ -104,34 +136,19 @@ namespace MediaBrowser.Common.Plugins
protected IXmlSerializer XmlSerializer { get; private set; }
/// <summary>
- /// Gets the type of configuration this plugin uses
+ /// Gets the type of configuration this plugin uses.
/// </summary>
/// <value>The type of the configuration.</value>
public Type ConfigurationType => typeof(TConfigurationType);
- private Action<string> _directoryCreateFn;
- public void SetStartupInfo(Action<string> directoryCreateFn)
- {
- // hack alert, until the .net core transition is complete
- _directoryCreateFn = directoryCreateFn;
- }
-
/// <summary>
- /// Gets the name the assembly file
+ /// Gets the name the assembly file.
/// </summary>
/// <value>The name of the assembly file.</value>
protected string AssemblyFileName => Path.GetFileName(AssemblyFilePath);
/// <summary>
- /// The _configuration sync lock
- /// </summary>
- private readonly object _configurationSyncLock = new object();
- /// <summary>
- /// The _configuration
- /// </summary>
- private TConfigurationType _configuration;
- /// <summary>
- /// Gets the plugin's configuration
+ /// Gets or sets the plugin's configuration.
/// </summary>
/// <value>The configuration.</value>
public TConfigurationType Configuration
@@ -149,55 +166,54 @@ namespace MediaBrowser.Common.Plugins
}
}
}
+
return _configuration;
}
- protected set => _configuration = value;
- }
- private TConfigurationType LoadConfiguration()
- {
- var path = ConfigurationFilePath;
-
- try
- {
- return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path);
- }
- catch
- {
- return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
- }
+ protected set => _configuration = value;
}
/// <summary>
- /// Gets the name of the configuration file. Subclasses should override
+ /// Gets the name of the configuration file. Subclasses should override.
/// </summary>
/// <value>The name of the configuration file.</value>
public virtual string ConfigurationFileName => Path.ChangeExtension(AssemblyFileName, ".xml");
/// <summary>
- /// Gets the full path to the configuration file
+ /// Gets the full path to the configuration file.
/// </summary>
/// <value>The configuration file path.</value>
public string ConfigurationFilePath => Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName);
/// <summary>
- /// Initializes a new instance of the <see cref="BasePlugin{TConfigurationType}" /> class.
+ /// Gets the plugin's configuration.
/// </summary>
- /// <param name="applicationPaths">The application paths.</param>
- /// <param name="xmlSerializer">The XML serializer.</param>
- protected BasePlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ /// <value>The configuration.</value>
+ BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration;
+
+ /// <inheritdoc />
+ public void SetStartupInfo(Action<string> directoryCreateFn)
{
- ApplicationPaths = applicationPaths;
- XmlSerializer = xmlSerializer;
+ // hack alert, until the .net core transition is complete
+ _directoryCreateFn = directoryCreateFn;
}
- /// <summary>
- /// The _save lock
- /// </summary>
- private readonly object _configurationSaveLock = new object();
+ private TConfigurationType LoadConfiguration()
+ {
+ var path = ConfigurationFilePath;
+
+ try
+ {
+ return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path);
+ }
+ catch
+ {
+ return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
+ }
+ }
/// <summary>
- /// Saves the current configuration to the file system
+ /// Saves the current configuration to the file system.
/// </summary>
public virtual void SaveConfiguration()
{
@@ -209,12 +225,7 @@ namespace MediaBrowser.Common.Plugins
}
}
- /// <summary>
- /// Completely overwrites the current configuration with a new copy
- /// Returns true or false indicating success or failure
- /// </summary>
- /// <param name="configuration">The configuration.</param>
- /// <exception cref="ArgumentNullException">configuration</exception>
+ /// <inheritdoc />
public virtual void UpdateConfiguration(BasePluginConfiguration configuration)
{
if (configuration == null)
@@ -227,12 +238,7 @@ namespace MediaBrowser.Common.Plugins
SaveConfiguration();
}
- /// <summary>
- /// Gets the plugin's configuration
- /// </summary>
- /// <value>The configuration.</value>
- BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration;
-
+ /// <inheritdoc />
public override PluginInfo GetPluginInfo()
{
var info = base.GetPluginInfo();
@@ -242,10 +248,4 @@ namespace MediaBrowser.Common.Plugins
return info;
}
}
-
- public interface IPluginAssembly
- {
- void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion);
- void SetId(Guid assemblyId);
- }
}
diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs
index 7bd90c964..001ca8be8 100644
--- a/MediaBrowser.Common/Plugins/IPlugin.cs
+++ b/MediaBrowser.Common/Plugins/IPlugin.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using MediaBrowser.Model.Plugins;
@@ -6,12 +7,12 @@ using MediaBrowser.Model.Plugins;
namespace MediaBrowser.Common.Plugins
{
/// <summary>
- /// Interface IPlugin
+ /// Interface IPlugin.
/// </summary>
public interface IPlugin
{
/// <summary>
- /// Gets the name of the plugin
+ /// Gets the name of the plugin.
/// </summary>
/// <value>The name.</value>
string Name { get; }
@@ -29,19 +30,19 @@ namespace MediaBrowser.Common.Plugins
Guid Id { get; }
/// <summary>
- /// Gets the plugin version
+ /// Gets the plugin version.
/// </summary>
/// <value>The version.</value>
Version Version { get; }
/// <summary>
- /// Gets the path to the assembly file
+ /// Gets the path to the assembly file.
/// </summary>
/// <value>The assembly file path.</value>
string AssemblyFilePath { get; }
/// <summary>
- /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed
+ /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed.
/// </summary>
/// <value>The data folder path.</value>
string DataFolderPath { get; }
@@ -61,24 +62,24 @@ namespace MediaBrowser.Common.Plugins
public interface IHasPluginConfiguration
{
/// <summary>
- /// Gets the type of configuration this plugin uses
+ /// Gets the type of configuration this plugin uses.
/// </summary>
/// <value>The type of the configuration.</value>
Type ConfigurationType { get; }
/// <summary>
- /// Completely overwrites the current configuration with a new copy
- /// Returns true or false indicating success or failure
+ /// Gets the plugin's configuration.
/// </summary>
- /// <param name="configuration">The configuration.</param>
- /// <exception cref="ArgumentNullException">configuration</exception>
- void UpdateConfiguration(BasePluginConfiguration configuration);
+ /// <value>The configuration.</value>
+ BasePluginConfiguration Configuration { get; }
/// <summary>
- /// Gets the plugin's configuration
+ /// Completely overwrites the current configuration with a new copy
+ /// Returns true or false indicating success or failure.
/// </summary>
- /// <value>The configuration.</value>
- BasePluginConfiguration Configuration { get; }
+ /// <param name="configuration">The configuration.</param>
+ /// <exception cref="ArgumentNullException"><c>configuration</c> is <c>null</c>.</exception>
+ void UpdateConfiguration(BasePluginConfiguration configuration);
void SetStartupInfo(Action<string> directoryCreateFn);
}
diff --git a/MediaBrowser.Common/Plugins/IPluginAssembly.cs b/MediaBrowser.Common/Plugins/IPluginAssembly.cs
new file mode 100644
index 000000000..388ac61ab
--- /dev/null
+++ b/MediaBrowser.Common/Plugins/IPluginAssembly.cs
@@ -0,0 +1,14 @@
+#pragma warning disable CS1591
+#pragma warning disable SA1600
+
+using System;
+
+namespace MediaBrowser.Common.Plugins
+{
+ public interface IPluginAssembly
+ {
+ void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion);
+
+ void SetId(Guid assemblyId);
+ }
+}
diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs
index af69055aa..92141ba52 100644
--- a/MediaBrowser.Common/Progress/ActionableProgress.cs
+++ b/MediaBrowser.Common/Progress/ActionableProgress.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
diff --git a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs
index 0445397ad..a6422e2c8 100644
--- a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs
+++ b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
diff --git a/MediaBrowser.Common/System/OperatingSystem.cs b/MediaBrowser.Common/System/OperatingSystem.cs
index 7d38ddb6e..f23af4799 100644
--- a/MediaBrowser.Common/System/OperatingSystem.cs
+++ b/MediaBrowser.Common/System/OperatingSystem.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Runtime.InteropServices;
diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs
index e49812f15..a09c1916c 100644
--- a/MediaBrowser.Common/Updates/IInstallationManager.cs
+++ b/MediaBrowser.Common/Updates/IInstallationManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -103,17 +104,16 @@ namespace MediaBrowser.Common.Updates
Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken = default);
/// <summary>
- /// Uninstalls a plugin
+ /// Uninstalls a plugin.
/// </summary>
/// <param name="plugin">The plugin.</param>
- /// <exception cref="ArgumentException"></exception>
void UninstallPlugin(IPlugin plugin);
/// <summary>
- /// Cancels the installation
+ /// Cancels the installation.
/// </summary>
- /// <param name="id">The id of the package that is being installed</param>
- /// <returns>Returns true if the install was cancelled</returns>
+ /// <param name="id">The id of the package that is being installed.</param>
+ /// <returns>Returns true if the install was cancelled.</returns>
bool CancelInstallation(Guid id);
}
}
diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs
index 36e124ddf..8bbb231ce 100644
--- a/MediaBrowser.Common/Updates/InstallationEventArgs.cs
+++ b/MediaBrowser.Common/Updates/InstallationEventArgs.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using MediaBrowser.Model.Updates;
diff --git a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs
index 46f10c84f..c8967f9db 100644
--- a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs
+++ b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable SA1600
using System;
diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs
index 3c65156e3..5248ea4c1 100644
--- a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs
+++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs
@@ -1,14 +1,19 @@
+#pragma warning disable CS1591
+#pragma warning disable SA1600
+
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
-
namespace MediaBrowser.Controller.Authentication
{
public class AuthenticationResult
{
public UserDto User { get; set; }
+
public SessionInfo SessionInfo { get; set; }
+
public string AccessToken { get; set; }
+
public string ServerId { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index cba2c9dda..353c675cb 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -19,7 +19,6 @@ using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
index 09e6fda88..0ceabd0e6 100644
--- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs
+++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
@@ -55,7 +55,7 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets the playack media sources.
/// </summary>
- Task<List<MediaSourceInfo>> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken);
+ Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken);
/// <summary>
/// Gets the static media sources.
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index c371e8b94..c530c9fd8 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -59,7 +59,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
throw new ArgumentNullException(nameof(mediaSourceId));
}
- var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);
+ var mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSources
.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
if (mediaSource == null)
@@ -196,7 +196,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
var exitCode = ranToCompletion ? process.ExitCode : -1;
process.Dispose();
-
+
var failed = false;
if (exitCode != 0)
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 6bcd6cd46..e0f7b992c 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -397,7 +397,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
result = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(
- process.StandardOutput.BaseStream).ConfigureAwait(false);
+ process.StandardOutput.BaseStream,
+ cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch
{
@@ -406,24 +407,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw;
}
- if (result == null || (result.streams == null && result.format == null))
+ if (result == null || (result.Streams == null && result.Format == null))
{
throw new Exception("ffprobe failed - streams and format are both null.");
}
- if (result.streams != null)
+ if (result.Streams != null)
{
// Normalize aspect ratio if invalid
- foreach (var stream in result.streams)
+ foreach (var stream in result.Streams)
{
- if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(stream.DisplayAspectRatio, "0:1", StringComparison.OrdinalIgnoreCase))
{
- stream.display_aspect_ratio = string.Empty;
+ stream.DisplayAspectRatio = string.Empty;
}
- if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(stream.SampleAspectRatio, "0:1", StringComparison.OrdinalIgnoreCase))
{
- stream.sample_aspect_ratio = string.Empty;
+ stream.SampleAspectRatio = string.Empty;
}
}
}
@@ -778,6 +779,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_runningProcesses.Add(process);
}
}
+
private void StopProcess(ProcessWrapper process, int waitTimeMs)
{
try
@@ -786,18 +788,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
return;
}
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in WaitForExit");
- }
- try
- {
_logger.LogInformation("Killing ffmpeg process");
process.Process.Kill();
}
+ catch (InvalidOperationException)
+ {
+ // The process has already exited or
+ // there is no process associated with this Process object.
+ }
catch (Exception ex)
{
_logger.LogError(ex, "Error killing process");
diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
index e4eabaf38..78dc7b607 100644
--- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
+++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
@@ -16,24 +16,19 @@ namespace MediaBrowser.MediaEncoding.Probing
throw new ArgumentNullException(nameof(result));
}
- if (result.format != null && result.format.tags != null)
+ if (result.Format != null && result.Format.Tags != null)
{
- result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags);
+ result.Format.Tags = ConvertDictionaryToCaseInsensitive(result.Format.Tags);
}
- if (result.streams != null)
+ if (result.Streams != null)
{
// Convert all dictionaries to case insensitive
- foreach (var stream in result.streams)
+ foreach (var stream in result.Streams)
{
- if (stream.tags != null)
+ if (stream.Tags != null)
{
- stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags);
- }
-
- if (stream.disposition != null)
- {
- stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition);
+ stream.Tags = ConvertDictionaryToCaseInsensitive(stream.Tags);
}
}
}
@@ -45,7 +40,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
/// <returns>System.String.</returns>
- public static string GetDictionaryValue(Dictionary<string, string> tags, string key)
+ public static string GetDictionaryValue(IReadOnlyDictionary<string, string> tags, string key)
{
if (tags == null)
{
@@ -103,7 +98,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <param name="dict">The dict.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
- private static Dictionary<string, string> ConvertDictionaryToCaseInSensitive(Dictionary<string, string> dict)
+ private static Dictionary<string, string> ConvertDictionaryToCaseInsensitive(IReadOnlyDictionary<string, string> dict)
{
return new Dictionary<string, string>(dict, StringComparer.OrdinalIgnoreCase);
}
diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
index cc9d27608..0e319c1a8 100644
--- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
+++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
@@ -1,9 +1,10 @@
using System.Collections.Generic;
+using System.Text.Json.Serialization;
namespace MediaBrowser.MediaEncoding.Probing
{
/// <summary>
- /// Class MediaInfoResult
+ /// Class MediaInfoResult.
/// </summary>
public class InternalMediaInfoResult
{
@@ -11,331 +12,21 @@ namespace MediaBrowser.MediaEncoding.Probing
/// Gets or sets the streams.
/// </summary>
/// <value>The streams.</value>
- public MediaStreamInfo[] streams { get; set; }
+ [JsonPropertyName("streams")]
+ public IReadOnlyList<MediaStreamInfo> Streams { get; set; }
/// <summary>
/// Gets or sets the format.
/// </summary>
/// <value>The format.</value>
- public MediaFormatInfo format { get; set; }
+ [JsonPropertyName("format")]
+ public MediaFormatInfo Format { get; set; }
/// <summary>
/// Gets or sets the chapters.
/// </summary>
/// <value>The chapters.</value>
- public MediaChapter[] Chapters { get; set; }
- }
-
- public class MediaChapter
- {
- public int id { get; set; }
- public string time_base { get; set; }
- public long start { get; set; }
- public string start_time { get; set; }
- public long end { get; set; }
- public string end_time { get; set; }
- public Dictionary<string, string> tags { get; set; }
- }
-
- /// <summary>
- /// Represents a stream within the output
- /// </summary>
- public class MediaStreamInfo
- {
- /// <summary>
- /// Gets or sets the index.
- /// </summary>
- /// <value>The index.</value>
- public int index { get; set; }
-
- /// <summary>
- /// Gets or sets the profile.
- /// </summary>
- /// <value>The profile.</value>
- public string profile { get; set; }
-
- /// <summary>
- /// Gets or sets the codec_name.
- /// </summary>
- /// <value>The codec_name.</value>
- public string codec_name { get; set; }
-
- /// <summary>
- /// Gets or sets the codec_long_name.
- /// </summary>
- /// <value>The codec_long_name.</value>
- public string codec_long_name { get; set; }
-
- /// <summary>
- /// Gets or sets the codec_type.
- /// </summary>
- /// <value>The codec_type.</value>
- public string codec_type { get; set; }
-
- /// <summary>
- /// Gets or sets the sample_rate.
- /// </summary>
- /// <value>The sample_rate.</value>
- public string sample_rate { get; set; }
-
- /// <summary>
- /// Gets or sets the channels.
- /// </summary>
- /// <value>The channels.</value>
- public int channels { get; set; }
-
- /// <summary>
- /// Gets or sets the channel_layout.
- /// </summary>
- /// <value>The channel_layout.</value>
- public string channel_layout { get; set; }
-
- /// <summary>
- /// Gets or sets the avg_frame_rate.
- /// </summary>
- /// <value>The avg_frame_rate.</value>
- public string avg_frame_rate { get; set; }
-
- /// <summary>
- /// Gets or sets the duration.
- /// </summary>
- /// <value>The duration.</value>
- public string duration { get; set; }
-
- /// <summary>
- /// Gets or sets the bit_rate.
- /// </summary>
- /// <value>The bit_rate.</value>
- public string bit_rate { get; set; }
-
- /// <summary>
- /// Gets or sets the width.
- /// </summary>
- /// <value>The width.</value>
- public int width { get; set; }
-
- /// <summary>
- /// Gets or sets the refs.
- /// </summary>
- /// <value>The refs.</value>
- public int refs { get; set; }
-
- /// <summary>
- /// Gets or sets the height.
- /// </summary>
- /// <value>The height.</value>
- public int height { get; set; }
-
- /// <summary>
- /// Gets or sets the display_aspect_ratio.
- /// </summary>
- /// <value>The display_aspect_ratio.</value>
- public string display_aspect_ratio { get; set; }
-
- /// <summary>
- /// Gets or sets the tags.
- /// </summary>
- /// <value>The tags.</value>
- public Dictionary<string, string> tags { get; set; }
-
- /// <summary>
- /// Gets or sets the bits_per_sample.
- /// </summary>
- /// <value>The bits_per_sample.</value>
- public int bits_per_sample { get; set; }
-
- /// <summary>
- /// Gets or sets the bits_per_raw_sample.
- /// </summary>
- /// <value>The bits_per_raw_sample.</value>
- public int bits_per_raw_sample { get; set; }
-
- /// <summary>
- /// Gets or sets the r_frame_rate.
- /// </summary>
- /// <value>The r_frame_rate.</value>
- public string r_frame_rate { get; set; }
-
- /// <summary>
- /// Gets or sets the has_b_frames.
- /// </summary>
- /// <value>The has_b_frames.</value>
- public int has_b_frames { get; set; }
-
- /// <summary>
- /// Gets or sets the sample_aspect_ratio.
- /// </summary>
- /// <value>The sample_aspect_ratio.</value>
- public string sample_aspect_ratio { get; set; }
-
- /// <summary>
- /// Gets or sets the pix_fmt.
- /// </summary>
- /// <value>The pix_fmt.</value>
- public string pix_fmt { get; set; }
-
- /// <summary>
- /// Gets or sets the level.
- /// </summary>
- /// <value>The level.</value>
- public int level { get; set; }
-
- /// <summary>
- /// Gets or sets the time_base.
- /// </summary>
- /// <value>The time_base.</value>
- public string time_base { get; set; }
-
- /// <summary>
- /// Gets or sets the start_time.
- /// </summary>
- /// <value>The start_time.</value>
- public string start_time { get; set; }
-
- /// <summary>
- /// Gets or sets the codec_time_base.
- /// </summary>
- /// <value>The codec_time_base.</value>
- public string codec_time_base { get; set; }
-
- /// <summary>
- /// Gets or sets the codec_tag.
- /// </summary>
- /// <value>The codec_tag.</value>
- public string codec_tag { get; set; }
-
- /// <summary>
- /// Gets or sets the codec_tag_string.
- /// </summary>
- /// <value>The codec_tag_string.</value>
- public string codec_tag_string { get; set; }
-
- /// <summary>
- /// Gets or sets the sample_fmt.
- /// </summary>
- /// <value>The sample_fmt.</value>
- public string sample_fmt { get; set; }
-
- /// <summary>
- /// Gets or sets the dmix_mode.
- /// </summary>
- /// <value>The dmix_mode.</value>
- public string dmix_mode { get; set; }
-
- /// <summary>
- /// Gets or sets the start_pts.
- /// </summary>
- /// <value>The start_pts.</value>
- public string start_pts { get; set; }
-
- /// <summary>
- /// Gets or sets the is_avc.
- /// </summary>
- /// <value>The is_avc.</value>
- public string is_avc { get; set; }
-
- /// <summary>
- /// Gets or sets the nal_length_size.
- /// </summary>
- /// <value>The nal_length_size.</value>
- public string nal_length_size { get; set; }
-
- /// <summary>
- /// Gets or sets the ltrt_cmixlev.
- /// </summary>
- /// <value>The ltrt_cmixlev.</value>
- public string ltrt_cmixlev { get; set; }
-
- /// <summary>
- /// Gets or sets the ltrt_surmixlev.
- /// </summary>
- /// <value>The ltrt_surmixlev.</value>
- public string ltrt_surmixlev { get; set; }
-
- /// <summary>
- /// Gets or sets the loro_cmixlev.
- /// </summary>
- /// <value>The loro_cmixlev.</value>
- public string loro_cmixlev { get; set; }
-
- /// <summary>
- /// Gets or sets the loro_surmixlev.
- /// </summary>
- /// <value>The loro_surmixlev.</value>
- public string loro_surmixlev { get; set; }
-
- public string field_order { get; set; }
-
- /// <summary>
- /// Gets or sets the disposition.
- /// </summary>
- /// <value>The disposition.</value>
- public Dictionary<string, string> disposition { get; set; }
- }
-
- /// <summary>
- /// Class MediaFormat
- /// </summary>
- public class MediaFormatInfo
- {
- /// <summary>
- /// Gets or sets the filename.
- /// </summary>
- /// <value>The filename.</value>
- public string filename { get; set; }
-
- /// <summary>
- /// Gets or sets the nb_streams.
- /// </summary>
- /// <value>The nb_streams.</value>
- public int nb_streams { get; set; }
-
- /// <summary>
- /// Gets or sets the format_name.
- /// </summary>
- /// <value>The format_name.</value>
- public string format_name { get; set; }
-
- /// <summary>
- /// Gets or sets the format_long_name.
- /// </summary>
- /// <value>The format_long_name.</value>
- public string format_long_name { get; set; }
-
- /// <summary>
- /// Gets or sets the start_time.
- /// </summary>
- /// <value>The start_time.</value>
- public string start_time { get; set; }
-
- /// <summary>
- /// Gets or sets the duration.
- /// </summary>
- /// <value>The duration.</value>
- public string duration { get; set; }
-
- /// <summary>
- /// Gets or sets the size.
- /// </summary>
- /// <value>The size.</value>
- public string size { get; set; }
-
- /// <summary>
- /// Gets or sets the bit_rate.
- /// </summary>
- /// <value>The bit_rate.</value>
- public string bit_rate { get; set; }
-
- /// <summary>
- /// Gets or sets the probe_score.
- /// </summary>
- /// <value>The probe_score.</value>
- public int probe_score { get; set; }
-
- /// <summary>
- /// Gets or sets the tags.
- /// </summary>
- /// <value>The tags.</value>
- public Dictionary<string, string> tags { get; set; }
+ [JsonPropertyName("chapters")]
+ public IReadOnlyList<MediaChapter> Chapters { get; set; }
}
}
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs
new file mode 100644
index 000000000..6a45ccf49
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.MediaEncoding.Probing
+{
+ /// <summary>
+ /// Class MediaChapter.
+ /// </summary>
+ public class MediaChapter
+ {
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ [JsonPropertyName("time_base")]
+ public string TimeBase { get; set; }
+
+ [JsonPropertyName("start")]
+ public long Start { get; set; }
+
+ [JsonPropertyName("start_time")]
+ public string StartTime { get; set; }
+
+ [JsonPropertyName("end")]
+ public long End { get; set; }
+
+ [JsonPropertyName("end_time")]
+ public string EndTime { get; set; }
+
+ [JsonPropertyName("tags")]
+ public IReadOnlyDictionary<string, string> Tags { get; set; }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs
new file mode 100644
index 000000000..8af122ef9
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.MediaEncoding.Probing
+{
+ /// <summary>
+ /// Class MediaFormat.
+ /// </summary>
+ public class MediaFormatInfo
+ {
+ /// <summary>
+ /// Gets or sets the filename.
+ /// </summary>
+ /// <value>The filename.</value>
+ [JsonPropertyName("filename")]
+ public string FileName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the nb_streams.
+ /// </summary>
+ /// <value>The nb_streams.</value>
+ [JsonPropertyName("nb_streams")]
+ public int NbStreams { get; set; }
+
+ /// <summary>
+ /// Gets or sets the format_name.
+ /// </summary>
+ /// <value>The format_name.</value>
+ [JsonPropertyName("format_name")]
+ public string FormatName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the format_long_name.
+ /// </summary>
+ /// <value>The format_long_name.</value>
+ [JsonPropertyName("format_long_name")]
+ public string FormatLongName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the start_time.
+ /// </summary>
+ /// <value>The start_time.</value>
+ [JsonPropertyName("start_time")]
+ public string StartTime { get; set; }
+
+ /// <summary>
+ /// Gets or sets the duration.
+ /// </summary>
+ /// <value>The duration.</value>
+ [JsonPropertyName("duration")]
+ public string Duration { get; set; }
+
+ /// <summary>
+ /// Gets or sets the size.
+ /// </summary>
+ /// <value>The size.</value>
+ [JsonPropertyName("size")]
+ public string Size { get; set; }
+
+ /// <summary>
+ /// Gets or sets the bit_rate.
+ /// </summary>
+ /// <value>The bit_rate.</value>
+ [JsonPropertyName("bit_rate")]
+ public string BitRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the probe_score.
+ /// </summary>
+ /// <value>The probe_score.</value>
+ [JsonPropertyName("probe_score")]
+ public int ProbeScore { get; set; }
+
+ /// <summary>
+ /// Gets or sets the tags.
+ /// </summary>
+ /// <value>The tags.</value>
+ [JsonPropertyName("tags")]
+ public IReadOnlyDictionary<string, string> Tags { get; set; }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
new file mode 100644
index 000000000..7fa7afa5b
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -0,0 +1,282 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using MediaBrowser.Common.Json.Converters;
+
+namespace MediaBrowser.MediaEncoding.Probing
+{
+ /// <summary>
+ /// Represents a stream within the output.
+ /// </summary>
+ public class MediaStreamInfo
+ {
+ /// <summary>
+ /// Gets or sets the index.
+ /// </summary>
+ /// <value>The index.</value>
+ [JsonPropertyName("index")]
+ public int Index { get; set; }
+
+ /// <summary>
+ /// Gets or sets the profile.
+ /// </summary>
+ /// <value>The profile.</value>
+ [JsonPropertyName("profile")]
+ public string Profile { get; set; }
+
+ /// <summary>
+ /// Gets or sets the codec_name.
+ /// </summary>
+ /// <value>The codec_name.</value>
+ [JsonPropertyName("codec_name")]
+ public string CodecName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the codec_long_name.
+ /// </summary>
+ /// <value>The codec_long_name.</value>
+ [JsonPropertyName("codec_long_name")]
+ public string CodecLongName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the codec_type.
+ /// </summary>
+ /// <value>The codec_type.</value>
+ [JsonPropertyName("codec_type")]
+ public string CodecType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the sample_rate.
+ /// </summary>
+ /// <value>The sample_rate.</value>
+ [JsonPropertyName("sample_rate")]
+ public string SampleRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the channels.
+ /// </summary>
+ /// <value>The channels.</value>
+ [JsonPropertyName("channels")]
+ public int Channels { get; set; }
+
+ /// <summary>
+ /// Gets or sets the channel_layout.
+ /// </summary>
+ /// <value>The channel_layout.</value>
+ [JsonPropertyName("channel_layout")]
+ public string ChannelLayout { get; set; }
+
+ /// <summary>
+ /// Gets or sets the avg_frame_rate.
+ /// </summary>
+ /// <value>The avg_frame_rate.</value>
+ [JsonPropertyName("avg_frame_rate")]
+ public string AverageFrameRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the duration.
+ /// </summary>
+ /// <value>The duration.</value>
+ [JsonPropertyName("duration")]
+ public string Duration { get; set; }
+
+ /// <summary>
+ /// Gets or sets the bit_rate.
+ /// </summary>
+ /// <value>The bit_rate.</value>
+ [JsonPropertyName("bit_rate")]
+ public string BitRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the width.
+ /// </summary>
+ /// <value>The width.</value>
+ [JsonPropertyName("width")]
+ public int Width { get; set; }
+
+ /// <summary>
+ /// Gets or sets the refs.
+ /// </summary>
+ /// <value>The refs.</value>
+ [JsonPropertyName("refs")]
+ public int Refs { get; set; }
+
+ /// <summary>
+ /// Gets or sets the height.
+ /// </summary>
+ /// <value>The height.</value>
+ [JsonPropertyName("height")]
+ public int Height { get; set; }
+
+ /// <summary>
+ /// Gets or sets the display_aspect_ratio.
+ /// </summary>
+ /// <value>The display_aspect_ratio.</value>
+ [JsonPropertyName("display_aspect_ratio")]
+ public string DisplayAspectRatio { get; set; }
+
+ /// <summary>
+ /// Gets or sets the tags.
+ /// </summary>
+ /// <value>The tags.</value>
+ [JsonPropertyName("tags")]
+ public IReadOnlyDictionary<string, string> Tags { get; set; }
+
+ /// <summary>
+ /// Gets or sets the bits_per_sample.
+ /// </summary>
+ /// <value>The bits_per_sample.</value>
+ [JsonPropertyName("bits_per_sample")]
+ public int BitsPerSample { get; set; }
+
+ /// <summary>
+ /// Gets or sets the bits_per_raw_sample.
+ /// </summary>
+ /// <value>The bits_per_raw_sample.</value>
+ [JsonPropertyName("bits_per_raw_sample")]
+ [JsonConverter(typeof(JsonInt32Converter))]
+ public int BitsPerRawSample { get; set; }
+
+ /// <summary>
+ /// Gets or sets the r_frame_rate.
+ /// </summary>
+ /// <value>The r_frame_rate.</value>
+ [JsonPropertyName("r_frame_rate")]
+ public string RFrameRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the has_b_frames.
+ /// </summary>
+ /// <value>The has_b_frames.</value>
+ [JsonPropertyName("has_b_frames")]
+ public int HasBFrames { get; set; }
+
+ /// <summary>
+ /// Gets or sets the sample_aspect_ratio.
+ /// </summary>
+ /// <value>The sample_aspect_ratio.</value>
+ [JsonPropertyName("sample_aspect_ratio")]
+ public string SampleAspectRatio { get; set; }
+
+ /// <summary>
+ /// Gets or sets the pix_fmt.
+ /// </summary>
+ /// <value>The pix_fmt.</value>
+ [JsonPropertyName("pix_fmt")]
+ public string PixelFormat { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level.
+ /// </summary>
+ /// <value>The level.</value>
+ [JsonPropertyName("level")]
+ public int Level { get; set; }
+
+ /// <summary>
+ /// Gets or sets the time_base.
+ /// </summary>
+ /// <value>The time_base.</value>
+ [JsonPropertyName("time_base")]
+ public string TimeBase { get; set; }
+
+ /// <summary>
+ /// Gets or sets the start_time.
+ /// </summary>
+ /// <value>The start_time.</value>
+ [JsonPropertyName("start_time")]
+ public string StartTime { get; set; }
+
+ /// <summary>
+ /// Gets or sets the codec_time_base.
+ /// </summary>
+ /// <value>The codec_time_base.</value>
+ [JsonPropertyName("codec_time_base")]
+ public string CodecTimeBase { get; set; }
+
+ /// <summary>
+ /// Gets or sets the codec_tag.
+ /// </summary>
+ /// <value>The codec_tag.</value>
+ [JsonPropertyName("codec_tag")]
+ public string CodecTag { get; set; }
+
+ /// <summary>
+ /// Gets or sets the codec_tag_string.
+ /// </summary>
+ /// <value>The codec_tag_string.</value>
+ [JsonPropertyName("codec_tag_string")]
+ public string CodecTagString { get; set; }
+
+ /// <summary>
+ /// Gets or sets the sample_fmt.
+ /// </summary>
+ /// <value>The sample_fmt.</value>
+ [JsonPropertyName("sample_fmt")]
+ public string SampleFmt { get; set; }
+
+ /// <summary>
+ /// Gets or sets the dmix_mode.
+ /// </summary>
+ /// <value>The dmix_mode.</value>
+ [JsonPropertyName("dmix_mode")]
+ public string DmixMode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the start_pts.
+ /// </summary>
+ /// <value>The start_pts.</value>
+ [JsonPropertyName("start_pts")]
+ public int StartPts { get; set; }
+
+ /// <summary>
+ /// Gets or sets the is_avc.
+ /// </summary>
+ /// <value>The is_avc.</value>
+ [JsonPropertyName("is_avc")]
+ public string IsAvc { get; set; }
+
+ /// <summary>
+ /// Gets or sets the nal_length_size.
+ /// </summary>
+ /// <value>The nal_length_size.</value>
+ [JsonPropertyName("nal_length_size")]
+ public string NalLengthSize { get; set; }
+
+ /// <summary>
+ /// Gets or sets the ltrt_cmixlev.
+ /// </summary>
+ /// <value>The ltrt_cmixlev.</value>
+ [JsonPropertyName("ltrt_cmixlev")]
+ public string LtrtCmixlev { get; set; }
+
+ /// <summary>
+ /// Gets or sets the ltrt_surmixlev.
+ /// </summary>
+ /// <value>The ltrt_surmixlev.</value>
+ [JsonPropertyName("ltrt_surmixlev")]
+ public string LtrtSurmixlev { get; set; }
+
+ /// <summary>
+ /// Gets or sets the loro_cmixlev.
+ /// </summary>
+ /// <value>The loro_cmixlev.</value>
+ [JsonPropertyName("loro_cmixlev")]
+ public string LoroCmixlev { get; set; }
+
+ /// <summary>
+ /// Gets or sets the loro_surmixlev.
+ /// </summary>
+ /// <value>The loro_surmixlev.</value>
+ [JsonPropertyName("loro_surmixlev")]
+ public string LoroSurmixlev { get; set; }
+
+ [JsonPropertyName("field_order")]
+ public string FieldOrder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the disposition.
+ /// </summary>
+ /// <value>The disposition.</value>
+ [JsonPropertyName("disposition")]
+ public IReadOnlyDictionary<string, int> Disposition { get; set; }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index a46aa38d8..bd89c6cae 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -8,7 +8,6 @@ using System.Xml;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
@@ -41,9 +40,9 @@ namespace MediaBrowser.MediaEncoding.Probing
FFProbeHelpers.NormalizeFFProbeResult(data);
SetSize(data, info);
- var internalStreams = data.streams ?? new MediaStreamInfo[] { };
+ var internalStreams = data.Streams ?? new MediaStreamInfo[] { };
- info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.format))
+ info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.Format))
.Where(i => i != null)
// Drop subtitle streams if we don't know the codec because it will just cause failures if we don't know how to handle them
.Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec))
@@ -53,13 +52,13 @@ namespace MediaBrowser.MediaEncoding.Probing
.Where(i => i != null)
.ToList();
- if (data.format != null)
+ if (data.Format != null)
{
- info.Container = NormalizeFormat(data.format.format_name);
+ info.Container = NormalizeFormat(data.Format.FormatName);
- if (!string.IsNullOrEmpty(data.format.bit_rate))
+ if (!string.IsNullOrEmpty(data.Format.BitRate))
{
- if (int.TryParse(data.format.bit_rate, NumberStyles.Any, _usCulture, out var value))
+ if (int.TryParse(data.Format.BitRate, NumberStyles.Any, _usCulture, out var value))
{
info.Bitrate = value;
}
@@ -69,22 +68,22 @@ namespace MediaBrowser.MediaEncoding.Probing
var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var tagStreamType = isAudio ? "audio" : "video";
- if (data.streams != null)
+ if (data.Streams != null)
{
- var tagStream = data.streams.FirstOrDefault(i => string.Equals(i.codec_type, tagStreamType, StringComparison.OrdinalIgnoreCase));
+ var tagStream = data.Streams.FirstOrDefault(i => string.Equals(i.CodecType, tagStreamType, StringComparison.OrdinalIgnoreCase));
- if (tagStream != null && tagStream.tags != null)
+ if (tagStream != null && tagStream.Tags != null)
{
- foreach (var pair in tagStream.tags)
+ foreach (var pair in tagStream.Tags)
{
tags[pair.Key] = pair.Value;
}
}
}
- if (data.format != null && data.format.tags != null)
+ if (data.Format != null && data.Format.Tags != null)
{
- foreach (var pair in data.format.tags)
+ foreach (var pair in data.Format.Tags)
{
tags[pair.Key] = pair.Value;
}
@@ -157,9 +156,9 @@ namespace MediaBrowser.MediaEncoding.Probing
FetchFromItunesInfo(itunesXml, info);
}
- if (data.format != null && !string.IsNullOrEmpty(data.format.duration))
+ if (data.Format != null && !string.IsNullOrEmpty(data.Format.Duration))
{
- info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
+ info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.Format.Duration, _usCulture)).Ticks;
}
FetchWtvInfo(info, data);
@@ -524,27 +523,27 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <returns>MediaAttachments.</returns>
private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo)
{
- if (!string.Equals(streamInfo.codec_type, "attachment", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var attachment = new MediaAttachment
{
- Codec = streamInfo.codec_name,
- Index = streamInfo.index
+ Codec = streamInfo.CodecName,
+ Index = streamInfo.Index
};
- if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string))
+ if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString))
{
- attachment.CodecTag = streamInfo.codec_tag_string;
+ attachment.CodecTag = streamInfo.CodecTagString;
}
- if (streamInfo.tags != null)
+ if (streamInfo.Tags != null)
{
- attachment.FileName = GetDictionaryValue(streamInfo.tags, "filename");
- attachment.MimeType = GetDictionaryValue(streamInfo.tags, "mimetype");
- attachment.Comment = GetDictionaryValue(streamInfo.tags, "comment");
+ attachment.FileName = GetDictionaryValue(streamInfo.Tags, "filename");
+ attachment.MimeType = GetDictionaryValue(streamInfo.Tags, "mimetype");
+ attachment.Comment = GetDictionaryValue(streamInfo.Tags, "comment");
}
return attachment;
@@ -560,7 +559,7 @@ namespace MediaBrowser.MediaEncoding.Probing
private MediaStream GetMediaStream(bool isAudio, MediaStreamInfo streamInfo, MediaFormatInfo formatInfo)
{
// These are mp4 chapters
- if (string.Equals(streamInfo.codec_name, "mov_text", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(streamInfo.CodecName, "mov_text", StringComparison.OrdinalIgnoreCase))
{
// Edit: but these are also sometimes subtitles?
//return null;
@@ -568,71 +567,71 @@ namespace MediaBrowser.MediaEncoding.Probing
var stream = new MediaStream
{
- Codec = streamInfo.codec_name,
- Profile = streamInfo.profile,
- Level = streamInfo.level,
- Index = streamInfo.index,
- PixelFormat = streamInfo.pix_fmt,
- NalLengthSize = streamInfo.nal_length_size,
- TimeBase = streamInfo.time_base,
- CodecTimeBase = streamInfo.codec_time_base
+ Codec = streamInfo.CodecName,
+ Profile = streamInfo.Profile,
+ Level = streamInfo.Level,
+ Index = streamInfo.Index,
+ PixelFormat = streamInfo.PixelFormat,
+ NalLengthSize = streamInfo.NalLengthSize,
+ TimeBase = streamInfo.TimeBase,
+ CodecTimeBase = streamInfo.CodecTimeBase
};
- if (string.Equals(streamInfo.is_avc, "true", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(streamInfo.is_avc, "1", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(streamInfo.IsAvc, "true", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(streamInfo.IsAvc, "1", StringComparison.OrdinalIgnoreCase))
{
stream.IsAVC = true;
}
- else if (string.Equals(streamInfo.is_avc, "false", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(streamInfo.is_avc, "0", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(streamInfo.IsAvc, "false", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(streamInfo.IsAvc, "0", StringComparison.OrdinalIgnoreCase))
{
stream.IsAVC = false;
}
- if (!string.IsNullOrWhiteSpace(streamInfo.field_order) && !string.Equals(streamInfo.field_order, "progressive", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrWhiteSpace(streamInfo.FieldOrder) && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase))
{
stream.IsInterlaced = true;
}
// Filter out junk
- if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1)
+ if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString) && streamInfo.CodecTagString.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1)
{
- stream.CodecTag = streamInfo.codec_tag_string;
+ stream.CodecTag = streamInfo.CodecTagString;
}
- if (streamInfo.tags != null)
+ if (streamInfo.Tags != null)
{
- stream.Language = GetDictionaryValue(streamInfo.tags, "language");
- stream.Comment = GetDictionaryValue(streamInfo.tags, "comment");
- stream.Title = GetDictionaryValue(streamInfo.tags, "title");
+ stream.Language = GetDictionaryValue(streamInfo.Tags, "language");
+ stream.Comment = GetDictionaryValue(streamInfo.Tags, "comment");
+ stream.Title = GetDictionaryValue(streamInfo.Tags, "title");
}
- if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase))
{
stream.Type = MediaStreamType.Audio;
- stream.Channels = streamInfo.channels;
+ stream.Channels = streamInfo.Channels;
- if (!string.IsNullOrEmpty(streamInfo.sample_rate))
+ if (!string.IsNullOrEmpty(streamInfo.SampleRate))
{
- if (int.TryParse(streamInfo.sample_rate, NumberStyles.Any, _usCulture, out var value))
+ if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, _usCulture, out var value))
{
stream.SampleRate = value;
}
}
- stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout);
+ stream.ChannelLayout = ParseChannelLayout(streamInfo.ChannelLayout);
- if (streamInfo.bits_per_sample > 0)
+ if (streamInfo.BitsPerSample > 0)
{
- stream.BitDepth = streamInfo.bits_per_sample;
+ stream.BitDepth = streamInfo.BitsPerSample;
}
- else if (streamInfo.bits_per_raw_sample > 0)
+ else if (streamInfo.BitsPerRawSample > 0)
{
- stream.BitDepth = streamInfo.bits_per_raw_sample;
+ stream.BitDepth = streamInfo.BitsPerRawSample;
}
}
- else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(streamInfo.CodecType, "subtitle", StringComparison.OrdinalIgnoreCase))
{
stream.Type = MediaStreamType.Subtitle;
stream.Codec = NormalizeSubtitleCodec(stream.Codec);
@@ -640,14 +639,14 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.localizedDefault = _localization.GetLocalizedString("Default");
stream.localizedForced = _localization.GetLocalizedString("Forced");
}
- else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase))
{
stream.Type = isAudio || string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)
? MediaStreamType.EmbeddedImage
: MediaStreamType.Video;
- stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate);
- stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate);
+ stream.AverageFrameRate = GetFrameRate(streamInfo.AverageFrameRate);
+ stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate);
if (isAudio || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) ||
string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase))
@@ -672,17 +671,17 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.Type = MediaStreamType.Video;
}
- stream.Width = streamInfo.width;
- stream.Height = streamInfo.height;
+ stream.Width = streamInfo.Width;
+ stream.Height = streamInfo.Height;
stream.AspectRatio = GetAspectRatio(streamInfo);
- if (streamInfo.bits_per_sample > 0)
+ if (streamInfo.BitsPerSample > 0)
{
- stream.BitDepth = streamInfo.bits_per_sample;
+ stream.BitDepth = streamInfo.BitsPerSample;
}
- else if (streamInfo.bits_per_raw_sample > 0)
+ else if (streamInfo.BitsPerRawSample > 0)
{
- stream.BitDepth = streamInfo.bits_per_raw_sample;
+ stream.BitDepth = streamInfo.BitsPerRawSample;
}
//stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
@@ -690,11 +689,11 @@ namespace MediaBrowser.MediaEncoding.Probing
// string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
// http://stackoverflow.com/questions/17353387/how-to-detect-anamorphic-video-with-ffprobe
- stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase);
+ stream.IsAnamorphic = string.Equals(streamInfo.SampleAspectRatio, "0:1", StringComparison.OrdinalIgnoreCase);
- if (streamInfo.refs > 0)
+ if (streamInfo.Refs > 0)
{
- stream.RefFrames = streamInfo.refs;
+ stream.RefFrames = streamInfo.Refs;
}
}
else
@@ -705,18 +704,18 @@ namespace MediaBrowser.MediaEncoding.Probing
// Get stream bitrate
var bitrate = 0;
- if (!string.IsNullOrEmpty(streamInfo.bit_rate))
+ if (!string.IsNullOrEmpty(streamInfo.BitRate))
{
- if (int.TryParse(streamInfo.bit_rate, NumberStyles.Any, _usCulture, out var value))
+ if (int.TryParse(streamInfo.BitRate, NumberStyles.Any, _usCulture, out var value))
{
bitrate = value;
}
}
- if (bitrate == 0 && formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video)
+ if (bitrate == 0 && formatInfo != null && !string.IsNullOrEmpty(formatInfo.BitRate) && stream.Type == MediaStreamType.Video)
{
// If the stream info doesn't have a bitrate get the value from the media format info
- if (int.TryParse(formatInfo.bit_rate, NumberStyles.Any, _usCulture, out var value))
+ if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, _usCulture, out var value))
{
bitrate = value;
}
@@ -727,14 +726,18 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.BitRate = bitrate;
}
- if (streamInfo.disposition != null)
+ var disposition = streamInfo.Disposition;
+ if (disposition != null)
{
- var isDefault = GetDictionaryValue(streamInfo.disposition, "default");
- var isForced = GetDictionaryValue(streamInfo.disposition, "forced");
-
- stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase);
+ if (disposition.GetValueOrDefault("default") == 1)
+ {
+ stream.IsDefault = true;
+ }
- stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase);
+ if (disposition.GetValueOrDefault("forced") == 1)
+ {
+ stream.IsForced = true;
+ }
}
NormalizeStreamTitle(stream);
@@ -761,7 +764,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
/// <returns>System.String.</returns>
- private string GetDictionaryValue(Dictionary<string, string> tags, string key)
+ private string GetDictionaryValue(IReadOnlyDictionary<string, string> tags, string key)
{
if (tags == null)
{
@@ -784,7 +787,7 @@ namespace MediaBrowser.MediaEncoding.Probing
private string GetAspectRatio(MediaStreamInfo info)
{
- var original = info.display_aspect_ratio;
+ var original = info.DisplayAspectRatio;
var parts = (original ?? string.Empty).Split(':');
if (!(parts.Length == 2 &&
@@ -793,8 +796,8 @@ namespace MediaBrowser.MediaEncoding.Probing
width > 0 &&
height > 0))
{
- width = info.width;
- height = info.height;
+ width = info.Width;
+ height = info.Height;
}
if (width > 0 && height > 0)
@@ -887,20 +890,20 @@ namespace MediaBrowser.MediaEncoding.Probing
private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data)
{
- if (result.streams != null)
+ if (result.Streams != null)
{
// Get the first info stream
- var stream = result.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase));
+ var stream = result.Streams.FirstOrDefault(s => string.Equals(s.CodecType, "audio", StringComparison.OrdinalIgnoreCase));
if (stream != null)
{
// Get duration from stream properties
- var duration = stream.duration;
+ var duration = stream.Duration;
// If it's not there go into format properties
if (string.IsNullOrEmpty(duration))
{
- duration = result.format.duration;
+ duration = result.Format.Duration;
}
// If we got something, parse it
@@ -914,11 +917,11 @@ namespace MediaBrowser.MediaEncoding.Probing
private void SetSize(InternalMediaInfoResult data, MediaInfo info)
{
- if (data.format != null)
+ if (data.Format != null)
{
- if (!string.IsNullOrEmpty(data.format.size))
+ if (!string.IsNullOrEmpty(data.Format.Size))
{
- info.Size = long.Parse(data.format.size, _usCulture);
+ info.Size = long.Parse(data.Format.Size, _usCulture);
}
else
{
@@ -1231,16 +1234,16 @@ namespace MediaBrowser.MediaEncoding.Probing
{
var info = new ChapterInfo();
- if (chapter.tags != null)
+ if (chapter.Tags != null)
{
- if (chapter.tags.TryGetValue("title", out string name))
+ if (chapter.Tags.TryGetValue("title", out string name))
{
info.Name = name;
}
}
// Limit accuracy to milliseconds to match xml saving
- var secondsString = chapter.start_time;
+ var secondsString = chapter.StartTime;
if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds))
{
@@ -1255,12 +1258,12 @@ namespace MediaBrowser.MediaEncoding.Probing
private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data)
{
- if (data.format == null || data.format.tags == null)
+ if (data.Format == null || data.Format.Tags == null)
{
return;
}
- var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre");
+ var genres = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/Genre");
if (!string.IsNullOrWhiteSpace(genres))
{
@@ -1276,14 +1279,14 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
+ var officialRating = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/ParentalRating");
if (!string.IsNullOrWhiteSpace(officialRating))
{
video.OfficialRating = officialRating;
}
- var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");
+ var people = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/MediaCredits");
if (!string.IsNullOrEmpty(people))
{
@@ -1293,7 +1296,7 @@ namespace MediaBrowser.MediaEncoding.Probing
.ToArray();
}
- var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
+ var year = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/OriginalReleaseTime");
if (!string.IsNullOrWhiteSpace(year))
{
if (int.TryParse(year, NumberStyles.Integer, _usCulture, out var val))
@@ -1302,7 +1305,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime");
+ var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/MediaOriginalBroadcastDateTime");
if (!string.IsNullOrWhiteSpace(premiereDateString))
{
// Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
@@ -1313,9 +1316,9 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
+ var description = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/SubTitleDescription");
- var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle");
+ var subTitle = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/SubTitle");
// For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 183d7566d..99bb368b2 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -123,7 +123,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentNullException(nameof(mediaSourceId));
}
- var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);
+ var mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSources
.First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
diff --git a/MediaBrowser.Model/Entities/MediaType.cs b/MediaBrowser.Model/Entities/MediaType.cs
index c56c8f8f2..d8b02c9ea 100644
--- a/MediaBrowser.Model/Entities/MediaType.cs
+++ b/MediaBrowser.Model/Entities/MediaType.cs
@@ -3,20 +3,23 @@ namespace MediaBrowser.Model.Entities
/// <summary>
/// Class MediaType
/// </summary>
- public class MediaType
+ public static class MediaType
{
/// <summary>
/// The video
/// </summary>
public const string Video = "Video";
+
/// <summary>
/// The audio
/// </summary>
public const string Audio = "Audio";
+
/// <summary>
/// The photo
/// </summary>
public const string Photo = "Photo";
+
/// <summary>
/// The book
/// </summary>
diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs
index 38638af42..440818c3e 100644
--- a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs
+++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs
@@ -1,15 +1,20 @@
+using System;
+using System.Collections.Generic;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.MediaInfo
{
+ /// <summary>
+ /// Class PlaybackInfoResponse.
+ /// </summary>
public class PlaybackInfoResponse
{
/// <summary>
/// Gets or sets the media sources.
/// </summary>
/// <value>The media sources.</value>
- public MediaSourceInfo[] MediaSources { get; set; }
+ public IReadOnlyList<MediaSourceInfo> MediaSources { get; set; }
/// <summary>
/// Gets or sets the play session identifier.
@@ -23,9 +28,12 @@ namespace MediaBrowser.Model.MediaInfo
/// <value>The error code.</value>
public PlaybackErrorCode? ErrorCode { get; set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlaybackInfoResponse" /> class.
+ /// </summary>
public PlaybackInfoResponse()
{
- MediaSources = new MediaSourceInfo[] { };
+ MediaSources = Array.Empty<MediaSourceInfo>();
}
}
}
diff --git a/jellyfin.ruleset b/jellyfin.ruleset
index 27d8a7cd9..92b7a03fd 100644
--- a/jellyfin.ruleset
+++ b/jellyfin.ruleset
@@ -24,6 +24,8 @@
<Rule Id="SA1413" Action="None" />
<!-- disable warning SA1512: Single-line comments must not be followed by blank line -->
<Rule Id="SA1512" Action="None" />
+ <!-- disable warning SA1515: Single-line comment should be preceded by blank line -->
+ <Rule Id="SA1515" Action="None" />
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
<Rule Id="SA1633" Action="None" />
</Rules>
@@ -35,6 +37,8 @@
<Rule Id="CA1032" Action="Info" />
<!-- disable warning CA1062: Validate arguments of public methods -->
<Rule Id="CA1062" Action="Info" />
+ <!-- disable warning CA1716: Identifiers should not match keywords -->
+ <Rule Id="CA1716" Action="Info" />
<!-- disable warning CA1720: Identifiers should not contain type names -->
<Rule Id="CA1720" Action="Info" />
<!-- disable warning CA1812: internal class that is apparently never instantiated.
@@ -43,14 +47,18 @@
<Rule Id="CA1812" Action="Info" />
<!-- disable warning CA1822: Member does not access instance data and can be marked as static -->
<Rule Id="CA1822" Action="Info" />
+ <!-- disable warning CA2000: Dispose objects before losing scope -->
+ <Rule Id="CA2000" Action="Info" />
<!-- disable warning CA1054: Change the type of parameter url from string to System.Uri -->
<Rule Id="CA1054" Action="None" />
+ <!-- disable warning CA1055: URI return values should not be strings -->
+ <Rule Id="CA1055" Action="None" />
+ <!-- disable warning CA1056: URI properties should not be strings -->
+ <Rule Id="CA1056" Action="None" />
<!-- disable warning CA1303: Do not pass literals as localized parameters -->
<Rule Id="CA1303" Action="None" />
<!-- disable warning CA1308: Normalize strings to uppercase -->
<Rule Id="CA1308" Action="None" />
- <!-- disable warning CA2000: Dispose objects before losing scope -->
- <Rule Id="CA2000" Action="None" />
</Rules>
</RuleSet>
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 1671b8d79..e0deeeabb 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
diff --git a/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
new file mode 100644
index 000000000..eb69d915c
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
@@ -0,0 +1,57 @@
+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(@"disc1"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disk1"));
+
+ // Add a space
+ Assert.True(IsMultiDiscAlbumFolder(@"cd 1"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disc 1"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disk 1"));
+
+ 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] Oppurtunities (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..e8f14cdc4
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
@@ -0,0 +1,40 @@
+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..1ae637281
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
@@ -0,0 +1,424 @@
+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 TestEpisodeNumber50()
+ {
+ // This convention is not currently supported, just adding in case we want to look at it in the future
+ Assert.Equal(1, GetEpisodeNumberFromFile(@"2016/Season s2016e1.mp4"));
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestEpisodeNumber51()
+ {
+ // This convention is not currently supported, just adding in case we want to look at it in the future
+ Assert.Equal(1, GetEpisodeNumberFromFile(@"2016/Season 2016x1.mp4"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber52()
+ {
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode - 16.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber53()
+ {
+ // This is not supported. Expected to fail, although it would be a good one to add support for.
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16.avi"));
+ }
+
+ [Fact]
+ public void TestEpisodeNumber54()
+ {
+ // This is not supported. Expected to fail, although it would be a good one to add support for.
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16 - Some Title.avi"));
+ }
+
+ // [Fact]
+ public void TestEpisodeNumber55()
+ {
+ // This is not supported. Expected to fail, although it would be a good one to add support for.
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Season 3 Episode 16.avi"));
+ }
+
+ // [Fact]
+ public void TestEpisodeNumber56()
+ {
+ // This is not supported. Expected to fail, although it would be a good one to add support for.
+ Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Season 3 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..b993e241c
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs
@@ -0,0 +1,15 @@
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public abstract class BaseVideoTest
+ {
+ protected VideoResolver GetParser()
+ {
+ var options = new NamingOptions();
+
+ return new VideoResolver(options);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
new file mode 100644
index 000000000..bba73ad91
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
@@ -0,0 +1,143 @@
+using System.IO;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public class CleanDateTimeTests : BaseVideoTest
+ {
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTime()
+ {
+ Test(@"The Wolf of Wall Street (2013).mkv", "The Wolf of Wall Street", 2013);
+ Test(@"The Wolf of Wall Street 2 (2013).mkv", "The Wolf of Wall Street 2", 2013);
+ Test(@"The Wolf of Wall Street - 2 (2013).mkv", "The Wolf of Wall Street - 2", 2013);
+ Test(@"The Wolf of Wall Street 2001 (2013).mkv", "The Wolf of Wall Street 2001", 2013);
+
+ Test(@"300 (2006).mkv", "300", 2006);
+ Test(@"d:/movies/300 (2006).mkv", "300", 2006);
+ Test(@"300 2 (2006).mkv", "300 2", 2006);
+ Test(@"300 - 2 (2006).mkv", "300 - 2", 2006);
+ Test(@"300 2001 (2006).mkv", "300 2001", 2006);
+
+ Test(@"curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", "curse.of.chucky", 2013);
+ Test(@"curse.of.chucky.2013.stv.unrated.multi.2160p.bluray.x264-rough", "curse.of.chucky", 2013);
+
+ Test(@"/server/Movies/300 (2007)/300 (2006).bluray.disc", "300", 2006);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTime1()
+ {
+ Test(@"Arrival.2016.2160p.Blu-Ray.HEVC.mkv", "Arrival", 2016);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTimeWithoutFileExtension()
+ {
+ Test(@"The Wolf of Wall Street (2013)", "The Wolf of Wall Street", 2013);
+ Test(@"The Wolf of Wall Street 2 (2013)", "The Wolf of Wall Street 2", 2013);
+ Test(@"The Wolf of Wall Street - 2 (2013)", "The Wolf of Wall Street - 2", 2013);
+ Test(@"The Wolf of Wall Street 2001 (2013)", "The Wolf of Wall Street 2001", 2013);
+
+ Test(@"300 (2006)", "300", 2006);
+ Test(@"d:/movies/300 (2006)", "300", 2006);
+ Test(@"300 2 (2006)", "300 2", 2006);
+ Test(@"300 - 2 (2006)", "300 - 2", 2006);
+ Test(@"300 2001 (2006)", "300 2001", 2006);
+
+ Test(@"/server/Movies/300 (2007)/300 (2006)", "300", 2006);
+ Test(@"/server/Movies/300 (2007)/300 (2006).mkv", "300", 2006);
+ }
+
+ [Fact]
+ public void TestCleanDateTimeWithoutDate()
+ {
+ Test(@"American.Psycho.mkv", "American.Psycho.mkv", null);
+ Test(@"American Psycho.mkv", "American Psycho.mkv", null);
+ }
+
+ [Fact]
+ public void TestCleanDateTimeWithBracketedName()
+ {
+ Test(@"[rec].mkv", "[rec].mkv", null);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTimeWithoutExtension()
+ {
+ Test(@"St. Vincent (2014)", "St. Vincent", 2014);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTimeWithoutDate1()
+ {
+ Test("Super movie(2009).mp4", "Super movie", 2009);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTimeWithoutParenthesis()
+ {
+ Test("Drug War 2013.mp4", "Drug War", 2013);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTimeWithMultipleYears()
+ {
+ Test("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTimeWithYearAndResolution()
+ {
+ Test("First Man 2018 1080p.mkv", "First Man", 2018);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTimeWithYearAndResolution1()
+ {
+ Test("First Man (2018) 1080p.mkv", "First Man", 2018);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateTimeWithSceneRelease()
+ {
+ Test("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestYearInBrackets()
+ {
+ Test("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018);
+ }
+
+ private void Test(string input, string expectedName, int? expectedYear)
+ {
+ input = Path.GetFileName(input);
+
+ var result = GetParser().CleanDateTime(input);
+
+ Assert.Equal(expectedName, result.Name, true);
+ Assert.Equal(expectedYear, result.Year);
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanDateAndStringsSequence()
+ {
+ // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
+
+ Test(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
new file mode 100644
index 000000000..cd90ac236
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.Video
+{
+ public class CleanStringTests : BaseVideoTest
+ {
+ // FIXME
+ // [Fact]
+ public void TestCleanString()
+ {
+ Test("Super movie 480p.mp4", "Super movie");
+ Test("Super movie 480p 2001.mp4", "Super movie");
+ Test("Super movie [480p].mp4", "Super movie");
+ Test("480 Super movie [tmdbid=12345].mp4", "480 Super movie");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanString1()
+ {
+ Test("Super movie(2009).mp4", "Super movie(2009).mp4");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestCleanString2()
+ {
+ Test("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestStringWithoutDate()
+ {
+ Test(@"American.Psycho.mkv", "American.Psycho.mkv");
+ Test(@"American Psycho.mkv", "American Psycho.mkv");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestNameWithBrackets()
+ {
+ Test(@"[rec].mkv", "[rec].mkv");
+ }
+
+ // FIXME
+ // [Fact]
+ public void Test4k()
+ {
+ Test("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestUltraHd()
+ {
+ Test("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestUHd()
+ {
+ Test("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestHDR()
+ {
+ Test("Crouching.Tiger.Hidden.Dragon.HDR.mkv", "Crouching.Tiger.Hidden.Dragon");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestHDC()
+ {
+ Test("Crouching.Tiger.Hidden.Dragon.HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestHDC1()
+ {
+ Test("Crouching.Tiger.Hidden.Dragon-HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestBDrip()
+ {
+ Test("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestBDripHDC()
+ {
+ Test("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestMulti()
+ {
+ Test("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestLeadingBraces()
+ {
+ // Not actually supported, just reported by a user
+ Test("[0004] - After The Sunset.el.mkv", "After The Sunset");
+ }
+
+ // FIXME
+ // [Fact]
+ public void TestTrailingBraces()
+ {
+ Test("After The Sunset - [0004].mkv", "After The Sunset");
+ }
+
+ private void Test(string input, string expectedName)
+ {
+ var result = GetParser().CleanString(input).ToString();
+
+ Assert.Equal(expectedName, result, true);
+ }
+ }
+}
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..b8674ec49
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
@@ -0,0 +1,438 @@
+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()
+ {
+ // This is currently not supported and will fail, but we should try to figure it out
+ 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..5faef0e3d
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
@@ -0,0 +1,478 @@
+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 TestDirectories2()
+ {
+ //TestDirectory(@"blah blah", false, @"blah blah");
+ //TestDirectory(@"d:/music/weezer/03 Pinkerton", false, "03 Pinkerton");
+ //TestDirectory(@"d:/music/michael jackson/Bad (2012 Remaster)", false, "Bad (2012 Remaster)");
+
+ //TestDirectory(@"blah blah - cd1", true, "blah blah");
+ //TestDirectory(@"blah blah - disc1", true, "blah blah");
+ //TestDirectory(@"blah blah - disk1", true, "blah blah");
+ //TestDirectory(@"blah blah - pt1", true, "blah blah");
+ //TestDirectory(@"blah blah - part1", true, "blah blah");
+ //TestDirectory(@"blah blah - dvd1", true, "blah blah");
+
+ //// Add a space
+ //TestDirectory(@"blah blah - cd 1", true, "blah blah");
+ //TestDirectory(@"blah blah - disc 1", true, "blah blah");
+ //TestDirectory(@"blah blah - disk 1", true, "blah blah");
+ //TestDirectory(@"blah blah - pt 1", true, "blah blah");
+ //TestDirectory(@"blah blah - part 1", true, "blah blah");
+ //TestDirectory(@"blah blah - dvd 1", true, "blah blah");
+
+ //// Not case sensitive
+ //TestDirectory(@"blah blah - Disc1", true, "blah blah");
+ }
+
+ [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..96fa8c5a5
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Globalization;
+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);
+ }
+ }
+}