< 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: 131
Coverable lines: 131
Total lines: 607
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 121
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 10/18/2025 - 12:10:13 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: 607 10/18/2025 - 12:10:13 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: 607

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ParseStreamOptions(...)0%2040%
GetOutputFileExtension(...)0%1190340%
GetOutputFilePath(...)100%210%
ParseParams(...)0%6320790%
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 MediaBrowser.Model.Entities;
 21using Microsoft.AspNetCore.Http;
 22using Microsoft.AspNetCore.Http.HttpResults;
 23using Microsoft.Net.Http.Headers;
 24
 25namespace Jellyfin.Api.Helpers;
 26
 27/// <summary>
 28/// The streaming helpers.
 29/// </summary>
 30public static class StreamingHelpers
 31{
 32    /// <summary>
 33    /// Gets the current streaming state.
 34    /// </summary>
 35    /// <param name="streamingRequest">The <see cref="StreamingRequestDto"/>.</param>
 36    /// <param name="httpContext">The <see cref="HttpContext"/>.</param>
 37    /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
 38    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 39    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 40    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 41    /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
 42    /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
 43    /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
 44    /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
 45    /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
 46    /// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns>
 47    public static async Task<StreamState> GetStreamingState(
 48        StreamingRequestDto streamingRequest,
 49        HttpContext httpContext,
 50        IMediaSourceManager mediaSourceManager,
 51        IUserManager userManager,
 52        ILibraryManager libraryManager,
 53        IServerConfigurationManager serverConfigurationManager,
 54        IMediaEncoder mediaEncoder,
 55        EncodingHelper encodingHelper,
 56        ITranscodeManager transcodeManager,
 57        TranscodingJobType transcodingJobType,
 58        CancellationToken cancellationToken)
 59    {
 60        var httpRequest = httpContext.Request;
 61        if (!string.IsNullOrWhiteSpace(streamingRequest.Params))
 62        {
 63            ParseParams(streamingRequest);
 64        }
 65
 66        streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
 67        if (httpRequest.Path.Value is null)
 68        {
 69            throw new ResourceNotFoundException(nameof(httpRequest.Path));
 70        }
 71
 72        var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString();
 73
 74        if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
 75        {
 76            streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
 77        }
 78
 79        var state = new StreamState(mediaSourceManager, transcodingJobType, transcodeManager)
 80        {
 81            Request = streamingRequest,
 82            RequestedUrl = url,
 83            UserAgent = httpRequest.Headers[HeaderNames.UserAgent]
 84        };
 85
 86        var userId = httpContext.User.GetUserId();
 87        if (!userId.IsEmpty())
 88        {
 89            state.User = userManager.GetUserById(userId);
 90        }
 91
 92        if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec))
 93        {
 94            state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
 95            state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 96        }
 97
 98        if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec))
 99        {
 100            state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
 101            state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec)
 102                                       ?? state.SupportedAudioCodecs.FirstOrDefault();
 103        }
 104
 105        if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec))
 106        {
 107            state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntr
 108            state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleC
 109                                          ?? state.SupportedSubtitleCodecs.FirstOrDefault();
 110        }
 111
 112        var item = libraryManager.GetItemById<BaseItem>(streamingRequest.Id)
 113            ?? throw new ResourceNotFoundException();
 114
 115        state.IsInputVideo = item.MediaType == MediaType.Video;
 116
 117        MediaSourceInfo? mediaSource = null;
 118        if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId))
 119        {
 120            var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId)
 121                ? transcodeManager.GetTranscodingJob(streamingRequest.PlaySessionId)
 122                : null;
 123
 124            if (currentJob is not null)
 125            {
 126                mediaSource = currentJob.MediaSource;
 127            }
 128
 129            if (mediaSource is null)
 130            {
 131                var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById<BaseItem>
 132
 133                mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
 134                    ? mediaSources[0]
 135                    : mediaSources.FirstOrDefault(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringCompari
 136
 137                if (mediaSource is null && Guid.Parse(streamingRequest.MediaSourceId).Equals(streamingRequest.Id))
 138                {
 139                    mediaSource = mediaSources[0];
 140                }
 141            }
 142        }
 143        else
 144        {
 145            var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStr
 146            mediaSource = liveStreamInfo.Item1;
 147            state.DirectStreamProvider = liveStreamInfo.Item2;
 148
 149            // Cap the max bitrate when it is too high. This is usually due to ffmpeg is unable to probe the source live
 150            if (mediaSource.FallbackMaxStreamingBitrate is not null && streamingRequest.VideoBitRate is not null)
 151            {
 152                streamingRequest.VideoBitRate = Math.Min(streamingRequest.VideoBitRate.Value, mediaSource.FallbackMaxStr
 153            }
 154        }
 155
 156        var encodingOptions = serverConfigurationManager.GetEncodingOptions();
 157
 158        encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url);
 159
 160        string? containerInternal = Path.GetExtension(state.RequestedUrl);
 161
 162        if (string.IsNullOrEmpty(containerInternal)
 163            && (!string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)
 164                || (mediaSource != null && mediaSource.IsInfiniteStream)))
 165        {
 166            containerInternal = ".ts";
 167        }
 168
 169        if (!string.IsNullOrEmpty(streamingRequest.Container))
 170        {
 171            containerInternal = streamingRequest.Container;
 172        }
 173
 174        if (string.IsNullOrEmpty(containerInternal))
 175        {
 176            containerInternal = streamingRequest.Static ?
 177                StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.
 178                : GetOutputFileExtension(state, mediaSource);
 179        }
 180
 181        var outputAudioCodec = streamingRequest.AudioCodec;
 182        state.OutputAudioCodec = outputAudioCodec;
 183        state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
 184        state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioC
 185        if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec))
 186        {
 187            state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0;
 188        }
 189        else
 190        {
 191            state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingReque
 192        }
 193
 194        if (outputAudioCodec.StartsWith("pcm_", StringComparison.Ordinal))
 195        {
 196            containerInternal = ".pcm";
 197        }
 198
 199        if (state.VideoRequest is not null)
 200        {
 201            state.OutputVideoCodec = state.Request.VideoCodec;
 202            state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, s
 203
 204            encodingHelper.TryStreamCopy(state, encodingOptions);
 205
 206            if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue)
 207            {
 208                var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue
 209                    && !state.VideoRequest.Height.HasValue
 210                    && !state.VideoRequest.MaxWidth.HasValue
 211                    && !state.VideoRequest.MaxHeight.HasValue;
 212
 213                if (isVideoResolutionNotRequested
 214                    && state.VideoStream is not null
 215                    && state.VideoRequest.VideoBitRate.HasValue
 216                    && state.VideoStream.BitRate.HasValue
 217                    && state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value)
 218                {
 219                    // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested,
 220                    // and the requested video bitrate is greater than source video bitrate.
 221                    if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue)
 222                    {
 223                        state.VideoRequest.MaxWidth = state.VideoStream?.Width;
 224                        state.VideoRequest.MaxHeight = state.VideoStream?.Height;
 225                    }
 226                }
 227                else
 228                {
 229                    var h264EquivalentBitrate = EncodingHelper.ScaleBitrate(
 230                        state.OutputVideoBitrate.Value,
 231                        state.ActualOutputVideoCodec,
 232                        "h264");
 233                    var resolution = ResolutionNormalizer.Normalize(
 234                        state.VideoStream?.BitRate,
 235                        state.OutputVideoBitrate.Value,
 236                        h264EquivalentBitrate,
 237                        state.VideoRequest.MaxWidth,
 238                        state.VideoRequest.MaxHeight,
 239                        state.TargetFramerate);
 240
 241                    state.VideoRequest.MaxWidth = resolution.MaxWidth;
 242                    state.VideoRequest.MaxHeight = resolution.MaxHeight;
 243                }
 244            }
 245
 246            if (state.AudioStream is not null && !EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(st
 247            {
 248                state.OutputAudioCodec = state.SupportedAudioCodecs.Where(c => !EncodingHelper.LosslessAudioCodecs.Conta
 249            }
 250        }
 251
 252        var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
 253            ? GetOutputFileExtension(state, mediaSource)
 254            : ("." + GetContainerFileExtension(state.OutputContainer));
 255
 256        state.OutputFilePath = GetOutputFilePath(state, ext, serverConfigurationManager, streamingRequest.DeviceId, stre
 257
 258        return state;
 259    }
 260
 261    /// <summary>
 262    /// Parses query parameters as StreamOptions.
 263    /// </summary>
 264    /// <param name="queryString">The query string.</param>
 265    /// <returns>A <see cref="Dictionary{String,String}"/> containing the stream options.</returns>
 266    private static Dictionary<string, string?> ParseStreamOptions(IQueryCollection queryString)
 267    {
 0268        Dictionary<string, string?> streamOptions = new Dictionary<string, string?>();
 0269        foreach (var param in queryString)
 270        {
 0271            if (char.IsLower(param.Key[0]))
 272            {
 273                // This was probably not parsed initially and should be a StreamOptions
 274                // or the generated URL should correctly serialize it
 275                // TODO: This should be incorporated either in the lower framework for parsing requests
 0276                streamOptions[param.Key] = param.Value;
 277            }
 278        }
 279
 0280        return streamOptions;
 281    }
 282
 283    /// <summary>
 284    /// Gets the output file extension.
 285    /// </summary>
 286    /// <param name="state">The state.</param>
 287    /// <param name="mediaSource">The mediaSource.</param>
 288    /// <returns>System.String.</returns>
 289    private static string GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource)
 290    {
 0291        var ext = Path.GetExtension(state.RequestedUrl);
 0292        if (!string.IsNullOrEmpty(ext))
 293        {
 0294            return ext;
 295        }
 296
 297        // Try to infer based on the desired video codec
 0298        if (state.IsVideoRequest)
 299        {
 0300            var videoCodec = state.Request.VideoCodec;
 301
 0302            if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 303            {
 0304                return ".ts";
 305            }
 306
 0307            if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 0308                || string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 309            {
 0310                return ".mp4";
 311            }
 312
 0313            if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
 314            {
 0315                return ".ogv";
 316            }
 317
 0318            if (string.Equals(videoCodec, "vp8", StringComparison.OrdinalIgnoreCase)
 0319                || string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 0320                || string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
 321            {
 0322                return ".webm";
 323            }
 324
 0325            if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
 326            {
 0327                return ".asf";
 328            }
 329        }
 330        else
 331        {
 332            // Try to infer based on the desired audio codec
 0333            var audioCodec = state.Request.AudioCodec;
 334
 0335            if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
 336            {
 0337                return ".aac";
 338            }
 339
 0340            if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
 341            {
 0342                return ".mp3";
 343            }
 344
 0345            if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
 346            {
 0347                return ".ogg";
 348            }
 349
 0350            if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
 351            {
 0352                return ".wma";
 353            }
 354        }
 355
 356        // Fallback to the container of mediaSource
 0357        if (!string.IsNullOrEmpty(mediaSource?.Container))
 358        {
 0359            var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase);
 0360            return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim();
 361        }
 362
 0363        throw new InvalidOperationException("Failed to find an appropriate file extension");
 364    }
 365
 366    /// <summary>
 367    /// Gets the output file path for transcoding.
 368    /// </summary>
 369    /// <param name="state">The current <see cref="StreamState"/>.</param>
 370    /// <param name="outputFileExtension">The file extension of the output file.</param>
 371    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 372    /// <param name="deviceId">The device id.</param>
 373    /// <param name="playSessionId">The play session id.</param>
 374    /// <returns>The complete file path, including the folder, for the transcoding file.</returns>
 375    private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager s
 376    {
 0377        var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}";
 378
 0379        var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
 0380        var ext = outputFileExtension.ToLowerInvariant();
 0381        var folder = serverConfigurationManager.GetTranscodePath();
 382
 0383        return Path.Combine(folder, filename + ext);
 384    }
 385
 386    /// <summary>
 387    /// Parses the parameters.
 388    /// </summary>
 389    /// <param name="request">The request.</param>
 390    private static void ParseParams(StreamingRequestDto request)
 391    {
 0392        if (string.IsNullOrEmpty(request.Params))
 393        {
 0394            return;
 395        }
 396
 0397        var vals = request.Params.Split(';');
 398
 0399        var videoRequest = request as VideoRequestDto;
 400
 0401        for (var i = 0; i < vals.Length; i++)
 402        {
 0403            var val = vals[i];
 404
 0405            if (string.IsNullOrWhiteSpace(val))
 406            {
 407                continue;
 408            }
 409
 410            switch (i)
 411            {
 412                case 0:
 413                    // DeviceProfileId
 414                    break;
 415                case 1:
 0416                    request.DeviceId = val;
 0417                    break;
 418                case 2:
 0419                    request.MediaSourceId = val;
 0420                    break;
 421                case 3:
 0422                    request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 0423                    break;
 424                case 4:
 0425                    if (videoRequest is not null)
 426                    {
 0427                        videoRequest.VideoCodec = val;
 428                    }
 429
 0430                    break;
 431                case 5:
 0432                    request.AudioCodec = val;
 0433                    break;
 434                case 6:
 0435                    if (videoRequest is not null)
 436                    {
 0437                        videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
 438                    }
 439
 0440                    break;
 441                case 7:
 0442                    if (videoRequest is not null)
 443                    {
 0444                        videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
 445                    }
 446
 0447                    break;
 448                case 8:
 0449                    if (videoRequest is not null)
 450                    {
 0451                        videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
 452                    }
 453
 0454                    break;
 455                case 9:
 0456                    request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
 0457                    break;
 458                case 10:
 0459                    request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
 0460                    break;
 461                case 11:
 0462                    if (videoRequest is not null)
 463                    {
 0464                        videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
 465                    }
 466
 0467                    break;
 468                case 12:
 0469                    if (videoRequest is not null)
 470                    {
 0471                        videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
 472                    }
 473
 0474                    break;
 475                case 13:
 0476                    if (videoRequest is not null)
 477                    {
 0478                        videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
 479                    }
 480
 0481                    break;
 482                case 14:
 0483                    request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
 0484                    break;
 485                case 15:
 0486                    if (videoRequest is not null)
 487                    {
 0488                        videoRequest.Level = val;
 489                    }
 490
 0491                    break;
 492                case 16:
 0493                    if (videoRequest is not null)
 494                    {
 0495                        videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
 496                    }
 497
 0498                    break;
 499                case 17:
 0500                    if (videoRequest is not null)
 501                    {
 0502                        videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
 503                    }
 504
 0505                    break;
 506                case 18:
 0507                    if (videoRequest is not null)
 508                    {
 0509                        videoRequest.Profile = val;
 510                    }
 511
 0512                    break;
 513                case 19:
 514                    // cabac no longer used
 515                    break;
 516                case 20:
 0517                    request.PlaySessionId = val;
 0518                    break;
 519                case 21:
 520                    // api_key
 521                    break;
 522                case 22:
 0523                    request.LiveStreamId = val;
 0524                    break;
 525                case 23:
 526                    // Duplicating ItemId because of MediaMonkey
 527                    break;
 528                case 24:
 0529                    if (videoRequest is not null)
 530                    {
 0531                        videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 532                    }
 533
 0534                    break;
 535                case 25:
 0536                    if (!string.IsNullOrWhiteSpace(val) && videoRequest is not null)
 537                    {
 0538                        if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
 539                        {
 0540                            videoRequest.SubtitleMethod = method;
 541                        }
 542                    }
 543
 0544                    break;
 545                case 26:
 0546                    request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
 0547                    break;
 548                case 27:
 0549                    if (videoRequest is not null)
 550                    {
 0551                        videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgno
 552                    }
 553
 0554                    break;
 555                case 28:
 0556                    request.Tag = val;
 0557                    break;
 558                case 29:
 0559                    if (videoRequest is not null)
 560                    {
 0561                        videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 562                    }
 563
 0564                    break;
 565                case 30:
 0566                    request.SubtitleCodec = val;
 0567                    break;
 568                case 31:
 0569                    if (videoRequest is not null)
 570                    {
 0571                        videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCas
 572                    }
 573
 0574                    break;
 575                case 32:
 0576                    if (videoRequest is not null)
 577                    {
 0578                        videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 579                    }
 580
 0581                    break;
 582                case 33:
 0583                    request.TranscodeReasons = val;
 584                    break;
 585            }
 586        }
 0587    }
 588
 589    /// <summary>
 590    /// Parses the container into its file extension.
 591    /// </summary>
 592    /// <param name="container">The container.</param>
 593    private static string? GetContainerFileExtension(string? container)
 594    {
 0595        if (string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase))
 596        {
 0597            return "ts";
 598        }
 599
 0600        if (string.Equals(container, "matroska", StringComparison.OrdinalIgnoreCase))
 601        {
 0602            return "mkv";
 603        }
 604
 0605        return container;
 606    }
 607}