< Summary - Jellyfin

Information
Class: Jellyfin.Api.Helpers.StreamingHelpers
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Helpers/StreamingHelpers.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 134
Coverable lines: 134
Total lines: 618
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 133
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 12/27/2025 - 12:11:51 AM Line coverage: 0% (0/131) Branch coverage: 0% (0/121) Total lines: 60012/29/2025 - 12:13:19 AM Line coverage: 0% (0/131) Branch coverage: 0% (0/121) Total lines: 6072/27/2026 - 12:13:29 AM Line coverage: 0% (0/131) Branch coverage: 0% (0/123) Total lines: 6074/7/2026 - 12:14:03 AM Line coverage: 0% (0/134) Branch coverage: 0% (0/133) Total lines: 618 12/27/2025 - 12:11:51 AM Line coverage: 0% (0/131) Branch coverage: 0% (0/121) Total lines: 60012/29/2025 - 12:13:19 AM Line coverage: 0% (0/131) Branch coverage: 0% (0/121) Total lines: 6072/27/2026 - 12:13:29 AM Line coverage: 0% (0/131) Branch coverage: 0% (0/123) Total lines: 6074/7/2026 - 12:14:03 AM Line coverage: 0% (0/134) Branch coverage: 0% (0/133) Total lines: 618

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ParseStreamOptions(...)0%4260%
GetOutputFileExtension(...)0%1190340%
GetOutputFilePath(...)100%210%
ParseParams(...)0%8010890%
IsValidCodecName(...)100%210%
GetContainerFileExtension(...)0%2040%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Helpers/StreamingHelpers.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.IO;
 5using System.Linq;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using Jellyfin.Api.Extensions;
 9using Jellyfin.Data.Enums;
 10using Jellyfin.Extensions;
 11using MediaBrowser.Common.Configuration;
 12using MediaBrowser.Common.Extensions;
 13using MediaBrowser.Controller.Configuration;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Controller.MediaEncoding;
 17using MediaBrowser.Controller.Streaming;
 18using MediaBrowser.Model.Dlna;
 19using MediaBrowser.Model.Dto;
 20using Microsoft.AspNetCore.Http;
 21using Microsoft.Net.Http.Headers;
 22
 23namespace Jellyfin.Api.Helpers;
 24
 25/// <summary>
 26/// The streaming helpers.
 27/// </summary>
 28public static class StreamingHelpers
 29{
 30    /// <summary>
 31    /// Gets the current streaming state.
 32    /// </summary>
 33    /// <param name="streamingRequest">The <see cref="StreamingRequestDto"/>.</param>
 34    /// <param name="httpContext">The <see cref="HttpContext"/>.</param>
 35    /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
 36    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 37    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 38    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 39    /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
 40    /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
 41    /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
 42    /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
 43    /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
 44    /// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns>
 45    public static async Task<StreamState> GetStreamingState(
 46        StreamingRequestDto streamingRequest,
 47        HttpContext httpContext,
 48        IMediaSourceManager mediaSourceManager,
 49        IUserManager userManager,
 50        ILibraryManager libraryManager,
 51        IServerConfigurationManager serverConfigurationManager,
 52        IMediaEncoder mediaEncoder,
 53        EncodingHelper encodingHelper,
 54        ITranscodeManager transcodeManager,
 55        TranscodingJobType transcodingJobType,
 56        CancellationToken cancellationToken)
 57    {
 58        var httpRequest = httpContext.Request;
 59        if (!string.IsNullOrWhiteSpace(streamingRequest.Params))
 60        {
 61            ParseParams(streamingRequest);
 62        }
 63
 64        streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
 65        if (httpRequest.Path.Value is null)
 66        {
 67            throw new ResourceNotFoundException(nameof(httpRequest.Path));
 68        }
 69
 70        var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString();
 71
 72        if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
 73        {
 74            streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
 75        }
 76
 77        var state = new StreamState(mediaSourceManager, transcodingJobType, transcodeManager)
 78        {
 79            Request = streamingRequest,
 80            RequestedUrl = url,
 81            UserAgent = httpRequest.Headers[HeaderNames.UserAgent]
 82        };
 83
 84        var userId = httpContext.User.GetUserId();
 85        if (!userId.IsEmpty())
 86        {
 87            state.User = userManager.GetUserById(userId);
 88        }
 89
 90        if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec))
 91        {
 92            state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
 93            state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 94        }
 95
 96        if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec))
 97        {
 98            state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
 99            state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec)
 100                                       ?? state.SupportedAudioCodecs.FirstOrDefault();
 101        }
 102
 103        if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec))
 104        {
 105            state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntr
 106            state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleC
 107                                          ?? state.SupportedSubtitleCodecs.FirstOrDefault();
 108        }
 109
 110        var item = libraryManager.GetItemById<BaseItem>(streamingRequest.Id)
 111            ?? throw new ResourceNotFoundException();
 112
 113        state.IsInputVideo = item.MediaType == MediaType.Video;
 114
 115        MediaSourceInfo? mediaSource = null;
 116        if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId))
 117        {
 118            var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId)
 119                ? transcodeManager.GetTranscodingJob(streamingRequest.PlaySessionId)
 120                : null;
 121
 122            if (currentJob is not null)
 123            {
 124                mediaSource = currentJob.MediaSource;
 125            }
 126
 127            if (mediaSource is null)
 128            {
 129                var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById<BaseItem>
 130
 131                mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
 132                    ? mediaSources[0]
 133                    : mediaSources.FirstOrDefault(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringCompari
 134
 135                if (mediaSource is null && Guid.Parse(streamingRequest.MediaSourceId).Equals(streamingRequest.Id))
 136                {
 137                    mediaSource = mediaSources[0];
 138                }
 139            }
 140        }
 141        else
 142        {
 143            var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStr
 144            mediaSource = liveStreamInfo.Item1;
 145            state.DirectStreamProvider = liveStreamInfo.Item2;
 146
 147            // Cap the max bitrate when it is too high. This is usually due to ffmpeg is unable to probe the source live
 148            if (mediaSource.FallbackMaxStreamingBitrate is not null && streamingRequest.VideoBitRate is not null)
 149            {
 150                streamingRequest.VideoBitRate = Math.Min(streamingRequest.VideoBitRate.Value, mediaSource.FallbackMaxStr
 151            }
 152        }
 153
 154        var encodingOptions = serverConfigurationManager.GetEncodingOptions();
 155
 156        encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url);
 157
 158        string? containerInternal = Path.GetExtension(state.RequestedUrl);
 159
 160        if (string.IsNullOrEmpty(containerInternal)
 161            && (!string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)
 162                || (mediaSource != null && mediaSource.IsInfiniteStream)))
 163        {
 164            containerInternal = ".ts";
 165        }
 166
 167        if (!string.IsNullOrEmpty(streamingRequest.Container))
 168        {
 169            containerInternal = streamingRequest.Container;
 170        }
 171
 172        if (string.IsNullOrEmpty(containerInternal))
 173        {
 174            containerInternal = streamingRequest.Static ?
 175                StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.
 176                : GetOutputFileExtension(state, mediaSource);
 177        }
 178
 179        var outputAudioCodec = streamingRequest.AudioCodec;
 180        state.OutputAudioCodec = outputAudioCodec;
 181        state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
 182        state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioC
 183        if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec))
 184        {
 185            state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0;
 186        }
 187        else
 188        {
 189            state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingReque
 190        }
 191
 192        if (outputAudioCodec.StartsWith("pcm_", StringComparison.Ordinal))
 193        {
 194            containerInternal = ".pcm";
 195        }
 196
 197        if (state.VideoRequest is not null)
 198        {
 199            state.OutputVideoCodec = state.Request.VideoCodec;
 200            state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, s
 201
 202            encodingHelper.TryStreamCopy(state, encodingOptions);
 203
 204            if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue)
 205            {
 206                var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue
 207                    && !state.VideoRequest.Height.HasValue
 208                    && !state.VideoRequest.MaxWidth.HasValue
 209                    && !state.VideoRequest.MaxHeight.HasValue;
 210
 211                if (isVideoResolutionNotRequested
 212                    && state.VideoStream is not null
 213                    && state.VideoRequest.VideoBitRate.HasValue
 214                    && state.VideoStream.BitRate.HasValue
 215                    && state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value)
 216                {
 217                    // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested,
 218                    // and the requested video bitrate is greater than source video bitrate.
 219                    if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue)
 220                    {
 221                        state.VideoRequest.MaxWidth = state.VideoStream?.Width;
 222                        state.VideoRequest.MaxHeight = state.VideoStream?.Height;
 223                    }
 224                }
 225                else
 226                {
 227                    var h264EquivalentBitrate = EncodingHelper.ScaleBitrate(
 228                        state.OutputVideoBitrate.Value,
 229                        state.ActualOutputVideoCodec,
 230                        "h264");
 231                    var resolution = ResolutionNormalizer.Normalize(
 232                        state.VideoStream?.BitRate,
 233                        state.OutputVideoBitrate.Value,
 234                        h264EquivalentBitrate,
 235                        state.VideoRequest.MaxWidth,
 236                        state.VideoRequest.MaxHeight,
 237                        state.TargetFramerate);
 238
 239                    state.VideoRequest.MaxWidth = resolution.MaxWidth;
 240                    state.VideoRequest.MaxHeight = resolution.MaxHeight;
 241                }
 242            }
 243
 244            if (state.AudioStream is not null && !EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(st
 245            {
 246                state.OutputAudioCodec = state.SupportedAudioCodecs.Where(c => !EncodingHelper.LosslessAudioCodecs.Conta
 247            }
 248        }
 249
 250        var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
 251            ? GetOutputFileExtension(state, mediaSource)
 252            : ("." + GetContainerFileExtension(state.OutputContainer));
 253
 254        state.OutputFilePath = GetOutputFilePath(state, ext, serverConfigurationManager, streamingRequest.DeviceId, stre
 255
 256        return state;
 257    }
 258
 259    /// <summary>
 260    /// Parses query parameters as StreamOptions.
 261    /// </summary>
 262    /// <param name="queryString">The query string.</param>
 263    /// <returns>A <see cref="Dictionary{String,String}"/> containing the stream options.</returns>
 264    private static Dictionary<string, string?> ParseStreamOptions(IQueryCollection queryString)
 265    {
 0266        Dictionary<string, string?> streamOptions = new Dictionary<string, string?>();
 0267        foreach (var param in queryString)
 268        {
 0269            if (param.Key.Length > 0 && char.IsLower(param.Key[0]))
 270            {
 271                // This was probably not parsed initially and should be a StreamOptions
 272                // or the generated URL should correctly serialize it
 273                // TODO: This should be incorporated either in the lower framework for parsing requests
 0274                streamOptions[param.Key] = param.Value;
 275            }
 276        }
 277
 0278        return streamOptions;
 279    }
 280
 281    /// <summary>
 282    /// Gets the output file extension.
 283    /// </summary>
 284    /// <param name="state">The state.</param>
 285    /// <param name="mediaSource">The mediaSource.</param>
 286    /// <returns>System.String.</returns>
 287    private static string GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource)
 288    {
 0289        var ext = Path.GetExtension(state.RequestedUrl);
 0290        if (!string.IsNullOrEmpty(ext))
 291        {
 0292            return ext;
 293        }
 294
 295        // Try to infer based on the desired video codec
 0296        if (state.IsVideoRequest)
 297        {
 0298            var videoCodec = state.Request.VideoCodec;
 299
 0300            if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 301            {
 0302                return ".ts";
 303            }
 304
 0305            if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 0306                || string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 307            {
 0308                return ".mp4";
 309            }
 310
 0311            if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
 312            {
 0313                return ".ogv";
 314            }
 315
 0316            if (string.Equals(videoCodec, "vp8", StringComparison.OrdinalIgnoreCase)
 0317                || string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 0318                || string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
 319            {
 0320                return ".webm";
 321            }
 322
 0323            if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
 324            {
 0325                return ".asf";
 326            }
 327        }
 328        else
 329        {
 330            // Try to infer based on the desired audio codec
 0331            var audioCodec = state.Request.AudioCodec;
 332
 0333            if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
 334            {
 0335                return ".aac";
 336            }
 337
 0338            if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
 339            {
 0340                return ".mp3";
 341            }
 342
 0343            if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
 344            {
 0345                return ".ogg";
 346            }
 347
 0348            if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
 349            {
 0350                return ".wma";
 351            }
 352        }
 353
 354        // Fallback to the container of mediaSource
 0355        if (!string.IsNullOrEmpty(mediaSource?.Container))
 356        {
 0357            var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase);
 0358            return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim();
 359        }
 360
 0361        throw new InvalidOperationException("Failed to find an appropriate file extension");
 362    }
 363
 364    /// <summary>
 365    /// Gets the output file path for transcoding.
 366    /// </summary>
 367    /// <param name="state">The current <see cref="StreamState"/>.</param>
 368    /// <param name="outputFileExtension">The file extension of the output file.</param>
 369    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 370    /// <param name="deviceId">The device id.</param>
 371    /// <param name="playSessionId">The play session id.</param>
 372    /// <returns>The complete file path, including the folder, for the transcoding file.</returns>
 373    private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager s
 374    {
 0375        var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}";
 376
 0377        var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
 0378        var ext = outputFileExtension.ToLowerInvariant();
 0379        var folder = serverConfigurationManager.GetTranscodePath();
 380
 0381        return Path.Combine(folder, filename + ext);
 382    }
 383
 384    /// <summary>
 385    /// Parses the parameters.
 386    /// </summary>
 387    /// <param name="request">The request.</param>
 388    private static void ParseParams(StreamingRequestDto request)
 389    {
 0390        if (string.IsNullOrEmpty(request.Params))
 391        {
 0392            return;
 393        }
 394
 0395        var vals = request.Params.Split(';');
 396
 0397        var videoRequest = request as VideoRequestDto;
 398
 0399        for (var i = 0; i < vals.Length; i++)
 400        {
 0401            var val = vals[i];
 402
 0403            if (string.IsNullOrWhiteSpace(val))
 404            {
 405                continue;
 406            }
 407
 408            switch (i)
 409            {
 410                case 0:
 411                    // DeviceProfileId
 412                    break;
 413                case 1:
 0414                    request.DeviceId = val;
 0415                    break;
 416                case 2:
 0417                    request.MediaSourceId = val;
 0418                    break;
 419                case 3:
 0420                    request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 0421                    break;
 422                case 4:
 0423                    if (videoRequest is not null && IsValidCodecName(val))
 424                    {
 0425                        videoRequest.VideoCodec = val;
 426                    }
 427
 0428                    break;
 429                case 5:
 0430                    if (IsValidCodecName(val))
 431                    {
 0432                        request.AudioCodec = val;
 433                    }
 434
 0435                    break;
 436                case 6:
 0437                    if (videoRequest is not null)
 438                    {
 0439                        videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
 440                    }
 441
 0442                    break;
 443                case 7:
 0444                    if (videoRequest is not null)
 445                    {
 0446                        videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
 447                    }
 448
 0449                    break;
 450                case 8:
 0451                    if (videoRequest is not null)
 452                    {
 0453                        videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
 454                    }
 455
 0456                    break;
 457                case 9:
 0458                    request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
 0459                    break;
 460                case 10:
 0461                    request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
 0462                    break;
 463                case 11:
 0464                    if (videoRequest is not null)
 465                    {
 0466                        videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
 467                    }
 468
 0469                    break;
 470                case 12:
 0471                    if (videoRequest is not null)
 472                    {
 0473                        videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
 474                    }
 475
 0476                    break;
 477                case 13:
 0478                    if (videoRequest is not null)
 479                    {
 0480                        videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
 481                    }
 482
 0483                    break;
 484                case 14:
 0485                    request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
 0486                    break;
 487                case 15:
 0488                    if (videoRequest is not null && EncodingHelper.LevelValidationRegex().IsMatch(val))
 489                    {
 0490                        videoRequest.Level = val;
 491                    }
 492
 0493                    break;
 494                case 16:
 0495                    if (videoRequest is not null)
 496                    {
 0497                        videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
 498                    }
 499
 0500                    break;
 501                case 17:
 0502                    if (videoRequest is not null)
 503                    {
 0504                        videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
 505                    }
 506
 0507                    break;
 508                case 18:
 0509                    if (videoRequest is not null && IsValidCodecName(val))
 510                    {
 0511                        videoRequest.Profile = val;
 512                    }
 513
 0514                    break;
 515                case 19:
 516                    // cabac no longer used
 517                    break;
 518                case 20:
 0519                    request.PlaySessionId = val;
 0520                    break;
 521                case 21:
 522                    // api_key
 523                    break;
 524                case 22:
 0525                    request.LiveStreamId = val;
 0526                    break;
 527                case 23:
 528                    // Duplicating ItemId because of MediaMonkey
 529                    break;
 530                case 24:
 0531                    if (videoRequest is not null)
 532                    {
 0533                        videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 534                    }
 535
 0536                    break;
 537                case 25:
 0538                    if (!string.IsNullOrWhiteSpace(val) && videoRequest is not null)
 539                    {
 0540                        if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
 541                        {
 0542                            videoRequest.SubtitleMethod = method;
 543                        }
 544                    }
 545
 0546                    break;
 547                case 26:
 0548                    request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
 0549                    break;
 550                case 27:
 0551                    if (videoRequest is not null)
 552                    {
 0553                        videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgno
 554                    }
 555
 0556                    break;
 557                case 28:
 0558                    request.Tag = val;
 0559                    break;
 560                case 29:
 0561                    if (videoRequest is not null)
 562                    {
 0563                        videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 564                    }
 565
 0566                    break;
 567                case 30:
 0568                    if (IsValidCodecName(val))
 569                    {
 0570                        request.SubtitleCodec = val;
 571                    }
 572
 0573                    break;
 574                case 31:
 0575                    if (videoRequest is not null)
 576                    {
 0577                        videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCas
 578                    }
 579
 0580                    break;
 581                case 32:
 0582                    if (videoRequest is not null)
 583                    {
 0584                        videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 585                    }
 586
 0587                    break;
 588                case 33:
 0589                    request.TranscodeReasons = val;
 590                    break;
 591            }
 592        }
 0593    }
 594
 595    private static bool IsValidCodecName(string val)
 596    {
 0597        return EncodingHelper.ContainerValidationRegex().IsMatch(val);
 598    }
 599
 600    /// <summary>
 601    /// Parses the container into its file extension.
 602    /// </summary>
 603    /// <param name="container">The container.</param>
 604    private static string? GetContainerFileExtension(string? container)
 605    {
 0606        if (string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase))
 607        {
 0608            return "ts";
 609        }
 610
 0611        if (string.Equals(container, "matroska", StringComparison.OrdinalIgnoreCase))
 612        {
 0613            return "mkv";
 614        }
 615
 0616        return container;
 617    }
 618}