aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTORS.md2
-rw-r--r--Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs4
-rw-r--r--Jellyfin.Server/Migrations/Routines/20260915104305_FixNullEncoderPreset.cs77
-rw-r--r--tests/Jellyfin.Server.Tests/Migrations/FixNullEncoderPresetTests.cs118
4 files changed, 199 insertions, 2 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index f5f844bcbc..63ba6bf52d 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -242,6 +242,8 @@
- [m0g3r](https://github.com/m0g3r)
- [martin-77](https://github.com/martin-77)
- [Oggeb1](https://github.com/Oggeb1)
+ - [scr4bble](https://github.com/scr4bble)
+ - [Mateus Bandeira](https://github.com/mateusbandeiraa)
# Emby Contributors
diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs
index 0141b43c96..a5006c9bfa 100644
--- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs
+++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs
@@ -79,8 +79,8 @@ public class MigrateEncodingOptions : IMigrationRoutine
tonemappingRange = parsedTonemappingRange;
}
- var encoderPreset = EncoderPreset.superfast;
- if (Enum.TryParse<EncoderPreset>(oldConfig.TonemappingRange, true, out var parsedEncoderPreset))
+ var encoderPreset = EncoderPreset.auto;
+ if (Enum.TryParse<EncoderPreset>(oldConfig.EncoderPreset, true, out var parsedEncoderPreset))
{
encoderPreset = parsedEncoderPreset;
}
diff --git a/Jellyfin.Server/Migrations/Routines/20260915104305_FixNullEncoderPreset.cs b/Jellyfin.Server/Migrations/Routines/20260915104305_FixNullEncoderPreset.cs
new file mode 100644
index 0000000000..f8e63f1920
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/20260915104305_FixNullEncoderPreset.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using Jellyfin.Server.Migrations.Stages;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Server.Migrations.Routines;
+
+/// <summary>
+/// Replaces the nullable encoder preset written by older versions with the default preset.
+/// </summary>
+[JellyfinMigration("2026-09-15T10:43:05", nameof(FixNullEncoderPreset), Stage = JellyfinMigrationStageTypes.PreInitialisation)]
+internal class FixNullEncoderPreset : IAsyncMigrationRoutine
+{
+ private static readonly XNamespace _xsiNamespace = "http://www.w3.org/2001/XMLSchema-instance";
+
+ private readonly IApplicationPaths _applicationPaths;
+ private readonly ILogger<FixNullEncoderPreset> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FixNullEncoderPreset"/> class.
+ /// </summary>
+ /// <param name="applicationPaths">The application paths.</param>
+ /// <param name="logger">The logger.</param>
+ public FixNullEncoderPreset(IApplicationPaths applicationPaths, ILogger<FixNullEncoderPreset> logger)
+ {
+ _applicationPaths = applicationPaths;
+ _logger = logger;
+ }
+
+ /// <inheritdoc />
+ public Task PerformAsync(CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "encoding.xml");
+ if (!File.Exists(path))
+ {
+ return Task.CompletedTask;
+ }
+
+ XDocument document;
+ try
+ {
+ document = XDocument.Load(path, LoadOptions.PreserveWhitespace);
+ }
+ catch (XmlException ex)
+ {
+ _logger.LogWarning(ex, "Could not parse encoding configuration; skipping null encoder preset migration");
+ return Task.CompletedTask;
+ }
+
+ var encoderPreset = document.Root?.Element(nameof(EncodingOptions.EncoderPreset));
+ if (encoderPreset is null)
+ {
+ return Task.CompletedTask;
+ }
+
+ var nilAttribute = encoderPreset.Attribute(_xsiNamespace + "nil");
+ if (!string.Equals(nilAttribute?.Value, "true", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(encoderPreset.Value))
+ {
+ return Task.CompletedTask;
+ }
+
+ nilAttribute?.Remove();
+ encoderPreset.Value = nameof(EncoderPreset.auto);
+ document.Save(path, SaveOptions.DisableFormatting);
+ _logger.LogInformation("Replaced null encoder preset with auto in encoding configuration");
+ return Task.CompletedTask;
+ }
+}
diff --git a/tests/Jellyfin.Server.Tests/Migrations/FixNullEncoderPresetTests.cs b/tests/Jellyfin.Server.Tests/Migrations/FixNullEncoderPresetTests.cs
new file mode 100644
index 0000000000..a6ebc82425
--- /dev/null
+++ b/tests/Jellyfin.Server.Tests/Migrations/FixNullEncoderPresetTests.cs
@@ -0,0 +1,118 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Xml.Serialization;
+using Emby.Server.Implementations;
+using Jellyfin.Server.Migrations.PreStartupRoutines;
+using Jellyfin.Server.Migrations.Routines;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Tests.Migrations;
+
+public sealed class FixNullEncoderPresetTests : IDisposable
+{
+ private readonly string _configurationDirectory;
+ private readonly string _encodingConfigurationPath;
+ private readonly FixNullEncoderPreset _migration;
+
+ public FixNullEncoderPresetTests()
+ {
+ _configurationDirectory = Directory.CreateTempSubdirectory("jellyfin-migration-test-").FullName;
+ _encodingConfigurationPath = Path.Combine(_configurationDirectory, "encoding.xml");
+
+ var applicationPaths = new Mock<IApplicationPaths>();
+ applicationPaths.SetupGet(paths => paths.ConfigurationDirectoryPath).Returns(_configurationDirectory);
+ _migration = new FixNullEncoderPreset(applicationPaths.Object, NullLogger<FixNullEncoderPreset>.Instance);
+ }
+
+ [Fact]
+ public async Task PerformAsync_ReplacesNullEncoderPresetWithAuto()
+ {
+ const string Configuration = """
+ <?xml version="1.0" encoding="utf-8"?>
+ <EncodingOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <HardwareAccelerationType>qsv</HardwareAccelerationType>
+ <EncoderPreset xsi:nil="true" />
+ <EnableHardwareEncoding>true</EnableHardwareEncoding>
+ </EncodingOptions>
+ """;
+ await File.WriteAllTextAsync(_encodingConfigurationPath, Configuration, TestContext.Current.CancellationToken);
+
+ await _migration.PerformAsync(TestContext.Current.CancellationToken);
+
+ var serializer = new XmlSerializer(typeof(EncodingOptions));
+ using var reader = File.OpenRead(_encodingConfigurationPath);
+ var encodingOptions = Assert.IsType<EncodingOptions>(serializer.Deserialize(reader));
+ Assert.Equal(EncoderPreset.auto, encodingOptions.EncoderPreset);
+ Assert.Equal(HardwareAccelerationType.qsv, encodingOptions.HardwareAccelerationType);
+ Assert.True(encodingOptions.EnableHardwareEncoding);
+ }
+
+ [Fact]
+ public async Task PerformAsync_LeavesValidEncoderPresetUnchanged()
+ {
+ const string Configuration = """
+ <?xml version="1.0" encoding="utf-8"?>
+ <EncodingOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <EncoderPreset>fast</EncoderPreset>
+ </EncodingOptions>
+ """;
+ await File.WriteAllTextAsync(_encodingConfigurationPath, Configuration, TestContext.Current.CancellationToken);
+
+ await _migration.PerformAsync(TestContext.Current.CancellationToken);
+
+ Assert.Equal(Configuration, await File.ReadAllTextAsync(_encodingConfigurationPath, TestContext.Current.CancellationToken));
+ }
+
+ [Fact]
+ public async Task PerformAsync_LeavesMalformedConfigurationUnchanged()
+ {
+ const string Configuration = "<EncodingOptions><EncoderPreset xsi:nil=\"true\" />";
+ await File.WriteAllTextAsync(_encodingConfigurationPath, Configuration, TestContext.Current.CancellationToken);
+
+ await _migration.PerformAsync(TestContext.Current.CancellationToken);
+
+ Assert.Equal(Configuration, await File.ReadAllTextAsync(_encodingConfigurationPath, TestContext.Current.CancellationToken));
+ }
+
+ [Fact]
+ public async Task MigrateEncodingOptions_UsesAutoForNullEncoderPreset()
+ {
+ const string Configuration = """
+ <?xml version="1.0" encoding="utf-8"?>
+ <EncodingOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <HardwareAccelerationType>qsv</HardwareAccelerationType>
+ <TonemappingRange>tv</TonemappingRange>
+ <EncoderPreset xsi:nil="true" />
+ <EnableHardwareEncoding>true</EnableHardwareEncoding>
+ </EncodingOptions>
+ """;
+ await File.WriteAllTextAsync(_encodingConfigurationPath, Configuration, TestContext.Current.CancellationToken);
+ var applicationPaths = new ServerApplicationPaths(
+ _configurationDirectory,
+ _configurationDirectory,
+ _configurationDirectory,
+ _configurationDirectory,
+ _configurationDirectory);
+
+ var migration = new MigrateEncodingOptions(applicationPaths, NullLoggerFactory.Instance);
+ migration.Perform();
+
+ var serializer = new XmlSerializer(typeof(EncodingOptions));
+ using var reader = File.OpenRead(_encodingConfigurationPath);
+ var encodingOptions = Assert.IsType<EncodingOptions>(serializer.Deserialize(reader));
+ Assert.Equal(EncoderPreset.auto, encodingOptions.EncoderPreset);
+ Assert.Equal(HardwareAccelerationType.qsv, encodingOptions.HardwareAccelerationType);
+ Assert.True(encodingOptions.EnableHardwareEncoding);
+ }
+
+ public void Dispose()
+ {
+ Directory.Delete(_configurationDirectory, true);
+ }
+}