From 33496c16932b115c7f628d8aa3cb52e3522c4874 Mon Sep 17 00:00:00 2001 From: MBR#0001 Date: Sun, 8 Feb 2026 11:10:09 +0100 Subject: [PATCH] Fix broken library subtitle download settings --- .../FixLibrarySubtitleDownloadLanguages.cs | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 Jellyfin.Server/Migrations/Routines/FixLibrarySubtitleDownloadLanguages.cs diff --git a/Jellyfin.Server/Migrations/Routines/FixLibrarySubtitleDownloadLanguages.cs b/Jellyfin.Server/Migrations/Routines/FixLibrarySubtitleDownloadLanguages.cs new file mode 100644 index 0000000000..e82123e5ac --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/FixLibrarySubtitleDownloadLanguages.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Globalization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// Migration to fix broken library subtitle download languages. +/// +[JellyfinMigration("2026-02-06T20:00:00", nameof(FixLibrarySubtitleDownloadLanguages))] +internal class FixLibrarySubtitleDownloadLanguages : IAsyncMigrationRoutine +{ + private readonly ILocalizationManager _localizationManager; + private readonly ILibraryManager _libraryManager; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The Localization manager. + /// The startup logger for Startup UI integration. + /// The Library manager. + /// The logger. + public FixLibrarySubtitleDownloadLanguages( + ILocalizationManager localizationManager, + IStartupLogger startupLogger, + ILibraryManager libraryManager, + ILogger logger) + { + _localizationManager = localizationManager; + _libraryManager = libraryManager; + _logger = startupLogger.With(logger); + } + + /// + public Task PerformAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Starting to fix library subtitle download languages."); + + var virtualFolders = _libraryManager.GetVirtualFolders(false); + + foreach (var virtualFolder in virtualFolders) + { + var options = virtualFolder.LibraryOptions; + if (options.SubtitleDownloadLanguages is null || options.SubtitleDownloadLanguages.Length == 0) + { + continue; + } + + // Some virtual folders don't have a proper item id. + if (!Guid.TryParse(virtualFolder.ItemId, out var folderId)) + { + continue; + } + + var collectionFolder = _libraryManager.GetItemById(folderId); + if (collectionFolder is null) + { + _logger.LogWarning("Could not find collection folder for virtual folder '{LibraryName}' with id '{FolderId}'. Skipping.", virtualFolder.Name, folderId); + continue; + } + + var fixedLanguages = new List(); + + foreach (var language in options.SubtitleDownloadLanguages) + { + var foundLanguage = _localizationManager.FindLanguageInfo(language)?.ThreeLetterISOLanguageName; + if (foundLanguage is not null) + { + // Converted ISO 639-2/B to T (ger to deu) + if (!string.Equals(foundLanguage, language, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogInformation("Converted '{Language}' to '{ResolvedLanguage}' in library '{LibraryName}'.", language, foundLanguage, virtualFolder.Name); + } + + if (fixedLanguages.Contains(foundLanguage, StringComparer.OrdinalIgnoreCase)) + { + _logger.LogInformation("Language '{Language}' already exists for library '{LibraryName}'. Skipping duplicate.", foundLanguage, virtualFolder.Name); + continue; + } + + fixedLanguages.Add(foundLanguage); + } + else + { + _logger.LogInformation("Could not resolve language '{Language}' in library '{LibraryName}'. Skipping.", language, virtualFolder.Name); + } + } + + options.SubtitleDownloadLanguages = [.. fixedLanguages]; + collectionFolder.UpdateLibraryOptions(options); + } + + _logger.LogInformation("Library subtitle download languages fixed."); + + return Task.CompletedTask; + } +}