Re-apply HA fork patches on upstream v12.0 #3

Merged
benvin merged 2139 commits from benvin/rebase-v12 into main 2026-09-12 12:48:36 +10:00
3 changed files with 104 additions and 23 deletions
Showing only changes of commit 2a689f268b - Show all commits
@@ -1267,22 +1267,23 @@ namespace MediaBrowser.Controller.MediaEncoding
.Append(_mediaEncoder.GetInputPathArgument(state));
}
// sub2video for external graphical subtitles
if (state.SubtitleStream is not null
&& ShouldEncodeSubtitle(state)
&& !state.SubtitleStream.IsTextSubtitleStream
&& state.SubtitleStream.IsExternal)
if (NeedsExternalSubtitleMuxing(state))
{
var subtitlePath = state.SubtitleStream.Path;
var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
var isGraphicalBurnIn = ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream;
// dvdsub/vobsub graphical subtitles use .sub+.idx pairs
if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
if (isGraphicalBurnIn)
{
var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
if (File.Exists(idxFile))
var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
// dvdsub/vobsub graphical subtitles use .sub+.idx pairs
if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
{
subtitlePath = idxFile;
var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
if (File.Exists(idxFile))
{
subtitlePath = idxFile;
}
}
}
@@ -1307,7 +1308,7 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(' ').Append(seekSubParam);
}
if (!string.IsNullOrEmpty(canvasArgs))
if (isGraphicalBurnIn && !string.IsNullOrEmpty(canvasArgs))
{
arg.Append(canvasArgs);
}
@@ -3072,11 +3073,8 @@ namespace MediaBrowser.Controller.MediaEncoding
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
if (state.AudioStream.IsExternal)
{
bool hasExternalGraphicsSubs = state.SubtitleStream is not null
&& ShouldEncodeSubtitle(state)
&& state.SubtitleStream.IsExternal
&& !state.SubtitleStream.IsTextSubtitleStream;
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
bool hasExternalSubAsInput = NeedsExternalSubtitleMuxing(state);
int externalAudioMapIndex = hasExternalSubAsInput ? 2 : 1;
args += string.Format(
CultureInfo.InvariantCulture,
@@ -3104,12 +3102,20 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
{
int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
if (state.SubtitleStream.IsExternal)
{
// External subtitle file is added as second FFmpeg input
args += " -map 1:0";
}
else
{
int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
subtitleStreamIndex);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
subtitleStreamIndex);
}
}
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{
@@ -7886,6 +7892,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|| (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
}
private static bool NeedsExternalSubtitleMuxing(EncodingJobInfo state)
{
return state.SubtitleStream is not null
&& state.SubtitleStream.IsExternal
&& (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed
|| (ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream));
}
public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
{
if (string.IsNullOrEmpty(videoSync))
+14 -1
View File
@@ -1451,7 +1451,7 @@ namespace MediaBrowser.Model.Dlna
string? outputContainer,
MediaStreamProtocol? transcodingSubProtocol)
{
if (!subtitleStream.IsExternal && (playMethod != PlayMethod.Transcode || transcodingSubProtocol != MediaStreamProtocol.hls))
if (CanConsiderEmbedSubtitle(subtitleStream, playMethod, transcodingSubProtocol, outputContainer))
{
// Look for supported embedded subs of the same format
foreach (var profile in subtitleProfiles)
@@ -1540,6 +1540,19 @@ namespace MediaBrowser.Model.Dlna
return false;
}
private static bool CanConsiderEmbedSubtitle(MediaStream subtitleStream, PlayMethod playMethod, MediaStreamProtocol? transcodingSubProtocol, string? outputContainer)
{
if (subtitleStream.IsExternal)
{
return playMethod == PlayMethod.Transcode
&& transcodingSubProtocol != MediaStreamProtocol.hls
&& IsSubtitleEmbedSupported(outputContainer);
}
return playMethod != PlayMethod.Transcode
|| transcodingSubProtocol != MediaStreamProtocol.hls;
}
private static SubtitleProfile? GetExternalSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, bool allowConversion)
{
foreach (var profile in subtitleProfiles)
@@ -675,5 +675,59 @@ namespace Jellyfin.Model.Tests
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)]
// External graphical subs embedded into MKV when transcoding
[InlineData("pgssub", true, PlayMethod.Transcode, "mkv", MediaStreamProtocol.http, SubtitleDeliveryMethod.Embed)]
[InlineData("dvdsub", true, PlayMethod.Transcode, "mkv", MediaStreamProtocol.http, SubtitleDeliveryMethod.Embed)]
// External subs remain external when transcoding to non-MKV containers
[InlineData("srt", true, PlayMethod.Transcode, "mp4", MediaStreamProtocol.hls, SubtitleDeliveryMethod.External)]
[InlineData("srt", true, PlayMethod.Transcode, "ts", MediaStreamProtocol.hls, SubtitleDeliveryMethod.External)]
// External subs remain external during DirectPlay even with MKV
[InlineData("srt", true, PlayMethod.DirectPlay, "mkv", null, SubtitleDeliveryMethod.External)]
// Internal subs still embedded into MKV when transcoding (existing behavior)
[InlineData("srt", false, PlayMethod.Transcode, "mkv", MediaStreamProtocol.http, SubtitleDeliveryMethod.Embed)]
[InlineData("pgssub", false, PlayMethod.Transcode, "mkv", MediaStreamProtocol.http, SubtitleDeliveryMethod.Embed)]
public void GetSubtitleProfile_ReturnsExpectedDeliveryMethod(
string codec,
bool isExternal,
PlayMethod playMethod,
string outputContainer,
MediaStreamProtocol? transcodingSubProtocol,
SubtitleDeliveryMethod expectedMethod)
{
var mediaSource = new MediaSourceInfo();
var subtitleStream = new MediaStream
{
Codec = codec,
Language = "eng",
IsExternal = isExternal,
Type = MediaStreamType.Subtitle,
SupportsExternalStream = true
};
var subtitleProfiles = new[]
{
new SubtitleProfile { Format = codec, Method = SubtitleDeliveryMethod.Embed },
new SubtitleProfile { Format = codec, Method = SubtitleDeliveryMethod.External }
};
var transcoderSupport = new Mock<ITranscoderSupport>();
transcoderSupport.Setup(x => x.CanExtractSubtitles(It.IsAny<string>())).Returns(true);
var result = StreamBuilder.GetSubtitleProfile(
mediaSource,
subtitleStream,
subtitleProfiles,
playMethod,
transcoderSupport.Object,
outputContainer,
transcodingSubProtocol);
Assert.Equal(expectedMethod, result.Method);
}
}
}