From 991adc8efe76dbe07faaea1a96de0e746e0f2386 Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sat, 13 Feb 2021 15:28:37 +0100 Subject: [PATCH 001/118] Fix BaseItemKind conversion for PlaylistsFolder Return the correct ClientTypeName to allow Enum Parse Added dynamic unit tests to ensure all BaseItem concrete descend --- CONTRIBUTORS.md | 1 + .../Playlists/ManualPlaylistsFolder.cs | 5 ++ MediaBrowser.sln | 9 ++- .../BaseItemKindTests.cs | 62 +++++++++++++++++++ .../Jellyfin.Server.Tests.csproj | 48 ++++++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/Jellyfin.Server.Tests/BaseItemKindTests.cs create mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1200275d52..89788b2343 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -207,3 +207,4 @@ - [Tim Hobbs](https://github.com/timhobbs) - [SvenVandenbrande](https://github.com/SvenVandenbrande) - [olsh](https://github.com/olsh) + - [lbenini] (https://github.com/lbenini) diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 358606b0dc..4160f3a500 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -49,5 +49,10 @@ namespace Emby.Server.Implementations.Playlists query.Parent = null; return LibraryManager.GetItemsResult(query); } + + public override string GetClientTypeName() + { + return "ManualPlaylistsFolder"; + } } } diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 4e6687cceb..c16e6032e4 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -72,7 +72,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{B26F671A-D5C0-4461-B7C3-324EB167E4B3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -200,6 +202,10 @@ Global {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -214,6 +220,7 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {B26F671A-D5C0-4461-B7C3-324EB167E4B3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs b/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs new file mode 100644 index 0000000000..282760bf97 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Entities; +using Xunit; + +namespace Jellyfin.Server.Tests +{ + public class BaseItemKindTests + { + [Theory] + [ClassData(typeof(GetBaseItemDescendant))] + public void BaseKindEnumTest(Type baseItemDescendantType) + { + var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes); + + Assert.NotNull(defaultConstructor); + if (defaultConstructor != null) + { + var instance = (BaseItem)defaultConstructor.Invoke(null); + var exception = Record.Exception(() => instance.GetBaseItemKind()); + Assert.Null(exception); + } + } + + private static bool IsProjectAssemblyName(string? name) + { + if (name == null) + { + return false; + } + + return name.Contains("Jellyfin", StringComparison.InvariantCulture) + || name.Contains("Emby", StringComparison.InvariantCulture) + || name.Contains("MediaBrowser", StringComparison.InvariantCulture) + || name.Contains("RSSDP", StringComparison.InvariantCulture); + } + + private class GetBaseItemDescendant : IEnumerable + { + public IEnumerator GetEnumerator() + { + var projectAssemblies = AppDomain.CurrentDomain.GetAssemblies() + .Where(x => IsProjectAssemblyName(x.FullName)); + + foreach (var projectAssembly in projectAssemblies) + { + var baseItemDescendantTypes = projectAssembly.GetTypes() + .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(BaseItem))); + + foreach (var descendantType in baseItemDescendantTypes) + { + yield return new object?[] { descendantType }; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } +} diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj new file mode 100644 index 0000000000..55f6005028 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -0,0 +1,48 @@ + + + + + {0FD15BDA-FA63-4FFF-9E6B-781F20DA88D9} + + + + net5.0 + false + true + enable + Jellyfin.Server.Tests + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + ../jellyfin-tests.ruleset + + + From c8395899ba8da1908b0cef54eff266c32baebc97 Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sat, 13 Feb 2021 19:40:15 +0100 Subject: [PATCH 002/118] Moved test to Jellyfin.Server.Implementation.Tests as by review Aligned code base to review comments: Jellyfin.Server.Implementation.Tests is the correct place --- MediaBrowser.sln | 7 --- .../BaseItem}/BaseItemKindTests.cs | 6 +-- .../Jellyfin.Server.Tests.csproj | 48 ------------------- 3 files changed, 3 insertions(+), 58 deletions(-) rename tests/{Jellyfin.Server.Tests => Jellyfin.Server.Implementations.Tests/BaseItem}/BaseItemKindTests.cs (88%) delete mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj diff --git a/MediaBrowser.sln b/MediaBrowser.sln index c16e6032e4..122421f34c 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -74,8 +74,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{B26F671A-D5C0-4461-B7C3-324EB167E4B3}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -202,10 +200,6 @@ Global {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -220,7 +214,6 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {B26F671A-D5C0-4461-B7C3-324EB167E4B3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs similarity index 88% rename from tests/Jellyfin.Server.Tests/BaseItemKindTests.cs rename to tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs index 282760bf97..3f56c82f4d 100644 --- a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs @@ -5,7 +5,7 @@ using System.Linq; using MediaBrowser.Controller.Entities; using Xunit; -namespace Jellyfin.Server.Tests +namespace Jellyfin.Server.Implementations.Tests.BaseItem { public class BaseItemKindTests { @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Tests Assert.NotNull(defaultConstructor); if (defaultConstructor != null) { - var instance = (BaseItem)defaultConstructor.Invoke(null); + var instance = (MediaBrowser.Controller.Entities.BaseItem)defaultConstructor.Invoke(null); var exception = Record.Exception(() => instance.GetBaseItemKind()); Assert.Null(exception); } @@ -47,7 +47,7 @@ namespace Jellyfin.Server.Tests foreach (var projectAssembly in projectAssemblies) { var baseItemDescendantTypes = projectAssembly.GetTypes() - .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(BaseItem))); + .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); foreach (var descendantType in baseItemDescendantTypes) { diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj deleted file mode 100644 index 55f6005028..0000000000 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - - {0FD15BDA-FA63-4FFF-9E6B-781F20DA88D9} - - - - net5.0 - false - true - enable - Jellyfin.Server.Tests - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - ../jellyfin-tests.ruleset - - - From c4d142eda15644d045de884984cb644d7948837e Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sat, 13 Feb 2021 15:28:37 +0100 Subject: [PATCH 003/118] Fix BaseItemKind conversion for PlaylistsFolder Return the correct ClientTypeName to allow Enum Parse Added dynamic unit tests to ensure all BaseItem concrete descend --- CONTRIBUTORS.md | 1 + .../Playlists/ManualPlaylistsFolder.cs | 5 ++ Jellyfin.sln | 9 ++- .../BaseItemKindTests.cs | 62 +++++++++++++++++++ .../Jellyfin.Server.Tests.csproj | 48 ++++++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/Jellyfin.Server.Tests/BaseItemKindTests.cs create mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1200275d52..89788b2343 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -207,3 +207,4 @@ - [Tim Hobbs](https://github.com/timhobbs) - [SvenVandenbrande](https://github.com/SvenVandenbrande) - [olsh](https://github.com/olsh) + - [lbenini] (https://github.com/lbenini) diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 358606b0dc..4160f3a500 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -49,5 +49,10 @@ namespace Emby.Server.Implementations.Playlists query.Parent = null; return LibraryManager.GetItemsResult(query); } + + public override string GetClientTypeName() + { + return "ManualPlaylistsFolder"; + } } } diff --git a/Jellyfin.sln b/Jellyfin.sln index 4e6687cceb..c16e6032e4 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -72,7 +72,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{B26F671A-D5C0-4461-B7C3-324EB167E4B3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -200,6 +202,10 @@ Global {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -214,6 +220,7 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {B26F671A-D5C0-4461-B7C3-324EB167E4B3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs b/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs new file mode 100644 index 0000000000..282760bf97 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Entities; +using Xunit; + +namespace Jellyfin.Server.Tests +{ + public class BaseItemKindTests + { + [Theory] + [ClassData(typeof(GetBaseItemDescendant))] + public void BaseKindEnumTest(Type baseItemDescendantType) + { + var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes); + + Assert.NotNull(defaultConstructor); + if (defaultConstructor != null) + { + var instance = (BaseItem)defaultConstructor.Invoke(null); + var exception = Record.Exception(() => instance.GetBaseItemKind()); + Assert.Null(exception); + } + } + + private static bool IsProjectAssemblyName(string? name) + { + if (name == null) + { + return false; + } + + return name.Contains("Jellyfin", StringComparison.InvariantCulture) + || name.Contains("Emby", StringComparison.InvariantCulture) + || name.Contains("MediaBrowser", StringComparison.InvariantCulture) + || name.Contains("RSSDP", StringComparison.InvariantCulture); + } + + private class GetBaseItemDescendant : IEnumerable + { + public IEnumerator GetEnumerator() + { + var projectAssemblies = AppDomain.CurrentDomain.GetAssemblies() + .Where(x => IsProjectAssemblyName(x.FullName)); + + foreach (var projectAssembly in projectAssemblies) + { + var baseItemDescendantTypes = projectAssembly.GetTypes() + .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(BaseItem))); + + foreach (var descendantType in baseItemDescendantTypes) + { + yield return new object?[] { descendantType }; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } +} diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj new file mode 100644 index 0000000000..55f6005028 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -0,0 +1,48 @@ + + + + + {0FD15BDA-FA63-4FFF-9E6B-781F20DA88D9} + + + + net5.0 + false + true + enable + Jellyfin.Server.Tests + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + ../jellyfin-tests.ruleset + + + From 2f8d15ed08015e58fea0842fea9591111b823a9c Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sat, 13 Feb 2021 19:40:15 +0100 Subject: [PATCH 004/118] Moved test to Jellyfin.Server.Implementation.Tests as by review Aligned code base to review comments: Jellyfin.Server.Implementation.Tests is the correct place --- Jellyfin.sln | 7 --- .../BaseItem}/BaseItemKindTests.cs | 6 +-- .../Jellyfin.Server.Tests.csproj | 48 ------------------- 3 files changed, 3 insertions(+), 58 deletions(-) rename tests/{Jellyfin.Server.Tests => Jellyfin.Server.Implementations.Tests/BaseItem}/BaseItemKindTests.cs (88%) delete mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj diff --git a/Jellyfin.sln b/Jellyfin.sln index c16e6032e4..122421f34c 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -74,8 +74,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{B26F671A-D5C0-4461-B7C3-324EB167E4B3}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -202,10 +200,6 @@ Global {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -220,7 +214,6 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {B26F671A-D5C0-4461-B7C3-324EB167E4B3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs similarity index 88% rename from tests/Jellyfin.Server.Tests/BaseItemKindTests.cs rename to tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs index 282760bf97..3f56c82f4d 100644 --- a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs @@ -5,7 +5,7 @@ using System.Linq; using MediaBrowser.Controller.Entities; using Xunit; -namespace Jellyfin.Server.Tests +namespace Jellyfin.Server.Implementations.Tests.BaseItem { public class BaseItemKindTests { @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Tests Assert.NotNull(defaultConstructor); if (defaultConstructor != null) { - var instance = (BaseItem)defaultConstructor.Invoke(null); + var instance = (MediaBrowser.Controller.Entities.BaseItem)defaultConstructor.Invoke(null); var exception = Record.Exception(() => instance.GetBaseItemKind()); Assert.Null(exception); } @@ -47,7 +47,7 @@ namespace Jellyfin.Server.Tests foreach (var projectAssembly in projectAssemblies) { var baseItemDescendantTypes = projectAssembly.GetTypes() - .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(BaseItem))); + .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); foreach (var descendantType in baseItemDescendantTypes) { diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj deleted file mode 100644 index 55f6005028..0000000000 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - - {0FD15BDA-FA63-4FFF-9E6B-781F20DA88D9} - - - - net5.0 - false - true - enable - Jellyfin.Server.Tests - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - ../jellyfin-tests.ruleset - - - From 078b6244ee060b2c5caddc3ba8a60633c4e95054 Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sun, 14 Feb 2021 12:46:28 +0100 Subject: [PATCH 005/118] Restored GUID in Jellyfin.XbmcMetadata.Tests Restored the project type guid as by review See https://github.com/dotnet/project-system/issues/1821 --- Jellyfin.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index 122421f34c..4e6687cceb 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -72,7 +72,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 415b03d719e054a3cc6998d89c3f4a13806e0a9e Mon Sep 17 00:00:00 2001 From: Mason Tran Date: Wed, 24 Mar 2021 02:35:44 -0400 Subject: [PATCH 006/118] Delay starting services until after network is online --- debian/jellyfin.service | 2 +- fedora/jellyfin.service | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/jellyfin.service b/debian/jellyfin.service index f1a8f4652c..c9d1a4d130 100644 --- a/debian/jellyfin.service +++ b/debian/jellyfin.service @@ -1,6 +1,6 @@ [Unit] Description = Jellyfin Media Server -After = network.target +After = network-online.target [Service] Type = simple diff --git a/fedora/jellyfin.service b/fedora/jellyfin.service index b092ebf2f0..f706b0ad3f 100644 --- a/fedora/jellyfin.service +++ b/fedora/jellyfin.service @@ -1,5 +1,5 @@ [Unit] -After=network.target +After=network-online.target Description=Jellyfin is a free software media system that puts you in control of managing and streaming your media. [Service] From 351ae665090c238bc34b57fe4ab7dc91f30dd5fc Mon Sep 17 00:00:00 2001 From: Stanislav Ionascu Date: Sun, 13 Jun 2021 18:56:13 +0200 Subject: [PATCH 007/118] Better detection of the ISO DVD/BD types The ISO image will be opened and checked for disc-type specific folders. Can be overridden using NAME.dvd.iso / NAME.bluray.iso --- .../Emby.Server.Implementations.csproj | 1 + .../Library/Resolvers/BaseVideoResolver.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 5566625853..6b99f3dcbc 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -23,6 +23,7 @@ + diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index cdb492022b..f114a88b78 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Linq; +using DiscUtils.Udf; using Emby.Naming.Video; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -201,6 +202,22 @@ namespace Emby.Server.Implementations.Library.Resolvers { video.IsoType = IsoType.BluRay; } + else + { + // use disc-utils, both DVDs and BDs use UDF filesystem + using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read)) + { + UdfReader udfReader = new UdfReader(videoFileStream); + if (udfReader.DirectoryExists("VIDEO_TS")) + { + video.IsoType = IsoType.Dvd; + } + else if (udfReader.DirectoryExists("BDMV")) + { + video.IsoType = IsoType.BluRay; + } + } + } } } From 790b284184aa7ce54f409fd205540f33e5ce4f88 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 20 Jun 2021 07:15:32 -0600 Subject: [PATCH 008/118] Add missing BaseItemKind --- Jellyfin.Data/Enums/BaseItemKind.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Data/Enums/BaseItemKind.cs b/Jellyfin.Data/Enums/BaseItemKind.cs index aac30279ed..8757817463 100644 --- a/Jellyfin.Data/Enums/BaseItemKind.cs +++ b/Jellyfin.Data/Enums/BaseItemKind.cs @@ -78,6 +78,16 @@ /// Movie, + /// + /// Item is a live tv channel. + /// + LiveTvChannel, + + /// + /// Item is a live tv program. + /// + LiveTvProgram, + /// /// Item is music album. /// @@ -118,6 +128,11 @@ /// Playlist, + /// + /// Item is playlist folder. + /// + PlaylistsFolder, + /// /// Item is program /// @@ -187,4 +202,4 @@ /// Year } -} \ No newline at end of file +} From ac765190813caa20f3742dccca6477b9e4b9dd60 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 20 Jun 2021 07:15:46 -0600 Subject: [PATCH 009/118] Enhance BaseItemKindTests --- .../BaseItem/BaseItemKindTests.cs | 75 ++++++++++++++----- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs index 3f56c82f4d..995a75164b 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs @@ -1,9 +1,12 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; -using MediaBrowser.Controller.Entities; +using System.Reflection; +using Jellyfin.Data.Enums; using Xunit; +using Xunit.Sdk; namespace Jellyfin.Server.Implementations.Tests.BaseItem { @@ -11,7 +14,15 @@ namespace Jellyfin.Server.Implementations.Tests.BaseItem { [Theory] [ClassData(typeof(GetBaseItemDescendant))] - public void BaseKindEnumTest(Type baseItemDescendantType) + public void BaseItemKindEnumTest(Type baseItemType) + { + var enumValue = Enum.Parse(baseItemType.Name); + Assert.True(Enum.IsDefined(typeof(BaseItemKind), enumValue)); + } + + [Theory] + [ClassData(typeof(GetBaseItemDescendant))] + public void GetBaseKindEnumTest(Type baseItemDescendantType) { var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes); @@ -24,34 +35,58 @@ namespace Jellyfin.Server.Implementations.Tests.BaseItem } } - private static bool IsProjectAssemblyName(string? name) + private class GetBaseItemDescendant : IEnumerable { - if (name == null) + private static bool IsProjectAssemblyName(string? name) { - return false; - } + if (name == null) + { + return false; + } - return name.Contains("Jellyfin", StringComparison.InvariantCulture) - || name.Contains("Emby", StringComparison.InvariantCulture) - || name.Contains("MediaBrowser", StringComparison.InvariantCulture) - || name.Contains("RSSDP", StringComparison.InvariantCulture); - } + return name.StartsWith("Jellyfin", StringComparison.OrdinalIgnoreCase) + || name.StartsWith("Emby", StringComparison.OrdinalIgnoreCase) + || name.StartsWith("MediaBrowser", StringComparison.OrdinalIgnoreCase) + || name.StartsWith("RSSDP", StringComparison.OrdinalIgnoreCase); + } - private class GetBaseItemDescendant : IEnumerable - { public IEnumerator GetEnumerator() { - var projectAssemblies = AppDomain.CurrentDomain.GetAssemblies() - .Where(x => IsProjectAssemblyName(x.FullName)); + var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in loadedAssemblies) + { + if (IsProjectAssemblyName(assembly.FullName)) + { + var baseItemTypes = assembly.GetTypes() + .Where(targetType => targetType.IsClass + && !targetType.IsAbstract + && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); + foreach (var baseItemType in baseItemTypes) + { + yield return new object?[] { baseItemType }; + } + } + } - foreach (var projectAssembly in projectAssemblies) + var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if (path == null) { - var baseItemDescendantTypes = projectAssembly.GetTypes() - .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); + throw new NullException("Assembly location is null"); + } - foreach (var descendantType in baseItemDescendantTypes) + foreach (string dll in Directory.GetFiles(path, "*.dll")) + { + var assembly = Assembly.LoadFile(dll); + if (IsProjectAssemblyName(assembly.FullName)) { - yield return new object?[] { descendantType }; + var baseItemTypes = assembly.GetTypes() + .Where(targetType => targetType.IsClass + && !targetType.IsAbstract + && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); + foreach (var baseItemType in baseItemTypes) + { + yield return new object?[] { baseItemType }; + } } } } From fa7b7b84e7a75fc9dbbf4e905916033d3f1f84a5 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Mon, 21 Jun 2021 16:06:25 -0600 Subject: [PATCH 010/118] Update CONTRIBUTORS.md Co-authored-by: Bond-009 --- CONTRIBUTORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 59addc7faf..c5f8b4ba40 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -212,4 +212,4 @@ - [Tim Hobbs](https://github.com/timhobbs) - [SvenVandenbrande](https://github.com/SvenVandenbrande) - [olsh](https://github.com/olsh) - - [lbenini] (https://github.com/lbenini) + - [lbenini](https://github.com/lbenini) From 39e84ba4abbcf0796ad87984c41c0f26b8783e71 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 21 Jun 2021 16:06:54 -0600 Subject: [PATCH 011/118] Suggestions from review --- .../BaseItem/BaseItemKindTests.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs index 995a75164b..f3e7e208ac 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.Implementations.Tests.BaseItem public class BaseItemKindTests { [Theory] - [ClassData(typeof(GetBaseItemDescendant))] + [ClassData(typeof(GetBaseItemDescendants))] public void BaseItemKindEnumTest(Type baseItemType) { var enumValue = Enum.Parse(baseItemType.Name); @@ -21,7 +21,7 @@ namespace Jellyfin.Server.Implementations.Tests.BaseItem } [Theory] - [ClassData(typeof(GetBaseItemDescendant))] + [ClassData(typeof(GetBaseItemDescendants))] public void GetBaseKindEnumTest(Type baseItemDescendantType) { var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes); @@ -35,7 +35,7 @@ namespace Jellyfin.Server.Implementations.Tests.BaseItem } } - private class GetBaseItemDescendant : IEnumerable + private class GetBaseItemDescendants : IEnumerable { private static bool IsProjectAssemblyName(string? name) { @@ -46,8 +46,7 @@ namespace Jellyfin.Server.Implementations.Tests.BaseItem return name.StartsWith("Jellyfin", StringComparison.OrdinalIgnoreCase) || name.StartsWith("Emby", StringComparison.OrdinalIgnoreCase) - || name.StartsWith("MediaBrowser", StringComparison.OrdinalIgnoreCase) - || name.StartsWith("RSSDP", StringComparison.OrdinalIgnoreCase); + || name.StartsWith("MediaBrowser", StringComparison.OrdinalIgnoreCase); } public IEnumerator GetEnumerator() From 66e452422cd20e059ad9b83cc4ff71af13e0bbd1 Mon Sep 17 00:00:00 2001 From: Julien Voisin Date: Mon, 28 Jun 2021 06:15:07 +0000 Subject: [PATCH 012/118] Add a LGTM.com badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6859a8a76f..69fa8ca268 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ Master Commits RSS Feed + +Total LGTM alerts +

--- From aaab6a351876880fe1f240b356de7c319f3bd01b Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 25 Jul 2021 00:52:03 +0800 Subject: [PATCH 013/118] add tests for FFmpeg 4.4 and 4.3.2 --- .../EncoderValidatorTests.cs | 6 ++++- .../EncoderValidatorTestsData.cs | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs index 39fd8afda1..2310f5b244 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs @@ -14,10 +14,12 @@ namespace Jellyfin.MediaEncoding.Tests public void GetFFmpegVersionTest(string versionOutput, Version? version) { var val = new EncoderValidator(new NullLogger()); - Assert.Equal(version, val.GetFFmpegVersion(versionOutput)); + Assert.Equal(version, val.GetFFmpegVersionInternal(versionOutput)); } [Theory] + [InlineData(EncoderValidatorTestsData.FFmpegV44Output, true)] + [InlineData(EncoderValidatorTestsData.FFmpegV432Output, true)] [InlineData(EncoderValidatorTestsData.FFmpegV431Output, true)] [InlineData(EncoderValidatorTestsData.FFmpegV43Output, true)] [InlineData(EncoderValidatorTestsData.FFmpegV421Output, true)] @@ -36,6 +38,8 @@ namespace Jellyfin.MediaEncoding.Tests { public IEnumerator GetEnumerator() { + yield return new object?[] { EncoderValidatorTestsData.FFmpegV44Output, new Version(4, 4) }; + yield return new object?[] { EncoderValidatorTestsData.FFmpegV432Output, new Version(4, 3, 2) }; yield return new object?[] { EncoderValidatorTestsData.FFmpegV431Output, new Version(4, 3, 1) }; yield return new object?[] { EncoderValidatorTestsData.FFmpegV43Output, new Version(4, 3) }; yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) }; diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs index 9f5bef9a88..02bf046ed1 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs @@ -2,6 +2,30 @@ namespace Jellyfin.MediaEncoding.Tests { internal static class EncoderValidatorTestsData { + public const string FFmpegV44Output = @"ffmpeg version 4.4-Jellyfin Copyright (c) 2000-2021 the FFmpeg developers +built with gcc 10.3.0 (Rev5, Built by MSYS2 project) +configuration: --disable-static --enable-shared --extra-version=Jellyfin --disable-ffplay --disable-debug --enable-gpl --enable-version3 --enable-bzlib --enable-iconv --enable-lzma --enable-zlib --enable-sdl2 --enable-fontconfig --enable-gmp --enable-libass --enable-libzimg --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libvpx --enable-libx264 --enable-libx265 --enable-libdav1d --enable-opencl --enable-dxva2 --enable-d3d11va --enable-amf --enable-libmfx --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvenc --enable-nvdec --enable-ffnvcodec --enable-gnutls +libavutil 56. 70.100 / 56. 70.100 +libavcodec 58.134.100 / 58.134.100 +libavformat 58. 76.100 / 58. 76.100 +libavdevice 58. 13.100 / 58. 13.100 +libavfilter 7.110.100 / 7.110.100 +libswscale 5. 9.100 / 5. 9.100 +libswresample 3. 9.100 / 3. 9.100 +libpostproc 55. 9.100 / 55. 9.100"; + + public const string FFmpegV432Output = @"ffmpeg version n4.3.2-Jellyfin Copyright (c) 2000-2021 the FFmpeg developers +built with gcc 10.2.0 (Rev9, Built by MSYS2 project) +configuration: --disable-static --enable-shared --cc='ccache gcc' --cxx='ccache g++' --extra-version=Jellyfin --disable-ffplay --disable-debug --enable-lto --enable-gpl --enable-version3 --enable-bzlib --enable-iconv --enable-lzma --enable-zlib --enable-sdl2 --enable-fontconfig --enable-gmp --enable-libass --enable-libzimg --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libvpx --enable-libx264 --enable-libx265 --enable-libdav1d --enable-opencl --enable-dxva2 --enable-d3d11va --enable-amf --enable-libmfx --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvenc --enable-nvdec --enable-ffnvcodec --enable-gnutls +libavutil 56. 51.100 / 56. 51.100 +libavcodec 58. 91.100 / 58. 91.100 +libavformat 58. 45.100 / 58. 45.100 +libavdevice 58. 10.100 / 58. 10.100 +libavfilter 7. 85.100 / 7. 85.100 +libswscale 5. 7.100 / 5. 7.100 +libswresample 3. 7.100 / 3. 7.100 +libpostproc 55. 7.100 / 55. 7.100"; + public const string FFmpegV431Output = @"ffmpeg version n4.3.1 Copyright (c) 2000-2020 the FFmpeg developers built with gcc 10.1.0 (GCC) configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-avisynth --enable-fontconfig --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libiec61883 --enable-libjack --enable-libmfx --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librav1e --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-nvdec --enable-nvenc --enable-omx --enable-shared --enable-version3 From 3beda02d925c74c7a7083eaee733537f3396ec92 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 25 Jul 2021 00:52:16 +0800 Subject: [PATCH 014/118] add support for cuda tonemap and overlay --- .../MediaEncoding/EncodingHelper.cs | 385 ++++++++++++++---- .../MediaEncoding/FilterOptionType.cs | 23 ++ .../MediaEncoding/IMediaEncoder.cs | 14 +- .../Encoder/EncoderValidator.cs | 102 ++++- .../Encoder/MediaEncoder.cs | 39 +- .../Probing/ProbeResultNormalizer.cs | 17 + 6 files changed, 477 insertions(+), 103 deletions(-) create mode 100644 MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 257cd5df6d..b12cacb6fa 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -40,6 +40,8 @@ namespace MediaBrowser.Controller.MediaEncoding "ConstrainedHigh" }; + private static readonly Version minVersionForCudaOverlay = new Version(4, 4); + public EncodingHelper( IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder) @@ -109,17 +111,41 @@ namespace MediaBrowser.Controller.MediaEncoding private bool IsCudaSupported() { return _mediaEncoder.SupportsHwaccel("cuda") - && _mediaEncoder.SupportsFilter("scale_cuda", null) - && _mediaEncoder.SupportsFilter("yadif_cuda", null); + && _mediaEncoder.SupportsFilter("scale_cuda") + && _mediaEncoder.SupportsFilter("yadif_cuda") + && _mediaEncoder.SupportsFilter("hwupload_cuda"); } - private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + private bool IsOpenclTonemappingSupported(EncodingJobInfo state, EncodingOptions options) { var videoStream = state.VideoStream; - return IsColorDepth10(state) + if (videoStream == null) + { + return false; + } + + return (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + && IsColorDepth10(state) && _mediaEncoder.SupportsHwaccel("opencl") - && options.EnableTonemapping - && string.Equals(videoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase); + && _mediaEncoder.SupportsFilter("tonemap_opencl") + && options.EnableTonemapping; + } + + private bool IsCudaTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + { + var videoStream = state.VideoStream; + if (videoStream == null) + { + return false; + } + + return (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + && IsColorDepth10(state) + && _mediaEncoder.SupportsHwaccel("cuda") + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName) + && options.EnableTonemapping; } private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options) @@ -135,23 +161,25 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return IsColorDepth10(state) + return string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + && IsColorDepth10(state) && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.SupportsHwaccel("vaapi") - && options.EnableVppTonemapping - && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase); + && _mediaEncoder.SupportsFilter("tonemap_vaapi") + && options.EnableVppTonemapping; } // Hybrid VPP tonemapping for QSV with VAAPI if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return IsColorDepth10(state) + return string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + && IsColorDepth10(state) && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.SupportsHwaccel("vaapi") + && _mediaEncoder.SupportsFilter("tonemap_vaapi") && _mediaEncoder.SupportsHwaccel("qsv") - && options.EnableVppTonemapping - && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase); + && options.EnableVppTonemapping; } // Native VPP tonemapping may come to QSV in the future. @@ -489,11 +517,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Gets the input argument. /// - public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) + public string GetInputArgument(EncodingJobInfo state, EncodingOptions options) { var arg = new StringBuilder(); - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; - var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; + var outputVideoCodec = GetVideoEncoder(state, options) ?? string.Empty; + var isWindows = OperatingSystem.IsWindows(); + var isLinux = OperatingSystem.IsLinux(); + var isMacOS = OperatingSystem.IsMacOS(); var isSwDecoder = string.IsNullOrEmpty(videoDecoder); var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; @@ -502,26 +533,24 @@ namespace MediaBrowser.Controller.MediaEncoding var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); - var isWindows = OperatingSystem.IsWindows(); - var isLinux = OperatingSystem.IsLinux(); - var isMacOS = OperatingSystem.IsMacOS(); - var isTonemappingSupported = IsTonemappingSupported(state, encodingOptions); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, encodingOptions); + var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); if (!IsCopyCodec(outputVideoCodec)) { if (state.IsVideoRequest && _mediaEncoder.SupportsHwaccel("vaapi") - && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { if (isVaapiDecoder) { - if (isTonemappingSupported && !isVppTonemappingSupported) + if (isOpenclTonemappingSupported && !isVppTonemappingSupported) { arg.Append("-init_hw_device vaapi=va:") - .Append(encodingOptions.VaapiDevice) - .Append(' ') - .Append("-init_hw_device opencl=ocl@va ") + .Append(options.VaapiDevice) + .Append(" -init_hw_device opencl=ocl@va ") .Append("-hwaccel_device va ") .Append("-hwaccel_output_format vaapi ") .Append("-filter_hw_device ocl "); @@ -530,14 +559,14 @@ namespace MediaBrowser.Controller.MediaEncoding { arg.Append("-hwaccel_output_format vaapi ") .Append("-vaapi_device ") - .Append(encodingOptions.VaapiDevice) + .Append(options.VaapiDevice) .Append(' '); } } else if (!isVaapiDecoder && isVaapiEncoder) { arg.Append("-vaapi_device ") - .Append(encodingOptions.VaapiDevice) + .Append(options.VaapiDevice) .Append(' '); } @@ -545,7 +574,7 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -581,9 +610,8 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isVaapiDecoder && isVppTonemappingSupported) { arg.Append("-init_hw_device vaapi=va:") - .Append(encodingOptions.VaapiDevice) - .Append(' ') - .Append("-init_hw_device qsv@va ") + .Append(options.VaapiDevice) + .Append(" -init_hw_device qsv@va ") .Append("-hwaccel_output_format vaapi "); } @@ -592,7 +620,7 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) + && string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecDecoder) { // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562 @@ -600,22 +628,31 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) - && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder)) - || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) - && (isD3d11vaDecoder || isSwDecoder)))) + && ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) + && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder)))) + { + if (!isCudaTonemappingSupported && isOpenclTonemappingSupported) + { + arg.Append("-init_hw_device opencl=ocl:") + .Append(options.OpenclDevice) + .Append(" -filter_hw_device ocl "); + } + } + + if (state.IsVideoRequest + && string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) + && (isD3d11vaDecoder || isSwDecoder)) { - if (isTonemappingSupported) + if (isOpenclTonemappingSupported) { arg.Append("-init_hw_device opencl=ocl:") - .Append(encodingOptions.OpenclDevice) - .Append(' ') - .Append("-filter_hw_device ocl "); + .Append(options.OpenclDevice) + .Append(" -filter_hw_device ocl "); } } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + && string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) { arg.Append("-hwaccel videotoolbox "); } @@ -1991,14 +2028,18 @@ namespace MediaBrowser.Controller.MediaEncoding var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); - var isTonemappingSupported = IsTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + + var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); // Tonemapping and burn-in graphical subtitles requires overlay_vaapi. // But it's still in ffmpeg mailing list. Disable it for now. - if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported) + if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported) { return GetOutputSizeParam(state, options, outputVideoCodec); } @@ -2024,13 +2065,22 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(videoSizeParam) && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { - // For QSV, feed it into hardware encoder now + // upload graphical subtitle to QSV if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))) { videoSizeParam += ",hwupload=extra_hw_frames=64"; } } + + if (!string.IsNullOrEmpty(videoSizeParam)) + { + // upload graphical subtitle to cuda + if (isNvdecDecoder && isNvencEncoder && isCudaOverlaySupported && isCudaFormatConversionSupported) + { + videoSizeParam += ",hwupload_cuda"; + } + } } var mapPrefix = state.SubtitleStream.IsExternal ? @@ -2043,9 +2093,9 @@ namespace MediaBrowser.Controller.MediaEncoding // Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference) // Always put the scaler before the overlay for better performance - var retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""; + var retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; // When the input may or may not be hardware VAAPI decodable if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) @@ -2056,9 +2106,9 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first @@ -2071,9 +2121,9 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""; + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; } else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) @@ -2090,16 +2140,25 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (isLinux) { - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""; + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""; } } else if (isNvdecDecoder && isNvencEncoder) { - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""; + if (isCudaOverlaySupported && isCudaFormatConversionSupported) + { + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]scale_cuda=format=yuv420p[base];[base][sub]overlay_cuda\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_cuda\""; + } + else + { + retStr = !outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""; + } } return string.Format( @@ -2196,11 +2255,11 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase); var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase); var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); - var isTonemappingSupported = IsTonemappingSupported(state, options); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); - var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)) + var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported)) || (isTonemappingSupportedOnQsv && isVppTonemappingSupported); var outputPixFmt = "format=nv12"; @@ -2251,15 +2310,23 @@ namespace MediaBrowser.Controller.MediaEncoding var outputWidth = width.Value; var outputHeight = height.Value; - var isTonemappingSupported = IsTonemappingSupported(state, options); + var isNvencEncoder = videoEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase); - var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")"); + var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var outputPixFmt = string.Empty; if (isCudaFormatConversionSupported) { - outputPixFmt = "format=nv12"; - if (isTonemappingSupported && isTonemappingSupportedOnNvenc) + outputPixFmt = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder) + ? "format=yuv420p" + : "format=nv12"; + if ((isOpenclTonemappingSupported || isCudaTonemappingSupported) + && isTonemappingSupportedOnNvenc) { outputPixFmt = "format=p010"; } @@ -2525,16 +2592,21 @@ namespace MediaBrowser.Controller.MediaEncoding var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase); var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); + var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase); var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1; var isLinux = OperatingSystem.IsLinux(); var isColorDepth10 = IsColorDepth10(state); - var isTonemappingSupported = IsTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); - var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder); + + var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder); var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); + var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -2546,19 +2618,25 @@ namespace MediaBrowser.Controller.MediaEncoding var isScalingInAdvance = false; var isCudaDeintInAdvance = false; var isHwuploadCudaRequired = false; + var isNoTonemapFilterApplied = true; var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); // Add OpenCL tonemapping filter for NVENC/AMF/VAAPI. - if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported)) + if ((isTonemappingSupportedOnNvenc && !isCudaTonemappingSupported) || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported)) { - // Currently only with the use of NVENC decoder can we get a decent performance. - // Currently only the HEVC/H265 format is supported with NVDEC decoder. // NVIDIA Pascal and Turing or higher are recommended. // AMD Polaris and Vega or higher are recommended. // Intel Kaby Lake or newer is required. - if (isTonemappingSupported) + if (isOpenclTonemappingSupported) { + isNoTonemapFilterApplied = false; + var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer); + if (!string.IsNullOrEmpty(inputHdrParams)) + { + filters.Add(inputHdrParams); + } + var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; if (options.TonemappingParam != 0) @@ -2630,7 +2708,11 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwdownload,format=p010"); } - if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder) + if (isNvdecDecoder + || isCuvidHevcDecoder + || isCuvidVp9Decoder + || isSwDecoder + || isD3d11vaDecoder) { // Upload the HDR10 or HLG data to the OpenCL device, // use tonemap_opencl filter for tone mapping, @@ -2638,6 +2720,14 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwupload"); } + // Fallback to hable if bt2390 is chosen but not supported in tonemap_opencl. + var isBt2390SupportedInOpenclTonemap = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390); + if (string.Equals(options.TonemappingAlgorithm, "bt2390", StringComparison.OrdinalIgnoreCase) + && !isBt2390SupportedInOpenclTonemap) + { + options.TonemappingAlgorithm = "hable"; + } + filters.Add( string.Format( CultureInfo.InvariantCulture, @@ -2649,7 +2739,11 @@ namespace MediaBrowser.Controller.MediaEncoding options.TonemappingParam, options.TonemappingRange)); - if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder) + if (isNvdecDecoder + || isCuvidHevcDecoder + || isCuvidVp9Decoder + || isSwDecoder + || isD3d11vaDecoder) { filters.Add("hwdownload"); filters.Add("format=nv12"); @@ -2665,12 +2759,18 @@ namespace MediaBrowser.Controller.MediaEncoding // Reverse the data route from opencl to vaapi. filters.Add("hwmap=derive_device=vaapi:reverse=1"); } + + var outputSdrParams = GetOutputSdrParams(options.TonemappingRange); + if (!string.IsNullOrEmpty(outputSdrParams)) + { + filters.Add(outputSdrParams); + } } } // When the input may or may not be hardware VAAPI decodable. if ((isVaapiH264Encoder || isVaapiHevcEncoder) - && !(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))) + && !(isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported))) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); @@ -2778,6 +2878,61 @@ namespace MediaBrowser.Controller.MediaEncoding request.MaxHeight)); } + // Add Cuda tonemapping filter. + if (isNvdecDecoder && isCudaTonemappingSupported) + { + isNoTonemapFilterApplied = false; + var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer); + if (!string.IsNullOrEmpty(inputHdrParams)) + { + filters.Add(inputHdrParams); + } + + var parameters = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder) + ? "tonemap_cuda=format=yuv420p:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}" + : "tonemap_cuda=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}"; + + if (options.TonemappingParam != 0) + { + parameters += ":param={3}"; + } + + if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) + { + parameters += ":range={4}"; + } + + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + parameters, + options.TonemappingAlgorithm, + options.TonemappingPeak, + options.TonemappingDesat, + options.TonemappingParam, + options.TonemappingRange)); + + if (isLibX264Encoder + || isLibX265Encoder + || hasTextSubs + || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)) + { + if (isNvencEncoder) + { + isHwuploadCudaRequired = true; + } + + filters.Add("hwdownload"); + filters.Add("format=nv12"); + } + + var outputSdrParams = GetOutputSdrParams(options.TonemappingRange); + if (!string.IsNullOrEmpty(outputSdrParams)) + { + filters.Add(outputSdrParams); + } + } + // Add VPP tonemapping filter for VAAPI. // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options. if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv) @@ -2787,10 +2942,10 @@ namespace MediaBrowser.Controller.MediaEncoding } // Another case is when using Nvenc decoder. - if (isNvdecDecoder && !isTonemappingSupported) + if (isNvdecDecoder && !isOpenclTonemappingSupported && !isCudaTonemappingSupported) { var codec = videoStream.Codec; - var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")"); + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); // Assert 10-bit hardware decodable if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) @@ -2799,7 +2954,10 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isCudaFormatConversionSupported) { - if (isLibX264Encoder || isLibX265Encoder || hasSubs) + if (isLibX264Encoder + || isLibX265Encoder + || hasTextSubs + || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)) { if (isNvencEncoder) { @@ -2826,7 +2984,11 @@ namespace MediaBrowser.Controller.MediaEncoding } // Assert 8-bit hardware decodable - else if (!isColorDepth10 && (isLibX264Encoder || isLibX265Encoder || hasSubs)) + else if (!isColorDepth10 + && (isLibX264Encoder + || isLibX265Encoder + || hasTextSubs + || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))) { if (isNvencEncoder) { @@ -2847,7 +3009,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // Convert hw context from ocl to va. // For tonemapping and text subs burn-in. - if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported) + if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported) { filters.Add("scale_vaapi"); } @@ -2893,6 +3055,17 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwupload_cuda"); } + // If no tonemap filter is applied, + // tag the video range as SDR to prevent the encoder from encoding HDR video. + if (isNoTonemapFilterApplied) + { + var outputSdrParams = GetOutputSdrParams(null); + if (!string.IsNullOrEmpty(outputSdrParams)) + { + filters.Add(outputSdrParams); + } + } + var output = string.Empty; if (filters.Count > 0) { @@ -2905,6 +3078,36 @@ namespace MediaBrowser.Controller.MediaEncoding return output; } + public static string GetInputHdrParams(string colorTransfer) + { + if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + { + // HLG + return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc"; + } + else + { + // HDR10 + return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc"; + } + } + + public static string GetOutputSdrParams(string tonemappingRange) + { + // SDR + if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)) + { + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv"; + } + + if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase)) + { + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc"; + } + + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709"; + } + /// /// Gets the number of threads. /// @@ -3371,8 +3574,13 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && IsVppTonemappingSupported(state, encodingOptions)) { - // Since tonemap_vaapi only support HEVC for now, no need to check the codec again. - return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); + var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; + var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + if (isQsvEncoder) + { + // Since tonemap_vaapi only support HEVC for now, no need to check the codec again. + return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); + } } if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) @@ -3895,6 +4103,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (videoStream != null) { + if (videoStream.BitDepth.HasValue) + { + return videoStream.BitDepth.Value == 10; + } + if (!string.IsNullOrEmpty(videoStream.PixelFormat)) { result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase); @@ -3914,12 +4127,6 @@ namespace MediaBrowser.Controller.MediaEncoding return true; } } - - result = (videoStream.BitDepth ?? 8) == 10; - if (result) - { - return true; - } } return result; diff --git a/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs new file mode 100644 index 0000000000..7ce707b19e --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Controller.MediaEncoding +{ + /// + /// Enum FilterOptionType. + /// + public enum FilterOptionType + { + /// + /// The scale_cuda_format. + /// + ScaleCudaFormat = 0, + + /// + /// The tonemap_cuda_name. + /// + TonemapCudaName = 1, + + /// + /// The tonemap_opencl_bt2390. + /// + TonemapOpenclBt2390 = 2 + } +} diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 76a9fd7c74..31913acefd 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -55,9 +55,21 @@ namespace MediaBrowser.Controller.MediaEncoding /// Whether given filter is supported. /// /// The filter. + /// true if the filter is supported, false otherwise. + bool SupportsFilter(string filter); + + /// + /// Whether filter is supported with the given option. + /// /// The option. /// true if the filter is supported, false otherwise. - bool SupportsFilter(string filter, string option); + bool SupportsFilterWithOption(FilterOptionType option); + + /// + /// Get the version of media encoder. + /// + /// The version of media encoder. + Version GetMediaEncoderVersion(); /// /// Extracts the audio image. diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index f782e65bd1..1ec159c9a4 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -89,6 +89,24 @@ namespace MediaBrowser.MediaEncoding.Encoder "hevc_videotoolbox" }; + private static readonly string[] _requiredFilters = new[] + { + "scale_cuda", + "yadif_cuda", + "hwupload_cuda", + "overlay_cuda", + "tonemap_cuda", + "tonemap_opencl", + "tonemap_vaapi", + }; + + private static readonly IReadOnlyDictionary _filterOptionsDict = new Dictionary + { + { 0, new string[] { "scale_cuda", "Output format (default \"same\")" } }, + { 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } }, + { 2, new string[] { "tonemap_opencl", "bt2390" } } + }; + // These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below private static readonly IReadOnlyDictionary _ffmpegMinimumLibraryVersions = new Dictionary { @@ -156,7 +174,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Work out what the version under test is - var version = GetFFmpegVersion(versionOutput); + var version = GetFFmpegVersionInternal(versionOutput); _logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown"); @@ -200,6 +218,34 @@ namespace MediaBrowser.MediaEncoding.Encoder public IEnumerable GetHwaccels() => GetHwaccelTypes(); + public IEnumerable GetFilters() => GetFFmpegFilters(); + + public IDictionary GetFiltersWithOption() => GetFFmpegFiltersWithOption(); + + public Version? GetFFmpegVersion() + { + string output; + try + { + output = GetProcessOutput(_encoderPath, "-version"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error validating encoder"); + return null; + } + + if (string.IsNullOrWhiteSpace(output)) + { + _logger.LogError("FFmpeg validation: The process returned no result"); + return null; + } + + _logger.LogDebug("ffmpeg output: {Output}", output); + + return GetFFmpegVersionInternal(output); + } + /// /// Using the output from "ffmpeg -version" work out the FFmpeg version. /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy @@ -208,7 +254,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// The output from "ffmpeg -version". /// The FFmpeg version. - internal Version? GetFFmpegVersion(string output) + internal Version? GetFFmpegVersionInternal(string output) { // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)"); @@ -297,9 +343,9 @@ namespace MediaBrowser.MediaEncoding.Encoder return found; } - public bool CheckFilter(string filter, string option) + public bool CheckFilterWithOption(string filter, string option) { - if (string.IsNullOrEmpty(filter)) + if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option)) { return false; } @@ -317,11 +363,6 @@ namespace MediaBrowser.MediaEncoding.Encoder if (output.Contains("Filter " + filter, StringComparison.Ordinal)) { - if (string.IsNullOrEmpty(option)) - { - return true; - } - return output.Contains(option, StringComparison.Ordinal); } @@ -362,6 +403,49 @@ namespace MediaBrowser.MediaEncoding.Encoder return found; } + private IEnumerable GetFFmpegFilters() + { + string output; + try + { + output = GetProcessOutput(_encoderPath, "-filters"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error detecting available filters"); + return Enumerable.Empty(); + } + + if (string.IsNullOrWhiteSpace(output)) + { + return Enumerable.Empty(); + } + + var found = Regex + .Matches(output, @"^\s\S{3}\s(?[\w|-]+)\s+.+$", RegexOptions.Multiline) + .Cast() + .Select(x => x.Groups["filter"].Value) + .Where(x => _requiredFilters.Contains(x)); + + _logger.LogInformation("Available filters: {Filters}", found); + + return found; + } + + private IDictionary GetFFmpegFiltersWithOption() + { + IDictionary dict = new Dictionary(); + for (int i = 0; i < _filterOptionsDict.Count; i++) + { + if (_filterOptionsDict.TryGetValue(i, out var val) && val.Length == 2) + { + dict.Add(i, CheckFilterWithOption(val[0], val[1])); + } + } + + return dict; + } + private string GetProcessOutput(string path, string arguments) { using (var process = new Process() diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 412a953216..238627e96e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -66,7 +66,10 @@ namespace MediaBrowser.MediaEncoding.Encoder private List _encoders = new List(); private List _decoders = new List(); private List _hwaccels = new List(); + private List _filters = new List(); + private IDictionary _filtersWithOption = new Dictionary(); + private Version _ffmpegVersion = null; private string _ffmpegPath = string.Empty; private string _ffprobePath; private int threads; @@ -130,7 +133,11 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableDecoders(validator.GetDecoders()); SetAvailableEncoders(validator.GetEncoders()); + SetAvailableFilters(validator.GetFilters()); + SetAvailableFiltersWithOption(validator.GetFiltersWithOption()); SetAvailableHwaccels(validator.GetHwaccels()); + SetMediaEncoderVersion(validator); + threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null); } @@ -278,6 +285,21 @@ namespace MediaBrowser.MediaEncoding.Encoder _hwaccels = list.ToList(); } + public void SetAvailableFilters(IEnumerable list) + { + _filters = list.ToList(); + } + + public void SetAvailableFiltersWithOption(IDictionary dict) + { + _filtersWithOption = dict; + } + + public void SetMediaEncoderVersion(EncoderValidator validator) + { + _ffmpegVersion = validator.GetFFmpegVersion(); + } + public bool SupportsEncoder(string encoder) { return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase); @@ -293,17 +315,26 @@ namespace MediaBrowser.MediaEncoding.Encoder return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase); } - public bool SupportsFilter(string filter, string option) + public bool SupportsFilter(string filter) { - if (_ffmpegPath != null) + return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase); + } + + public bool SupportsFilterWithOption(FilterOptionType option) + { + if (_filtersWithOption.TryGetValue((int)option, out var val)) { - var validator = new EncoderValidator(_logger, _ffmpegPath); - return validator.CheckFilter(filter, option); + return val; } return false; } + public Version GetMediaEncoderVersion() + { + return _ffmpegVersion; + } + public bool CanEncodeToAudioCodec(string codec) { if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index c9ad3c41eb..d6799a170f 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -739,6 +739,23 @@ namespace MediaBrowser.MediaEncoding.Probing stream.BitDepth = streamInfo.BitsPerRawSample; } + if (!stream.BitDepth.HasValue) + { + if (!string.IsNullOrEmpty(streamInfo.PixelFormat) + && streamInfo.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase)) + { + stream.BitDepth = 10; + } + + if (!string.IsNullOrEmpty(streamInfo.Profile) + && (streamInfo.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) + || streamInfo.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase) + || streamInfo.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase))) + { + stream.BitDepth = 10; + } + } + // stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || // string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) || // string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase); From 19e3c38fa8766e1dccb21d80224ae87cecf42df4 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Mon, 26 Jul 2021 03:09:44 +0800 Subject: [PATCH 015/118] Apply suggestions from code review Co-authored-by: Claus Vium --- .../MediaEncoding/EncodingHelper.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b12cacb6fa..249c6f46e1 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -124,12 +124,12 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - return (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + return options.EnableTonemapping + && (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) && IsColorDepth10(state) && _mediaEncoder.SupportsHwaccel("opencl") - && _mediaEncoder.SupportsFilter("tonemap_opencl") - && options.EnableTonemapping; + && _mediaEncoder.SupportsFilter("tonemap_opencl"); } private bool IsCudaTonemappingSupported(EncodingJobInfo state, EncodingOptions options) @@ -140,12 +140,12 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - return (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + return options.EnableTonemapping + && (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) && IsColorDepth10(state) && _mediaEncoder.SupportsHwaccel("cuda") - && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName) - && options.EnableTonemapping; + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName); } private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options) @@ -161,25 +161,25 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + return options.EnableVppTonemapping + && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) && IsColorDepth10(state) && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.SupportsHwaccel("vaapi") - && _mediaEncoder.SupportsFilter("tonemap_vaapi") - && options.EnableVppTonemapping; + && _mediaEncoder.SupportsFilter("tonemap_vaapi"); } // Hybrid VPP tonemapping for QSV with VAAPI if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + return options.EnableVppTonemapping + && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) && IsColorDepth10(state) && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.SupportsHwaccel("vaapi") && _mediaEncoder.SupportsFilter("tonemap_vaapi") - && _mediaEncoder.SupportsHwaccel("qsv") - && options.EnableVppTonemapping; + && _mediaEncoder.SupportsHwaccel("qsv"); } // Native VPP tonemapping may come to QSV in the future. @@ -2155,7 +2155,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else { - retStr = !outputSizeParam.IsEmpty + retStr = outputSizeParam.IsEmpty ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"" : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""; } From 4e9fbabef25a73a3bfed255f2cb586817ec83bb2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 3 Aug 2021 17:54:55 +0200 Subject: [PATCH 016/118] Enable nullable for DlnaManager --- Emby.Dlna/DlnaManager.cs | 68 +++++++++++--------- MediaBrowser.Controller/Dlna/IDlnaManager.cs | 8 +-- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index b08f7590d4..af70793ccf 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -1,7 +1,4 @@ -#nullable disable - #pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -96,12 +93,14 @@ namespace Emby.Dlna } } + /// public DeviceProfile GetDefaultProfile() { return new DefaultProfile(); } - public DeviceProfile GetProfile(DeviceIdentification deviceInfo) + /// + public DeviceProfile? GetProfile(DeviceIdentification deviceInfo) { if (deviceInfo == null) { @@ -111,13 +110,13 @@ namespace Emby.Dlna var profile = GetProfiles() .FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification)); - if (profile != null) + if (profile == null) { - _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name); + LogUnmatchedProfile(deviceInfo); } else { - LogUnmatchedProfile(deviceInfo); + _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name); } return profile; @@ -187,7 +186,8 @@ namespace Emby.Dlna } } - public DeviceProfile GetProfile(IHeaderDictionary headers) + /// + public DeviceProfile? GetProfile(IHeaderDictionary headers) { if (headers == null) { @@ -195,15 +195,13 @@ namespace Emby.Dlna } var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification)); - - if (profile != null) + if (profile == null) { - _logger.LogDebug("Found matching device profile: {0}", profile.Name); + _logger.LogDebug("No matching device profile found. {@Headers}", headers); } else { - var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value))); - _logger.LogDebug("No matching device profile found. {0}", headerString); + _logger.LogDebug("Found matching device profile: {0}", profile.Name); } return profile; @@ -253,19 +251,19 @@ namespace Emby.Dlna return xmlFies .Select(i => ParseProfileFile(i, type)) .Where(i => i != null) - .ToList(); + .ToList()!; // We just filtered out all the nulls } catch (IOException) { - return new List(); + return Array.Empty(); } } - private DeviceProfile ParseProfileFile(string path, DeviceProfileType type) + private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type) { lock (_profiles) { - if (_profiles.TryGetValue(path, out Tuple profileTuple)) + if (_profiles.TryGetValue(path, out Tuple? profileTuple)) { return profileTuple.Item2; } @@ -293,7 +291,8 @@ namespace Emby.Dlna } } - public DeviceProfile GetProfile(string id) + /// + public DeviceProfile? GetProfile(string id) { if (string.IsNullOrEmpty(id)) { @@ -322,6 +321,7 @@ namespace Emby.Dlna } } + /// public IEnumerable GetProfileInfos() { return GetProfileInfosInternal().Select(i => i.Info); @@ -329,17 +329,14 @@ namespace Emby.Dlna private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type) { - return new InternalProfileInfo - { - Path = file.FullName, - - Info = new DeviceProfileInfo + return new InternalProfileInfo( + new DeviceProfileInfo { Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture), Name = _fileSystem.GetFileNameWithoutExtension(file), Type = type - } - }; + }, + file.FullName); } private async Task ExtractSystemProfilesAsync() @@ -359,7 +356,8 @@ namespace Emby.Dlna systemProfilesPath, Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length)); - using (var stream = _assembly.GetManifestResourceStream(name)) + // The stream should exist as we just got its name from GetManifestResourceNames + using (var stream = _assembly.GetManifestResourceStream(name)!) { var fileInfo = _fileSystem.GetFileInfo(path); @@ -380,6 +378,7 @@ namespace Emby.Dlna Directory.CreateDirectory(UserProfilesPath); } + /// public void DeleteProfile(string id) { var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase)); @@ -397,6 +396,7 @@ namespace Emby.Dlna } } + /// public void CreateProfile(DeviceProfile profile) { profile = ReserializeProfile(profile); @@ -412,6 +412,7 @@ namespace Emby.Dlna SaveProfile(profile, path, DeviceProfileType.User); } + /// public void UpdateProfile(DeviceProfile profile) { profile = ReserializeProfile(profile); @@ -470,9 +471,11 @@ namespace Emby.Dlna var json = JsonSerializer.Serialize(profile, _jsonOptions); - return JsonSerializer.Deserialize(json, _jsonOptions); + // Output can't be null if the input isn't null + return JsonSerializer.Deserialize(json, _jsonOptions)!; } + /// public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) { var profile = GetDefaultProfile(); @@ -482,6 +485,7 @@ namespace Emby.Dlna return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml(); } + /// public ImageStream GetIcon(string filename) { var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase) @@ -499,9 +503,15 @@ namespace Emby.Dlna private class InternalProfileInfo { - internal DeviceProfileInfo Info { get; set; } + internal InternalProfileInfo(DeviceProfileInfo info, string path) + { + Info = info; + Path = path; + } + + internal DeviceProfileInfo Info { get; } - internal string Path { get; set; } + internal string Path { get; } } } diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index b51dc255ce..a64919700d 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System.Collections.Generic; @@ -22,7 +20,7 @@ namespace MediaBrowser.Controller.Dlna /// /// The headers. /// DeviceProfile. - DeviceProfile GetProfile(IHeaderDictionary headers); + DeviceProfile? GetProfile(IHeaderDictionary headers); /// /// Gets the default profile. @@ -53,14 +51,14 @@ namespace MediaBrowser.Controller.Dlna /// /// The identifier. /// DeviceProfile. - DeviceProfile GetProfile(string id); + DeviceProfile? GetProfile(string id); /// /// Gets the profile. /// /// The device information. /// DeviceProfile. - DeviceProfile GetProfile(DeviceIdentification deviceInfo); + DeviceProfile? GetProfile(DeviceIdentification deviceInfo); /// /// Gets the server description XML. From d4f09c6c9b142081064c4008bc1e84fb17c81ad8 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Wed, 4 Aug 2021 16:25:17 +0800 Subject: [PATCH 017/118] Apply suggestions from code review Co-authored-by: Claus Vium --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 249c6f46e1..3f90de3186 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3575,7 +3575,7 @@ namespace MediaBrowser.Controller.MediaEncoding && IsVppTonemappingSupported(state, encodingOptions)) { var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; - var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + var isQsvEncoder = outputVideoCodec.Contains("qsv", StringComparison.OrdinalIgnoreCase); if (isQsvEncoder) { // Since tonemap_vaapi only support HEVC for now, no need to check the codec again. From d212b6fb08fc8d45008499f3dfce33f59bb425e3 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Wed, 4 Aug 2021 06:24:25 -0600 Subject: [PATCH 018/118] Clean test --- .../BaseItem/BaseItemKindTests.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs index f3e7e208ac..061e81f30a 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs @@ -25,14 +25,9 @@ namespace Jellyfin.Server.Implementations.Tests.BaseItem public void GetBaseKindEnumTest(Type baseItemDescendantType) { var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes); - - Assert.NotNull(defaultConstructor); - if (defaultConstructor != null) - { - var instance = (MediaBrowser.Controller.Entities.BaseItem)defaultConstructor.Invoke(null); - var exception = Record.Exception(() => instance.GetBaseItemKind()); - Assert.Null(exception); - } + var instance = (MediaBrowser.Controller.Entities.BaseItem)defaultConstructor!.Invoke(null); + var exception = Record.Exception(() => instance.GetBaseItemKind()); + Assert.Null(exception); } private class GetBaseItemDescendants : IEnumerable From 442dc10aaccf153873fd711dc80ee55052fc2063 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 4 Aug 2021 18:43:26 +0200 Subject: [PATCH 019/118] ApiServiceCollectionExtensions.AddProxyAddresses: Add more tests --- MediaBrowser.Common/Net/IPHost.cs | 42 ++++----- .../ParseNetworkTests.cs | 94 ++++++++++++++----- 2 files changed, 89 insertions(+), 47 deletions(-) diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index d78d7def2b..bdadcbf952 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net; using System.Net.Sockets; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace MediaBrowser.Common.Net { @@ -400,7 +399,7 @@ namespace MediaBrowser.Common.Net if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout))) { _lastResolved = DateTime.UtcNow; - ResolveHostInternal().GetAwaiter().GetResult(); + ResolveHostInternal(); Resolved = true; } @@ -410,30 +409,31 @@ namespace MediaBrowser.Common.Net /// /// Task that looks up a Host name and returns its IP addresses. /// - /// A representing the asynchronous operation. - private async Task ResolveHostInternal() + private void ResolveHostInternal() { - if (!string.IsNullOrEmpty(HostName)) + var hostName = HostName; + if (string.IsNullOrEmpty(hostName)) { - // Resolves the host name - so save a DNS lookup. - if (string.Equals(HostName, "localhost", StringComparison.OrdinalIgnoreCase)) + return; + } + + // Resolves the host name - so save a DNS lookup. + if (string.Equals(hostName, "localhost", StringComparison.OrdinalIgnoreCase)) + { + _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback }; + return; + } + + if (Uri.CheckHostName(hostName) == UriHostNameType.Dns) + { + try { - _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback }; - return; + _addresses = Dns.GetHostAddresses(hostName); } - - if (Uri.CheckHostName(HostName).Equals(UriHostNameType.Dns)) + catch (SocketException ex) { - try - { - IPHostEntry ip = await Dns.GetHostEntryAsync(HostName).ConfigureAwait(false); - _addresses = ip.AddressList; - } - catch (SocketException ex) - { - // Log and then ignore socket errors, as the result value will just be an empty array. - Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message); - } + // Log and then ignore socket errors, as the result value will just be an empty array. + Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message); } } } diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs index 146b16cf94..b92cb165c9 100644 --- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs +++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs @@ -1,10 +1,15 @@ +using System; +using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Net; using System.Text; using Jellyfin.Networking.Configuration; using Jellyfin.Networking.Manager; using Jellyfin.Server.Extensions; using MediaBrowser.Common.Configuration; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; @@ -13,20 +18,63 @@ namespace Jellyfin.Server.Tests { public class ParseNetworkTests { - /// - /// Order of the result has always got to be hosts, then networks. - /// - /// IP4 enabled. - /// IP6 enabled. - /// List to parse. - /// What it should match. + public static TheoryData TestNetworks_TestData() + { + var data = new TheoryData(); + data.Add( + true, + true, + new string[] { "192.168.t", "127.0.0.1", "1234.1232.12.1234" }, + new IPAddress[] { IPAddress.Loopback.MapToIPv6() }, + Array.Empty()); + + data.Add( + true, + false, + new string[] { "192.168.x", "127.0.0.1", "1234.1232.12.1234" }, + new IPAddress[] { IPAddress.Loopback }, + Array.Empty()); + + data.Add( + true, + true, + new string[] { "::1" }, + Array.Empty(), + new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) }); + + data.Add( + false, + false, + new string[] { "localhost" }, + Array.Empty(), + Array.Empty()); + + data.Add( + true, + false, + new string[] { "localhost" }, + new IPAddress[] { IPAddress.Loopback }, + Array.Empty()); + + data.Add( + false, + true, + new string[] { "localhost" }, + Array.Empty(), + new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) }); + + data.Add( + true, + true, + new string[] { "localhost" }, + new IPAddress[] { IPAddress.Loopback.MapToIPv6() }, + new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) }); + return data; + } + [Theory] - // [InlineData(true, true, "192.168.0.0/16,www.yahoo.co.uk", "::ffff:212.82.100.150,::ffff:192.168.0.0/16")] <- fails on Max. www.yahoo.co.uk resolves to a different ip address. - // [InlineData(true, false, "192.168.0.0/16,www.yahoo.co.uk", "212.82.100.150,192.168.0.0/16")] - [InlineData(true, true, "192.168.t,127.0.0.1,1234.1232.12.1234", "::ffff:127.0.0.1")] - [InlineData(true, false, "192.168.x,127.0.0.1,1234.1232.12.1234", "127.0.0.1")] - [InlineData(true, true, "::1", "::1/128")] - public void TestNetworks(bool ip4, bool ip6, string hostList, string match) + [MemberData(nameof(TestNetworks_TestData))] + public void TestNetworks(bool ip4, bool ip6, string[] hostList, IPAddress[] knownProxies, IPNetwork[] knownNetworks) { using var nm = CreateNetworkManager(); @@ -36,31 +84,25 @@ namespace Jellyfin.Server.Tests EnableIPV6 = ip6 }; - var result = match + ","; ForwardedHeadersOptions options = new ForwardedHeadersOptions(); // Need this here as ::1 and 127.0.0.1 are in them by default. options.KnownProxies.Clear(); options.KnownNetworks.Clear(); - ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(','), options); + ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList, options); - var sb = new StringBuilder(); - foreach (var item in options.KnownProxies) + Assert.Equal(knownProxies.Length, options.KnownProxies.Count); + foreach (var item in knownProxies) { - sb.Append(item) - .Append(','); + Assert.True(options.KnownProxies.Contains(item)); } - foreach (var item in options.KnownNetworks) + Assert.Equal(knownNetworks.Length, options.KnownNetworks.Count); + foreach (var item in knownNetworks) { - sb.Append(item.Prefix) - .Append('/') - .Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture)) - .Append(','); + Assert.NotNull(options.KnownNetworks.FirstOrDefault(x => x.Prefix.Equals(item.Prefix) && x.PrefixLength == item.PrefixLength)); } - - Assert.Equal(sb.ToString(), result); } private static IConfigurationManager GetMockConfig(NetworkConfiguration conf) From 60053c7f3d2ea6dbdeae5eecc8a46da23c672cd6 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 4 Aug 2021 18:49:54 +0200 Subject: [PATCH 020/118] Fix log messages --- MediaBrowser.Common/Net/IPHost.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index bdadcbf952..e4f9142508 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -195,7 +195,7 @@ namespace MediaBrowser.Common.Net return res; } - throw new InvalidCastException("Host does not contain a valid value. {host}"); + throw new InvalidCastException($"Host does not contain a valid value. {host}"); } /// @@ -220,7 +220,7 @@ namespace MediaBrowser.Common.Net return res; } - throw new InvalidCastException("Host does not contain a valid value. {host}"); + throw new InvalidCastException($"Host does not contain a valid value. {host}"); } /// @@ -433,7 +433,7 @@ namespace MediaBrowser.Common.Net catch (SocketException ex) { // Log and then ignore socket errors, as the result value will just be an empty array. - Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message); + Debug.WriteLine("GetHostAddresses failed with {Message}.", ex.Message); } } } From 6c42d2345dd575d1f9bfd5293aa0a1eb30b00518 Mon Sep 17 00:00:00 2001 From: MrChip53 Date: Wed, 4 Aug 2021 19:19:03 -0500 Subject: [PATCH 021/118] Properly stream M3U file over http --- .../LiveTv/TunerHosts/BaseTunerHost.cs | 3 +- .../LiveTv/TunerHosts/M3uParser.cs | 33 +++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 5941613cf9..b3524f27c3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -83,9 +83,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts try { var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false); - var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))).ToList(); - list.AddRange(newChannels); + list.AddRange(channels); if (!enableCache) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index c9657f6057..2725f65c3c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -13,7 +13,6 @@ using System.Threading.Tasks; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; -using MediaBrowser.Controller; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.LiveTv; using Microsoft.Extensions.Logging; @@ -44,22 +43,28 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken) { - if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + if (info == null) { - using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url); - if (!string.IsNullOrEmpty(info.UserAgent)) - { - requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent); - } + throw new ArgumentNullException(nameof(info)); + } + + if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + return File.OpenRead(info.Url); + } - var response = await _httpClientFactory.CreateClient(NamedClient.Default) - .SendAsync(requestMessage, cancellationToken) - .ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url); + if (!string.IsNullOrEmpty(info.UserAgent)) + { + requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent); } - return File.OpenRead(info.Url); + var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStreamAsync(cancellationToken); } private async Task> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId) @@ -83,7 +88,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase)) { extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim(); - _logger.LogInformation("Found m3u channel: {0}", extInf); } else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#')) { @@ -99,6 +103,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts channel.Path = trimmedLine; channels.Add(channel); + _logger.LogInformation("Parsed channel: {0}", channel.Name); extInf = string.Empty; } } From 883d28d03df02d2674c82c110071d2aa2baa0918 Mon Sep 17 00:00:00 2001 From: Chris Simoni <57076668+MrChip53@users.noreply.github.com> Date: Fri, 6 Aug 2021 08:10:28 -0500 Subject: [PATCH 022/118] Update Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs Co-authored-by: Claus Vium --- Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 2725f65c3c..a6334c1b3e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts channel.Path = trimmedLine; channels.Add(channel); - _logger.LogInformation("Parsed channel: {0}", channel.Name); + _logger.LogInformation("Parsed channel: {ChannelName}", channel.Name); extInf = string.Empty; } } From eaa5575b232570453315431d5461a736831b88d2 Mon Sep 17 00:00:00 2001 From: MrChip53 Date: Fri, 6 Aug 2021 10:07:50 -0500 Subject: [PATCH 023/118] Add comment --- Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index a6334c1b3e..16ff98a7d8 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -59,6 +59,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent); } + // Set HttpCompletionOption.ResponseHeadersRead to prevent timeouts on larger files var response = await _httpClientFactory.CreateClient(NamedClient.Default) .SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ConfigureAwait(false); From 14a53fe402feb62bef99eaf8100dba822c500e3f Mon Sep 17 00:00:00 2001 From: ankenyr Date: Fri, 6 Aug 2021 23:12:29 -0700 Subject: [PATCH 024/118] Adding tests for AiredEpisodeOrderComparer. --- Jellyfin.sln | 21 +++--- .../AiredEpisodeOrderComparerTests.cs | 74 +++++++++++++++++++ .../Emby.Server.Implementations.Tests.csproj | 27 +++++++ 3 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 tests/Emby.Server.Implementations.Tests/AiredEpisodeOrderComparerTests.cs create mode 100644 tests/Emby.Server.Implementations.Tests/Emby.Server.Implementations.Tests.csproj diff --git a/Jellyfin.sln b/Jellyfin.sln index 4626601c3d..69e3618626 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -77,17 +77,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions", "src\Jellyfin.Extensions\Jellyfin.Extensions.csproj", "{750B8757-BE3D-4F8C-941A-FBAD94904ADA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Extensions", "src\Jellyfin.Extensions\Jellyfin.Extensions.csproj", "{750B8757-BE3D-4F8C-941A-FBAD94904ADA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Server.Implementations.Tests", "tests\Emby.Server.Implementations.Tests\Emby.Server.Implementations.Tests.csproj", "{3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -219,10 +221,6 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -243,6 +241,10 @@ Global {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.Build.0 = Release|Any CPU + {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -263,6 +265,7 @@ Global {A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {750B8757-BE3D-4F8C-941A-FBAD94904ADA} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} {332A5C7A-F907-47CA-910E-BE6F7371B9E0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Emby.Server.Implementations.Tests/AiredEpisodeOrderComparerTests.cs b/tests/Emby.Server.Implementations.Tests/AiredEpisodeOrderComparerTests.cs new file mode 100644 index 0000000000..a13c8a5441 --- /dev/null +++ b/tests/Emby.Server.Implementations.Tests/AiredEpisodeOrderComparerTests.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Emby.Server.Implementations.Sorting; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using Xunit; + +namespace Emby.Server.Implementations.Tests +{ + public class AiredEpisodeOrderComparerTests + { + [Theory] + [ClassData(typeof(EpisodeTestData))] + public void Test1(BaseItem x, BaseItem y, int expected, bool err) + { + var cmp = new AiredEpisodeOrderComparer(); + if (err == true) + { + Assert.Throws(() => cmp.Compare(x, y)); + } + else + { + Assert.Equal(expected, cmp.Compare(x, y)); + if (expected == 1) + { + Assert.Equal(expected * -1, cmp.Compare(y, x)); + } + } + } + + private class EpisodeTestData : IEnumerable + { + public IEnumerator GetEnumerator() + { + // Some Error or "bad" cases + yield return new object?[] { null, new Episode(), 0, true }; + yield return new object?[] { new Episode(), null, 0, true }; + + yield return new object?[] { new Movie(), new Movie(), 0, false }; + yield return new object?[] { new Movie(), new Episode(), 1, false }; + // Good cases + yield return new object?[] { new Episode(), new Episode(), 0, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 0, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 2, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; + // Good Specials + yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 0, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + + // Specials to Episodes + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1, false }; + + yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, 1, false }; + + yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; + + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 0, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 3 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } +} diff --git a/tests/Emby.Server.Implementations.Tests/Emby.Server.Implementations.Tests.csproj b/tests/Emby.Server.Implementations.Tests/Emby.Server.Implementations.Tests.csproj new file mode 100644 index 0000000000..aa5d4bff4b --- /dev/null +++ b/tests/Emby.Server.Implementations.Tests/Emby.Server.Implementations.Tests.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + From 8b4d9339cf73d0d227f76866205b917546c152e0 Mon Sep 17 00:00:00 2001 From: ankenyr Date: Sat, 7 Aug 2021 15:07:12 -0700 Subject: [PATCH 025/118] Changing namespace to be within Jellyfin.Server.Implementations.Tests --- Jellyfin.sln | 7 -- .../Sorting/AiredEpisodeOrderComparerTests.cs | 74 +++++++++++++++++++ 2 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs diff --git a/Jellyfin.sln b/Jellyfin.sln index 69e3618626..1e5e10993f 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -89,8 +89,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Extensions", "src\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Server.Implementations.Tests", "tests\Emby.Server.Implementations.Tests\Emby.Server.Implementations.Tests.csproj", "{3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -241,10 +239,6 @@ Global {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.Build.0 = Release|Any CPU - {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -265,7 +259,6 @@ Global {A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {750B8757-BE3D-4F8C-941A-FBAD94904ADA} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} {332A5C7A-F907-47CA-910E-BE6F7371B9E0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {3FF50D0E-DA00-42B5-8742-55F2EA34C0EA} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs new file mode 100644 index 0000000000..46128f48b4 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Emby.Server.Implementations.Sorting; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Sorting +{ + public class AiredEpisodeOrderComparerTests + { + [Theory] + [ClassData(typeof(EpisodeTestData))] + public void Test1(BaseItem x, BaseItem y, int expected, bool err) + { + var cmp = new AiredEpisodeOrderComparer(); + if (err == true) + { + Assert.Throws(() => cmp.Compare(x, y)); + } + else + { + Assert.Equal(expected, cmp.Compare(x, y)); + if (expected == 1) + { + Assert.Equal(expected * -1, cmp.Compare(y, x)); + } + } + } + + private class EpisodeTestData : IEnumerable + { + public IEnumerator GetEnumerator() + { + // Some Error or "bad" cases + yield return new object?[] { null, new Episode(), 0, true }; + yield return new object?[] { new Episode(), null, 0, true }; + + yield return new object?[] { new Movie(), new Movie(), 0, false }; + yield return new object?[] { new Movie(), new Episode(), 1, false }; + // Good cases + yield return new object?[] { new Episode(), new Episode(), 0, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 0, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 2, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; + // Good Specials + yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 0, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + + // Specials to Episodes + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1, false }; + + yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, 1, false }; + + yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; + + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 0, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 3 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } +} From 8e6436d8dda7640db6a02fd669bbbd03b7c29c03 Mon Sep 17 00:00:00 2001 From: ankenyr Date: Sat, 7 Aug 2021 15:36:23 -0700 Subject: [PATCH 026/118] Removing moved files. --- .../AiredEpisodeOrderComparerTests.cs | 74 ------------------- .../Emby.Server.Implementations.Tests.csproj | 27 ------- 2 files changed, 101 deletions(-) delete mode 100644 tests/Emby.Server.Implementations.Tests/AiredEpisodeOrderComparerTests.cs delete mode 100644 tests/Emby.Server.Implementations.Tests/Emby.Server.Implementations.Tests.csproj diff --git a/tests/Emby.Server.Implementations.Tests/AiredEpisodeOrderComparerTests.cs b/tests/Emby.Server.Implementations.Tests/AiredEpisodeOrderComparerTests.cs deleted file mode 100644 index a13c8a5441..0000000000 --- a/tests/Emby.Server.Implementations.Tests/AiredEpisodeOrderComparerTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Emby.Server.Implementations.Sorting; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using Xunit; - -namespace Emby.Server.Implementations.Tests -{ - public class AiredEpisodeOrderComparerTests - { - [Theory] - [ClassData(typeof(EpisodeTestData))] - public void Test1(BaseItem x, BaseItem y, int expected, bool err) - { - var cmp = new AiredEpisodeOrderComparer(); - if (err == true) - { - Assert.Throws(() => cmp.Compare(x, y)); - } - else - { - Assert.Equal(expected, cmp.Compare(x, y)); - if (expected == 1) - { - Assert.Equal(expected * -1, cmp.Compare(y, x)); - } - } - } - - private class EpisodeTestData : IEnumerable - { - public IEnumerator GetEnumerator() - { - // Some Error or "bad" cases - yield return new object?[] { null, new Episode(), 0, true }; - yield return new object?[] { new Episode(), null, 0, true }; - - yield return new object?[] { new Movie(), new Movie(), 0, false }; - yield return new object?[] { new Movie(), new Episode(), 1, false }; - // Good cases - yield return new object?[] { new Episode(), new Episode(), 0, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 0, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 2, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; - // Good Specials - yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 0, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; - - // Specials to Episodes - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; - - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1, false }; - - yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, 1, false }; - - yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; - - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 0, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 3 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - } -} diff --git a/tests/Emby.Server.Implementations.Tests/Emby.Server.Implementations.Tests.csproj b/tests/Emby.Server.Implementations.Tests/Emby.Server.Implementations.Tests.csproj deleted file mode 100644 index aa5d4bff4b..0000000000 --- a/tests/Emby.Server.Implementations.Tests/Emby.Server.Implementations.Tests.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - net5.0 - - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - From 5eac2675696b7774e212983bf18d56f62ad1a7ba Mon Sep 17 00:00:00 2001 From: ankenyr Date: Sat, 7 Aug 2021 15:55:52 -0700 Subject: [PATCH 027/118] Reverting sln file. --- Jellyfin.sln | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index 1e5e10993f..4626601c3d 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -77,17 +77,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Extensions", "src\Jellyfin.Extensions\Jellyfin.Extensions.csproj", "{750B8757-BE3D-4F8C-941A-FBAD94904ADA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions", "src\Jellyfin.Extensions\Jellyfin.Extensions.csproj", "{750B8757-BE3D-4F8C-941A-FBAD94904ADA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -219,6 +219,10 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU From 579ed5c1fc771f5598747ad3bdfdb5e025aac051 Mon Sep 17 00:00:00 2001 From: Rob Date: Mon, 9 Aug 2021 11:27:40 -0700 Subject: [PATCH 028/118] Update tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs Co-authored-by: Cody Robibero --- .../Sorting/AiredEpisodeOrderComparerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs index 46128f48b4..5646a1bd97 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting Assert.Equal(expected, cmp.Compare(x, y)); if (expected == 1) { - Assert.Equal(expected * -1, cmp.Compare(y, x)); + Assert.Equal(-expected, cmp.Compare(y, x)); } } } From 67f81e8c07d57186e8ef28e9caa40dab552f1ffe Mon Sep 17 00:00:00 2001 From: ankenyr Date: Mon, 9 Aug 2021 11:27:19 -0700 Subject: [PATCH 029/118] Changing test name to be more descrptive. --- .../Sorting/AiredEpisodeOrderComparerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs index 5646a1bd97..d11a4f6b69 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting { [Theory] [ClassData(typeof(EpisodeTestData))] - public void Test1(BaseItem x, BaseItem y, int expected, bool err) + public void AiredEpisodeOrderCompareTest(BaseItem x, BaseItem y, int expected, bool err) { var cmp = new AiredEpisodeOrderComparer(); if (err == true) From 08152d2a981401d803d0c64aba51df3e7891fea6 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 11 Aug 2021 12:50:35 +0200 Subject: [PATCH 030/118] Apply suggestions from code review --- Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index b3524f27c3..de178ad5b1 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -83,8 +83,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts try { var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false); + var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))); - list.AddRange(channels); + list.AddRange(newChannels); if (!enableCache) { From 78bbfca990de552427317260dcdb02f515e2db1c Mon Sep 17 00:00:00 2001 From: ankenyr Date: Wed, 11 Aug 2021 09:56:36 -0700 Subject: [PATCH 031/118] Separating out error cases from good cases. --- .../Sorting/AiredEpisodeOrderComparerTests.cs | 75 +++++++++++-------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs index d11a4f6b69..ff7999612b 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs @@ -11,61 +11,70 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting { public class AiredEpisodeOrderComparerTests { + [Theory] + [ClassData(typeof(EpisodeBadData))] + public void AiredEpisodeOrderCompareErrorTest(BaseItem x, BaseItem y) + { + var cmp = new AiredEpisodeOrderComparer(); + Assert.Throws(() => cmp.Compare(x, y)); + } + [Theory] [ClassData(typeof(EpisodeTestData))] - public void AiredEpisodeOrderCompareTest(BaseItem x, BaseItem y, int expected, bool err) + public void AiredEpisodeOrderCompareTest(BaseItem x, BaseItem y, int expected) { var cmp = new AiredEpisodeOrderComparer(); - if (err == true) + + Assert.Equal(expected, cmp.Compare(x, y)); + if (expected == 1) { - Assert.Throws(() => cmp.Compare(x, y)); + Assert.Equal(-expected, cmp.Compare(y, x)); } - else + } + + private class EpisodeBadData : IEnumerable + { + public IEnumerator GetEnumerator() { - Assert.Equal(expected, cmp.Compare(x, y)); - if (expected == 1) - { - Assert.Equal(-expected, cmp.Compare(y, x)); - } + yield return new object?[] { null, new Episode() }; + yield return new object?[] { new Episode() }; } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } private class EpisodeTestData : IEnumerable { public IEnumerator GetEnumerator() { - // Some Error or "bad" cases - yield return new object?[] { null, new Episode(), 0, true }; - yield return new object?[] { new Episode(), null, 0, true }; - - yield return new object?[] { new Movie(), new Movie(), 0, false }; - yield return new object?[] { new Movie(), new Episode(), 1, false }; + yield return new object?[] { new Movie(), new Movie(), 0 }; + yield return new object?[] { new Movie(), new Episode(), 1 }; // Good cases - yield return new object?[] { new Episode(), new Episode(), 0, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 0, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 2, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode(), new Episode(), 0 }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 0 }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1 }; + yield return new object?[] { new Episode { ParentIndexNumber = 2, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1 }; // Good Specials - yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 0, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 0 }; + yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1 }; // Specials to Episodes - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1 }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1 }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1 }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1 }; + yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 0, false }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 3 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1, false }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1 }, 1 }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1 }; + yield return new object?[] { new Episode { ParentIndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 0 }; + yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 3 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1 }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); From 4c1286fd24139527f1fbd3db23e8d74e4d8da283 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 12 Aug 2021 21:38:54 +0200 Subject: [PATCH 032/118] Address comment --- MediaBrowser.Common/Net/IPHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index e4f9142508..1f125f2b1d 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -348,7 +348,7 @@ namespace MediaBrowser.Common.Net } } - output = output[0..^1]; + output = output[..^1]; if (moreThanOne) { @@ -428,7 +428,7 @@ namespace MediaBrowser.Common.Net { try { - _addresses = Dns.GetHostAddresses(hostName); + _addresses = Dns.GetHostEntry(hostName).AddressList; } catch (SocketException ex) { From 577d665192ab79cdd2f725ca0be0b9948ff5eed3 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Fri, 13 Aug 2021 20:16:05 +0200 Subject: [PATCH 033/118] Move thumb tag parsing to separate method --- .../Parsers/BaseNfoParser.cs | 110 +++++++++--------- 1 file changed, 57 insertions(+), 53 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 2c86f9242d..242a7132b1 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -783,59 +783,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers case "thumb": { - var artType = reader.GetAttribute("aspect"); - var val = reader.ReadElementContentAsString(); - - // skip: - // - empty aspect tag - // - empty uri - // - tag containing '.' because we can't set images for seasons, episodes or movie sets within series or movies - if (string.IsNullOrEmpty(artType) || string.IsNullOrEmpty(val) || artType.Contains('.', StringComparison.Ordinal)) - { - break; - } - - ImageType imageType = GetImageType(artType); - - if (!Uri.TryCreate(val, UriKind.Absolute, out var uri)) - { - Logger.LogError("Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, item.Name); - break; - } - - if (uri.IsFile) - { - // only allow one item of each type - if (itemResult.Images.Any(x => x.Type == imageType)) - { - break; - } - - var fileSystemMetadata = _directoryService.GetFile(val); - // non existing file returns null - if (fileSystemMetadata == null || !fileSystemMetadata.Exists) - { - Logger.LogWarning("Artwork file {Path} specified in nfo file for {ItemName} does not exist.", uri, item.Name); - break; - } - - itemResult.Images.Add(new LocalImageInfo() - { - FileInfo = fileSystemMetadata, - Type = imageType - }); - } - else - { - // only allow one item of each type - if (itemResult.RemoteImages.Any(x => x.type == imageType)) - { - break; - } - - itemResult.RemoteImages.Add((uri.ToString(), imageType)); - } - + FetchThumbNode(reader, itemResult); break; } @@ -858,6 +806,62 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } + private void FetchThumbNode(XmlReader reader, MetadataResult itemResult) + { + var artType = reader.GetAttribute("aspect"); + var val = reader.ReadElementContentAsString(); + + // skip: + // - empty aspect tag + // - empty uri + // - tag containing '.' because we can't set images for seasons, episodes or movie sets within series or movies + if (string.IsNullOrEmpty(artType) || string.IsNullOrEmpty(val) || artType.Contains('.', StringComparison.Ordinal)) + { + return; + } + + ImageType imageType = GetImageType(artType); + + if (!Uri.TryCreate(val, UriKind.Absolute, out var uri)) + { + Logger.LogError("Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, itemResult.Item.Name); + return; + } + + if (uri.IsFile) + { + // only allow one item of each type + if (itemResult.Images.Any(x => x.Type == imageType)) + { + return; + } + + var fileSystemMetadata = _directoryService.GetFile(val); + // non existing file returns null + if (fileSystemMetadata == null || !fileSystemMetadata.Exists) + { + Logger.LogWarning("Artwork file {Path} specified in nfo file for {ItemName} does not exist.", uri, itemResult.Item.Name); + return; + } + + itemResult.Images.Add(new LocalImageInfo() + { + FileInfo = fileSystemMetadata, + Type = imageType + }); + } + else + { + // only allow one item of each type + if (itemResult.RemoteImages.Any(x => x.type == imageType)) + { + return; + } + + itemResult.RemoteImages.Add((uri.ToString(), imageType)); + } + } + private void FetchFromFileInfoNode(XmlReader reader, T item) { reader.MoveToContent(); From 12e58840eb6d7045e6b706580e057f4fb910fc3d Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Fri, 13 Aug 2021 20:33:53 +0200 Subject: [PATCH 034/118] Modify FetchThumbNode method to read children of fanart tag --- .../Parsers/BaseNfoParser.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 242a7132b1..0edb7f43c4 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -787,6 +787,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers break; } + case "fanart": + { + var subtree = reader.ReadSubtree(); + subtree.ReadToDescendant("thumb"); + FetchThumbNode(subtree, itemResult); + break; + } + default: string readerName = reader.Name; if (_validProviderIds.TryGetValue(readerName, out string? providerIdValue)) @@ -811,11 +819,17 @@ namespace MediaBrowser.XbmcMetadata.Parsers var artType = reader.GetAttribute("aspect"); var val = reader.ReadElementContentAsString(); + // artType is null if the thumb node is a child of the fanart tag + // -> set image type to fanart + if (string.IsNullOrWhiteSpace(artType)) + { + artType = "fanart"; + } + // skip: - // - empty aspect tag // - empty uri // - tag containing '.' because we can't set images for seasons, episodes or movie sets within series or movies - if (string.IsNullOrEmpty(artType) || string.IsNullOrEmpty(val) || artType.Contains('.', StringComparison.Ordinal)) + if (string.IsNullOrEmpty(val) || artType.Contains('.', StringComparison.Ordinal)) { return; } From bf441e49b9128024eec57de1122960ad4cde90f7 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Fri, 13 Aug 2021 20:36:14 +0200 Subject: [PATCH 035/118] Add test for fanart tag --- .../Parsers/MovieNfoParserTests.cs | 14 ++++++++ .../Test Data/Fanart.nfo | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Fanart.nfo diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index cbcce73eb6..ef3ca15d5a 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -207,6 +207,20 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers Assert.Equal(id, item.ProviderIds[provider]); } + [Fact] + public void Parse_GivenFileWithFanartTag_Success() + { + var result = new MetadataResult public abstract class BaseItem : IHasProviderIds, IHasLookupInfo, IEquatable { + /// + /// The trailer folder name. + /// + public const string TrailerFolderName = "trailers"; + public const string ThemeSongsFolderName = "theme-music"; + public const string ThemeSongFilename = "theme"; + public const string ThemeVideosFolderName = "backdrops"; + public const string ExtrasFolderName = "extras"; + public const string BehindTheScenesFolderName = "behind the scenes"; + public const string DeletedScenesFolderName = "deleted scenes"; + public const string InterviewFolderName = "interviews"; + public const string SceneFolderName = "scenes"; + public const string SampleFolderName = "samples"; + public const string ShortsFolderName = "shorts"; + public const string FeaturettesFolderName = "featurettes"; + /// /// The supported image extensions. /// @@ -61,38 +77,21 @@ namespace MediaBrowser.Controller.Entities ".ttml" }; - protected BaseItem() - { - Tags = Array.Empty(); - Genres = Array.Empty(); - Studios = Array.Empty(); - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - LockedFields = Array.Empty(); - ImageInfos = Array.Empty(); - ProductionLocations = Array.Empty(); - RemoteTrailers = Array.Empty(); - ExtraIds = Array.Empty(); - } - - public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; - public static char SlugChar = '-'; - /// - /// The trailer folder name. + /// Extra types that should be counted and displayed as "Special Features" in the UI. /// - public const string TrailerFolderName = "trailers"; - public const string ThemeSongsFolderName = "theme-music"; - public const string ThemeSongFilename = "theme"; - public const string ThemeVideosFolderName = "backdrops"; - public const string ExtrasFolderName = "extras"; - public const string BehindTheScenesFolderName = "behind the scenes"; - public const string DeletedScenesFolderName = "deleted scenes"; - public const string InterviewFolderName = "interviews"; - public const string SceneFolderName = "scenes"; - public const string SampleFolderName = "samples"; - public const string ShortsFolderName = "shorts"; - public const string FeaturettesFolderName = "featurettes"; + public static readonly IReadOnlyCollection DisplayExtraTypes = new HashSet + { + Model.Entities.ExtraType.Unknown, + Model.Entities.ExtraType.BehindTheScenes, + Model.Entities.ExtraType.Clip, + Model.Entities.ExtraType.DeletedScene, + Model.Entities.ExtraType.Interview, + Model.Entities.ExtraType.Sample, + Model.Entities.ExtraType.Scene + }; + public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; public static readonly string[] AllExtrasTypesFolderNames = { ExtrasFolderName, @@ -105,6 +104,29 @@ namespace MediaBrowser.Controller.Entities FeaturettesFolderName }; + private string _sortName; + private Guid[] _themeSongIds; + private Guid[] _themeVideoIds; + + private string _forcedSortName; + + private string _name; + + public static char SlugChar = '-'; + + protected BaseItem() + { + Tags = Array.Empty(); + Genres = Array.Empty(); + Studios = Array.Empty(); + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + LockedFields = Array.Empty(); + ImageInfos = Array.Empty(); + ProductionLocations = Array.Empty(); + RemoteTrailers = Array.Empty(); + ExtraIds = Array.Empty(); + } + [JsonIgnore] public Guid[] ThemeSongIds { @@ -194,8 +216,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool SupportsRemoteImageDownloading => true; - private string _name; - /// /// Gets or sets the name. /// @@ -328,12 +348,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool IsHidden => false; - public BaseItem GetOwner() - { - var ownerId = OwnerId; - return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId); - } - /// /// Gets the type of the location. /// @@ -379,13 +393,6 @@ namespace MediaBrowser.Controller.Entities } } - public bool IsPathProtocol(MediaProtocol protocol) - { - var current = PathProtocol; - - return current.HasValue && current.Value == protocol; - } - [JsonIgnore] public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File); @@ -423,35 +430,17 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool EnableAlphaNumericSorting => true; - private List> GetSortChunks(string s1) - { - var list = new List>(); - - int thisMarker = 0; - - while (thisMarker < s1.Length) - { - char thisCh = s1[thisMarker]; + public virtual bool IsHD => Height >= 720; - var thisChunk = new StringBuilder(); - bool isNumeric = char.IsDigit(thisCh); + public bool IsShortcut { get; set; } - while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric) - { - thisChunk.Append(thisCh); - thisMarker++; + public string ShortcutPath { get; set; } - if (thisMarker < s1.Length) - { - thisCh = s1[thisMarker]; - } - } + public int Width { get; set; } - list.Add(new Tuple(thisChunk, isNumeric)); - } + public int Height { get; set; } - return list; - } + public Guid[] ExtraIds { get; set; } /// /// Gets the primary image path. @@ -463,72 +452,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public string PrimaryImagePath => this.GetImagePath(ImageType.Primary); - public virtual bool CanDelete() - { - if (SourceType == SourceType.Channel) - { - return ChannelManager.CanDelete(this); - } - - return IsFileProtocol; - } - - public virtual bool IsAuthorizedToDelete(User user, List allCollectionFolders) - { - if (user.HasPermission(PermissionKind.EnableContentDeletion)) - { - return true; - } - - var allowed = user.GetPreferenceValues(PreferenceKind.EnableContentDeletionFromFolders); - - if (SourceType == SourceType.Channel) - { - return allowed.Contains(ChannelId); - } - else - { - var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders); - - foreach (var folder in collectionFolders) - { - if (allowed.Contains(folder.Id)) - { - return true; - } - } - } - - return false; - } - - public bool CanDelete(User user, List allCollectionFolders) - { - return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders); - } - - public bool CanDelete(User user) - { - var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType().ToList(); - - return CanDelete(user, allCollectionFolders); - } - - public virtual bool CanDownload() - { - return false; - } - - public virtual bool IsAuthorizedToDownload(User user) - { - return user.HasPermission(PermissionKind.EnableContentDownloading); - } - - public bool CanDownload(User user) - { - return CanDownload() && IsAuthorizedToDownload(user); - } - /// /// Gets or sets the date created. /// @@ -548,38 +471,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public DateTime DateLastRefreshed { get; set; } - /// - /// Gets or sets the logger. - /// - public static ILogger Logger { get; set; } - - public static ILibraryManager LibraryManager { get; set; } - - public static IServerConfigurationManager ConfigurationManager { get; set; } - - public static IProviderManager ProviderManager { get; set; } - - public static ILocalizationManager LocalizationManager { get; set; } - - public static IItemRepository ItemRepository { get; set; } - - public static IFileSystem FileSystem { get; set; } - - public static IUserDataManager UserDataManager { get; set; } - - public static IChannelManager ChannelManager { get; set; } - - public static IMediaSourceManager MediaSourceManager { get; set; } - - /// - /// Returns a that represents this instance. - /// - /// A that represents this instance. - public override string ToString() - { - return Name; - } - [JsonIgnore] public bool IsLocked { get; set; } @@ -611,211 +502,87 @@ namespace MediaBrowser.Controller.Entities } } - private string _forcedSortName; - - /// - /// Gets or sets the name of the forced sort. - /// - /// The name of the forced sort. [JsonIgnore] - public string ForcedSortName + public bool EnableMediaSourceDisplay { - get => _forcedSortName; - set + get { - _forcedSortName = value; - _sortName = null; + if (SourceType == SourceType.Channel) + { + return ChannelManager.EnableMediaSourceDisplay(this); + } + + return true; } } - private string _sortName; - private Guid[] _themeSongIds; - private Guid[] _themeVideoIds; + [JsonIgnore] + public Guid ParentId { get; set; } /// - /// Gets or sets the name of the sort. + /// Gets or sets the logger. /// - /// The name of the sort. - [JsonIgnore] - public string SortName - { - get - { - if (_sortName == null) - { - if (!string.IsNullOrEmpty(ForcedSortName)) - { - // Need the ToLower because that's what CreateSortName does - _sortName = ModifySortChunks(ForcedSortName).ToLowerInvariant(); - } - else - { - _sortName = CreateSortName(); - } - } - - return _sortName; - } - - set => _sortName = value; - } - - public string GetInternalMetadataPath() - { - var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath; - - return GetInternalMetadataPath(basePath); - } - - protected virtual string GetInternalMetadataPath(string basePath) - { - if (SourceType == SourceType.Channel) - { - return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); - } - - ReadOnlySpan idString = Id.ToString("N", CultureInfo.InvariantCulture); - - return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString); - } - - /// - /// Creates the name of the sort. - /// - /// System.String. - protected virtual string CreateSortName() - { - if (Name == null) - { - return null; // some items may not have name filled in properly - } - - if (!EnableAlphaNumericSorting) - { - return Name.TrimStart(); - } - - var sortable = Name.Trim().ToLowerInvariant(); - - foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters) - { - sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal); - } - - foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters) - { - sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal); - } - - foreach (var search in ConfigurationManager.Configuration.SortRemoveWords) - { - // Remove from beginning if a space follows - if (sortable.StartsWith(search + " ", StringComparison.Ordinal)) - { - sortable = sortable.Remove(0, search.Length + 1); - } - - // Remove from middle if surrounded by spaces - sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal); - - // Remove from end if followed by a space - if (sortable.EndsWith(" " + search, StringComparison.Ordinal)) - { - sortable = sortable.Remove(sortable.Length - (search.Length + 1)); - } - } + public static ILogger Logger { get; set; } - return ModifySortChunks(sortable); - } + public static ILibraryManager LibraryManager { get; set; } - private string ModifySortChunks(string name) - { - var chunks = GetSortChunks(name); + public static IServerConfigurationManager ConfigurationManager { get; set; } - var builder = new StringBuilder(); + public static IProviderManager ProviderManager { get; set; } - foreach (var chunk in chunks) - { - var chunkBuilder = chunk.Item1; + public static ILocalizationManager LocalizationManager { get; set; } - // This chunk is numeric - if (chunk.Item2) - { - while (chunkBuilder.Length < 10) - { - chunkBuilder.Insert(0, '0'); - } - } + public static IItemRepository ItemRepository { get; set; } - builder.Append(chunkBuilder); - } + public static IFileSystem FileSystem { get; set; } - // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString()); - return builder.ToString().RemoveDiacritics(); - } + public static IUserDataManager UserDataManager { get; set; } - [JsonIgnore] - public bool EnableMediaSourceDisplay - { - get - { - if (SourceType == SourceType.Channel) - { - return ChannelManager.EnableMediaSourceDisplay(this); - } + public static IChannelManager ChannelManager { get; set; } - return true; - } - } + public static IMediaSourceManager MediaSourceManager { get; set; } + /// + /// Gets or sets the name of the forced sort. + /// + /// The name of the forced sort. [JsonIgnore] - public Guid ParentId { get; set; } - - public void SetParent(Folder parent) - { - ParentId = parent == null ? Guid.Empty : parent.Id; - } - - public BaseItem GetParent() - { - var parentId = ParentId; - if (!parentId.Equals(Guid.Empty)) - { - return LibraryManager.GetItemById(parentId); - } - - return null; - } - - public IEnumerable GetParents() + public string ForcedSortName { - var parent = GetParent(); - - while (parent != null) + get => _forcedSortName; + set { - yield return parent; - - parent = parent.GetParent(); + _forcedSortName = value; + _sortName = null; } } /// - /// Finds a parent of a given type. + /// Gets or sets the name of the sort. /// - /// - /// ``0. - public T FindParent() - where T : Folder + /// The name of the sort. + [JsonIgnore] + public string SortName { - foreach (var parent in GetParents()) + get { - if (parent is T item) + if (_sortName == null) { - return item; + if (!string.IsNullOrEmpty(ForcedSortName)) + { + // Need the ToLower because that's what CreateSortName does + _sortName = ModifySortChunks(ForcedSortName).ToLowerInvariant(); + } + else + { + _sortName = CreateSortName(); + } } + + return _sortName; } - return null; + set => _sortName = value; } [JsonIgnore] @@ -948,56 +715,399 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public int? IndexNumber { get; set; } - /// - /// Gets or sets the parent index number. For an episode this could be the season number, or for a song this could be the disc number. - /// - /// The parent index number. - [JsonIgnore] - public int? ParentIndexNumber { get; set; } + /// + /// Gets or sets the parent index number. For an episode this could be the season number, or for a song this could be the disc number. + /// + /// The parent index number. + [JsonIgnore] + public int? ParentIndexNumber { get; set; } + + [JsonIgnore] + public virtual bool HasLocalAlternateVersions => false; + + [JsonIgnore] + public string OfficialRatingForComparison + { + get + { + var officialRating = OfficialRating; + if (!string.IsNullOrEmpty(officialRating)) + { + return officialRating; + } + + var parent = DisplayParent; + if (parent != null) + { + return parent.OfficialRatingForComparison; + } + + return null; + } + } + + [JsonIgnore] + public string CustomRatingForComparison + { + get + { + var customRating = CustomRating; + if (!string.IsNullOrEmpty(customRating)) + { + return customRating; + } + + var parent = DisplayParent; + if (parent != null) + { + return parent.CustomRatingForComparison; + } + + return null; + } + } + + /// + /// Gets or sets the provider ids. + /// + /// The provider ids. + [JsonIgnore] + public Dictionary ProviderIds { get; set; } + + [JsonIgnore] + public virtual Folder LatestItemsIndexContainer => null; + + [JsonIgnore] + public string PresentationUniqueKey { get; set; } + + [JsonIgnore] + public virtual bool EnableRememberingTrackSelections => true; + + [JsonIgnore] + public virtual bool IsTopParent + { + get + { + if (this is BasePluginFolder || this is Channel) + { + return true; + } + + if (this is IHasCollectionType view) + { + if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + if (GetParent() is AggregateFolder) + { + return true; + } + + return false; + } + } + + [JsonIgnore] + public virtual bool SupportsAncestors => true; + + [JsonIgnore] + public virtual bool StopRefreshIfLocalMetadataFound => true; + + [JsonIgnore] + protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol; + + [JsonIgnore] + public virtual bool SupportsPeople => false; + + [JsonIgnore] + public virtual bool SupportsThemeMedia => false; + + [JsonIgnore] + public virtual bool SupportsInheritedParentImages => false; + + /// + /// Gets a value indicating whether this instance is folder. + /// + /// true if this instance is folder; otherwise, false. + [JsonIgnore] + public virtual bool IsFolder => false; + + [JsonIgnore] + public virtual bool IsDisplayedAsFolder => false; + + /// + /// Gets or sets the remote trailers. + /// + /// The remote trailers. + public IReadOnlyList RemoteTrailers { get; set; } + + public virtual bool SupportsExternalTransfer => false; + + public virtual double GetDefaultPrimaryImageAspectRatio() + { + return 0; + } + + public virtual string CreatePresentationUniqueKey() + { + return Id.ToString("N", CultureInfo.InvariantCulture); + } + + public bool IsPathProtocol(MediaProtocol protocol) + { + var current = PathProtocol; + + return current.HasValue && current.Value == protocol; + } + + private List> GetSortChunks(string s1) + { + var list = new List>(); + + int thisMarker = 0; + + while (thisMarker < s1.Length) + { + char thisCh = s1[thisMarker]; + + var thisChunk = new StringBuilder(); + bool isNumeric = char.IsDigit(thisCh); + + while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric) + { + thisChunk.Append(thisCh); + thisMarker++; + + if (thisMarker < s1.Length) + { + thisCh = s1[thisMarker]; + } + } + + list.Add(new Tuple(thisChunk, isNumeric)); + } + + return list; + } + + public virtual bool CanDelete() + { + if (SourceType == SourceType.Channel) + { + return ChannelManager.CanDelete(this); + } + + return IsFileProtocol; + } + + public virtual bool IsAuthorizedToDelete(User user, List allCollectionFolders) + { + if (user.HasPermission(PermissionKind.EnableContentDeletion)) + { + return true; + } + + var allowed = user.GetPreferenceValues(PreferenceKind.EnableContentDeletionFromFolders); + + if (SourceType == SourceType.Channel) + { + return allowed.Contains(ChannelId); + } + else + { + var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders); + + foreach (var folder in collectionFolders) + { + if (allowed.Contains(folder.Id)) + { + return true; + } + } + } + + return false; + } + + public BaseItem GetOwner() + { + var ownerId = OwnerId; + return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId); + } + + public bool CanDelete(User user, List allCollectionFolders) + { + return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders); + } + + public bool CanDelete(User user) + { + var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType().ToList(); + + return CanDelete(user, allCollectionFolders); + } + + public virtual bool CanDownload() + { + return false; + } + + public virtual bool IsAuthorizedToDownload(User user) + { + return user.HasPermission(PermissionKind.EnableContentDownloading); + } + + public bool CanDownload(User user) + { + return CanDownload() && IsAuthorizedToDownload(user); + } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return Name; + } + + public string GetInternalMetadataPath() + { + var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath; + + return GetInternalMetadataPath(basePath); + } + + protected virtual string GetInternalMetadataPath(string basePath) + { + if (SourceType == SourceType.Channel) + { + return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); + } + + ReadOnlySpan idString = Id.ToString("N", CultureInfo.InvariantCulture); + + return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString); + } + + /// + /// Creates the name of the sort. + /// + /// System.String. + protected virtual string CreateSortName() + { + if (Name == null) + { + return null; // some items may not have name filled in properly + } + + if (!EnableAlphaNumericSorting) + { + return Name.TrimStart(); + } + + var sortable = Name.Trim().ToLowerInvariant(); + + foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters) + { + sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal); + } + + foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters) + { + sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal); + } + + foreach (var search in ConfigurationManager.Configuration.SortRemoveWords) + { + // Remove from beginning if a space follows + if (sortable.StartsWith(search + " ", StringComparison.Ordinal)) + { + sortable = sortable.Remove(0, search.Length + 1); + } + + // Remove from middle if surrounded by spaces + sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal); + + // Remove from end if followed by a space + if (sortable.EndsWith(" " + search, StringComparison.Ordinal)) + { + sortable = sortable.Remove(sortable.Length - (search.Length + 1)); + } + } + + return ModifySortChunks(sortable); + } + + private string ModifySortChunks(string name) + { + var chunks = GetSortChunks(name); + + var builder = new StringBuilder(); + + foreach (var chunk in chunks) + { + var chunkBuilder = chunk.Item1; + + // This chunk is numeric + if (chunk.Item2) + { + while (chunkBuilder.Length < 10) + { + chunkBuilder.Insert(0, '0'); + } + } + + builder.Append(chunkBuilder); + } - [JsonIgnore] - public virtual bool HasLocalAlternateVersions => false; + // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString()); + return builder.ToString().RemoveDiacritics(); + } - [JsonIgnore] - public string OfficialRatingForComparison + public BaseItem GetParent() { - get + var parentId = ParentId; + if (!parentId.Equals(Guid.Empty)) { - var officialRating = OfficialRating; - if (!string.IsNullOrEmpty(officialRating)) - { - return officialRating; - } + return LibraryManager.GetItemById(parentId); + } - var parent = DisplayParent; - if (parent != null) - { - return parent.OfficialRatingForComparison; - } + return null; + } - return null; + public IEnumerable GetParents() + { + var parent = GetParent(); + + while (parent != null) + { + yield return parent; + + parent = parent.GetParent(); } } - [JsonIgnore] - public string CustomRatingForComparison + /// + /// Finds a parent of a given type. + /// + /// Type of parent. + /// ``0. + public T FindParent() + where T : Folder { - get + foreach (var parent in GetParents()) { - var customRating = CustomRating; - if (!string.IsNullOrEmpty(customRating)) - { - return customRating; - } - - var parent = DisplayParent; - if (parent != null) + if (parent is T item) { - return parent.CustomRatingForComparison; + return item; } - - return null; } + + return null; } /// @@ -1405,14 +1515,46 @@ namespace MediaBrowser.Controller.Entities } } - [JsonIgnore] - protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol; + protected bool IsVisibleStandaloneInternal(User user, bool checkFolders) + { + if (!IsVisible(user)) + { + return false; + } - [JsonIgnore] - public virtual bool SupportsPeople => false; + if (GetParents().Any(i => !i.IsVisible(user))) + { + return false; + } - [JsonIgnore] - public virtual bool SupportsThemeMedia => false; + if (checkFolders) + { + var topParent = GetParents().LastOrDefault() ?? this; + + if (string.IsNullOrEmpty(topParent.Path)) + { + return true; + } + + var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList(); + + if (itemCollectionFolders.Count > 0) + { + var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList(); + if (!itemCollectionFolders.Any(userCollectionFolders.Contains)) + { + return false; + } + } + } + + return true; + } + + public void SetParent(Folder parent) + { + ParentId = parent == null ? Guid.Empty : parent.Id; + } /// /// Refreshes owned items such as trailers, theme videos, special features, etc. @@ -1609,29 +1751,6 @@ namespace MediaBrowser.Controller.Entities return themeSongsChanged; } - /// - /// Gets or sets the provider ids. - /// - /// The provider ids. - [JsonIgnore] - public Dictionary ProviderIds { get; set; } - - [JsonIgnore] - public virtual Folder LatestItemsIndexContainer => null; - - public virtual double GetDefaultPrimaryImageAspectRatio() - { - return 0; - } - - public virtual string CreatePresentationUniqueKey() - { - return Id.ToString("N", CultureInfo.InvariantCulture); - } - - [JsonIgnore] - public string PresentationUniqueKey { get; set; } - public string GetPresentationUniqueKey() { return PresentationUniqueKey ?? CreatePresentationUniqueKey(); @@ -1929,55 +2048,6 @@ namespace MediaBrowser.Controller.Entities return IsVisibleStandaloneInternal(user, true); } - [JsonIgnore] - public virtual bool SupportsInheritedParentImages => false; - - protected bool IsVisibleStandaloneInternal(User user, bool checkFolders) - { - if (!IsVisible(user)) - { - return false; - } - - if (GetParents().Any(i => !i.IsVisible(user))) - { - return false; - } - - if (checkFolders) - { - var topParent = GetParents().LastOrDefault() ?? this; - - if (string.IsNullOrEmpty(topParent.Path)) - { - return true; - } - - var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList(); - - if (itemCollectionFolders.Count > 0) - { - var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList(); - if (!itemCollectionFolders.Any(userCollectionFolders.Contains)) - { - return false; - } - } - } - - return true; - } - - /// - /// Gets a value indicating whether this instance is folder. - /// - /// true if this instance is folder; otherwise, false. - [JsonIgnore] - public virtual bool IsFolder => false; - - [JsonIgnore] - public virtual bool IsDisplayedAsFolder => false; - public virtual string GetClientTypeName() { if (IsFolder && SourceType == SourceType.Channel && !(this is Channel)) @@ -2066,14 +2136,11 @@ namespace MediaBrowser.Controller.Entities return null; } - [JsonIgnore] - public virtual bool EnableRememberingTrackSelections => true; - /// /// Adds a studio to the item. /// /// The name. - /// + /// Throws if name is null. public void AddStudio(string name) { if (string.IsNullOrEmpty(name)) @@ -2109,7 +2176,7 @@ namespace MediaBrowser.Controller.Entities /// Adds a genre to the item. /// /// The name. - /// + /// Throwns if name is null. public void AddGenre(string name) { if (string.IsNullOrEmpty(name)) @@ -2132,8 +2199,7 @@ namespace MediaBrowser.Controller.Entities /// The user. /// The date played. /// if set to true [reset position]. - /// Task. - /// + /// Throws if user is null. public virtual void MarkPlayed( User user, DateTime? datePlayed, @@ -2170,8 +2236,7 @@ namespace MediaBrowser.Controller.Entities /// Marks the unplayed. /// /// The user. - /// Task. - /// + /// Throws if user is null. public virtual void MarkUnplayed(User user) { if (user == null) @@ -2271,6 +2336,7 @@ namespace MediaBrowser.Controller.Entities /// /// The type. /// The index. + /// A task. public async Task DeleteImageAsync(ImageType type, int index) { var info = GetImageInfo(type, index); @@ -2308,6 +2374,8 @@ namespace MediaBrowser.Controller.Entities /// /// Validates that images within the item are still on the filesystem. /// + /// The directory service to use. + /// true if the images validate, false if not. public bool ValidateImages(IDirectoryService directoryService) { var allFiles = ImageInfos @@ -2335,7 +2403,6 @@ namespace MediaBrowser.Controller.Entities /// Type of the image. /// Index of the image. /// System.String. - /// /// Item is null. public string GetImagePath(ImageType imageType, int imageIndex) => GetImageInfo(imageType, imageIndex)?.Path; @@ -2821,39 +2888,6 @@ namespace MediaBrowser.Controller.Entities return GetParents().FirstOrDefault(parent => parent.IsTopParent); } - [JsonIgnore] - public virtual bool IsTopParent - { - get - { - if (this is BasePluginFolder || this is Channel) - { - return true; - } - - if (this is IHasCollectionType view) - { - if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - if (GetParent() is AggregateFolder) - { - return true; - } - - return false; - } - } - - [JsonIgnore] - public virtual bool SupportsAncestors => true; - - [JsonIgnore] - public virtual bool StopRefreshIfLocalMetadataFound => true; - public virtual IEnumerable GetIdsForAncestorQuery() { return new[] { Id }; @@ -2888,6 +2922,7 @@ namespace MediaBrowser.Controller.Entities /// /// Updates the official rating based on content and returns true or false indicating if it changed. /// + /// Media children. /// true if the rating was updated; otherwise false. public bool UpdateRatingToItems(IList children) { @@ -2920,12 +2955,6 @@ namespace MediaBrowser.Controller.Entities return ThemeVideoIds.Select(LibraryManager.GetItemById); } - /// - /// Gets or sets the remote trailers. - /// - /// The remote trailers. - public IReadOnlyList RemoteTrailers { get; set; } - /// /// Get all extras associated with this item, sorted by . /// @@ -2963,39 +2992,11 @@ namespace MediaBrowser.Controller.Entities } } - public virtual bool IsHD => Height >= 720; - - public bool IsShortcut { get; set; } - - public string ShortcutPath { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public Guid[] ExtraIds { get; set; } - public virtual long GetRunTimeTicksForPlayState() { return RunTimeTicks ?? 0; } - /// - /// Extra types that should be counted and displayed as "Special Features" in the UI. - /// - public static readonly IReadOnlyCollection DisplayExtraTypes = new HashSet - { - Model.Entities.ExtraType.Unknown, - Model.Entities.ExtraType.BehindTheScenes, - Model.Entities.ExtraType.Clip, - Model.Entities.ExtraType.DeletedScene, - Model.Entities.ExtraType.Interview, - Model.Entities.ExtraType.Sample, - Model.Entities.ExtraType.Scene - }; - - public virtual bool SupportsExternalTransfer => false; - /// public override bool Equals(object obj) { diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs index 89ad392a4b..e88121212a 100644 --- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs +++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs @@ -64,6 +64,8 @@ namespace MediaBrowser.Controller.Entities /// /// The source object. /// The destination object. + /// Source type. + /// Destination type. public static void DeepCopy(this T source, TU dest) where T : BaseItem where TU : BaseItem @@ -109,6 +111,9 @@ namespace MediaBrowser.Controller.Entities /// Copies all properties on newly created object. Skips properties that do not exist. /// /// The source object. + /// Source type. + /// Destination type. + /// Destination object. public static TU DeepCopy(this T source) where T : BaseItem where TU : BaseItem, new() diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index 1bd25042f2..272a37df1b 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual string CollectionType => null; + [JsonIgnore] + public override bool SupportsInheritedParentImages => false; + + [JsonIgnore] + public override bool SupportsPeople => false; + public override bool CanDelete() { return false; @@ -24,11 +30,5 @@ namespace MediaBrowser.Controller.Entities { return true; } - - [JsonIgnore] - public override bool SupportsInheritedParentImages => false; - - [JsonIgnore] - public override bool SupportsPeople => false; } } diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 4f367fe2b5..0fb4771dd3 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -41,6 +41,23 @@ namespace MediaBrowser.Controller.Entities PhysicalFolderIds = Array.Empty(); } + /// + /// Gets the display preferences id. + /// + /// + /// Allow different display preferences for each collection folder. + /// + /// The display prefs id. + [JsonIgnore] + public override Guid DisplayPreferencesId => Id; + + [JsonIgnore] + public override string[] PhysicalLocations => PhysicalLocationsList; + + public string[] PhysicalLocationsList { get; set; } + + public Guid[] PhysicalFolderIds { get; set; } + public static IXmlSerializer XmlSerializer { get; set; } public static IServerApplicationHost ApplicationHost { get; set; } @@ -63,6 +80,9 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override IEnumerable Children => GetActualChildren(); + [JsonIgnore] + public override bool SupportsPeople => false; + public override bool CanDelete() { return false; @@ -160,23 +180,6 @@ namespace MediaBrowser.Controller.Entities } } - /// - /// Gets the display preferences id. - /// - /// - /// Allow different display preferences for each collection folder. - /// - /// The display prefs id. - [JsonIgnore] - public override Guid DisplayPreferencesId => Id; - - [JsonIgnore] - public override string[] PhysicalLocations => PhysicalLocationsList; - - public string[] PhysicalLocationsList { get; set; } - - public Guid[] PhysicalFolderIds { get; set; } - public override bool IsSaveLocalMetadataEnabled() { return true; @@ -373,8 +376,5 @@ namespace MediaBrowser.Controller.Entities return result; } - - [JsonIgnore] - public override bool SupportsPeople => false; } } diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs index d8bc0069c7..9ce8eebe34 100644 --- a/MediaBrowser.Controller/Entities/Extensions.cs +++ b/MediaBrowser.Controller/Entities/Extensions.cs @@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities /// /// Adds the trailer URL. /// + /// Media item. + /// Trailer URL. public static void AddTrailerUrl(this BaseItem item, string url) { if (string.IsNullOrEmpty(url)) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 34ad5bbbb6..d45a02cf2d 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1669,7 +1669,6 @@ namespace MediaBrowser.Controller.Entities /// The user. /// The date played. /// if set to true [reset position]. - /// Task. public override void MarkPlayed( User user, DateTime? datePlayed, @@ -1711,7 +1710,6 @@ namespace MediaBrowser.Controller.Entities /// Marks the unplayed. /// /// The user. - /// Task. public override void MarkUnplayed(User user) { var itemsResult = GetItemList(new InternalItemsQuery diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs index 98c3b3edf6..b11dac381c 100644 --- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs +++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs @@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities /// /// Gets the media sources. /// + /// true to enable path substitution, false to not. + /// A lits of media sources. List GetMediaSources(bool enablePathSubstitution); List GetMediaStreams(); diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs index 2bd9ded337..f4271678d4 100644 --- a/MediaBrowser.Controller/Entities/IHasTrailers.cs +++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs @@ -39,6 +39,7 @@ namespace MediaBrowser.Controller.Entities /// /// Gets the trailer count. /// + /// Media item. /// . public static int GetTrailerCount(this IHasTrailers item) => item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count; @@ -46,6 +47,7 @@ namespace MediaBrowser.Controller.Entities /// /// Gets the trailer ids. /// + /// Media item. /// . public static IReadOnlyList GetTrailerIds(this IHasTrailers item) { @@ -70,6 +72,7 @@ namespace MediaBrowser.Controller.Entities /// /// Gets the trailers. /// + /// Media item. /// . public static IReadOnlyList GetTrailers(this IHasTrailers item) { diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index b9e37269e4..045c1b89fd 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -129,6 +129,8 @@ namespace MediaBrowser.Controller.Entities /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// + /// true to replace all metadata, false to not. + /// true if changes were made, false if not. public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 556624e14e..c8feb1c946 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -105,6 +105,8 @@ namespace MediaBrowser.Controller.Entities /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// + /// true to replace all metadata, false to not. + /// true if changes were made, false if not. public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 31c179bcac..27c3ff81bd 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -49,12 +49,6 @@ namespace MediaBrowser.Controller.Entities.TV /// The index number. public int? IndexNumberEnd { get; set; } - public string FindSeriesSortName() - { - var series = Series; - return series == null ? SeriesName : series.SortName; - } - [JsonIgnore] protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1; @@ -76,45 +70,6 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] protected override bool EnableDefaultVideoUserDataKeys => false; - public override double GetDefaultPrimaryImageAspectRatio() - { - // hack for tv plugins - if (SourceType == SourceType.Channel) - { - return 0; - } - - return 16.0 / 9; - } - - public override List GetUserDataKeys() - { - var list = base.GetUserDataKeys(); - - var series = Series; - if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue) - { - var seriesUserDataKeys = series.GetUserDataKeys(); - var take = seriesUserDataKeys.Count; - if (seriesUserDataKeys.Count > 1) - { - take--; - } - - var newList = seriesUserDataKeys.GetRange(0, take); - var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture); - for (int i = 0; i < take; i++) - { - newList[i] = newList[i] + suffix; - } - - newList.AddRange(list); - list = newList; - } - - return list; - } - /// /// Gets the Episode's Series Instance. /// @@ -161,6 +116,74 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] public string SeasonName { get; set; } + [JsonIgnore] + public override bool SupportsRemoteImageDownloading + { + get + { + if (IsMissingEpisode) + { + return false; + } + + return true; + } + } + + [JsonIgnore] + public bool IsMissingEpisode => LocationType == LocationType.Virtual; + + [JsonIgnore] + public Guid SeasonId { get; set; } + + [JsonIgnore] + public Guid SeriesId { get; set; } + + public string FindSeriesSortName() + { + var series = Series; + return series == null ? SeriesName : series.SortName; + } + + public override double GetDefaultPrimaryImageAspectRatio() + { + // hack for tv plugins + if (SourceType == SourceType.Channel) + { + return 0; + } + + return 16.0 / 9; + } + + public override List GetUserDataKeys() + { + var list = base.GetUserDataKeys(); + + var series = Series; + if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue) + { + var seriesUserDataKeys = series.GetUserDataKeys(); + var take = seriesUserDataKeys.Count; + if (seriesUserDataKeys.Count > 1) + { + take--; + } + + var newList = seriesUserDataKeys.GetRange(0, take); + var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture); + for (int i = 0; i < take; i++) + { + newList[i] = newList[i] + suffix; + } + + newList.AddRange(list); + list = newList; + } + + return list; + } + public string FindSeriesPresentationUniqueKey() { var series = Series; @@ -242,29 +265,6 @@ namespace MediaBrowser.Controller.Entities.TV return false; } - [JsonIgnore] - public override bool SupportsRemoteImageDownloading - { - get - { - if (IsMissingEpisode) - { - return false; - } - - return true; - } - } - - [JsonIgnore] - public bool IsMissingEpisode => LocationType == LocationType.Virtual; - - [JsonIgnore] - public Guid SeasonId { get; set; } - - [JsonIgnore] - public Guid SeriesId { get; set; } - public Guid FindSeriesId() { var series = FindParent(); diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index aa62bb35b0..926c7b0459 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -38,6 +38,50 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] public override Guid DisplayParentId => SeriesId; + /// + /// Gets this Episode's Series Instance. + /// + /// The series. + [JsonIgnore] + public Series Series + { + get + { + var seriesId = SeriesId; + if (seriesId == Guid.Empty) + { + seriesId = FindSeriesId(); + } + + return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series); + } + } + + [JsonIgnore] + public string SeriesPath + { + get + { + var series = Series; + + if (series != null) + { + return series.Path; + } + + return System.IO.Path.GetDirectoryName(Path); + } + } + + [JsonIgnore] + public string SeriesPresentationUniqueKey { get; set; } + + [JsonIgnore] + public string SeriesName { get; set; } + + [JsonIgnore] + public Guid SeriesId { get; set; } + public override double GetDefaultPrimaryImageAspectRatio() { double value = 2; @@ -80,41 +124,6 @@ namespace MediaBrowser.Controller.Entities.TV return result; } - /// - /// Gets this Episode's Series Instance. - /// - /// The series. - [JsonIgnore] - public Series Series - { - get - { - var seriesId = SeriesId; - if (seriesId == Guid.Empty) - { - seriesId = FindSeriesId(); - } - - return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series); - } - } - - [JsonIgnore] - public string SeriesPath - { - get - { - var series = Series; - - if (series != null) - { - return series.Path; - } - - return System.IO.Path.GetDirectoryName(Path); - } - } - public override string CreatePresentationUniqueKey() { if (IndexNumber.HasValue) @@ -157,6 +166,9 @@ namespace MediaBrowser.Controller.Entities.TV /// /// Gets the episodes. /// + /// The user. + /// The options to use. + /// Set of episodes. public List GetEpisodes(User user, DtoOptions options) { return GetEpisodes(Series, user, options); @@ -193,15 +205,6 @@ namespace MediaBrowser.Controller.Entities.TV return UnratedItem.Series; } - [JsonIgnore] - public string SeriesPresentationUniqueKey { get; set; } - - [JsonIgnore] - public string SeriesName { get; set; } - - [JsonIgnore] - public Guid SeriesId { get; set; } - public string FindSeriesPresentationUniqueKey() { var series = Series; @@ -241,6 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// + /// true to replace metdata, false to not. /// true if XXXX, false otherwise. public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 44d07b4a48..beda504b91 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -72,6 +72,9 @@ namespace MediaBrowser.Controller.Entities.TV /// The status. public SeriesStatus? Status { get; set; } + [JsonIgnore] + public override bool StopRefreshIfLocalMetadataFound => false; + public override double GetDefaultPrimaryImageAspectRatio() { double value = 2; @@ -394,6 +397,10 @@ namespace MediaBrowser.Controller.Entities.TV /// /// Filters the episodes by season. /// + /// The episodes. + /// The season. + /// true to include special, false to not. + /// The set of episodes. public static IEnumerable FilterEpisodesBySeason(IEnumerable episodes, Season parentSeason, bool includeSpecials) { var seasonNumber = parentSeason.IndexNumber; @@ -424,6 +431,10 @@ namespace MediaBrowser.Controller.Entities.TV /// /// Filters the episodes by season. /// + /// The episodes. + /// The season. + /// true to include special, false to not. + /// The set of episodes. public static IEnumerable FilterEpisodesBySeason(IEnumerable episodes, int seasonNumber, bool includeSpecials) { if (!includeSpecials || seasonNumber < 1) @@ -499,8 +510,5 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - - [JsonIgnore] - public override bool StopRefreshIfLocalMetadataFound => false; } } diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index 6ab2116d73..9179eae939 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -12,6 +12,13 @@ namespace MediaBrowser.Controller.Entities /// public class UserItemData { + /// + /// The _rating. + /// + private double? _rating; + + public const double MinLikeValue = 6.5; + /// /// Gets or sets the user id. /// @@ -24,11 +31,6 @@ namespace MediaBrowser.Controller.Entities /// The key. public string Key { get; set; } - /// - /// The _rating. - /// - private double? _rating; - /// /// Gets or sets the users 0-10 rating. /// @@ -93,8 +95,6 @@ namespace MediaBrowser.Controller.Entities /// The index of the subtitle stream. public int? SubtitleStreamIndex { get; set; } - public const double MinLikeValue = 6.5; - /// /// Gets or sets a value indicating whether the item is liked or not. /// This should never be serialized. diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 2b15a52f09..f3bf4749d2 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -21,8 +21,28 @@ namespace MediaBrowser.Controller.Entities /// public class UserRootFolder : Folder { - private List _childrenIds = null; private readonly object _childIdsLock = new object(); + private List _childrenIds = null; + + [JsonIgnore] + public override bool SupportsInheritedParentImages => false; + + [JsonIgnore] + public override bool SupportsPlayedStatus => false; + + [JsonIgnore] + protected override bool SupportsShortcutChildren => true; + + [JsonIgnore] + public override bool IsPreSorted => true; + + private void ClearCache() + { + lock (_childIdsLock) + { + _childrenIds = null; + } + } protected override List LoadChildren() { @@ -39,20 +59,6 @@ namespace MediaBrowser.Controller.Entities } } - [JsonIgnore] - public override bool SupportsInheritedParentImages => false; - - [JsonIgnore] - public override bool SupportsPlayedStatus => false; - - private void ClearCache() - { - lock (_childIdsLock) - { - _childrenIds = null; - } - } - protected override QueryResult GetItemsInternal(InternalItemsQuery query) { if (query.Recursive) @@ -74,12 +80,6 @@ namespace MediaBrowser.Controller.Entities return GetChildren(user, true).Count; } - [JsonIgnore] - protected override bool SupportsShortcutChildren => true; - - [JsonIgnore] - public override bool IsPreSorted => true; - protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) { var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList(); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index d05b5df2f1..7dd95b85cf 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities ISupportsPlaceHolders, IHasMediaSources { + public Video() + { + AdditionalParts = Array.Empty(); + LocalAlternateVersions = Array.Empty(); + SubtitleFiles = Array.Empty(); + LinkedAlternateVersions = Array.Empty(); + } + [JsonIgnore] public string PrimaryVersionId { get; set; } @@ -74,30 +82,6 @@ namespace MediaBrowser.Controller.Entities } } - public void SetPrimaryVersionId(string id) - { - if (string.IsNullOrEmpty(id)) - { - PrimaryVersionId = null; - } - else - { - PrimaryVersionId = id; - } - - PresentationUniqueKey = CreatePresentationUniqueKey(); - } - - public override string CreatePresentationUniqueKey() - { - if (!string.IsNullOrEmpty(PrimaryVersionId)) - { - return PrimaryVersionId; - } - - return base.CreatePresentationUniqueKey(); - } - [JsonIgnore] public override bool SupportsThemeMedia => true; @@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities /// The aspect ratio. public string AspectRatio { get; set; } - public Video() - { - AdditionalParts = Array.Empty(); - LocalAlternateVersions = Array.Empty(); - SubtitleFiles = Array.Empty(); - LinkedAlternateVersions = Array.Empty(); - } - - public override bool CanDownload() - { - if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay) - { - return false; - } - - return IsFileProtocol; - } - [JsonIgnore] public override bool SupportsAddingToPlaylist => true; @@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0; - public IEnumerable GetAdditionalPartIds() - { - return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); - } - - public IEnumerable GetLocalAlternateVersionIds() - { - return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); - } - public static ILiveTvManager LiveTvManager { get; set; } [JsonIgnore] @@ -222,37 +178,77 @@ namespace MediaBrowser.Controller.Entities } } - protected override bool IsActiveRecording() + [JsonIgnore] + public bool IsCompleteMedia { - return LiveTvManager.GetActiveRecordingInfo(Path) != null; + get + { + if (SourceType == SourceType.Channel) + { + return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase); + } + + return !IsActiveRecording(); + } } - public override bool CanDelete() + [JsonIgnore] + protected virtual bool EnableDefaultVideoUserDataKeys => true; + + [JsonIgnore] + public override string ContainingFolderPath { - if (IsActiveRecording()) + get { - return false; - } + if (IsStacked) + { + return System.IO.Path.GetDirectoryName(Path); + } - return base.CanDelete(); + if (!IsPlaceHolder) + { + if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) + { + return Path; + } + } + + return base.ContainingFolderPath; + } } [JsonIgnore] - public bool IsCompleteMedia + public override string FileNameWithoutExtension { get { - if (SourceType == SourceType.Channel) + if (IsFileProtocol) { - return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase); + if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) + { + return System.IO.Path.GetFileName(Path); + } + + return System.IO.Path.GetFileNameWithoutExtension(Path); } - return !IsActiveRecording(); + return null; } } + /// + /// Gets a value indicating whether [is3 D]. + /// + /// true if [is3 D]; otherwise, false. [JsonIgnore] - protected virtual bool EnableDefaultVideoUserDataKeys => true; + public bool Is3D => Video3DFormat.HasValue; + + /// + /// Gets the type of the media. + /// + /// The type of the media. + [JsonIgnore] + public override string MediaType => Model.Entities.MediaType.Video; public override List GetUserDataKeys() { @@ -293,6 +289,65 @@ namespace MediaBrowser.Controller.Entities return list; } + public void SetPrimaryVersionId(string id) + { + if (string.IsNullOrEmpty(id)) + { + PrimaryVersionId = null; + } + else + { + PrimaryVersionId = id; + } + + PresentationUniqueKey = CreatePresentationUniqueKey(); + } + + public override string CreatePresentationUniqueKey() + { + if (!string.IsNullOrEmpty(PrimaryVersionId)) + { + return PrimaryVersionId; + } + + return base.CreatePresentationUniqueKey(); + } + + public override bool CanDownload() + { + if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay) + { + return false; + } + + return IsFileProtocol; + } + + protected override bool IsActiveRecording() + { + return LiveTvManager.GetActiveRecordingInfo(Path) != null; + } + + public override bool CanDelete() + { + if (IsActiveRecording()) + { + return false; + } + + return base.CanDelete(); + } + + public IEnumerable GetAdditionalPartIds() + { + return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); + } + + public IEnumerable GetLocalAlternateVersionIds() + { + return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); + } + private string GetUserDataKey(string providerId) { var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant(); @@ -328,47 +383,6 @@ namespace MediaBrowser.Controller.Entities .OrderBy(i => i.SortName); } - [JsonIgnore] - public override string ContainingFolderPath - { - get - { - if (IsStacked) - { - return System.IO.Path.GetDirectoryName(Path); - } - - if (!IsPlaceHolder) - { - if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) - { - return Path; - } - } - - return base.ContainingFolderPath; - } - } - - [JsonIgnore] - public override string FileNameWithoutExtension - { - get - { - if (IsFileProtocol) - { - if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) - { - return System.IO.Path.GetFileName(Path); - } - - return System.IO.Path.GetFileNameWithoutExtension(Path); - } - - return null; - } - } - internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem) { var updateType = base.UpdateFromResolvedItem(newItem); @@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities return updateType; } - /// - /// Gets a value indicating whether [is3 D]. - /// - /// true if [is3 D]; otherwise, false. - [JsonIgnore] - public bool Is3D => Video3DFormat.HasValue; - - /// - /// Gets the type of the media. - /// - /// The type of the media. - [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Video; - protected override async Task RefreshedOwnedItems(MetadataRefreshOptions options, List fileSystemChildren, CancellationToken cancellationToken) { var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index d8995ce74a..0813a8e7d5 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -1,6 +1,6 @@ #nullable disable -#pragma warning disable CS1591 +#pragma warning disable CS1591, SA1306, SA1401 using System; using System.Collections.Generic; @@ -30,6 +30,21 @@ namespace MediaBrowser.Controller.Net private readonly List> _activeConnections = new List>(); + /// + /// The logger. + /// + protected ILogger> Logger; + + protected BasePeriodicWebSocketListener(ILogger> logger) + { + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + Logger = logger; + } + /// /// Gets the type used for the messages sent to the client. /// @@ -54,21 +69,6 @@ namespace MediaBrowser.Controller.Net /// Task{`1}. protected abstract Task GetDataToSend(); - /// - /// The logger. - /// - protected ILogger> Logger; - - protected BasePeriodicWebSocketListener(ILogger> logger) - { - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - Logger = logger; - } - /// /// Processes the message. /// diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index 5fa5834c85..c43acfb6de 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -18,7 +18,6 @@ namespace MediaBrowser.Controller.Persistence /// The key. /// The user data. /// The cancellation token. - /// Task. void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken); /// diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 3eaf235152..5e671a725d 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -31,24 +31,18 @@ namespace MediaBrowser.Controller.Playlists ".zpl" }; - public Guid OwnerUserId { get; set; } - - public Share[] Shares { get; set; } - public Playlist() { Shares = Array.Empty(); } + public Guid OwnerUserId { get; set; } + + public Share[] Shares { get; set; } + [JsonIgnore] public bool IsFile => IsPlaylistFile(Path); - public static bool IsPlaylistFile(string path) - { - // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot). - return System.IO.Path.HasExtension(path) && !Directory.Exists(path); - } - [JsonIgnore] public override string ContainingFolderPath { @@ -80,6 +74,41 @@ namespace MediaBrowser.Controller.Playlists [JsonIgnore] public override bool SupportsCumulativeRunTimeTicks => true; + [JsonIgnore] + public override bool IsPreSorted => true; + + public string PlaylistMediaType { get; set; } + + [JsonIgnore] + public override string MediaType => PlaylistMediaType; + + [JsonIgnore] + private bool IsSharedItem + { + get + { + var path = Path; + + if (string.IsNullOrEmpty(path)) + { + return false; + } + + return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path); + } + } + + public static bool IsPlaylistFile(string path) + { + // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot). + return System.IO.Path.HasExtension(path) && !Directory.Exists(path); + } + + public void SetMediaType(string value) + { + PlaylistMediaType = value; + } + public override double GetDefaultPrimaryImageAspectRatio() { return 1; @@ -197,35 +226,6 @@ namespace MediaBrowser.Controller.Playlists return new[] { item }; } - [JsonIgnore] - public override bool IsPreSorted => true; - - public string PlaylistMediaType { get; set; } - - [JsonIgnore] - public override string MediaType => PlaylistMediaType; - - public void SetMediaType(string value) - { - PlaylistMediaType = value; - } - - [JsonIgnore] - private bool IsSharedItem - { - get - { - var path = Path; - - if (string.IsNullOrEmpty(path)) - { - return false; - } - - return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path); - } - } - public override bool IsVisible(User user) { if (!IsSharedItem) diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs index 75286eadc0..b95d00aa3c 100644 --- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs @@ -13,18 +13,18 @@ namespace MediaBrowser.Controller.Resolvers /// public interface IItemResolver { + /// + /// Gets the priority. + /// + /// The priority. + ResolverPriority Priority { get; } + /// /// Resolves the path. /// /// The args. /// BaseItem. BaseItem ResolvePath(ItemResolveArgs args); - - /// - /// Gets the priority. - /// - /// The priority. - ResolverPriority Priority { get; } } public interface IMultiItemResolver @@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Resolvers public class MultiItemResolverResult { - public List Items { get; set; } - - public List ExtraFiles { get; set; } - public MultiItemResolverResult() { Items = new List(); ExtraFiles = new List(); } + + public List Items { get; set; } + + public List ExtraFiles { get; set; } } } diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 9e661cbe42..3330dd5408 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Subtitles /// /// Searches the subtitles. /// + /// The video. + /// Subtitle language. + /// Require perfect match. + /// CancellationToken to use for the operation. + /// Subtitles, wrapped in task. Task SearchSubtitles( Video video, string language, @@ -47,11 +52,20 @@ namespace MediaBrowser.Controller.Subtitles /// /// Downloads the subtitles. /// + /// The video. + /// Subtitle ID. + /// CancellationToken to use for the operation. + /// A task. Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken); /// /// Downloads the subtitles. /// + /// The video. + /// Library options to use. + /// Subtitle ID. + /// CancellationToken to use for the operation. + /// A task. Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken); /// @@ -73,11 +87,16 @@ namespace MediaBrowser.Controller.Subtitles /// /// Deletes the subtitles. /// + /// Media item. + /// Subtitle index. + /// A task. Task DeleteSubtitles(BaseItem item, int index); /// /// Gets the providers. /// + /// The media item. + /// Subtitles providers. SubtitleProviderInfo[] GetSupportedProviders(BaseItem item); } } diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs index 0f7c47e767..767d87d465 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs @@ -11,6 +11,15 @@ namespace MediaBrowser.Controller.Subtitles { public class SubtitleSearchRequest : IHasProviderIds { + public SubtitleSearchRequest() + { + SearchAllProviders = true; + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + + DisabledSubtitleFetchers = Array.Empty(); + SubtitleFetcherOrder = Array.Empty(); + } + public string Language { get; set; } public string TwoLetterISOLanguageName { get; set; } @@ -42,14 +51,5 @@ namespace MediaBrowser.Controller.Subtitles public string[] DisabledSubtitleFetchers { get; set; } public string[] SubtitleFetcherOrder { get; set; } - - public SubtitleSearchRequest() - { - SearchAllProviders = true; - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - - DisabledSubtitleFetchers = Array.Empty(); - SubtitleFetcherOrder = Array.Empty(); - } } } From 2345646ff159f474588e24c29ab94a97a4f41e6d Mon Sep 17 00:00:00 2001 From: boolemancer Date: Sat, 14 Aug 2021 02:55:51 -0700 Subject: [PATCH 037/118] Fix explicit stream selection in MediaEncoder.ExtractImageInternal --- .../Encoder/MediaEncoder.cs | 12 ++---------- .../MediaInfo/VideoImageProvider.cs | 18 +----------------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 412a953216..e0a8102f92 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -503,15 +503,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { var inputArgument = GetInputArgument(inputFile, mediaSource); - if (isAudio) - { - if (imageStreamIndex.HasValue && imageStreamIndex.Value > 0) - { - // It seems for audio files we need to subtract 1 (for the audio stream??) - imageStreamIndex = imageStreamIndex.Value - 1; - } - } - else + if (!isAudio) { // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter. try @@ -582,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _ => string.Empty }; - var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; + var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; var enableHdrExtraction = allowTonemap && string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase); if (enableHdrExtraction) diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 30af6710ab..453938be79 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -88,22 +88,6 @@ namespace MediaBrowser.Providers.MediaInfo if (imageStream != null) { - // Instead of using the raw stream index, we need to use nth video/embedded image stream - var videoIndex = -1; - foreach (var mediaStream in mediaStreams) - { - if (mediaStream.Type == MediaStreamType.Video || - mediaStream.Type == MediaStreamType.EmbeddedImage) - { - videoIndex++; - } - - if (mediaStream == imageStream) - { - break; - } - } - MediaSourceInfo mediaSource = new MediaSourceInfo { VideoType = item.VideoType, @@ -111,7 +95,7 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = item.PathProtocol.Value, }; - extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, imageStream, videoIndex, cancellationToken).ConfigureAwait(false); + extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); } else { From 7ee404ec98715ac31d4893c568d62c216d6bedc8 Mon Sep 17 00:00:00 2001 From: artiume Date: Sat, 14 Aug 2021 06:21:44 -0400 Subject: [PATCH 038/118] Added translation using Weblate (Pirate) --- Emby.Server.Implementations/Localization/Core/pr.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/pr.json diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/pr.json @@ -0,0 +1 @@ +{} From 9b451f357f22b7a45b58128c1b5d0baf580e4812 Mon Sep 17 00:00:00 2001 From: FancyNerd92 Date: Fri, 13 Aug 2021 23:44:57 +0000 Subject: [PATCH 039/118] Translated using Weblate (English) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en/ --- Emby.Server.Implementations/Localization/Core/en-US.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index 65964f6d9f..ca127cdb86 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -17,7 +17,7 @@ "Folders": "Folders", "Forced": "Forced", "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", + "HeaderAlbumArtists": "Artist's Album", "HeaderContinueWatching": "Continue Watching", "HeaderFavoriteAlbums": "Favorite Albums", "HeaderFavoriteArtists": "Favorite Artists", @@ -27,7 +27,7 @@ "HeaderLiveTV": "Live TV", "HeaderNextUp": "Next Up", "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", + "HomeVideos": "Home Videos", "Inherit": "Inherit", "ItemAddedWithName": "{0} was added to the library", "ItemRemovedWithName": "{0} was removed from the library", @@ -41,7 +41,7 @@ "MixedContent": "Mixed content", "Movies": "Movies", "Music": "Music", - "MusicVideos": "Music videos", + "MusicVideos": "Music Videos", "NameInstallFailed": "{0} installation failed", "NameSeasonNumber": "Season {0}", "NameSeasonUnknown": "Season Unknown", From b6691d3fe9cb6a0c76cd92a7ea529dadd1e5f68e Mon Sep 17 00:00:00 2001 From: cotlol Date: Fri, 13 Aug 2021 11:06:04 +0000 Subject: [PATCH 040/118] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- Emby.Server.Implementations/Localization/Core/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index d992bf79b1..88b182f8d9 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -118,5 +118,6 @@ "TaskCleanActivityLog": "Rensa Aktivitets Logg", "Undefined": "odefinierad", "Forced": "Tvingad", - "Default": "Standard" + "Default": "Standard", + "TaskOptimizeDatabase": "Optimera databasen" } From 7eb4da4eefe9ed0699289b273d0ec2e4c8aee86d Mon Sep 17 00:00:00 2001 From: FancyNerd92 Date: Fri, 13 Aug 2021 22:51:38 +0000 Subject: [PATCH 041/118] Translated using Weblate (Greek) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/ --- Emby.Server.Implementations/Localization/Core/el.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 23d45b4737..834d5e75f2 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -39,7 +39,7 @@ "MixedContent": "Ανάμεικτο Περιεχόμενο", "Movies": "Ταινίες", "Music": "Μουσική", - "MusicVideos": "Μουσικά βίντεο", + "MusicVideos": "Μουσικά Βίντεο", "NameInstallFailed": "{0} η εγκατάσταση απέτυχε", "NameSeasonNumber": "Κύκλος {0}", "NameSeasonUnknown": "Άγνωστος Κύκλος", @@ -62,7 +62,7 @@ "NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε", "Photos": "Φωτογραφίες", "Playlists": "Λίστες αναπαραγωγής", - "Plugin": "Plugin", + "Plugin": "Πρόσθετο", "PluginInstalledWithName": "{0} εγκαταστήθηκε", "PluginUninstalledWithName": "{0} έχει απεγκατασταθεί", "PluginUpdatedWithName": "{0} έχει αναβαθμιστεί", @@ -118,5 +118,7 @@ "TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων", "Undefined": "Απροσδιόριστο", "Forced": "Εξαναγκασμένο", - "Default": "Προεπιλογή" + "Default": "Προεπιλογή", + "TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.", + "TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων" } From 585207e05bdec2f2e468b549270a16c73bc2a657 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 12 Aug 2021 12:07:02 +0000 Subject: [PATCH 042/118] Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/ --- Emby.Server.Implementations/Localization/Core/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index 1b612dc716..7715daa7ce 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -5,7 +5,7 @@ "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament", "Books": "Llibres", - "CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}", + "CameraImageUploadedFrom": "S'ha pujat una nova imatge des de la camera desde {0}", "Channels": "Canals", "ChapterNameValue": "Capítol {0}", "Collections": "Col·leccions", From c80930c6d417db1c999c986b42de9fe10fa92341 Mon Sep 17 00:00:00 2001 From: dtorner Date: Sat, 14 Aug 2021 05:39:10 +0000 Subject: [PATCH 043/118] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/ --- Emby.Server.Implementations/Localization/Core/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index 7d42182b0c..d3d9d27038 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -15,7 +15,7 @@ "Favorites": "Favoritos", "Folders": "Carpetas", "Genres": "Géneros", - "HeaderAlbumArtists": "Artistas del álbum", + "HeaderAlbumArtists": "Artista del álbum", "HeaderContinueWatching": "Continuar viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", From 890e99725e9222550750d4742557d77e55acb0ee Mon Sep 17 00:00:00 2001 From: Winter <78392041+winterqt@users.noreply.github.com> Date: Sat, 14 Aug 2021 19:29:31 -0400 Subject: [PATCH 044/118] Disable UseAppHost in portable deployment --- deployment/build.portable | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/build.portable b/deployment/build.portable index ea40ade5d9..a6c7418810 100755 --- a/deployment/build.portable +++ b/deployment/build.portable @@ -16,7 +16,7 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" +dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=false" tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version} rm -rf dist/jellyfin-server_${version} From e8e0836f95a38b5723136839def75a60a1907afc Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 15 Aug 2021 14:13:20 +0200 Subject: [PATCH 045/118] Fix PasswordHash tests --- .../Cryptography/PasswordHashTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs index e6c325bac0..18d3f97638 100644 --- a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs +++ b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs @@ -171,11 +171,11 @@ namespace Jellyfin.Common.Tests.Cryptography [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter - [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $ - [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment - [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment - [InlineData("$PBKDF2$iterations=$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt - [InlineData("$PBKDF2$iterations=$69F420$invalid hash")] // Invalid hash + [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $ + [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment + [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment + [InlineData("$PBKDF2$iterations=1000$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt + [InlineData("$PBKDF2$iterations=1000$69F420$invalid hash")] // Invalid hash [InlineData("$PBKDF2$69F420$")] // Empty hash public static void Parse_InvalidFormat_ThrowsFormatException(string passwordHash) { From 709f8e9faa64d3d51a717be8453b4a57faf371b5 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 15 Aug 2021 08:30:15 -0600 Subject: [PATCH 046/118] Update to dotnet 5.0.9 --- .../Emby.Server.Implementations.csproj | 2 +- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- .../Jellyfin.Server.Implementations.csproj | 8 ++++---- Jellyfin.Server/Jellyfin.Server.csproj | 4 ++-- deployment/Dockerfile.ubuntu.amd64 | 2 +- deployment/Dockerfile.ubuntu.arm64 | 2 +- deployment/Dockerfile.ubuntu.armhf | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 4c9e058212..e0f841d529 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -28,7 +28,7 @@ - + diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index a527282d15..669925198b 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -14,7 +14,7 @@ - + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 728f9021dc..a75b285936 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -19,13 +19,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 49529b7944..4ad39c60a9 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -33,8 +33,8 @@ - - + + diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index 97e3ff8023..d88efcdc95 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -19,7 +19,7 @@ RUN apt-get update -yqq \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8468e541-a99a-4191-8470-654fa0747a9a/cb32548d2fd3d60ef3fe8fc80cd735ef/dotnet-sdk-5.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/13b9d84c-a35b-4ffe-8f62-447a01403d64/1f9ae31daa0f7d98513e7551246899f2/dotnet-sdk-5.0.400-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index c94ee91dd7..4f41bba2d9 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -18,7 +18,7 @@ RUN apt-get update -yqq \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8468e541-a99a-4191-8470-654fa0747a9a/cb32548d2fd3d60ef3fe8fc80cd735ef/dotnet-sdk-5.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/13b9d84c-a35b-4ffe-8f62-447a01403d64/1f9ae31daa0f7d98513e7551246899f2/dotnet-sdk-5.0.400-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index aaaedda82c..01752d5367 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -18,7 +18,7 @@ RUN apt-get update -yqq \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8468e541-a99a-4191-8470-654fa0747a9a/cb32548d2fd3d60ef3fe8fc80cd735ef/dotnet-sdk-5.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/13b9d84c-a35b-4ffe-8f62-447a01403d64/1f9ae31daa0f7d98513e7551246899f2/dotnet-sdk-5.0.400-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 4edd843841..ecf7d2b364 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index cf42153393..8f2eb9ef58 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 2f95f5c01c..6f3c532cd0 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,7 +10,7 @@ - + From 312e2685f8e1c734680ea9872fd31b27694d607d Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Sun, 15 Aug 2021 10:30:12 -0700 Subject: [PATCH 047/118] Update MediaBrowser.Controller/Entities/IHasMediaSources.cs Co-authored-by: Bond-009 --- MediaBrowser.Controller/Entities/IHasMediaSources.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs index b11dac381c..90d9bdd2d3 100644 --- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs +++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Entities /// Gets the media sources. /// /// true to enable path substitution, false to not. - /// A lits of media sources. + /// A list of media sources. List GetMediaSources(bool enablePathSubstitution); List GetMediaStreams(); From e7a3552aae556a5afa58207db353f10de9021274 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Sun, 15 Aug 2021 10:32:18 -0700 Subject: [PATCH 048/118] Update per feedback --- MediaBrowser.Controller/Entities/UserItemData.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index 9179eae939..50ba9ef308 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -12,13 +12,13 @@ namespace MediaBrowser.Controller.Entities /// public class UserItemData { + public const double MinLikeValue = 6.5; + /// /// The _rating. /// private double? _rating; - public const double MinLikeValue = 6.5; - /// /// Gets or sets the user id. /// From 19824bff94a9f557c3fb1616e1b5031fd125a53a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 15 Aug 2021 17:20:07 +0200 Subject: [PATCH 049/118] Minor improvements --- .../Channels/ChannelManager.cs | 2 +- .../Collections/CollectionManager.cs | 32 +++--- .../Localization/LocalizationManager.cs | 63 +++++------ MediaBrowser.Common/Plugins/BasePluginOfT.cs | 4 +- .../BaseItemManager/BaseItemManager.cs | 8 +- .../BaseItemManager/IBaseItemManager.cs | 4 +- .../Channels/ChannelItemResult.cs | 9 +- .../Collections/CollectionCreationOptions.cs | 2 +- .../CollectionModifiedEventArgs.cs | 2 - .../Collections/ICollectionManager.cs | 8 +- .../IServerConfigurationManager.cs | 2 - .../MediaEncoding/EncodingHelper.cs | 3 - .../Globalization/ILocalizationManager.cs | 11 +- .../Parsers/BaseNfoParser.cs | 102 +++++++++--------- .../Parsers/EpisodeNfoParser.cs | 92 ++++++++-------- .../Localization/LocalizationManagerTests.cs | 2 +- 16 files changed, 151 insertions(+), 195 deletions(-) diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 093607dd5e..aa54510a71 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -880,7 +880,7 @@ namespace Emby.Server.Implementations.Channels } } - private async Task CacheResponse(object result, string path) + private async Task CacheResponse(ChannelItemResult result, string path) { try { diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 08acd17672..8270c2e84c 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -63,13 +61,13 @@ namespace Emby.Server.Implementations.Collections } /// - public event EventHandler CollectionCreated; + public event EventHandler? CollectionCreated; /// - public event EventHandler ItemsAddedToCollection; + public event EventHandler? ItemsAddedToCollection; /// - public event EventHandler ItemsRemovedFromCollection; + public event EventHandler? ItemsRemovedFromCollection; private IEnumerable FindFolders(string path) { @@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Collections .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path)); } - internal async Task EnsureLibraryFolder(string path, bool createIfNeeded) + internal async Task EnsureLibraryFolder(string path, bool createIfNeeded) { var existingFolder = FindFolders(path).FirstOrDefault(); if (existingFolder != null) @@ -114,7 +112,7 @@ namespace Emby.Server.Implementations.Collections return Path.Combine(_appPaths.DataPath, "collections"); } - private Task GetCollectionsFolder(bool createIfNeeded) + private Task GetCollectionsFolder(bool createIfNeeded) { return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded); } @@ -203,8 +201,7 @@ namespace Emby.Server.Implementations.Collections private async Task AddToCollectionAsync(Guid collectionId, IEnumerable ids, bool fireEvent, MetadataRefreshOptions refreshOptions) { - var collection = _libraryManager.GetItemById(collectionId) as BoxSet; - if (collection == null) + if (_libraryManager.GetItemById(collectionId) is not BoxSet collection) { throw new ArgumentException("No collection exists with the supplied Id"); } @@ -256,9 +253,7 @@ namespace Emby.Server.Implementations.Collections /// public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable itemIds) { - var collection = _libraryManager.GetItemById(collectionId) as BoxSet; - - if (collection == null) + if (_libraryManager.GetItemById(collectionId) is not BoxSet collection) { throw new ArgumentException("No collection exists with the supplied Id"); } @@ -312,11 +307,7 @@ namespace Emby.Server.Implementations.Collections foreach (var item in items) { - if (item is not ISupportsBoxSetGrouping) - { - results[item.Id] = item; - } - else + if (item is ISupportsBoxSetGrouping) { var itemId = item.Id; @@ -340,6 +331,7 @@ namespace Emby.Server.Implementations.Collections } var alreadyInResults = false; + // this is kind of a performance hack because only Video has alternate versions that should be in a box set? if (item is Video video) { @@ -355,11 +347,13 @@ namespace Emby.Server.Implementations.Collections } } - if (!alreadyInResults) + if (alreadyInResults) { - results[itemId] = item; + continue; } } + + results[item.Id] = item; } return results.Values; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 9808e47de2..03919197e2 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -38,10 +36,10 @@ namespace Emby.Server.Implementations.Localization private readonly ConcurrentDictionary> _dictionaries = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); - private List _cultures; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private List _cultures = new List(); + /// /// Initializes a new instance of the class. /// @@ -72,8 +70,8 @@ namespace Emby.Server.Implementations.Localization string countryCode = resource.Substring(RatingsPath.Length, 2); var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); - await using var str = _assembly.GetManifestResourceStream(resource); - using var reader = new StreamReader(str); + await using var stream = _assembly.GetManifestResourceStream(resource); + using var reader = new StreamReader(stream!); // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames() await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { if (string.IsNullOrWhiteSpace(line)) @@ -113,7 +111,8 @@ namespace Emby.Server.Implementations.Localization { List list = new List(); - await using var stream = _assembly.GetManifestResourceStream(CulturesPath); + await using var stream = _assembly.GetManifestResourceStream(CulturesPath) + ?? throw new InvalidOperationException($"Invalid resource path: '{CulturesPath}'"); using var reader = new StreamReader(stream); await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { @@ -162,7 +161,7 @@ namespace Emby.Server.Implementations.Localization } /// - public CultureDto FindLanguageInfo(string language) + public CultureDto? FindLanguageInfo(string language) { // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs for (var i = 0; i < _cultures.Count; i++) @@ -183,9 +182,10 @@ namespace Emby.Server.Implementations.Localization /// public IEnumerable GetCountries() { - using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream(CountriesPath)); - - return JsonSerializer.Deserialize>(reader.ReadToEnd(), _jsonOptions); + using StreamReader reader = new StreamReader( + _assembly.GetManifestResourceStream(CountriesPath) ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'")); + return JsonSerializer.Deserialize>(reader.ReadToEnd(), _jsonOptions) + ?? throw new InvalidOperationException($"Resource contains invalid data: '{CountriesPath}'"); } /// @@ -205,7 +205,9 @@ namespace Emby.Server.Implementations.Localization countryCode = "us"; } - return GetRatings(countryCode) ?? GetRatings("us"); + return GetRatings(countryCode) + ?? GetRatings("us") + ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"); } /// @@ -213,7 +215,7 @@ namespace Emby.Server.Implementations.Localization /// /// The country code. /// The ratings. - private Dictionary GetRatings(string countryCode) + private Dictionary? GetRatings(string countryCode) { _allParentalRatings.TryGetValue(countryCode, out var value); @@ -238,7 +240,7 @@ namespace Emby.Server.Implementations.Localization var ratingsDictionary = GetParentalRatingsDictionary(); - if (ratingsDictionary.TryGetValue(rating, out ParentalRating value)) + if (ratingsDictionary.TryGetValue(rating, out ParentalRating? value)) { return value.Value; } @@ -268,20 +270,6 @@ namespace Emby.Server.Implementations.Localization return null; } - /// - public bool HasUnicodeCategory(string value, UnicodeCategory category) - { - foreach (var chr in value) - { - if (char.GetUnicodeCategory(chr) == category) - { - return true; - } - } - - return false; - } - /// public string GetLocalizedString(string phrase) { @@ -347,18 +335,21 @@ namespace Emby.Server.Implementations.Localization { await using var stream = _assembly.GetManifestResourceStream(resourcePath); // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain - if (stream != null) + if (stream == null) { - var dict = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions).ConfigureAwait(false); + _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath); + return; + } - foreach (var key in dict.Keys) - { - dictionary[key] = dict[key]; - } + var dict = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions).ConfigureAwait(false); + if (dict == null) + { + throw new InvalidOperationException($"Resource contains invalid data: '{stream}'"); } - else + + foreach (var key in dict.Keys) { - _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath); + dictionary[key] = dict[key]; } } diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index 8a6d28e0f6..afda83a7c5 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -47,10 +47,10 @@ namespace MediaBrowser.Common.Plugins var assemblyFilePath = assembly.Location; var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - if (!Directory.Exists(dataFolderPath) && Version != null) + if (Version != null && !Directory.Exists(dataFolderPath)) { // Try again with the version number appended to the folder name. - dataFolderPath = dataFolderPath + "_" + Version.ToString(); + dataFolderPath += "_" + Version.ToString(); } SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 97f40b5372..abfdb41d80 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -1,6 +1,5 @@ -#nullable disable - using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Jellyfin.Extensions; @@ -16,7 +15,7 @@ namespace MediaBrowser.Controller.BaseItemManager { private readonly IServerConfigurationManager _serverConfigurationManager; - private int _metadataRefreshConcurrency = 0; + private int _metadataRefreshConcurrency; /// /// Initializes a new instance of the class. @@ -101,7 +100,7 @@ namespace MediaBrowser.Controller.BaseItemManager /// Called when the configuration is updated. /// It will refresh the metadata throttler if the relevant config changed. /// - private void OnConfigurationUpdated(object sender, EventArgs e) + private void OnConfigurationUpdated(object? sender, EventArgs e) { int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency(); if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency) @@ -114,6 +113,7 @@ namespace MediaBrowser.Controller.BaseItemManager /// /// Creates the metadata refresh throttler. /// + [MemberNotNull(nameof(MetadataRefreshThrottler))] private void SetupMetadataThrottler() { MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency); diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs index b2b36c040b..e18994214e 100644 --- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; @@ -34,4 +32,4 @@ namespace MediaBrowser.Controller.BaseItemManager /// true if image fetcher is enabled, else false. bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name); } -} \ No newline at end of file +} diff --git a/MediaBrowser.Controller/Channels/ChannelItemResult.cs b/MediaBrowser.Controller/Channels/ChannelItemResult.cs index 7a0addd9f9..ca7721991d 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemResult.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemResult.cs @@ -1,7 +1,6 @@ -#nullable disable - -#pragma warning disable CA1002, CA2227, CS1591 +#pragma warning disable CS1591 +using System; using System.Collections.Generic; namespace MediaBrowser.Controller.Channels @@ -10,10 +9,10 @@ namespace MediaBrowser.Controller.Channels { public ChannelItemResult() { - Items = new List(); + Items = Array.Empty(); } - public List Items { get; set; } + public IReadOnlyList Items { get; set; } public int? TotalRecordCount { get; set; } } diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index 76ad335c59..30f5f4efa2 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -1,6 +1,6 @@ #nullable disable -#pragma warning disable CA2227, CS1591 +#pragma warning disable CS1591 using System; using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs index 8155cf3dbd..e538fa4b3f 100644 --- a/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs +++ b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index 49cc39f047..b8c33ee5a0 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -16,17 +14,17 @@ namespace MediaBrowser.Controller.Collections /// /// Occurs when [collection created]. /// - event EventHandler CollectionCreated; + event EventHandler? CollectionCreated; /// /// Occurs when [items added to collection]. /// - event EventHandler ItemsAddedToCollection; + event EventHandler? ItemsAddedToCollection; /// /// Occurs when [items removed from collection]. /// - event EventHandler ItemsRemovedFromCollection; + event EventHandler? ItemsRemovedFromCollection; /// /// Creates the collection. diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index 44e2c45dd4..43ad04dbac 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9bae95a272..141bb91c57 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -16,9 +15,7 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using Microsoft.Extensions.Configuration; namespace MediaBrowser.Controller.MediaEncoding { diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index baefeb39cf..b213e7aa00 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -1,4 +1,3 @@ -#nullable disable using System.Collections.Generic; using System.Globalization; using MediaBrowser.Model.Entities; @@ -56,19 +55,11 @@ namespace MediaBrowser.Model.Globalization /// . IEnumerable GetLocalizationOptions(); - /// - /// Checks if the string contains a character with the specified unicode category. - /// - /// The string. - /// The unicode category. - /// Wether or not the string contains a character with the specified unicode category. - bool HasUnicodeCategory(string value, UnicodeCategory category); - /// /// Returns the correct for the given language. /// /// The language. /// The correct for the given language. - CultureDto FindLanguageInfo(string language); + CultureDto? FindLanguageInfo(string language); } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 2c86f9242d..1125154ac8 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -148,80 +148,76 @@ namespace MediaBrowser.XbmcMetadata.Parsers return; } - using (var fileStream = File.OpenRead(metadataFile)) - using (var streamReader = new StreamReader(fileStream, Encoding.UTF8)) - { - item.ResetPeople(); - - // Need to handle a url after the xml data - // http://kodi.wiki/view/NFO_files/movies + item.ResetPeople(); - var xml = streamReader.ReadToEnd(); + // Need to handle a url after the xml data + // http://kodi.wiki/view/NFO_files/movies - // Find last closing Tag - // Need to do this in two steps to account for random > characters after the closing xml - var index = xml.LastIndexOf(@"', index); - } + // Find last closing Tag + // Need to do this in two steps to account for random > characters after the closing xml + var index = xml.LastIndexOf(@"', index); + } - ParseProviderLinks(item.Item, endingXml); + if (index != -1) + { + var endingXml = xml.AsSpan().Slice(index); - // If the file is just an imdb url, don't go any further - if (index == 0) - { - return; - } + ParseProviderLinks(item.Item, endingXml); - xml = xml.Substring(0, index + 1); - } - else + // If the file is just an imdb url, don't go any further + if (index == 0) { - // If the file is just provider urls, handle that - ParseProviderLinks(item.Item, xml); - return; } - // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions - try + xml = xml.Substring(0, index + 1); + } + else + { + // If the file is just provider urls, handle that + ParseProviderLinks(item.Item, xml); + + return; + } + + // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions + try + { + using (var stringReader = new StringReader(xml)) + using (var reader = XmlReader.Create(stringReader, settings)) { - using (var stringReader = new StringReader(xml)) - using (var reader = XmlReader.Create(stringReader, settings)) + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) { - reader.MoveToContent(); - reader.Read(); + cancellationToken.ThrowIfCancellationRequested(); - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + if (reader.NodeType == XmlNodeType.Element) { - cancellationToken.ThrowIfCancellationRequested(); - - if (reader.NodeType == XmlNodeType.Element) - { - FetchDataFromXmlNode(reader, item); - } - else - { - reader.Read(); - } + FetchDataFromXmlNode(reader, item); + } + else + { + reader.Read(); } } } - catch (XmlException) - { - } + } + catch (XmlException) + { } } - protected void ParseProviderLinks(T item, string xml) + protected void ParseProviderLinks(T item, ReadOnlySpan xml) { if (ProviderIdParsers.TryFindImdbId(xml, out var imdbId)) { diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index 6b16075305..ca3ec79b78 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -40,72 +40,68 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// protected override void Fetch(MetadataResult item, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken) { - using (var fileStream = File.OpenRead(metadataFile)) - using (var streamReader = new StreamReader(fileStream, Encoding.UTF8)) - { - item.ResetPeople(); + item.ResetPeople(); - var xmlFile = streamReader.ReadToEnd(); + var xmlFile = File.ReadAllText(metadataFile); - var srch = ""; - var index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase); + var srch = ""; + var index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - var xml = xmlFile; + var xml = xmlFile; - if (index != -1) - { - xml = xmlFile.Substring(0, index + srch.Length); - xmlFile = xmlFile.Substring(index + srch.Length); - } + if (index != -1) + { + xml = xmlFile.Substring(0, index + srch.Length); + xmlFile = xmlFile.Substring(index + srch.Length); + } - // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions - try + // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions + try + { + // Extract episode details from the first episodedetails block + using (var stringReader = new StringReader(xml)) + using (var reader = XmlReader.Create(stringReader, settings)) { - // Extract episode details from the first episodedetails block - using (var stringReader = new StringReader(xml)) - using (var reader = XmlReader.Create(stringReader, settings)) + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) { - reader.MoveToContent(); - reader.Read(); + cancellationToken.ThrowIfCancellationRequested(); - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + if (reader.NodeType == XmlNodeType.Element) { - cancellationToken.ThrowIfCancellationRequested(); - - if (reader.NodeType == XmlNodeType.Element) - { - FetchDataFromXmlNode(reader, item); - } - else - { - reader.Read(); - } + FetchDataFromXmlNode(reader, item); + } + else + { + reader.Read(); } } + } - // Extract the last episode number from nfo - // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag - while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1) + // Extract the last episode number from nfo + // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag + while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1) + { + xml = xmlFile.Substring(0, index + srch.Length); + xmlFile = xmlFile.Substring(index + srch.Length); + + using (var stringReader = new StringReader(xml)) + using (var reader = XmlReader.Create(stringReader, settings)) { - xml = xmlFile.Substring(0, index + srch.Length); - xmlFile = xmlFile.Substring(index + srch.Length); + reader.MoveToContent(); - using (var stringReader = new StringReader(xml)) - using (var reader = XmlReader.Create(stringReader, settings)) + if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num)) { - reader.MoveToContent(); - - if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num)) - { - item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num); - } + item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num); } } } - catch (XmlException) - { - } + } + catch (XmlException) + { } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs index edd4b1e558..143020d436 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -66,7 +66,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization var germany = localizationManager.FindLanguageInfo(identifier); Assert.NotNull(germany); - Assert.Equal("ger", germany.ThreeLetterISOLanguageName); + Assert.Equal("ger", germany!.ThreeLetterISOLanguageName); Assert.Equal("German", germany.DisplayName); Assert.Equal("German", germany.Name); Assert.Contains("deu", germany.ThreeLetterISOLanguageNames); From 3300d2d7d3086b84e0158c8e507b2412d54ebe04 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Tue, 27 Jul 2021 14:02:49 +0200 Subject: [PATCH 050/118] Enable people for audio files --- MediaBrowser.Controller/Entities/Audio/Audio.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 7bf1219ec2..536668e508 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Controller.Entities.Audio public override bool SupportsPlayedStatus => true; [JsonIgnore] - public override bool SupportsPeople => false; + public override bool SupportsPeople => true; [JsonIgnore] public override bool SupportsAddingToPlaylist => true; From 7e71c25059d373818f228cc524ac4d68f1ea0602 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Tue, 27 Jul 2021 14:46:18 +0200 Subject: [PATCH 051/118] Add test for audio file ffprobe normalization --- .../Probing/ProbeResultNormalizerTests.cs | 28 ++++ .../Test Data/Probing/music_metadata.json | 144 ++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_metadata.json diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 59037c2636..690c5d1c14 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text.Json; using Jellyfin.Extensions.Json; using MediaBrowser.MediaEncoding.Probing; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging.Abstractions; @@ -91,5 +92,32 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Contains("Pop", res.Genres); Assert.Contains("Jazz", res.Genres); } + + [Fact] + public void GetMediaInfo_Music_Success() + { + var bytes = File.ReadAllBytes("Test Data/Probing/music_metadata.json"); + var internalMediaInfoResult = JsonSerializer.Deserialize(bytes, _jsonOptions); + MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, null, true, "Test Data/Probing/music.flac", MediaProtocol.File); + + Assert.Equal("UP NO MORE", res.Name); + Assert.Single(res.Artists); + Assert.Equal("TWICE", res.Artists[0]); + Assert.Equal("Eyes wide open", res.Album); + Assert.Equal(2020, res.ProductionYear); + Assert.True(res.PremiereDate.HasValue); + Assert.Equal(DateTime.Parse("2020-10-26T00:00Z", DateTimeFormatInfo.CurrentInfo).ToUniversalTime(), res.PremiereDate); + Assert.NotEmpty(res.People); + Assert.Equal("Krysta Youngs", res.People[0].Name); + Assert.Equal(PersonType.Composer, res.People[0].Type); + Assert.Equal("Julia Ross", res.People[1].Name); + Assert.Equal(PersonType.Composer, res.People[1].Type); + Assert.Equal("Yiwoomin", res.People[2].Name); + Assert.Equal(PersonType.Composer, res.People[2].Type); + Assert.Equal("Ji-hyo Park", res.People[3].Name); + Assert.Equal(PersonType.Lyricist, res.People[3].Type); + Assert.NotEmpty(res.Genres); + Assert.Equal(new string[] { "Electronic", "Trance", "Dance", "Jazz" }, res.Genres); + } } } diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_metadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_metadata.json new file mode 100644 index 0000000000..6530629fe8 --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_metadata.json @@ -0,0 +1,144 @@ +{ + "streams": [ + { + "index": 0, + "codec_name": "flac", + "codec_long_name": "FLAC (Free Lossless Audio Codec)", + "codec_type": "audio", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "sample_fmt": "s16", + "sample_rate": "44100", + "channels": 2, + "channel_layout": "stereo", + "bits_per_sample": 0, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/44100", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 9447984, + "duration": "214.240000", + "bits_per_raw_sample": "16", + "disposition": { + "default": 0, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + } + }, + { + "index": 1, + "codec_name": "mjpeg", + "codec_long_name": "Motion JPEG", + "profile": "Baseline", + "codec_type": "video", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "width": 500, + "height": 500, + "coded_width": 500, + "coded_height": 500, + "closed_captions": 0, + "has_b_frames": 0, + "sample_aspect_ratio": "1:1", + "display_aspect_ratio": "1:1", + "pix_fmt": "yuvj420p", + "level": -99, + "color_range": "pc", + "color_space": "bt470bg", + "chroma_location": "center", + "refs": 1, + "r_frame_rate": "90000/1", + "avg_frame_rate": "0/0", + "time_base": "1/90000", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 19281600, + "duration": "214.240000", + "bits_per_raw_sample": "8", + "disposition": { + "default": 0, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 1, + "timed_thumbnails": 0 + }, + "tags": { + "comment": "Cover (front)" + } + } + ], + "format": { + "filename": "03 UP NO MORE.flac", + "nb_streams": 2, + "nb_programs": 0, + "format_name": "flac", + "format_long_name": "raw FLAC", + "start_time": "0.000000", + "duration": "214.240000", + "size": "28714641", + "bit_rate": "1072242", + "probe_score": 100, + "tags": { + "MUSICBRAINZ_RELEASEGROUPID": "aa05ff10-8589-4c9c-a0d4-6b024f4e4556", + "ORIGINALDATE": "2020-10-26", + "ORIGINALYEAR": "2020", + "RELEASETYPE": "album", + "MUSICBRAINZ_ALBUMID": "222e6610-75c9-400e-8dc3-bb61f9fc5ca7", + "SCRIPT": "Latn", + "ALBUM": "Eyes wide open", + "RELEASECOUNTRY": "JP", + "BARCODE": "190295105280", + "LABEL": "JYP Entertainment", + "RELEASESTATUS": "official", + "DATE": "2020-10-26", + "MUSICBRAINZ_ALBUMARTISTID": "8da127cc-c432-418f-b356-ef36210d82ac", + "album_artist": "TWICE", + "ALBUMARTISTSORT": "TWICE", + "TOTALDISCS": "1", + "TOTALTRACKS": "13", + "MEDIA": "Digital Media", + "disc": "1", + "MUSICBRAINZ_TRACKID": "7d1a1044-b564-480d-9df3-22f9656fdb97", + "TITLE": "UP NO MORE", + "ISRC": "US5TA2000136", + "PERFORMER": "Yiwoomin (electric piano);Yiwoomin (synthesizer);Yiwoomin (bass);Yiwoomin (guitar);TWICE;Tzu-yu Chou (vocals);Momo Hirai (vocals);Na-yeon Im (vocals);Da-hyun Kim (vocals);Sana Minatozaki (vocals);Mina Myoui (vocals);Ji-hyo Park (vocals);Chae-young Son (vocals);Jeong-yeon Yoo (vocals);Perrie (background vocals)", + "MIXER": "Bong Won Shin", + "ARRANGER": "Krysta Youngs;Julia Ross;Yiwoomin", + "MUSICBRAINZ_WORKID": "02b37083-0337-4721-9f17-bf31971043e8", + "LANGUAGE": "kor;eng", + "WORK": "Up No More", + "COMPOSER": "Krysta Youngs;Julia Ross;Yiwoomin", + "COMPOSERSORT": "Krysta Youngs;Ross, Julia;Yiwoomin", + "LYRICIST": "Ji-hyo Park", + "MUSICBRAINZ_ARTISTID": "8da127cc-c432-418f-b356-ef36210d82ac", + "ARTIST": "TWICE", + "ARTISTSORT": "TWICE", + "ARTISTS": "TWICE", + "MUSICBRAINZ_RELEASETRACKID": "ad49b840-da9e-4e7c-924b-29fdee187052", + "track": "3", + "GENRE": "Electronic;Trance;Dance;Jazz", + "WEBSITE": "http://twice.jype.com/;http://www.twicejapan.com/", + "ACOUSTID_ID": "aae2e972-108c-4d0c-8e31-9d078283e3dc", + "MOOD": "Not acoustic;Not aggressive;Electronic;Happy;Party;Not relaxed;Not sad", + "TRACKTOTAL": "13", + "DISCTOTAL": "1" + } + } +} From 24083d2e38c17e005c536339d555355b9155485f Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Tue, 27 Jul 2021 16:25:46 +0200 Subject: [PATCH 052/118] Address comments --- .../Probing/ProbeResultNormalizerTests.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 690c5d1c14..4d6b7a5140 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -107,7 +107,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal(2020, res.ProductionYear); Assert.True(res.PremiereDate.HasValue); Assert.Equal(DateTime.Parse("2020-10-26T00:00Z", DateTimeFormatInfo.CurrentInfo).ToUniversalTime(), res.PremiereDate); - Assert.NotEmpty(res.People); + Assert.Equal(4, res.People.Length); Assert.Equal("Krysta Youngs", res.People[0].Name); Assert.Equal(PersonType.Composer, res.People[0].Type); Assert.Equal("Julia Ross", res.People[1].Name); @@ -116,8 +116,11 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal(PersonType.Composer, res.People[2].Type); Assert.Equal("Ji-hyo Park", res.People[3].Name); Assert.Equal(PersonType.Lyricist, res.People[3].Type); - Assert.NotEmpty(res.Genres); - Assert.Equal(new string[] { "Electronic", "Trance", "Dance", "Jazz" }, res.Genres); + Assert.Equal(4, res.Genres.Length); + Assert.Contains("Electronic", res.Genres); + Assert.Contains("Trance", res.Genres); + Assert.Contains("Dance", res.Genres); + Assert.Contains("Jazz", res.Genres); } } } From f35a527608fb3f6efde3f76042e0e701768eb3f2 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Tue, 27 Jul 2021 17:09:23 +0200 Subject: [PATCH 053/118] Add performers to the ffprobe normalization for audio --- .../Probing/ProbeResultNormalizer.cs | 21 +++++++++++++++++++ .../Probing/ProbeResultNormalizerTests.cs | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 875ee6f04c..20dead0771 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Xml; using Jellyfin.Extensions; using MediaBrowser.Controller.Library; @@ -1111,6 +1112,26 @@ namespace MediaBrowser.MediaEncoding.Probing } } + if (tags.TryGetValue("performer", out var performer) && !string.IsNullOrWhiteSpace(performer)) + { + foreach (var person in Split(performer, false)) + { + Regex pattern = new Regex(@"(?.*) \((?.*)\)"); + Match match = pattern.Match(person); + + // If the performer doesn't have any instrument/role associated, it won't match. In that case, chances are it's simply a band name, so we skip it. + if (match.Success) + { + people.Add(new BaseItemPerson + { + Name = match.Groups["name"].Value, + Type = PersonType.Actor, + Role = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value) + }); + } + } + } + // Check for writer some music is tagged that way as alternative to composer/lyricist if (tags.TryGetValue("writer", out var writer) && !string.IsNullOrWhiteSpace(writer)) { diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 4d6b7a5140..928f275d7e 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -107,7 +107,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal(2020, res.ProductionYear); Assert.True(res.PremiereDate.HasValue); Assert.Equal(DateTime.Parse("2020-10-26T00:00Z", DateTimeFormatInfo.CurrentInfo).ToUniversalTime(), res.PremiereDate); - Assert.Equal(4, res.People.Length); + Assert.Equal(18, res.People.Length); Assert.Equal("Krysta Youngs", res.People[0].Name); Assert.Equal(PersonType.Composer, res.People[0].Type); Assert.Equal("Julia Ross", res.People[1].Name); @@ -116,6 +116,9 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal(PersonType.Composer, res.People[2].Type); Assert.Equal("Ji-hyo Park", res.People[3].Name); Assert.Equal(PersonType.Lyricist, res.People[3].Type); + Assert.Equal("Yiwoomin", res.People[4].Name); + Assert.Equal(PersonType.Actor, res.People[4].Type); + Assert.Equal("Electric Piano", res.People[4].Role); Assert.Equal(4, res.Genres.Length); Assert.Contains("Electronic", res.Genres); Assert.Contains("Trance", res.Genres); From c9b1cd1d8cfa3764eff3dfe97baac10ce229d2e9 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Tue, 27 Jul 2021 23:52:05 +0200 Subject: [PATCH 054/118] Add some new music-related person types and parse from ffprobe --- .../Probing/ProbeResultNormalizer.cs | 34 ++++++++++++++++- MediaBrowser.Model/Entities/PersonType.cs | 38 ++++++++++++++----- .../Probing/ProbeResultNormalizerTests.cs | 2 +- 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 20dead0771..aa6674468b 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1132,7 +1132,7 @@ namespace MediaBrowser.MediaEncoding.Probing } } - // Check for writer some music is tagged that way as alternative to composer/lyricist + // In cases where there isn't sufficient information as to which role a writer performed on a recording, tagging software uses the "writer" tag. if (tags.TryGetValue("writer", out var writer) && !string.IsNullOrWhiteSpace(writer)) { foreach (var person in Split(writer, false)) @@ -1141,6 +1141,38 @@ namespace MediaBrowser.MediaEncoding.Probing } } + if (tags.TryGetValue("arranger", out var arranger) && !string.IsNullOrWhiteSpace(arranger)) + { + foreach (var person in Split(arranger, false)) + { + people.Add(new BaseItemPerson { Name = person, Type = PersonType.Arranger }); + } + } + + if (tags.TryGetValue("engineer", out var engineer) && !string.IsNullOrWhiteSpace(engineer)) + { + foreach (var person in Split(engineer, false)) + { + people.Add(new BaseItemPerson { Name = person, Type = PersonType.Engineer }); + } + } + + if (tags.TryGetValue("mixer", out var mixer) && !string.IsNullOrWhiteSpace(mixer)) + { + foreach (var person in Split(mixer, false)) + { + people.Add(new BaseItemPerson { Name = person, Type = PersonType.Mixer }); + } + } + + if (tags.TryGetValue("remixer", out var remixer) && !string.IsNullOrWhiteSpace(remixer)) + { + foreach (var person in Split(remixer, false)) + { + people.Add(new BaseItemPerson { Name = person, Type = PersonType.Remixer }); + } + } + audio.People = people.ToArray(); // Set album artist diff --git a/MediaBrowser.Model/Entities/PersonType.cs b/MediaBrowser.Model/Entities/PersonType.cs index 81db9c6131..7b780f7d7b 100644 --- a/MediaBrowser.Model/Entities/PersonType.cs +++ b/MediaBrowser.Model/Entities/PersonType.cs @@ -1,42 +1,42 @@ namespace MediaBrowser.Model.Entities { /// - /// Struct PersonType. + /// Types of persons. /// - public class PersonType + public static class PersonType { /// - /// The actor. + /// A person whose profession is acting on the stage, in films, or on television. /// public const string Actor = "Actor"; /// - /// The director. + /// A person who supervises the actors and other staff in a film, play, or similar production. /// public const string Director = "Director"; /// - /// The composer. + /// A person who writes music, especially as a professional occupation. /// public const string Composer = "Composer"; /// - /// The writer. + /// A writer of a book, article, or document. Can also be used as a generic term for music writer if there is a lack of specificity. /// public const string Writer = "Writer"; /// - /// The guest star. + /// A well-known actor or other performer who appears in a work in which they do not have a regular role. /// public const string GuestStar = "GuestStar"; /// - /// The producer. + /// A person responsible for the financial and managerial aspects of the making of a film or broadcast or for staging a play, opera, etc. /// public const string Producer = "Producer"; /// - /// The conductor. + /// A person who directs the performance of an orchestra or choir. /// public const string Conductor = "Conductor"; @@ -44,5 +44,25 @@ namespace MediaBrowser.Model.Entities /// The lyricist. /// public const string Lyricist = "Lyricist"; + + /// + /// A person who writes the words to a song or musical. + /// + public const string Arranger = "Arranger"; + + /// + /// An audio engineer who performed a general engineering role. + /// + public const string Engineer = "Engineer"; + + /// + /// An engineer responsible for using a mixing console to mix a recorded track into a single piece of music suitable for release. + /// + public const string Mixer = "Mixer"; + + /// + /// A person who remixed a recording by taking one or more other tracks, substantially altering them and mixing them together with other material. + /// + public const string Remixer = "Remixer"; } } diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 928f275d7e..fcb85a3acf 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -107,7 +107,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal(2020, res.ProductionYear); Assert.True(res.PremiereDate.HasValue); Assert.Equal(DateTime.Parse("2020-10-26T00:00Z", DateTimeFormatInfo.CurrentInfo).ToUniversalTime(), res.PremiereDate); - Assert.Equal(18, res.People.Length); + Assert.Equal(22, res.People.Length); Assert.Equal("Krysta Youngs", res.People[0].Name); Assert.Equal(PersonType.Composer, res.People[0].Type); Assert.Equal("Julia Ross", res.People[1].Name); From 8594ee7a2275403d0569529cd1e1237e10cc7d77 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Wed, 28 Jul 2021 23:25:45 +0200 Subject: [PATCH 055/118] Fix documentation for lyricist and arranger --- MediaBrowser.Model/Entities/PersonType.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Model/Entities/PersonType.cs b/MediaBrowser.Model/Entities/PersonType.cs index 7b780f7d7b..b985507f0c 100644 --- a/MediaBrowser.Model/Entities/PersonType.cs +++ b/MediaBrowser.Model/Entities/PersonType.cs @@ -41,12 +41,12 @@ namespace MediaBrowser.Model.Entities public const string Conductor = "Conductor"; /// - /// The lyricist. + /// A person who writes the words to a song or musical. /// public const string Lyricist = "Lyricist"; /// - /// A person who writes the words to a song or musical. + /// A person who adapts a musical composition for performance. /// public const string Arranger = "Arranger"; From d82c2e423766566883be850d60e94cc36656f0e8 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Mon, 2 Aug 2021 03:48:45 +0200 Subject: [PATCH 056/118] Address comments --- .../Probing/ProbeResultNormalizer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index aa6674468b..cdfdbc5dad 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -28,7 +28,9 @@ namespace MediaBrowser.MediaEncoding.Probing private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' }; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly Regex _performerPattern = new (@"(?.*) \((?.*)\)"); + + private readonly CultureInfo _usCulture = new ("en-US"); private readonly ILogger _logger; private readonly ILocalizationManager _localization; @@ -1116,8 +1118,7 @@ namespace MediaBrowser.MediaEncoding.Probing { foreach (var person in Split(performer, false)) { - Regex pattern = new Regex(@"(?.*) \((?.*)\)"); - Match match = pattern.Match(person); + Match match = _performerPattern.Match(person); // If the performer doesn't have any instrument/role associated, it won't match. In that case, chances are it's simply a band name, so we skip it. if (match.Success) From 7f52cda03c9731fcbd57fd8c9ed8806c6965d054 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 7 Aug 2021 21:58:19 +0200 Subject: [PATCH 057/118] Make performer regex static --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index cdfdbc5dad..e83e533c92 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.MediaEncoding.Probing private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' }; - private readonly Regex _performerPattern = new (@"(?.*) \((?.*)\)"); + private static readonly Regex _performerPattern = new (@"(?.*) \((?.*)\)"); private readonly CultureInfo _usCulture = new ("en-US"); private readonly ILogger _logger; From 10fbb4d48d772e287cc3a03045b22e1225681935 Mon Sep 17 00:00:00 2001 From: Rob Date: Sun, 15 Aug 2021 15:23:18 -0700 Subject: [PATCH 058/118] Update tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs Thanks for this suggestion. I will try to keep this in mind going forward with future PRs Co-authored-by: Claus Vium --- .../Sorting/AiredEpisodeOrderComparerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs index ff7999612b..7d7a110b2d 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting { [Theory] [ClassData(typeof(EpisodeBadData))] - public void AiredEpisodeOrderCompareErrorTest(BaseItem x, BaseItem y) + public void Compare_GivenNull_ThrowsArgumentNullException(BaseItem x, BaseItem y) { var cmp = new AiredEpisodeOrderComparer(); Assert.Throws(() => cmp.Compare(x, y)); From 9220682fd11e1dc9872959eb1dcf2f1dac38066c Mon Sep 17 00:00:00 2001 From: Rob Date: Sun, 15 Aug 2021 15:26:00 -0700 Subject: [PATCH 059/118] Update tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs Co-authored-by: Bond-009 --- .../Sorting/AiredEpisodeOrderComparerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs index 7d7a110b2d..61cec81124 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs @@ -37,7 +37,7 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting public IEnumerator GetEnumerator() { yield return new object?[] { null, new Episode() }; - yield return new object?[] { new Episode() }; + yield return new object?[] { new Episode(), null }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); From 0f9e95b4c5ad15c6779c3241bc581313a234b357 Mon Sep 17 00:00:00 2001 From: ankenyr Date: Sun, 15 Aug 2021 15:31:58 -0700 Subject: [PATCH 060/118] Breaking up the test theory so episodes and the results are on their own lines. --- .../Sorting/AiredEpisodeOrderComparerTests.cs | 140 +++++++++++++++--- 1 file changed, 120 insertions(+), 20 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs index 61cec81124..2733c55acc 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs @@ -47,34 +47,134 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting { public IEnumerator GetEnumerator() { - yield return new object?[] { new Movie(), new Movie(), 0 }; - yield return new object?[] { new Movie(), new Episode(), 1 }; + yield return new object?[] + { + new Movie(), + new Movie(), + 0 + }; + yield return new object?[] + { + new Movie(), + new Episode(), + 1 + }; // Good cases - yield return new object?[] { new Episode(), new Episode(), 0 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 0 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 2, IndexNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1 }; + yield return new object?[] + { + new Episode(), + new Episode(), + 0 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, + new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, + 0 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, + new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, + 1 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 2, IndexNumber = 1 }, + new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, + 1 + }; // Good Specials - yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 0 }; - yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1 }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, + 0 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, + 1 + }; // Specials to Episodes - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1 }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, + 1 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, + 1 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, + 1 + }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, 1 }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1 }, + 1 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 2 }, + 1 + }; - yield return new object?[] { new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, 1 }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, + new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, + 1 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 }, + 1 + }; - yield return new object?[] { new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1 }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 3, IndexNumber = 1 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, + 1 + }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 0 }; - yield return new object?[] { new Episode { ParentIndexNumber = 1, IndexNumber = 3 }, new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, 1 }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 1 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1 }, + 1 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 2 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, + 1 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, + 0 + }; + yield return new object?[] + { + new Episode { ParentIndexNumber = 1, IndexNumber = 3 }, + new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 }, + 1 + }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); From 6ba7db3d55b2172e3344a040019a0bf25943a35b Mon Sep 17 00:00:00 2001 From: ankenyr Date: Sun, 15 Aug 2021 15:48:01 -0700 Subject: [PATCH 061/118] Removing if statement and always testing reverse of x and y. --- .../Sorting/AiredEpisodeOrderComparerTests.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs index 2733c55acc..d9b206f663 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs @@ -26,10 +26,7 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting var cmp = new AiredEpisodeOrderComparer(); Assert.Equal(expected, cmp.Compare(x, y)); - if (expected == 1) - { - Assert.Equal(-expected, cmp.Compare(y, x)); - } + Assert.Equal(-expected, cmp.Compare(y, x)); } private class EpisodeBadData : IEnumerable From 33bf8e34d1c4e5d6d89e21a38d23d2f801079397 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 15 Aug 2021 18:57:14 -0600 Subject: [PATCH 062/118] Update Dockerfile to use debian:bullseye-slim --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0859fdc4c8..3190fec5c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine- && npm ci --no-audit --unsafe-perm \ && mv dist /dist -FROM debian:buster-slim as app +FROM debian:bullseye-slim as app # https://askubuntu.com/questions/972516/debian-frontend-environment-variable ARG DEBIAN_FRONTEND="noninteractive" From b504d1f724c7786a0f71437aacbdbe04bd831d0a Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 15 Aug 2021 19:32:45 -0600 Subject: [PATCH 063/118] Also update arm, arm64 Dockerfile --- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.arm b/Dockerfile.arm index cc0c79c94f..dcd006ff83 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine- && mv dist /dist FROM multiarch/qemu-user-static:x86_64-arm as qemu -FROM arm32v7/debian:buster-slim as app +FROM arm32v7/debian:bullseye-slim as app # https://askubuntu.com/questions/972516/debian-frontend-environment-variable ARG DEBIAN_FRONTEND="noninteractive" diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 64367a32da..7311c6b9ff 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine- && mv dist /dist FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu -FROM arm64v8/debian:buster-slim as app +FROM arm64v8/debian:bullseye-slim as app # https://askubuntu.com/questions/972516/debian-frontend-environment-variable ARG DEBIAN_FRONTEND="noninteractive" From e3c2a8a3bef7043a2c347472bb01d8b31fe00fbc Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 16 Aug 2021 08:52:16 +0200 Subject: [PATCH 064/118] Update Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs --- Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index de178ad5b1..5941613cf9 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts try { var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false); - var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))); + var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))).ToList(); list.AddRange(newChannels); From 05103a483eaf9fa3f2ab21d1c3f4032fc285e5bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 12:00:51 +0000 Subject: [PATCH 065/118] Bump Serilog.Settings.Configuration from 3.1.0 to 3.2.0 Bumps [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/serilog/serilog-settings-configuration/releases) - [Changelog](https://github.com/serilog/serilog-settings-configuration/blob/dev/CHANGES.md) - [Commits](https://github.com/serilog/serilog-settings-configuration/compare/v3.1.0...v3.2.0) --- updated-dependencies: - dependency-name: Serilog.Settings.Configuration dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 4ad39c60a9..ea64663bd7 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -39,7 +39,7 @@ - + From 4077cb2648fad935a3c591536a24473c6a1efd3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 12:00:55 +0000 Subject: [PATCH 066/118] Bump FsCheck.Xunit from 2.15.3 to 2.16.0 Bumps [FsCheck.Xunit](https://github.com/fsharp/FsCheck) from 2.15.3 to 2.16.0. - [Release notes](https://github.com/fsharp/FsCheck/releases) - [Changelog](https://github.com/fscheck/FsCheck/blob/master/FsCheck%20Release%20Notes.md) - [Commits](https://github.com/fsharp/FsCheck/compare/2.15.3...2.16.0) --- updated-dependencies: - dependency-name: FsCheck.Xunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj | 2 +- .../Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj | 2 +- tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj | 2 +- .../Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index e4350c3369..2eb983e878 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -16,7 +16,7 @@ - +
diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index 9272d5eef9..9dcb80baba 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -17,7 +17,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index 06ff22c7e0..06e8050a4d 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index 2c6e2e5f66..1199c9649a 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -16,7 +16,7 @@ - + From ee37784a35c28c36659eb2a98367e5968a847162 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 12:01:04 +0000 Subject: [PATCH 067/118] Bump Microsoft.NET.Test.Sdk from 16.10.0 to 16.11.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.10.0 to 16.11.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.10.0...v16.11.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj | 2 +- .../Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj | 2 +- tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj | 2 +- .../Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj | 2 +- .../Jellyfin.MediaEncoding.Tests.csproj | 2 +- tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj | 2 +- tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj | 2 +- .../Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj | 2 +- tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- .../Jellyfin.XbmcMetadata.Tests.csproj | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index ecf7d2b364..0c36e81cca 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index e4350c3369..299d3c0f33 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index 5b269a4b20..a5778b59c8 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index 713f6423c9..5a48631c29 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index 9272d5eef9..6e5ff31b54 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -7,7 +7,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index a6a948e2b7..7ea5039138 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index 06ff22c7e0..f5aa1549fd 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 510c8f60a3..a4ebab141e 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index 2c6e2e5f66..2b7e86742e 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj index 195fc8801d..d9e33617bc 100644 --- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 387f259ce3..9b6ab7bdf5 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -21,7 +21,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 8f2eb9ef58..592b444c99 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 6f3c532cd0..f249be674c 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index 78837bba67..e085907583 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -13,7 +13,7 @@ - + From f23ef1f1b9982f68d03e7095593ee56667769071 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 17 Aug 2021 13:38:28 +0200 Subject: [PATCH 068/118] Use ProgressiveFileStream for LiveRecordings endpoint --- Jellyfin.Api/Controllers/LiveTvController.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 24ee833ef7..47ebe9f579 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1172,7 +1172,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesVideoFile] - public async Task GetLiveRecordingFile([FromRoute, Required] string recordingId) + public ActionResult GetLiveRecordingFile([FromRoute, Required] string recordingId) { var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId); @@ -1181,11 +1181,8 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - await using var memoryStream = new MemoryStream(); - await new ProgressiveFileCopier(path, null, _transcodingJobHelper, CancellationToken.None) - .WriteToAsync(memoryStream, CancellationToken.None) - .ConfigureAwait(false); - return File(memoryStream, MimeTypes.GetMimeType(path)); + var stream = new ProgressiveFileStream(path, null, _transcodingJobHelper); + return new FileStreamResult(stream, MimeTypes.GetMimeType(path)); } /// From 12a7fda0a6216c506853941eeabaca6367e0a8a8 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 17 Aug 2021 19:18:26 +0200 Subject: [PATCH 069/118] Add timeout to ProgressiveFileStream --- Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index 824870c7ef..499dbe84d6 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading; @@ -16,6 +17,7 @@ namespace Jellyfin.Api.Helpers private readonly FileStream _fileStream; private readonly TranscodingJobDto? _job; private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly int _timeoutMs; private readonly bool _allowAsyncFileRead; private int _bytesWritten; private bool _disposed; @@ -26,10 +28,12 @@ namespace Jellyfin.Api.Helpers /// The path to the transcoded file. /// The transcoding job information. /// The transcoding job helper. - public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper) + /// The timeout duration in milliseconds. + public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000) { _job = job; _transcodingJobHelper = transcodingJobHelper; + _timeoutMs = timeoutMs; _bytesWritten = 0; var fileOptions = FileOptions.SequentialScan; @@ -81,6 +85,7 @@ namespace Jellyfin.Api.Helpers { int totalBytesRead = 0; int remainingBytesToRead = count; + var stopwatch = Stopwatch.StartNew(); int newOffset = offset; while (remainingBytesToRead > 0) @@ -111,8 +116,8 @@ namespace Jellyfin.Api.Helpers } else { - // If the job is null it's a live stream and will require user action to close - if (_job?.HasExited ?? false) + // If the job is null it's a live stream and will require user action to close, but don't keep it open indefinitely + if (_job?.HasExited ?? stopwatch.ElapsedMilliseconds > _timeoutMs) { break; } From 9d445cf7fbe9f4ca58bee116d242c5661e26cefa Mon Sep 17 00:00:00 2001 From: FancyNerd92 Date: Tue, 17 Aug 2021 01:52:51 +0000 Subject: [PATCH 070/118] Translated using Weblate (Greek) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/ --- Emby.Server.Implementations/Localization/Core/el.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 834d5e75f2..697063f262 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -1,5 +1,5 @@ { - "Albums": "Άλμπουμς", + "Albums": "Άλμπουμ", "AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}", "Application": "Εφαρμογή", "Artists": "Καλλιτέχνες", @@ -15,7 +15,7 @@ "Favorites": "Αγαπημένα", "Folders": "Φάκελοι", "Genres": "Είδη", - "HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ", + "HeaderAlbumArtists": "Άλμπουμ Καλλιτέχνη", "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση", "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ", "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες", From 88b2b9a82acb35614a106a7f141d1c557685821c Mon Sep 17 00:00:00 2001 From: Henry Moritz Date: Tue, 17 Aug 2021 02:20:40 +0000 Subject: [PATCH 071/118] Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_MX/ --- .../Localization/Core/es-MX.json | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index 5d7ed243f4..432814dac1 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -15,7 +15,7 @@ "Favorites": "Favoritos", "Folders": "Carpetas", "Genres": "Géneros", - "HeaderAlbumArtists": "Artistas del álbum", + "HeaderAlbumArtists": "Artistas del Álbum", "HeaderContinueWatching": "Continuar viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", @@ -25,7 +25,7 @@ "HeaderLiveTV": "TV en vivo", "HeaderNextUp": "A continuación", "HeaderRecordingGroups": "Grupos de grabación", - "HomeVideos": "Videos caseros", + "HomeVideos": "Videos Caseros", "Inherit": "Heredar", "ItemAddedWithName": "{0} fue agregado a la biblioteca", "ItemRemovedWithName": "{0} fue removido de la biblioteca", @@ -39,7 +39,7 @@ "MixedContent": "Contenido mezclado", "Movies": "Películas", "Music": "Música", - "MusicVideos": "Videos musicales", + "MusicVideos": "Videos Musicales", "NameInstallFailed": "Falló la instalación de {0}", "NameSeasonNumber": "Temporada {0}", "NameSeasonUnknown": "Temporada desconocida", @@ -49,7 +49,7 @@ "NotificationOptionAudioPlayback": "Reproducción de audio iniciada", "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", - "NotificationOptionInstallationFailed": "Falla de instalación", + "NotificationOptionInstallationFailed": "Fallo en la instalación", "NotificationOptionNewLibraryContent": "Nuevo contenido agregado", "NotificationOptionPluginError": "Falla de complemento", "NotificationOptionPluginInstalled": "Complemento instalado", @@ -69,7 +69,7 @@ "ProviderValue": "Proveedor: {0}", "ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskStartedWithName": "{0} iniciado", - "ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado", + "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado", "Shows": "Programas", "Songs": "Canciones", "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", @@ -94,9 +94,9 @@ "VersionNumber": "Versión {0}", "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", - "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", + "TaskRefreshChannelsDescription": "Actualiza la información de los canales de Internet.", "TaskRefreshChannels": "Actualizar canales", - "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.", + "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día de antigüedad.", "TaskCleanTranscode": "Limpiar directorio de transcodificado", "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", "TaskUpdatePlugins": "Actualizar complementos", @@ -118,5 +118,7 @@ "TaskCleanActivityLog": "Limpiar registro de actividades", "Undefined": "Sin definir", "Forced": "Forzado", - "Default": "Predeterminado" + "Default": "Predeterminado", + "TaskOptimizeDatabase": "Optimizar base de datos", + "TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos." } From 61aa992628a4ac1c825e63aab56b751106424742 Mon Sep 17 00:00:00 2001 From: Adam Bokor Date: Mon, 16 Aug 2021 11:28:11 +0000 Subject: [PATCH 072/118] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/ --- Emby.Server.Implementations/Localization/Core/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index 255d5427a6..85ab1511a4 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -15,7 +15,7 @@ "Favorites": "Kedvencek", "Folders": "Könyvtárak", "Genres": "Műfajok", - "HeaderAlbumArtists": "Album előadók", + "HeaderAlbumArtists": "Előadó albumai", "HeaderContinueWatching": "Megtekintés folytatása", "HeaderFavoriteAlbums": "Kedvenc albumok", "HeaderFavoriteArtists": "Kedvenc előadók", From c842a2efa2fe0868f7f1dc34bc73e861dbe7b1ec Mon Sep 17 00:00:00 2001 From: millallo Date: Sun, 15 Aug 2021 07:22:35 +0000 Subject: [PATCH 073/118] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 0bde76e744..5e28cf09fb 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -15,7 +15,7 @@ "Favorites": "Preferiti", "Folders": "Cartelle", "Genres": "Generi", - "HeaderAlbumArtists": "Artisti degli Album", + "HeaderAlbumArtists": "Artisti dell'Album", "HeaderContinueWatching": "Continua a guardare", "HeaderFavoriteAlbums": "Album Preferiti", "HeaderFavoriteArtists": "Artisti Preferiti", @@ -25,7 +25,7 @@ "HeaderLiveTV": "Diretta TV", "HeaderNextUp": "Prossimo", "HeaderRecordingGroups": "Gruppi di Registrazione", - "HomeVideos": "Video personali", + "HomeVideos": "Video Personali", "Inherit": "Eredita", "ItemAddedWithName": "{0} è stato aggiunto alla libreria", "ItemRemovedWithName": "{0} è stato rimosso dalla libreria", @@ -39,7 +39,7 @@ "MixedContent": "Contenuto misto", "Movies": "Film", "Music": "Musica", - "MusicVideos": "Video musicali", + "MusicVideos": "Video Musicali", "NameInstallFailed": "{0} installazione fallita", "NameSeasonNumber": "Stagione {0}", "NameSeasonUnknown": "Stagione sconosciuta", From a311116f51cd4387d5b8904164e89b19da24ae2b Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 14 Aug 2021 22:10:45 +0000 Subject: [PATCH 074/118] Translated using Weblate (Kazakh) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/ --- Emby.Server.Implementations/Localization/Core/kk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 1b4a18deb5..d28564a7c6 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -15,7 +15,7 @@ "Favorites": "Tañdaulylar", "Folders": "Qaltalar", "Genres": "Janrlar", - "HeaderAlbumArtists": "Älbom oryndauşylary", + "HeaderAlbumArtists": "Oryndauşynyñ älbomy", "HeaderContinueWatching": "Qaraudy jalğastyru", "HeaderFavoriteAlbums": "Tañdauly älbomdar", "HeaderFavoriteArtists": "Tañdauly oryndauşylar", From 2e5cb8d8b184f7d50c05e9607f7751a993212639 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 14 Aug 2021 22:11:51 +0000 Subject: [PATCH 075/118] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/ --- Emby.Server.Implementations/Localization/Core/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 248f06c4b2..cd016b51b4 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -25,7 +25,7 @@ "HeaderLiveTV": "Эфир", "HeaderNextUp": "Очередное", "HeaderRecordingGroups": "Группы записей", - "HomeVideos": "Домашнее видео", + "HomeVideos": "Домашние видео", "Inherit": "Наследуемое", "ItemAddedWithName": "{0} - добавлено в медиатеку", "ItemRemovedWithName": "{0} - изъято из медиатеки", From 0c54708e1c77cd25e42d79d6d91553d442b96a69 Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Sat, 14 Aug 2021 12:21:23 +0000 Subject: [PATCH 076/118] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sk/ --- Emby.Server.Implementations/Localization/Core/sk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index 37da7d5ab3..ad90bd8134 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -39,7 +39,7 @@ "MixedContent": "Zmiešaný obsah", "Movies": "Filmy", "Music": "Hudba", - "MusicVideos": "Hudobné videoklipy", + "MusicVideos": "Hudobné videá", "NameInstallFailed": "Inštalácia {0} zlyhala", "NameSeasonNumber": "Séria {0}", "NameSeasonUnknown": "Neznáma séria", From 2a8cb5d795d62d056b65fd6947f98bde9d2b4012 Mon Sep 17 00:00:00 2001 From: aqweg Date: Sun, 15 Aug 2021 15:39:06 +0000 Subject: [PATCH 077/118] Translated using Weblate (Afrikaans) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/af/ --- .../Localization/Core/af.json | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json index 4f21c66bc4..18f17dda95 100644 --- a/Emby.Server.Implementations/Localization/Core/af.json +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -2,24 +2,24 @@ "Artists": "Kunstenare", "Channels": "Kanale", "Folders": "Lêergidse", - "Favorites": "Gunstellinge", + "Favorites": "Gunstelinge", "HeaderFavoriteShows": "Gunsteling Vertonings", "ValueSpecialEpisodeName": "Spesiale - {0}", - "HeaderAlbumArtists": "Album Kunstenaars", + "HeaderAlbumArtists": "Kunstenaars se Album", "Books": "Boeke", "HeaderNextUp": "Volgende", "Movies": "Flieks", "Shows": "Televisie Reekse", "HeaderContinueWatching": "Kyk Verder", "HeaderFavoriteEpisodes": "Gunsteling Episodes", - "Photos": "Fotos", + "Photos": "Foto's", "Playlists": "Snitlyste", "HeaderFavoriteArtists": "Gunsteling Kunstenaars", "HeaderFavoriteAlbums": "Gunsteling Albums", "Sync": "Sinkroniseer", "HeaderFavoriteSongs": "Gunsteling Liedjies", "Songs": "Liedjies", - "DeviceOnlineWithName": "{0} gekoppel is", + "DeviceOnlineWithName": "{0} is gekoppel", "DeviceOfflineWithName": "{0} is ontkoppel", "Collections": "Versamelings", "Inherit": "Ontvang", @@ -71,7 +71,7 @@ "NameSeasonUnknown": "Seisoen Onbekend", "NameSeasonNumber": "Seisoen {0}", "NameInstallFailed": "{0} installering het misluk", - "MusicVideos": "Musiek videos", + "MusicVideos": "Musiek Videos", "Music": "Musiek", "MixedContent": "Gemengde inhoud", "MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer", @@ -79,15 +79,15 @@ "MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}", "MessageApplicationUpdated": "Jellyfin Bediener is opgedateer", "Latest": "Nuutste", - "LabelRunningTimeValue": "Lopende tyd: {0}", + "LabelRunningTimeValue": "Werktyd: {0}", "LabelIpAddressValue": "IP adres: {0}", "ItemRemovedWithName": "{0} is uit versameling verwyder", - "ItemAddedWithName": "{0} is in die versameling", - "HomeVideos": "Tuis opnames", + "ItemAddedWithName": "{0} is by die versameling gevoeg", + "HomeVideos": "Tuis Videos", "HeaderRecordingGroups": "Groep Opnames", "Genres": "Genres", "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}", - "ChapterNameValue": "Hoofstuk", + "ChapterNameValue": "Hoofstuk {0}", "CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}", "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer", "Albums": "Albums", @@ -117,5 +117,7 @@ "Forced": "Geforseer", "Default": "Oorspronklik", "TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.", - "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon" + "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon", + "TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.", + "TaskOptimizeDatabase": "Optimaliseer databasis" } From 6de2a519f2adcadb9125fd19bb37608adfd645f5 Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Mon, 16 Aug 2021 16:22:07 +0000 Subject: [PATCH 078/118] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index 20ab1dd7db..3d69e418b9 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -3,7 +3,7 @@ "Favorites": "Yêu Thích", "Folders": "Thư Mục", "Genres": "Thể Loại", - "HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ", + "HeaderAlbumArtists": "Album Nghệ sĩ", "HeaderContinueWatching": "Xem Tiếp", "HeaderLiveTV": "TV Trực Tiếp", "Movies": "Phim", @@ -82,7 +82,7 @@ "NameSeasonUnknown": "Không Rõ Mùa", "NameSeasonNumber": "Phần {0}", "NameInstallFailed": "{0} cài đặt thất bại", - "MusicVideos": "Video Nhạc", + "MusicVideos": "Videos Nhạc", "Music": "Nhạc", "MixedContent": "Nội dung hỗn hợp", "MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật", From 6438410111c6022a2e1da53b04211757996ece90 Mon Sep 17 00:00:00 2001 From: Oatavandi Date: Sat, 14 Aug 2021 15:54:24 +0000 Subject: [PATCH 079/118] Translated using Weblate (Malayalam) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ml/ --- Emby.Server.Implementations/Localization/Core/ml.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ml.json b/Emby.Server.Implementations/Localization/Core/ml.json index 435f9b6305..09ef349136 100644 --- a/Emby.Server.Implementations/Localization/Core/ml.json +++ b/Emby.Server.Implementations/Localization/Core/ml.json @@ -103,7 +103,7 @@ "ValueSpecialEpisodeName": "പ്രത്യേക - {0}", "Collections": "ശേഖരങ്ങൾ", "Folders": "ഫോൾഡറുകൾ", - "HeaderAlbumArtists": "ആൽബം ആർട്ടിസ്റ്റുകൾ", + "HeaderAlbumArtists": "കലാകാരന്റെ ആൽബം", "Sync": "സമന്വയിപ്പിക്കുക", "Movies": "സിനിമകൾ", "Photos": "ഫോട്ടോകൾ", From 468283bfb2cd7120b53e32cea67fd0c055d38812 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 18 Aug 2021 20:11:28 +0200 Subject: [PATCH 080/118] Move test to TypedBaseItem folder to avoid conflicts --- .../{BaseItem => TypedBaseItem}/BaseItemKindTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename tests/Jellyfin.Server.Implementations.Tests/{BaseItem => TypedBaseItem}/BaseItemKindTests.cs (93%) diff --git a/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs similarity index 93% rename from tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs rename to tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs index 061e81f30a..63d97b3cb2 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs @@ -8,13 +8,13 @@ using Jellyfin.Data.Enums; using Xunit; using Xunit.Sdk; -namespace Jellyfin.Server.Implementations.Tests.BaseItem +namespace Jellyfin.Server.Implementations.Tests.TypedBaseItem { public class BaseItemKindTests { [Theory] [ClassData(typeof(GetBaseItemDescendants))] - public void BaseItemKindEnumTest(Type baseItemType) + public void EnumParse_GivenValidBaseItemType_ReturnsEnumValue(Type baseItemType) { var enumValue = Enum.Parse(baseItemType.Name); Assert.True(Enum.IsDefined(typeof(BaseItemKind), enumValue)); @@ -22,7 +22,7 @@ namespace Jellyfin.Server.Implementations.Tests.BaseItem [Theory] [ClassData(typeof(GetBaseItemDescendants))] - public void GetBaseKindEnumTest(Type baseItemDescendantType) + public void GetBaseItemKind_WhenCalledAfterDefaultCtor_DoesNotThrow(Type baseItemDescendantType) { var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes); var instance = (MediaBrowser.Controller.Entities.BaseItem)defaultConstructor!.Invoke(null); From 50b3d74c953ea80af2cd2a276cf0475c5e907f27 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 21 Aug 2021 17:31:06 -0600 Subject: [PATCH 081/118] Switch to TheoryData and clean up tests --- .../TypedBaseItem/BaseItemKindTests.cs | 94 +++++++------------ 1 file changed, 33 insertions(+), 61 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs index 63d97b3cb2..31f33c6825 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs @@ -1,27 +1,45 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Reflection; using Jellyfin.Data.Enums; using Xunit; -using Xunit.Sdk; namespace Jellyfin.Server.Implementations.Tests.TypedBaseItem { public class BaseItemKindTests { + public static TheoryData BaseItemKind_TestData() + { + var data = new TheoryData(); + + var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in loadedAssemblies) + { + if (IsProjectAssemblyName(assembly.FullName)) + { + var baseItemTypes = assembly.GetTypes() + .Where(targetType => targetType.IsClass + && !targetType.IsAbstract + && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); + foreach (var baseItemType in baseItemTypes) + { + data.Add(baseItemType); + } + } + } + + return data; + } + [Theory] - [ClassData(typeof(GetBaseItemDescendants))] - public void EnumParse_GivenValidBaseItemType_ReturnsEnumValue(Type baseItemType) + [MemberData(nameof(BaseItemKind_TestData))] + public void EnumParse_GivenValidBaseItemType_ReturnsEnumValue(Type baseItemDescendantType) { - var enumValue = Enum.Parse(baseItemType.Name); + var enumValue = Enum.Parse(baseItemDescendantType.Name); Assert.True(Enum.IsDefined(typeof(BaseItemKind), enumValue)); } [Theory] - [ClassData(typeof(GetBaseItemDescendants))] + [MemberData(nameof(BaseItemKind_TestData))] public void GetBaseItemKind_WhenCalledAfterDefaultCtor_DoesNotThrow(Type baseItemDescendantType) { var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes); @@ -30,62 +48,16 @@ namespace Jellyfin.Server.Implementations.Tests.TypedBaseItem Assert.Null(exception); } - private class GetBaseItemDescendants : IEnumerable + private static bool IsProjectAssemblyName(string? name) { - private static bool IsProjectAssemblyName(string? name) + if (name == null) { - if (name == null) - { - return false; - } - - return name.StartsWith("Jellyfin", StringComparison.OrdinalIgnoreCase) - || name.StartsWith("Emby", StringComparison.OrdinalIgnoreCase) - || name.StartsWith("MediaBrowser", StringComparison.OrdinalIgnoreCase); - } - - public IEnumerator GetEnumerator() - { - var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var assembly in loadedAssemblies) - { - if (IsProjectAssemblyName(assembly.FullName)) - { - var baseItemTypes = assembly.GetTypes() - .Where(targetType => targetType.IsClass - && !targetType.IsAbstract - && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); - foreach (var baseItemType in baseItemTypes) - { - yield return new object?[] { baseItemType }; - } - } - } - - var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - if (path == null) - { - throw new NullException("Assembly location is null"); - } - - foreach (string dll in Directory.GetFiles(path, "*.dll")) - { - var assembly = Assembly.LoadFile(dll); - if (IsProjectAssemblyName(assembly.FullName)) - { - var baseItemTypes = assembly.GetTypes() - .Where(targetType => targetType.IsClass - && !targetType.IsAbstract - && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); - foreach (var baseItemType in baseItemTypes) - { - yield return new object?[] { baseItemType }; - } - } - } + return false; } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + return name.StartsWith("Jellyfin", StringComparison.OrdinalIgnoreCase) + || name.StartsWith("Emby", StringComparison.OrdinalIgnoreCase) + || name.StartsWith("MediaBrowser", StringComparison.OrdinalIgnoreCase); } } } From 7c7ad820c231426845a8c8d74c72c657eb0cf0cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Woli=C5=84ski?= Date: Sun, 22 Aug 2021 01:17:46 +0000 Subject: [PATCH 082/118] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pl/ --- Emby.Server.Implementations/Localization/Core/pl.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index 275bdec6e6..e8a32a13e8 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -15,7 +15,7 @@ "Favorites": "Ulubione", "Folders": "Foldery", "Genres": "Gatunki", - "HeaderAlbumArtists": "Wykonawcy albumów", + "HeaderAlbumArtists": "Album artysty", "HeaderContinueWatching": "Kontynuuj odtwarzanie", "HeaderFavoriteAlbums": "Ulubione albumy", "HeaderFavoriteArtists": "Ulubieni wykonawcy", @@ -25,7 +25,7 @@ "HeaderLiveTV": "Telewizja", "HeaderNextUp": "Do obejrzenia", "HeaderRecordingGroups": "Grupy nagrań", - "HomeVideos": "Nagrania prywatne", + "HomeVideos": "Nagrania domowe", "Inherit": "Dziedzicz", "ItemAddedWithName": "{0} zostało dodane do biblioteki", "ItemRemovedWithName": "{0} zostało usunięte z biblioteki", @@ -119,5 +119,6 @@ "Undefined": "Nieustalony", "Forced": "Wymuszony", "Default": "Domyślne", - "TaskOptimizeDatabase": "Optymalizuj bazę danych" + "TaskOptimizeDatabase": "Optymalizuj bazę danych", + "TaskOptimizeDatabaseDescription": "Kompaktuje bazę danych i obcina wolne miejsce. Uruchomienie tego zadania po przeskanowaniu biblioteki lub dokonaniu innych zmian, które pociągają za sobą modyfikacje bazy danych, może poprawić wydajność." } From 98b72019e697d0709991904610bbee3dd50f5d13 Mon Sep 17 00:00:00 2001 From: wolong gl Date: Sun, 22 Aug 2021 06:42:03 +0000 Subject: [PATCH 083/118] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index faa9c40e27..71adc0f6ee 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -7,7 +7,7 @@ "Books": "书籍", "CameraImageUploadedFrom": "新的相机图像已从 {0} 上传", "Channels": "频道", - "ChapterNameValue": "第 {0} 集", + "ChapterNameValue": "章节 {0}", "Collections": "合集", "DeviceOfflineWithName": "{0} 已断开", "DeviceOnlineWithName": "{0} 已连接", @@ -108,8 +108,8 @@ "TaskCleanLogs": "清理日志目录", "TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。", "TaskRefreshLibrary": "扫描媒体库", - "TaskRefreshChapterImagesDescription": "为包含剧集的视频提取缩略图。", - "TaskRefreshChapterImages": "提取剧集图片", + "TaskRefreshChapterImagesDescription": "为包含章节的视频提取缩略图。", + "TaskRefreshChapterImages": "提取章节图片", "TaskCleanCacheDescription": "删除系统不再需要的缓存文件。", "TaskCleanCache": "清理缓存目录", "TasksApplicationCategory": "应用程序", From efed4728e6ce613727257776d990a0bd60b85dae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 12:00:54 +0000 Subject: [PATCH 084/118] Bump prometheus-net from 4.2.0 to 5.0.1 Bumps [prometheus-net](https://github.com/prometheus-net/prometheus-net) from 4.2.0 to 5.0.1. - [Release notes](https://github.com/prometheus-net/prometheus-net/releases) - [Changelog](https://github.com/prometheus-net/prometheus-net/blob/master/History) - [Commits](https://github.com/prometheus-net/prometheus-net/compare/v4.2.0...v5.0.1) --- updated-dependencies: - dependency-name: prometheus-net dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index ea64663bd7..11342adba2 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -35,7 +35,7 @@ - + From 74d22d2f9afbee54c88e65866d7fbba258f62557 Mon Sep 17 00:00:00 2001 From: Kaiay Date: Tue, 24 Aug 2021 21:02:30 +0000 Subject: [PATCH 085/118] Translated using Weblate (Chinese (Hong Kong)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant_HK/ --- Emby.Server.Implementations/Localization/Core/zh-HK.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 3dad21dcbc..1cc97bc27e 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -13,7 +13,7 @@ "DeviceOnlineWithName": "{0} 已經連接", "FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗", "Favorites": "我的最愛", - "Folders": "檔案夾", + "Folders": "資料夾", "Genres": "風格", "HeaderAlbumArtists": "專輯藝人", "HeaderContinueWatching": "繼續觀看", @@ -39,7 +39,7 @@ "MixedContent": "混合內容", "Movies": "電影", "Music": "音樂", - "MusicVideos": "音樂視頻", + "MusicVideos": "音樂影片", "NameInstallFailed": "{0} 安裝失敗", "NameSeasonNumber": "第 {0} 季", "NameSeasonUnknown": "未知季數", @@ -117,5 +117,8 @@ "TaskCleanActivityLog": "清理活動記錄", "Undefined": "未定義", "Forced": "強制", - "Default": "預設" + "Default": "預設", + "TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。", + "TaskOptimizeDatabase": "最佳化數據庫", + "TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。" } From d3527334bc449df0d16fbf5554236fc42df23372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Wed, 25 Aug 2021 07:33:23 +0000 Subject: [PATCH 086/118] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- Emby.Server.Implementations/Localization/Core/cs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 62b2b6328c..4f1d231a4a 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -15,7 +15,7 @@ "Favorites": "Oblíbené", "Folders": "Složky", "Genres": "Žánry", - "HeaderAlbumArtists": "Umělci alba", + "HeaderAlbumArtists": "Album umělce", "HeaderContinueWatching": "Pokračovat ve sledování", "HeaderFavoriteAlbums": "Oblíbená alba", "HeaderFavoriteArtists": "Oblíbení interpreti", From 6c7b9d4044a63b7b6bdef5638642c7dec7c7b401 Mon Sep 17 00:00:00 2001 From: Weevild Date: Tue, 24 Aug 2021 19:33:42 +0000 Subject: [PATCH 087/118] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- Emby.Server.Implementations/Localization/Core/sv.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index 88b182f8d9..6c772c6a24 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -15,7 +15,7 @@ "Favorites": "Favoriter", "Folders": "Mappar", "Genres": "Genrer", - "HeaderAlbumArtists": "Albumartister", + "HeaderAlbumArtists": "Artistens album", "HeaderContinueWatching": "Fortsätt kolla", "HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteArtists": "Favoritartister", @@ -25,7 +25,7 @@ "HeaderLiveTV": "Live-TV", "HeaderNextUp": "Nästa", "HeaderRecordingGroups": "Inspelningsgrupper", - "HomeVideos": "Hemvideor", + "HomeVideos": "Hemmavideor", "Inherit": "Ärv", "ItemAddedWithName": "{0} lades till i biblioteket", "ItemRemovedWithName": "{0} togs bort från biblioteket", @@ -119,5 +119,6 @@ "Undefined": "odefinierad", "Forced": "Tvingad", "Default": "Standard", - "TaskOptimizeDatabase": "Optimera databasen" + "TaskOptimizeDatabase": "Optimera databasen", + "TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna task efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats." } From 3ebede56fe2f7e063d59498a5c38c2037ecca031 Mon Sep 17 00:00:00 2001 From: lsousa Date: Wed, 25 Aug 2021 15:32:17 +0000 Subject: [PATCH 088/118] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/ --- Emby.Server.Implementations/Localization/Core/pt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index b435672adf..474dacd7cc 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -117,5 +117,6 @@ "Undefined": "Indefinido", "Forced": "Forçado", "Default": "Predefinição", - "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado." + "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado.", + "TaskOptimizeDatabase": "Otimizar base de dados" } From 80dc92238a8976002467aeb0ac6ab714b3be03bc Mon Sep 17 00:00:00 2001 From: Changho Park Date: Thu, 26 Aug 2021 01:10:08 +0000 Subject: [PATCH 089/118] Translated using Weblate (Korean) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/ --- Emby.Server.Implementations/Localization/Core/ko.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index 409b4d26b3..a37de07488 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -15,7 +15,7 @@ "Favorites": "즐겨찾기", "Folders": "폴더", "Genres": "장르", - "HeaderAlbumArtists": "앨범 아티스트", + "HeaderAlbumArtists": "아티스트의 앨범", "HeaderContinueWatching": "계속 시청하기", "HeaderFavoriteAlbums": "즐겨찾는 앨범", "HeaderFavoriteArtists": "즐겨찾는 아티스트", From c2bcceaa212e8e94f9987d37e0c53d4ac58f6a0f Mon Sep 17 00:00:00 2001 From: SeanPai Date: Tue, 24 Aug 2021 09:55:28 +0000 Subject: [PATCH 090/118] Translated using Weblate (Chinese (Traditional)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant/ --- Emby.Server.Implementations/Localization/Core/zh-TW.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index c3b223f631..585d81450a 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -24,7 +24,7 @@ "HeaderFavoriteSongs": "最愛歌曲", "HeaderLiveTV": "電視直播", "HeaderNextUp": "接下來", - "HomeVideos": "自製影片", + "HomeVideos": "家庭影片", "ItemAddedWithName": "{0} 已新增至媒體庫", "ItemRemovedWithName": "{0} 已從媒體庫移除", "LabelIpAddressValue": "IP 位址:{0}", @@ -117,5 +117,7 @@ "TaskCleanActivityLog": "清除活動紀錄", "Undefined": "未定義的", "Forced": "強制", - "Default": "原本" + "Default": "原本", + "TaskOptimizeDatabaseDescription": "縮小資料庫並釋放可用空間。在掃描資料庫或進行資料庫相關的更動後使用此功能會增加效能。", + "TaskOptimizeDatabase": "最佳化資料庫" } From 5bcd1648cb4dfe617599ec2b2fe5e5f8a461ec61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Costa?= Date: Tue, 24 Aug 2021 00:04:37 +0000 Subject: [PATCH 091/118] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 323dcced07..be71289b1b 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -118,5 +118,7 @@ "TaskCleanActivityLog": "Limpar Registro de Atividades", "Undefined": "Indefinido", "Forced": "Forçado", - "Default": "Padrão" + "Default": "Padrão", + "TaskOptimizeDatabaseDescription": "Compactar base de dados e liberar espaço livre. Executar esta tarefa após realizar mudanças que impliquem em modificações da base de dados pode trazer melhorias de desempenho.", + "TaskOptimizeDatabase": "Otimizar base de dados" } From 59b8056addad8ec33b3785bc748cb4e97c4910da Mon Sep 17 00:00:00 2001 From: wolong gl Date: Wed, 25 Aug 2021 06:29:01 +0000 Subject: [PATCH 092/118] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 71adc0f6ee..f9df627245 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -15,8 +15,8 @@ "Favorites": "我的最爱", "Folders": "文件夹", "Genres": "风格", - "HeaderAlbumArtists": "专辑作家", - "HeaderContinueWatching": "继续观影", + "HeaderAlbumArtists": "专辑艺术家", + "HeaderContinueWatching": "继续观看", "HeaderFavoriteAlbums": "收藏的专辑", "HeaderFavoriteArtists": "最爱的艺术家", "HeaderFavoriteEpisodes": "最爱的剧集", From 41712e16becca7c9f483503eb7ce6a9fabb1c88a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Aug 2021 15:32:56 +0000 Subject: [PATCH 093/118] Bump prometheus-net.AspNetCore from 4.2.0 to 5.0.1 Bumps [prometheus-net.AspNetCore](https://github.com/prometheus-net/prometheus-net) from 4.2.0 to 5.0.1. - [Release notes](https://github.com/prometheus-net/prometheus-net/releases) - [Changelog](https://github.com/prometheus-net/prometheus-net/blob/master/History) - [Commits](https://github.com/prometheus-net/prometheus-net/compare/v4.2.0...v5.0.1) --- updated-dependencies: - dependency-name: prometheus-net.AspNetCore dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 11342adba2..c4d3d8a1f3 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -36,7 +36,7 @@ - + From e88367fe70f8d5f2a0cec35792739fe23000a818 Mon Sep 17 00:00:00 2001 From: TheFeelTrain Date: Fri, 27 Aug 2021 11:28:28 +0000 Subject: [PATCH 094/118] Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/ --- Emby.Server.Implementations/Localization/Core/ja.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index 0d90ad31cc..c689bc58a5 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -15,7 +15,7 @@ "Favorites": "お気に入り", "Folders": "フォルダー", "Genres": "ジャンル", - "HeaderAlbumArtists": "アルバムアーティスト", + "HeaderAlbumArtists": "アーティストのアルバム", "HeaderContinueWatching": "視聴を続ける", "HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteArtists": "お気に入りのアーティスト", From 645825db36f2de9771aa5d0ebcdab25855d18b5d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 28 Aug 2021 17:32:09 +0200 Subject: [PATCH 095/118] Enable nullable for more files --- .../Collections/CollectionManager.cs | 2 +- .../Library/LibraryManager.cs | 5 +- .../LiveTv/EmbyTV/DirectRecorder.cs | 8 ++-- .../LiveTv/EmbyTV/EmbyTV.cs | 47 ++++++++----------- .../LiveTv/EmbyTV/EncodedRecorder.cs | 5 -- .../LiveTv/EmbyTV/EpgChannelData.cs | 1 - .../LiveTv/EmbyTV/IRecorder.cs | 2 +- .../LiveTv/EmbyTV/TimerManager.cs | 12 ++--- .../LiveTv/Listings/SchedulesDirect.cs | 2 +- .../Controllers/LibraryStructureController.cs | 4 +- .../Configuration/MediaPathInfo.cs | 8 +++- .../Configuration/NfoConfigurationFactory.cs | 4 +- 12 files changed, 42 insertions(+), 58 deletions(-) diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 8270c2e84c..b00a519229 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Collections var libraryOptions = new LibraryOptions { - PathInfos = new[] { new MediaPathInfo { Path = path } }, + PathInfos = new[] { new MediaPathInfo(path) }, EnableRealtimeMonitor = false, SaveLocalMetadata = true }; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 13fb8b2fd5..0cde8101c5 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -3173,10 +3173,7 @@ namespace Emby.Server.Implementations.Library { if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal))) { - list.Add(new MediaPathInfo - { - Path = location - }); + list.Add(new MediaPathInfo(location)); } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 3fcadf5b1b..bb3d635d12 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -33,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return targetFile; } - public Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + public Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { if (directStreamProvider != null) { @@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { - Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); + Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None)) @@ -71,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logger.LogInformation("Opened recording stream from tuner provider"); - Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); + Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7970631201..f2b9f3cb90 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -159,8 +159,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV try { var recordingFolders = GetRecordingFolders().ToArray(); - var virtualFolders = _libraryManager.GetVirtualFolders() - .ToList(); + var virtualFolders = _libraryManager.GetVirtualFolders(); var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList(); @@ -177,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV continue; } - var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray(); + var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo(i)).ToArray(); var libraryOptions = new LibraryOptions { @@ -210,7 +209,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV foreach (var path in pathsToRemove) { - await RemovePathFromLibrary(path).ConfigureAwait(false); + await RemovePathFromLibraryAsync(path).ConfigureAwait(false); } } catch (Exception ex) @@ -219,13 +218,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private async Task RemovePathFromLibrary(string path) + private async Task RemovePathFromLibraryAsync(string path) { _logger.LogDebug("Removing path from library: {0}", path); var requiresRefresh = false; - var virtualFolders = _libraryManager.GetVirtualFolders() - .ToList(); + var virtualFolders = _libraryManager.GetVirtualFolders(); foreach (var virtualFolder in virtualFolders) { @@ -460,7 +458,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId)) { var tunerChannelId = tunerChannel.TunerChannelId; - if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1) + if (tunerChannelId.Contains(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase)) { tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I'); } @@ -620,8 +618,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (existingTimer != null) { - if (existingTimer.Status == RecordingStatus.Cancelled || - existingTimer.Status == RecordingStatus.Completed) + if (existingTimer.Status == RecordingStatus.Cancelled + || existingTimer.Status == RecordingStatus.Completed) { existingTimer.Status = RecordingStatus.New; existingTimer.IsManual = true; @@ -913,18 +911,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false); - List programs; - if (epgChannel == null) { _logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty); - programs = new List(); + continue; } - else - { - programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken) + + List programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken) .ConfigureAwait(false)).ToList(); - } // Replace the value that came from the provider with a normalized value foreach (var program in programs) @@ -940,7 +934,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - return new List(); + return Enumerable.Empty(); } private List> GetListingProviders() @@ -1292,7 +1286,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logger.LogInformation("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture)); - _logger.LogInformation("Writing file to path: " + recordPath); + _logger.LogInformation("Writing file to: {Path}", recordPath); Action onStarted = async () => { @@ -1417,13 +1411,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private void TriggerRefresh(string path) { - _logger.LogInformation("Triggering refresh on {path}", path); + _logger.LogInformation("Triggering refresh on {Path}", path); var item = GetAffectedBaseItem(Path.GetDirectoryName(path)); if (item != null) { - _logger.LogInformation("Refreshing recording parent {path}", item.Path); + _logger.LogInformation("Refreshing recording parent {Path}", item.Path); _providerManager.QueueRefresh( item.Id, @@ -1458,7 +1452,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase)) { var parentItem = item.GetParent(); - if (parentItem != null && !(parentItem is AggregateFolder)) + if (parentItem != null && parentItem is not AggregateFolder) { item = parentItem; } @@ -1512,8 +1506,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV DeleteLibraryItemsForTimers(timersToDelete); - var librarySeries = _libraryManager.FindByPath(seriesPath, true) as Folder; - if (librarySeries == null) + if (_libraryManager.FindByPath(seriesPath, true) is not Folder librarySeries) { return; } @@ -1667,7 +1660,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - process.Exited += Process_Exited; + process.Exited += OnProcessExited; process.Start(); } catch (Exception ex) @@ -1681,7 +1674,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return arguments.Replace("{path}", path, StringComparison.OrdinalIgnoreCase); } - private void Process_Exited(object sender, EventArgs e) + private void OnProcessExited(object sender, EventArgs e) { using (var process = (Process)sender) { @@ -2239,7 +2232,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var enabledTimersForSeries = new List(); foreach (var timer in allTimers) { - var existingTimer = _timerProvider.GetTimer(timer.Id) + var existingTimer = _timerProvider.GetTimer(timer.Id) ?? (string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _timerProvider.GetTimerByProgramId(timer.ProgramId)); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 93781cb7ba..e10bc76470 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -319,11 +319,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } } - catch (ObjectDisposedException) - { - // TODO Investigate and properly fix. - // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux - } catch (Exception ex) { _logger.LogError(ex, "Error reading ffmpeg recording log"); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs index 0ec52a9598..20a8213a77 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs @@ -8,7 +8,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { internal class EpgChannelData { - private readonly Dictionary _channelsById; private readonly Dictionary _channelsByNumber; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs index 4712724d67..dfe3517b22 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV /// /// Records the specified media source. /// - Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken); + Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken); string GetOutputPath(MediaSourceInfo mediaSource, string targetFile); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 6c52a9a73d..a861e6ae44 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -23,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { } - public event EventHandler> TimerFired; + public event EventHandler>? TimerFired; public void RestartTimers() { @@ -145,9 +143,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private void TimerCallback(object state) + private void TimerCallback(object? state) { - var timerId = (string)state; + var timerId = (string?)state ?? throw new ArgumentNullException(nameof(state)); var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase)); if (timer != null) @@ -156,12 +154,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - public TimerInfo GetTimer(string id) + public TimerInfo? GetTimer(string id) { return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase)); } - public TimerInfo GetTimerByProgramId(string programId) + public TimerInfo? GetTimerByProgramId(string programId) { return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase)); } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index b7639a51ce..d029d56c87 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -14,8 +14,8 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common; using Jellyfin.Extensions.Json; +using MediaBrowser.Common; using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Cryptography; diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index be9127dd39..ec1170411c 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers if (paths != null && paths.Length > 0) { - libraryOptions.PathInfos = paths.Select(i => new MediaPathInfo { Path = i }).ToArray(); + libraryOptions.PathInfos = paths.Select(i => new MediaPathInfo(i)).ToArray(); } await _libraryManager.AddVirtualFolder(name, collectionType, libraryOptions, refreshLibrary).ConfigureAwait(false); @@ -212,7 +212,7 @@ namespace Jellyfin.Api.Controllers try { - var mediaPath = mediaPathDto.PathInfo ?? new MediaPathInfo { Path = mediaPathDto.Path }; + var mediaPath = mediaPathDto.PathInfo ?? new MediaPathInfo(mediaPathDto.Path ?? throw new ArgumentException("PathInfo and Path can't both be null.")); _libraryManager.AddMediaPath(mediaPathDto.Name, mediaPath); } diff --git a/MediaBrowser.Model/Configuration/MediaPathInfo.cs b/MediaBrowser.Model/Configuration/MediaPathInfo.cs index 4f311c58f0..d096defcb8 100644 --- a/MediaBrowser.Model/Configuration/MediaPathInfo.cs +++ b/MediaBrowser.Model/Configuration/MediaPathInfo.cs @@ -1,12 +1,16 @@ -#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration { public class MediaPathInfo { + public MediaPathInfo(string path) + { + Path = path; + } + public string Path { get; set; } - public string NetworkPath { get; set; } + public string? NetworkPath { get; set; } } } diff --git a/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs index 8325bfdbd5..c73989e761 100644 --- a/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs +++ b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs @@ -15,8 +15,8 @@ namespace MediaBrowser.XbmcMetadata.Configuration { new ConfigurationStore { - ConfigurationType = typeof(XbmcMetadataOptions), - Key = "xbmcmetadata" + ConfigurationType = typeof(XbmcMetadataOptions), + Key = "xbmcmetadata" } }; } From 909573f2f142ca2f5e1e54093c820b9f9ebb6168 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 28 Aug 2021 19:30:57 +0200 Subject: [PATCH 096/118] Add tests for LibraryStructureController --- .../MediaStructureControllerTests.cs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs new file mode 100644 index 0000000000..9f189aaab9 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs @@ -0,0 +1,83 @@ +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Models.LibraryStructureDto; +using Jellyfin.Extensions.Json; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers +{ + public sealed class MediaStructureControllerTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private static string? _accessToken; + + public MediaStructureControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task AddMediaPath_PathDoesntExist_ReturnsNotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var data = new MediaPathDto() + { + Name = "Test", + Path = "/this/path/doesnt/exist" + }; + + using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions)); + postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + var response = await client.PostAsync("Library/VirtualFolders/Paths", postContent).ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task UpdateMediaPath_EmptyName_ReturnsBadRequest() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var data = new UpdateMediaPathRequestDto() + { + Name = string.Empty, + }; + + using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions)); + postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + var response = await client.PostAsync("Library/VirtualFolders/Paths/Update", postContent).ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task RemoveMediaPath_EmptyName_ReturnsBadRequest() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task RemoveMediaPath_PathDoesntExist_ReturnsNotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=none&path=%2Fthis%2Fpath%2Fdoesnt%2Fexist").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + } +} From 69ba539c700c502f189c9067c31860c889aed1f7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 28 Aug 2021 20:12:25 +0200 Subject: [PATCH 097/118] Add more tests for LibraryStructureController --- .../MediaStructureControllerTests.cs | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs index 9f189aaab9..5855f0266f 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs @@ -1,3 +1,4 @@ +using System; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -21,6 +22,42 @@ namespace Jellyfin.Server.Integration.Tests.Controllers _factory = factory; } + [Fact] + public async Task RenameVirtualFolder_WhiteSpaceName_ReturnsBadRequest() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + using var postContent = new ByteArrayContent(Array.Empty()); + var response = await client.PostAsync("Library/VirtualFolders/Name?name=+&newName=test", postContent).ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task RenameVirtualFolder_WhiteSpaceNewName_ReturnsBadRequest() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + using var postContent = new ByteArrayContent(Array.Empty()); + var response = await client.PostAsync("Library/VirtualFolders/Name?name=test&newName=+", postContent).ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task RenameVirtualFolder_NameDoesntExist_ReturnsNotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + using var postContent = new ByteArrayContent(Array.Empty()); + var response = await client.PostAsync("Library/VirtualFolders/Name?name=doesnt+exist&newName=test", postContent).ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + [Fact] public async Task AddMediaPath_PathDoesntExist_ReturnsNotFound() { @@ -59,12 +96,12 @@ namespace Jellyfin.Server.Integration.Tests.Controllers } [Fact] - public async Task RemoveMediaPath_EmptyName_ReturnsBadRequest() + public async Task RemoveMediaPath_WhiteSpaceName_ReturnsBadRequest() { var client = _factory.CreateClient(); client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); - var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=").ConfigureAwait(false); + var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=+").ConfigureAwait(false); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } From 0f7c875076e29f0c86c536840c71cec29fab08fb Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 28 Aug 2021 20:34:04 +0200 Subject: [PATCH 098/118] Fix test --- .../Controllers/MediaStructureControllerTests.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs index 5855f0266f..8af43050f6 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models.LibraryStructureDto; using Jellyfin.Extensions.Json; +using MediaBrowser.Model.Configuration; using Xunit; namespace Jellyfin.Server.Integration.Tests.Controllers @@ -78,14 +79,18 @@ namespace Jellyfin.Server.Integration.Tests.Controllers } [Fact] - public async Task UpdateMediaPath_EmptyName_ReturnsBadRequest() + public async Task UpdateMediaPath_WhiteSpaceName_ReturnsBadRequest() { var client = _factory.CreateClient(); client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); var data = new UpdateMediaPathRequestDto() { - Name = string.Empty, + Name = " ", + PathInfo = new MediaPathInfo + { + Path = "test" + } }; using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions)); From cba07b1ca6862ef8450021b0e60ce2f366ff0d33 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 28 Aug 2021 16:32:50 -0600 Subject: [PATCH 099/118] Remove more and more warnings --- Emby.Dlna/ContentDirectory/ServerItem.cs | 2 +- Emby.Dlna/Didl/DidlBuilder.cs | 2 +- .../ApplicationHost.cs | 3 +- .../Channels/ChannelManager.cs | 6 +- .../Data/BaseSqliteRepository.cs | 2 +- .../Data/SqliteItemRepository.cs | 9 +- .../Data/SqliteUserDataRepository.cs | 8 +- Emby.Server.Implementations/Dto/DtoService.cs | 41 +- .../EntryPoints/LibraryChangedNotifier.cs | 4 +- .../EntryPoints/UdpServerEntryPoint.cs | 3 + .../Security/AuthorizationContext.cs | 2 + .../HttpServer/WebSocketConnection.cs | 4 +- .../IStartupOptions.cs | 2 +- .../Images/CollectionFolderImageProvider.cs | 12 +- .../Library/LibraryManager.cs | 8 +- .../Library/Resolvers/Audio/AudioResolver.cs | 12 +- .../Library/Resolvers/BaseVideoResolver.cs | 26 +- ...deoResolver.cs => GenericVideoResolver.cs} | 0 .../Library/Resolvers/ItemResolver.cs | 2 +- .../Library/Resolvers/Movies/MovieResolver.cs | 4 +- .../Library/Resolvers/PlaylistResolver.cs | 3 +- .../Library/Validators/StudiosValidator.cs | 9 +- .../LiveTv/EmbyTV/EmbyTV.cs | 2 +- .../LiveTv/EmbyTV/EpgChannelData.cs | 1 - .../LiveTv/Listings/SchedulesDirect.cs | 620 ++++-------------- .../SchedulesDirectDtos/BroadcasterDto.cs | 36 + .../SchedulesDirectDtos/CaptionDto.cs | 24 + .../Listings/SchedulesDirectDtos/CastDto.cs | 48 ++ .../SchedulesDirectDtos/ChannelDto.cs | 31 + .../SchedulesDirectDtos/ContentRatingDto.cs | 24 + .../Listings/SchedulesDirectDtos/CrewDto.cs | 42 ++ .../Listings/SchedulesDirectDtos/DayDto.cs | 39 ++ .../SchedulesDirectDtos/Description1000Dto.cs | 24 + .../SchedulesDirectDtos/Description100Dto.cs | 24 + .../DescriptionsProgramDto.cs | 25 + .../SchedulesDirectDtos/EventDetailsDto.cs | 18 + .../SchedulesDirectDtos/GracenoteDto.cs | 24 + .../SchedulesDirectDtos/HeadendsDto.cs | 37 ++ .../SchedulesDirectDtos/ImageDataDto.cs | 69 ++ .../Listings/SchedulesDirectDtos/LineupDto.cs | 42 ++ .../SchedulesDirectDtos/LineupsDto.cs | 37 ++ .../Listings/SchedulesDirectDtos/LogoDto.cs | 36 + .../Listings/SchedulesDirectDtos/MapDto.cs | 48 ++ .../SchedulesDirectDtos/MetadataDto.cs | 30 + .../MetadataProgramsDto.cs | 18 + .../MetadataScheduleDto.cs | 42 ++ .../Listings/SchedulesDirectDtos/MovieDto.cs | 31 + .../SchedulesDirectDtos/MultipartDto.cs | 24 + .../SchedulesDirectDtos/ProgramDetailsDto.cs | 157 +++++ .../SchedulesDirectDtos/ProgramDto.cs | 91 +++ .../SchedulesDirectDtos/QualityRatingDto.cs | 42 ++ .../Listings/SchedulesDirectDtos/RatingDto.cs | 24 + .../SchedulesDirectDtos/RecommendationDto.cs | 24 + .../RequestScheduleForChannelDto.cs | 25 + .../SchedulesDirectDtos/ShowImagesDto.cs | 22 + .../SchedulesDirectDtos/StationDto.cs | 67 ++ .../Listings/SchedulesDirectDtos/TitleDto.cs | 18 + .../Listings/SchedulesDirectDtos/TokenDto.cs | 36 + .../LiveTv/LiveTvManager.cs | 6 +- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 1 + .../Playlists/PlaylistManager.cs | 4 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 3 +- .../Sorting/ArtistComparer.cs | 2 +- .../Controllers/ItemUpdateController.cs | 10 +- Jellyfin.Api/Controllers/ItemsController.cs | 4 +- Jellyfin.Api/Controllers/LibraryController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 8 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 4 - MediaBrowser.Controller/Entities/BaseItem.cs | 4 +- MediaBrowser.Controller/Entities/Folder.cs | 10 +- MediaBrowser.Controller/Entities/TV/Series.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 12 +- .../MediaEncoding/EncodingJobInfo.cs | 6 +- .../Images/LocalImageProvider.cs | 2 +- .../Savers/BaseXmlSaver.cs | 8 +- MediaBrowser.Model/Dlna/MediaFormatProfile.cs | 2 +- .../Dlna/ResolutionNormalizer.cs | 4 - MediaBrowser.Model/Dlna/StreamBuilder.cs | 33 +- MediaBrowser.Model/Dlna/StreamInfo.cs | 2 +- MediaBrowser.Model/Entities/MediaStream.cs | 2 +- MediaBrowser.Model/IO/IFileSystem.cs | 4 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 2 +- .../Manager/ItemImageProvider.cs | 4 +- .../Manager/MetadataService.cs | 6 +- .../Manager/ProviderManager.cs | 10 +- .../Manager/ProviderUtils.cs | 2 +- .../Savers/BaseNfoSaver.cs | 6 +- .../Savers/MovieNfoSaver.cs | 2 +- .../Savers/SeasonNfoSaver.cs | 2 +- 89 files changed, 1554 insertions(+), 681 deletions(-) rename Emby.Server.Implementations/Library/Resolvers/{VideoResolver.cs => GenericVideoResolver.cs} (100%) create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs create mode 100644 Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs diff --git a/Emby.Dlna/ContentDirectory/ServerItem.cs b/Emby.Dlna/ContentDirectory/ServerItem.cs index 34244000c1..ff30e6e4af 100644 --- a/Emby.Dlna/ContentDirectory/ServerItem.cs +++ b/Emby.Dlna/ContentDirectory/ServerItem.cs @@ -17,7 +17,7 @@ namespace Emby.Dlna.ContentDirectory { Item = item; - if (item is IItemByName && !(item is Folder)) + if (item is IItemByName && item is not Folder) { StubType = Dlna.ContentDirectory.StubType.Folder; } diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 2982ce97e1..c000784997 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -748,7 +748,7 @@ namespace Emby.Dlna.Didl AddValue(writer, "upnp", "publisher", studio, NsUpnp); } - if (!(item is Folder)) + if (item is not Folder) { if (filter.Contains("dc:description")) { diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index bf7ddace2d..b640f06c6e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -456,6 +456,7 @@ namespace Emby.Server.Implementations /// /// Runs the startup tasks. /// + /// The cancellation token. /// . public async Task RunStartupTasksAsync(CancellationToken cancellationToken) { @@ -469,7 +470,7 @@ namespace Emby.Server.Implementations _mediaEncoder.SetFFmpegPath(); - Logger.LogInformation("ServerId: {0}", SystemId); + Logger.LogInformation("ServerId: {ServerId}", SystemId); var entryPoints = GetExports(); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index aa54510a71..41d1f9b392 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Channels var internalChannel = _libraryManager.GetItemById(item.ChannelId); var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); - return !(channel is IDisableMediaSourceDisplay); + return channel is not IDisableMediaSourceDisplay; } /// @@ -1079,11 +1079,11 @@ namespace Emby.Server.Implementations.Channels // was used for status // if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal)) - //{ + // { // item.ExternalEtag = info.Etag; // forceUpdate = true; // _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name); - //} + // } if (!internalChannelId.Equals(item.ChannelId)) { diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 6f23a0888e..01c9fbca81 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Data protected virtual int? CacheSize => null; /// - /// Gets the journal mode. + /// Gets the journal mode. . /// /// The journal mode. protected virtual string JournalMode => "TRUNCATE"; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 2cb10765ff..ab1b55164b 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -72,9 +72,16 @@ namespace Emby.Server.Implementations.Data _mediaAttachmentInsertPrefix = queryPrefixText.ToString(); } + /// /// Initializes a new instance of the class. /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// config is null. public SqliteItemRepository( IServerConfigurationManager config, IServerApplicationHost appHost, @@ -4879,7 +4886,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type foreach (var t in _knownTypes) { - dict[t.Name] = t.FullName ; + dict[t.Name] = t.FullName; } dict["Program"] = typeof(LiveTvProgram).FullName; diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index ef9af1dcd0..613d07d775 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -174,7 +174,6 @@ namespace Emby.Server.Implementations.Data /// The key. /// The user data. /// The cancellation token. - /// Task. public void PersistUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -319,8 +318,8 @@ namespace Emby.Server.Implementations.Data /// /// Return all user-data associated with the given user. /// - /// - /// + /// The internal user id. + /// The list of user item data. public List GetAllUserData(long internalUserId) { if (internalUserId <= 0) @@ -349,7 +348,8 @@ namespace Emby.Server.Implementations.Data /// /// Read a row from the specified reader into the provided userData object. /// - /// + /// The list of result set values. + /// The user item data. private UserItemData ReadRow(IReadOnlyList reader) { var userData = new UserItemData(); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 7411239a1e..7c2d02037c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -807,7 +807,7 @@ namespace Emby.Server.Implementations.Dto dto.MediaType = item.MediaType; - if (!(item is LiveTvProgram)) + if (item is not LiveTvProgram) { dto.LocationType = item.LocationType; } @@ -928,9 +928,9 @@ namespace Emby.Server.Implementations.Dto } // if (options.ContainsField(ItemFields.MediaSourceCount)) - //{ + // { // Songs always have one - //} + // } } if (item is IHasArtist hasArtist) @@ -938,10 +938,10 @@ namespace Emby.Server.Implementations.Dto dto.Artists = hasArtist.Artists; // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery - //{ + // { // EnableTotalRecordCount = false, // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } - //}); + // }); // dto.ArtistItems = artistItems.Items // .Select(i => @@ -958,7 +958,7 @@ namespace Emby.Server.Implementations.Dto // Include artists that are not in the database yet, e.g., just added via metadata editor // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); dto.ArtistItems = hasArtist.Artists - //.Except(foundArtists, new DistinctNameComparer()) + // .Except(foundArtists, new DistinctNameComparer()) .Select(i => { // This should not be necessary but we're seeing some cases of it @@ -990,10 +990,10 @@ namespace Emby.Server.Implementations.Dto dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery - //{ + // { // EnableTotalRecordCount = false, // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } - //}); + // }); // dto.AlbumArtists = artistItems.Items // .Select(i => @@ -1008,7 +1008,7 @@ namespace Emby.Server.Implementations.Dto // .ToList(); dto.AlbumArtists = hasAlbumArtist.AlbumArtists - //.Except(foundArtists, new DistinctNameComparer()) + // .Except(foundArtists, new DistinctNameComparer()) .Select(i => { // This should not be necessary but we're seeing some cases of it @@ -1035,8 +1035,7 @@ namespace Emby.Server.Implementations.Dto } // Add video info - var video = item as Video; - if (video != null) + if (item is Video video) { dto.VideoType = video.VideoType; dto.Video3DFormat = video.Video3DFormat; @@ -1075,9 +1074,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.MediaStreams)) { // Add VideoInfo - var iHasMediaSources = item as IHasMediaSources; - - if (iHasMediaSources != null) + if (item is IHasMediaSources) { MediaStream[] mediaStreams; @@ -1146,7 +1143,7 @@ namespace Emby.Server.Implementations.Dto // TODO maybe remove the if statement entirely // if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { - episodeSeries = episodeSeries ?? episode.Series; + episodeSeries ??= episode.Series; if (episodeSeries != null) { dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary); @@ -1159,7 +1156,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.SeriesStudio)) { - episodeSeries = episodeSeries ?? episode.Series; + episodeSeries ??= episode.Series; if (episodeSeries != null) { dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault(); @@ -1172,7 +1169,7 @@ namespace Emby.Server.Implementations.Dto { dto.AirDays = series.AirDays; dto.AirTime = series.AirTime; - dto.Status = series.Status.HasValue ? series.Status.Value.ToString() : null; + dto.Status = series.Status?.ToString(); } // Add SeasonInfo @@ -1185,7 +1182,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.SeriesStudio)) { - series = series ?? season.Series; + series ??= season.Series; if (series != null) { dto.SeriesStudio = series.Studios.FirstOrDefault(); @@ -1196,7 +1193,7 @@ namespace Emby.Server.Implementations.Dto // TODO maybe remove the if statement entirely // if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { - series = series ?? season.Series; + series ??= season.Series; if (series != null) { dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary); @@ -1283,7 +1280,7 @@ namespace Emby.Server.Implementations.Dto var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent(); - if (parent == null && !(originalItem is UserRootFolder) && !(originalItem is UserView) && !(originalItem is AggregateFolder) && !(originalItem is ICollectionFolder) && !(originalItem is Channel)) + if (parent == null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is not AggregateFolder && originalItem is not ICollectionFolder && originalItem is not Channel) { parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault(); } @@ -1317,7 +1314,7 @@ namespace Emby.Server.Implementations.Dto var imageTags = dto.ImageTags; while (((!(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0) || parent is Series) && - (parent = parent ?? (isFirst ? GetImageDisplayParent(item, item) ?? owner : parent)) != null) + (parent ??= (isFirst ? GetImageDisplayParent(item, item) ?? owner : parent)) != null) { if (parent == null) { @@ -1348,7 +1345,7 @@ namespace Emby.Server.Implementations.Dto } } - if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView)) + if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && parent is not ICollectionFolder && parent is not UserView) { var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb); diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 5bb4100ba9..df48346e3c 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.EntryPoints private static bool EnableRefreshMessage(BaseItem item) { - if (!(item is Folder folder)) + if (item is not Folder folder) { return false; } @@ -403,7 +403,7 @@ namespace Emby.Server.Implementations.EntryPoints return false; } - if (item is IItemByName && !(item is MusicArtist)) + if (item is IItemByName && item is not MusicArtist) { return false; } diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 2e72b18f57..feaccf9fa1 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -37,6 +37,9 @@ namespace Emby.Server.Implementations.EntryPoints /// /// Initializes a new instance of the class. /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. public UdpServerEntryPoint( ILogger logger, IServerApplicationHost appHost, diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index b2625a68c9..badc6ce6c6 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -74,6 +74,7 @@ namespace Emby.Server.Implementations.HttpServer.Security auth.TryGetValue("Token", out token); } +#pragma warning disable CA1508 // string.IsNullOrEmpty(token) is always false. if (string.IsNullOrEmpty(token)) { token = headers["X-Emby-Token"]; @@ -111,6 +112,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // Request doesn't contain a token. return authInfo; } +#pragma warning restore CA1508 authInfo.HasToken = true; var result = _authRepo.Get(new AuthenticationInfoQuery diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 5d38ea0ca6..7010a6fb08 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.HttpServer public event EventHandler? Closed; /// - /// Gets or sets the remote end point. + /// Gets the remote end point. /// public IPAddress? RemoteEndPoint { get; } @@ -82,7 +82,7 @@ namespace Emby.Server.Implementations.HttpServer public DateTime LastKeepAliveDate { get; set; } /// - /// Gets or sets the query string. + /// Gets the query string. /// /// The query string. public IQueryCollection QueryString { get; } diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index a430b9e720..1d97882db5 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -10,7 +10,7 @@ namespace Emby.Server.Implementations string? FFmpegPath { get; } /// - /// Gets the value of the --service command line option. + /// Gets a value value indicating whether to run as service by the --service command line option. /// bool IsService { get; } diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index ff5f26ce09..0229fbae79 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -30,27 +30,27 @@ namespace Emby.Server.Implementations.Images string[] includeItemTypes; - if (string.Equals(viewType, CollectionType.Movies)) + if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal)) { includeItemTypes = new string[] { "Movie" }; } - else if (string.Equals(viewType, CollectionType.TvShows)) + else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal)) { includeItemTypes = new string[] { "Series" }; } - else if (string.Equals(viewType, CollectionType.Music)) + else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal)) { includeItemTypes = new string[] { "MusicAlbum" }; } - else if (string.Equals(viewType, CollectionType.Books)) + else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal)) { includeItemTypes = new string[] { "Book", "AudioBook" }; } - else if (string.Equals(viewType, CollectionType.BoxSets)) + else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal)) { includeItemTypes = new string[] { "BoxSet" }; } - else if (string.Equals(viewType, CollectionType.HomeVideos) || string.Equals(viewType, CollectionType.Photos)) + else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal)) { includeItemTypes = new string[] { "Video", "Photo" }; } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 13fb8b2fd5..6a2983d9ba 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -287,14 +287,14 @@ namespace Emby.Server.Implementations.Library if (item is IItemByName) { - if (!(item is MusicArtist)) + if (item is not MusicArtist) { return; } } else if (!item.IsFolder) { - if (!(item is Video) && !(item is LiveTvChannel)) + if (item is not Video && item is not LiveTvChannel) { return; } @@ -866,7 +866,7 @@ namespace Emby.Server.Implementations.Library { var path = Person.GetPath(name); var id = GetItemByNameId(path); - if (!(GetItemById(id) is Person item)) + if (GetItemById(id) is not Person item) { item = new Person { @@ -2118,7 +2118,7 @@ namespace Emby.Server.Implementations.Library public LibraryOptions GetLibraryOptions(BaseItem item) { - if (!(item is CollectionFolder collectionFolder)) + if (item is not CollectionFolder collectionFolder) { // List.Find is more performant than FirstOrDefault due to enumerator allocation collectionFolder = GetCollectionFolders(item) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index e893d63350..fd9747b4b7 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -21,11 +21,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio /// public class AudioResolver : ItemResolver, IMultiItemResolver { - private readonly ILibraryManager LibraryManager; + private readonly ILibraryManager _libraryManager; public AudioResolver(ILibraryManager libraryManager) { - LibraryManager = libraryManager; + _libraryManager = libraryManager; } /// @@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio } var files = args.FileSystemChildren - .Where(i => !LibraryManager.IgnoreFile(i, args.Parent)) + .Where(i => !_libraryManager.IgnoreFile(i, args.Parent)) .ToList(); return FindAudio(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); } - if (LibraryManager.IsAudioFile(args.Path)) + if (_libraryManager.IsAudioFile(args.Path)) { var extension = Path.GetExtension(args.Path); @@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var isMixedCollectionType = string.IsNullOrEmpty(collectionType); // For conflicting extensions, give priority to videos - if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path)) + if (isMixedCollectionType && _libraryManager.IsVideoFile(args.Path)) { return null; } @@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio } } - var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); + var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var resolver = new AudioBookListResolver(namingOptions); var resolverResult = resolver.Resolve(files).ToList(); diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index cdb492022b..e003328089 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers /// /// Resolves a Path into a Video or Video subclass. /// - /// + /// The type of item to resolve. public abstract class BaseVideoResolver : MediaBrowser.Controller.Resolvers.ItemResolver where T : Video, new() { @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers break; } - if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService)) + if (IsBluRayDirectory(filename)) { videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); @@ -279,25 +279,13 @@ namespace Emby.Server.Implementations.Library.Resolvers } /// - /// Determines whether [is blu ray directory] [the specified directory name]. + /// Determines whether [is bluray directory] [the specified directory name]. /// - protected bool IsBluRayDirectory(string fullPath, string directoryName, IDirectoryService directoryService) + /// The directory name. + /// Whether the directory is a bluray directory. + protected bool IsBluRayDirectory(string directoryName) { - if (!string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - return true; - // var blurayExtensions = new[] - //{ - // ".mts", - // ".m2ts", - // ".bdmv", - // ".mpls" - //}; - - // return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)); + return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase); } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs similarity index 100% rename from Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs rename to Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs index fa45ccf840..3f29ab191f 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Library.Resolvers /// /// Class ItemResolver. /// - /// + /// The type of BaseItem. public abstract class ItemResolver : IItemResolver where T : BaseItem, new() { diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 889e29a6ba..8b55a77449 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -400,7 +400,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return movie; } - if (IsBluRayDirectory(child.FullName, filename, directoryService)) + if (IsBluRayDirectory(filename)) { var movie = new T { @@ -481,7 +481,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return true; } - if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService))) + if (subfolders.Any(s => IsBluRayDirectory(s.Name))) { videoTypes.Add(VideoType.BluRay); return true; diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index ecd44be477..2c4ead7198 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -18,7 +18,8 @@ namespace Emby.Server.Implementations.Library.Resolvers /// public class PlaylistResolver : FolderResolver { - private string[] _musicPlaylistCollectionTypes = new string[] { + private string[] _musicPlaylistCollectionTypes = + { string.Empty, CollectionType.Music }; diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 9a8c5f39d5..16bdf720ce 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -87,12 +87,15 @@ namespace Emby.Server.Implementations.Library.Validators foreach (var item in deadEntities) { - _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); + _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); - _libraryManager.DeleteItem(item, new DeleteOptions + _libraryManager.DeleteItem( + item, + new DeleteOptions { DeleteFileLocation = false - }, false); + }, + false); } progress.Report(100); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7970631201..a6be407450 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1458,7 +1458,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase)) { var parentItem = item.GetParent(); - if (parentItem != null && !(parentItem is AggregateFolder)) + if (parentItem != null && parentItem is not AggregateFolder) { item = parentItem; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs index 0ec52a9598..20a8213a77 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs @@ -8,7 +8,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { internal class EpgChannelData { - private readonly Dictionary _channelsById; private readonly Dictionary _channelsByNumber; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index b7639a51ce..bcfc4011c5 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -14,6 +14,7 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos; using MediaBrowser.Common; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; @@ -96,12 +97,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings var dates = GetScheduleRequestDates(startDateUtc, endDateUtc); _logger.LogInformation("Channel Station ID is: {ChannelID}", channelId); - var requestList = new List() + var requestList = new List() { - new ScheduleDirect.RequestScheduleForChannel() + new RequestScheduleForChannelDto() { - stationID = channelId, - date = dates + StationId = channelId, + Date = dates } }; @@ -113,61 +114,61 @@ namespace Emby.Server.Implementations.LiveTv.Listings options.Headers.TryAddWithoutValidation("token", token); using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - var dailySchedules = await JsonSerializer.DeserializeAsync>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var dailySchedules = await JsonSerializer.DeserializeAsync>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs"); programRequestOptions.Headers.TryAddWithoutValidation("token", token); - var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct(); + var programsID = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json); using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - var programDetails = await JsonSerializer.DeserializeAsync>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - var programDict = programDetails.ToDictionary(p => p.programID, y => y); + var programDetails = await JsonSerializer.DeserializeAsync>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y); var programIdsWithImages = programDetails - .Where(p => p.hasImageArtwork).Select(p => p.programID) + .Where(p => p.HasImageArtwork).Select(p => p.ProgramId) .ToList(); var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false); var programsInfo = new List(); - foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs)) + foreach (ProgramDto schedule in dailySchedules.SelectMany(d => d.Programs)) { // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID + // " which corresponds to channel " + channelNumber + " and program id " + - // schedule.programID + " which says it has images? " + - // programDict[schedule.programID].hasImageArtwork); + // schedule.ProgramId + " which says it has images? " + + // programDict[schedule.ProgramId].hasImageArtwork); if (images != null) { - var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10)); + var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]); if (imageIndex > -1) { - var programEntry = programDict[schedule.programID]; + var programEntry = programDict[schedule.ProgramId]; - var allImages = images[imageIndex].data ?? new List(); - var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase)); - var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase)); + var allImages = images[imageIndex].Data ?? new List(); + var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase)); + var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase)); const double DesiredAspect = 2.0 / 3; - programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ?? + programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ?? GetProgramImage(ApiUrl, allImages, true, DesiredAspect); const double WideAspect = 16.0 / 9; - programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect); + programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect); // Don't supply the same image twice - if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal)) + if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal)) { - programEntry.thumbImage = null; + programEntry.ThumbImage = null; } - programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect); + programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect); // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? @@ -176,15 +177,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID])); + programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.ProgramId])); } return programsInfo; } - private static int GetSizeOrder(ScheduleDirect.ImageData image) + private static int GetSizeOrder(ImageDataDto image) { - if (int.TryParse(image.height, out int value)) + if (int.TryParse(image.Height, out int value)) { return value; } @@ -192,53 +193,53 @@ namespace Emby.Server.Implementations.LiveTv.Listings return 0; } - private static string GetChannelNumber(ScheduleDirect.Map map) + private static string GetChannelNumber(MapDto map) { - var channelNumber = map.logicalChannelNumber; + var channelNumber = map.LogicalChannelNumber; if (string.IsNullOrWhiteSpace(channelNumber)) { - channelNumber = map.channel; + channelNumber = map.Channel; } if (string.IsNullOrWhiteSpace(channelNumber)) { - channelNumber = map.atscMajor + "." + map.atscMinor; + channelNumber = map.AtscMajor + "." + map.AtscMinor; } return channelNumber.TrimStart('0'); } - private static bool IsMovie(ScheduleDirect.ProgramDetails programInfo) + private static bool IsMovie(ProgramDetailsDto programInfo) { - return string.Equals(programInfo.entityType, "movie", StringComparison.OrdinalIgnoreCase); + return string.Equals(programInfo.EntityType, "movie", StringComparison.OrdinalIgnoreCase); } - private ProgramInfo GetProgram(string channelId, ScheduleDirect.Program programInfo, ScheduleDirect.ProgramDetails details) + private ProgramInfo GetProgram(string channelId, ProgramDto programInfo, ProgramDetailsDto details) { - var startAt = GetDate(programInfo.airDateTime); - var endAt = startAt.AddSeconds(programInfo.duration); + var startAt = GetDate(programInfo.AirDateTime); + var endAt = startAt.AddSeconds(programInfo.Duration); var audioType = ProgramAudio.Stereo; - var programId = programInfo.programID ?? string.Empty; + var programId = programInfo.ProgramId ?? string.Empty; string newID = programId + "T" + startAt.Ticks + "C" + channelId; - if (programInfo.audioProperties != null) + if (programInfo.AudioProperties != null) { - if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase))) + if (programInfo.AudioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase))) { audioType = ProgramAudio.Atmos; } - else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) + else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) { audioType = ProgramAudio.DolbyDigital; } - else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase))) + else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase))) { audioType = ProgramAudio.DolbyDigital; } - else if (programInfo.audioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase))) + else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase))) { audioType = ProgramAudio.Stereo; } @@ -249,9 +250,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings } string episodeTitle = null; - if (details.episodeTitle150 != null) + if (details.EpisodeTitle150 != null) { - episodeTitle = details.episodeTitle150; + episodeTitle = details.EpisodeTitle150; } var info = new ProgramInfo @@ -260,22 +261,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings Id = newID, StartDate = startAt, EndDate = endAt, - Name = details.titles[0].title120 ?? "Unknown", + Name = details.Titles[0].Title120 ?? "Unknown", OfficialRating = null, CommunityRating = null, EpisodeTitle = episodeTitle, Audio = audioType, // IsNew = programInfo.@new ?? false, - IsRepeat = programInfo.@new == null, - IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase), - ImageUrl = details.primaryImage, - ThumbImageUrl = details.thumbImage, - IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase), - IsSports = string.Equals(details.entityType, "sports", StringComparison.OrdinalIgnoreCase), + IsRepeat = programInfo.New == null, + IsSeries = string.Equals(details.EntityType, "episode", StringComparison.OrdinalIgnoreCase), + ImageUrl = details.PrimaryImage, + ThumbImageUrl = details.ThumbImage, + IsKids = string.Equals(details.Audience, "children", StringComparison.OrdinalIgnoreCase), + IsSports = string.Equals(details.EntityType, "sports", StringComparison.OrdinalIgnoreCase), IsMovie = IsMovie(details), - Etag = programInfo.md5, - IsLive = string.Equals(programInfo.liveTapeDelay, "live", StringComparison.OrdinalIgnoreCase), - IsPremiere = programInfo.premiere || (programInfo.isPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1 + Etag = programInfo.Md5, + IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase), + IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1 }; var showId = programId; @@ -298,15 +299,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings info.ShowId = showId; - if (programInfo.videoProperties != null) + if (programInfo.VideoProperties != null) { - info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase); - info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase); + info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase); + info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase); } - if (details.contentRating != null && details.contentRating.Count > 0) + if (details.ContentRating != null && details.ContentRating.Count > 0) { - info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-", StringComparison.Ordinal) + info.OfficialRating = details.ContentRating[0].Code.Replace("TV", "TV-", StringComparison.Ordinal) .Replace("--", "-", StringComparison.Ordinal); var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" }; @@ -316,15 +317,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - if (details.descriptions != null) + if (details.Descriptions != null) { - if (details.descriptions.description1000 != null && details.descriptions.description1000.Count > 0) + if (details.Descriptions.Description1000 != null && details.Descriptions.Description1000.Count > 0) { - info.Overview = details.descriptions.description1000[0].description; + info.Overview = details.Descriptions.Description1000[0].Description; } - else if (details.descriptions.description100 != null && details.descriptions.description100.Count > 0) + else if (details.Descriptions.Description100 != null && details.Descriptions.Description100.Count > 0) { - info.Overview = details.descriptions.description100[0].description; + info.Overview = details.Descriptions.Description100[0].Description; } } @@ -334,18 +335,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings info.SeriesProviderIds[MetadataProvider.Zap2It.ToString()] = info.SeriesId; - if (details.metadata != null) + if (details.Metadata != null) { - foreach (var metadataProgram in details.metadata) + foreach (var metadataProgram in details.Metadata) { var gracenote = metadataProgram.Gracenote; if (gracenote != null) { - info.SeasonNumber = gracenote.season; + info.SeasonNumber = gracenote.Season; - if (gracenote.episode > 0) + if (gracenote.Episode > 0) { - info.EpisodeNumber = gracenote.episode; + info.EpisodeNumber = gracenote.Episode; } break; @@ -354,25 +355,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - if (!string.IsNullOrWhiteSpace(details.originalAirDate)) + if (!string.IsNullOrWhiteSpace(details.OriginalAirDate)) { - info.OriginalAirDate = DateTime.Parse(details.originalAirDate, CultureInfo.InvariantCulture); + info.OriginalAirDate = DateTime.Parse(details.OriginalAirDate, CultureInfo.InvariantCulture); info.ProductionYear = info.OriginalAirDate.Value.Year; } - if (details.movie != null) + if (details.Movie != null) { - if (!string.IsNullOrEmpty(details.movie.year) - && int.TryParse(details.movie.year, out int year)) + if (!string.IsNullOrEmpty(details.Movie.Year) + && int.TryParse(details.Movie.Year, out int year)) { info.ProductionYear = year; } } - if (details.genres != null) + if (details.Genres != null) { - info.Genres = details.genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList(); - info.IsNews = details.genres.Contains("news", StringComparer.OrdinalIgnoreCase); + info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList(); + info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase); if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase)) { @@ -395,11 +396,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings return date; } - private string GetProgramImage(string apiUrl, IEnumerable images, bool returnDefaultImage, double desiredAspect) + private string GetProgramImage(string apiUrl, IEnumerable images, bool returnDefaultImage, double desiredAspect) { var match = images .OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i))) - .ThenByDescending(GetSizeOrder) + .ThenByDescending(i => GetSizeOrder(i)) .FirstOrDefault(); if (match == null) @@ -407,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings return null; } - var uri = match.uri; + var uri = match.Uri; if (string.IsNullOrWhiteSpace(uri)) { @@ -423,19 +424,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - private static double GetAspectRatio(ScheduleDirect.ImageData i) + private static double GetAspectRatio(ImageDataDto i) { int width = 0; int height = 0; - if (!string.IsNullOrWhiteSpace(i.width)) + if (!string.IsNullOrWhiteSpace(i.Width)) { - int.TryParse(i.width, out width); + _ = int.TryParse(i.Width, out width); } - if (!string.IsNullOrWhiteSpace(i.height)) + if (!string.IsNullOrWhiteSpace(i.Height)) { - int.TryParse(i.height, out height); + _ = int.TryParse(i.Height, out height); } if (height == 0 || width == 0) @@ -448,14 +449,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings return result; } - private async Task> GetImageForPrograms( + private async Task> GetImageForPrograms( ListingsProviderInfo info, IReadOnlyList programIds, CancellationToken cancellationToken) { if (programIds.Count == 0) { - return new List(); + return new List(); } StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13)); @@ -479,13 +480,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings { using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false); await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); + return await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "Error getting image info from schedules direct"); - return new List(); + return new List(); } } @@ -508,18 +509,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false); await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - var root = await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); + var root = await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); if (root != null) { - foreach (ScheduleDirect.Headends headend in root) + foreach (HeadendsDto headend in root) { - foreach (ScheduleDirect.Lineup lineup in headend.lineups) + foreach (LineupDto lineup in headend.Lineups) { lineups.Add(new NameIdPair { - Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name, - Id = lineup.uri.Substring(18) + Name = string.IsNullOrWhiteSpace(lineup.Name) ? lineup.Lineup : lineup.Name, + Id = lineup.Uri[18..] }); } } @@ -649,14 +650,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - if (string.Equals(root.message, "OK", StringComparison.Ordinal)) + var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + if (string.Equals(root.Message, "OK", StringComparison.Ordinal)) { - _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token); - return root.token; + _logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token); + return root.Token; } - throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message); + throw new Exception("Could not authenticate with Schedules Direct Error: " + root.Message); } private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken) @@ -705,9 +706,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings httpResponse.EnsureSuccessStatusCode(); await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var response = httpResponse.Content; - var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase)); + return root.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase)); } catch (HttpRequestException ex) { @@ -777,35 +778,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false); await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count); + var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.Map.Count); _logger.LogInformation("Mapping Stations to Channel"); - var allStations = root.stations ?? new List(); + var allStations = root.Stations ?? new List(); - var map = root.map; + var map = root.Map; var list = new List(map.Count); foreach (var channel in map) { var channelNumber = GetChannelNumber(channel); - var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)) - ?? new ScheduleDirect.Station + var station = allStations.Find(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase)) + ?? new StationDto { - stationID = channel.stationID + StationId = channel.StationId }; var channelInfo = new ChannelInfo { - Id = station.stationID, - CallSign = station.callsign, + Id = station.StationId, + CallSign = station.Callsign, Number = channelNumber, - Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name + Name = string.IsNullOrWhiteSpace(station.Name) ? channelNumber : station.Name }; - if (station.logo != null) + if (station.Logo != null) { - channelInfo.ImageUrl = station.logo.URL; + channelInfo.ImageUrl = station.Logo.Url; } list.Add(channelInfo); @@ -818,402 +819,5 @@ namespace Emby.Server.Implementations.LiveTv.Listings { return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal); } - - public class ScheduleDirect - { - public class Token - { - public int code { get; set; } - - public string message { get; set; } - - public string serverID { get; set; } - - public string token { get; set; } - } - - public class Lineup - { - public string lineup { get; set; } - - public string name { get; set; } - - public string transport { get; set; } - - public string location { get; set; } - - public string uri { get; set; } - } - - public class Lineups - { - public int code { get; set; } - - public string serverID { get; set; } - - public string datetime { get; set; } - - public List lineups { get; set; } - } - - public class Headends - { - public string headend { get; set; } - - public string transport { get; set; } - - public string location { get; set; } - - public List lineups { get; set; } - } - - public class Map - { - public string stationID { get; set; } - - public string channel { get; set; } - - public string logicalChannelNumber { get; set; } - - public int uhfVhf { get; set; } - - public int atscMajor { get; set; } - - public int atscMinor { get; set; } - } - - public class Broadcaster - { - public string city { get; set; } - - public string state { get; set; } - - public string postalcode { get; set; } - - public string country { get; set; } - } - - public class Logo - { - public string URL { get; set; } - - public int height { get; set; } - - public int width { get; set; } - - public string md5 { get; set; } - } - - public class Station - { - public string stationID { get; set; } - - public string name { get; set; } - - public string callsign { get; set; } - - public List broadcastLanguage { get; set; } - - public List descriptionLanguage { get; set; } - - public Broadcaster broadcaster { get; set; } - - public string affiliate { get; set; } - - public Logo logo { get; set; } - - public bool? isCommercialFree { get; set; } - } - - public class Metadata - { - public string lineup { get; set; } - - public string modified { get; set; } - - public string transport { get; set; } - } - - public class Channel - { - public List map { get; set; } - - public List stations { get; set; } - - public Metadata metadata { get; set; } - } - - public class RequestScheduleForChannel - { - public string stationID { get; set; } - - public List date { get; set; } - } - - public class Rating - { - public string body { get; set; } - - public string code { get; set; } - } - - public class Multipart - { - public int partNumber { get; set; } - - public int totalParts { get; set; } - } - - public class Program - { - public string programID { get; set; } - - public string airDateTime { get; set; } - - public int duration { get; set; } - - public string md5 { get; set; } - - public List audioProperties { get; set; } - - public List videoProperties { get; set; } - - public List ratings { get; set; } - - public bool? @new { get; set; } - - public Multipart multipart { get; set; } - - public string liveTapeDelay { get; set; } - - public bool premiere { get; set; } - - public bool repeat { get; set; } - - public string isPremiereOrFinale { get; set; } - } - - public class MetadataSchedule - { - public string modified { get; set; } - - public string md5 { get; set; } - - public string startDate { get; set; } - - public string endDate { get; set; } - - public int days { get; set; } - } - - public class Day - { - public string stationID { get; set; } - - public List programs { get; set; } - - public MetadataSchedule metadata { get; set; } - - public Day() - { - programs = new List(); - } - } - - public class Title - { - public string title120 { get; set; } - } - - public class EventDetails - { - public string subType { get; set; } - } - - public class Description100 - { - public string descriptionLanguage { get; set; } - - public string description { get; set; } - } - - public class Description1000 - { - public string descriptionLanguage { get; set; } - - public string description { get; set; } - } - - public class DescriptionsProgram - { - public List description100 { get; set; } - - public List description1000 { get; set; } - } - - public class Gracenote - { - public int season { get; set; } - - public int episode { get; set; } - } - - public class MetadataPrograms - { - public Gracenote Gracenote { get; set; } - } - - public class ContentRating - { - public string body { get; set; } - - public string code { get; set; } - } - - public class Cast - { - public string billingOrder { get; set; } - - public string role { get; set; } - - public string nameId { get; set; } - - public string personId { get; set; } - - public string name { get; set; } - - public string characterName { get; set; } - } - - public class Crew - { - public string billingOrder { get; set; } - - public string role { get; set; } - - public string nameId { get; set; } - - public string personId { get; set; } - - public string name { get; set; } - } - - public class QualityRating - { - public string ratingsBody { get; set; } - - public string rating { get; set; } - - public string minRating { get; set; } - - public string maxRating { get; set; } - - public string increment { get; set; } - } - - public class Movie - { - public string year { get; set; } - - public int duration { get; set; } - - public List qualityRating { get; set; } - } - - public class Recommendation - { - public string programID { get; set; } - - public string title120 { get; set; } - } - - public class ProgramDetails - { - public string audience { get; set; } - - public string programID { get; set; } - - public List titles { get; set; } - - public EventDetails eventDetails { get; set; } - - public DescriptionsProgram descriptions { get; set; } - - public string originalAirDate { get; set; } - - public List<string> genres { get; set; } - - public string episodeTitle150 { get; set; } - - public List<MetadataPrograms> metadata { get; set; } - - public List<ContentRating> contentRating { get; set; } - - public List<Cast> cast { get; set; } - - public List<Crew> crew { get; set; } - - public string entityType { get; set; } - - public string showType { get; set; } - - public bool hasImageArtwork { get; set; } - - public string primaryImage { get; set; } - - public string thumbImage { get; set; } - - public string backdropImage { get; set; } - - public string bannerImage { get; set; } - - public string imageID { get; set; } - - public string md5 { get; set; } - - public List<string> contentAdvisory { get; set; } - - public Movie movie { get; set; } - - public List<Recommendation> recommendations { get; set; } - } - - public class Caption - { - public string content { get; set; } - - public string lang { get; set; } - } - - public class ImageData - { - public string width { get; set; } - - public string height { get; set; } - - public string uri { get; set; } - - public string size { get; set; } - - public string aspect { get; set; } - - public string category { get; set; } - - public string text { get; set; } - - public string primary { get; set; } - - public string tier { get; set; } - - public Caption caption { get; set; } - } - - public class ShowImages - { - public string programID { get; set; } - - public List<ImageData> data { get; set; } - } - } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs new file mode 100644 index 0000000000..b881b307c3 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs @@ -0,0 +1,36 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Broadcaster dto. + /// </summary> + public class BroadcasterDto + { + /// <summary> + /// Gets or sets the city. + /// </summary> + [JsonPropertyName("city")] + public string City { get; set; } + + /// <summary> + /// Gets or sets the state. + /// </summary> + [JsonPropertyName("state")] + public string State { get; set; } + + /// <summary> + /// Gets or sets the postal code. + /// </summary> + [JsonPropertyName("postalCode")] + public string Postalcode { get; set; } + + /// <summary> + /// Gets or sets the country. + /// </summary> + [JsonPropertyName("country")] + public string Country { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs new file mode 100644 index 0000000000..96b67d1eb3 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs @@ -0,0 +1,24 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Caption dto. + /// </summary> + public class CaptionDto + { + /// <summary> + /// Gets or sets the content. + /// </summary> + [JsonPropertyName("content")] + public string Content { get; set; } + + /// <summary> + /// Gets or sets the lang. + /// </summary> + [JsonPropertyName("lang")] + public string Lang { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs new file mode 100644 index 0000000000..dac6f5f3e0 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs @@ -0,0 +1,48 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Cast dto. + /// </summary> + public class CastDto + { + /// <summary> + /// Gets or sets the billing order. + /// </summary> + [JsonPropertyName("billingOrder")] + public string BillingOrder { get; set; } + + /// <summary> + /// Gets or sets the role. + /// </summary> + [JsonPropertyName("role")] + public string Role { get; set; } + + /// <summary> + /// Gets or sets the name id. + /// </summary> + [JsonPropertyName("nameId")] + public string NameId { get; set; } + + /// <summary> + /// Gets or sets the person id. + /// </summary> + [JsonPropertyName("personId")] + public string PersonId { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + [JsonPropertyName("name")] + public string Name { get; set; } + + /// <summary> + /// Gets or sets the character name. + /// </summary> + [JsonPropertyName("characterName")] + public string CharacterName { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs new file mode 100644 index 0000000000..8c9c2c1fcc --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs @@ -0,0 +1,31 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Channel dto. + /// </summary> + public class ChannelDto + { + /// <summary> + /// Gets or sets the list of maps. + /// </summary> + [JsonPropertyName("map")] + public List<MapDto> Map { get; set; } + + /// <summary> + /// Gets or sets the list of stations. + /// </summary> + [JsonPropertyName("stations")] + public List<StationDto> Stations { get; set; } + + /// <summary> + /// Gets or sets the metadata. + /// </summary> + [JsonPropertyName("metadata")] + public MetadataDto Metadata { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs new file mode 100644 index 0000000000..135b5bb08b --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs @@ -0,0 +1,24 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Content rating dto. + /// </summary> + public class ContentRatingDto + { + /// <summary> + /// Gets or sets the body. + /// </summary> + [JsonPropertyName("body")] + public string Body { get; set; } + + /// <summary> + /// Gets or sets the code. + /// </summary> + [JsonPropertyName("code")] + public string Code { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs new file mode 100644 index 0000000000..82d1001c88 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs @@ -0,0 +1,42 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Crew dto. + /// </summary> + public class CrewDto + { + /// <summary> + /// Gets or sets the billing order. + /// </summary> + [JsonPropertyName("billingOrder")] + public string BillingOrder { get; set; } + + /// <summary> + /// Gets or sets the role. + /// </summary> + [JsonPropertyName("role")] + public string Role { get; set; } + + /// <summary> + /// Gets or sets the name id. + /// </summary> + [JsonPropertyName("nameId")] + public string NameId { get; set; } + + /// <summary> + /// Gets or sets the person id. + /// </summary> + [JsonPropertyName("personId")] + public string PersonId { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + [JsonPropertyName("name")] + public string Name { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs new file mode 100644 index 0000000000..68876b0686 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs @@ -0,0 +1,39 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Day dto. + /// </summary> + public class DayDto + { + /// <summary> + /// Initializes a new instance of the <see cref="DayDto"/> class. + /// </summary> + public DayDto() + { + Programs = new List<ProgramDto>(); + } + + /// <summary> + /// Gets or sets the station id. + /// </summary> + [JsonPropertyName("stationID")] + public string StationId { get; set; } + + /// <summary> + /// Gets or sets the list of programs. + /// </summary> + [JsonPropertyName("programs")] + public List<ProgramDto> Programs { get; set; } + + /// <summary> + /// Gets or sets the metadata schedule. + /// </summary> + [JsonPropertyName("metadata")] + public MetadataScheduleDto Metadata { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs new file mode 100644 index 0000000000..d3e6ff3935 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs @@ -0,0 +1,24 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Description 1_000 dto. + /// </summary> + public class Description1000Dto + { + /// <summary> + /// Gets or sets the description language. + /// </summary> + [JsonPropertyName("descriptionLanguage")] + public string DescriptionLanguage { get; set; } + + /// <summary> + /// Gets or sets the description. + /// </summary> + [JsonPropertyName("description")] + public string Description { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs new file mode 100644 index 0000000000..04360266c9 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs @@ -0,0 +1,24 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Description 100 dto. + /// </summary> + public class Description100Dto + { + /// <summary> + /// Gets or sets the description language. + /// </summary> + [JsonPropertyName("descriptionLanguage")] + public string DescriptionLanguage { get; set; } + + /// <summary> + /// Gets or sets the description. + /// </summary> + [JsonPropertyName("description")] + public string Description { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs new file mode 100644 index 0000000000..3af36ae965 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs @@ -0,0 +1,25 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Descriptions program dto. + /// </summary> + public class DescriptionsProgramDto + { + /// <summary> + /// Gets or sets the list of description 100. + /// </summary> + [JsonPropertyName("description100")] + public List<Description100Dto> Description100 { get; set; } + + /// <summary> + /// Gets or sets the list of description1000. + /// </summary> + [JsonPropertyName("description1000")] + public List<Description1000Dto> Description1000 { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs new file mode 100644 index 0000000000..c3b2bd9c13 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs @@ -0,0 +1,18 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Event details dto. + /// </summary> + public class EventDetailsDto + { + /// <summary> + /// Gets or sets the sub type. + /// </summary> + [JsonPropertyName("subType")] + public string SubType { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs new file mode 100644 index 0000000000..3d8bea362a --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs @@ -0,0 +1,24 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Gracenote dto. + /// </summary> + public class GracenoteDto + { + /// <summary> + /// Gets or sets the season. + /// </summary> + [JsonPropertyName("season")] + public int Season { get; set; } + + /// <summary> + /// Gets or sets the episode. + /// </summary> + [JsonPropertyName("episode")] + public int Episode { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs new file mode 100644 index 0000000000..1fb3decb23 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs @@ -0,0 +1,37 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Headends dto. + /// </summary> + public class HeadendsDto + { + /// <summary> + /// Gets or sets the headend. + /// </summary> + [JsonPropertyName("headend")] + public string Headend { get; set; } + + /// <summary> + /// Gets or sets the transport. + /// </summary> + [JsonPropertyName("transport")] + public string Transport { get; set; } + + /// <summary> + /// Gets or sets the location. + /// </summary> + [JsonPropertyName("location")] + public string Location { get; set; } + + /// <summary> + /// Gets or sets the list of lineups. + /// </summary> + [JsonPropertyName("lineups")] + public List<LineupDto> Lineups { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs new file mode 100644 index 0000000000..b80ac624cc --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs @@ -0,0 +1,69 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + public class ImageDataDto + { + /// <summary> + /// Gets or sets the width. + /// </summary> + [JsonPropertyName("width")] + public string Width { get; set; } + + /// <summary> + /// Gets or sets the height. + /// </summary> + [JsonPropertyName("height")] + public string Height { get; set; } + + /// <summary> + /// Gets or sets the uri. + /// </summary> + [JsonPropertyName("uri")] + public string Uri { get; set; } + + /// <summary> + /// Gets or sets the size. + /// </summary> + [JsonPropertyName("size")] + public string Size { get; set; } + + /// <summary> + /// Gets or sets the aspect. + /// </summary> + [JsonPropertyName("aspect")] + public string aspect { get; set; } + + /// <summary> + /// Gets or sets the category. + /// </summary> + [JsonPropertyName("category")] + public string Category { get; set; } + + /// <summary> + /// Gets or sets the text. + /// </summary> + [JsonPropertyName("text")] + public string Text { get; set; } + + /// <summary> + /// Gets or sets the primary. + /// </summary> + [JsonPropertyName("primary")] + public string Primary { get; set; } + + /// <summary> + /// Gets or sets the tier. + /// </summary> + [JsonPropertyName("tier")] + public string Tier { get; set; } + + /// <summary> + /// Gets or sets the caption. + /// </summary> + [JsonPropertyName("caption")] + public CaptionDto Caption { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs new file mode 100644 index 0000000000..b8f27a8ac7 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs @@ -0,0 +1,42 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// The lineup dto. + /// </summary> + public class LineupDto + { + /// <summary> + /// Gets or sets the linup. + /// </summary> + [JsonPropertyName("lineup")] + public string Lineup { get; set; } + + /// <summary> + /// Gets or sets the lineup name. + /// </summary> + [JsonPropertyName("name")] + public string Name { get; set; } + + /// <summary> + /// Gets or sets the transport. + /// </summary> + [JsonPropertyName("transport.")] + public string Transport { get; set; } + + /// <summary> + /// Gets or sets the location. + /// </summary> + [JsonPropertyName("location")] + public string Location { get; set; } + + /// <summary> + /// Gets or sets the uri. + /// </summary> + [JsonPropertyName("uri")] + public string Uri { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs new file mode 100644 index 0000000000..15139ba3b2 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs @@ -0,0 +1,37 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Lineups dto. + /// </summary> + public class LineupsDto + { + /// <summary> + /// Gets or sets the response code. + /// </summary> + [JsonPropertyName("code")] + public int Code { get; set; } + + /// <summary> + /// Gets or sets the server id. + /// </summary> + [JsonPropertyName("serverID")] + public string ServerId { get; set; } + + /// <summary> + /// Gets or sets the datetime. + /// </summary> + [JsonPropertyName("datetime")] + public string Datetime { get; set; } + + /// <summary> + /// Gets or sets the list of lineups. + /// </summary> + [JsonPropertyName("lineups")] + public List<LineupDto> Lineups { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs new file mode 100644 index 0000000000..7b235ed7f6 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs @@ -0,0 +1,36 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Logo dto. + /// </summary> + public class LogoDto + { + /// <summary> + /// Gets or sets the url. + /// </summary> + [JsonPropertyName("URL")] + public string Url { get; set; } + + /// <summary> + /// Gets or sets the height. + /// </summary> + [JsonPropertyName("height")] + public int Height { get; set; } + + /// <summary> + /// Gets or sets the width. + /// </summary> + [JsonPropertyName("width")] + public int Width { get; set; } + + /// <summary> + /// Gets or sets the md5. + /// </summary> + [JsonPropertyName("md5")] + public string Md5 { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs new file mode 100644 index 0000000000..8d45e8fff7 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs @@ -0,0 +1,48 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Map dto. + /// </summary> + public class MapDto + { + /// <summary> + /// Gets or sets the station id. + /// </summary> + [JsonPropertyName("stationID")] + public string StationId { get; set; } + + /// <summary> + /// Gets or sets the channel. + /// </summary> + [JsonPropertyName("channel")] + public string Channel { get; set; } + + /// <summary> + /// Gets or sets the logical channel number. + /// </summary> + [JsonPropertyName("logicalChannelNumber")] + public string LogicalChannelNumber { get; set; } + + /// <summary> + /// Gets or sets the uhfvhf. + /// </summary> + [JsonPropertyName("uhfVhf")] + public int UhfVhf { get; set; } + + /// <summary> + /// Gets or sets the astc major. + /// </summary> + [JsonPropertyName("astcMajor")] + public int AtscMajor { get; set; } + + /// <summary> + /// Gets or sets the astc minor. + /// </summary> + [JsonPropertyName("astcMinor")] + public int AtscMinor { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs new file mode 100644 index 0000000000..5a3893a35f --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs @@ -0,0 +1,30 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Metadata dto. + /// </summary> + public class MetadataDto + { + /// <summary> + /// Gets or sets the linup. + /// </summary> + [JsonPropertyName("lineup")] + public string Lineup { get; set; } + + /// <summary> + /// Gets or sets the modified timestamp. + /// </summary> + [JsonPropertyName("modified")] + public string Modified { get; set; } + + /// <summary> + /// Gets or sets the transport. + /// </summary> + [JsonPropertyName("transport")] + public string Transport { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs new file mode 100644 index 0000000000..4057e98024 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs @@ -0,0 +1,18 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Metadata programs dto. + /// </summary> + public class MetadataProgramsDto + { + /// <summary> + /// Gets or sets the gracenote object. + /// </summary> + [JsonPropertyName("gracenote")] + public GracenoteDto Gracenote { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs new file mode 100644 index 0000000000..4979296da4 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs @@ -0,0 +1,42 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Metadata schedule dto. + /// </summary> + public class MetadataScheduleDto + { + /// <summary> + /// Gets or sets the modified timestamp. + /// </summary> + [JsonPropertyName("modified")] + public string Modified { get; set; } + + /// <summary> + /// Gets or sets the md5. + /// </summary> + [JsonPropertyName("md5")] + public string Md5 { get; set; } + + /// <summary> + /// Gets or sets the start date. + /// </summary> + [JsonPropertyName("startDate")] + public string StartDate { get; set; } + + /// <summary> + /// Gets or sets the end date. + /// </summary> + [JsonPropertyName("endDate")] + public string EndDate { get; set; } + + /// <summary> + /// Gets or sets the days count. + /// </summary> + [JsonPropertyName("days")] + public int Days { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs new file mode 100644 index 0000000000..48d731d890 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs @@ -0,0 +1,31 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Movie dto. + /// </summary> + public class MovieDto + { + /// <summary> + /// Gets or sets the year. + /// </summary> + [JsonPropertyName("year")] + public string Year { get; set; } + + /// <summary> + /// Gets or sets the duration. + /// </summary> + [JsonPropertyName("duration")] + public int Duration { get; set; } + + /// <summary> + /// Gets or sets the list of quality rating. + /// </summary> + [JsonPropertyName("qualityRating")] + public List<QualityRatingDto> QualityRating { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs new file mode 100644 index 0000000000..42eddfff26 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs @@ -0,0 +1,24 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Multipart dto. + /// </summary> + public class MultipartDto + { + /// <summary> + /// Gets or sets the part number. + /// </summary> + [JsonPropertyName("partNumber")] + public int PartNumber { get; set; } + + /// <summary> + /// Gets or sets the total parts. + /// </summary> + [JsonPropertyName("totalParts")] + public int TotalParts { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs new file mode 100644 index 0000000000..a84c47c129 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs @@ -0,0 +1,157 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Program details dto. + /// </summary> + public class ProgramDetailsDto + { + /// <summary> + /// Gets or sets the audience. + /// </summary> + [JsonPropertyName("audience")] + public string Audience { get; set; } + + /// <summary> + /// Gets or sets the program id. + /// </summary> + [JsonPropertyName("programID")] + public string ProgramId { get; set; } + + /// <summary> + /// Gets or sets the list of titles. + /// </summary> + [JsonPropertyName("titles")] + public List<TitleDto> Titles { get; set; } + + /// <summary> + /// Gets or sets the event details object. + /// </summary> + [JsonPropertyName("eventDetails")] + public EventDetailsDto EventDetails { get; set; } + + /// <summary> + /// Gets or sets the descriptions. + /// </summary> + [JsonPropertyName("descriptions")] + public DescriptionsProgramDto Descriptions { get; set; } + + /// <summary> + /// Gets or sets the original air date. + /// </summary> + [JsonPropertyName("originalAirDate")] + public string OriginalAirDate { get; set; } + + /// <summary> + /// Gets or sets the list of genres. + /// </summary> + [JsonPropertyName("genres")] + public List<string> Genres { get; set; } + + /// <summary> + /// Gets or sets the episode title. + /// </summary> + [JsonPropertyName("episodeTitle150")] + public string EpisodeTitle150 { get; set; } + + /// <summary> + /// Gets or sets the list of metadata. + /// </summary> + [JsonPropertyName("metadata")] + public List<MetadataProgramsDto> Metadata { get; set; } + + /// <summary> + /// Gets or sets the list of content raitings. + /// </summary> + [JsonPropertyName("contentRating")] + public List<ContentRatingDto> ContentRating { get; set; } + + /// <summary> + /// Gets or sets the list of cast. + /// </summary> + [JsonPropertyName("cast")] + public List<CastDto> Cast { get; set; } + + /// <summary> + /// Gets or sets the list of crew. + /// </summary> + [JsonPropertyName("crew")] + public List<CrewDto> Crew { get; set; } + + /// <summary> + /// Gets or sets the entity type. + /// </summary> + [JsonPropertyName("entityType")] + public string EntityType { get; set; } + + /// <summary> + /// Gets or sets the show type. + /// </summary> + [JsonPropertyName("showType")] + public string ShowType { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether there is image artwork. + /// </summary> + [JsonPropertyName("hasImageArtwork")] + public bool HasImageArtwork { get; set; } + + /// <summary> + /// Gets or sets the primary image. + /// </summary> + [JsonPropertyName("primaryImage")] + public string PrimaryImage { get; set; } + + /// <summary> + /// Gets or sets the thumb image. + /// </summary> + [JsonPropertyName("thumbImage")] + public string ThumbImage { get; set; } + + /// <summary> + /// Gets or sets the backdrop image. + /// </summary> + [JsonPropertyName("backdropImage")] + public string BackdropImage { get; set; } + + /// <summary> + /// Gets or sets the banner image. + /// </summary> + [JsonPropertyName("bannerImage")] + public string BannerImage { get; set; } + + /// <summary> + /// Gets or sets the image id. + /// </summary> + [JsonPropertyName("imageID")] + public string ImageId { get; set; } + + /// <summary> + /// Gets or sets the md5. + /// </summary> + [JsonPropertyName("md5")] + public string Md5 { get; set; } + + /// <summary> + /// Gets or sets the list of content advisory. + /// </summary> + [JsonPropertyName("contentAdvisory")] + public List<string> ContentAdvisory { get; set; } + + /// <summary> + /// Gets or sets the movie object. + /// </summary> + [JsonPropertyName("movie")] + public MovieDto Movie { get; set; } + + /// <summary> + /// Gets or sets the list of recommendations. + /// </summary> + [JsonPropertyName("recommendations")] + public List<RecommendationDto> Recommendations { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs new file mode 100644 index 0000000000..ad5389100c --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs @@ -0,0 +1,91 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Program dto. + /// </summary> + public class ProgramDto + { + /// <summary> + /// Gets or sets the program id. + /// </summary> + [JsonPropertyName("programID")] + public string ProgramId { get; set; } + + /// <summary> + /// Gets or sets the air date time. + /// </summary> + [JsonPropertyName("airDateTime")] + public string AirDateTime { get; set; } + + /// <summary> + /// Gets or sets the duration. + /// </summary> + [JsonPropertyName("duration")] + public int Duration { get; set; } + + /// <summary> + /// Gets or sets the md5. + /// </summary> + [JsonPropertyName("md5")] + public string Md5 { get; set; } + + /// <summary> + /// Gets or sets the list of audio properties. + /// </summary> + [JsonPropertyName("audioProperties")] + public List<string> AudioProperties { get; set; } + + /// <summary> + /// Gets or sets the list of video properties. + /// </summary> + [JsonPropertyName("videoProperties")] + public List<string> VideoProperties { get; set; } + + /// <summary> + /// Gets or sets the list of ratings. + /// </summary> + [JsonPropertyName("ratings")] + public List<RatingDto> Ratings { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this program is new. + /// </summary> + [JsonPropertyName("new")] + public bool? New { get; set; } + + /// <summary> + /// Gets or sets the multipart object. + /// </summary> + [JsonPropertyName("multipart")] + public MultipartDto Multipart { get; set; } + + /// <summary> + /// Gets or sets the live tape delay. + /// </summary> + [JsonPropertyName("liveTapeDelay")] + public string LiveTapeDelay { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this is the premiere. + /// </summary> + [JsonPropertyName("premiere")] + public bool Premiere { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this is a repeat. + /// </summary> + [JsonPropertyName("repeat")] + public bool Repeat { get; set; } + + /// <summary> + /// Gets or sets the premiere or finale. + /// </summary> + [JsonPropertyName("isPremiereOrFinale")] + public string IsPremiereOrFinale { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs new file mode 100644 index 0000000000..5cd0a74595 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs @@ -0,0 +1,42 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Quality rating dto. + /// </summary> + public class QualityRatingDto + { + /// <summary> + /// Gets or sets the ratings body. + /// </summary> + [JsonPropertyName("ratingsBody")] + public string RatingsBody { get; set; } + + /// <summary> + /// Gets or sets the rating. + /// </summary> + [JsonPropertyName("rating")] + public string Rating { get; set; } + + /// <summary> + /// Gets or sets the min rating. + /// </summary> + [JsonPropertyName("minRating")] + public string MinRating { get; set; } + + /// <summary> + /// Gets or sets the max rating. + /// </summary> + [JsonPropertyName("maxRating")] + public string MaxRating { get; set; } + + /// <summary> + /// Gets or sets the increment. + /// </summary> + [JsonPropertyName("increment")] + public string Increment { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs new file mode 100644 index 0000000000..948b831446 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs @@ -0,0 +1,24 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Rating dto. + /// </summary> + public class RatingDto + { + /// <summary> + /// Gets or sets the body. + /// </summary> + [JsonPropertyName("body")] + public string Body { get; set; } + + /// <summary> + /// Gets or sets the code. + /// </summary> + [JsonPropertyName("code")] + public string Code { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs new file mode 100644 index 0000000000..1308f45ce7 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs @@ -0,0 +1,24 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Recommendation dto. + /// </summary> + public class RecommendationDto + { + /// <summary> + /// Gets or sets the program id. + /// </summary> + [JsonPropertyName("programID")] + public string ProgramId { get; set; } + + /// <summary> + /// Gets or sets the title. + /// </summary> + [JsonPropertyName("title120")] + public string Title120 { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs new file mode 100644 index 0000000000..fb7a31ac85 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs @@ -0,0 +1,25 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Request schedule for channel dto. + /// </summary> + public class RequestScheduleForChannelDto + { + /// <summary> + /// Gets or sets the station id. + /// </summary> + [JsonPropertyName("stationID")] + public string StationId { get; set; } + + /// <summary> + /// Gets or sets the list of dates. + /// </summary> + [JsonPropertyName("date")] + public List<string> Date { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs new file mode 100644 index 0000000000..332edf7c38 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs @@ -0,0 +1,22 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + public class ShowImagesDto + { + /// <summary> + /// Gets or sets the program id. + /// </summary> + [JsonPropertyName("programID")] + public string ProgramId { get; set; } + + /// <summary> + /// Gets or sets the list of data. + /// </summary> + [JsonPropertyName("data")] + public List<ImageDataDto> Data { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs new file mode 100644 index 0000000000..12f3576c65 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs @@ -0,0 +1,67 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Station dto. + /// </summary> + public class StationDto + { + /// <summary> + /// Gets or sets the station id. + /// </summary> + [JsonPropertyName("stationID")] + public string StationId { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + [JsonPropertyName("name")] + public string Name { get; set; } + + /// <summary> + /// Gets or sets the callsign. + /// </summary> + [JsonPropertyName("callsign")] + public string Callsign { get; set; } + + /// <summary> + /// Gets or sets the broadcast language. + /// </summary> + [JsonPropertyName("broadcastLanguage")] + public List<string> BroadcastLanguage { get; set; } + + /// <summary> + /// Gets or sets the description language. + /// </summary> + [JsonPropertyName("descriptionLanguage")] + public List<string> DescriptionLanguage { get; set; } + + /// <summary> + /// Gets or sets the broadcaster. + /// </summary> + [JsonPropertyName("broadcaster")] + public BroadcasterDto Broadcaster { get; set; } + + /// <summary> + /// Gets or sets the affiliate. + /// </summary> + [JsonPropertyName("affiliate")] + public string Affiliate { get; set; } + + /// <summary> + /// Gets or sets the logo. + /// </summary> + [JsonPropertyName("logo")] + public LogoDto Logo { get; set; } + + /// <summary> + /// Gets or set a value indicating whether it is commercial free. + /// </summary> + [JsonPropertyName("isCommercialFree")] + public bool? IsCommercialFree { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs new file mode 100644 index 0000000000..06c95524b0 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs @@ -0,0 +1,18 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Title dto. + /// </summary> + public class TitleDto + { + /// <summary> + /// Gets or sets the title. + /// </summary> + [JsonPropertyName("title120")] + public string Title120 { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs new file mode 100644 index 0000000000..c3ec1c7d61 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs @@ -0,0 +1,36 @@ +#nullable disable + +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// The token dto. + /// </summary> + public class TokenDto + { + /// <summary> + /// Gets or sets the response code. + /// </summary> + [JsonPropertyName("code")] + public int Code { get; set; } + + /// <summary> + /// Gets or sets the response message. + /// </summary> + [JsonPropertyName("message")] + public string Message { get; set; } + + /// <summary> + /// Gets or sets the server id. + /// </summary> + [JsonPropertyName("serverID")] + public string ServerId { get; set; } + + /// <summary> + /// Gets or sets the token. + /// </summary> + [JsonPropertyName("token")] + public string Token { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index d964769b59..ce585d0fb5 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -403,7 +403,7 @@ namespace Emby.Server.Implementations.LiveTv // Set the total bitrate if not already supplied mediaSource.InferTotalBitrate(); - if (!(service is EmbyTV.EmbyTV)) + if (service is not EmbyTV.EmbyTV) { // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says // mediaSource.SupportsDirectPlay = false; @@ -1724,7 +1724,7 @@ namespace Emby.Server.Implementations.LiveTv await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false); - if (!(service is EmbyTV.EmbyTV)) + if (service is not EmbyTV.EmbyTV) { TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id))); } @@ -2050,7 +2050,7 @@ namespace Emby.Server.Implementations.LiveTv _logger.LogInformation("New recording scheduled"); - if (!(service is EmbyTV.EmbyTV)) + if (service is not EmbyTV.EmbyTV) { TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>( new TimerEventInfo(newTimerId) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 3016eeda24..b2e555c7dc 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -27,6 +27,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { private string _channel; private string _program; + public LegacyHdHomerunChannelCommands(string url) { // parse url for channel and program diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 8cafde38ee..b07798fa40 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -260,7 +260,7 @@ namespace Emby.Server.Implementations.Playlists public async Task RemoveFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds) { - if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist)) + if (_libraryManager.GetItemById(playlistId) is not Playlist playlist) { throw new ArgumentException("No Playlist exists with the supplied Id"); } @@ -293,7 +293,7 @@ namespace Emby.Server.Implementations.Playlists public async Task MoveItemAsync(string playlistId, string entryId, int newIndex) { - if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist)) + if (_libraryManager.GetItemById(playlistId) is not Playlist playlist) { throw new ArgumentException("No Playlist exists with the supplied Id"); } diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index b343250419..fb93c375d8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -24,7 +24,6 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> public class ScheduledTaskWorker : IScheduledTaskWorker { - /// <summary> /// Gets or sets the application paths. /// </summary> @@ -267,7 +266,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Gets the triggers that define when the task will run. + /// Gets or sets the triggers that define when the task will run. /// </summary> /// <value>The triggers.</value> /// <exception cref="ArgumentNullException"><c>value</c> is <c>null</c>.</exception> diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs index 98bee3fd9f..7b7ba57531 100644 --- a/Emby.Server.Implementations/Sorting/ArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Sorting /// <returns>System.String.</returns> private static string? GetValue(BaseItem? x) { - if (!(x is Audio audio)) + if (x is not Audio audio) { return string.Empty; } diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index a9f4a5a58c..64d7b2f3e0 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -154,11 +154,11 @@ namespace Jellyfin.Api.Controllers }; if (!item.IsVirtualItem - && !(item is ICollectionFolder) - && !(item is UserView) - && !(item is AggregateFolder) - && !(item is LiveTvChannel) - && !(item is IItemByName) + && item is not ICollectionFolder + && item is not UserView + && item is not AggregateFolder + && item is not LiveTvChannel + && item is not IItemByName && item.SourceType == SourceType.Library) { var inheritedContentType = _libraryManager.GetInheritedContentType(item); diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 35c27dd0e9..52eefc5c23 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -241,7 +241,7 @@ namespace Jellyfin.Api.Controllers var item = _libraryManager.GetParentItem(parentId, userId); QueryResult<BaseItem> result; - if (!(item is Folder folder)) + if (item is not Folder folder) { folder = _libraryManager.GetUserRootFolder(); } @@ -285,7 +285,7 @@ namespace Jellyfin.Api.Controllers return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}."); } - if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || !(item is UserRootFolder)) + if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || item is not UserRootFolder) { var query = new InternalItemsQuery(user!) { diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 4ed15e1d51..8cc369a5cd 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -700,7 +700,7 @@ namespace Jellyfin.Api.Controllers : _libraryManager.RootFolder) : _libraryManager.GetItemById(itemId); - if (item is Episode || (item is IItemByName && !(item is MusicArtist))) + if (item is Episode || (item is IItemByName && item is not MusicArtist)) { return new QueryResult<BaseItemDto>(); } diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 51d40994e1..7c5b8a43b7 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -228,7 +228,7 @@ namespace Jellyfin.Api.Controllers if (seasonId.HasValue) // Season id was supplied. Get episodes by season id. { var item = _libraryManager.GetItemById(seasonId.Value); - if (!(item is Season seasonItem)) + if (item is not Season seasonItem) { return NotFound("No season exists with Id " + seasonId); } @@ -237,7 +237,7 @@ namespace Jellyfin.Api.Controllers } else if (season.HasValue) // Season number was supplied. Get episodes by season number { - if (!(_libraryManager.GetItemById(seriesId) is Series series)) + if (_libraryManager.GetItemById(seriesId) is not Series series) { return NotFound("Series not found"); } @@ -252,7 +252,7 @@ namespace Jellyfin.Api.Controllers } else // No season number or season id was supplied. Returning all episodes. { - if (!(_libraryManager.GetItemById(seriesId) is Series series)) + if (_libraryManager.GetItemById(seriesId) is not Series series) { return NotFound("Series not found"); } @@ -336,7 +336,7 @@ namespace Jellyfin.Api.Controllers ? _userManager.GetUserById(userId.Value) : null; - if (!(_libraryManager.GetItemById(seriesId) is Series series)) + if (_libraryManager.GetItemById(seriesId) is not Series series) { return NotFound("Series not found"); } diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 8cffe9c4c9..5b14b6468b 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -222,11 +222,7 @@ namespace Jellyfin.Api.Helpers { var resolution = ResolutionNormalizer.Normalize( state.VideoStream?.BitRate, - state.VideoStream?.Width, - state.VideoStream?.Height, state.OutputVideoBitrate.Value, - state.VideoStream?.Codec, - state.OutputVideoCodec, state.VideoRequest.MaxWidth, state.VideoRequest.MaxHeight); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 067fecd878..3b182f7c98 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -112,7 +112,7 @@ namespace MediaBrowser.Controller.Entities private string _name; - public static char SlugChar = '-'; + public const char SlugChar = '-'; protected BaseItem() { @@ -2050,7 +2050,7 @@ namespace MediaBrowser.Controller.Entities public virtual string GetClientTypeName() { - if (IsFolder && SourceType == SourceType.Channel && !(this is Channel)) + if (IsFolder && SourceType == SourceType.Channel && this is not Channel) { return "ChannelFolderItem"; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index d45a02cf2d..dd08c31eda 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Entities public override bool IsVisible(User user) { - if (this is ICollectionFolder && !(this is BasePluginFolder)) + if (this is ICollectionFolder && this is not BasePluginFolder) { var blockedMediaFolders = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedMediaFolders); if (blockedMediaFolders.Length > 0) @@ -673,7 +673,7 @@ namespace MediaBrowser.Controller.Entities { if (LinkedChildren.Length > 0) { - if (!(this is ICollectionFolder)) + if (this is not ICollectionFolder) { return GetChildren(user, true).Count; } @@ -730,7 +730,7 @@ namespace MediaBrowser.Controller.Entities return PostFilterAndSort(items, query, true); } - if (!(this is UserRootFolder) && !(this is AggregateFolder) && query.ParentId == Guid.Empty) + if (this is not UserRootFolder && this is not AggregateFolder && query.ParentId == Guid.Empty) { query.Parent = this; } @@ -805,7 +805,7 @@ namespace MediaBrowser.Controller.Entities { if (LinkedChildren.Length > 0) { - if (!(this is ICollectionFolder)) + if (this is not ICollectionFolder) { Logger.LogDebug("Query requires post-filtering due to LinkedChildren. Type: " + GetType().Name); return true; @@ -1545,7 +1545,7 @@ namespace MediaBrowser.Controller.Entities var childOwner = child.GetOwner() ?? child; - if (childOwner != null && !(child is IItemByName)) + if (child is not IItemByName) { var childProtocol = childOwner.PathProtocol; if (!childProtocol.HasValue || childProtocol.Value != Model.MediaInfo.MediaProtocol.File) diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index beda504b91..e4933e9682 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -296,7 +296,7 @@ namespace MediaBrowser.Controller.Entities.TV // Refresh seasons foreach (var item in items) { - if (!(item is Season)) + if (item is not Season) { continue; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 141bb91c57..3e577ebb76 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -504,7 +504,9 @@ namespace MediaBrowser.Controller.MediaEncoding var arg = new StringBuilder(); var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; +#pragma warning disable CA1508 // Defaults to string.Empty var isSwDecoder = string.IsNullOrEmpty(videoDecoder); +#pragma warning restore CA1508 var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; @@ -1759,7 +1761,7 @@ namespace MediaBrowser.Controller.MediaEncoding var request = state.BaseRequest; - var inputChannels = audioStream?.Channels; + var inputChannels = audioStream.Channels; if (inputChannels <= 0) { @@ -2027,8 +2029,8 @@ namespace MediaBrowser.Controller.MediaEncoding { // Adjust the size of graphical subtitles to fit the video stream. var videoStream = state.VideoStream; - var inputWidth = videoStream?.Width; - var inputHeight = videoStream?.Height; + var inputWidth = videoStream.Width; + var inputHeight = videoStream.Height; var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); if (width.HasValue && height.HasValue) @@ -3101,7 +3103,7 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += " " + videoDecoder; if (!IsCopyCodec(state.OutputVideoCodec) - && (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1) + && videoDecoder.IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1) { var videoStream = state.VideoStream; var inputWidth = videoStream?.Width; @@ -3110,7 +3112,7 @@ namespace MediaBrowser.Controller.MediaEncoding var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 + if (videoDecoder.IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 && width.HasValue && height.HasValue) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index fa9f40d60d..b09b7dba6c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -422,7 +422,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) { - return VideoStream?.Codec; + return VideoStream.Codec; } return OutputVideoCodec; @@ -440,7 +440,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (EncodingHelper.IsCopyCodec(OutputAudioCodec)) { - return AudioStream?.Codec; + return AudioStream.Codec; } return OutputAudioCodec; @@ -568,7 +568,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced; + return forceDeinterlaceIfSourceIsInterlaced; } public string[] GetRequestedProfiles(string codec) diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 31475e22f4..b7398880ef 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -283,7 +283,7 @@ namespace MediaBrowser.LocalMetadata.Images { imageFileNames = _seriesImageFileNames; } - else if (item is Video && !(item is Episode)) + else if (item is Video && item is not Episode) { imageFileNames = _videoImageFileNames; } diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index dd824206f7..6a3896eb6f 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -223,7 +223,7 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteElementString("CustomRating", item.CustomRating); } - if (!string.IsNullOrEmpty(item.Name) && !(item is Episode)) + if (!string.IsNullOrEmpty(item.Name) && item is not Episode) { writer.WriteElementString("LocalTitle", item.Name); } @@ -240,7 +240,7 @@ namespace MediaBrowser.LocalMetadata.Savers { writer.WriteElementString("BirthDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } - else if (!(item is Episode)) + else if (item is not Episode) { writer.WriteElementString("PremiereDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } @@ -252,7 +252,7 @@ namespace MediaBrowser.LocalMetadata.Savers { writer.WriteElementString("DeathDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } - else if (!(item is Episode)) + else if (item is not Episode) { writer.WriteElementString("EndDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } @@ -292,7 +292,7 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(_usCulture)); } - if (item.ProductionYear.HasValue && !(item is Person)) + if (item.ProductionYear.HasValue && item is not Person) { writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(_usCulture)); } diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfile.cs b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs index 20e05b8a94..06f6660f4b 100644 --- a/MediaBrowser.Model/Dlna/MediaFormatProfile.cs +++ b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591, CA1707 namespace MediaBrowser.Model.Dlna { diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index 65fccbdd41..806877ff0b 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -21,11 +21,7 @@ namespace MediaBrowser.Model.Dlna public static ResolutionOptions Normalize( int? inputBitrate, - int? unused1, - int? unused2, int outputBitrate, - string inputCodec, - string outputCodec, int? maxWidth, int? maxHeight) { diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index f4c69fe8f5..635420a76a 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -694,7 +694,7 @@ namespace MediaBrowser.Model.Dlna if (isEligibleForDirectPlay || isEligibleForDirectStream) { // See if it can be direct played - var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectPlay, isEligibleForDirectStream); + var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectStream); var directPlay = directPlayInfo.Item1; if (directPlay != null) @@ -810,7 +810,7 @@ namespace MediaBrowser.Model.Dlna // Honor requested max channels playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; - int audioBitrate = GetAudioBitrate(playlistItem.SubProtocol, options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem); + int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem); playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate); isFirstAppliedCodecProfile = true; @@ -907,7 +907,7 @@ namespace MediaBrowser.Model.Dlna return 192000; } - private static int GetAudioBitrate(string subProtocol, long maxTotalBitrate, string[] targetAudioCodecs, MediaStream audioStream, StreamInfo item) + private static int GetAudioBitrate(long maxTotalBitrate, string[] targetAudioCodecs, MediaStream audioStream, StreamInfo item) { string targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0]; @@ -1005,7 +1005,6 @@ namespace MediaBrowser.Model.Dlna MediaSourceInfo mediaSource, MediaStream videoStream, MediaStream audioStream, - bool isEligibleForDirectPlay, bool isEligibleForDirectStream) { if (options.ForceDirectPlay) @@ -1146,7 +1145,7 @@ namespace MediaBrowser.Model.Dlna { string audioCodec = audioStream.Codec; conditions = new List<ProfileCondition>(); - bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream); + bool? isSecondaryAudio = mediaSource.IsSecondaryAudio(audioStream); foreach (var i in profile.CodecProfiles) { @@ -1262,7 +1261,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(subtitleStream, profile, transcodingSubProtocol, outputContainer)) + if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(outputContainer)) { continue; } @@ -1291,7 +1290,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(subtitleStream, profile, transcodingSubProtocol, outputContainer)) + if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(outputContainer)) { continue; } @@ -1313,7 +1312,7 @@ namespace MediaBrowser.Model.Dlna }; } - private static bool IsSubtitleEmbedSupported(MediaStream subtitleStream, SubtitleProfile subtitleProfile, string transcodingSubProtocol, string transcodingContainer) + private static bool IsSubtitleEmbedSupported(string transcodingContainer) { if (!string.IsNullOrEmpty(transcodingContainer)) { @@ -1728,18 +1727,14 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!string.IsNullOrEmpty(value)) - { - // change from split by | to comma - - // strip spaces to avoid having to encode - var values = value - .Split('|', StringSplitOptions.RemoveEmptyEntries); + // change from split by | to comma + // strip spaces to avoid having to encode + var values = value + .Split('|', StringSplitOptions.RemoveEmptyEntries); - if (condition.Condition == ProfileConditionType.Equals || condition.Condition == ProfileConditionType.EqualsAny) - { - item.SetOption(qualifier, "profile", string.Join(',', values)); - } + if (condition.Condition == ProfileConditionType.Equals || condition.Condition == ProfileConditionType.EqualsAny) + { + item.SetOption(qualifier, "profile", string.Join(',', values)); } break; diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 252872847a..432c1c1d60 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -636,7 +636,7 @@ namespace MediaBrowser.Model.Dlna continue; } - var encodedValue = pair.Value.Replace(" ", "%20"); + var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal); list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue)); } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 9653a8ece7..de9fe6ff63 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -261,7 +261,7 @@ namespace MediaBrowser.Model.Entities foreach (var tag in attributes) { // Keep Tags that are not already in Title. - if (Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) + if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) { result.Append(" - ").Append(tag); } diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index be4f1e16b5..0f77d6b5bf 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -51,7 +51,7 @@ namespace MediaBrowser.Model.IO /// <returns>A <see cref="FileSystemMetadata" /> object.</returns> /// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata" /> object's /// <see cref="FileSystemMetadata.IsDirectory" /> property and the <see cref="FileSystemMetadata.Exists" /> property will both be set to false.</para> - /// <para>For automatic handling of files <b>and</b> directories, use <see cref="M:IFileSystem.GetFileSystemInfo(System.String)" />.</para></remarks> + /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo(string)" />.</para></remarks> FileSystemMetadata GetFileInfo(string path); /// <summary> @@ -61,7 +61,7 @@ namespace MediaBrowser.Model.IO /// <returns>A <see cref="FileSystemMetadata" /> object.</returns> /// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata" /> object's /// <see cref="FileSystemMetadata.IsDirectory" /> property will be set to true and the <see cref="FileSystemMetadata.Exists" /> property will be set to false.</para> - /// <para>For automatic handling of files <b>and</b> directories, use <see cref="M:IFileSystem.GetFileSystemInfo(System.String)" />.</para></remarks> + /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo(string)" />.</para></remarks> FileSystemMetadata GetDirectoryInfo(string path); /// <summary> diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index fb1d4f4906..7d259a9d36 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -91,7 +91,7 @@ namespace MediaBrowser.Providers.Manager throw new ArgumentNullException(nameof(mimeType)); } - var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.ExtraType.HasValue && !(item is Audio); + var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.ExtraType.HasValue && item is not Audio; if (type != ImageType.Primary && item is Episode) { diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 607fd127b2..cc4c75d7dd 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Providers.Manager { var hasChanges = false; - if (!(item is Photo)) + if (item is not Photo) { var images = providers.OfType<ILocalImageProvider>() .SelectMany(i => i.GetImages(item, directoryService)) @@ -529,7 +529,7 @@ namespace MediaBrowser.Providers.Manager return true; } - if (item is IItemByName && !(item is MusicArtist)) + if (item is IItemByName && item is not MusicArtist) { var hasDualAccess = item as IHasDualAccess; if (hasDualAccess == null || hasDualAccess.IsAccessedByName) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 3a42eb4c19..ab8d3a2a61 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -584,7 +584,7 @@ namespace MediaBrowser.Providers.Manager protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(BaseItem item, IEnumerable<IImageProvider> allImageProviders, ImageRefreshOptions options) { // Get providers to refresh - var providers = allImageProviders.Where(i => !(i is ILocalImageProvider)); + var providers = allImageProviders.Where(i => i is not ILocalImageProvider); var dateLastImageRefresh = item.DateLastRefreshed; @@ -729,7 +729,7 @@ namespace MediaBrowser.Providers.Manager refreshResult.Failures += remoteResult.Failures; } - if (providers.Any(i => !(i is ICustomMetadataProvider))) + if (providers.Any(i => i is not ICustomMetadataProvider)) { if (refreshResult.UpdateType > ItemUpdateType.None) { @@ -748,7 +748,7 @@ namespace MediaBrowser.Providers.Manager // var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0; - foreach (var provider in customProviders.Where(i => !(i is IPreRefreshProvider))) + foreach (var provider in customProviders.Where(i => i is not IPreRefreshProvider)) { await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 2dfaa372c5..7e60eced0f 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -323,7 +323,7 @@ namespace MediaBrowser.Providers.Manager .OrderBy(i => { // See if there's a user-defined order - if (!(i is ILocalImageProvider)) + if (i is not ILocalImageProvider) { var fetcherOrder = typeFetcherOrder ?? currentOptions.ImageFetcherOrder; var index = Array.IndexOf(fetcherOrder, i.Name); @@ -390,7 +390,7 @@ namespace MediaBrowser.Providers.Manager if (!includeDisabled) { // If locked only allow local providers - if (item.IsLocked && !(provider is ILocalMetadataProvider) && !(provider is IForcedProvider)) + if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider) { return false; } @@ -431,7 +431,7 @@ namespace MediaBrowser.Providers.Manager if (!includeDisabled) { // If locked only allow local providers - if (item.IsLocked && !(provider is ILocalImageProvider)) + if (item.IsLocked && provider is not ILocalImageProvider) { if (refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh) { @@ -466,7 +466,7 @@ namespace MediaBrowser.Providers.Manager /// <returns>System.Int32.</returns> private int GetOrder(IImageProvider provider) { - if (!(provider is IHasOrder hasOrder)) + if (provider is not IHasOrder hasOrder) { return 0; } @@ -745,7 +745,7 @@ namespace MediaBrowser.Providers.Manager { // Manual edit occurred // Even if save local is off, save locally anyway if the metadata file already exists - if (!(saver is IMetadataFileSaver fileSaver) || !File.Exists(fileSaver.GetSavePath(item))) + if (saver is not IMetadataFileSaver fileSaver || !File.Exists(fileSaver.GetSavePath(item))) { return false; } diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index aceba22158..6d088e6e70 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -135,7 +135,7 @@ namespace MediaBrowser.Providers.Manager { if (replaceData || !target.RunTimeTicks.HasValue) { - if (!(target is Audio) && !(target is Video)) + if (target is not Audio && target is not Video) { target.RunTimeTicks = source.RunTimeTicks; } diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 3be35e2d9b..38726a6f08 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -555,7 +555,7 @@ namespace MediaBrowser.XbmcMetadata.Savers } // Series xml saver already saves this - if (!(item is Series)) + if (item is not Series) { var tvdb = item.GetProviderId(MetadataProvider.Tvdb); if (!string.IsNullOrEmpty(tvdb)) @@ -582,7 +582,7 @@ namespace MediaBrowser.XbmcMetadata.Savers writer.WriteElementString("countrycode", item.PreferredMetadataCountryCode); } - if (item.PremiereDate.HasValue && !(item is Episode)) + if (item.PremiereDate.HasValue && item is not Episode) { var formatString = options.ReleaseDateFormat; @@ -605,7 +605,7 @@ namespace MediaBrowser.XbmcMetadata.Savers if (item.EndDate.HasValue) { - if (!(item is Episode)) + if (item is not Episode) { var formatString = options.ReleaseDateFormat; diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index 412e8031b2..21e7e2335b 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs @@ -82,7 +82,7 @@ namespace MediaBrowser.XbmcMetadata.Savers } // Check parent for null to avoid running this against things like video backdrops - if (item is Video video && !(item is Episode) && !video.ExtraType.HasValue) + if (item is Video video && item is not Episode && !video.ExtraType.HasValue) { return updateType >= MinimumUpdateType; } diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs index b9d73ba822..e975506308 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.XbmcMetadata.Savers return false; } - if (!(item is Season)) + if (item is not Season) { return false; } From d7459fa2ec41ee80271dde415997c23c2eb97c65 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Sat, 28 Aug 2021 23:02:13 -0600 Subject: [PATCH 100/118] Add missing xmldoc --- .../LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs | 3 +++ .../LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs index b80ac624cc..912e680dd7 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs @@ -4,6 +4,9 @@ using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos { + /// <summary> + /// Image data dto. + /// </summary> public class ImageDataDto { /// <summary> diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs index 332edf7c38..34302370d6 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs @@ -5,6 +5,9 @@ using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos { + /// <summary> + /// Show image dto. + /// </summary> public class ShowImagesDto { /// <summary> From e40fde04d64189e7ed8cd43134ee536774b127f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Aug 2021 12:00:52 +0000 Subject: [PATCH 101/118] Bump FsCheck.Xunit from 2.16.0 to 2.16.1 Bumps [FsCheck.Xunit](https://github.com/fsharp/FsCheck) from 2.16.0 to 2.16.1. - [Release notes](https://github.com/fsharp/FsCheck/releases) - [Changelog](https://github.com/fscheck/FsCheck/blob/master/FsCheck%20Release%20Notes.md) - [Commits](https://github.com/fsharp/FsCheck/compare/2.16.0...2.16.1) --- updated-dependencies: - dependency-name: FsCheck.Xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj | 2 +- .../Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj | 2 +- tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj | 2 +- .../Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index bab5f9e369..8e6b07716c 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -16,7 +16,7 @@ <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.0" /> - <PackageReference Include="FsCheck.Xunit" Version="2.16.0" /> + <PackageReference Include="FsCheck.Xunit" Version="2.16.1" /> </ItemGroup> <!-- Code Analyzers --> diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index 10ec31b838..72cd9aa450 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -17,7 +17,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> - <PackageReference Include="FsCheck.Xunit" Version="2.16.0" /> + <PackageReference Include="FsCheck.Xunit" Version="2.16.1" /> </ItemGroup> <!-- Code Analyzers --> diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index 5371853bc0..e9b7b18509 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -11,7 +11,7 @@ <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Include="coverlet.collector" Version="3.1.0" /> - <PackageReference Include="FsCheck.Xunit" Version="2.16.0" /> + <PackageReference Include="FsCheck.Xunit" Version="2.16.1" /> </ItemGroup> <!-- Code Analyzers --> diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index b2a6fdcf27..dd593c9e74 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -16,7 +16,7 @@ <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Include="coverlet.collector" Version="3.1.0" /> - <PackageReference Include="FsCheck.Xunit" Version="2.16.0" /> + <PackageReference Include="FsCheck.Xunit" Version="2.16.1" /> <PackageReference Include="Moq" Version="4.16.1" /> </ItemGroup> From 4c5f7207e32676b242951cadb233e80e1856df9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Aug 2021 12:00:56 +0000 Subject: [PATCH 102/118] Bump prometheus-net.DotNetRuntime from 4.1.0 to 4.2.0 Bumps [prometheus-net.DotNetRuntime](https://github.com/djluck/prometheus-net.DotNetRuntime) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/djluck/prometheus-net.DotNetRuntime/releases) - [Commits](https://github.com/djluck/prometheus-net.DotNetRuntime/compare/4.1.0...4.2.0) --- updated-dependencies: - dependency-name: prometheus-net.DotNetRuntime dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index e0f841d529..927aa0c20c 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -30,7 +30,7 @@ <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" /> <PackageReference Include="Mono.Nat" Version="3.0.1" /> - <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" /> + <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.0" /> <PackageReference Include="sharpcompress" Version="0.28.3" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" /> <PackageReference Include="DotNet.Glob" Version="3.1.2" /> From e2fdab4be45d214e2b65c63671902a00e05741ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Aug 2021 12:01:04 +0000 Subject: [PATCH 103/118] Bump SQLitePCLRaw.bundle_e_sqlite3 from 2.0.4 to 2.0.5 Bumps [SQLitePCLRaw.bundle_e_sqlite3](https://github.com/ericsink/SQLitePCL.raw) from 2.0.4 to 2.0.5. - [Release notes](https://github.com/ericsink/SQLitePCL.raw/releases) - [Commits](https://github.com/ericsink/SQLitePCL.raw/commits) --- updated-dependencies: - dependency-name: SQLitePCLRaw.bundle_e_sqlite3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index c4d3d8a1f3..a57666cd62 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -44,7 +44,7 @@ <PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" /> - <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" /> + <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.5" /> </ItemGroup> <ItemGroup> From ecb4b8e0aacef56331e7eadd1f82839b0f6e1e00 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Mon, 30 Aug 2021 07:11:34 -0600 Subject: [PATCH 104/118] Apply suggestions from code review Co-authored-by: Bond-009 <bond.009@outlook.com> --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 1 - .../Library/Validators/StudiosValidator.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index ab1b55164b..108ea783d4 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -72,7 +72,6 @@ namespace Emby.Server.Implementations.Data _mediaAttachmentInsertPrefix = queryPrefixText.ToString(); } - /// <summary> /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class. /// </summary> diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 16bdf720ce..7a22f851b4 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -87,7 +87,7 @@ namespace Emby.Server.Implementations.Library.Validators foreach (var item in deadEntities) { - _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); + _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); _libraryManager.DeleteItem( item, From 963ab2dab6a8c4dafadae61312b36ed5fbb1f323 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Sun, 4 Apr 2021 23:02:28 +0200 Subject: [PATCH 105/118] Simplify the way we choose our ffmpeg * no longer search $PATH * no longer require a full path * don't fall back --- .../ApplicationHost.cs | 1 - .../MediaEncoding/IMediaEncoder.cs | 6 - .../Encoder/EncoderValidator.cs | 4 +- .../Encoder/MediaEncoder.cs | 107 ++++++------------ MediaBrowser.Model/System/SystemInfo.cs | 1 + .../EncoderValidatorTests.cs | 8 +- 6 files changed, 38 insertions(+), 89 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index bf7ddace2d..0b5322f394 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1099,7 +1099,6 @@ namespace Emby.Server.Implementations ServerName = FriendlyName, LocalAddress = GetSmartApiUrl(source), SupportsLibraryMonitor = true, - EncoderLocation = _mediaEncoder.EncoderLocation, SystemArchitecture = RuntimeInformation.OSArchitecture, PackageName = _startupOptions.PackageName }; diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index ff24560703..63308fa098 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -10,7 +10,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.System; namespace MediaBrowser.Controller.MediaEncoding { @@ -19,11 +18,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// </summary> public interface IMediaEncoder : ITranscoderSupport { - /// <summary> - /// Gets location of the discovered FFmpeg tool. - /// </summary> - FFmpegLocation EncoderLocation { get; } - /// <summary> /// Gets the encoder path. /// </summary> diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index f782e65bd1..ef831ab828 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -12,8 +12,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { public class EncoderValidator { - private const string DefaultEncoderPath = "ffmpeg"; - private static readonly string[] _requiredDecoders = new[] { "h264", @@ -106,7 +104,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly string _encoderPath; - public EncoderValidator(ILogger logger, string encoderPath = DefaultEncoderPath) + public EncoderValidator(ILogger logger, string encoderPath) { _logger = logger; _encoderPath = encoderPath; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 412a953216..f8ba78e463 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -23,7 +23,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -69,7 +68,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private string _ffmpegPath = string.Empty; private string _ffprobePath; - private int threads; + private int _threads; public MediaEncoder( ILogger<MediaEncoder> logger, @@ -89,9 +88,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public string EncoderPath => _ffmpegPath; - /// <inheritdoc /> - public FFmpegLocation EncoderLocation { get; private set; } - /// <summary> /// Run at startup or if the user removes a Custom path from transcode page. /// Sets global variables FFmpegPath. @@ -100,20 +96,23 @@ namespace MediaBrowser.MediaEncoding.Encoder public void SetFFmpegPath() { // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence - if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom)) + var ffmpegPath = _configurationManager.GetEncodingOptions().EncoderAppPath; + if (string.IsNullOrEmpty(ffmpegPath)) { // 2) Check if the --ffmpeg CLI switch has been given - if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument)) + ffmpegPath = _startupOptionFFmpegPath; + if (string.IsNullOrEmpty(ffmpegPath)) { - // 3) Search system $PATH environment variable for valid FFmpeg - if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) - { - EncoderLocation = FFmpegLocation.NotFound; - _ffmpegPath = null; - } + // 3) Check "ffmpeg" + ffmpegPath = "ffmpeg"; } } + if (!ValidatePath(ffmpegPath)) + { + _ffmpegPath = null; + } + // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI var config = _configurationManager.GetEncodingOptions(); config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty; @@ -131,10 +130,10 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableDecoders(validator.GetDecoders()); SetAvailableEncoders(validator.GetEncoders()); SetAvailableHwaccels(validator.GetHwaccels()); - threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null); + _threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null); } - _logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty); + _logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty); } /// <summary> @@ -153,15 +152,12 @@ namespace MediaBrowser.MediaEncoding.Encoder { throw new ArgumentException("Unexpected pathType value"); } - else if (string.IsNullOrWhiteSpace(path)) + + if (string.IsNullOrWhiteSpace(path)) { // User had cleared the custom path in UI newPath = string.Empty; } - else if (File.Exists(path)) - { - newPath = path; - } else if (Directory.Exists(path)) { // Given path is directory, so resolve down to filename @@ -169,7 +165,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } else { - throw new ResourceNotFoundException(); + newPath = path; } // Write the new ffmpeg path to the xml as <EncoderAppPath> @@ -184,37 +180,26 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <summary> /// Validates the supplied FQPN to ensure it is a ffmpeg utility. - /// If checks pass, global variable FFmpegPath and EncoderLocation are updated. + /// If checks pass, global variable FFmpegPath is updated. /// </summary> /// <param name="path">FQPN to test.</param> - /// <param name="location">Location (External, Custom, System) of tool.</param> /// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns> - private bool ValidatePath(string path, FFmpegLocation location) + private bool ValidatePath(string path) { - bool rc = false; - - if (!string.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - if (File.Exists(path)) - { - rc = new EncoderValidator(_logger, path).ValidateVersion(); - - if (!rc) - { - _logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path); - } + return false; + } - _ffmpegPath = path; - EncoderLocation = location; - return true; - } - else - { - _logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path); - } + bool rc = new EncoderValidator(_logger, path).ValidateVersion(); + if (!rc) + { + _logger.LogWarning("FFmpeg: Failed version check: {Path}", path); + return false; } - return rc; + _ffmpegPath = path; + return true; } private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false) @@ -235,34 +220,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - /// <summary> - /// Search the system $PATH environment variable looking for given filename. - /// </summary> - /// <param name="fileName">The filename.</param> - /// <returns>The full path to the file.</returns> - private string ExistsOnSystemPath(string fileName) - { - var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true); - if (!string.IsNullOrEmpty(inJellyfinPath)) - { - return inJellyfinPath; - } - - var values = Environment.GetEnvironmentVariable("PATH"); - - foreach (var path in values.Split(Path.PathSeparator)) - { - var candidatePath = GetEncoderPathFromDirectory(path, fileName); - - if (!string.IsNullOrEmpty(candidatePath)) - { - return candidatePath; - } - } - - return null; - } - public void SetAvailableEncoders(IEnumerable<string> list) { _encoders = list.ToList(); @@ -394,7 +351,7 @@ namespace MediaBrowser.MediaEncoding.Encoder var args = extractChapters ? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format" : "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format"; - args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, threads).Trim(); + args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim(); var process = new Process { @@ -615,7 +572,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads); + var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads); if (offset.HasValue) { @@ -728,7 +685,7 @@ namespace MediaBrowser.MediaEncoding.Encoder Directory.CreateDirectory(targetDirectory); var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg"); - var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, threads); + var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads); if (!string.IsNullOrWhiteSpace(container)) { diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index e45b2f33a6..a82c1c8c0c 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -133,6 +133,7 @@ namespace MediaBrowser.Model.System [Obsolete("This should be handled by the package manager")] public bool HasUpdateAvailable { get; set; } + [Obsolete("This isn't set correctly anymore")] public FFmpegLocation EncoderLocation { get; set; } public Architecture SystemArchitecture { get; set; } diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs index 39fd8afda1..cc429b442d 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs @@ -9,12 +9,13 @@ namespace Jellyfin.MediaEncoding.Tests { public class EncoderValidatorTests { + private readonly EncoderValidator _encoderValidator = new EncoderValidator(new NullLogger<EncoderValidatorTests>(), "ffmpeg"); + [Theory] [ClassData(typeof(GetFFmpegVersionTestData))] public void GetFFmpegVersionTest(string versionOutput, Version? version) { - var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>()); - Assert.Equal(version, val.GetFFmpegVersion(versionOutput)); + Assert.Equal(version, _encoderValidator.GetFFmpegVersion(versionOutput)); } [Theory] @@ -28,8 +29,7 @@ namespace Jellyfin.MediaEncoding.Tests [InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, false)] public void ValidateVersionInternalTest(string versionOutput, bool valid) { - var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>()); - Assert.Equal(valid, val.ValidateVersionInternal(versionOutput)); + Assert.Equal(valid, _encoderValidator.ValidateVersionInternal(versionOutput)); } private class GetFFmpegVersionTestData : IEnumerable<object?[]> From a0ee16d38d79d385f967a3164e0f2b095ef5deaa Mon Sep 17 00:00:00 2001 From: Mark Titorenkov <mark.titorenkov@outlook.com> Date: Tue, 31 Aug 2021 15:12:09 +0300 Subject: [PATCH 106/118] Update M3U Channel Name Precedence Sets the ExtInf display name to have a higher precedence than the `tvg-name` attribute for channel names. Usually `namInExtInf` is a more descriptive and human readable name if both it and `tvg-name` are available. `tvg-name` is more likely to be an internal identifier such as just the channel number with a prefix in my provider's case. --- Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 16ff98a7d8..d28c39e213 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -295,11 +295,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } - attributes.TryGetValue("tvg-name", out string name); + string name = nameInExtInf; if (string.IsNullOrWhiteSpace(name)) { - name = nameInExtInf; + attributes.TryGetValue("tvg-name", out name); } if (string.IsNullOrWhiteSpace(name)) From 07f64102ddc5b19a0c2e3e9cae308336dad18c5a Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Wed, 1 Sep 2021 14:00:06 +0200 Subject: [PATCH 107/118] Fix build --- .../Controllers/MediaStructureControllerTests.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs index 8af43050f6..19d8381ea5 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs @@ -87,10 +87,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var data = new UpdateMediaPathRequestDto() { Name = " ", - PathInfo = new MediaPathInfo - { - Path = "test" - } + PathInfo = new MediaPathInfo("test") }; using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions)); From 55c8fb7814c3c364b55b879cd5b080442d6f387d Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Wed, 1 Sep 2021 14:18:23 +0200 Subject: [PATCH 108/118] Ignore Omnisharp crash logs --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 252210e57c..c2ae76c1e3 100644 --- a/.gitignore +++ b/.gitignore @@ -278,3 +278,6 @@ web/ web-src.* MediaBrowser.WebDashboard/jellyfin-web apiclient/generated + +# Omnisharp crash logs +mono_crash.*.json From cf9c678406b3f412d70637da35ccb0e9f70c3a00 Mon Sep 17 00:00:00 2001 From: Mike <Vagab0nd@users.noreply.github.com> Date: Wed, 1 Sep 2021 18:59:59 +0200 Subject: [PATCH 109/118] Add subtitle format(codec) to stream display title (#5853) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Kurek <michal.kurek@mail.com> --- MediaBrowser.Model/Entities/MediaStream.cs | 5 ++ .../Entities/MediaStreamTests.cs | 80 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 9653a8ece7..8955012d7a 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -255,6 +255,11 @@ namespace MediaBrowser.Model.Entities attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced); } + if (!string.IsNullOrEmpty(Codec)) + { + attributes.Add(Codec.ToUpperInvariant()); + } + if (!string.IsNullOrEmpty(Title)) { var result = new StringBuilder(Title); diff --git a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs index e2274e19ee..ce9ecea6a9 100644 --- a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs +++ b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using MediaBrowser.Model.Entities; using Xunit; @@ -5,6 +6,85 @@ namespace Jellyfin.Model.Tests.Entities { public class MediaStreamTests { + public static IEnumerable<object[]> Get_DisplayTitle_TestData() + { + return new List<object[]> + { + new object[] + { + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = "English", + Language = string.Empty, + IsForced = false, + IsDefault = false, + Codec = "ASS" + }, + "English - Und - ASS" + }, + new object[] + { + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = "English", + Language = string.Empty, + IsForced = false, + IsDefault = false, + Codec = string.Empty + }, + "English - Und" + }, + new object[] + { + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = "English", + Language = "EN", + IsForced = false, + IsDefault = false, + Codec = string.Empty + }, + "English" + }, + new object[] + { + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = "English", + Language = "EN", + IsForced = true, + IsDefault = true, + Codec = "SRT" + }, + "English - Default - Forced - SRT" + }, + new object[] + { + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = null, + Language = null, + IsForced = false, + IsDefault = false, + Codec = null + }, + "Und" + } + }; + } + + [Theory] + [MemberData(nameof(Get_DisplayTitle_TestData))] + public void Get_DisplayTitle_should_return_valid_title(MediaStream mediaStream, string expected) + { + Assert.Equal(expected, mediaStream.DisplayTitle); + } + [Theory] [InlineData(null, null, false, null)] [InlineData(null, 0, false, null)] From 2cf08dcd34db4d749a93de29e3ba13c633938e0c Mon Sep 17 00:00:00 2001 From: qsniyg <qsniyg@protonmail.com> Date: Wed, 1 Sep 2021 15:52:59 -0700 Subject: [PATCH 110/118] Allow zero activity log retention days --- .../ScheduledTasks/Tasks/CleanActivityLogTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs index 19600b1e64..79886cb52c 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays; - if (!retentionDays.HasValue || retentionDays <= 0) + if (!retentionDays.HasValue || retentionDays < 0) { throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}"); } From 4bed19e22fb2f548d001303a1f9a44475641aec6 Mon Sep 17 00:00:00 2001 From: Gokdag Goktepe <gokdag.goktepe@gmail.com> Date: Wed, 1 Sep 2021 07:59:23 +0000 Subject: [PATCH 111/118] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/ --- Emby.Server.Implementations/Localization/Core/tr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 771c91d59f..e661299c41 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -25,7 +25,7 @@ "HeaderLiveTV": "Canlı TV", "HeaderNextUp": "Gelecek Hafta", "HeaderRecordingGroups": "Kayıt Grupları", - "HomeVideos": "Ev videoları", + "HomeVideos": "Ana sayfa videoları", "Inherit": "Devral", "ItemAddedWithName": "{0} kütüphaneye eklendi", "ItemRemovedWithName": "{0} kütüphaneden silindi", From 286dabdc4bcff65430f0abe78fbeaaed28635e18 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Thu, 2 Sep 2021 21:28:00 +0200 Subject: [PATCH 112/118] Add SqliteItemRepository.ItemImageInfoFromValueString as a fuzzing target and add test cases --- .../Data/SqliteItemRepository.cs | 12 +++++++- .../Properties/AssemblyInfo.cs | 1 + .../Emby.Server.Implementations.Fuzz.csproj | 7 +++++ .../Program.cs | 30 +++++++++++++++++++ .../test1.txt | 1 + .../Data/SqliteItemRepositoryTests.cs | 3 ++ 6 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 fuzz/Emby.Server.Implementations.Fuzz/Testcases/SqliteItemRepository.ItemImageInfoFromValueString/test1.txt diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 2cb10765ff..93d527a4d1 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1135,15 +1135,25 @@ namespace Emby.Server.Implementations.Data Path = RestorePath(path.ToString()) }; - if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)) + if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks) + && ticks >= DateTime.MinValue.Ticks + && ticks <= DateTime.MaxValue.Ticks) { image.DateModified = new DateTime(ticks, DateTimeKind.Utc); } + else + { + return null; + } if (Enum.TryParse(imageType.ToString(), true, out ImageType type)) { image.Type = type; } + else + { + return null; + } // Optional parameters: width*height*blurhash if (nextSegment + 1 < value.Length - 1) diff --git a/Emby.Server.Implementations/Properties/AssemblyInfo.cs b/Emby.Server.Implementations/Properties/AssemblyInfo.cs index cb7972173e..41c396ac1d 100644 --- a/Emby.Server.Implementations/Properties/AssemblyInfo.cs +++ b/Emby.Server.Implementations/Properties/AssemblyInfo.cs @@ -16,6 +16,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] [assembly: InternalsVisibleTo("Jellyfin.Server.Implementations.Tests")] +[assembly: InternalsVisibleTo("Emby.Server.Implementations.Fuzz")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from diff --git a/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj b/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj index 791cb140db..6abdb77342 100644 --- a/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj +++ b/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj @@ -12,6 +12,13 @@ </ItemGroup> <ItemGroup> + <ProjectReference Include="../../MediaBrowser.Controller/MediaBrowser.Controller.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="AutoFixture" Version="4.17.0" /> + <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> + <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="SharpFuzz" Version="1.6.2" /> </ItemGroup> diff --git a/fuzz/Emby.Server.Implementations.Fuzz/Program.cs b/fuzz/Emby.Server.Implementations.Fuzz/Program.cs index a4a6f5f54d..03b2964948 100644 --- a/fuzz/Emby.Server.Implementations.Fuzz/Program.cs +++ b/fuzz/Emby.Server.Implementations.Fuzz/Program.cs @@ -1,5 +1,12 @@ using System; +using AutoFixture; +using AutoFixture.AutoMoq; +using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Library; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using Moq; using SharpFuzz; namespace Emby.Server.Implementations.Fuzz @@ -11,6 +18,7 @@ namespace Emby.Server.Implementations.Fuzz switch (args[0]) { case "PathExtensions.TryReplaceSubPath": Run(PathExtensions_TryReplaceSubPath); return; + case "SqliteItemRepository.ItemImageInfoFromValueString": Run(SqliteItemRepository_ItemImageInfoFromValueString); return; default: throw new ArgumentException($"Unknown fuzzing function: {args[0]}"); } } @@ -28,5 +36,27 @@ namespace Emby.Server.Implementations.Fuzz _ = PathExtensions.TryReplaceSubPath(parts[0], parts[1], parts[2], out _); } + + private static void SqliteItemRepository_ItemImageInfoFromValueString(string data) + { + var sqliteItemRepository = MockSqliteItemRepository(); + sqliteItemRepository.ItemImageInfoFromValueString(data); + } + + private static SqliteItemRepository MockSqliteItemRepository() + { + const string VirtualMetaDataPath = "%MetadataPath%"; + const string MetaDataPath = "/meta/data/path"; + + var appHost = new Mock<IServerApplicationHost>(); + appHost.Setup(x => x.ExpandVirtualPath(It.IsAny<string>())) + .Returns((string x) => x.Replace(VirtualMetaDataPath, MetaDataPath, StringComparison.Ordinal)); + appHost.Setup(x => x.ReverseVirtualPath(It.IsAny<string>())) + .Returns((string x) => x.Replace(MetaDataPath, VirtualMetaDataPath, StringComparison.Ordinal)); + + IFixture fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true }); + fixture.Inject(appHost); + return fixture.Create<SqliteItemRepository>(); + } } } diff --git a/fuzz/Emby.Server.Implementations.Fuzz/Testcases/SqliteItemRepository.ItemImageInfoFromValueString/test1.txt b/fuzz/Emby.Server.Implementations.Fuzz/Testcases/SqliteItemRepository.ItemImageInfoFromValueString/test1.txt new file mode 100644 index 0000000000..1b0115882f --- /dev/null +++ b/fuzz/Emby.Server.Implementations.Fuzz/Testcases/SqliteItemRepository.ItemImageInfoFromValueString/test1.txt @@ -0,0 +1 @@ +/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs index f312933fbf..a6e1dfe8f6 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs @@ -109,6 +109,9 @@ namespace Jellyfin.Server.Implementations.Tests.Data [InlineData("")] [InlineData("*")] [InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")] + [InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*6374520964785129080*WjQbtJtSO8nhNZ%L_Io#R/oaS<o}-;adXAoIn7j[%hW9s:WGw[nN")] // Invalid modified date + [InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*-637452096478512963*WjQbtJtSO8nhNZ%L_Io#R/oaS<o}-;adXAoIn7j[%hW9s:WGw[nN")] // Negative modified date + [InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Invalid*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN")] // Invalid type public void ItemImageInfoFromValueString_Invalid_Null(string value) { Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value)); From 8e498b01e73b84f4343f8a00df11869ab8ab7089 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Fri, 3 Sep 2021 06:57:04 -0600 Subject: [PATCH 113/118] Fix issues from merge --- Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index bcfc4011c5..8125ed57df 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -15,8 +15,8 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos; -using MediaBrowser.Common; using Jellyfin.Extensions.Json; +using MediaBrowser.Common; using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Cryptography; From 611c20dba88ba83e41cb421baa12aa238af76216 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Fri, 3 Sep 2021 06:57:33 -0600 Subject: [PATCH 114/118] Fix indentation --- .../Library/Validators/StudiosValidator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 7a22f851b4..8577d722e0 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -92,9 +92,9 @@ namespace Emby.Server.Implementations.Library.Validators _libraryManager.DeleteItem( item, new DeleteOptions - { - DeleteFileLocation = false - }, + { + DeleteFileLocation = false + }, false); } From 3f2c7065756682c96c90189ab5157b96fdb92bd0 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Fri, 3 Sep 2021 07:19:50 -0600 Subject: [PATCH 115/118] Apply suggestions from code review Co-authored-by: Claus Vium <cvium@users.noreply.github.com> --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5444e89050..1be3a1ca43 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3306,7 +3306,7 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += " " + videoDecoder; if (!IsCopyCodec(state.OutputVideoCodec) - && videoDecoder.IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1) + && videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase)) { var videoStream = state.VideoStream; var inputWidth = videoStream?.Width; @@ -3315,7 +3315,7 @@ namespace MediaBrowser.Controller.MediaEncoding var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - if (videoDecoder.IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 + if (videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase) && width.HasValue && height.HasValue) { From bfb37a9ed9989f63ee8b00f41f4efef7a92fc157 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Fri, 3 Sep 2021 07:21:21 -0600 Subject: [PATCH 116/118] Fix typos --- .../LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs | 2 +- .../LiveTv/Listings/SchedulesDirectDtos/MapDto.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs index b8f27a8ac7..52e920aa61 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// <summary> /// Gets or sets the transport. /// </summary> - [JsonPropertyName("transport.")] + [JsonPropertyName("transport")] public string Transport { get; set; } /// <summary> diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs index 8d45e8fff7..5140277b2c 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs @@ -34,15 +34,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos public int UhfVhf { get; set; } /// <summary> - /// Gets or sets the astc major. + /// Gets or sets the atsc major. /// </summary> - [JsonPropertyName("astcMajor")] + [JsonPropertyName("atscMajor")] public int AtscMajor { get; set; } /// <summary> - /// Gets or sets the astc minor. + /// Gets or sets the atsc minor. /// </summary> - [JsonPropertyName("astcMinor")] + [JsonPropertyName("atscMinor")] public int AtscMinor { get; set; } } } From 2f360151c266ff0885589338fdcdebd2a0c57fae Mon Sep 17 00:00:00 2001 From: siankatabg <siankata91@gmail.com> Date: Thu, 2 Sep 2021 13:17:12 +0000 Subject: [PATCH 117/118] Translated using Weblate (Bulgarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/bg/ --- Emby.Server.Implementations/Localization/Core/bg-BG.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index f55287d15b..e1c9233087 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -25,7 +25,7 @@ "HeaderLiveTV": "Телевизия на живо", "HeaderNextUp": "Следва", "HeaderRecordingGroups": "Запис групи", - "HomeVideos": "Домашни клипове", + "HomeVideos": "Домашни Клипове", "Inherit": "Наследяване", "ItemAddedWithName": "{0} е добавено към библиотеката", "ItemRemovedWithName": "{0} е премахнато от библиотеката", @@ -39,7 +39,7 @@ "MixedContent": "Смесено съдържание", "Movies": "Филми", "Music": "Музика", - "MusicVideos": "Музикални видеа", + "MusicVideos": "Музикални Видеа", "NameInstallFailed": "{0} не можа да се инсталира", "NameSeasonNumber": "Сезон {0}", "NameSeasonUnknown": "Неразпознат сезон", From b458f85c47e5f63e53062b8c96c564c674bb62dc Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Fri, 3 Sep 2021 16:39:03 +0200 Subject: [PATCH 118/118] Fix InvalidOperationException when serializing MediaPathInfo --- MediaBrowser.Model/Configuration/MediaPathInfo.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MediaBrowser.Model/Configuration/MediaPathInfo.cs b/MediaBrowser.Model/Configuration/MediaPathInfo.cs index d096defcb8..a7bc435901 100644 --- a/MediaBrowser.Model/Configuration/MediaPathInfo.cs +++ b/MediaBrowser.Model/Configuration/MediaPathInfo.cs @@ -9,6 +9,12 @@ namespace MediaBrowser.Model.Configuration Path = path; } + // Needed for xml serialization + public MediaPathInfo() + { + Path = string.Empty; + } + public string Path { get; set; } public string? NetworkPath { get; set; }