|  |  | 1 |  | #pragma warning disable CA1819 // Properties should not return arrays | 
|  |  | 2 |  |  | 
|  |  | 3 |  | using System; | 
|  |  | 4 |  | using System.Collections.Generic; | 
|  |  | 5 |  | using System.ComponentModel; | 
|  |  | 6 |  | using System.Globalization; | 
|  |  | 7 |  | using System.Linq; | 
|  |  | 8 |  | using System.Text; | 
|  |  | 9 |  | using Jellyfin.Data.Enums; | 
|  |  | 10 |  | using Jellyfin.Extensions; | 
|  |  | 11 |  | using MediaBrowser.Model.Drawing; | 
|  |  | 12 |  | using MediaBrowser.Model.Dto; | 
|  |  | 13 |  | using MediaBrowser.Model.Entities; | 
|  |  | 14 |  | using MediaBrowser.Model.MediaInfo; | 
|  |  | 15 |  | using MediaBrowser.Model.Session; | 
|  |  | 16 |  |  | 
|  |  | 17 |  | namespace MediaBrowser.Model.Dlna; | 
|  |  | 18 |  |  | 
|  |  | 19 |  | /// <summary> | 
|  |  | 20 |  | /// Class holding information on a stream. | 
|  |  | 21 |  | /// </summary> | 
|  |  | 22 |  | public class StreamInfo | 
|  |  | 23 |  | { | 
|  |  | 24 |  |     /// <summary> | 
|  |  | 25 |  |     /// Initializes a new instance of the <see cref="StreamInfo"/> class. | 
|  |  | 26 |  |     /// </summary> | 
|  |  | 27 |  |     public StreamInfo() | 
|  |  | 28 |  |     { | 
|  |  | 29 |  |         AudioCodecs = []; | 
|  |  | 30 |  |         VideoCodecs = []; | 
|  |  | 31 |  |         SubtitleCodecs = []; | 
|  |  | 32 |  |         StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | 
|  |  | 33 |  |     } | 
|  |  | 34 |  |  | 
|  |  | 35 |  |     /// <summary> | 
|  |  | 36 |  |     /// Gets or sets the item id. | 
|  |  | 37 |  |     /// </summary> | 
|  |  | 38 |  |     /// <value>The item id.</value> | 
|  |  | 39 |  |     public Guid ItemId { get; set; } | 
|  |  | 40 |  |  | 
|  |  | 41 |  |     /// <summary> | 
|  |  | 42 |  |     /// Gets or sets the play method. | 
|  |  | 43 |  |     /// </summary> | 
|  |  | 44 |  |     /// <value>The play method.</value> | 
|  |  | 45 |  |     public PlayMethod PlayMethod { get; set; } | 
|  |  | 46 |  |  | 
|  |  | 47 |  |     /// <summary> | 
|  |  | 48 |  |     /// Gets or sets the encoding context. | 
|  |  | 49 |  |     /// </summary> | 
|  |  | 50 |  |     /// <value>The encoding context.</value> | 
|  |  | 51 |  |     public EncodingContext Context { get; set; } | 
|  |  | 52 |  |  | 
|  |  | 53 |  |     /// <summary> | 
|  |  | 54 |  |     /// Gets or sets the media type. | 
|  |  | 55 |  |     /// </summary> | 
|  |  | 56 |  |     /// <value>The media type.</value> | 
|  |  | 57 |  |     public DlnaProfileType MediaType { get; set; } | 
|  |  | 58 |  |  | 
|  |  | 59 |  |     /// <summary> | 
|  |  | 60 |  |     /// Gets or sets the container. | 
|  |  | 61 |  |     /// </summary> | 
|  |  | 62 |  |     /// <value>The container.</value> | 
|  |  | 63 |  |     public string? Container { get; set; } | 
|  |  | 64 |  |  | 
|  |  | 65 |  |     /// <summary> | 
|  |  | 66 |  |     /// Gets or sets the sub protocol. | 
|  |  | 67 |  |     /// </summary> | 
|  |  | 68 |  |     /// <value>The sub protocol.</value> | 
|  |  | 69 |  |     public MediaStreamProtocol SubProtocol { get; set; } | 
|  |  | 70 |  |  | 
|  |  | 71 |  |     /// <summary> | 
|  |  | 72 |  |     /// Gets or sets the start position ticks. | 
|  |  | 73 |  |     /// </summary> | 
|  |  | 74 |  |     /// <value>The start position ticks.</value> | 
|  |  | 75 |  |     public long StartPositionTicks { get; set; } | 
|  |  | 76 |  |  | 
|  |  | 77 |  |     /// <summary> | 
|  |  | 78 |  |     /// Gets or sets the segment length. | 
|  |  | 79 |  |     /// </summary> | 
|  |  | 80 |  |     /// <value>The segment length.</value> | 
|  |  | 81 |  |     public int? SegmentLength { get; set; } | 
|  |  | 82 |  |  | 
|  |  | 83 |  |     /// <summary> | 
|  |  | 84 |  |     /// Gets or sets the minimum segments count. | 
|  |  | 85 |  |     /// </summary> | 
|  |  | 86 |  |     /// <value>The minimum segments count.</value> | 
|  |  | 87 |  |     public int? MinSegments { get; set; } | 
|  |  | 88 |  |  | 
|  |  | 89 |  |     /// <summary> | 
|  |  | 90 |  |     /// Gets or sets a value indicating whether the stream can be broken on non-keyframes. | 
|  |  | 91 |  |     /// </summary> | 
|  |  | 92 |  |     public bool BreakOnNonKeyFrames { get; set; } | 
|  |  | 93 |  |  | 
|  |  | 94 |  |     /// <summary> | 
|  |  | 95 |  |     /// Gets or sets a value indicating whether the stream requires AVC. | 
|  |  | 96 |  |     /// </summary> | 
|  |  | 97 |  |     public bool RequireAvc { get; set; } | 
|  |  | 98 |  |  | 
|  |  | 99 |  |     /// <summary> | 
|  |  | 100 |  |     /// Gets or sets a value indicating whether the stream requires AVC. | 
|  |  | 101 |  |     /// </summary> | 
|  |  | 102 |  |     public bool RequireNonAnamorphic { get; set; } | 
|  |  | 103 |  |  | 
|  |  | 104 |  |     /// <summary> | 
|  |  | 105 |  |     /// Gets or sets a value indicating whether timestamps should be copied. | 
|  |  | 106 |  |     /// </summary> | 
|  |  | 107 |  |     public bool CopyTimestamps { get; set; } | 
|  |  | 108 |  |  | 
|  |  | 109 |  |     /// <summary> | 
|  |  | 110 |  |     /// Gets or sets a value indicating whether timestamps should be copied. | 
|  |  | 111 |  |     /// </summary> | 
|  |  | 112 |  |     public bool EnableMpegtsM2TsMode { get; set; } | 
|  |  | 113 |  |  | 
|  |  | 114 |  |     /// <summary> | 
|  |  | 115 |  |     /// Gets or sets a value indicating whether the subtitle manifest is enabled. | 
|  |  | 116 |  |     /// </summary> | 
|  |  | 117 |  |     public bool EnableSubtitlesInManifest { get; set; } | 
|  |  | 118 |  |  | 
|  |  | 119 |  |     /// <summary> | 
|  |  | 120 |  |     /// Gets or sets the audio codecs. | 
|  |  | 121 |  |     /// </summary> | 
|  |  | 122 |  |     /// <value>The audio codecs.</value> | 
|  |  | 123 |  |     public IReadOnlyList<string> AudioCodecs { get; set; } | 
|  |  | 124 |  |  | 
|  |  | 125 |  |     /// <summary> | 
|  |  | 126 |  |     /// Gets or sets the video codecs. | 
|  |  | 127 |  |     /// </summary> | 
|  |  | 128 |  |     /// <value>The video codecs.</value> | 
|  |  | 129 |  |     public IReadOnlyList<string> VideoCodecs { get; set; } | 
|  |  | 130 |  |  | 
|  |  | 131 |  |     /// <summary> | 
|  |  | 132 |  |     /// Gets or sets the audio stream index. | 
|  |  | 133 |  |     /// </summary> | 
|  |  | 134 |  |     /// <value>The audio stream index.</value> | 
|  |  | 135 |  |     public int? AudioStreamIndex { get; set; } | 
|  |  | 136 |  |  | 
|  |  | 137 |  |     /// <summary> | 
|  |  | 138 |  |     /// Gets or sets the video stream index. | 
|  |  | 139 |  |     /// </summary> | 
|  |  | 140 |  |     /// <value>The subtitle stream index.</value> | 
|  |  | 141 |  |     public int? SubtitleStreamIndex { get; set; } | 
|  |  | 142 |  |  | 
|  |  | 143 |  |     /// <summary> | 
|  |  | 144 |  |     /// Gets or sets the maximum transcoding audio channels. | 
|  |  | 145 |  |     /// </summary> | 
|  |  | 146 |  |     /// <value>The maximum transcoding audio channels.</value> | 
|  |  | 147 |  |     public int? TranscodingMaxAudioChannels { get; set; } | 
|  |  | 148 |  |  | 
|  |  | 149 |  |     /// <summary> | 
|  |  | 150 |  |     /// Gets or sets the global maximum audio channels. | 
|  |  | 151 |  |     /// </summary> | 
|  |  | 152 |  |     /// <value>The global maximum audio channels.</value> | 
|  |  | 153 |  |     public int? GlobalMaxAudioChannels { get; set; } | 
|  |  | 154 |  |  | 
|  |  | 155 |  |     /// <summary> | 
|  |  | 156 |  |     /// Gets or sets the audio bitrate. | 
|  |  | 157 |  |     /// </summary> | 
|  |  | 158 |  |     /// <value>The audio bitrate.</value> | 
|  |  | 159 |  |     public int? AudioBitrate { get; set; } | 
|  |  | 160 |  |  | 
|  |  | 161 |  |     /// <summary> | 
|  |  | 162 |  |     /// Gets or sets the audio sample rate. | 
|  |  | 163 |  |     /// </summary> | 
|  |  | 164 |  |     /// <value>The audio sample rate.</value> | 
|  |  | 165 |  |     public int? AudioSampleRate { get; set; } | 
|  |  | 166 |  |  | 
|  |  | 167 |  |     /// <summary> | 
|  |  | 168 |  |     /// Gets or sets the video bitrate. | 
|  |  | 169 |  |     /// </summary> | 
|  |  | 170 |  |     /// <value>The video bitrate.</value> | 
|  |  | 171 |  |     public int? VideoBitrate { get; set; } | 
|  |  | 172 |  |  | 
|  |  | 173 |  |     /// <summary> | 
|  |  | 174 |  |     /// Gets or sets the maximum output width. | 
|  |  | 175 |  |     /// </summary> | 
|  |  | 176 |  |     /// <value>The output width.</value> | 
|  |  | 177 |  |     public int? MaxWidth { get; set; } | 
|  |  | 178 |  |  | 
|  |  | 179 |  |     /// <summary> | 
|  |  | 180 |  |     /// Gets or sets the maximum output height. | 
|  |  | 181 |  |     /// </summary> | 
|  |  | 182 |  |     /// <value>The maximum output height.</value> | 
|  |  | 183 |  |     public int? MaxHeight { get; set; } | 
|  |  | 184 |  |  | 
|  |  | 185 |  |     /// <summary> | 
|  |  | 186 |  |     /// Gets or sets the maximum framerate. | 
|  |  | 187 |  |     /// </summary> | 
|  |  | 188 |  |     /// <value>The maximum framerate.</value> | 
|  |  | 189 |  |     public float? MaxFramerate { get; set; } | 
|  |  | 190 |  |  | 
|  |  | 191 |  |     /// <summary> | 
|  |  | 192 |  |     /// Gets or sets the device profile. | 
|  |  | 193 |  |     /// </summary> | 
|  |  | 194 |  |     /// <value>The device profile.</value> | 
|  |  | 195 |  |     public required DeviceProfile DeviceProfile { get; set; } | 
|  |  | 196 |  |  | 
|  |  | 197 |  |     /// <summary> | 
|  |  | 198 |  |     /// Gets or sets the device profile id. | 
|  |  | 199 |  |     /// </summary> | 
|  |  | 200 |  |     /// <value>The device profile id.</value> | 
|  |  | 201 |  |     public string? DeviceProfileId { get; set; } | 
|  |  | 202 |  |  | 
|  |  | 203 |  |     /// <summary> | 
|  |  | 204 |  |     /// Gets or sets the device id. | 
|  |  | 205 |  |     /// </summary> | 
|  |  | 206 |  |     /// <value>The device id.</value> | 
|  |  | 207 |  |     public string? DeviceId { get; set; } | 
|  |  | 208 |  |  | 
|  |  | 209 |  |     /// <summary> | 
|  |  | 210 |  |     /// Gets or sets the runtime ticks. | 
|  |  | 211 |  |     /// </summary> | 
|  |  | 212 |  |     /// <value>The runtime ticks.</value> | 
|  |  | 213 |  |     public long? RunTimeTicks { get; set; } | 
|  |  | 214 |  |  | 
|  |  | 215 |  |     /// <summary> | 
|  |  | 216 |  |     /// Gets or sets the transcode seek info. | 
|  |  | 217 |  |     /// </summary> | 
|  |  | 218 |  |     /// <value>The transcode seek info.</value> | 
|  |  | 219 |  |     public TranscodeSeekInfo TranscodeSeekInfo { get; set; } | 
|  |  | 220 |  |  | 
|  |  | 221 |  |     /// <summary> | 
|  |  | 222 |  |     /// Gets or sets a value indicating whether content length should be estimated. | 
|  |  | 223 |  |     /// </summary> | 
|  |  | 224 |  |     public bool EstimateContentLength { get; set; } | 
|  |  | 225 |  |  | 
|  |  | 226 |  |     /// <summary> | 
|  |  | 227 |  |     /// Gets or sets the media source info. | 
|  |  | 228 |  |     /// </summary> | 
|  |  | 229 |  |     /// <value>The media source info.</value> | 
|  |  | 230 |  |     public MediaSourceInfo? MediaSource { get; set; } | 
|  |  | 231 |  |  | 
|  |  | 232 |  |     /// <summary> | 
|  |  | 233 |  |     /// Gets or sets the subtitle codecs. | 
|  |  | 234 |  |     /// </summary> | 
|  |  | 235 |  |     /// <value>The subtitle codecs.</value> | 
|  |  | 236 |  |     public IReadOnlyList<string> SubtitleCodecs { get; set; } | 
|  |  | 237 |  |  | 
|  |  | 238 |  |     /// <summary> | 
|  |  | 239 |  |     /// Gets or sets the subtitle delivery method. | 
|  |  | 240 |  |     /// </summary> | 
|  |  | 241 |  |     /// <value>The subtitle delivery method.</value> | 
|  |  | 242 |  |     public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } | 
|  |  | 243 |  |  | 
|  |  | 244 |  |     /// <summary> | 
|  |  | 245 |  |     /// Gets or sets the subtitle format. | 
|  |  | 246 |  |     /// </summary> | 
|  |  | 247 |  |     /// <value>The subtitle format.</value> | 
|  |  | 248 |  |     public string? SubtitleFormat { get; set; } | 
|  |  | 249 |  |  | 
|  |  | 250 |  |     /// <summary> | 
|  |  | 251 |  |     /// Gets or sets the play session id. | 
|  |  | 252 |  |     /// </summary> | 
|  |  | 253 |  |     /// <value>The play session id.</value> | 
|  |  | 254 |  |     public string? PlaySessionId { get; set; } | 
|  |  | 255 |  |  | 
|  |  | 256 |  |     /// <summary> | 
|  |  | 257 |  |     /// Gets or sets the transcode reasons. | 
|  |  | 258 |  |     /// </summary> | 
|  |  | 259 |  |     /// <value>The transcode reasons.</value> | 
|  |  | 260 |  |     public TranscodeReason TranscodeReasons { get; set; } | 
|  |  | 261 |  |  | 
|  |  | 262 |  |     /// <summary> | 
|  |  | 263 |  |     /// Gets the stream options. | 
|  |  | 264 |  |     /// </summary> | 
|  |  | 265 |  |     /// <value>The stream options.</value> | 
|  |  | 266 |  |     public Dictionary<string, string> StreamOptions { get; private set; } | 
|  |  | 267 |  |  | 
|  |  | 268 |  |     /// <summary> | 
|  |  | 269 |  |     /// Gets the media source id. | 
|  |  | 270 |  |     /// </summary> | 
|  |  | 271 |  |     /// <value>The media source id.</value> | 
|  | 201396 | 272 |  |     public string? MediaSourceId => MediaSource?.Id; | 
|  |  | 273 |  |  | 
|  |  | 274 |  |     /// <summary> | 
|  |  | 275 |  |     /// Gets or sets a value indicating whether audio VBR encoding is enabled. | 
|  |  | 276 |  |     /// </summary> | 
|  |  | 277 |  |     public bool EnableAudioVbrEncoding { get; set; } | 
|  |  | 278 |  |  | 
|  |  | 279 |  |     /// <summary> | 
|  |  | 280 |  |     /// Gets or sets a value indicating whether always burn in subtitles when transcoding. | 
|  |  | 281 |  |     /// </summary> | 
|  |  | 282 |  |     public bool AlwaysBurnInSubtitleWhenTranscoding { get; set; } | 
|  |  | 283 |  |  | 
|  |  | 284 |  |     /// <summary> | 
|  |  | 285 |  |     /// Gets a value indicating whether the stream is direct. | 
|  |  | 286 |  |     /// </summary> | 
|  | 603096 | 287 |  |     public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay) | 
|  | 603096 | 288 |  |         && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay; | 
|  |  | 289 |  |  | 
|  |  | 290 |  |     /// <summary> | 
|  |  | 291 |  |     /// Gets the audio stream that will be used in the output stream. | 
|  |  | 292 |  |     /// </summary> | 
|  |  | 293 |  |     /// <value>The audio stream.</value> | 
|  | 692 | 294 |  |     public MediaStream? TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex); | 
|  |  | 295 |  |  | 
|  |  | 296 |  |     /// <summary> | 
|  |  | 297 |  |     /// Gets the video stream that will be used in the output stream. | 
|  |  | 298 |  |     /// </summary> | 
|  |  | 299 |  |     /// <value>The video stream.</value> | 
|  | 1006 | 300 |  |     public MediaStream? TargetVideoStream => MediaSource?.VideoStream; | 
|  |  | 301 |  |  | 
|  |  | 302 |  |     /// <summary> | 
|  |  | 303 |  |     /// Gets the audio sample rate that will be in the output stream. | 
|  |  | 304 |  |     /// </summary> | 
|  |  | 305 |  |     /// <value>The target audio sample rate.</value> | 
|  |  | 306 |  |     public int? TargetAudioSampleRate | 
|  |  | 307 |  |     { | 
|  |  | 308 |  |         get | 
|  |  | 309 |  |         { | 
|  | 0 | 310 |  |             var stream = TargetAudioStream; | 
|  | 0 | 311 |  |             return AudioSampleRate.HasValue && !IsDirectStream | 
|  | 0 | 312 |  |                 ? AudioSampleRate | 
|  | 0 | 313 |  |                 : stream?.SampleRate; | 
|  |  | 314 |  |         } | 
|  |  | 315 |  |     } | 
|  |  | 316 |  |  | 
|  |  | 317 |  |     /// <summary> | 
|  |  | 318 |  |     /// Gets the audio bit depth that will be in the output stream. | 
|  |  | 319 |  |     /// </summary> | 
|  |  | 320 |  |     /// <value>The target bit depth.</value> | 
|  |  | 321 |  |     public int? TargetAudioBitDepth | 
|  |  | 322 |  |     { | 
|  |  | 323 |  |         get | 
|  |  | 324 |  |         { | 
|  | 0 | 325 |  |             if (IsDirectStream) | 
|  |  | 326 |  |             { | 
|  | 0 | 327 |  |                 return TargetAudioStream?.BitDepth; | 
|  |  | 328 |  |             } | 
|  |  | 329 |  |  | 
|  | 0 | 330 |  |             var targetAudioCodecs = TargetAudioCodec; | 
|  | 0 | 331 |  |             var audioCodec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0]; | 
|  | 0 | 332 |  |             if (!string.IsNullOrEmpty(audioCodec)) | 
|  |  | 333 |  |             { | 
|  | 0 | 334 |  |                 return GetTargetAudioBitDepth(audioCodec); | 
|  |  | 335 |  |             } | 
|  |  | 336 |  |  | 
|  | 0 | 337 |  |             return TargetAudioStream?.BitDepth; | 
|  |  | 338 |  |         } | 
|  |  | 339 |  |     } | 
|  |  | 340 |  |  | 
|  |  | 341 |  |     /// <summary> | 
|  |  | 342 |  |     /// Gets the video bit depth that will be in the output stream. | 
|  |  | 343 |  |     /// </summary> | 
|  |  | 344 |  |     /// <value>The target video bit depth.</value> | 
|  |  | 345 |  |     public int? TargetVideoBitDepth | 
|  |  | 346 |  |     { | 
|  |  | 347 |  |         get | 
|  |  | 348 |  |         { | 
|  | 96 | 349 |  |             if (IsDirectStream) | 
|  |  | 350 |  |             { | 
|  | 0 | 351 |  |                 return TargetVideoStream?.BitDepth; | 
|  |  | 352 |  |             } | 
|  |  | 353 |  |  | 
|  | 96 | 354 |  |             var targetVideoCodecs = TargetVideoCodec; | 
|  | 96 | 355 |  |             var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0]; | 
|  | 96 | 356 |  |             if (!string.IsNullOrEmpty(videoCodec)) | 
|  |  | 357 |  |             { | 
|  | 96 | 358 |  |                 return GetTargetVideoBitDepth(videoCodec); | 
|  |  | 359 |  |             } | 
|  |  | 360 |  |  | 
|  | 0 | 361 |  |             return TargetVideoStream?.BitDepth; | 
|  |  | 362 |  |         } | 
|  |  | 363 |  |     } | 
|  |  | 364 |  |  | 
|  |  | 365 |  |     /// <summary> | 
|  |  | 366 |  |     /// Gets the target reference frames that will be in the output stream. | 
|  |  | 367 |  |     /// </summary> | 
|  |  | 368 |  |     /// <value>The target reference frames.</value> | 
|  |  | 369 |  |     public int? TargetRefFrames | 
|  |  | 370 |  |     { | 
|  |  | 371 |  |         get | 
|  |  | 372 |  |         { | 
|  | 0 | 373 |  |             if (IsDirectStream) | 
|  |  | 374 |  |             { | 
|  | 0 | 375 |  |                 return TargetVideoStream?.RefFrames; | 
|  |  | 376 |  |             } | 
|  |  | 377 |  |  | 
|  | 0 | 378 |  |             var targetVideoCodecs = TargetVideoCodec; | 
|  | 0 | 379 |  |             var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0]; | 
|  | 0 | 380 |  |             if (!string.IsNullOrEmpty(videoCodec)) | 
|  |  | 381 |  |             { | 
|  | 0 | 382 |  |                 return GetTargetRefFrames(videoCodec); | 
|  |  | 383 |  |             } | 
|  |  | 384 |  |  | 
|  | 0 | 385 |  |             return TargetVideoStream?.RefFrames; | 
|  |  | 386 |  |         } | 
|  |  | 387 |  |     } | 
|  |  | 388 |  |  | 
|  |  | 389 |  |     /// <summary> | 
|  |  | 390 |  |     /// Gets the target framerate that will be in the output stream. | 
|  |  | 391 |  |     /// </summary> | 
|  |  | 392 |  |     /// <value>The target framerate.</value> | 
|  |  | 393 |  |     public float? TargetFramerate | 
|  |  | 394 |  |     { | 
|  |  | 395 |  |         get | 
|  |  | 396 |  |         { | 
|  | 0 | 397 |  |             var stream = TargetVideoStream; | 
|  | 0 | 398 |  |             return MaxFramerate.HasValue && !IsDirectStream | 
|  | 0 | 399 |  |                 ? MaxFramerate | 
|  | 0 | 400 |  |                 : stream?.ReferenceFrameRate; | 
|  |  | 401 |  |         } | 
|  |  | 402 |  |     } | 
|  |  | 403 |  |  | 
|  |  | 404 |  |     /// <summary> | 
|  |  | 405 |  |     /// Gets the target video level that will be in the output stream. | 
|  |  | 406 |  |     /// </summary> | 
|  |  | 407 |  |     /// <value>The target video level.</value> | 
|  |  | 408 |  |     public double? TargetVideoLevel | 
|  |  | 409 |  |     { | 
|  |  | 410 |  |         get | 
|  |  | 411 |  |         { | 
|  | 96 | 412 |  |             if (IsDirectStream) | 
|  |  | 413 |  |             { | 
|  | 0 | 414 |  |                 return TargetVideoStream?.Level; | 
|  |  | 415 |  |             } | 
|  |  | 416 |  |  | 
|  | 96 | 417 |  |             var targetVideoCodecs = TargetVideoCodec; | 
|  | 96 | 418 |  |             var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0]; | 
|  | 96 | 419 |  |             if (!string.IsNullOrEmpty(videoCodec)) | 
|  |  | 420 |  |             { | 
|  | 96 | 421 |  |                 return GetTargetVideoLevel(videoCodec); | 
|  |  | 422 |  |             } | 
|  |  | 423 |  |  | 
|  | 0 | 424 |  |             return TargetVideoStream?.Level; | 
|  |  | 425 |  |         } | 
|  |  | 426 |  |     } | 
|  |  | 427 |  |  | 
|  |  | 428 |  |     /// <summary> | 
|  |  | 429 |  |     /// Gets the target packet length that will be in the output stream. | 
|  |  | 430 |  |     /// </summary> | 
|  |  | 431 |  |     /// <value>The target packet length.</value> | 
|  |  | 432 |  |     public int? TargetPacketLength | 
|  |  | 433 |  |     { | 
|  |  | 434 |  |         get | 
|  |  | 435 |  |         { | 
|  | 0 | 436 |  |             var stream = TargetVideoStream; | 
|  | 0 | 437 |  |             return !IsDirectStream | 
|  | 0 | 438 |  |                 ? null | 
|  | 0 | 439 |  |                 : stream?.PacketLength; | 
|  |  | 440 |  |         } | 
|  |  | 441 |  |     } | 
|  |  | 442 |  |  | 
|  |  | 443 |  |     /// <summary> | 
|  |  | 444 |  |     /// Gets the target video profile that will be in the output stream. | 
|  |  | 445 |  |     /// </summary> | 
|  |  | 446 |  |     /// <value>The target video profile.</value> | 
|  |  | 447 |  |     public string? TargetVideoProfile | 
|  |  | 448 |  |     { | 
|  |  | 449 |  |         get | 
|  |  | 450 |  |         { | 
|  | 96 | 451 |  |             if (IsDirectStream) | 
|  |  | 452 |  |             { | 
|  | 0 | 453 |  |                 return TargetVideoStream?.Profile; | 
|  |  | 454 |  |             } | 
|  |  | 455 |  |  | 
|  | 96 | 456 |  |             var targetVideoCodecs = TargetVideoCodec; | 
|  | 96 | 457 |  |             var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0]; | 
|  | 96 | 458 |  |             if (!string.IsNullOrEmpty(videoCodec)) | 
|  |  | 459 |  |             { | 
|  | 96 | 460 |  |                 return GetOption(videoCodec, "profile"); | 
|  |  | 461 |  |             } | 
|  |  | 462 |  |  | 
|  | 0 | 463 |  |             return TargetVideoStream?.Profile; | 
|  |  | 464 |  |         } | 
|  |  | 465 |  |     } | 
|  |  | 466 |  |  | 
|  |  | 467 |  |     /// <summary> | 
|  |  | 468 |  |     /// Gets the target video range type that will be in the output stream. | 
|  |  | 469 |  |     /// </summary> | 
|  |  | 470 |  |     /// <value>The video range type.</value> | 
|  |  | 471 |  |     public VideoRangeType TargetVideoRangeType | 
|  |  | 472 |  |     { | 
|  |  | 473 |  |         get | 
|  |  | 474 |  |         { | 
|  | 0 | 475 |  |             if (IsDirectStream) | 
|  |  | 476 |  |             { | 
|  | 0 | 477 |  |                 return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown; | 
|  |  | 478 |  |             } | 
|  |  | 479 |  |  | 
|  | 0 | 480 |  |             var targetVideoCodecs = TargetVideoCodec; | 
|  | 0 | 481 |  |             var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0]; | 
|  | 0 | 482 |  |             if (!string.IsNullOrEmpty(videoCodec) | 
|  | 0 | 483 |  |                 && Enum.TryParse(GetOption(videoCodec, "rangetype"), true, out VideoRangeType videoRangeType)) | 
|  |  | 484 |  |             { | 
|  | 0 | 485 |  |                 return videoRangeType; | 
|  |  | 486 |  |             } | 
|  |  | 487 |  |  | 
|  | 0 | 488 |  |             return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown; | 
|  |  | 489 |  |         } | 
|  |  | 490 |  |     } | 
|  |  | 491 |  |  | 
|  |  | 492 |  |     /// <summary> | 
|  |  | 493 |  |     /// Gets the target video codec tag. | 
|  |  | 494 |  |     /// </summary> | 
|  |  | 495 |  |     /// <value>The video codec tag.</value> | 
|  |  | 496 |  |     public string? TargetVideoCodecTag | 
|  |  | 497 |  |     { | 
|  |  | 498 |  |         get | 
|  |  | 499 |  |         { | 
|  | 0 | 500 |  |             var stream = TargetVideoStream; | 
|  | 0 | 501 |  |             return !IsDirectStream | 
|  | 0 | 502 |  |                 ? null | 
|  | 0 | 503 |  |                 : stream?.CodecTag; | 
|  |  | 504 |  |         } | 
|  |  | 505 |  |     } | 
|  |  | 506 |  |  | 
|  |  | 507 |  |     /// <summary> | 
|  |  | 508 |  |     /// Gets the audio bitrate that will be in the output stream. | 
|  |  | 509 |  |     /// </summary> | 
|  |  | 510 |  |     /// <value>The audio bitrate.</value> | 
|  |  | 511 |  |     public int? TargetAudioBitrate | 
|  |  | 512 |  |     { | 
|  |  | 513 |  |         get | 
|  |  | 514 |  |         { | 
|  | 0 | 515 |  |             var stream = TargetAudioStream; | 
|  | 0 | 516 |  |             return AudioBitrate.HasValue && !IsDirectStream | 
|  | 0 | 517 |  |                 ? AudioBitrate | 
|  | 0 | 518 |  |                 : stream?.BitRate; | 
|  |  | 519 |  |         } | 
|  |  | 520 |  |     } | 
|  |  | 521 |  |  | 
|  |  | 522 |  |     /// <summary> | 
|  |  | 523 |  |     /// Gets the amount of audio channels that will be in the output stream. | 
|  |  | 524 |  |     /// </summary> | 
|  |  | 525 |  |     /// <value>The target audio channels.</value> | 
|  |  | 526 |  |     public int? TargetAudioChannels | 
|  |  | 527 |  |     { | 
|  |  | 528 |  |         get | 
|  |  | 529 |  |         { | 
|  | 0 | 530 |  |             if (IsDirectStream) | 
|  |  | 531 |  |             { | 
|  | 0 | 532 |  |                 return TargetAudioStream?.Channels; | 
|  |  | 533 |  |             } | 
|  |  | 534 |  |  | 
|  | 0 | 535 |  |             var targetAudioCodecs = TargetAudioCodec; | 
|  | 0 | 536 |  |             var codec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0]; | 
|  | 0 | 537 |  |             if (!string.IsNullOrEmpty(codec)) | 
|  |  | 538 |  |             { | 
|  | 0 | 539 |  |                 return GetTargetRefFrames(codec); | 
|  |  | 540 |  |             } | 
|  |  | 541 |  |  | 
|  | 0 | 542 |  |             return TargetAudioStream?.Channels; | 
|  |  | 543 |  |         } | 
|  |  | 544 |  |     } | 
|  |  | 545 |  |  | 
|  |  | 546 |  |     /// <summary> | 
|  |  | 547 |  |     /// Gets the audio codec that will be in the output stream. | 
|  |  | 548 |  |     /// </summary> | 
|  |  | 549 |  |     /// <value>The audio codec.</value> | 
|  |  | 550 |  |     public IReadOnlyList<string> TargetAudioCodec | 
|  |  | 551 |  |     { | 
|  |  | 552 |  |         get | 
|  |  | 553 |  |         { | 
|  | 394 | 554 |  |             var stream = TargetAudioStream; | 
|  |  | 555 |  |  | 
|  | 394 | 556 |  |             string? inputCodec = stream?.Codec; | 
|  |  | 557 |  |  | 
|  | 394 | 558 |  |             if (IsDirectStream) | 
|  |  | 559 |  |             { | 
|  | 248 | 560 |  |                 return string.IsNullOrEmpty(inputCodec) ? [] : [inputCodec]; | 
|  |  | 561 |  |             } | 
|  |  | 562 |  |  | 
|  | 784 | 563 |  |             foreach (string codec in AudioCodecs) | 
|  |  | 564 |  |             { | 
|  | 286 | 565 |  |                 if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase)) | 
|  |  | 566 |  |                 { | 
|  | 80 | 567 |  |                     return string.IsNullOrEmpty(codec) ? [] : [codec]; | 
|  |  | 568 |  |                 } | 
|  |  | 569 |  |             } | 
|  |  | 570 |  |  | 
|  | 66 | 571 |  |             return AudioCodecs; | 
|  | 80 | 572 |  |         } | 
|  |  | 573 |  |     } | 
|  |  | 574 |  |  | 
|  |  | 575 |  |     /// <summary> | 
|  |  | 576 |  |     /// Gets the video codec that will be in the output stream. | 
|  |  | 577 |  |     /// </summary> | 
|  |  | 578 |  |     /// <value>The target video codec.</value> | 
|  |  | 579 |  |     public IReadOnlyList<string> TargetVideoCodec | 
|  |  | 580 |  |     { | 
|  |  | 581 |  |         get | 
|  |  | 582 |  |         { | 
|  | 728 | 583 |  |             var stream = TargetVideoStream; | 
|  |  | 584 |  |  | 
|  | 728 | 585 |  |             string? inputCodec = stream?.Codec; | 
|  |  | 586 |  |  | 
|  | 728 | 587 |  |             if (IsDirectStream) | 
|  |  | 588 |  |             { | 
|  | 248 | 589 |  |                 return string.IsNullOrEmpty(inputCodec) ? [] : [inputCodec]; | 
|  |  | 590 |  |             } | 
|  |  | 591 |  |  | 
|  | 2280 | 592 |  |             foreach (string codec in VideoCodecs) | 
|  |  | 593 |  |             { | 
|  | 900 | 594 |  |                 if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase)) | 
|  |  | 595 |  |                 { | 
|  | 480 | 596 |  |                     return string.IsNullOrEmpty(codec) ? [] : [codec]; | 
|  |  | 597 |  |                 } | 
|  |  | 598 |  |             } | 
|  |  | 599 |  |  | 
|  | 0 | 600 |  |             return VideoCodecs; | 
|  | 480 | 601 |  |         } | 
|  |  | 602 |  |     } | 
|  |  | 603 |  |  | 
|  |  | 604 |  |     /// <summary> | 
|  |  | 605 |  |     /// Gets the target size of the output stream. | 
|  |  | 606 |  |     /// </summary> | 
|  |  | 607 |  |     /// <value>The target size.</value> | 
|  |  | 608 |  |     public long? TargetSize | 
|  |  | 609 |  |     { | 
|  |  | 610 |  |         get | 
|  |  | 611 |  |         { | 
|  | 0 | 612 |  |             if (IsDirectStream) | 
|  |  | 613 |  |             { | 
|  | 0 | 614 |  |                 return MediaSource?.Size; | 
|  |  | 615 |  |             } | 
|  |  | 616 |  |  | 
|  | 0 | 617 |  |             if (RunTimeTicks.HasValue) | 
|  |  | 618 |  |             { | 
|  | 0 | 619 |  |                 int? totalBitrate = TargetTotalBitrate; | 
|  |  | 620 |  |  | 
|  | 0 | 621 |  |                 double totalSeconds = RunTimeTicks.Value; | 
|  |  | 622 |  |                 // Convert to ms | 
|  | 0 | 623 |  |                 totalSeconds /= 10000; | 
|  |  | 624 |  |                 // Convert to seconds | 
|  | 0 | 625 |  |                 totalSeconds /= 1000; | 
|  |  | 626 |  |  | 
|  | 0 | 627 |  |                 return totalBitrate.HasValue ? | 
|  | 0 | 628 |  |                     Convert.ToInt64(totalBitrate.Value * totalSeconds) : | 
|  | 0 | 629 |  |                     null; | 
|  |  | 630 |  |             } | 
|  |  | 631 |  |  | 
|  | 0 | 632 |  |             return null; | 
|  |  | 633 |  |         } | 
|  |  | 634 |  |     } | 
|  |  | 635 |  |  | 
|  |  | 636 |  |     /// <summary> | 
|  |  | 637 |  |     /// Gets the target video bitrate of the output stream. | 
|  |  | 638 |  |     /// </summary> | 
|  |  | 639 |  |     /// <value>The video bitrate.</value> | 
|  |  | 640 |  |     public int? TargetVideoBitrate | 
|  |  | 641 |  |     { | 
|  |  | 642 |  |         get | 
|  |  | 643 |  |         { | 
|  | 0 | 644 |  |             var stream = TargetVideoStream; | 
|  |  | 645 |  |  | 
|  | 0 | 646 |  |             return VideoBitrate.HasValue && !IsDirectStream | 
|  | 0 | 647 |  |                 ? VideoBitrate | 
|  | 0 | 648 |  |                 : stream?.BitRate; | 
|  |  | 649 |  |         } | 
|  |  | 650 |  |     } | 
|  |  | 651 |  |  | 
|  |  | 652 |  |     /// <summary> | 
|  |  | 653 |  |     /// Gets the target timestamp of the output stream. | 
|  |  | 654 |  |     /// </summary> | 
|  |  | 655 |  |     /// <value>The target timestamp.</value> | 
|  |  | 656 |  |     public TransportStreamTimestamp TargetTimestamp | 
|  |  | 657 |  |     { | 
|  |  | 658 |  |         get | 
|  |  | 659 |  |         { | 
|  | 0 | 660 |  |             var defaultValue = string.Equals(Container, "m2ts", StringComparison.OrdinalIgnoreCase) | 
|  | 0 | 661 |  |                 ? TransportStreamTimestamp.Valid | 
|  | 0 | 662 |  |                 : TransportStreamTimestamp.None; | 
|  |  | 663 |  |  | 
|  | 0 | 664 |  |             return !IsDirectStream | 
|  | 0 | 665 |  |                 ? defaultValue | 
|  | 0 | 666 |  |                 : MediaSource is null ? defaultValue : MediaSource.Timestamp ?? TransportStreamTimestamp.None; | 
|  |  | 667 |  |         } | 
|  |  | 668 |  |     } | 
|  |  | 669 |  |  | 
|  |  | 670 |  |     /// <summary> | 
|  |  | 671 |  |     /// Gets the target total bitrate of the output stream. | 
|  |  | 672 |  |     /// </summary> | 
|  |  | 673 |  |     /// <value>The target total bitrate.</value> | 
|  | 0 | 674 |  |     public int? TargetTotalBitrate => (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0); | 
|  |  | 675 |  |  | 
|  |  | 676 |  |     /// <summary> | 
|  |  | 677 |  |     /// Gets a value indicating whether the output stream is anamorphic. | 
|  |  | 678 |  |     /// </summary> | 
|  |  | 679 |  |     public bool? IsTargetAnamorphic | 
|  |  | 680 |  |     { | 
|  |  | 681 |  |         get | 
|  |  | 682 |  |         { | 
|  | 0 | 683 |  |             if (IsDirectStream) | 
|  |  | 684 |  |             { | 
|  | 0 | 685 |  |                 return TargetVideoStream?.IsAnamorphic; | 
|  |  | 686 |  |             } | 
|  |  | 687 |  |  | 
|  | 0 | 688 |  |             return false; | 
|  |  | 689 |  |         } | 
|  |  | 690 |  |     } | 
|  |  | 691 |  |  | 
|  |  | 692 |  |     /// <summary> | 
|  |  | 693 |  |     /// Gets a value indicating whether the output stream is interlaced. | 
|  |  | 694 |  |     /// </summary> | 
|  |  | 695 |  |     public bool? IsTargetInterlaced | 
|  |  | 696 |  |     { | 
|  |  | 697 |  |         get | 
|  |  | 698 |  |         { | 
|  | 0 | 699 |  |             if (IsDirectStream) | 
|  |  | 700 |  |             { | 
|  | 0 | 701 |  |                 return TargetVideoStream?.IsInterlaced; | 
|  |  | 702 |  |             } | 
|  |  | 703 |  |  | 
|  | 0 | 704 |  |             var targetVideoCodecs = TargetVideoCodec; | 
|  | 0 | 705 |  |             var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0]; | 
|  | 0 | 706 |  |             if (!string.IsNullOrEmpty(videoCodec)) | 
|  |  | 707 |  |             { | 
|  | 0 | 708 |  |                 if (string.Equals(GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase)) | 
|  |  | 709 |  |                 { | 
|  | 0 | 710 |  |                     return false; | 
|  |  | 711 |  |                 } | 
|  |  | 712 |  |             } | 
|  |  | 713 |  |  | 
|  | 0 | 714 |  |             return TargetVideoStream?.IsInterlaced; | 
|  |  | 715 |  |         } | 
|  |  | 716 |  |     } | 
|  |  | 717 |  |  | 
|  |  | 718 |  |     /// <summary> | 
|  |  | 719 |  |     /// Gets a value indicating whether the output stream is AVC. | 
|  |  | 720 |  |     /// </summary> | 
|  |  | 721 |  |     public bool? IsTargetAVC | 
|  |  | 722 |  |     { | 
|  |  | 723 |  |         get | 
|  |  | 724 |  |         { | 
|  | 0 | 725 |  |             if (IsDirectStream) | 
|  |  | 726 |  |             { | 
|  | 0 | 727 |  |                 return TargetVideoStream?.IsAVC; | 
|  |  | 728 |  |             } | 
|  |  | 729 |  |  | 
|  | 0 | 730 |  |             return true; | 
|  |  | 731 |  |         } | 
|  |  | 732 |  |     } | 
|  |  | 733 |  |  | 
|  |  | 734 |  |     /// <summary> | 
|  |  | 735 |  |     /// Gets the target width of the output stream. | 
|  |  | 736 |  |     /// </summary> | 
|  |  | 737 |  |     /// <value>The target width.</value> | 
|  |  | 738 |  |     public int? TargetWidth | 
|  |  | 739 |  |     { | 
|  |  | 740 |  |         get | 
|  |  | 741 |  |         { | 
|  | 0 | 742 |  |             var videoStream = TargetVideoStream; | 
|  |  | 743 |  |  | 
|  | 0 | 744 |  |             if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue) | 
|  |  | 745 |  |             { | 
|  | 0 | 746 |  |                 ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value); | 
|  |  | 747 |  |  | 
|  | 0 | 748 |  |                 size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0); | 
|  |  | 749 |  |  | 
|  | 0 | 750 |  |                 return size.Width; | 
|  |  | 751 |  |             } | 
|  |  | 752 |  |  | 
|  | 0 | 753 |  |             return MaxWidth; | 
|  |  | 754 |  |         } | 
|  |  | 755 |  |     } | 
|  |  | 756 |  |  | 
|  |  | 757 |  |     /// <summary> | 
|  |  | 758 |  |     /// Gets the target height of the output stream. | 
|  |  | 759 |  |     /// </summary> | 
|  |  | 760 |  |     /// <value>The target height.</value> | 
|  |  | 761 |  |     public int? TargetHeight | 
|  |  | 762 |  |     { | 
|  |  | 763 |  |         get | 
|  |  | 764 |  |         { | 
|  | 0 | 765 |  |             var videoStream = TargetVideoStream; | 
|  |  | 766 |  |  | 
|  | 0 | 767 |  |             if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue) | 
|  |  | 768 |  |             { | 
|  | 0 | 769 |  |                 ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value); | 
|  |  | 770 |  |  | 
|  | 0 | 771 |  |                 size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0); | 
|  |  | 772 |  |  | 
|  | 0 | 773 |  |                 return size.Height; | 
|  |  | 774 |  |             } | 
|  |  | 775 |  |  | 
|  | 0 | 776 |  |             return MaxHeight; | 
|  |  | 777 |  |         } | 
|  |  | 778 |  |     } | 
|  |  | 779 |  |  | 
|  |  | 780 |  |     /// <summary> | 
|  |  | 781 |  |     /// Gets the target video stream count of the output stream. | 
|  |  | 782 |  |     /// </summary> | 
|  |  | 783 |  |     /// <value>The target video stream count.</value> | 
|  |  | 784 |  |     public int? TargetVideoStreamCount | 
|  |  | 785 |  |     { | 
|  |  | 786 |  |         get | 
|  |  | 787 |  |         { | 
|  | 0 | 788 |  |             if (IsDirectStream) | 
|  |  | 789 |  |             { | 
|  | 0 | 790 |  |                 return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue); | 
|  |  | 791 |  |             } | 
|  |  | 792 |  |  | 
|  | 0 | 793 |  |             return GetMediaStreamCount(MediaStreamType.Video, 1); | 
|  |  | 794 |  |         } | 
|  |  | 795 |  |     } | 
|  |  | 796 |  |  | 
|  |  | 797 |  |     /// <summary> | 
|  |  | 798 |  |     /// Gets the target audio stream count of the output stream. | 
|  |  | 799 |  |     /// </summary> | 
|  |  | 800 |  |     /// <value>The target audio stream count.</value> | 
|  |  | 801 |  |     public int? TargetAudioStreamCount | 
|  |  | 802 |  |     { | 
|  |  | 803 |  |         get | 
|  |  | 804 |  |         { | 
|  | 0 | 805 |  |             if (IsDirectStream) | 
|  |  | 806 |  |             { | 
|  | 0 | 807 |  |                 return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue); | 
|  |  | 808 |  |             } | 
|  |  | 809 |  |  | 
|  | 0 | 810 |  |             return GetMediaStreamCount(MediaStreamType.Audio, 1); | 
|  |  | 811 |  |         } | 
|  |  | 812 |  |     } | 
|  |  | 813 |  |  | 
|  |  | 814 |  |     /// <summary> | 
|  |  | 815 |  |     /// Sets a stream option. | 
|  |  | 816 |  |     /// </summary> | 
|  |  | 817 |  |     /// <param name="qualifier">The qualifier.</param> | 
|  |  | 818 |  |     /// <param name="name">The name.</param> | 
|  |  | 819 |  |     /// <param name="value">The value.</param> | 
|  |  | 820 |  |     public void SetOption(string? qualifier, string name, string value) | 
|  |  | 821 |  |     { | 
|  | 1388 | 822 |  |         if (string.IsNullOrEmpty(qualifier)) | 
|  |  | 823 |  |         { | 
|  | 0 | 824 |  |             SetOption(name, value); | 
|  |  | 825 |  |         } | 
|  |  | 826 |  |         else | 
|  |  | 827 |  |         { | 
|  | 1388 | 828 |  |             SetOption(qualifier + "-" + name, value); | 
|  |  | 829 |  |         } | 
|  | 1388 | 830 |  |     } | 
|  |  | 831 |  |  | 
|  |  | 832 |  |     /// <summary> | 
|  |  | 833 |  |     /// Sets a stream option. | 
|  |  | 834 |  |     /// </summary> | 
|  |  | 835 |  |     /// <param name="name">The name.</param> | 
|  |  | 836 |  |     /// <param name="value">The value.</param> | 
|  |  | 837 |  |     public void SetOption(string name, string value) | 
|  |  | 838 |  |     { | 
|  | 1388 | 839 |  |         StreamOptions[name] = value; | 
|  | 1388 | 840 |  |     } | 
|  |  | 841 |  |  | 
|  |  | 842 |  |     /// <summary> | 
|  |  | 843 |  |     /// Gets a stream option. | 
|  |  | 844 |  |     /// </summary> | 
|  |  | 845 |  |     /// <param name="qualifier">The qualifier.</param> | 
|  |  | 846 |  |     /// <param name="name">The name.</param> | 
|  |  | 847 |  |     /// <returns>The value.</returns> | 
|  |  | 848 |  |     public string? GetOption(string? qualifier, string name) | 
|  |  | 849 |  |     { | 
|  | 1170 | 850 |  |         var value = GetOption(qualifier + "-" + name); | 
|  |  | 851 |  |  | 
|  | 1170 | 852 |  |         if (string.IsNullOrEmpty(value)) | 
|  |  | 853 |  |         { | 
|  | 722 | 854 |  |             value = GetOption(name); | 
|  |  | 855 |  |         } | 
|  |  | 856 |  |  | 
|  | 1170 | 857 |  |         return value; | 
|  |  | 858 |  |     } | 
|  |  | 859 |  |  | 
|  |  | 860 |  |     /// <summary> | 
|  |  | 861 |  |     /// Gets a stream option. | 
|  |  | 862 |  |     /// </summary> | 
|  |  | 863 |  |     /// <param name="name">The name.</param> | 
|  |  | 864 |  |     /// <returns>The value.</returns> | 
|  |  | 865 |  |     public string? GetOption(string name) | 
|  |  | 866 |  |     { | 
|  | 1892 | 867 |  |         if (StreamOptions.TryGetValue(name, out var value)) | 
|  |  | 868 |  |         { | 
|  | 448 | 869 |  |             return value; | 
|  |  | 870 |  |         } | 
|  |  | 871 |  |  | 
|  | 1444 | 872 |  |         return null; | 
|  |  | 873 |  |     } | 
|  |  | 874 |  |  | 
|  |  | 875 |  |     /// <summary> | 
|  |  | 876 |  |     /// Returns this output stream URL for this class. | 
|  |  | 877 |  |     /// </summary> | 
|  |  | 878 |  |     /// <param name="baseUrl">The base Url.</param> | 
|  |  | 879 |  |     /// <param name="accessToken">The access Token.</param> | 
|  |  | 880 |  |     /// <param name="query">Optional extra query.</param> | 
|  |  | 881 |  |     /// <returns>A querystring representation of this object.</returns> | 
|  |  | 882 |  |     public string ToUrl(string? baseUrl, string? accessToken, string? query) | 
|  |  | 883 |  |     { | 
|  | 100559 | 884 |  |         var sb = new StringBuilder(); | 
|  | 100559 | 885 |  |         if (!string.IsNullOrEmpty(baseUrl)) | 
|  |  | 886 |  |         { | 
|  | 100559 | 887 |  |             sb.Append(baseUrl.TrimEnd('/')); | 
|  |  | 888 |  |         } | 
|  |  | 889 |  |  | 
|  | 100559 | 890 |  |         if (MediaType == DlnaProfileType.Audio) | 
|  |  | 891 |  |         { | 
|  | 100001 | 892 |  |             sb.Append("/audio/"); | 
|  |  | 893 |  |         } | 
|  |  | 894 |  |         else | 
|  |  | 895 |  |         { | 
|  | 558 | 896 |  |             sb.Append("/videos/"); | 
|  |  | 897 |  |         } | 
|  |  | 898 |  |  | 
|  | 100559 | 899 |  |         sb.Append(ItemId); | 
|  |  | 900 |  |  | 
|  | 100559 | 901 |  |         if (SubProtocol == MediaStreamProtocol.hls) | 
|  |  | 902 |  |         { | 
|  | 264 | 903 |  |             sb.Append("/master.m3u8?"); | 
|  |  | 904 |  |         } | 
|  |  | 905 |  |         else | 
|  |  | 906 |  |         { | 
|  | 100295 | 907 |  |             sb.Append("/stream"); | 
|  |  | 908 |  |  | 
|  | 100295 | 909 |  |             if (!string.IsNullOrEmpty(Container)) | 
|  |  | 910 |  |             { | 
|  | 100276 | 911 |  |                 sb.Append('.'); | 
|  | 100276 | 912 |  |                 sb.Append(Container); | 
|  |  | 913 |  |             } | 
|  |  | 914 |  |  | 
|  | 100295 | 915 |  |             sb.Append('?'); | 
|  |  | 916 |  |         } | 
|  |  | 917 |  |  | 
|  | 100559 | 918 |  |         if (!string.IsNullOrEmpty(DeviceProfileId)) | 
|  |  | 919 |  |         { | 
|  | 100000 | 920 |  |             sb.Append("&DeviceProfileId="); | 
|  | 100000 | 921 |  |             sb.Append(DeviceProfileId); | 
|  |  | 922 |  |         } | 
|  |  | 923 |  |  | 
|  | 100559 | 924 |  |         if (!string.IsNullOrEmpty(DeviceId)) | 
|  |  | 925 |  |         { | 
|  | 100278 | 926 |  |             sb.Append("&DeviceId="); | 
|  | 100278 | 927 |  |             sb.Append(DeviceId); | 
|  |  | 928 |  |         } | 
|  |  | 929 |  |  | 
|  | 100559 | 930 |  |         if (!string.IsNullOrEmpty(MediaSourceId)) | 
|  |  | 931 |  |         { | 
|  | 556 | 932 |  |             sb.Append("&MediaSourceId="); | 
|  | 556 | 933 |  |             sb.Append(MediaSourceId); | 
|  |  | 934 |  |         } | 
|  |  | 935 |  |  | 
|  |  | 936 |  |         // default true so don't store. | 
|  | 100559 | 937 |  |         if (IsDirectStream) | 
|  |  | 938 |  |         { | 
|  | 100248 | 939 |  |             sb.Append("&Static=true"); | 
|  |  | 940 |  |         } | 
|  |  | 941 |  |  | 
|  | 100559 | 942 |  |         if (VideoCodecs.Count != 0) | 
|  |  | 943 |  |         { | 
|  | 540 | 944 |  |             sb.Append("&VideoCodec="); | 
|  | 540 | 945 |  |             sb.AppendJoin(',', VideoCodecs); | 
|  |  | 946 |  |         } | 
|  |  | 947 |  |  | 
|  | 100559 | 948 |  |         if (AudioCodecs.Count != 0) | 
|  |  | 949 |  |         { | 
|  | 540 | 950 |  |             sb.Append("&AudioCodec="); | 
|  | 540 | 951 |  |             sb.AppendJoin(',', AudioCodecs); | 
|  |  | 952 |  |         } | 
|  |  | 953 |  |  | 
|  | 100559 | 954 |  |         if (AudioStreamIndex.HasValue) | 
|  |  | 955 |  |         { | 
|  | 554 | 956 |  |             sb.Append("&AudioStreamIndex="); | 
|  | 554 | 957 |  |             sb.Append(AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 958 |  |         } | 
|  |  | 959 |  |  | 
|  | 100559 | 960 |  |         if (SubtitleStreamIndex.HasValue && (AlwaysBurnInSubtitleWhenTranscoding || SubtitleDeliveryMethod != SubtitleDe | 
|  |  | 961 |  |         { | 
|  | 16 | 962 |  |             sb.Append("&SubtitleStreamIndex="); | 
|  | 16 | 963 |  |             sb.Append(SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 964 |  |         } | 
|  |  | 965 |  |  | 
|  | 100559 | 966 |  |         if (VideoBitrate.HasValue) | 
|  |  | 967 |  |         { | 
|  | 292 | 968 |  |             sb.Append("&VideoBitrate="); | 
|  | 292 | 969 |  |             sb.Append(VideoBitrate.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 970 |  |         } | 
|  |  | 971 |  |  | 
|  | 100559 | 972 |  |         if (AudioBitrate.HasValue) | 
|  |  | 973 |  |         { | 
|  | 292 | 974 |  |             sb.Append("&AudioBitrate="); | 
|  | 292 | 975 |  |             sb.Append(AudioBitrate.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 976 |  |         } | 
|  |  | 977 |  |  | 
|  | 100559 | 978 |  |         if (AudioSampleRate.HasValue) | 
|  |  | 979 |  |         { | 
|  | 110 | 980 |  |             sb.Append("&AudioSampleRate="); | 
|  | 110 | 981 |  |             sb.Append(AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 982 |  |         } | 
|  |  | 983 |  |  | 
|  | 100559 | 984 |  |         if (MaxFramerate.HasValue) | 
|  |  | 985 |  |         { | 
|  | 290 | 986 |  |             sb.Append("&MaxFramerate="); | 
|  | 290 | 987 |  |             sb.Append(MaxFramerate.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 988 |  |         } | 
|  |  | 989 |  |  | 
|  | 100559 | 990 |  |         if (MaxWidth.HasValue) | 
|  |  | 991 |  |         { | 
|  | 8 | 992 |  |             sb.Append("&MaxWidth="); | 
|  | 8 | 993 |  |             sb.Append(MaxWidth.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 994 |  |         } | 
|  |  | 995 |  |  | 
|  | 100559 | 996 |  |         if (MaxHeight.HasValue) | 
|  |  | 997 |  |         { | 
|  | 0 | 998 |  |             sb.Append("&MaxHeight="); | 
|  | 0 | 999 |  |             sb.Append(MaxHeight.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1000 |  |         } | 
|  |  | 1001 |  |  | 
|  | 100559 | 1002 |  |         if (SubProtocol == MediaStreamProtocol.hls) | 
|  |  | 1003 |  |         { | 
|  | 264 | 1004 |  |             if (!string.IsNullOrEmpty(Container)) | 
|  |  | 1005 |  |             { | 
|  | 264 | 1006 |  |                 sb.Append("&SegmentContainer="); | 
|  | 264 | 1007 |  |                 sb.Append(Container); | 
|  |  | 1008 |  |             } | 
|  |  | 1009 |  |  | 
|  | 264 | 1010 |  |             if (SegmentLength.HasValue) | 
|  |  | 1011 |  |             { | 
|  | 0 | 1012 |  |                 sb.Append("&SegmentLength="); | 
|  | 0 | 1013 |  |                 sb.Append(SegmentLength.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1014 |  |             } | 
|  |  | 1015 |  |  | 
|  | 264 | 1016 |  |             if (MinSegments.HasValue) | 
|  |  | 1017 |  |             { | 
|  | 204 | 1018 |  |                 sb.Append("&MinSegments="); | 
|  | 204 | 1019 |  |                 sb.Append(MinSegments.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1020 |  |             } | 
|  |  | 1021 |  |  | 
|  | 264 | 1022 |  |             sb.Append("&BreakOnNonKeyFrames="); | 
|  | 264 | 1023 |  |             sb.Append(BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1024 |  |         } | 
|  |  | 1025 |  |         else | 
|  |  | 1026 |  |         { | 
|  | 100295 | 1027 |  |             if (StartPositionTicks != 0) | 
|  |  | 1028 |  |             { | 
|  | 100000 | 1029 |  |                 sb.Append("&StartTimeTicks="); | 
|  | 100000 | 1030 |  |                 sb.Append(StartPositionTicks.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1031 |  |             } | 
|  |  | 1032 |  |         } | 
|  |  | 1033 |  |  | 
|  | 100559 | 1034 |  |         if (!string.IsNullOrEmpty(PlaySessionId)) | 
|  |  | 1035 |  |         { | 
|  | 100000 | 1036 |  |             sb.Append("&PlaySessionId="); | 
|  | 100000 | 1037 |  |             sb.Append(PlaySessionId); | 
|  |  | 1038 |  |         } | 
|  |  | 1039 |  |  | 
|  | 100559 | 1040 |  |         if (!string.IsNullOrEmpty(accessToken)) | 
|  |  | 1041 |  |         { | 
|  | 100559 | 1042 |  |             sb.Append("&ApiKey="); | 
|  | 100559 | 1043 |  |             sb.Append(accessToken); | 
|  |  | 1044 |  |         } | 
|  |  | 1045 |  |  | 
|  | 100559 | 1046 |  |         var liveStreamId = MediaSource?.LiveStreamId; | 
|  | 100559 | 1047 |  |         if (!string.IsNullOrEmpty(liveStreamId)) | 
|  |  | 1048 |  |         { | 
|  | 0 | 1049 |  |             sb.Append("&LiveStreamId="); | 
|  | 0 | 1050 |  |             sb.Append(liveStreamId); | 
|  |  | 1051 |  |         } | 
|  |  | 1052 |  |  | 
|  | 100559 | 1053 |  |         if (!IsDirectStream) | 
|  |  | 1054 |  |         { | 
|  | 311 | 1055 |  |             if (RequireNonAnamorphic) | 
|  |  | 1056 |  |             { | 
|  | 0 | 1057 |  |                 sb.Append("&RequireNonAnamorphic="); | 
|  | 0 | 1058 |  |                 sb.Append(RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1059 |  |             } | 
|  |  | 1060 |  |  | 
|  | 311 | 1061 |  |             if (TranscodingMaxAudioChannels.HasValue) | 
|  |  | 1062 |  |             { | 
|  | 266 | 1063 |  |                 sb.Append("&TranscodingMaxAudioChannels="); | 
|  | 266 | 1064 |  |                 sb.Append(TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1065 |  |             } | 
|  |  | 1066 |  |  | 
|  | 311 | 1067 |  |             if (EnableSubtitlesInManifest) | 
|  |  | 1068 |  |             { | 
|  | 0 | 1069 |  |                 sb.Append("&EnableSubtitlesInManifest="); | 
|  | 0 | 1070 |  |                 sb.Append(EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1071 |  |             } | 
|  |  | 1072 |  |  | 
|  | 311 | 1073 |  |             if (EnableMpegtsM2TsMode) | 
|  |  | 1074 |  |             { | 
|  | 0 | 1075 |  |                 sb.Append("&EnableMpegtsM2TsMode="); | 
|  | 0 | 1076 |  |                 sb.Append(EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1077 |  |             } | 
|  |  | 1078 |  |  | 
|  | 311 | 1079 |  |             if (EstimateContentLength) | 
|  |  | 1080 |  |             { | 
|  | 0 | 1081 |  |                 sb.Append("&EstimateContentLength="); | 
|  | 0 | 1082 |  |                 sb.Append(EstimateContentLength.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1083 |  |             } | 
|  |  | 1084 |  |  | 
|  | 311 | 1085 |  |             if (TranscodeSeekInfo != TranscodeSeekInfo.Auto) | 
|  |  | 1086 |  |             { | 
|  | 0 | 1087 |  |                 sb.Append("&TranscodeSeekInfo="); | 
|  | 0 | 1088 |  |                 sb.Append(TranscodeSeekInfo.ToString()); | 
|  |  | 1089 |  |             } | 
|  |  | 1090 |  |  | 
|  | 311 | 1091 |  |             if (CopyTimestamps) | 
|  |  | 1092 |  |             { | 
|  | 24 | 1093 |  |                 sb.Append("&CopyTimestamps="); | 
|  | 24 | 1094 |  |                 sb.Append(CopyTimestamps.ToString(CultureInfo.InvariantCulture)); | 
|  |  | 1095 |  |             } | 
|  |  | 1096 |  |  | 
|  | 311 | 1097 |  |             sb.Append("&RequireAvc="); | 
|  | 311 | 1098 |  |             sb.Append(RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); | 
|  |  | 1099 |  |  | 
|  | 311 | 1100 |  |             sb.Append("&EnableAudioVbrEncoding="); | 
|  | 311 | 1101 |  |             sb.Append(EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); | 
|  |  | 1102 |  |         } | 
|  |  | 1103 |  |  | 
|  | 100559 | 1104 |  |         var etag = MediaSource?.ETag; | 
|  | 100559 | 1105 |  |         if (!string.IsNullOrEmpty(etag)) | 
|  |  | 1106 |  |         { | 
|  | 556 | 1107 |  |             sb.Append("&Tag="); | 
|  | 556 | 1108 |  |             sb.Append(etag); | 
|  |  | 1109 |  |         } | 
|  |  | 1110 |  |  | 
|  | 100559 | 1111 |  |         if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) | 
|  |  | 1112 |  |         { | 
|  | 52 | 1113 |  |             sb.Append("&SubtitleMethod="); | 
|  | 52 | 1114 |  |             sb.Append(SubtitleDeliveryMethod); | 
|  |  | 1115 |  |         } | 
|  |  | 1116 |  |  | 
|  | 100559 | 1117 |  |         if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed && SubtitleCodecs.Cou | 
|  |  | 1118 |  |         { | 
|  | 0 | 1119 |  |             sb.Append("&SubtitleCodec="); | 
|  | 0 | 1120 |  |             sb.AppendJoin(',', SubtitleCodecs); | 
|  |  | 1121 |  |         } | 
|  |  | 1122 |  |  | 
|  | 206030 | 1123 |  |         foreach (var pair in StreamOptions) | 
|  |  | 1124 |  |         { | 
|  |  | 1125 |  |             // Strip spaces to avoid having to encode h264 profile names | 
|  | 2456 | 1126 |  |             sb.Append('&'); | 
|  | 2456 | 1127 |  |             sb.Append(pair.Key); | 
|  | 2456 | 1128 |  |             sb.Append('='); | 
|  | 2456 | 1129 |  |             sb.Append(pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)); | 
|  |  | 1130 |  |         } | 
|  |  | 1131 |  |  | 
|  | 100559 | 1132 |  |         var transcodeReasonsValues = TranscodeReasons.GetUniqueFlags().ToArray(); | 
|  | 100559 | 1133 |  |         if (!IsDirectStream && transcodeReasonsValues.Length > 0) | 
|  |  | 1134 |  |         { | 
|  | 308 | 1135 |  |             sb.Append("&TranscodeReasons="); | 
|  | 308 | 1136 |  |             sb.AppendJoin(',', transcodeReasonsValues); | 
|  |  | 1137 |  |         } | 
|  |  | 1138 |  |  | 
|  | 100559 | 1139 |  |         if (!string.IsNullOrEmpty(query)) | 
|  |  | 1140 |  |         { | 
|  | 0 | 1141 |  |             sb.Append(query); | 
|  |  | 1142 |  |         } | 
|  |  | 1143 |  |  | 
|  | 100559 | 1144 |  |         return sb.ToString(); | 
|  |  | 1145 |  |     } | 
|  |  | 1146 |  |  | 
|  |  | 1147 |  |     /// <summary> | 
|  |  | 1148 |  |     /// Gets the subtitle profiles. | 
|  |  | 1149 |  |     /// </summary> | 
|  |  | 1150 |  |     /// <param name="transcoderSupport">The transcoder support.</param> | 
|  |  | 1151 |  |     /// <param name="includeSelectedTrackOnly">If only the selected track should be included.</param> | 
|  |  | 1152 |  |     /// <param name="baseUrl">The base URL.</param> | 
|  |  | 1153 |  |     /// <param name="accessToken">The access token.</param> | 
|  |  | 1154 |  |     /// <returns>The <see cref="SubtitleStreamInfo"/> of the profiles.</returns> | 
|  |  | 1155 |  |     public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelecte | 
|  |  | 1156 |  |     { | 
|  | 0 | 1157 |  |         return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken); | 
|  |  | 1158 |  |     } | 
|  |  | 1159 |  |  | 
|  |  | 1160 |  |     /// <summary> | 
|  |  | 1161 |  |     /// Gets the subtitle profiles. | 
|  |  | 1162 |  |     /// </summary> | 
|  |  | 1163 |  |     /// <param name="transcoderSupport">The transcoder support.</param> | 
|  |  | 1164 |  |     /// <param name="includeSelectedTrackOnly">If only the selected track should be included.</param> | 
|  |  | 1165 |  |     /// <param name="enableAllProfiles">If all profiles are enabled.</param> | 
|  |  | 1166 |  |     /// <param name="baseUrl">The base URL.</param> | 
|  |  | 1167 |  |     /// <param name="accessToken">The access token.</param> | 
|  |  | 1168 |  |     /// <returns>The <see cref="SubtitleStreamInfo"/> of the profiles.</returns> | 
|  |  | 1169 |  |     public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelecte | 
|  |  | 1170 |  |     { | 
|  | 0 | 1171 |  |         if (MediaSource is null) | 
|  |  | 1172 |  |         { | 
|  | 0 | 1173 |  |             return []; | 
|  |  | 1174 |  |         } | 
|  |  | 1175 |  |  | 
|  | 0 | 1176 |  |         List<SubtitleStreamInfo> list = []; | 
|  |  | 1177 |  |  | 
|  |  | 1178 |  |         // HLS will preserve timestamps so we can just grab the full subtitle stream | 
|  | 0 | 1179 |  |         long startPositionTicks = SubProtocol == MediaStreamProtocol.hls | 
|  | 0 | 1180 |  |             ? 0 | 
|  | 0 | 1181 |  |             : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0); | 
|  |  | 1182 |  |  | 
|  |  | 1183 |  |         // First add the selected track | 
|  | 0 | 1184 |  |         if (SubtitleStreamIndex.HasValue) | 
|  |  | 1185 |  |         { | 
|  | 0 | 1186 |  |             foreach (var stream in MediaSource.MediaStreams) | 
|  |  | 1187 |  |             { | 
|  | 0 | 1188 |  |                 if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value) | 
|  |  | 1189 |  |                 { | 
|  | 0 | 1190 |  |                     AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startP | 
|  |  | 1191 |  |                 } | 
|  |  | 1192 |  |             } | 
|  |  | 1193 |  |         } | 
|  |  | 1194 |  |  | 
|  | 0 | 1195 |  |         if (!includeSelectedTrackOnly) | 
|  |  | 1196 |  |         { | 
|  | 0 | 1197 |  |             foreach (var stream in MediaSource.MediaStreams) | 
|  |  | 1198 |  |             { | 
|  | 0 | 1199 |  |                 if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != Subtitl | 
|  |  | 1200 |  |                 { | 
|  | 0 | 1201 |  |                     AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startP | 
|  |  | 1202 |  |                 } | 
|  |  | 1203 |  |             } | 
|  |  | 1204 |  |         } | 
|  |  | 1205 |  |  | 
|  | 0 | 1206 |  |         return list; | 
|  |  | 1207 |  |     } | 
|  |  | 1208 |  |  | 
|  |  | 1209 |  |     private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSup | 
|  |  | 1210 |  |     { | 
|  | 0 | 1211 |  |         if (enableAllProfiles) | 
|  |  | 1212 |  |         { | 
|  | 0 | 1213 |  |             foreach (var profile in DeviceProfile.SubtitleProfiles) | 
|  |  | 1214 |  |             { | 
|  | 0 | 1215 |  |                 var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, tr | 
|  | 0 | 1216 |  |                 if (info is not null) | 
|  |  | 1217 |  |                 { | 
|  | 0 | 1218 |  |                     list.Add(info); | 
|  |  | 1219 |  |                 } | 
|  |  | 1220 |  |             } | 
|  |  | 1221 |  |         } | 
|  |  | 1222 |  |         else | 
|  |  | 1223 |  |         { | 
|  | 0 | 1224 |  |             var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitlePro | 
|  | 0 | 1225 |  |             if (info is not null) | 
|  |  | 1226 |  |             { | 
|  | 0 | 1227 |  |                 list.Add(info); | 
|  |  | 1228 |  |             } | 
|  |  | 1229 |  |         } | 
|  | 0 | 1230 |  |     } | 
|  |  | 1231 |  |  | 
|  |  | 1232 |  |     private SubtitleStreamInfo? GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string? accessToken, long star | 
|  |  | 1233 |  |     { | 
|  | 0 | 1234 |  |         if (MediaSource is null) | 
|  |  | 1235 |  |         { | 
|  | 0 | 1236 |  |             return null; | 
|  |  | 1237 |  |         } | 
|  |  | 1238 |  |  | 
|  | 0 | 1239 |  |         var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transc | 
|  | 0 | 1240 |  |         var info = new SubtitleStreamInfo | 
|  | 0 | 1241 |  |         { | 
|  | 0 | 1242 |  |             IsForced = stream.IsForced, | 
|  | 0 | 1243 |  |             Language = stream.Language, | 
|  | 0 | 1244 |  |             Name = stream.Language ?? "Unknown", | 
|  | 0 | 1245 |  |             Format = subtitleProfile.Format, | 
|  | 0 | 1246 |  |             Index = stream.Index, | 
|  | 0 | 1247 |  |             DeliveryMethod = subtitleProfile.Method, | 
|  | 0 | 1248 |  |             DisplayTitle = stream.DisplayTitle | 
|  | 0 | 1249 |  |         }; | 
|  |  | 1250 |  |  | 
|  | 0 | 1251 |  |         if (info.DeliveryMethod == SubtitleDeliveryMethod.External) | 
|  |  | 1252 |  |         { | 
|  | 0 | 1253 |  |             if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, Strin | 
|  |  | 1254 |  |             { | 
|  | 0 | 1255 |  |                 info.Url = string.Format( | 
|  | 0 | 1256 |  |                     CultureInfo.InvariantCulture, | 
|  | 0 | 1257 |  |                     "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", | 
|  | 0 | 1258 |  |                     baseUrl, | 
|  | 0 | 1259 |  |                     ItemId, | 
|  | 0 | 1260 |  |                     MediaSourceId, | 
|  | 0 | 1261 |  |                     stream.Index.ToString(CultureInfo.InvariantCulture), | 
|  | 0 | 1262 |  |                     startPositionTicks.ToString(CultureInfo.InvariantCulture), | 
|  | 0 | 1263 |  |                     subtitleProfile.Format); | 
|  |  | 1264 |  |  | 
|  | 0 | 1265 |  |                 if (!string.IsNullOrEmpty(accessToken)) | 
|  |  | 1266 |  |                 { | 
|  | 0 | 1267 |  |                     info.Url += "?ApiKey=" + accessToken; | 
|  |  | 1268 |  |                 } | 
|  |  | 1269 |  |  | 
|  | 0 | 1270 |  |                 info.IsExternalUrl = false; | 
|  |  | 1271 |  |             } | 
|  |  | 1272 |  |             else | 
|  |  | 1273 |  |             { | 
|  | 0 | 1274 |  |                 info.Url = stream.Path; | 
|  | 0 | 1275 |  |                 info.IsExternalUrl = true; | 
|  |  | 1276 |  |             } | 
|  |  | 1277 |  |         } | 
|  |  | 1278 |  |  | 
|  | 0 | 1279 |  |         return info; | 
|  |  | 1280 |  |     } | 
|  |  | 1281 |  |  | 
|  |  | 1282 |  |     /// <summary> | 
|  |  | 1283 |  |     /// Gets the target video bit depth. | 
|  |  | 1284 |  |     /// </summary> | 
|  |  | 1285 |  |     /// <param name="codec">The codec.</param> | 
|  |  | 1286 |  |     /// <returns>The target video bit depth.</returns> | 
|  |  | 1287 |  |     public int? GetTargetVideoBitDepth(string? codec) | 
|  |  | 1288 |  |     { | 
|  | 96 | 1289 |  |         var value = GetOption(codec, "videobitdepth"); | 
|  |  | 1290 |  |  | 
|  | 96 | 1291 |  |         if (int.TryParse(value, CultureInfo.InvariantCulture, out var result)) | 
|  |  | 1292 |  |         { | 
|  | 96 | 1293 |  |             return result; | 
|  |  | 1294 |  |         } | 
|  |  | 1295 |  |  | 
|  | 0 | 1296 |  |         return null; | 
|  |  | 1297 |  |     } | 
|  |  | 1298 |  |  | 
|  |  | 1299 |  |     /// <summary> | 
|  |  | 1300 |  |     /// Gets the target audio bit depth. | 
|  |  | 1301 |  |     /// </summary> | 
|  |  | 1302 |  |     /// <param name="codec">The codec.</param> | 
|  |  | 1303 |  |     /// <returns>The target audio bit depth.</returns> | 
|  |  | 1304 |  |     public int? GetTargetAudioBitDepth(string? codec) | 
|  |  | 1305 |  |     { | 
|  | 0 | 1306 |  |         var value = GetOption(codec, "audiobitdepth"); | 
|  |  | 1307 |  |  | 
|  | 0 | 1308 |  |         if (int.TryParse(value, CultureInfo.InvariantCulture, out var result)) | 
|  |  | 1309 |  |         { | 
|  | 0 | 1310 |  |             return result; | 
|  |  | 1311 |  |         } | 
|  |  | 1312 |  |  | 
|  | 0 | 1313 |  |         return null; | 
|  |  | 1314 |  |     } | 
|  |  | 1315 |  |  | 
|  |  | 1316 |  |     /// <summary> | 
|  |  | 1317 |  |     /// Gets the target video level. | 
|  |  | 1318 |  |     /// </summary> | 
|  |  | 1319 |  |     /// <param name="codec">The codec.</param> | 
|  |  | 1320 |  |     /// <returns>The target video level.</returns> | 
|  |  | 1321 |  |     public double? GetTargetVideoLevel(string? codec) | 
|  |  | 1322 |  |     { | 
|  | 308 | 1323 |  |         var value = GetOption(codec, "level"); | 
|  |  | 1324 |  |  | 
|  | 308 | 1325 |  |         if (double.TryParse(value, CultureInfo.InvariantCulture, out var result)) | 
|  |  | 1326 |  |         { | 
|  | 170 | 1327 |  |             return result; | 
|  |  | 1328 |  |         } | 
|  |  | 1329 |  |  | 
|  | 138 | 1330 |  |         return null; | 
|  |  | 1331 |  |     } | 
|  |  | 1332 |  |  | 
|  |  | 1333 |  |     /// <summary> | 
|  |  | 1334 |  |     /// Gets the target reference frames. | 
|  |  | 1335 |  |     /// </summary> | 
|  |  | 1336 |  |     /// <param name="codec">The codec.</param> | 
|  |  | 1337 |  |     /// <returns>The target reference frames.</returns> | 
|  |  | 1338 |  |     public int? GetTargetRefFrames(string? codec) | 
|  |  | 1339 |  |     { | 
|  | 2 | 1340 |  |         var value = GetOption(codec, "maxrefframes"); | 
|  |  | 1341 |  |  | 
|  | 2 | 1342 |  |         if (int.TryParse(value, CultureInfo.InvariantCulture, out var result)) | 
|  |  | 1343 |  |         { | 
|  | 0 | 1344 |  |             return result; | 
|  |  | 1345 |  |         } | 
|  |  | 1346 |  |  | 
|  | 2 | 1347 |  |         return null; | 
|  |  | 1348 |  |     } | 
|  |  | 1349 |  |  | 
|  |  | 1350 |  |     /// <summary> | 
|  |  | 1351 |  |     /// Gets the target audio channels. | 
|  |  | 1352 |  |     /// </summary> | 
|  |  | 1353 |  |     /// <param name="codec">The codec.</param> | 
|  |  | 1354 |  |     /// <returns>The target audio channels.</returns> | 
|  |  | 1355 |  |     public int? GetTargetAudioChannels(string? codec) | 
|  |  | 1356 |  |     { | 
|  | 174 | 1357 |  |         var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels; | 
|  |  | 1358 |  |  | 
|  | 174 | 1359 |  |         var value = GetOption(codec, "audiochannels"); | 
|  | 174 | 1360 |  |         if (string.IsNullOrEmpty(value)) | 
|  |  | 1361 |  |         { | 
|  | 174 | 1362 |  |             return defaultValue; | 
|  |  | 1363 |  |         } | 
|  |  | 1364 |  |  | 
|  | 0 | 1365 |  |         if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) | 
|  |  | 1366 |  |         { | 
|  | 0 | 1367 |  |             return Math.Min(result, defaultValue ?? result); | 
|  |  | 1368 |  |         } | 
|  |  | 1369 |  |  | 
|  | 0 | 1370 |  |         return defaultValue; | 
|  |  | 1371 |  |     } | 
|  |  | 1372 |  |  | 
|  |  | 1373 |  |     /// <summary> | 
|  |  | 1374 |  |     /// Gets the media stream count. | 
|  |  | 1375 |  |     /// </summary> | 
|  |  | 1376 |  |     /// <param name="type">The type.</param> | 
|  |  | 1377 |  |     /// <param name="limit">The limit.</param> | 
|  |  | 1378 |  |     /// <returns>The media stream count.</returns> | 
|  |  | 1379 |  |     private int? GetMediaStreamCount(MediaStreamType type, int limit) | 
|  |  | 1380 |  |     { | 
|  | 0 | 1381 |  |         var count = MediaSource?.GetStreamCount(type); | 
|  |  | 1382 |  |  | 
|  | 0 | 1383 |  |         if (count.HasValue) | 
|  |  | 1384 |  |         { | 
|  | 0 | 1385 |  |             count = Math.Min(count.Value, limit); | 
|  |  | 1386 |  |         } | 
|  |  | 1387 |  |  | 
|  | 0 | 1388 |  |         return count; | 
|  |  | 1389 |  |     } | 
|  |  | 1390 |  | } |