From 37983c943a7f65850cc2bd4854f22dbe2ae92c28 Mon Sep 17 00:00:00 2001 From: Piotr Niełacny Date: Fri, 13 Mar 2026 16:34:44 +0100 Subject: Respect EnableSubtitleExtraction setting in subtitle delivery Wire up EnableSubtitleExtraction config to MediaEncoder.CanExtractSubtitles so the setting is actually respected. Gate subtitle extraction check behind PlayMethod.Transcode since DirectPlay has no competing ffmpeg process. Add parameterized tests for StreamBuilder.GetSubtitleProfile covering text and graphical codecs, profile format matching, and extraction setting behavior. Remove misplaced SubtitleEncoder extraction test. --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 42cb208d08..166d574d25 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1555,7 +1555,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!subtitleStream.IsExternal && !transcoderSupport.CanExtractSubtitles(subtitleStream.Codec)) + if (!subtitleStream.IsExternal && playMethod == PlayMethod.Transcode && !transcoderSupport.CanExtractSubtitles(subtitleStream.Codec)) { continue; } -- cgit v1.2.3 From 418beafebb49527974c5563907367e6b689352a3 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Mon, 23 Mar 2026 17:15:49 -0400 Subject: Update FolderStorageInfo record --- MediaBrowser.Model/System/FolderStorageInfo.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/System/FolderStorageInfo.cs b/MediaBrowser.Model/System/FolderStorageInfo.cs index 7b10e4ea58..66356c6c48 100644 --- a/MediaBrowser.Model/System/FolderStorageInfo.cs +++ b/MediaBrowser.Model/System/FolderStorageInfo.cs @@ -11,17 +11,22 @@ public record FolderStorageInfo public required string Path { get; init; } /// - /// Gets the free space of the underlying storage device of the . + /// Gets the fully resolved path of the folder in question (interpolating any symlinks if present) + /// + public required string ResolvedPath { get; init; } + + /// + /// Gets the free space of the underlying storage device of the . /// public long FreeSpace { get; init; } /// - /// Gets the used space of the underlying storage device of the . + /// Gets the used space of the underlying storage device of the . /// public long UsedSpace { get; init; } /// - /// Gets the kind of storage device of the . + /// Gets the kind of storage device of the . /// public string? StorageType { get; init; } -- cgit v1.2.3 From c22933260b1d9b8cd97980c00a70f53bbaaf4f54 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Tue, 24 Mar 2026 22:22:52 -0400 Subject: Fix linting issue --- MediaBrowser.Model/System/FolderStorageInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/System/FolderStorageInfo.cs b/MediaBrowser.Model/System/FolderStorageInfo.cs index 66356c6c48..ebca39228b 100644 --- a/MediaBrowser.Model/System/FolderStorageInfo.cs +++ b/MediaBrowser.Model/System/FolderStorageInfo.cs @@ -11,7 +11,7 @@ public record FolderStorageInfo public required string Path { get; init; } /// - /// Gets the fully resolved path of the folder in question (interpolating any symlinks if present) + /// Gets the fully resolved path of the folder in question (interpolating any symlinks if present). /// public required string ResolvedPath { get; init; } -- cgit v1.2.3 From 553f38a2377cf843404cd4d3b3602e8a72bc75f8 Mon Sep 17 00:00:00 2001 From: Lasath Fernando Date: Sat, 4 Apr 2026 16:10:07 +0000 Subject: Fix language display for ISO 639-2-only codes (e.g. mul, und) LoadCultures() in LocalizationManager skipped all iso6392.txt entries without a two-letter ISO 639-1 code, dropping 302 of 496 languages including mul (Multiple languages), und (Undetermined), mis (Uncoded languages), zxx, and many real languages like Achinese, Akkadian, etc. This caused FindLanguageInfo() to return null for these codes, which meant: - ExternalPathParser could not recognize them as valid language codes in subtitle filenames, so the Language field was never set - DisplayTitle fell back to the raw code string (e.g. "Mul") Fix by allowing entries without two-letter codes to be loaded with an empty TwoLetterISOLanguageName. Also set LocalizedLanguage in ProbeResultNormalizer for ffprobe-detected streams (the DB repository path was already handled on master). --- .../Localization/LocalizationManager.cs | 2 +- .../Probing/ProbeResultNormalizer.cs | 6 ++++++ MediaBrowser.Model/Entities/MediaStream.cs | 1 - .../Localization/LocalizationManagerTests.cs | 21 ++++++++++++++++++++- 4 files changed, 27 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index bc80c2b405..6fca5bc1ba 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -138,7 +138,7 @@ namespace Emby.Server.Implementations.Localization string twoCharName = parts[2]; if (string.IsNullOrWhiteSpace(twoCharName)) { - continue; + twoCharName = string.Empty; } else if (twoCharName.Contains('-', StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index d3e7b52315..203e72de36 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -729,6 +729,9 @@ namespace MediaBrowser.MediaEncoding.Probing stream.Type = MediaStreamType.Audio; stream.LocalizedDefault = _localization.GetLocalizedString("Default"); stream.LocalizedExternal = _localization.GetLocalizedString("External"); + stream.LocalizedLanguage = !string.IsNullOrEmpty(stream.Language) + ? _localization.FindLanguageInfo(stream.Language)?.DisplayName + : null; stream.Channels = streamInfo.Channels; @@ -767,6 +770,9 @@ namespace MediaBrowser.MediaEncoding.Probing stream.LocalizedForced = _localization.GetLocalizedString("Forced"); stream.LocalizedExternal = _localization.GetLocalizedString("External"); stream.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired"); + stream.LocalizedLanguage = !string.IsNullOrEmpty(stream.Language) + ? _localization.FindLanguageInfo(stream.Language)?.DisplayName + : null; if (string.IsNullOrEmpty(stream.Title)) { diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 11f81ff7d8..4491fb5ace 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; -using System.Linq; using System.Text; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs index e60522bf78..700ac5dced 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -41,7 +41,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization await localizationManager.LoadAll(); var cultures = localizationManager.GetCultures().ToList(); - Assert.Equal(194, cultures.Count); + Assert.Equal(496, cultures.Count); var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal)); Assert.NotNull(germany); @@ -99,6 +99,25 @@ namespace Jellyfin.Server.Implementations.Tests.Localization Assert.Contains("ger", germany.ThreeLetterISOLanguageNames); } + [Theory] + [InlineData("mul", "Multiple languages")] + [InlineData("und", "Undetermined")] + [InlineData("mis", "Uncoded languages")] + [InlineData("zxx", "No linguistic content; Not applicable")] + public async Task FindLanguageInfo_ISO6392Only_Success(string code, string expectedDisplayName) + { + var localizationManager = Setup(new ServerConfiguration + { + UICulture = "en-US" + }); + await localizationManager.LoadAll(); + + var culture = localizationManager.FindLanguageInfo(code); + Assert.NotNull(culture); + Assert.Equal(expectedDisplayName, culture.DisplayName); + Assert.Equal(code, culture.ThreeLetterISOLanguageName); + } + [Fact] public async Task GetParentalRatings_Default_Success() { -- cgit v1.2.3 From 1d44899606110ffd7d984bd7d52b8cd57e18c273 Mon Sep 17 00:00:00 2001 From: KGT1 Date: Sat, 18 Apr 2026 17:06:46 +0200 Subject: Remove global subtitle configuration (#14957) --- .../Providers/SubtitleConfigurationFactory.cs | 21 ---------- MediaBrowser.Model/Providers/SubtitleOptions.cs | 36 ----------------- .../MediaInfo/FFProbeVideoInfo.cs | 41 +++---------------- .../MediaInfo/SubtitleScheduledTask.cs | 46 +++++++--------------- 4 files changed, 20 insertions(+), 124 deletions(-) delete mode 100644 MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs delete mode 100644 MediaBrowser.Model/Providers/SubtitleOptions.cs (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs deleted file mode 100644 index 0445397ad8..0000000000 --- a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.Common.Providers -{ - public class SubtitleConfigurationFactory : IConfigurationFactory - { - /// - public IEnumerable GetConfigurations() - { - yield return new ConfigurationStore() - { - Key = "subtitles", - ConfigurationType = typeof(SubtitleOptions) - }; - } - } -} diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs deleted file mode 100644 index 6ea1e14862..0000000000 --- a/MediaBrowser.Model/Providers/SubtitleOptions.cs +++ /dev/null @@ -1,36 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Providers -{ - public class SubtitleOptions - { - public SubtitleOptions() - { - DownloadLanguages = Array.Empty(); - - SkipIfAudioTrackMatches = true; - RequirePerfectMatch = true; - } - - public bool SkipIfEmbeddedSubtitlesPresent { get; set; } - - public bool SkipIfAudioTrackMatches { get; set; } - - public string[] DownloadLanguages { get; set; } - - public bool DownloadMovieSubtitles { get; set; } - - public bool DownloadEpisodeSubtitles { get; set; } - - public string OpenSubtitlesUsername { get; set; } - - public string OpenSubtitlesPasswordHash { get; set; } - - public bool IsOpenSubtitleVipAccount { get; set; } - - public bool RequirePerfectMatch { get; set; } - } -} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index a89f059060..fdc2f36469 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; using Jellyfin.Extensions; -using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -25,7 +24,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo @@ -74,7 +72,6 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleResolver = subtitleResolver; _mediaAttachmentRepository = mediaAttachmentRepository; _mediaStreamRepository = mediaStreamRepository; - _mediaStreamRepository = mediaStreamRepository; } public async Task ProbeVideo( @@ -551,47 +548,19 @@ namespace MediaBrowser.Providers.MediaInfo var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; - var subtitleOptions = _config.GetConfiguration("subtitles"); - var libraryOptions = _libraryManager.GetLibraryOptions(video); - string[] subtitleDownloadLanguages; - bool skipIfEmbeddedSubtitlesPresent; - bool skipIfAudioTrackMatches; - bool requirePerfectMatch; - bool enabled; - - if (libraryOptions.SubtitleDownloadLanguages is null) - { - subtitleDownloadLanguages = subtitleOptions.DownloadLanguages; - skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent; - skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches; - requirePerfectMatch = subtitleOptions.RequirePerfectMatch; - enabled = (subtitleOptions.DownloadEpisodeSubtitles && - video is Episode) || - (subtitleOptions.DownloadMovieSubtitles && - video is Movie); - } - else - { - subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; - skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; - skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; - requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; - enabled = true; - } - - if (enableSubtitleDownloading && enabled) + if (enableSubtitleDownloading && libraryOptions.SubtitleDownloadLanguages is not null) { var downloadedLanguages = await new SubtitleDownloader( _logger, _subtitleManager).DownloadSubtitles( video, currentStreams.Concat(externalSubtitleStreams).ToList(), - skipIfEmbeddedSubtitlesPresent, - skipIfAudioTrackMatches, - requirePerfectMatch, - subtitleDownloadLanguages, + libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent, + libraryOptions.SkipSubtitlesIfAudioTrackMatches, + libraryOptions.RequirePerfectSubtitleMatch, + libraryOptions.SubtitleDownloadLanguages, libraryOptions.DisabledSubtitleFetchers, libraryOptions.SubtitleFetcherOrder, true, diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 1134baf92d..7188e9804e 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -8,14 +8,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; -using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Providers; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -57,16 +55,9 @@ namespace MediaBrowser.Providers.MediaInfo public bool IsLogged => true; - private SubtitleOptions GetOptions() - { - return _config.GetConfiguration("subtitles"); - } - /// public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { - var options = GetOptions(); - var types = new[] { BaseItemKind.Episode, BaseItemKind.Movie }; var dict = new Dictionary(); @@ -81,17 +72,14 @@ namespace MediaBrowser.Providers.MediaInfo if (libraryOptions.SubtitleDownloadLanguages is null) { - subtitleDownloadLanguages = options.DownloadLanguages; - skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; - skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; - } - else - { - subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; - skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; - skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + // Skip this library if subtitle download languages are not configured + continue; } + subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; + skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + foreach (var lang in subtitleDownloadLanguages) { var query = new InternalItemsQuery @@ -144,7 +132,7 @@ namespace MediaBrowser.Providers.MediaInfo try { - await DownloadSubtitles(video as Video, options, cancellationToken).ConfigureAwait(false); + await DownloadSubtitles(video as Video, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -160,7 +148,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - private async Task DownloadSubtitles(Video video, SubtitleOptions options, CancellationToken cancellationToken) + private async Task DownloadSubtitles(Video video, CancellationToken cancellationToken) { var mediaStreams = video.GetMediaStreams(); @@ -173,19 +161,15 @@ namespace MediaBrowser.Providers.MediaInfo if (libraryOptions.SubtitleDownloadLanguages is null) { - subtitleDownloadLanguages = options.DownloadLanguages; - skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; - skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; - requirePerfectMatch = options.RequirePerfectMatch; - } - else - { - subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; - skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; - skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; - requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + // Subtitle downloading is not configured for this library + return true; } + subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; + skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + var downloadedLanguages = await new SubtitleDownloader( _logger, _subtitleManager).DownloadSubtitles( -- cgit v1.2.3