aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraltqx <al@altqx.com>2026-07-03 09:30:22 +0700
committeraltqx <al@altqx.com>2026-07-03 09:30:22 +0700
commit8622c3bfb78075ef78b2e89f0cf519e130b062c4 (patch)
tree4a294a7549f2e2114448df4d538fd17e446c382d
parent8f3eb3205d61d71638a1c695372cef273e76d2b3 (diff)
Match VobSub MKS subtitle profiles by container
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs30
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs42
2 files changed, 62 insertions, 10 deletions
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 59f97d8c7c..a9ab7d6db0 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -576,11 +576,8 @@ namespace MediaBrowser.Model.Dlna
foreach (var profile in subtitleProfiles)
{
if (profile.Method == SubtitleDeliveryMethod.External
- && (string.Equals(profile.Format, stream.Codec, StringComparison.OrdinalIgnoreCase)
- // FFmpeg cannot mux VobSub back into an .idx/.sub pair, so extracted VobSub streams are exposed as .mks.
- || (string.Equals(profile.Format, "mks", StringComparison.OrdinalIgnoreCase)
- && stream.IsVobSubSubtitleStream
- && (!stream.IsExternal || stream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase)))))
+ && (IsVobSubMksProfile(profile, stream)
+ || (!IsVobSubMksDeliveryProfile(profile) && string.Equals(profile.Format, stream.Codec, StringComparison.OrdinalIgnoreCase))))
{
return stream.Index;
}
@@ -1590,13 +1587,11 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- // FFmpeg cannot mux VobSub back into an .idx/.sub pair, so extracted VobSub streams are matched against external .mks delivery profiles.
- bool isVobSubMksProfile = string.Equals(profile.Format, "mks", StringComparison.OrdinalIgnoreCase)
- && subtitleStream.IsVobSubSubtitleStream
- && (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase));
+ bool isVobSubMksProfile = IsVobSubMksProfile(profile, subtitleStream);
if ((profile.Method == SubtitleDeliveryMethod.External
- && (isVobSubMksProfile || subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))) ||
+ && (isVobSubMksProfile
+ || (!IsVobSubMksDeliveryProfile(profile) && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format)))) ||
(profile.Method == SubtitleDeliveryMethod.Hls && subtitleStream.IsTextSubtitleStream))
{
bool requiresConversion = !isVobSubMksProfile
@@ -1628,6 +1623,21 @@ namespace MediaBrowser.Model.Dlna
return null;
}
+ private static bool IsVobSubMksDeliveryProfile(SubtitleProfile profile)
+ {
+ return MediaStream.IsVobSubFormat(profile.Format)
+ && !string.IsNullOrWhiteSpace(profile.Container)
+ && ContainerHelper.ContainsContainer(profile.Container, "mks");
+ }
+
+ private static bool IsVobSubMksProfile(SubtitleProfile profile, MediaStream subtitleStream)
+ {
+ // FFmpeg cannot mux VobSub back into an .idx/.sub pair, so extracted VobSub streams are exposed as .mks.
+ return IsVobSubMksDeliveryProfile(profile)
+ && subtitleStream.IsVobSubSubtitleStream
+ && (!subtitleStream.IsExternal || subtitleStream.Path?.EndsWith(".mks", StringComparison.OrdinalIgnoreCase) == true);
+ }
+
private bool IsBitrateLimitExceeded(MediaSourceInfo item, long maxBitrate)
{
// Don't restrict bitrate if item is remote.
diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
index d94d56bc20..5ba061296a 100644
--- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
+++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
@@ -677,6 +677,48 @@ namespace Jellyfin.Model.Tests
}
[Theory]
+ [InlineData(false, null, true, SubtitleDeliveryMethod.External)]
+ [InlineData(false, null, false, SubtitleDeliveryMethod.Encode)]
+ [InlineData(true, "/media/sub.mks", true, SubtitleDeliveryMethod.External)]
+ [InlineData(true, "/media/sub.idx", true, SubtitleDeliveryMethod.Encode)]
+ [InlineData(true, "/media/sub.sub", true, SubtitleDeliveryMethod.Encode)]
+ public void GetSubtitleProfile_MatchesVobSubMksProfileOnlyWhenDeliveredAsMks(
+ bool isExternal,
+ string? path,
+ bool enableSubtitleExtraction,
+ SubtitleDeliveryMethod expectedMethod)
+ {
+ var mediaSource = new MediaSourceInfo();
+ var subtitleStream = new MediaStream
+ {
+ Type = MediaStreamType.Subtitle,
+ Index = 0,
+ IsExternal = isExternal,
+ Path = path,
+ Codec = "vobsub"
+ };
+
+ var subtitleProfiles = new[]
+ {
+ new SubtitleProfile { Format = "vobsub", Container = "mks", Method = SubtitleDeliveryMethod.External }
+ };
+
+ var transcoderSupport = new Mock<ITranscoderSupport>();
+ transcoderSupport.Setup(t => t.CanExtractSubtitles(It.IsAny<string>())).Returns(enableSubtitleExtraction);
+
+ var result = StreamBuilder.GetSubtitleProfile(
+ mediaSource,
+ subtitleStream,
+ subtitleProfiles,
+ PlayMethod.Transcode,
+ transcoderSupport.Object,
+ null,
+ null);
+
+ Assert.Equal(expectedMethod, result.Method);
+ }
+
+ [Theory]
// External text subs embedded into MKV when transcoding (#16403)
[InlineData("srt", true, PlayMethod.Transcode, "mkv", MediaStreamProtocol.http, SubtitleDeliveryMethod.Embed)]
[InlineData("ass", true, PlayMethod.Transcode, "mkv", MediaStreamProtocol.http, SubtitleDeliveryMethod.Embed)]