From d944f415f3cc0e5433d94b11a16684ca3f0131ec Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 22 Sep 2024 00:34:47 +0800 Subject: [PATCH 1/4] Let HLS Controller decide if subtitle should be burn in Previously, we predicted whether the subtitle should be burned in with transcode reasons, but that was not accurate because the actual transcoding codec is only determined after the client has requested the stream. This pass through the option to the `DynamicHlsController` to handle the subtitle burn-in during the actual transcoding process. Now the client should be responsible to conditionally load the subtitle when this option is enabled. --- .../Controllers/DynamicHlsController.cs | 63 ++++++++++++------- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 6 ++ .../MediaEncoding/BaseEncodingJobOptions.cs | 2 + .../MediaEncoding/EncodingHelper.cs | 38 ++++++----- MediaBrowser.Model/Dlna/StreamBuilder.cs | 18 +----- MediaBrowser.Model/Dlna/StreamInfo.cs | 7 ++- 6 files changed, 81 insertions(+), 53 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index db1d866985..924f010e40 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -158,6 +158,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The max height. /// Optional. Whether to enable subtitles in the manifest. /// Optional. Whether to enable Audio Encoding. + /// Whether to always burn in subtitles when transcoding. /// Hls live stream retrieved. /// A containing the hls file. [HttpGet("Videos/{itemId}/live.m3u8")] @@ -216,7 +217,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] bool? enableSubtitlesInManifest, - [FromQuery] bool enableAudioVbrEncoding = true) + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) { VideoRequestDto streamingRequest = new VideoRequestDto { @@ -251,7 +253,7 @@ public class DynamicHlsController : BaseJellyfinApiController Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? false, @@ -271,7 +273,8 @@ public class DynamicHlsController : BaseJellyfinApiController MaxHeight = maxHeight, MaxWidth = maxWidth, EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true, - EnableAudioVbrEncoding = enableAudioVbrEncoding + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding }; // CTS lifecycle is managed internally. @@ -398,6 +401,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Enable adaptive bitrate streaming. /// Enable trickplay image playlists being added to master playlist. /// Whether to enable Audio Encoding. + /// Whether to always burn in subtitles when transcoding. /// Video stream returned. /// A containing the playlist file. [HttpGet("Videos/{itemId}/master.m3u8")] @@ -457,7 +461,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] Dictionary streamOptions, [FromQuery] bool enableAdaptiveBitrateStreaming = true, [FromQuery] bool enableTrickplay = true, - [FromQuery] bool enableAudioVbrEncoding = true) + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) { var streamingRequest = new HlsVideoRequestDto { @@ -493,7 +498,7 @@ public class DynamicHlsController : BaseJellyfinApiController MaxHeight = maxHeight, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? false, @@ -512,7 +517,8 @@ public class DynamicHlsController : BaseJellyfinApiController StreamOptions = streamOptions, EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming, EnableTrickplay = enableTrickplay, - EnableAudioVbrEncoding = enableAudioVbrEncoding + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding }; return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); @@ -572,6 +578,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The streaming options. /// Enable adaptive bitrate streaming. /// Optional. Whether to enable Audio Encoding. + /// Whether to always burn in subtitles when transcoding. /// Audio stream returned. /// A containing the playlist file. [HttpGet("Audio/{itemId}/master.m3u8")] @@ -629,7 +636,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, [FromQuery] bool enableAdaptiveBitrateStreaming = true, - [FromQuery] bool enableAudioVbrEncoding = true) + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) { var streamingRequest = new HlsAudioRequestDto { @@ -663,7 +671,7 @@ public class DynamicHlsController : BaseJellyfinApiController Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? false, @@ -681,7 +689,8 @@ public class DynamicHlsController : BaseJellyfinApiController Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming, - EnableAudioVbrEncoding = enableAudioVbrEncoding + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding }; return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); @@ -741,6 +750,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The . /// Optional. The streaming options. /// Optional. Whether to enable Audio Encoding. + /// Whether to always burn in subtitles when transcoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Videos/{itemId}/main.m3u8")] @@ -797,7 +807,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool enableAudioVbrEncoding = true) + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) { using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new VideoRequestDto @@ -834,7 +845,7 @@ public class DynamicHlsController : BaseJellyfinApiController MaxHeight = maxHeight, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? false, @@ -851,7 +862,8 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding }; return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) @@ -911,6 +923,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The . /// Optional. The streaming options. /// Optional. Whether to enable Audio Encoding. + /// Whether to always burn in subtitles when transcoding. /// Audio stream returned. /// A containing the audio file. [HttpGet("Audio/{itemId}/main.m3u8")] @@ -966,7 +979,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool enableAudioVbrEncoding = true) + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) { using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new StreamingRequestDto @@ -1001,7 +1015,7 @@ public class DynamicHlsController : BaseJellyfinApiController Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? false, @@ -1018,7 +1032,8 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding }; return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) @@ -1084,6 +1099,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The . /// Optional. The streaming options. /// Optional. Whether to enable Audio Encoding. + /// Whether to always burn in subtitles when transcoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")] @@ -1146,7 +1162,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool enableAudioVbrEncoding = true) + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) { var streamingRequest = new VideoRequestDto { @@ -1185,7 +1202,7 @@ public class DynamicHlsController : BaseJellyfinApiController MaxHeight = maxHeight, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? false, @@ -1202,7 +1219,8 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding }; return await GetDynamicSegment(streamingRequest, segmentId) @@ -1267,6 +1285,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The . /// Optional. The streaming options. /// Optional. Whether to enable Audio Encoding. + /// Whether to always burn in subtitles when transcoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")] @@ -1328,7 +1347,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool enableAudioVbrEncoding = true) + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) { var streamingRequest = new StreamingRequestDto { @@ -1365,7 +1385,7 @@ public class DynamicHlsController : BaseJellyfinApiController Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? false, @@ -1382,7 +1402,8 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding }; return await GetDynamicSegment(streamingRequest, segmentId) diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 5050cab418..2d9ecd4f08 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -293,6 +293,7 @@ public class MediaInfoHelper mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; + mediaSource.TranscodingUrl += "&alwaysBurnInSubtitleWhenTranscoding=true"; } else { @@ -310,6 +311,11 @@ public class MediaInfoHelper { mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; } + + if (streamInfo.AlwaysBurnInSubtitleWhenTranscoding) + { + mediaSource.TranscodingUrl += "&alwaysBurnInSubtitleWhenTranscoding=true"; + } } } diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs index f77186e25c..20f51ddb71 100644 --- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs @@ -193,6 +193,8 @@ namespace MediaBrowser.Controller.MediaEncoding public bool EnableAudioVbrEncoding { get; set; } + public bool AlwaysBurnInSubtitleWhenTranscoding { get; set; } + public string GetOption(string qualifier, string name) { var value = GetOption(qualifier + "-" + name); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index d8c4f2f850..f8ba8ddd80 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -941,7 +941,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // DVBSUB uses the fixed canvas size 720x576 if (state.SubtitleStream is not null - && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode + && ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)) { @@ -1240,7 +1240,7 @@ namespace MediaBrowser.Controller.MediaEncoding // sub2video for external graphical subtitles if (state.SubtitleStream is not null - && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode + && ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleStream.IsExternal) { @@ -2554,7 +2554,7 @@ namespace MediaBrowser.Controller.MediaEncoding } var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive; - if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && !isCopyingTimestamps) + if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(state) && !isCopyingTimestamps) { var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds; @@ -2755,7 +2755,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream.IsExternal) { bool hasExternalGraphicsSubs = state.SubtitleStream is not null - && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode + && ShouldEncodeSubtitle(state) && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream; int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1; @@ -3475,7 +3475,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doToneMap = IsSwTonemapAvailable(state, options); var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI; - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; @@ -3618,7 +3618,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintH2645 = doDeintH264 || doDeintHevc; var doCuTonemap = IsHwTonemapAvailable(state, options); - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; var hasAssSubs = hasSubs @@ -3824,7 +3824,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintH2645 = doDeintH264 || doDeintHevc; var doOclTonemap = IsHwTonemapAvailable(state, options); - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; var hasAssSubs = hasSubs @@ -4064,7 +4064,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options); var doTonemap = doVppTonemap || doOclTonemap; - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; var hasAssSubs = hasSubs @@ -4320,7 +4320,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doTonemap = doVaVppTonemap || doOclTonemap; var doDeintH2645 = doDeintH264 || doDeintHevc; - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; var hasAssSubs = hasSubs @@ -4636,7 +4636,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doTonemap = doVaVppTonemap || doOclTonemap; var doDeintH2645 = doDeintH264 || doDeintHevc; - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; var hasAssSubs = hasSubs @@ -4858,7 +4858,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doVkTonemap = IsVulkanHwTonemapAvailable(state, options); var doDeintH2645 = doDeintH264 || doDeintHevc; - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; var hasAssSubs = hasSubs @@ -5091,7 +5091,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintH2645 = doDeintH264 || doDeintHevc; var doOclTonemap = IsHwTonemapAvailable(state, options); - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; @@ -5339,7 +5339,7 @@ namespace MediaBrowser.Controller.MediaEncoding var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; var hasAssSubs = hasSubs @@ -5511,7 +5511,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintH2645 = doDeintH264 || doDeintHevc; var doOclTonemap = IsHwTonemapAvailable(state, options); - var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream != null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; var hasAssSubs = hasSubs @@ -5722,7 +5722,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; @@ -7156,7 +7156,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += keyFrameArg; - var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(state); var hasCopyTs = false; @@ -7361,5 +7361,11 @@ namespace MediaBrowser.Controller.MediaEncoding { return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); } + + private static bool ShouldEncodeSubtitle(EncodingJobInfo state) + { + return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode + || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec)); + } } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index bf122dcc7f..6c45f19468 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -639,7 +639,8 @@ namespace MediaBrowser.Model.Dlna RunTimeTicks = item.RunTimeTicks, Context = options.Context, DeviceProfile = options.Profile, - SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles) + SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles), + AlwaysBurnInSubtitleWhenTranscoding = options.AlwaysBurnInSubtitleWhenTranscoding }; var subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null; @@ -767,20 +768,7 @@ namespace MediaBrowser.Model.Dlna if (subtitleStream is not null) { var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol); - - if (options.AlwaysBurnInSubtitleWhenTranscoding && (playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0) - { - playlistItem.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode; - foreach (SubtitleProfile profile in options.Profile.SubtitleProfiles) - { - profile.Method = SubtitleDeliveryMethod.Encode; - } - } - else - { - playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; - } - + playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; playlistItem.SubtitleCodecs = [subtitleProfile.Format]; } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 3be6860880..1ae4e1962d 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -270,6 +270,11 @@ public class StreamInfo /// public bool EnableAudioVbrEncoding { get; set; } + /// + /// Gets or sets a value indicating whether always burn in subtitles when transcoding. + /// + public bool AlwaysBurnInSubtitleWhenTranscoding { get; set; } + /// /// Gets a value indicating whether the stream is direct. /// @@ -953,7 +958,7 @@ public class StreamInfo list.Add(new NameValuePair("VideoCodec", videoCodecs)); list.Add(new NameValuePair("AudioCodec", audioCodecs)); list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && (item.AlwaysBurnInSubtitleWhenTranscoding || item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); From c3e889cd41e534b15e0930e2e76fcf12238e5f16 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 22 Sep 2024 01:11:00 +0800 Subject: [PATCH 2/4] Conditionally add burn in option for remote source --- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 2d9ecd4f08..4adda0b695 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -293,7 +293,10 @@ public class MediaInfoHelper mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - mediaSource.TranscodingUrl += "&alwaysBurnInSubtitleWhenTranscoding=true"; + if (streamInfo.AlwaysBurnInSubtitleWhenTranscoding) + { + mediaSource.TranscodingUrl += "&alwaysBurnInSubtitleWhenTranscoding=true"; + } } else { From 1346ebc13448e4e4e4096ac31320abf7cf3a6baa Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 22 Sep 2024 03:13:30 +0800 Subject: [PATCH 3/4] Don't add subtitle option to audio endpoint --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 924f010e40..05c9faddf3 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -578,7 +578,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The streaming options. /// Enable adaptive bitrate streaming. /// Optional. Whether to enable Audio Encoding. - /// Whether to always burn in subtitles when transcoding. /// Audio stream returned. /// A containing the playlist file. [HttpGet("Audio/{itemId}/master.m3u8")] @@ -636,8 +635,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, [FromQuery] bool enableAdaptiveBitrateStreaming = true, - [FromQuery] bool enableAudioVbrEncoding = true, - [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) + [FromQuery] bool enableAudioVbrEncoding = true) { var streamingRequest = new HlsAudioRequestDto { @@ -690,7 +688,7 @@ public class DynamicHlsController : BaseJellyfinApiController StreamOptions = streamOptions, EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming, EnableAudioVbrEncoding = enableAudioVbrEncoding, - AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding + AlwaysBurnInSubtitleWhenTranscoding = false }; return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); From 4502024468ecd9a795cd482f4fbda30b35ee58de Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 22 Sep 2024 04:41:30 +0800 Subject: [PATCH 4/4] Remove all subtitle options from audio endpoints Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 05c9faddf3..48fda471a6 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -921,7 +921,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The . /// Optional. The streaming options. /// Optional. Whether to enable Audio Encoding. - /// Whether to always burn in subtitles when transcoding. /// Audio stream returned. /// A containing the audio file. [HttpGet("Audio/{itemId}/main.m3u8")] @@ -977,8 +976,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool enableAudioVbrEncoding = true, - [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) + [FromQuery] bool enableAudioVbrEncoding = true) { using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new StreamingRequestDto @@ -1031,7 +1029,7 @@ public class DynamicHlsController : BaseJellyfinApiController Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, EnableAudioVbrEncoding = enableAudioVbrEncoding, - AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding + AlwaysBurnInSubtitleWhenTranscoding = false }; return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) @@ -1283,7 +1281,6 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The . /// Optional. The streaming options. /// Optional. Whether to enable Audio Encoding. - /// Whether to always burn in subtitles when transcoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")] @@ -1345,8 +1342,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool enableAudioVbrEncoding = true, - [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) + [FromQuery] bool enableAudioVbrEncoding = true) { var streamingRequest = new StreamingRequestDto { @@ -1401,7 +1397,7 @@ public class DynamicHlsController : BaseJellyfinApiController Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, EnableAudioVbrEncoding = enableAudioVbrEncoding, - AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding + AlwaysBurnInSubtitleWhenTranscoding = false }; return await GetDynamicSegment(streamingRequest, segmentId)