Browse Source

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.
gnattu 8 months ago
parent
commit
d944f415f3

+ 42 - 21
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -158,6 +158,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="maxHeight">Optional. The max height.</param>
     /// <param name="maxHeight">Optional. The max height.</param>
     /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
     /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
+    /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param>
     /// <response code="200">Hls live stream retrieved.</response>
     /// <response code="200">Hls live stream retrieved.</response>
     /// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
     [HttpGet("Videos/{itemId}/live.m3u8")]
     [HttpGet("Videos/{itemId}/live.m3u8")]
@@ -216,7 +217,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? maxWidth,
         [FromQuery] int? maxWidth,
         [FromQuery] int? maxHeight,
         [FromQuery] int? maxHeight,
         [FromQuery] bool? enableSubtitlesInManifest,
         [FromQuery] bool? enableSubtitlesInManifest,
-        [FromQuery] bool enableAudioVbrEncoding = true)
+        [FromQuery] bool enableAudioVbrEncoding = true,
+        [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false)
     {
     {
         VideoRequestDto streamingRequest = new VideoRequestDto
         VideoRequestDto streamingRequest = new VideoRequestDto
         {
         {
@@ -251,7 +253,7 @@ public class DynamicHlsController : BaseJellyfinApiController
             Height = height,
             Height = height,
             VideoBitRate = videoBitRate,
             VideoBitRate = videoBitRate,
             SubtitleStreamIndex = subtitleStreamIndex,
             SubtitleStreamIndex = subtitleStreamIndex,
-            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
+            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External,
             MaxRefFrames = maxRefFrames,
             MaxRefFrames = maxRefFrames,
             MaxVideoBitDepth = maxVideoBitDepth,
             MaxVideoBitDepth = maxVideoBitDepth,
             RequireAvc = requireAvc ?? false,
             RequireAvc = requireAvc ?? false,
@@ -271,7 +273,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             MaxHeight = maxHeight,
             MaxHeight = maxHeight,
             MaxWidth = maxWidth,
             MaxWidth = maxWidth,
             EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true,
             EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true,
-            EnableAudioVbrEncoding = enableAudioVbrEncoding
+            EnableAudioVbrEncoding = enableAudioVbrEncoding,
+            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding
         };
         };
 
 
         // CTS lifecycle is managed internally.
         // CTS lifecycle is managed internally.
@@ -398,6 +401,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
     /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
     /// <param name="enableTrickplay">Enable trickplay image playlists being added to master playlist.</param>
     /// <param name="enableTrickplay">Enable trickplay image playlists being added to master playlist.</param>
     /// <param name="enableAudioVbrEncoding">Whether to enable Audio Encoding.</param>
     /// <param name="enableAudioVbrEncoding">Whether to enable Audio Encoding.</param>
+    /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
     [HttpGet("Videos/{itemId}/master.m3u8")]
     [HttpGet("Videos/{itemId}/master.m3u8")]
@@ -457,7 +461,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] bool enableAdaptiveBitrateStreaming = true,
         [FromQuery] bool enableAdaptiveBitrateStreaming = true,
         [FromQuery] bool enableTrickplay = true,
         [FromQuery] bool enableTrickplay = true,
-        [FromQuery] bool enableAudioVbrEncoding = true)
+        [FromQuery] bool enableAudioVbrEncoding = true,
+        [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false)
     {
     {
         var streamingRequest = new HlsVideoRequestDto
         var streamingRequest = new HlsVideoRequestDto
         {
         {
@@ -493,7 +498,7 @@ public class DynamicHlsController : BaseJellyfinApiController
             MaxHeight = maxHeight,
             MaxHeight = maxHeight,
             VideoBitRate = videoBitRate,
             VideoBitRate = videoBitRate,
             SubtitleStreamIndex = subtitleStreamIndex,
             SubtitleStreamIndex = subtitleStreamIndex,
-            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
+            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External,
             MaxRefFrames = maxRefFrames,
             MaxRefFrames = maxRefFrames,
             MaxVideoBitDepth = maxVideoBitDepth,
             MaxVideoBitDepth = maxVideoBitDepth,
             RequireAvc = requireAvc ?? false,
             RequireAvc = requireAvc ?? false,
@@ -512,7 +517,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             StreamOptions = streamOptions,
             StreamOptions = streamOptions,
             EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming,
             EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming,
             EnableTrickplay = enableTrickplay,
             EnableTrickplay = enableTrickplay,
-            EnableAudioVbrEncoding = enableAudioVbrEncoding
+            EnableAudioVbrEncoding = enableAudioVbrEncoding,
+            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding
         };
         };
 
 
         return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
         return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
@@ -572,6 +578,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
     /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
+    /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param>
     /// <response code="200">Audio stream returned.</response>
     /// <response code="200">Audio stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
     [HttpGet("Audio/{itemId}/master.m3u8")]
     [HttpGet("Audio/{itemId}/master.m3u8")]
@@ -629,7 +636,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] bool enableAdaptiveBitrateStreaming = true,
         [FromQuery] bool enableAdaptiveBitrateStreaming = true,
-        [FromQuery] bool enableAudioVbrEncoding = true)
+        [FromQuery] bool enableAudioVbrEncoding = true,
+        [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false)
     {
     {
         var streamingRequest = new HlsAudioRequestDto
         var streamingRequest = new HlsAudioRequestDto
         {
         {
@@ -663,7 +671,7 @@ public class DynamicHlsController : BaseJellyfinApiController
             Height = height,
             Height = height,
             VideoBitRate = videoBitRate,
             VideoBitRate = videoBitRate,
             SubtitleStreamIndex = subtitleStreamIndex,
             SubtitleStreamIndex = subtitleStreamIndex,
-            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
+            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External,
             MaxRefFrames = maxRefFrames,
             MaxRefFrames = maxRefFrames,
             MaxVideoBitDepth = maxVideoBitDepth,
             MaxVideoBitDepth = maxVideoBitDepth,
             RequireAvc = requireAvc ?? false,
             RequireAvc = requireAvc ?? false,
@@ -681,7 +689,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
             StreamOptions = streamOptions,
             StreamOptions = streamOptions,
             EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming,
             EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming,
-            EnableAudioVbrEncoding = enableAudioVbrEncoding
+            EnableAudioVbrEncoding = enableAudioVbrEncoding,
+            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding
         };
         };
 
 
         return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
         return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
@@ -741,6 +750,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
+    /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("Videos/{itemId}/main.m3u8")]
     [HttpGet("Videos/{itemId}/main.m3u8")]
@@ -797,7 +807,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] Dictionary<string, string> streamOptions,
-        [FromQuery] bool enableAudioVbrEncoding = true)
+        [FromQuery] bool enableAudioVbrEncoding = true,
+        [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false)
     {
     {
         using var cancellationTokenSource = new CancellationTokenSource();
         using var cancellationTokenSource = new CancellationTokenSource();
         var streamingRequest = new VideoRequestDto
         var streamingRequest = new VideoRequestDto
@@ -834,7 +845,7 @@ public class DynamicHlsController : BaseJellyfinApiController
             MaxHeight = maxHeight,
             MaxHeight = maxHeight,
             VideoBitRate = videoBitRate,
             VideoBitRate = videoBitRate,
             SubtitleStreamIndex = subtitleStreamIndex,
             SubtitleStreamIndex = subtitleStreamIndex,
-            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
+            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External,
             MaxRefFrames = maxRefFrames,
             MaxRefFrames = maxRefFrames,
             MaxVideoBitDepth = maxVideoBitDepth,
             MaxVideoBitDepth = maxVideoBitDepth,
             RequireAvc = requireAvc ?? false,
             RequireAvc = requireAvc ?? false,
@@ -851,7 +862,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
             StreamOptions = streamOptions,
             StreamOptions = streamOptions,
-            EnableAudioVbrEncoding = enableAudioVbrEncoding
+            EnableAudioVbrEncoding = enableAudioVbrEncoding,
+            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding
         };
         };
 
 
         return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
         return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
@@ -911,6 +923,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
+    /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param>
     /// <response code="200">Audio stream returned.</response>
     /// <response code="200">Audio stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("Audio/{itemId}/main.m3u8")]
     [HttpGet("Audio/{itemId}/main.m3u8")]
@@ -966,7 +979,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] Dictionary<string, string> streamOptions,
-        [FromQuery] bool enableAudioVbrEncoding = true)
+        [FromQuery] bool enableAudioVbrEncoding = true,
+        [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false)
     {
     {
         using var cancellationTokenSource = new CancellationTokenSource();
         using var cancellationTokenSource = new CancellationTokenSource();
         var streamingRequest = new StreamingRequestDto
         var streamingRequest = new StreamingRequestDto
@@ -1001,7 +1015,7 @@ public class DynamicHlsController : BaseJellyfinApiController
             Height = height,
             Height = height,
             VideoBitRate = videoBitRate,
             VideoBitRate = videoBitRate,
             SubtitleStreamIndex = subtitleStreamIndex,
             SubtitleStreamIndex = subtitleStreamIndex,
-            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
+            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External,
             MaxRefFrames = maxRefFrames,
             MaxRefFrames = maxRefFrames,
             MaxVideoBitDepth = maxVideoBitDepth,
             MaxVideoBitDepth = maxVideoBitDepth,
             RequireAvc = requireAvc ?? false,
             RequireAvc = requireAvc ?? false,
@@ -1018,7 +1032,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
             StreamOptions = streamOptions,
             StreamOptions = streamOptions,
-            EnableAudioVbrEncoding = enableAudioVbrEncoding
+            EnableAudioVbrEncoding = enableAudioVbrEncoding,
+            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding
         };
         };
 
 
         return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
         return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
@@ -1084,6 +1099,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
+    /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
     [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
@@ -1146,7 +1162,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] Dictionary<string, string> streamOptions,
-        [FromQuery] bool enableAudioVbrEncoding = true)
+        [FromQuery] bool enableAudioVbrEncoding = true,
+        [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false)
     {
     {
         var streamingRequest = new VideoRequestDto
         var streamingRequest = new VideoRequestDto
         {
         {
@@ -1185,7 +1202,7 @@ public class DynamicHlsController : BaseJellyfinApiController
             MaxHeight = maxHeight,
             MaxHeight = maxHeight,
             VideoBitRate = videoBitRate,
             VideoBitRate = videoBitRate,
             SubtitleStreamIndex = subtitleStreamIndex,
             SubtitleStreamIndex = subtitleStreamIndex,
-            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
+            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External,
             MaxRefFrames = maxRefFrames,
             MaxRefFrames = maxRefFrames,
             MaxVideoBitDepth = maxVideoBitDepth,
             MaxVideoBitDepth = maxVideoBitDepth,
             RequireAvc = requireAvc ?? false,
             RequireAvc = requireAvc ?? false,
@@ -1202,7 +1219,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
             StreamOptions = streamOptions,
             StreamOptions = streamOptions,
-            EnableAudioVbrEncoding = enableAudioVbrEncoding
+            EnableAudioVbrEncoding = enableAudioVbrEncoding,
+            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding
         };
         };
 
 
         return await GetDynamicSegment(streamingRequest, segmentId)
         return await GetDynamicSegment(streamingRequest, segmentId)
@@ -1267,6 +1285,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
+    /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
     [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
@@ -1328,7 +1347,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] Dictionary<string, string> streamOptions,
-        [FromQuery] bool enableAudioVbrEncoding = true)
+        [FromQuery] bool enableAudioVbrEncoding = true,
+        [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false)
     {
     {
         var streamingRequest = new StreamingRequestDto
         var streamingRequest = new StreamingRequestDto
         {
         {
@@ -1365,7 +1385,7 @@ public class DynamicHlsController : BaseJellyfinApiController
             Height = height,
             Height = height,
             VideoBitRate = videoBitRate,
             VideoBitRate = videoBitRate,
             SubtitleStreamIndex = subtitleStreamIndex,
             SubtitleStreamIndex = subtitleStreamIndex,
-            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
+            SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External,
             MaxRefFrames = maxRefFrames,
             MaxRefFrames = maxRefFrames,
             MaxVideoBitDepth = maxVideoBitDepth,
             MaxVideoBitDepth = maxVideoBitDepth,
             RequireAvc = requireAvc ?? false,
             RequireAvc = requireAvc ?? false,
@@ -1382,7 +1402,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
             StreamOptions = streamOptions,
             StreamOptions = streamOptions,
-            EnableAudioVbrEncoding = enableAudioVbrEncoding
+            EnableAudioVbrEncoding = enableAudioVbrEncoding,
+            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding
         };
         };
 
 
         return await GetDynamicSegment(streamingRequest, segmentId)
         return await GetDynamicSegment(streamingRequest, segmentId)

+ 6 - 0
Jellyfin.Api/Helpers/MediaInfoHelper.cs

@@ -293,6 +293,7 @@ public class MediaInfoHelper
                 mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
                 mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
                 mediaSource.TranscodingContainer = streamInfo.Container;
                 mediaSource.TranscodingContainer = streamInfo.Container;
                 mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
                 mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+                mediaSource.TranscodingUrl += "&alwaysBurnInSubtitleWhenTranscoding=true";
             }
             }
             else
             else
             {
             {
@@ -310,6 +311,11 @@ public class MediaInfoHelper
                     {
                     {
                         mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
                         mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
                     }
                     }
+
+                    if (streamInfo.AlwaysBurnInSubtitleWhenTranscoding)
+                    {
+                        mediaSource.TranscodingUrl += "&alwaysBurnInSubtitleWhenTranscoding=true";
+                    }
                 }
                 }
             }
             }
 
 

+ 2 - 0
MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs

@@ -193,6 +193,8 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
         public bool EnableAudioVbrEncoding { get; set; }
         public bool EnableAudioVbrEncoding { get; set; }
 
 
+        public bool AlwaysBurnInSubtitleWhenTranscoding { get; set; }
+
         public string GetOption(string qualifier, string name)
         public string GetOption(string qualifier, string name)
         {
         {
             var value = GetOption(qualifier + "-" + name);
             var value = GetOption(qualifier + "-" + name);

+ 22 - 16
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -941,7 +941,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
         {
             // DVBSUB uses the fixed canvas size 720x576
             // DVBSUB uses the fixed canvas size 720x576
             if (state.SubtitleStream is not null
             if (state.SubtitleStream is not null
-                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+                && ShouldEncodeSubtitle(state)
                 && !state.SubtitleStream.IsTextSubtitleStream
                 && !state.SubtitleStream.IsTextSubtitleStream
                 && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
                 && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
             {
             {
@@ -1240,7 +1240,7 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
             // sub2video for external graphical subtitles
             // sub2video for external graphical subtitles
             if (state.SubtitleStream is not null
             if (state.SubtitleStream is not null
-                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+                && ShouldEncodeSubtitle(state)
                 && !state.SubtitleStream.IsTextSubtitleStream
                 && !state.SubtitleStream.IsTextSubtitleStream
                 && state.SubtitleStream.IsExternal)
                 && state.SubtitleStream.IsExternal)
             {
             {
@@ -2554,7 +2554,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
             }
 
 
             var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
             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;
                 var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 
 
@@ -2755,7 +2755,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 if (state.AudioStream.IsExternal)
                 if (state.AudioStream.IsExternal)
                 {
                 {
                     bool hasExternalGraphicsSubs = state.SubtitleStream is not null
                     bool hasExternalGraphicsSubs = state.SubtitleStream is not null
-                        && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+                        && ShouldEncodeSubtitle(state)
                         && state.SubtitleStream.IsExternal
                         && state.SubtitleStream.IsExternal
                         && !state.SubtitleStream.IsTextSubtitleStream;
                         && !state.SubtitleStream.IsTextSubtitleStream;
                     int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
                     int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
@@ -3475,7 +3475,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             var doToneMap = IsSwTonemapAvailable(state, options);
             var doToneMap = IsSwTonemapAvailable(state, options);
             var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
             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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 
 
@@ -3618,7 +3618,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             var doCuTonemap = IsHwTonemapAvailable(state, options);
             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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasAssSubs = hasSubs
             var hasAssSubs = hasSubs
@@ -3824,7 +3824,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             var doOclTonemap = IsHwTonemapAvailable(state, options);
             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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasAssSubs = hasSubs
             var hasAssSubs = hasSubs
@@ -4064,7 +4064,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
             var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
             var doTonemap = doVppTonemap || doOclTonemap;
             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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasAssSubs = hasSubs
             var hasAssSubs = hasSubs
@@ -4320,7 +4320,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             var doTonemap = doVaVppTonemap || doOclTonemap;
             var doTonemap = doVaVppTonemap || doOclTonemap;
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasAssSubs = hasSubs
             var hasAssSubs = hasSubs
@@ -4636,7 +4636,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             var doTonemap = doVaVppTonemap || doOclTonemap;
             var doTonemap = doVaVppTonemap || doOclTonemap;
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasAssSubs = hasSubs
             var hasAssSubs = hasSubs
@@ -4858,7 +4858,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
             var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasAssSubs = hasSubs
             var hasAssSubs = hasSubs
@@ -5091,7 +5091,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             var doOclTonemap = IsHwTonemapAvailable(state, options);
             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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = 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 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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasAssSubs = hasSubs
             var hasAssSubs = hasSubs
@@ -5511,7 +5511,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             var doDeintH2645 = doDeintH264 || doDeintHevc;
             var doOclTonemap = IsHwTonemapAvailable(state, options);
             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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasAssSubs = hasSubs
             var hasAssSubs = hasSubs
@@ -5722,7 +5722,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 return string.Empty;
                 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 hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
             var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 
 
@@ -7156,7 +7156,7 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
                 args += keyFrameArg;
                 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;
                 var hasCopyTs = false;
 
 
@@ -7361,5 +7361,11 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
         {
             return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
             return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
         }
         }
+
+        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
+        {
+            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
+        }
     }
     }
 }
 }

+ 3 - 15
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -639,7 +639,8 @@ namespace MediaBrowser.Model.Dlna
                 RunTimeTicks = item.RunTimeTicks,
                 RunTimeTicks = item.RunTimeTicks,
                 Context = options.Context,
                 Context = options.Context,
                 DeviceProfile = options.Profile,
                 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;
             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)
                     if (subtitleStream is not null)
                     {
                     {
                         var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
                         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.SubtitleFormat = subtitleProfile.Format;
                         playlistItem.SubtitleCodecs = [subtitleProfile.Format];
                         playlistItem.SubtitleCodecs = [subtitleProfile.Format];
                     }
                     }

+ 6 - 1
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -270,6 +270,11 @@ public class StreamInfo
     /// </summary>
     /// </summary>
     public bool EnableAudioVbrEncoding { get; set; }
     public bool EnableAudioVbrEncoding { get; set; }
 
 
+    /// <summary>
+    /// Gets or sets a value indicating whether always burn in subtitles when transcoding.
+    /// </summary>
+    public bool AlwaysBurnInSubtitleWhenTranscoding { get; set; }
+
     /// <summary>
     /// <summary>
     /// Gets a value indicating whether the stream is direct.
     /// Gets a value indicating whether the stream is direct.
     /// </summary>
     /// </summary>
@@ -953,7 +958,7 @@ public class StreamInfo
         list.Add(new NameValuePair("VideoCodec", videoCodecs));
         list.Add(new NameValuePair("VideoCodec", videoCodecs));
         list.Add(new NameValuePair("AudioCodec", audioCodecs));
         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("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("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("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));
         list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));