diff options
| author | Piotr Niełacny <piotr.nielacny@gmail.com> | 2026-03-21 21:57:58 +0100 |
|---|---|---|
| committer | Piotr Niełacny <piotr.nielacny@gmail.com> | 2026-05-19 13:03:07 +0200 |
| commit | 2a689f268bc88ee7ab7e25121a6d43f71c1f8a5f (patch) | |
| tree | 2a41965b8cc2a88de16f9f0e54a5f83917ed8db6 /MediaBrowser.Controller/MediaEncoding | |
| parent | 2c66447f08f740193c4dd4f340691d2cdb07ea49 (diff) | |
Embed external subtitles into MKV when transcoding
Allow external subtitle files (SRT, ASS, PGS, etc.) to be muxed into
MKV output containers when the device profile requests Embed delivery.
Previously, the IsExternal guard in GetSubtitleProfile excluded external
subtitles from Embed consideration entirely, forcing them to be served
as separate sidecar files even when the output container supports
embedding.
Changes:
- Extract CanConsiderEmbedSubtitle in StreamBuilder to allow external
subs through when transcoding to MKV
- Add external subtitle file as FFmpeg input (-i) for Embed delivery
- Map external embedded subs from the correct FFmpeg input index
- Fix external audio map index to account for the new subtitle input
- Extract NeedsExternalSubtitleMuxing in EncodingHelper to deduplicate
the external subtitle input check
Fixes #16403
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding')
| -rw-r--r-- | MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 58 |
1 files changed, 36 insertions, 22 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 65f6b79656..1fdb5fd4bd 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -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)) |
