From 22ce1f25d0686b6f8a06d590289d50a7ee907845 Mon Sep 17 00:00:00 2001 From: sususu98 Date: Wed, 23 Apr 2025 18:18:38 +0800 Subject: refactor(StreamInfo): reorganize subtitle URL logic and conditions # Conflicts: # MediaBrowser.Model/Dlna/StreamInfo.cs --- MediaBrowser.Model/Dlna/StreamInfo.cs | 53 ++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 23 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 13acd15a3..e233797d7 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1250,34 +1250,41 @@ public class StreamInfo if (info.DeliveryMethod == SubtitleDeliveryMethod.External) { - if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal) - { - info.Url = string.Format( - CultureInfo.InvariantCulture, - "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", - baseUrl, - ItemId, - MediaSourceId, - stream.Index.ToString(CultureInfo.InvariantCulture), - startPositionTicks.ToString(CultureInfo.InvariantCulture), - subtitleProfile.Format); - - if (!string.IsNullOrEmpty(accessToken)) - { - info.Url += "?ApiKey=" + accessToken; - } - - info.IsExternalUrl = false; - } - else + // Default to using the API URL + info.Url = string.Format( + CultureInfo.InvariantCulture, + "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", + baseUrl, + ItemId, + MediaSourceId, + stream.Index.ToString(CultureInfo.InvariantCulture), + startPositionTicks.ToString(CultureInfo.InvariantCulture), + subtitleProfile.Format); + info.IsExternalUrl = false; // Default to API URL + + // Check conditions for potentially using the direct path + if (stream.IsExternal // Must be external + && MediaSource?.Protocol != MediaProtocol.File // Main media must not be a local file + && string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) // Format must match (no conversion needed) + && !string.IsNullOrEmpty(stream.Path) // Path must exist + && Uri.TryCreate(stream.Path, UriKind.Absolute, out Uri? uriResult) // Path must be an absolute URI + && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps)) // Scheme must be HTTP or HTTPS { + // All conditions met, override with the direct path info.Url = stream.Path; info.IsExternalUrl = true; } - } - return info; - } + // Append ApiKey only if we are using the API URL + if (!info.IsExternalUrl && !string.IsNullOrEmpty(accessToken)) + { + // Use "?ApiKey=" as seen in HEAD and other parts of the code + info.Url += "?ApiKey=" + accessToken; + } + } + + return info; + } /// /// Gets the target video bit depth. -- cgit v1.2.3 From fd108ff5284f7b59fe4f0e942a05051acbf14cdc Mon Sep 17 00:00:00 2001 From: sususu98 Date: Thu, 24 Apr 2025 14:17:33 +0800 Subject: Style: Fix indentation in StreamInfo.cs --- MediaBrowser.Model/Dlna/StreamInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index e233797d7..e465e0e31 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1281,10 +1281,10 @@ public class StreamInfo // Use "?ApiKey=" as seen in HEAD and other parts of the code info.Url += "?ApiKey=" + accessToken; } - } + } return info; - } + } /// /// Gets the target video bit depth. -- cgit v1.2.3 From aebabb15801a2d216f84d6a49d8b434ae59e1f34 Mon Sep 17 00:00:00 2001 From: sususu98 Date: Thu, 24 Apr 2025 14:25:12 +0800 Subject: style: fix return statement indentation in StreamInfo.cs --- MediaBrowser.Model/Dlna/StreamInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index e465e0e31..92404de50 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1283,7 +1283,7 @@ public class StreamInfo } } - return info; + return info; } /// -- cgit v1.2.3 From 0fb6d930e1ca14d1d3af06ecee310869d2e86dfe Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sun, 5 Oct 2025 10:59:10 +0200 Subject: Deprecate HasPassword property on UserDto --- .../Users/DefaultAuthenticationProvider.cs | 6 +--- .../Users/InvalidAuthProvider.cs | 6 ---- .../Users/UserManager.cs | 3 -- .../Authentication/IAuthenticationProvider.cs | 2 -- MediaBrowser.Model/Dto/UserDto.cs | 9 ++++-- .../Controllers/UserControllerTests.cs | 32 ---------------------- 6 files changed, 7 insertions(+), 51 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index 35c43b176..446849b6f 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -59,7 +59,7 @@ namespace Jellyfin.Server.Implementations.Users } // As long as jellyfin supports password-less users, we need this little block here to accommodate - if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password)) + if (string.IsNullOrEmpty(resolvedUser.Password) && string.IsNullOrEmpty(password)) { return Task.FromResult(new ProviderAuthenticationResult { @@ -93,10 +93,6 @@ namespace Jellyfin.Server.Implementations.Users }); } - /// - public bool HasPassword(User user) - => !string.IsNullOrEmpty(user?.Password); - /// public Task ChangePassword(User user, string newPassword) { diff --git a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs index caf9d5bd9..56b8a7fc4 100644 --- a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs +++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs @@ -21,12 +21,6 @@ namespace Jellyfin.Server.Implementations.Users throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); } - /// - public bool HasPassword(User user) - { - return true; - } - /// public Task ChangePassword(User user, string newPassword) { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index d0b41a7f6..9f36624bc 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -306,15 +306,12 @@ namespace Jellyfin.Server.Implementations.Users /// public UserDto GetUserDto(User user, string? remoteEndPoint = null) { - var hasPassword = GetAuthenticationProvider(user).HasPassword(user); var castReceiverApplications = _serverConfigurationManager.Configuration.CastReceiverApplications; return new UserDto { Name = user.Username, Id = user.Id, ServerId = _appHost.SystemId, - HasPassword = hasPassword, - HasConfiguredPassword = hasPassword, EnableAutoLogin = user.EnableAutoLogin, LastLoginDate = user.LastLoginDate, LastActivityDate = user.LastActivityDate, diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index 976a667ac..c993ceea8 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -14,8 +14,6 @@ namespace MediaBrowser.Controller.Authentication Task Authenticate(string username, string password); - bool HasPassword(User user); - Task ChangePassword(User user, string newPassword); } diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs index 05019741e..c6b4a4d14 100644 --- a/MediaBrowser.Model/Dto/UserDto.cs +++ b/MediaBrowser.Model/Dto/UserDto.cs @@ -1,5 +1,6 @@ #nullable disable using System; +using System.ComponentModel; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Users; @@ -54,20 +55,22 @@ namespace MediaBrowser.Model.Dto /// Gets or sets a value indicating whether this instance has password. /// /// true if this instance has password; otherwise, false. - public bool HasPassword { get; set; } + [Obsolete("This information is no longer provided")] + public bool? HasPassword { get; set; } = true; /// /// Gets or sets a value indicating whether this instance has configured password. /// /// true if this instance has configured password; otherwise, false. - public bool HasConfiguredPassword { get; set; } + [Obsolete("This is always true")] + public bool? HasConfiguredPassword { get; set; } = true; /// /// Gets or sets a value indicating whether this instance has configured easy password. /// /// true if this instance has configured easy password; otherwise, false. [Obsolete("Easy Password has been replaced with Quick Connect")] - public bool HasConfiguredEasyPassword { get; set; } + public bool? HasConfiguredEasyPassword { get; set; } = false; /// /// Gets or sets whether async login is enabled or not. diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs index 16c63ed49..4278fd069 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs @@ -61,7 +61,6 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var users = await response.Content.ReadFromJsonAsync(_jsonOptions); Assert.NotNull(users); Assert.Single(users); - Assert.False(users![0].HasConfiguredPassword); } [Fact] @@ -92,8 +91,6 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Assert.Equal(HttpStatusCode.OK, response.StatusCode); var user = await response.Content.ReadFromJsonAsync(_jsonOptions); Assert.Equal(TestUsername, user!.Name); - Assert.False(user.HasPassword); - Assert.False(user.HasConfiguredPassword); _testUserId = user.Id; @@ -149,35 +146,6 @@ namespace Jellyfin.Server.Integration.Tests.Controllers using var response = await UpdateUserPassword(client, _testUserId, createRequest); Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - var users = await JsonSerializer.DeserializeAsync( - await client.GetStreamAsync("Users"), _jsonOptions); - var user = users!.First(x => x.Id.Equals(_testUserId)); - Assert.True(user.HasPassword); - Assert.True(user.HasConfiguredPassword); - } - - [Fact] - [Priority(2)] - public async Task UpdateUserPassword_Empty_RemoveSetPassword() - { - var client = _factory.CreateClient(); - - client.DefaultRequestHeaders.AddAuthHeader(_accessToken!); - - var createRequest = new UpdateUserPassword() - { - CurrentPw = "4randomPa$$word", - }; - - using var response = await UpdateUserPassword(client, _testUserId, createRequest); - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - var users = await JsonSerializer.DeserializeAsync( - await client.GetStreamAsync("Users"), _jsonOptions); - var user = users!.First(x => x.Id.Equals(_testUserId)); - Assert.False(user.HasPassword); - Assert.False(user.HasConfiguredPassword); } } } -- cgit v1.2.3 From c274336563fa2f9c7e7b7df0126e815c812e528e Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 26 Oct 2025 21:52:03 -0400 Subject: Bump version to 10.12.0 (for real this time) --- Emby.Naming/Emby.Naming.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 2 +- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- SharedVersion.cs | 4 ++-- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 20b32f3a6..b84c96116 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -36,7 +36,7 @@ Jellyfin Contributors Jellyfin.Naming - 10.11.0 + 10.12.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 45374c22f..fd852ece9 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -18,7 +18,7 @@ Jellyfin Contributors Jellyfin.Data - 10.11.0 + 10.12.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index de6be4707..9af13b0a7 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Common - 10.11.0 + 10.12.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 3353ad63f..b5d14e94b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Controller - 10.11.0 + 10.12.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index e9dab6bc8..ef025d02d 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Model - 10.11.0 + 10.12.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/SharedVersion.cs b/SharedVersion.cs index d26eb31ae..3b394d28b 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.11.0")] -[assembly: AssemblyFileVersion("10.11.0")] +[assembly: AssemblyVersion("10.12.0")] +[assembly: AssemblyFileVersion("10.12.0")] diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index 1613d83bc..f52fd014d 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -15,7 +15,7 @@ Jellyfin Contributors Jellyfin.Extensions - 10.11.0 + 10.12.0 https://github.com/jellyfin/jellyfin GPL-3.0-only -- cgit v1.2.3 From 1ccd10863e24485978a2f5fd7650b2bcf42168d3 Mon Sep 17 00:00:00 2001 From: thornbill Date: Sun, 2 Nov 2025 21:58:42 -0500 Subject: Backport pull request #15254 from jellyfin/release-10.11.z Update password reset to always return the same response structure Original-merge: 4ad31418753840ca76c52fc2aa56fa1a4235ca87 Merged-by: crobibero Backported-by: Joshua M. Boniface --- .../Users/DefaultPasswordResetProvider.cs | 40 +++++++++++++--------- .../Users/UserManager.cs | 24 ++++++------- .../Authentication/IPasswordResetProvider.cs | 5 ++- MediaBrowser.Model/Users/ForgotPasswordAction.cs | 4 +++ 4 files changed, 41 insertions(+), 32 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index f20fb2d92..49a9fda94 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text.Json; @@ -92,33 +93,38 @@ namespace Jellyfin.Server.Implementations.Users } /// - public async Task StartForgotPasswordProcess(User user, bool isInNetwork) + public async Task StartForgotPasswordProcess(User? user, string enteredUsername, bool isInNetwork) { - byte[] bytes = new byte[4]; - RandomNumberGenerator.Fill(bytes); - string pin = BitConverter.ToString(bytes); - DateTime expireTime = DateTime.UtcNow.AddMinutes(30); - string filePath = _passwordResetFileBase + user.Id + ".json"; - SerializablePasswordReset spr = new SerializablePasswordReset - { - ExpirationDate = expireTime, - Pin = pin, - PinFile = filePath, - UserName = user.Username - }; + var usernameHash = enteredUsername.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); + var pinFile = _passwordResetFileBase + usernameHash + ".json"; - FileStream fileStream = AsyncFile.Create(filePath); - await using (fileStream.ConfigureAwait(false)) + if (user is not null && isInNetwork) { - await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false); + byte[] bytes = new byte[4]; + RandomNumberGenerator.Fill(bytes); + string pin = BitConverter.ToString(bytes); + + SerializablePasswordReset spr = new SerializablePasswordReset + { + ExpirationDate = expireTime, + Pin = pin, + PinFile = pinFile, + UserName = user.Username + }; + + FileStream fileStream = AsyncFile.Create(pinFile); + await using (fileStream.ConfigureAwait(false)) + { + await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false); + } } return new ForgotPasswordResult { Action = ForgotPasswordAction.PinCode, PinExpirationDate = expireTime, - PinFile = filePath + PinFile = pinFile }; } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index d0b41a7f6..b534ccd1b 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -508,23 +508,18 @@ namespace Jellyfin.Server.Implementations.Users public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) { var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); + var passwordResetProvider = GetPasswordResetProvider(user); + + var result = await passwordResetProvider + .StartForgotPasswordProcess(user, enteredUsername, isInNetwork) + .ConfigureAwait(false); if (user is not null && isInNetwork) { - var passwordResetProvider = GetPasswordResetProvider(user); - var result = await passwordResetProvider - .StartForgotPasswordProcess(user, isInNetwork) - .ConfigureAwait(false); - await UpdateUserAsync(user).ConfigureAwait(false); - return result; } - return new ForgotPasswordResult - { - Action = ForgotPasswordAction.InNetworkRequired, - PinFile = string.Empty - }; + return result; } /// @@ -760,8 +755,13 @@ namespace Jellyfin.Server.Implementations.Users return GetAuthenticationProviders(user)[0]; } - private IPasswordResetProvider GetPasswordResetProvider(User user) + private IPasswordResetProvider GetPasswordResetProvider(User? user) { + if (user is null) + { + return _defaultPasswordResetProvider; + } + return GetPasswordResetProviders(user)[0]; } diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs index 592ce9955..36cd5c5d1 100644 --- a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -15,11 +13,12 @@ namespace MediaBrowser.Controller.Authentication bool IsEnabled { get; } - Task StartForgotPasswordProcess(User user, bool isInNetwork); + Task StartForgotPasswordProcess(User? user, string enteredUsername, bool isInNetwork); Task RedeemPasswordResetPin(string pin); } +#nullable disable public class PasswordPinCreationResult { public string PinFile { get; set; } diff --git a/MediaBrowser.Model/Users/ForgotPasswordAction.cs b/MediaBrowser.Model/Users/ForgotPasswordAction.cs index f198476e3..55907e6c8 100644 --- a/MediaBrowser.Model/Users/ForgotPasswordAction.cs +++ b/MediaBrowser.Model/Users/ForgotPasswordAction.cs @@ -1,11 +1,15 @@ #pragma warning disable CS1591 +using System; + namespace MediaBrowser.Model.Users { public enum ForgotPasswordAction { + [Obsolete("Returning different actions represents a security concern.")] ContactAdmin = 0, PinCode = 1, + [Obsolete("Returning different actions represents a security concern.")] InNetworkRequired = 2 } } -- cgit v1.2.3 From 5182aec13f853839cddb74ae0ebcc6b35009dc04 Mon Sep 17 00:00:00 2001 From: Karolis Date: Mon, 17 Nov 2025 15:18:29 +0200 Subject: Add subtitle extraction timeout configuration option --- MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 16 ++++++++++++---- MediaBrowser.Model/Configuration/EncodingOptions.cs | 6 ++++++ 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 88a7bb4b4..49ac0fa03 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -13,8 +13,10 @@ using System.Threading; using System.Threading.Tasks; using AsyncKeyedLock; using MediaBrowser.Common; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; @@ -37,6 +39,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly IMediaSourceManager _mediaSourceManager; private readonly ISubtitleParser _subtitleParser; private readonly IPathManager _pathManager; + private readonly IServerConfigurationManager _serverConfigurationManager; /// /// The _semaphoreLocks. @@ -54,7 +57,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles IHttpClientFactory httpClientFactory, IMediaSourceManager mediaSourceManager, ISubtitleParser subtitleParser, - IPathManager pathManager) + IPathManager pathManager, + IServerConfigurationManager serverConfigurationManager) { _logger = logger; _fileSystem = fileSystem; @@ -63,6 +67,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles _mediaSourceManager = mediaSourceManager; _subtitleParser = subtitleParser; _pathManager = pathManager; + _serverConfigurationManager = serverConfigurationManager; } private MemoryStream ConvertSubtitles( @@ -394,7 +399,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles try { - await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); + var timeoutMinutes = _serverConfigurationManager.GetEncodingOptions().SubtitleExtractionTimeoutMinutes; + await process.WaitForExitAsync(TimeSpan.FromMinutes(timeoutMinutes)).ConfigureAwait(false); exitCode = process.ExitCode; } catch (OperationCanceledException) @@ -677,7 +683,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles try { - await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); + var timeoutMinutes = _serverConfigurationManager.GetEncodingOptions().SubtitleExtractionTimeoutMinutes; + await process.WaitForExitAsync(TimeSpan.FromMinutes(timeoutMinutes)).ConfigureAwait(false); exitCode = process.ExitCode; } catch (OperationCanceledException) @@ -828,7 +835,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles try { - await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); + var timeoutMinutes = _serverConfigurationManager.GetEncodingOptions().SubtitleExtractionTimeoutMinutes; + await process.WaitForExitAsync(TimeSpan.FromMinutes(timeoutMinutes)).ConfigureAwait(false); exitCode = process.ExitCode; } catch (OperationCanceledException) diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 2720c0bdf..f7f386d28 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -57,6 +57,7 @@ public class EncodingOptions AllowHevcEncoding = false; AllowAv1Encoding = false; EnableSubtitleExtraction = true; + SubtitleExtractionTimeoutMinutes = 30; AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = ["mkv"]; HardwareDecodingCodecs = ["h264", "vc1"]; } @@ -286,6 +287,11 @@ public class EncodingOptions /// public bool EnableSubtitleExtraction { get; set; } + /// + /// Gets or sets the timeout for subtitle extraction in minutes. + /// + public int SubtitleExtractionTimeoutMinutes { get; set; } + /// /// Gets or sets the codecs hardware encoding is used for. /// -- cgit v1.2.3 From 196c243a7d53d06a5fe492942442d48bba9727c4 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Tue, 18 Nov 2025 16:13:48 +0100 Subject: Disable legacy authorization methods by default --- .../Routines/DisableLegacyAuthorization.cs | 30 ++++++++++++++++++++++ .../Configuration/ServerConfiguration.cs | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs (limited to 'MediaBrowser.Model') diff --git a/Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs b/Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs new file mode 100644 index 000000000..8bfb0c4e8 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs @@ -0,0 +1,30 @@ +using MediaBrowser.Controller.Configuration; + +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// Migration to disable legacy authorization in the system config. +/// +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-11-18T16:00:00", nameof(DisableLegacyAuthorization), "F020F843-E079-4061-99E0-F43D145F2557")] +public class DisableLegacyAuthorization : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public DisableLegacyAuthorization(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// + public void Perform() + { + _serverConfigurationManager.Configuration.EnableLegacyAuthorization = false; + _serverConfigurationManager.SaveConfiguration(); + } +} diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index a58c01c96..ac5c12304 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -287,5 +287,5 @@ public class ServerConfiguration : BaseApplicationConfiguration /// /// Gets or sets a value indicating whether old authorization methods are allowed. /// - public bool EnableLegacyAuthorization { get; set; } = true; + public bool EnableLegacyAuthorization { get; set; } } -- cgit v1.2.3 From 0b3d6676d1dc78f38cd17c04ecafe2196a291199 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Mon, 8 Dec 2025 21:01:32 -0700 Subject: Add ability to sort and filter activity log entries (#15583) --- Jellyfin.Api/Controllers/ActivityLogController.cs | 76 ++++++- Jellyfin.Data/Enums/ActivityLogSortBy.cs | 49 +++++ Jellyfin.Data/Queries/ActivityLogQuery.cs | 69 +++++-- .../Activity/ActivityManager.cs | 229 +++++++++++++++------ MediaBrowser.Model/Activity/IActivityManager.cs | 43 ++-- 5 files changed, 362 insertions(+), 104 deletions(-) create mode 100644 Jellyfin.Data/Enums/ActivityLogSortBy.cs (limited to 'MediaBrowser.Model') diff --git a/Jellyfin.Api/Controllers/ActivityLogController.cs b/Jellyfin.Api/Controllers/ActivityLogController.cs index a19a203b5..d5f262773 100644 --- a/Jellyfin.Api/Controllers/ActivityLogController.cs +++ b/Jellyfin.Api/Controllers/ActivityLogController.cs @@ -1,13 +1,16 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; -using Jellyfin.Api.Constants; +using Jellyfin.Data.Enums; using Jellyfin.Data.Queries; +using Jellyfin.Database.Implementations.Enums; using MediaBrowser.Common.Api; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers; @@ -32,10 +35,19 @@ public class ActivityLogController : BaseJellyfinApiController /// /// Gets activity log entries. /// - /// Optional. The record index to start at. All items with a lower index will be dropped from the results. - /// Optional. The maximum number of records to return. - /// Optional. The minimum date. Format = ISO. - /// Optional. Filter log entries if it has user id, or not. + /// The record index to start at. All items with a lower index will be dropped from the results. + /// The maximum number of records to return. + /// The minimum date. + /// Filter log entries if it has user id, or not. + /// Filter by name. + /// Filter by overview. + /// Filter by short overview. + /// Filter by type. + /// Filter by item id. + /// Filter by username. + /// Filter by log severity. + /// Specify one or more sort orders. Format: SortBy=Name,Type. + /// Sort Order.. /// Activity log returned. /// A containing the log entries. [HttpGet("Entries")] @@ -44,14 +56,60 @@ public class ActivityLogController : BaseJellyfinApiController [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] DateTime? minDate, - [FromQuery] bool? hasUserId) + [FromQuery] bool? hasUserId, + [FromQuery] string? name, + [FromQuery] string? overview, + [FromQuery] string? shortOverview, + [FromQuery] string? type, + [FromQuery] Guid? itemId, + [FromQuery] string? username, + [FromQuery] LogLevel? severity, + [FromQuery] ActivityLogSortBy[]? sortBy, + [FromQuery] SortOrder[]? sortOrder) { - return await _activityManager.GetPagedResultAsync(new ActivityLogQuery + var query = new ActivityLogQuery { Skip = startIndex, Limit = limit, MinDate = minDate, - HasUserId = hasUserId - }).ConfigureAwait(false); + HasUserId = hasUserId, + Name = name, + Overview = overview, + ShortOverview = shortOverview, + Type = type, + ItemId = itemId, + Username = username, + Severity = severity, + OrderBy = GetOrderBy(sortBy ?? [], sortOrder ?? []), + }; + + return await _activityManager.GetPagedResultAsync(query).ConfigureAwait(false); + } + + private static (ActivityLogSortBy SortBy, SortOrder SortOrder)[] GetOrderBy( + IReadOnlyList sortBy, + IReadOnlyList requestedSortOrder) + { + if (sortBy.Count == 0) + { + return []; + } + + var result = new (ActivityLogSortBy, SortOrder)[sortBy.Count]; + var i = 0; + for (; i < requestedSortOrder.Count; i++) + { + result[i] = (sortBy[i], requestedSortOrder[i]); + } + + // Add remaining elements with the first specified SortOrder + // or the default one if no SortOrders are specified + var order = requestedSortOrder.Count > 0 ? requestedSortOrder[0] : SortOrder.Ascending; + for (; i < sortBy.Count; i++) + { + result[i] = (sortBy[i], order); + } + + return result; } } diff --git a/Jellyfin.Data/Enums/ActivityLogSortBy.cs b/Jellyfin.Data/Enums/ActivityLogSortBy.cs new file mode 100644 index 000000000..d6d44e8c0 --- /dev/null +++ b/Jellyfin.Data/Enums/ActivityLogSortBy.cs @@ -0,0 +1,49 @@ +namespace Jellyfin.Data.Enums; + +/// +/// Activity log sorting options. +/// +public enum ActivityLogSortBy +{ + /// + /// Sort by name. + /// + Name = 0, + + /// + /// Sort by overview. + /// + Overiew = 1, + + /// + /// Sort by short overview. + /// + ShortOverview = 2, + + /// + /// Sort by type. + /// + Type = 3, + + /* + /// + /// Sort by item name. + /// + Item = 4, + */ + + /// + /// Sort by date. + /// + DateCreated = 5, + + /// + /// Sort by username. + /// + Username = 6, + + /// + /// Sort by severity. + /// + LogSeverity = 7 +} diff --git a/Jellyfin.Data/Queries/ActivityLogQuery.cs b/Jellyfin.Data/Queries/ActivityLogQuery.cs index f1af099d3..95c52f870 100644 --- a/Jellyfin.Data/Queries/ActivityLogQuery.cs +++ b/Jellyfin.Data/Queries/ActivityLogQuery.cs @@ -1,20 +1,63 @@ using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using Microsoft.Extensions.Logging; -namespace Jellyfin.Data.Queries +namespace Jellyfin.Data.Queries; + +/// +/// A class representing a query to the activity logs. +/// +public class ActivityLogQuery : PaginatedQuery { /// - /// A class representing a query to the activity logs. + /// Gets or sets a value indicating whether to take entries with a user id. + /// + public bool? HasUserId { get; set; } + + /// + /// Gets or sets the minimum date to query for. + /// + public DateTime? MinDate { get; set; } + + /// + /// Gets or sets the name filter. /// - public class ActivityLogQuery : PaginatedQuery - { - /// - /// Gets or sets a value indicating whether to take entries with a user id. - /// - public bool? HasUserId { get; set; } + public string? Name { get; set; } - /// - /// Gets or sets the minimum date to query for. - /// - public DateTime? MinDate { get; set; } - } + /// + /// Gets or sets the overview filter. + /// + public string? Overview { get; set; } + + /// + /// Gets or sets the short overview filter. + /// + public string? ShortOverview { get; set; } + + /// + /// Gets or sets the type filter. + /// + public string? Type { get; set; } + + /// + /// Gets or sets the item filter. + /// + public Guid? ItemId { get; set; } + + /// + /// Gets or sets the username filter. + /// + public string? Username { get; set; } + + /// + /// Gets or sets the log level filter. + /// + public LogLevel? Severity { get; set; } + + /// + /// Gets or sets the result ordering. + /// + public IReadOnlyCollection<(ActivityLogSortBy, SortOrder)>? OrderBy { get; set; } } diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 8d492f7cd..7ee573f53 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -1,103 +1,198 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.Data.Queries; using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Querying; using Microsoft.EntityFrameworkCore; -namespace Jellyfin.Server.Implementations.Activity +namespace Jellyfin.Server.Implementations.Activity; + +/// +/// Manages the storage and retrieval of instances. +/// +public class ActivityManager : IActivityManager { + private readonly IDbContextFactory _provider; + /// - /// Manages the storage and retrieval of instances. + /// Initializes a new instance of the class. /// - public class ActivityManager : IActivityManager + /// The Jellyfin database provider. + public ActivityManager(IDbContextFactory provider) { - private readonly IDbContextFactory _provider; + _provider = provider; + } + + /// + public event EventHandler>? EntryCreated; - /// - /// Initializes a new instance of the class. - /// - /// The Jellyfin database provider. - public ActivityManager(IDbContextFactory provider) + /// + public async Task CreateAsync(ActivityLog entry) + { + var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) { - _provider = provider; + dbContext.ActivityLogs.Add(entry); + await dbContext.SaveChangesAsync().ConfigureAwait(false); } - /// - public event EventHandler>? EntryCreated; + EntryCreated?.Invoke(this, new GenericEventArgs(ConvertToOldModel(entry))); + } + + /// + public async Task> GetPagedResultAsync(ActivityLogQuery query) + { + // TODO allow sorting and filtering by item id. Currently not possible because ActivityLog stores the item id as a string. - /// - public async Task CreateAsync(ActivityLog entry) + var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) { - var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false); - await using (dbContext.ConfigureAwait(false)) + // TODO switch to LeftJoin in .NET 10. + var entries = from a in dbContext.ActivityLogs + join u in dbContext.Users on a.UserId equals u.Id into ugj + from u in ugj.DefaultIfEmpty() + select new ExpandedActivityLog { ActivityLog = a, Username = u.Username }; + + if (query.HasUserId is not null) { - dbContext.ActivityLogs.Add(entry); - await dbContext.SaveChangesAsync().ConfigureAwait(false); + entries = entries.Where(e => e.ActivityLog.UserId.Equals(default) != query.HasUserId.Value); } - EntryCreated?.Invoke(this, new GenericEventArgs(ConvertToOldModel(entry))); - } + if (query.MinDate is not null) + { + entries = entries.Where(e => e.ActivityLog.DateCreated >= query.MinDate.Value); + } - /// - public async Task> GetPagedResultAsync(ActivityLogQuery query) - { - var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false); - await using (dbContext.ConfigureAwait(false)) + if (!string.IsNullOrEmpty(query.Name)) { - var entries = dbContext.ActivityLogs - .OrderByDescending(entry => entry.DateCreated) - .Where(entry => query.MinDate == null || entry.DateCreated >= query.MinDate) - .Where(entry => !query.HasUserId.HasValue || entry.UserId.Equals(default) != query.HasUserId.Value); - - return new QueryResult( - query.Skip, - await entries.CountAsync().ConfigureAwait(false), - await entries - .Skip(query.Skip ?? 0) - .Take(query.Limit ?? 100) - .Select(entity => new ActivityLogEntry(entity.Name, entity.Type, entity.UserId) - { - Id = entity.Id, - Overview = entity.Overview, - ShortOverview = entity.ShortOverview, - ItemId = entity.ItemId, - Date = entity.DateCreated, - Severity = entity.LogSeverity - }) - .ToListAsync() - .ConfigureAwait(false)); + entries = entries.Where(e => EF.Functions.Like(e.ActivityLog.Name, $"%{query.Name}%")); } - } - /// - public async Task CleanAsync(DateTime startDate) - { - var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false); - await using (dbContext.ConfigureAwait(false)) + if (!string.IsNullOrEmpty(query.Overview)) + { + entries = entries.Where(e => EF.Functions.Like(e.ActivityLog.Overview, $"%{query.Overview}%")); + } + + if (!string.IsNullOrEmpty(query.ShortOverview)) + { + entries = entries.Where(e => EF.Functions.Like(e.ActivityLog.ShortOverview, $"%{query.ShortOverview}%")); + } + + if (!string.IsNullOrEmpty(query.Type)) + { + entries = entries.Where(e => EF.Functions.Like(e.ActivityLog.Type, $"%{query.Type}%")); + } + + if (!query.ItemId.IsNullOrEmpty()) + { + var itemId = query.ItemId.Value.ToString("N"); + entries = entries.Where(e => e.ActivityLog.ItemId == itemId); + } + + if (!string.IsNullOrEmpty(query.Username)) + { + entries = entries.Where(e => EF.Functions.Like(e.Username, $"%{query.Username}%")); + } + + if (query.Severity is not null) { - await dbContext.ActivityLogs - .Where(entry => entry.DateCreated <= startDate) - .ExecuteDeleteAsync() - .ConfigureAwait(false); + entries = entries.Where(e => e.ActivityLog.LogSeverity == query.Severity); } + + return new QueryResult( + query.Skip, + await entries.CountAsync().ConfigureAwait(false), + await ApplyOrdering(entries, query.OrderBy) + .Skip(query.Skip ?? 0) + .Take(query.Limit ?? 100) + .Select(entity => new ActivityLogEntry(entity.ActivityLog.Name, entity.ActivityLog.Type, entity.ActivityLog.UserId) + { + Id = entity.ActivityLog.Id, + Overview = entity.ActivityLog.Overview, + ShortOverview = entity.ActivityLog.ShortOverview, + ItemId = entity.ActivityLog.ItemId, + Date = entity.ActivityLog.DateCreated, + Severity = entity.ActivityLog.LogSeverity + }) + .ToListAsync() + .ConfigureAwait(false)); } + } - private static ActivityLogEntry ConvertToOldModel(ActivityLog entry) + /// + public async Task CleanAsync(DateTime startDate) + { + var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) { - return new ActivityLogEntry(entry.Name, entry.Type, entry.UserId) - { - Id = entry.Id, - Overview = entry.Overview, - ShortOverview = entry.ShortOverview, - ItemId = entry.ItemId, - Date = entry.DateCreated, - Severity = entry.LogSeverity - }; + await dbContext.ActivityLogs + .Where(entry => entry.DateCreated <= startDate) + .ExecuteDeleteAsync() + .ConfigureAwait(false); + } + } + + private static ActivityLogEntry ConvertToOldModel(ActivityLog entry) + { + return new ActivityLogEntry(entry.Name, entry.Type, entry.UserId) + { + Id = entry.Id, + Overview = entry.Overview, + ShortOverview = entry.ShortOverview, + ItemId = entry.ItemId, + Date = entry.DateCreated, + Severity = entry.LogSeverity + }; + } + + private IOrderedQueryable ApplyOrdering(IQueryable query, IReadOnlyCollection<(ActivityLogSortBy, SortOrder)>? sorting) + { + if (sorting is null || sorting.Count == 0) + { + return query.OrderByDescending(e => e.ActivityLog.DateCreated); } + + IOrderedQueryable ordered = null!; + + foreach (var (sortBy, sortOrder) in sorting) + { + var orderBy = MapOrderBy(sortBy); + ordered = sortOrder == SortOrder.Ascending + ? (ordered ?? query).OrderBy(orderBy) + : (ordered ?? query).OrderByDescending(orderBy); + } + + return ordered; + } + + private Expression> MapOrderBy(ActivityLogSortBy sortBy) + { + return sortBy switch + { + ActivityLogSortBy.Name => e => e.ActivityLog.Name, + ActivityLogSortBy.Overiew => e => e.ActivityLog.Overview, + ActivityLogSortBy.ShortOverview => e => e.ActivityLog.ShortOverview, + ActivityLogSortBy.Type => e => e.ActivityLog.Type, + ActivityLogSortBy.DateCreated => e => e.ActivityLog.DateCreated, + ActivityLogSortBy.Username => e => e.Username, + ActivityLogSortBy.LogSeverity => e => e.ActivityLog.LogSeverity, + _ => throw new ArgumentOutOfRangeException(nameof(sortBy), sortBy, "Unhandled ActivityLogSortBy") + }; + } + + private class ExpandedActivityLog + { + public ActivityLog ActivityLog { get; set; } = null!; + + public string? Username { get; set; } } } diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs index 95aa567ad..96958e9a7 100644 --- a/MediaBrowser.Model/Activity/IActivityManager.cs +++ b/MediaBrowser.Model/Activity/IActivityManager.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Threading.Tasks; using Jellyfin.Data.Events; @@ -7,21 +5,36 @@ using Jellyfin.Data.Queries; using Jellyfin.Database.Implementations.Entities; using MediaBrowser.Model.Querying; -namespace MediaBrowser.Model.Activity +namespace MediaBrowser.Model.Activity; + +/// +/// Interface for the activity manager. +/// +public interface IActivityManager { - public interface IActivityManager - { - event EventHandler> EntryCreated; + /// + /// The event that is triggered when an entity is created. + /// + event EventHandler> EntryCreated; - Task CreateAsync(ActivityLog entry); + /// + /// Create a new activity log entry. + /// + /// The entry to create. + /// A representing the asynchronous operation. + Task CreateAsync(ActivityLog entry); - Task> GetPagedResultAsync(ActivityLogQuery query); + /// + /// Get a paged list of activity log entries. + /// + /// The activity log query. + /// The page of entries. + Task> GetPagedResultAsync(ActivityLogQuery query); - /// - /// Remove all activity logs before the specified date. - /// - /// Activity log start date. - /// A representing the asynchronous operation. - Task CleanAsync(DateTime startDate); - } + /// + /// Remove all activity logs before the specified date. + /// + /// Activity log start date. + /// A representing the asynchronous operation. + Task CleanAsync(DateTime startDate); } -- cgit v1.2.3 From 2168847a45721c567c69767be28c4ce51fe5523a Mon Sep 17 00:00:00 2001 From: lostb1t Date: Sat, 29 Nov 2025 10:11:18 +0100 Subject: wip --- MediaBrowser.Model/Dlna/StreamInfo.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 92404de50..9cbdf03e1 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1260,11 +1260,10 @@ public class StreamInfo stream.Index.ToString(CultureInfo.InvariantCulture), startPositionTicks.ToString(CultureInfo.InvariantCulture), subtitleProfile.Format); - info.IsExternalUrl = false; // Default to API URL + info.IsExternalUrl = false; // Check conditions for potentially using the direct path if (stream.IsExternal // Must be external - && MediaSource?.Protocol != MediaProtocol.File // Main media must not be a local file && string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) // Format must match (no conversion needed) && !string.IsNullOrEmpty(stream.Path) // Path must exist && Uri.TryCreate(stream.Path, UriKind.Absolute, out Uri? uriResult) // Path must be an absolute URI -- cgit v1.2.3 From 172b054f487c185efbe3f83639e1e896ca38dcb3 Mon Sep 17 00:00:00 2001 From: lostb1t Date: Sat, 29 Nov 2025 10:20:18 +0100 Subject: wip --- MediaBrowser.Model/Dlna/StreamInfo.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 9cbdf03e1..3efb143bc 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1264,6 +1264,7 @@ public class StreamInfo // Check conditions for potentially using the direct path if (stream.IsExternal // Must be external + && stream.SupportsExternalStream && string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) // Format must match (no conversion needed) && !string.IsNullOrEmpty(stream.Path) // Path must exist && Uri.TryCreate(stream.Path, UriKind.Absolute, out Uri? uriResult) // Path must be an absolute URI -- cgit v1.2.3 From e5fb07170888f5c77df9a686b1a73bafae26c784 Mon Sep 17 00:00:00 2001 From: stevenaw Date: Wed, 31 Dec 2025 09:26:31 -0500 Subject: Optimize StringHelper.ToFirstUpper() --- MediaBrowser.Model/Extensions/StringHelper.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Extensions/StringHelper.cs b/MediaBrowser.Model/Extensions/StringHelper.cs index 77cbef00f..58cde8620 100644 --- a/MediaBrowser.Model/Extensions/StringHelper.cs +++ b/MediaBrowser.Model/Extensions/StringHelper.cs @@ -1,3 +1,5 @@ +using System; + namespace MediaBrowser.Model.Extensions { /// @@ -25,14 +27,11 @@ namespace MediaBrowser.Model.Extensions return string.Create( str.Length, - str, + str.AsSpan(), (chars, buf) => { chars[0] = char.ToUpperInvariant(buf[0]); - for (int i = 1; i < chars.Length; i++) - { - chars[i] = buf[i]; - } + buf.Slice(1).CopyTo(chars.Slice(1)); }); } } -- cgit v1.2.3 From 90390772869495228793e1b29db20c8741a309de Mon Sep 17 00:00:00 2001 From: ZeusCraft10 Date: Mon, 5 Jan 2026 06:22:44 -0500 Subject: Fix zh-CN subtitle language display The DisplayTitle property was using .NET's CultureInfo.GetCultures(NeutralCultures) to resolve language display names. Since zh-CN is a specific culture (not neutral), it would fall back to the base 'zh' code, resulting in generic 'Chinese' instead of 'Chinese (Simplified)'. This change adds a LocalizedLanguage property to MediaStream that gets populated via LocalizationManager.FindLanguageInfo() when streams are retrieved from the database. This leverages Jellyfin's existing iso6392.txt mappings which correctly map zh-CN to 'Chinese (Simplified)'. The same pattern is already used for other localized strings like LocalizedDefault and LocalizedExternal. --- .../Item/MediaStreamRepository.cs | 6 +++ MediaBrowser.Model/Entities/MediaStream.cs | 52 +++------------------- .../Entities/MediaStreamTests.cs | 43 ++++++++++++++++++ 3 files changed, 55 insertions(+), 46 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs index 7eb13b740..64874ccad 100644 --- a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs +++ b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs @@ -158,6 +158,12 @@ public class MediaStreamRepository : IMediaStreamRepository dto.LocalizedDefault = _localization.GetLocalizedString("Default"); dto.LocalizedExternal = _localization.GetLocalizedString("External"); + if (!string.IsNullOrEmpty(dto.Language)) + { + var culture = _localization.FindLanguageInfo(dto.Language); + dto.LocalizedLanguage = culture?.DisplayName; + } + if (dto.Type is MediaStreamType.Subtitle) { dto.LocalizedUndefined = _localization.GetLocalizedString("Undefined"); diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index b1626e2c9..c443af32c 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -260,6 +260,8 @@ namespace MediaBrowser.Model.Entities public string LocalizedHearingImpaired { get; set; } + public string LocalizedLanguage { get; set; } + public string DisplayTitle { get @@ -273,29 +275,8 @@ namespace MediaBrowser.Model.Entities // Do not display the language code in display titles if unset or set to a special code. Show it in all other cases (possibly expanded). if (!string.IsNullOrEmpty(Language) && !_specialCodes.Contains(Language, StringComparison.OrdinalIgnoreCase)) { - // Get full language string i.e. eng -> English, zh-Hans -> Chinese (Simplified). - var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); - CultureInfo match = null; - if (Language.Contains('-', StringComparison.OrdinalIgnoreCase)) - { - match = cultures.FirstOrDefault(r => - r.Name.Equals(Language, StringComparison.OrdinalIgnoreCase)); - - if (match is null) - { - string baseLang = Language.AsSpan().LeftPart('-').ToString(); - match = cultures.FirstOrDefault(r => - r.TwoLetterISOLanguageName.Equals(baseLang, StringComparison.OrdinalIgnoreCase)); - } - } - else - { - match = cultures.FirstOrDefault(r => - r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)); - } - - string fullLanguage = match?.DisplayName; - attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language)); + // Use pre-resolved localized language name, falling back to raw language code. + attributes.Add(StringHelper.FirstToUpper(LocalizedLanguage ?? Language)); } if (!string.IsNullOrEmpty(Profile) && !string.Equals(Profile, "lc", StringComparison.OrdinalIgnoreCase)) @@ -393,29 +374,8 @@ namespace MediaBrowser.Model.Entities if (!string.IsNullOrEmpty(Language)) { - // Get full language string i.e. eng -> English, zh-Hans -> Chinese (Simplified). - var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); - CultureInfo match = null; - if (Language.Contains('-', StringComparison.OrdinalIgnoreCase)) - { - match = cultures.FirstOrDefault(r => - r.Name.Equals(Language, StringComparison.OrdinalIgnoreCase)); - - if (match is null) - { - string baseLang = Language.AsSpan().LeftPart('-').ToString(); - match = cultures.FirstOrDefault(r => - r.TwoLetterISOLanguageName.Equals(baseLang, StringComparison.OrdinalIgnoreCase)); - } - } - else - { - match = cultures.FirstOrDefault(r => - r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)); - } - - string fullLanguage = match?.DisplayName; - attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language)); + // Use pre-resolved localized language name, falling back to raw language code. + attributes.Add(StringHelper.FirstToUpper(LocalizedLanguage ?? Language)); } else { diff --git a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs index f4c0d9fe8..c1a3a4544 100644 --- a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs +++ b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs @@ -108,6 +108,49 @@ namespace Jellyfin.Model.Tests.Entities IsExternal = true }); + // Test LocalizedLanguage is used when set (fixes zh-CN display issue #15935) + data.Add( + "Chinese (Simplified) - SRT", + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = null, + Language = "zh-CN", + LocalizedLanguage = "Chinese (Simplified)", + IsForced = false, + IsDefault = false, + Codec = "SRT" + }); + + // Test LocalizedLanguage for audio streams + data.Add( + "Japanese - AAC - Stereo", + new MediaStream + { + Type = MediaStreamType.Audio, + Title = null, + Language = "jpn", + LocalizedLanguage = "Japanese", + IsForced = false, + IsDefault = false, + Codec = "AAC", + ChannelLayout = "stereo" + }); + + // Test fallback to Language when LocalizedLanguage is null + data.Add( + "Eng - ASS", + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = null, + Language = "eng", + LocalizedLanguage = null, + IsForced = false, + IsDefault = false, + Codec = "ASS" + }); + return data; } -- cgit v1.2.3 From 9e480f6efb4bc0e1f0d1323ed7ed5a7208fded99 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 11 Nov 2025 17:41:46 +0100 Subject: Update to .NET 10.0 --- .editorconfig | 7 ++++ .github/workflows/ci-openapi.yml | 4 +- .vscode/launch.json | 6 +-- Directory.Packages.props | 47 +++++++++------------- Emby.Naming/Emby.Naming.csproj | 2 +- Emby.Photos/Emby.Photos.csproj | 2 +- .../Emby.Server.Implementations.csproj | 3 +- Jellyfin.Api/Helpers/HlsHelpers.cs | 10 +---- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- .../FullSystemBackup/BackupService.cs | 15 +++---- .../Jellyfin.Server.Implementations.csproj | 3 +- .../Extensions/ApiServiceCollectionExtensions.cs | 11 ++--- Jellyfin.Server/Jellyfin.Server.csproj | 5 +-- MediaBrowser.Common/MediaBrowser.Common.csproj | 7 +--- MediaBrowser.Common/Net/NetworkConstants.cs | 1 - MediaBrowser.Common/Net/NetworkUtils.cs | 10 ++--- .../MediaBrowser.Controller.csproj | 4 +- .../MediaBrowser.LocalMetadata.csproj | 2 +- .../MediaBrowser.MediaEncoding.csproj | 3 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 5 +-- MediaBrowser.Model/Net/IPData.cs | 5 +-- .../MediaBrowser.Providers.csproj | 3 +- .../MediaBrowser.XbmcMetadata.csproj | 2 +- README.md | 4 +- .../Emby.Server.Implementations.Fuzz.csproj | 2 +- fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh | 2 +- fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj | 2 +- fuzz/Jellyfin.Api.Fuzz/fuzz.sh | 2 +- global.json | 2 +- .../Jellyfin.Database.Implementations.csproj | 2 +- .../Locking/OptimisticLockBehavior.cs | 2 + .../Locking/PessimisticLockBehavior.cs | 1 + .../Jellyfin.Database.Providers.Sqlite.csproj | 2 +- .../Jellyfin.Drawing.Skia.csproj | 2 +- src/Jellyfin.Drawing/Jellyfin.Drawing.csproj | 2 +- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 2 +- src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj | 3 +- .../Jellyfin.MediaEncoding.Hls.csproj | 5 +-- .../Jellyfin.MediaEncoding.Keyframes.csproj | 6 +-- src/Jellyfin.Networking/Jellyfin.Networking.csproj | 2 +- src/Jellyfin.Networking/Manager/NetworkManager.cs | 21 +++++----- tests/Directory.Build.props | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 1 - .../Jellyfin.LiveTv.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 1 - .../Jellyfin.Server.Tests.csproj | 1 - tests/Jellyfin.Server.Tests/ParseNetworkTests.cs | 7 ++-- 48 files changed, 99 insertions(+), 140 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/.editorconfig b/.editorconfig index 313b02563..fa679f120 100644 --- a/.editorconfig +++ b/.editorconfig @@ -379,6 +379,9 @@ dotnet_diagnostic.CA1720.severity = suggestion # disable warning CA1724: Type names should not match namespaces dotnet_diagnostic.CA1724.severity = suggestion +# disable warning CA1873: Avoid potentially expensive logging +dotnet_diagnostic.CA1873.severity = suggestion + # disable warning CA1805: Do not initialize unnecessarily dotnet_diagnostic.CA1805.severity = suggestion @@ -400,6 +403,10 @@ dotnet_diagnostic.CA1861.severity = suggestion # disable warning CA2000: Dispose objects before losing scope dotnet_diagnostic.CA2000.severity = suggestion +# TODO: Reevaluate when false positives are fixed: https://github.com/dotnet/roslyn-analyzers/issues/7699 +# disable warning CA2025: Do not pass 'IDisposable' instances into unawaited tasks +dotnet_diagnostic.CA2025.severity = suggestion + # disable warning CA2253: Named placeholders should not be numeric values dotnet_diagnostic.CA2253.severity = suggestion diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index 8406d1d2d..cf2a2868d 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -35,7 +35,7 @@ jobs: name: openapi-head retention-days: 14 if-no-files-found: error - path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net9.0/openapi.json + path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json openapi-base: name: OpenAPI - BASE @@ -73,7 +73,7 @@ jobs: name: openapi-base retention-days: 14 if-no-files-found: error - path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net9.0/openapi.json + path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json openapi-diff: permissions: diff --git a/.vscode/launch.json b/.vscode/launch.json index d97d8de84..681f068b9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net9.0/jellyfin.dll", + "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net10.0/jellyfin.dll", "args": [], "cwd": "${workspaceFolder}/Jellyfin.Server", "console": "internalConsole", @@ -22,7 +22,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net9.0/jellyfin.dll", + "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net10.0/jellyfin.dll", "args": ["--nowebclient"], "cwd": "${workspaceFolder}/Jellyfin.Server", "console": "internalConsole", @@ -34,7 +34,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net9.0/jellyfin.dll", + "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net10.0/jellyfin.dll", "args": ["--nowebclient", "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"], "cwd": "${workspaceFolder}/Jellyfin.Server", "console": "internalConsole", diff --git a/Directory.Packages.props b/Directory.Packages.props index 31b46da61..d78e2d021 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -26,32 +26,27 @@ - - + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -81,12 +76,8 @@ - - - - - - + + diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index b84c96116..97b52e42a 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -6,7 +6,7 @@ - net9.0 + net10.0 false true true diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index 645a74aea..3faeae380 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -19,7 +19,7 @@ - net9.0 + net10.0 false true diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 15843730e..f312fb4db 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -27,7 +27,6 @@ - @@ -39,7 +38,7 @@ - net9.0 + net10.0 false true diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index cad8d650e..15540338b 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -45,15 +45,9 @@ public static class HlsHelpers using var reader = new StreamReader(fileStream); var count = 0; - while (!reader.EndOfStream) + string? line; + while ((line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false)) is not null) { - var line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); - if (line is null) - { - // Nothing currently in buffer. - break; - } - if (line.Contains("#EXTINF:", StringComparison.OrdinalIgnoreCase)) { count++; diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 25feaa2d7..3ccf7a746 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -6,7 +6,7 @@ - net9.0 + net10.0 true diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index fd852ece9..f7660f35d 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 false true true diff --git a/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs b/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs index 70483c36c..30094a88c 100644 --- a/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs +++ b/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs @@ -102,7 +102,7 @@ public class BackupService : IBackupService } BackupManifest? manifest; - var manifestStream = zipArchiveEntry.Open(); + var manifestStream = await zipArchiveEntry.OpenAsync().ConfigureAwait(false); await using (manifestStream.ConfigureAwait(false)) { manifest = await JsonSerializer.DeserializeAsync(manifestStream, _serializerSettings).ConfigureAwait(false); @@ -160,7 +160,7 @@ public class BackupService : IBackupService } HistoryRow[] historyEntries; - var historyArchive = historyEntry.Open(); + var historyArchive = await historyEntry.OpenAsync().ConfigureAwait(false); await using (historyArchive.ConfigureAwait(false)) { historyEntries = await JsonSerializer.DeserializeAsync(historyArchive).ConfigureAwait(false) ?? @@ -204,7 +204,7 @@ public class BackupService : IBackupService continue; } - var zipEntryStream = zipEntry.Open(); + var zipEntryStream = await zipEntry.OpenAsync().ConfigureAwait(false); await using (zipEntryStream.ConfigureAwait(false)) { _logger.LogInformation("Restore backup of {Table}", entityType.Type.Name); @@ -329,7 +329,7 @@ public class BackupService : IBackupService _logger.LogInformation("Begin backup of entity {Table}", entityType.SourceName); var zipEntry = zipArchive.CreateEntry(NormalizePathSeparator(Path.Combine("Database", $"{entityType.SourceName}.json"))); var entities = 0; - var zipEntryStream = zipEntry.Open(); + var zipEntryStream = await zipEntry.OpenAsync().ConfigureAwait(false); await using (zipEntryStream.ConfigureAwait(false)) { var jsonSerializer = new Utf8JsonWriter(zipEntryStream); @@ -366,7 +366,7 @@ public class BackupService : IBackupService foreach (var item in Directory.EnumerateFiles(_applicationPaths.ConfigurationDirectoryPath, "*.xml", SearchOption.TopDirectoryOnly) .Union(Directory.EnumerateFiles(_applicationPaths.ConfigurationDirectoryPath, "*.json", SearchOption.TopDirectoryOnly))) { - zipArchive.CreateEntryFromFile(item, NormalizePathSeparator(Path.Combine("Config", Path.GetFileName(item)))); + await zipArchive.CreateEntryFromFileAsync(item, NormalizePathSeparator(Path.Combine("Config", Path.GetFileName(item)))).ConfigureAwait(false); } void CopyDirectory(string source, string target, string filter = "*") @@ -380,6 +380,7 @@ public class BackupService : IBackupService foreach (var item in Directory.EnumerateFiles(source, filter, SearchOption.AllDirectories)) { + // TODO: @bond make async zipArchive.CreateEntryFromFile(item, NormalizePathSeparator(Path.Combine(target, Path.GetRelativePath(source, item)))); } } @@ -405,7 +406,7 @@ public class BackupService : IBackupService CopyDirectory(Path.Combine(_applicationPaths.InternalMetadataPath), Path.Combine("Data", "metadata")); } - var manifestStream = zipArchive.CreateEntry(ManifestEntryName).Open(); + var manifestStream = await zipArchive.CreateEntry(ManifestEntryName).OpenAsync().ConfigureAwait(false); await using (manifestStream.ConfigureAwait(false)) { await JsonSerializer.SerializeAsync(manifestStream, manifest).ConfigureAwait(false); @@ -505,7 +506,7 @@ public class BackupService : IBackupService return null; } - var manifestStream = manifestEntry.Open(); + var manifestStream = await manifestEntry.OpenAsync().ConfigureAwait(false); await using (manifestStream.ConfigureAwait(false)) { return await JsonSerializer.DeserializeAsync(manifestStream, _serializerSettings).ConfigureAwait(false); diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 6693ab8db..4f0c37722 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 false true @@ -27,7 +27,6 @@ - diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 8373fd50f..c7bcda442 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -174,7 +174,7 @@ namespace Jellyfin.Server.Extensions if (config.KnownProxies.Length == 0) { options.ForwardedHeaders = ForwardedHeaders.None; - options.KnownNetworks.Clear(); + options.KnownIPNetworks.Clear(); options.KnownProxies.Clear(); } else @@ -184,7 +184,7 @@ namespace Jellyfin.Server.Extensions } // Only set forward limit if we have some known proxies or some known networks. - if (options.KnownProxies.Count != 0 || options.KnownNetworks.Count != 0) + if (options.KnownProxies.Count != 0 || options.KnownIPNetworks.Count != 0) { options.ForwardLimit = null; } @@ -290,10 +290,7 @@ namespace Jellyfin.Server.Extensions } else if (NetworkUtils.TryParseToSubnet(allowedProxies[i], out var subnet)) { - if (subnet is not null) - { - AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength); - } + AddIPAddress(config, options, subnet.BaseAddress, subnet.PrefixLength); } else if (NetworkUtils.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6)) { @@ -323,7 +320,7 @@ namespace Jellyfin.Server.Extensions } else { - options.KnownNetworks.Add(new Microsoft.AspNetCore.HttpOverrides.IPNetwork(addr, prefixLength)); + options.KnownIPNetworks.Add(new System.Net.IPNetwork(addr, prefixLength)); } } diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 14ab114fb..9f5bf01a0 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -8,7 +8,7 @@ jellyfin Exe - net9.0 + net10.0 false false true @@ -44,9 +44,6 @@ - - - diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 5f15f845c..c128c2b6b 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -18,17 +18,12 @@ - - - - - - net9.0 + net10.0 false true true diff --git a/MediaBrowser.Common/Net/NetworkConstants.cs b/MediaBrowser.Common/Net/NetworkConstants.cs index ccef5d271..cec996a1a 100644 --- a/MediaBrowser.Common/Net/NetworkConstants.cs +++ b/MediaBrowser.Common/Net/NetworkConstants.cs @@ -1,5 +1,4 @@ using System.Net; -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; namespace MediaBrowser.Common.Net; diff --git a/MediaBrowser.Common/Net/NetworkUtils.cs b/MediaBrowser.Common/Net/NetworkUtils.cs index 24ed47a81..9c9a35a16 100644 --- a/MediaBrowser.Common/Net/NetworkUtils.cs +++ b/MediaBrowser.Common/Net/NetworkUtils.cs @@ -6,7 +6,6 @@ using System.Net; using System.Net.Sockets; using System.Text.RegularExpressions; using Jellyfin.Extensions; -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; namespace MediaBrowser.Common.Net; @@ -196,7 +195,7 @@ public static partial class NetworkUtils /// An . /// Boolean signaling if negated or not negated values should be parsed. /// True if parsing was successful. - public static bool TryParseToSubnet(ReadOnlySpan value, [NotNullWhen(true)] out IPNetwork? result, bool negated = false) + public static bool TryParseToSubnet(ReadOnlySpan value, out IPNetwork result, bool negated = false) { // If multiple IP addresses are in a comma-separated string, the individual addresses may contain leading and/or trailing whitespace value = value.Trim(); @@ -210,7 +209,7 @@ public static partial class NetworkUtils if (isAddressNegated != negated) { - result = null; + result = default; return false; } @@ -235,7 +234,7 @@ public static partial class NetworkUtils } } - result = null; + result = default; return false; } @@ -330,7 +329,7 @@ public static partial class NetworkUtils /// The broadcast address. public static IPAddress GetBroadcastAddress(IPNetwork network) { - var addressBytes = network.Prefix.GetAddressBytes(); + var addressBytes = network.BaseAddress.GetAddressBytes(); uint ipAddress = BitConverter.ToUInt32(addressBytes, 0); uint ipMaskV4 = BitConverter.ToUInt32(CidrToMask(network.PrefixLength, AddressFamily.InterNetwork).GetAddressBytes(), 0); uint broadCastIPAddress = ipAddress | ~ipMaskV4; @@ -347,7 +346,6 @@ public static partial class NetworkUtils public static bool SubnetContainsAddress(IPNetwork network, IPAddress address) { ArgumentNullException.ThrowIfNull(address); - ArgumentNullException.ThrowIfNull(network); if (address.IsIPv4MappedToIPv6) { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index b5d14e94b..0025080cc 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -19,9 +19,7 @@ - - @@ -36,7 +34,7 @@ - net9.0 + net10.0 false true true diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 8e3c8cf7f..c3c26085c 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -11,7 +11,7 @@ - net9.0 + net10.0 false true diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index be7eeda92..fc11047a7 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -6,7 +6,7 @@ - net9.0 + net10.0 false true @@ -26,7 +26,6 @@ - diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index ef025d02d..c655c4ccb 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -14,7 +14,7 @@ - net9.0 + net10.0 false true true @@ -37,13 +37,10 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive - - diff --git a/MediaBrowser.Model/Net/IPData.cs b/MediaBrowser.Model/Net/IPData.cs index c116d883e..e016ffea1 100644 --- a/MediaBrowser.Model/Net/IPData.cs +++ b/MediaBrowser.Model/Net/IPData.cs @@ -1,6 +1,5 @@ using System.Net; using System.Net.Sockets; -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; namespace MediaBrowser.Model.Net; @@ -66,9 +65,9 @@ public class IPData { if (Address.Equals(IPAddress.None)) { - return Subnet.Prefix.AddressFamily.Equals(IPAddress.None) + return Subnet.BaseAddress.AddressFamily.Equals(IPAddress.None) ? AddressFamily.Unspecified - : Subnet.Prefix.AddressFamily; + : Subnet.BaseAddress.AddressFamily; } else { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 34b3104b0..ed0c63b97 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -18,7 +18,6 @@ - @@ -28,7 +27,7 @@ - net9.0 + net10.0 false true diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index b195af96c..cfb3533f3 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -15,7 +15,7 @@ - net9.0 + net10.0 false true diff --git a/README.md b/README.md index 9830e8e9c..e546e7f11 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ --- -Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. It is an alternative to the proprietary Emby and Plex, to provide media from a dedicated server to end-user devices via multiple apps. Jellyfin is descended from Emby's 3.5.2 release and ported to the .NET platform to enable full cross-platform support. +Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. It is an alternative to the proprietary Emby and Plex, to provide media from a dedicated server to end-user devices via multiple apps. Jellyfin is descended from Emby's 3.5.2 release and ported to the .NET platform to enable full cross-platform support. There are no strings attached, no premium licenses or features, and no hidden agendas: just a team that wants to build something better and work together to achieve it. We welcome anyone who is interested in joining us in our quest! @@ -133,7 +133,7 @@ A second option is to build the project and then run the resulting executable fi ```bash dotnet build # Build the project -cd Jellyfin.Server/bin/Debug/net9.0 # Change into the build output directory +cd Jellyfin.Server/bin/Debug/net10.0 # Change into the build output directory ``` 2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`. 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 1373d2fe0..1ac7402f9 100644 --- a/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj +++ b/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 diff --git a/fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh b/fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh index 8183bb37a..771aa6677 100755 --- a/fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh +++ b/fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh @@ -8,4 +8,4 @@ cp bin/Emby.Server.Implementations.dll . dotnet build mkdir -p Findings -AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net9.0/Emby.Server.Implementations.Fuzz "$1" +AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net10.0/Emby.Server.Implementations.Fuzz "$1" diff --git a/fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj b/fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj index 04c7be11d..dad2f8e4e 100644 --- a/fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj +++ b/fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 diff --git a/fuzz/Jellyfin.Api.Fuzz/fuzz.sh b/fuzz/Jellyfin.Api.Fuzz/fuzz.sh index 15148e1bb..537de905d 100755 --- a/fuzz/Jellyfin.Api.Fuzz/fuzz.sh +++ b/fuzz/Jellyfin.Api.Fuzz/fuzz.sh @@ -8,4 +8,4 @@ cp bin/Jellyfin.Api.dll . dotnet build mkdir -p Findings -AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net9.0/Jellyfin.Api.Fuzz "$1" +AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net10.0/Jellyfin.Api.Fuzz "$1" diff --git a/global.json b/global.json index 2e13a6387..867a4cfa0 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.0", + "version": "10.0.0", "rollForward": "latestMinor" } } diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj index 28c4972d2..0b29a71cb 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 false true diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs index 7bcc7eeca..76ffa5a9e 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1873 + using System; using System.Data.Common; using System.Linq; diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs index 2d6bc6902..404292e8e 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs @@ -1,5 +1,6 @@ #pragma warning disable MT1013 // Releasing lock without guarantee of execution #pragma warning disable MT1012 // Acquiring lock without guarantee of releasing +#pragma warning disable CA1873 using System; using System.Data; diff --git a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj index 03e5fc495..aeee52701 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj +++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 false true diff --git a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index ba402dfe0..f7c20463f 100644 --- a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -6,7 +6,7 @@ - net9.0 + net10.0 false true diff --git a/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj index 5f4b3fe8d..a442f7457 100644 --- a/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj +++ b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj @@ -6,7 +6,7 @@ - net9.0 + net10.0 false true diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index f52fd014d..9a7cf4aab 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 false true true diff --git a/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj b/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj index f04c02504..575441de9 100644 --- a/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj +++ b/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj @@ -1,6 +1,6 @@ - net9.0 + net10.0 true @@ -13,7 +13,6 @@ - diff --git a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj index 80b5aa84e..902f51376 100644 --- a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj +++ b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 true @@ -12,9 +12,6 @@ - - - diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj index cc8d942eb..5e7e2090c 100644 --- a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj +++ b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 true @@ -22,10 +22,6 @@ - - - - <_Parameter1>Jellyfin.MediaEncoding.Keyframes.Tests diff --git a/src/Jellyfin.Networking/Jellyfin.Networking.csproj b/src/Jellyfin.Networking/Jellyfin.Networking.csproj index 1a146549d..36b9581a7 100644 --- a/src/Jellyfin.Networking/Jellyfin.Networking.csproj +++ b/src/Jellyfin.Networking/Jellyfin.Networking.csproj @@ -1,6 +1,6 @@ - net9.0 + net10.0 false true diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs index 126d9f15c..ed7b6dfde 100644 --- a/src/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs @@ -16,7 +16,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager; -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; namespace Jellyfin.Networking.Manager; @@ -376,7 +375,7 @@ public class NetworkManager : INetworkManager, IDisposable if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0])) { var bindAddresses = localNetworkAddresses.Select(p => NetworkUtils.TryParseToSubnet(p, out var network) - ? network.Prefix + ? network.BaseAddress : (interfaces.Where(x => x.Name.Equals(p, StringComparison.OrdinalIgnoreCase)) .Select(x => x.Address) .FirstOrDefault() ?? IPAddress.None)) @@ -545,7 +544,7 @@ public class NetworkManager : INetworkManager, IDisposable { foreach (var lan in _lanSubnets) { - var lanPrefix = lan.Prefix; + var lanPrefix = lan.BaseAddress; publishedServerUrls.Add( new PublishedServerUriOverride( new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)), @@ -554,9 +553,9 @@ public class NetworkManager : INetworkManager, IDisposable false)); } } - else if (NetworkUtils.TryParseToSubnet(identifier, out var result) && result is not null) + else if (NetworkUtils.TryParseToSubnet(identifier, out var result)) { - var data = new IPData(result.Prefix, result); + var data = new IPData(result.BaseAddress, result); publishedServerUrls.Add( new PublishedServerUriOverride( data, @@ -623,7 +622,7 @@ public class NetworkManager : INetworkManager, IDisposable var parts = details.Split(','); if (NetworkUtils.TryParseToSubnet(parts[0], out var subnet)) { - var address = subnet.Prefix; + var address = subnet.BaseAddress; var index = int.Parse(parts[1], CultureInfo.InvariantCulture); if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6) { @@ -920,7 +919,7 @@ public class NetworkManager : INetworkManager, IDisposable { if (NetworkUtils.TryParseToSubnet(address, out var subnet)) { - return IsInLocalNetwork(subnet.Prefix); + return IsInLocalNetwork(subnet.BaseAddress); } return NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled) @@ -1171,13 +1170,13 @@ public class NetworkManager : INetworkManager, IDisposable var logLevel = debug ? LogLevel.Debug : LogLevel.Information; if (_logger.IsEnabled(logLevel)) { - _logger.Log(logLevel, "Defined LAN subnets: {Subnets}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength)); - _logger.Log(logLevel, "Defined LAN exclusions: {Subnets}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength)); - _logger.Log(logLevel, "Used LAN subnets: {Subnets}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength)); + _logger.Log(logLevel, "Defined LAN subnets: {Subnets}", _lanSubnets.Select(s => s.BaseAddress + "/" + s.PrefixLength)); + _logger.Log(logLevel, "Defined LAN exclusions: {Subnets}", _excludedSubnets.Select(s => s.BaseAddress + "/" + s.PrefixLength)); + _logger.Log(logLevel, "Used LAN subnets: {Subnets}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.BaseAddress + "/" + s.PrefixLength)); _logger.Log(logLevel, "Filtered interface addresses: {Addresses}", _interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address)); _logger.Log(logLevel, "Bind Addresses {Addresses}", GetAllBindInterfaces(false).OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address)); _logger.Log(logLevel, "Remote IP filter is {Type}", config.IsRemoteIPFilterBlacklist ? "Blocklist" : "Allowlist"); - _logger.Log(logLevel, "Filtered subnets: {Subnets}", _remoteAddressFilter.Select(s => s.Prefix + "/" + s.PrefixLength)); + _logger.Log(logLevel, "Filtered subnets: {Subnets}", _remoteAddressFilter.Select(s => s.BaseAddress + "/" + s.PrefixLength)); } } } diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 6b851021f..feec35307 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -4,7 +4,7 @@ - net9.0 + net10.0 false diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 015018910..6b84c4438 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -10,7 +10,6 @@ - diff --git a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj index fdcf7d61e..bdf6bc383 100644 --- a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj +++ b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj @@ -1,6 +1,6 @@ - net9.0 + net10.0 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 8228c0df7..7b0e23788 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -5,7 +5,6 @@ - diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 5fea805ae..21596e0ed 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -5,7 +5,6 @@ - diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs index 123266d29..14f4c33b6 100644 --- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs +++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs @@ -11,7 +11,6 @@ using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager; -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; namespace Jellyfin.Server.Tests { @@ -87,7 +86,7 @@ namespace Jellyfin.Server.Tests // Need this here as ::1 and 127.0.0.1 are in them by default. options.KnownProxies.Clear(); - options.KnownNetworks.Clear(); + options.KnownIPNetworks.Clear(); ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList, options); @@ -97,10 +96,10 @@ namespace Jellyfin.Server.Tests Assert.True(options.KnownProxies.Contains(item)); } - Assert.Equal(knownNetworks.Length, options.KnownNetworks.Count); + Assert.Equal(knownNetworks.Length, options.KnownIPNetworks.Count); foreach (var item in knownNetworks) { - Assert.NotNull(options.KnownNetworks.FirstOrDefault(x => x.Prefix.Equals(item.Prefix) && x.PrefixLength == item.PrefixLength)); + Assert.NotEqual(default, options.KnownIPNetworks.FirstOrDefault(x => x.BaseAddress.Equals(item.BaseAddress) && x.PrefixLength == item.PrefixLength)); } } -- cgit v1.2.3 From 8d052a6cb17421e6c7774bafc6957029f3c9d4ae Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sun, 18 Jan 2026 12:17:06 +0100 Subject: Merge pull request #15926 from abitofevrything/feat/accurate_hls_seeking Refactor HLS transcode seeking --- Jellyfin.Api/Controllers/AudioController.cs | 6 ---- Jellyfin.Api/Controllers/DynamicHlsController.cs | 36 ---------------------- .../Controllers/UniversalAudioController.cs | 8 +---- Jellyfin.Api/Controllers/VideosController.cs | 6 ---- Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +- .../MediaEncoding/BaseEncodingJobOptions.cs | 2 -- .../MediaEncoding/EncodingHelper.cs | 35 ++++++++++++--------- .../MediaEncoding/EncodingJobInfo.cs | 15 --------- .../Transcoding/TranscodeManager.cs | 2 +- .../Configuration/EncodingOptions.cs | 8 +++++ .../Configuration/HlsAudioSeekStrategy.cs | 23 ++++++++++++++ MediaBrowser.Model/Dlna/StreamBuilder.cs | 1 - MediaBrowser.Model/Dlna/StreamInfo.cs | 8 ----- MediaBrowser.Model/Dlna/TranscodingProfile.cs | 4 +-- .../Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs | 2 -- .../Test Data/DeviceProfile-AndroidPixel.json | 3 -- .../DeviceProfile-AndroidTVExoPlayer.json | 2 -- .../Test Data/DeviceProfile-Chrome-NoHLS.json | 11 ------- .../Test Data/DeviceProfile-Chrome.json | 7 ++--- .../Test Data/DeviceProfile-Firefox.json | 3 -- .../DeviceProfile-JellyfinMediaPlayer.json | 3 -- .../Test Data/DeviceProfile-LowBandwidth.json | 3 -- .../Test Data/DeviceProfile-RokuSSPlus.json | 6 ---- .../Test Data/DeviceProfile-RokuSSPlusNext.json | 6 ---- .../Test Data/DeviceProfile-SafariNext.json | 7 ++--- .../Test Data/DeviceProfile-Tizen3-stereo.json | 13 -------- .../Test Data/DeviceProfile-Tizen4-4K-5.1.json | 13 -------- .../Test Data/DeviceProfile-TranscodeMedia.json | 4 --- .../Test Data/DeviceProfile-WebOS-23.json | 4 +-- .../Test Data/DeviceProfile-Yatse.json | 3 -- .../Test Data/DeviceProfile-Yatse2.json | 3 -- 31 files changed, 61 insertions(+), 188 deletions(-) create mode 100644 MediaBrowser.Model/Configuration/HlsAudioSeekStrategy.cs (limited to 'MediaBrowser.Model') diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index e334e1264..4be79ff5a 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -50,7 +50,6 @@ public class AudioController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. @@ -107,7 +106,6 @@ public class AudioController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? audioBitRate, @@ -159,7 +157,6 @@ public class AudioController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate, @@ -217,7 +214,6 @@ public class AudioController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. @@ -274,7 +270,6 @@ public class AudioController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? audioBitRate, @@ -326,7 +321,6 @@ public class AudioController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate, diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 1e3e2740f..15b04051f 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -122,7 +122,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. @@ -182,7 +181,6 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? audioBitRate, @@ -238,7 +236,6 @@ public class DynamicHlsController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate, @@ -364,7 +361,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. @@ -425,7 +421,6 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? audioBitRate, @@ -481,7 +476,6 @@ public class DynamicHlsController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate, @@ -543,7 +537,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. The maximum streaming bitrate. @@ -601,7 +594,6 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? maxStreamingBitrate, @@ -654,7 +646,6 @@ public class DynamicHlsController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate ?? maxStreamingBitrate, @@ -713,7 +704,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. @@ -771,7 +761,6 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? audioBitRate, @@ -826,7 +815,6 @@ public class DynamicHlsController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate, @@ -887,7 +875,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. The maximum streaming bitrate. @@ -943,7 +930,6 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? maxStreamingBitrate, @@ -996,7 +982,6 @@ public class DynamicHlsController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate ?? maxStreamingBitrate, @@ -1060,7 +1045,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. @@ -1124,7 +1108,6 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? audioBitRate, @@ -1181,7 +1164,6 @@ public class DynamicHlsController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate, @@ -1247,7 +1229,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. The maximum streaming bitrate. @@ -1309,7 +1290,6 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? maxStreamingBitrate, @@ -1364,7 +1344,6 @@ public class DynamicHlsController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate ?? maxStreamingBitrate, @@ -1586,16 +1565,6 @@ public class DynamicHlsController : BaseJellyfinApiController var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); var threads = EncodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec); - if (state.BaseRequest.BreakOnNonKeyFrames) - { - // FIXME: this is actually a workaround, as ideally it really should be the client which decides whether non-keyframe - // breakpoints are supported; but current implementation always uses "ffmpeg input seeking" which is liable - // to produce a missing part of video stream before first keyframe is encountered, which may lead to - // awkward cases like a few starting HLS segments having no video whatsoever, which breaks hls.js - _logger.LogInformation("Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request"); - state.BaseRequest.BreakOnNonKeyFrames = false; - } - var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty; var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); @@ -1746,11 +1715,6 @@ public class DynamicHlsController : BaseJellyfinApiController var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs; - if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec)) - { - return copyArgs + " -copypriorss:a:0 0"; - } - return copyArgs; } diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index fd6334703..b1a91ae70 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -83,7 +83,6 @@ public class UniversalAudioController : BaseJellyfinApiController /// Optional. The maximum audio bit depth. /// Optional. Whether to enable remote media. /// Optional. Whether to enable Audio Encoding. - /// Optional. Whether to break on non key frames. /// Whether to enable redirection. Defaults to true. /// Audio stream returned. /// Redirected to remote audio stream. @@ -114,7 +113,6 @@ public class UniversalAudioController : BaseJellyfinApiController [FromQuery] int? maxAudioBitDepth, [FromQuery] bool? enableRemoteMedia, [FromQuery] bool enableAudioVbrEncoding = true, - [FromQuery] bool breakOnNonKeyFrames = false, [FromQuery] bool enableRedirection = true) { userId = RequestHelpers.GetUserId(User, userId); @@ -127,7 +125,7 @@ public class UniversalAudioController : BaseJellyfinApiController return NotFound(); } - var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels); + var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels); _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile); @@ -208,7 +206,6 @@ public class UniversalAudioController : BaseJellyfinApiController EnableAutoStreamCopy = true, AllowAudioStreamCopy = true, AllowVideoStreamCopy = true, - BreakOnNonKeyFrames = breakOnNonKeyFrames, AudioSampleRate = maxAudioSampleRate, MaxAudioChannels = maxAudioChannels, MaxAudioBitDepth = maxAudioBitDepth, @@ -242,7 +239,6 @@ public class UniversalAudioController : BaseJellyfinApiController EnableAutoStreamCopy = true, AllowAudioStreamCopy = true, AllowVideoStreamCopy = true, - BreakOnNonKeyFrames = breakOnNonKeyFrames, AudioSampleRate = maxAudioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = isStatic ? null : (audioBitRate ?? maxStreamingBitrate), @@ -263,7 +259,6 @@ public class UniversalAudioController : BaseJellyfinApiController string? transcodingContainer, string? audioCodec, MediaStreamProtocol? transcodingProtocol, - bool? breakOnNonKeyFrames, int? transcodingAudioChannels, int? maxAudioSampleRate, int? maxAudioBitDepth, @@ -298,7 +293,6 @@ public class UniversalAudioController : BaseJellyfinApiController Container = transcodingContainer ?? "mp3", AudioCodec = audioCodec ?? "mp3", Protocol = transcodingProtocol ?? MediaStreamProtocol.http, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture) } }; diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index e7c6f23ce..ccf8e9063 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -270,7 +270,6 @@ public class VideosController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. @@ -329,7 +328,6 @@ public class VideosController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? audioBitRate, @@ -386,7 +384,6 @@ public class VideosController : BaseJellyfinApiController EnableAutoStreamCopy = enableAutoStreamCopy ?? true, AllowAudioStreamCopy = allowAudioStreamCopy ?? true, AllowVideoStreamCopy = allowVideoStreamCopy ?? true, - BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, AudioBitRate = audioBitRate, @@ -511,7 +508,6 @@ public class VideosController : BaseJellyfinApiController /// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true. /// Whether or not to allow copying of the video stream url. /// Whether or not to allow copying of the audio stream url. - /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. @@ -570,7 +566,6 @@ public class VideosController : BaseJellyfinApiController [FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowAudioStreamCopy, - [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] int? audioBitRate, @@ -624,7 +619,6 @@ public class VideosController : BaseJellyfinApiController enableAutoStreamCopy, allowVideoStreamCopy, allowAudioStreamCopy, - breakOnNonKeyFrames, audioSampleRate, maxAudioBitDepth, audioBitRate, diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index b3f5b9a80..1e984542e 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -201,7 +201,7 @@ public static class StreamingHelpers state.OutputVideoCodec = state.Request.VideoCodec; state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); - encodingHelper.TryStreamCopy(state); + encodingHelper.TryStreamCopy(state, encodingOptions); if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue) { diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs index 20f51ddb7..10f2f04af 100644 --- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs @@ -43,8 +43,6 @@ namespace MediaBrowser.Controller.MediaEncoding public bool AllowAudioStreamCopy { get; set; } - public bool BreakOnNonKeyFrames { get; set; } - /// /// Gets or sets the audio sample rate. /// diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 91d88dc08..11eee1a37 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2914,8 +2914,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (time > 0) { - // For direct streaming/remuxing, we seek at the exact position of the keyframe - // However, ffmpeg will seek to previous keyframe when the exact time is the input + // For direct streaming/remuxing, HLS segments start at keyframes. + // However, ffmpeg will seek to previous keyframe when the exact frame time is the input // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos. // This will help subtitle syncing. var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCodec(state.OutputVideoCodec); @@ -2932,17 +2932,16 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.IsVideoRequest) { - var outputVideoCodec = GetVideoEncoder(state, options); - var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.'); - - // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking - // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients, - // but it's still required for fMP4 container otherwise the audio can't be synced to the video. - if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) - && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase) - && state.TranscodingType != TranscodingJobType.Progressive - && !state.EnableBreakOnNonKeyFrames(outputVideoCodec) - && (state.BaseRequest.StartTimeTicks ?? 0) > 0) + // If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the nearest + // keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking to + // avoid A/V sync issues which cause playback issues on some devices. + // When remuxing video, the segment start times correspond to key frames in the source stream, so this + // option shouldn't change the seeked point that much. + // Important: make sure not to use it with wtv because it breaks seeking + if (state.TranscodingType is TranscodingJobType.Hls + && string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase) + && (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec)) + && !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)) { seekParam += " -noaccurate_seek"; } @@ -7084,7 +7083,7 @@ namespace MediaBrowser.Controller.MediaEncoding } #nullable disable - public void TryStreamCopy(EncodingJobInfo state) + public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options) { if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream)) { @@ -7101,8 +7100,14 @@ namespace MediaBrowser.Controller.MediaEncoding } } + var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls + && state.VideoStream is not null + && !IsCopyCodec(state.OutputVideoCodec) + && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio; + if (state.AudioStream is not null - && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)) + && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs) + && !preventHlsAudioCopy) { state.OutputAudioCodec = "copy"; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 43680f5c0..7d0384ef2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -515,21 +515,6 @@ namespace MediaBrowser.Controller.MediaEncoding public int HlsListSize => 0; - public bool EnableBreakOnNonKeyFrames(string videoCodec) - { - if (TranscodingType != TranscodingJobType.Progressive) - { - if (IsSegmentedLiveStream) - { - return false; - } - - return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec); - } - - return false; - } - private int? GetMediaStreamCount(MediaStreamType type, int limit) { var count = MediaSource.GetStreamCount(type); diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index 2fd054f11..defd855ec 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -673,7 +673,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable if (state.VideoRequest is not null) { - _encodingHelper.TryStreamCopy(state); + _encodingHelper.TryStreamCopy(state, encodingOptions); } } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index f7f386d28..98fc2e632 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -1,6 +1,7 @@ #pragma warning disable CA1819 // XML serialization handles collections improperly, so we need to use arrays #nullable disable +using System.ComponentModel; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Configuration; @@ -60,6 +61,7 @@ public class EncodingOptions SubtitleExtractionTimeoutMinutes = 30; AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = ["mkv"]; HardwareDecodingCodecs = ["h264", "vc1"]; + HlsAudioSeekStrategy = HlsAudioSeekStrategy.DisableAccurateSeek; } /// @@ -301,4 +303,10 @@ public class EncodingOptions /// Gets or sets the file extensions on-demand metadata based keyframe extraction is enabled for. /// public string[] AllowOnDemandMetadataBasedKeyframeExtractionForExtensions { get; set; } + + /// + /// Gets or sets the method used for audio seeking in HLS. + /// + [DefaultValue(HlsAudioSeekStrategy.DisableAccurateSeek)] + public HlsAudioSeekStrategy HlsAudioSeekStrategy { get; set; } } diff --git a/MediaBrowser.Model/Configuration/HlsAudioSeekStrategy.cs b/MediaBrowser.Model/Configuration/HlsAudioSeekStrategy.cs new file mode 100644 index 000000000..49feeb435 --- /dev/null +++ b/MediaBrowser.Model/Configuration/HlsAudioSeekStrategy.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Model.Configuration +{ + /// + /// An enum representing the options to seek the input audio stream when + /// transcoding HLS segments. + /// + public enum HlsAudioSeekStrategy + { + /// + /// If the video stream is transcoded and the audio stream is copied, + /// seek the video stream to the same keyframe as the audio stream. The + /// resulting timestamps in the output streams may be inaccurate. + /// + DisableAccurateSeek = 0, + + /// + /// Prevent audio streams from being copied if the video stream is transcoded. + /// The resulting timestamps will be accurate, but additional audio transcoding + /// overhead will be incurred. + /// + TranscodeAudio = 1, + } +} diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 61e04a813..42cb208d0 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -610,7 +610,6 @@ namespace MediaBrowser.Model.Dlna playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; - playlistItem.BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames; playlistItem.EnableAudioVbrEncoding = transcodingProfile.EnableAudioVbrEncoding; if (transcodingProfile.MinSegments > 0) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 92404de50..9edb4115c 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -86,11 +86,6 @@ public class StreamInfo /// The minimum segments count. public int? MinSegments { get; set; } - /// - /// Gets or sets a value indicating whether the stream can be broken on non-keyframes. - /// - public bool BreakOnNonKeyFrames { get; set; } - /// /// Gets or sets a value indicating whether the stream requires AVC. /// @@ -1018,9 +1013,6 @@ public class StreamInfo sb.Append("&MinSegments="); sb.Append(MinSegments.Value.ToString(CultureInfo.InvariantCulture)); } - - sb.Append("&BreakOnNonKeyFrames="); - sb.Append(BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)); } else { diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 5797d4250..f49b24976 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -41,7 +41,6 @@ public class TranscodingProfile MaxAudioChannels = other.MaxAudioChannels; MinSegments = other.MinSegments; SegmentLength = other.SegmentLength; - BreakOnNonKeyFrames = other.BreakOnNonKeyFrames; Conditions = other.Conditions; EnableAudioVbrEncoding = other.EnableAudioVbrEncoding; } @@ -143,7 +142,8 @@ public class TranscodingProfile /// [DefaultValue(false)] [XmlAttribute("breakOnNonKeyFrames")] - public bool BreakOnNonKeyFrames { get; set; } + [Obsolete("This is always false")] + public bool? BreakOnNonKeyFrames { get; set; } /// /// Gets or sets the profile conditions. diff --git a/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs b/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs index e32baef55..6436d7d0e 100644 --- a/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs +++ b/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs @@ -134,8 +134,6 @@ public class LegacyStreamInfo : StreamInfo { list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture))); } - - list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture))); } else { diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json index 68ce3ea4a..643ff2638 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json @@ -152,7 +152,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -169,7 +168,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -185,7 +183,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json index 3d3968268..44f63f384 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json @@ -130,7 +130,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -146,7 +145,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json index 5d1f5f162..f1fc9e0db 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json @@ -127,7 +127,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -144,7 +143,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -161,7 +159,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -178,7 +175,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -195,7 +191,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -212,7 +207,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -229,7 +223,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -246,7 +239,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -263,7 +255,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -281,7 +272,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -298,7 +288,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json index e2f75b569..7e37a6236 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json @@ -107,7 +107,6 @@ "Protocol": "hls", "MaxAudioChannels": "2", "MinSegments": "2", - "BreakOnNonKeyFrames": true, "EnableAudioVbrEncoding": true }, { @@ -182,8 +181,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "2", - "MinSegments": "2", - "BreakOnNonKeyFrames": true + "MinSegments": "2" }, { "Container": "ts", @@ -193,8 +191,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "2", - "MinSegments": "2", - "BreakOnNonKeyFrames": true + "MinSegments": "2" } ], "ContainerProfiles": [], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json index 21ae7e5cb..4380d80ef 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json @@ -95,7 +95,6 @@ "TranscodingProfiles": [ { "AudioCodec": "aac", - "BreakOnNonKeyFrames": true, "Container": "mp4", "Context": "Streaming", "EnableAudioVbrEncoding": true, @@ -170,7 +169,6 @@ }, { "AudioCodec": "aac,mp2,opus,flac", - "BreakOnNonKeyFrames": true, "Container": "mp4", "Context": "Streaming", "MaxAudioChannels": "2", @@ -181,7 +179,6 @@ }, { "AudioCodec": "aac,mp3,mp2", - "BreakOnNonKeyFrames": true, "Container": "ts", "Context": "Streaming", "MaxAudioChannels": "2", diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json index da9a1a4ad..cca1c16ee 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json @@ -30,7 +30,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -48,7 +47,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -62,7 +60,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json index 82b73fb0f..b7cd170b9 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json @@ -30,7 +30,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -48,7 +47,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -62,7 +60,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json index 37b923558..b823ac4b8 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json @@ -49,7 +49,6 @@ "MaxAudioChannels": " 2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -66,7 +65,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -83,7 +81,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -100,7 +97,6 @@ "MaxAudioChannels": " 2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -118,7 +114,6 @@ "MaxAudioChannels": " 2", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -135,7 +130,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json index 542bf6370..708ff73c4 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json @@ -49,7 +49,6 @@ "MaxAudioChannels": " 2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -66,7 +65,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -83,7 +81,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -100,7 +97,6 @@ "MaxAudioChannels": " 2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -118,7 +114,6 @@ "MaxAudioChannels": " 2", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -135,7 +130,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json index f61d0e36b..10382fa82 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json @@ -114,7 +114,6 @@ "Protocol": "hls", "MaxAudioChannels": "6", "MinSegments": "2", - "BreakOnNonKeyFrames": true, "EnableAudioVbrEncoding": true }, { @@ -173,8 +172,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "2", - "MinSegments": "2", - "BreakOnNonKeyFrames": true + "MinSegments": "2" }, { "Container": "ts", @@ -184,8 +182,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "2", - "MinSegments": "2", - "BreakOnNonKeyFrames": true + "MinSegments": "2" } ], "ContainerProfiles": [], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json index 9d43d2166..3625b099c 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json @@ -165,7 +165,6 @@ "MaxAudioChannels": "2", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": true, "$type": "TranscodingProfile" }, { @@ -182,7 +181,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -199,7 +197,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -216,7 +213,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -233,7 +229,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -250,7 +245,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -267,7 +261,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -284,7 +277,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -301,7 +293,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -319,7 +310,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -346,7 +336,6 @@ "MaxAudioChannels": "2", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -373,7 +362,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -399,7 +387,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json index 3859ef994..deee650b2 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json @@ -165,7 +165,6 @@ "MaxAudioChannels": "6", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": true, "$type": "TranscodingProfile" }, { @@ -182,7 +181,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -199,7 +197,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -216,7 +213,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -233,7 +229,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -250,7 +245,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -267,7 +261,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -284,7 +277,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -301,7 +293,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -319,7 +310,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -346,7 +336,6 @@ "MaxAudioChannels": "6", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -373,7 +362,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -399,7 +387,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json index 9fc1ae6bb..38de51b04 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json @@ -16,7 +16,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -28,7 +27,6 @@ "Protocol": "hls", "MaxAudioChannels": "2", "MinSegments": "2", - "BreakOnNonKeyFrames": true, "$type": "TranscodingProfile" }, { @@ -40,7 +38,6 @@ "Protocol": "hls", "MaxAudioChannels": "2", "MinSegments": "2", - "BreakOnNonKeyFrames": true, "$type": "TranscodingProfile" }, { @@ -64,7 +61,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json index 094b0723b..3ff11a684 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json @@ -135,7 +135,6 @@ "Protocol": "hls", "MaxAudioChannels": "6", "MinSegments": "1", - "BreakOnNonKeyFrames": false, "EnableAudioVbrEncoding": true }, { @@ -210,8 +209,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "6", - "MinSegments": "1", - "BreakOnNonKeyFrames": false + "MinSegments": "1" } ], "ContainerProfiles": [], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json index 256c8dc2f..838a1f920 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json @@ -52,7 +52,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -70,7 +69,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -88,7 +86,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json index 256c8dc2f..838a1f920 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json @@ -52,7 +52,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -70,7 +69,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -88,7 +86,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], -- cgit v1.2.3 From 874fd9ac0a2410dd49b01087610b52942d8614a7 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sun, 25 Jan 2026 21:39:57 +0100 Subject: Remove some deprecated API members --- .../Controllers/ConfigurationController.cs | 20 ------------------- Jellyfin.Api/Controllers/EnvironmentController.cs | 14 ------------- Jellyfin.Api/Controllers/QuickConnectController.cs | 10 ---------- Jellyfin.Api/Controllers/TvShowsController.cs | 2 -- Jellyfin.Api/Controllers/UserController.cs | 23 ---------------------- .../ConfigurationDtos/MediaEncoderPathDto.cs | 17 ---------------- .../Models/StartupDtos/StartupRemoteAccessDto.cs | 8 -------- .../Models/UserDtos/UpdateUserEasyPassword.cs | 22 --------------------- MediaBrowser.Model/Session/ClientCapabilities.cs | 10 ---------- 9 files changed, 126 deletions(-) delete mode 100644 Jellyfin.Api/Models/ConfigurationDtos/MediaEncoderPathDto.cs delete mode 100644 Jellyfin.Api/Models/UserDtos/UpdateUserEasyPassword.cs (limited to 'MediaBrowser.Model') diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs index 8dcaebf6d..9e03fbeb0 100644 --- a/Jellyfin.Api/Controllers/ConfigurationController.cs +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -3,8 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Net.Mime; using System.Text.Json; using Jellyfin.Api.Attributes; -using Jellyfin.Api.Constants; -using Jellyfin.Api.Models.ConfigurationDtos; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Configuration; @@ -143,22 +141,4 @@ public class ConfigurationController : BaseJellyfinApiController return NoContent(); } - - /// - /// Updates the path to the media encoder. - /// - /// Media encoder path form body. - /// Media encoder path updated. - /// Status. - [Obsolete("This endpoint is obsolete.")] - [ApiExplorerSettings(IgnoreApi = true)] - [HttpPost("MediaEncoder/Path")] - [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] - [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult UpdateMediaEncoderPath([FromBody, Required] MediaEncoderPathDto mediaEncoderPath) - { - // API ENDPOINT DISABLED (NOOP) FOR SECURITY PURPOSES - // _mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType); - return NoContent(); - } } diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs index 284a97621..70a8c72c0 100644 --- a/Jellyfin.Api/Controllers/EnvironmentController.cs +++ b/Jellyfin.Api/Controllers/EnvironmentController.cs @@ -128,20 +128,6 @@ public class EnvironmentController : BaseJellyfinApiController return NoContent(); } - /// - /// Gets network paths. - /// - /// Empty array returned. - /// List of entries. - [Obsolete("This endpoint is obsolete.")] - [HttpGet("NetworkShares")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetNetworkShares() - { - _logger.LogWarning("Obsolete endpoint accessed: /Environment/NetworkShares"); - return Array.Empty(); - } - /// /// Gets available drives from the server's file system. /// diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs index 14f5265aa..2a15ff767 100644 --- a/Jellyfin.Api/Controllers/QuickConnectController.cs +++ b/Jellyfin.Api/Controllers/QuickConnectController.cs @@ -65,16 +65,6 @@ public class QuickConnectController : BaseJellyfinApiController } } - /// - /// Old version of using a GET method. - /// Still available to avoid breaking compatibility. - /// - /// The result of . - [Obsolete("Use POST request instead")] - [HttpGet("Initiate")] - [ApiExplorerSettings(IgnoreApi = true)] - public Task> InitiateQuickConnectLegacy() => InitiateQuickConnect(); - /// /// Attempts to retrieve authentication information. /// diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 2817e3cbc..bd6d931b2 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -69,7 +69,6 @@ public class TvShowsController : BaseJellyfinApiController /// Optional. Include user data. /// Optional. Starting date of shows to show in Next Up section. /// Whether to enable the total records count. Defaults to true. - /// Whether to disable sending the first episode in a series as next up. /// Whether to include resumable episodes in next up results. /// Whether to include watched episodes in next up results. /// A with the next up episodes. @@ -88,7 +87,6 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] bool? enableUserData, [FromQuery] DateTime? nextUpDateCutoff, [FromQuery] bool enableTotalRecordCount = true, - [FromQuery][ParameterObsolete] bool disableFirstEpisode = false, [FromQuery] bool enableResumable = true, [FromQuery] bool enableRewatching = false) { diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index d0ced277a..536b95dbb 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -337,29 +337,6 @@ public class UserController : BaseJellyfinApiController [FromBody, Required] UpdateUserPassword request) => UpdateUserPassword(userId, request); - /// - /// Updates a user's easy password. - /// - /// The user id. - /// The request. - /// Password successfully reset. - /// User is not allowed to update the password. - /// User not found. - /// A indicating success or a or a on failure. - [HttpPost("{userId}/EasyPassword")] - [Obsolete("Use Quick Connect instead")] - [ApiExplorerSettings(IgnoreApi = true)] - [Authorize] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateUserEasyPassword( - [FromRoute, Required] Guid userId, - [FromBody, Required] UpdateUserEasyPassword request) - { - return Forbid(); - } - /// /// Updates a user. /// diff --git a/Jellyfin.Api/Models/ConfigurationDtos/MediaEncoderPathDto.cs b/Jellyfin.Api/Models/ConfigurationDtos/MediaEncoderPathDto.cs deleted file mode 100644 index 5a48345eb..000000000 --- a/Jellyfin.Api/Models/ConfigurationDtos/MediaEncoderPathDto.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Jellyfin.Api.Models.ConfigurationDtos; - -/// -/// Media Encoder Path Dto. -/// -public class MediaEncoderPathDto -{ - /// - /// Gets or sets media encoder path. - /// - public string Path { get; set; } = null!; - - /// - /// Gets or sets media encoder path type. - /// - public string PathType { get; set; } = null!; -} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs index 9c29e372c..2a1a312d5 100644 --- a/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs +++ b/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; namespace Jellyfin.Api.Models.StartupDtos; @@ -13,11 +12,4 @@ public class StartupRemoteAccessDto /// [Required] public bool EnableRemoteAccess { get; set; } - - /// - /// Gets or sets a value indicating whether enable automatic port mapping. - /// - [Required] - [Obsolete("No longer supported")] - public bool EnableAutomaticPortMapping { get; set; } } diff --git a/Jellyfin.Api/Models/UserDtos/UpdateUserEasyPassword.cs b/Jellyfin.Api/Models/UserDtos/UpdateUserEasyPassword.cs deleted file mode 100644 index f19d0b57a..000000000 --- a/Jellyfin.Api/Models/UserDtos/UpdateUserEasyPassword.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Jellyfin.Api.Models.UserDtos; - -/// -/// The update user easy password request body. -/// -public class UpdateUserEasyPassword -{ - /// - /// Gets or sets the new sha1-hashed password. - /// - public string? NewPassword { get; set; } - - /// - /// Gets or sets the new password. - /// - public string? NewPw { get; set; } - - /// - /// Gets or sets a value indicating whether to reset the password. - /// - public bool ResetPassword { get; set; } -} diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index fc1f24ae1..9b243c117 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -31,15 +31,5 @@ namespace MediaBrowser.Model.Session public string AppStoreUrl { get; set; } public string IconUrl { get; set; } - - // TODO: Remove after 10.9 - [Obsolete("Unused")] - [DefaultValue(false)] - public bool? SupportsContentUploading { get; set; } = false; - - // TODO: Remove after 10.9 - [Obsolete("Unused")] - [DefaultValue(false)] - public bool? SupportsSync { get; set; } = false; } } -- cgit v1.2.3 From b8d2f1f9115ed3ed18960fcebd1188a3fb7d2f5e Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Tue, 27 Jan 2026 11:13:32 +0100 Subject: Remove unused usings --- Jellyfin.Api/Controllers/EnvironmentController.cs | 1 - Jellyfin.Api/Controllers/TvShowsController.cs | 1 - MediaBrowser.Model/Session/ClientCapabilities.cs | 1 - 3 files changed, 3 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs index 70a8c72c0..794ca9693 100644 --- a/Jellyfin.Api/Controllers/EnvironmentController.cs +++ b/Jellyfin.Api/Controllers/EnvironmentController.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; -using Jellyfin.Api.Constants; using Jellyfin.Api.Models.EnvironmentDtos; using MediaBrowser.Common.Api; using MediaBrowser.Common.Extensions; diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index bd6d931b2..c86c9b8f6 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using Jellyfin.Api.Attributes; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index 9b243c117..597845fc1 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using Jellyfin.Data.Enums; using MediaBrowser.Model.Dlna; -- cgit v1.2.3