< 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: 245
Coverable lines: 245
Total lines: 622
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 235
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/3/2026 - 12:15:13 AM Line coverage: 0% (0/245) Branch coverage: 0% (0/235) Total lines: 6187/3/2026 - 12:15:32 AM Line coverage: 0% (0/247) Branch coverage: 0% (0/237) Total lines: 6278/6/2026 - 12:17:15 AM Line coverage: 0% (0/245) Branch coverage: 0% (0/235) Total lines: 622 5/3/2026 - 12:15:13 AM Line coverage: 0% (0/245) Branch coverage: 0% (0/235) Total lines: 6187/3/2026 - 12:15:32 AM Line coverage: 0% (0/247) Branch coverage: 0% (0/237) Total lines: 6278/6/2026 - 12:17:15 AM Line coverage: 0% (0/245) Branch coverage: 0% (0/235) Total lines: 622

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetStreamingState()0%105061020%
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    {
 058        var httpRequest = httpContext.Request;
 059        if (!string.IsNullOrWhiteSpace(streamingRequest.Params))
 60        {
 061            ParseParams(streamingRequest);
 62        }
 63
 064        streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
 065        if (httpRequest.Path.Value is null)
 66        {
 067            throw new ResourceNotFoundException(nameof(httpRequest.Path));
 68        }
 69
 070        var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString();
 71
 072        if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
 73        {
 074            streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
 75        }
 76
 077        var state = new StreamState(mediaSourceManager, transcodingJobType, transcodeManager)
 078        {
 079            Request = streamingRequest,
 080            RequestedUrl = url,
 081            UserAgent = httpRequest.Headers[HeaderNames.UserAgent]
 082        };
 83
 084        var userId = httpContext.User.GetUserId();
 085        if (!userId.IsEmpty())
 86        {
 087            state.User = userManager.GetUserById(userId);
 88        }
 89
 090        if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec))
 91        {
 092            state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
 093            state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 94        }
 95
 096        if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec))
 97        {
 098            state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
 099            state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec)
 0100                                       ?? state.SupportedAudioCodecs.FirstOrDefault();
 101        }
 102
 0103        if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec))
 104        {
 0105            state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntr
 0106            state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleC
 0107                                          ?? state.SupportedSubtitleCodecs.FirstOrDefault();
 108        }
 109
 0110        var item = libraryManager.GetItemById<BaseItem>(streamingRequest.Id)
 0111            ?? throw new ResourceNotFoundException();
 112
 0113        state.IsInputVideo = item.MediaType == MediaType.Video;
 114
 0115        MediaSourceInfo? mediaSource = null;
 0116        if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId))
 117        {
 0118            var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId)
 0119                ? transcodeManager.GetTranscodingJob(streamingRequest.PlaySessionId)
 0120                : null;
 121
 0122            if (currentJob is not null)
 123            {
 0124                mediaSource = currentJob.MediaSource;
 125            }
 126
 0127            if (mediaSource is null)
 128            {
 0129                var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById<BaseItem>
 130
 0131                mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
 0132                    ? mediaSources[0]
 0133                    : mediaSources.FirstOrDefault(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringCompari
 134
 0135                if (mediaSource is null && Guid.Parse(streamingRequest.MediaSourceId).Equals(streamingRequest.Id))
 136                {
 0137                    mediaSource = mediaSources[0];
 138                }
 139            }
 140        }
 141        else
 142        {
 0143            var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStr
 0144            mediaSource = liveStreamInfo.Item1;
 0145            state.DirectStreamProvider = liveStreamInfo.Item2;
 146
 147            // The requested live stream is no longer open. This commonly happens when a client keeps
 148            // polling the HLS playlist (e.g. live.m3u8) after the stream was disposed because its
 149            // consumer count dropped to zero. GetLiveStreamWithDirectStreamProvider returns a null
 150            // MediaSource in that case, so return 404 instead of dereferencing it below.
 0151            if (mediaSource is null)
 152            {
 0153                throw new ResourceNotFoundException($"The live stream with id {streamingRequest.LiveStreamId} could not 
 154            }
 155
 156            // Cap the max bitrate when it is too high. This is usually due to ffmpeg is unable to probe the source live
 0157            if (mediaSource.FallbackMaxStreamingBitrate is not null && streamingRequest.VideoBitRate is not null)
 158            {
 0159                streamingRequest.VideoBitRate = Math.Min(streamingRequest.VideoBitRate.Value, mediaSource.FallbackMaxStr
 160            }
 161        }
 162
 0163        var encodingOptions = serverConfigurationManager.GetEncodingOptions();
 164
 0165        encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url);
 166
 0167        string? containerInternal = Path.GetExtension(state.RequestedUrl);
 168
 0169        if (string.IsNullOrEmpty(containerInternal)
 0170            && (!string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)
 0171                || (mediaSource != null && mediaSource.IsInfiniteStream)))
 172        {
 0173            containerInternal = ".ts";
 174        }
 175
 0176        if (!string.IsNullOrEmpty(streamingRequest.Container))
 177        {
 0178            containerInternal = streamingRequest.Container;
 179        }
 180
 0181        if (string.IsNullOrEmpty(containerInternal))
 182        {
 0183            containerInternal = streamingRequest.Static ?
 0184                StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.
 0185                : GetOutputFileExtension(state, mediaSource);
 186        }
 187
 0188        var outputAudioCodec = streamingRequest.AudioCodec;
 0189        state.OutputAudioCodec = outputAudioCodec;
 0190        state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
 0191        state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioC
 0192        if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec))
 193        {
 0194            state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0;
 195        }
 196        else
 197        {
 0198            state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingReque
 199        }
 200
 0201        if (state.VideoRequest is not null)
 202        {
 0203            state.OutputVideoCodec = state.Request.VideoCodec;
 0204            state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, s
 205
 0206            encodingHelper.TryStreamCopy(state, encodingOptions);
 207
 0208            if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue)
 209            {
 0210                var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue
 0211                    && !state.VideoRequest.Height.HasValue
 0212                    && !state.VideoRequest.MaxWidth.HasValue
 0213                    && !state.VideoRequest.MaxHeight.HasValue;
 214
 0215                if (isVideoResolutionNotRequested
 0216                    && state.VideoStream is not null
 0217                    && state.VideoRequest.VideoBitRate.HasValue
 0218                    && state.VideoStream.BitRate.HasValue
 0219                    && state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value)
 220                {
 221                    // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested,
 222                    // and the requested video bitrate is greater than source video bitrate.
 0223                    if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue)
 224                    {
 0225                        state.VideoRequest.MaxWidth = state.VideoStream?.Width;
 0226                        state.VideoRequest.MaxHeight = state.VideoStream?.Height;
 227                    }
 228                }
 229                else
 230                {
 0231                    var h264EquivalentBitrate = EncodingHelper.ScaleBitrate(
 0232                        state.OutputVideoBitrate.Value,
 0233                        state.ActualOutputVideoCodec,
 0234                        "h264");
 0235                    var resolution = ResolutionNormalizer.Normalize(
 0236                        state.VideoStream?.BitRate,
 0237                        state.OutputVideoBitrate.Value,
 0238                        h264EquivalentBitrate,
 0239                        state.VideoRequest.MaxWidth,
 0240                        state.VideoRequest.MaxHeight,
 0241                        state.TargetFramerate);
 242
 0243                    state.VideoRequest.MaxWidth = resolution.MaxWidth;
 0244                    state.VideoRequest.MaxHeight = resolution.MaxHeight;
 245                }
 246            }
 247
 0248            if (state.AudioStream is not null && !EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(st
 249            {
 0250                state.OutputAudioCodec = state.SupportedAudioCodecs.Where(c => !EncodingHelper.LosslessAudioCodecs.Conta
 251            }
 252        }
 253
 0254        var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
 0255            ? GetOutputFileExtension(state, mediaSource)
 0256            : ("." + GetContainerFileExtension(state.OutputContainer));
 257
 0258        state.OutputFilePath = GetOutputFilePath(state, ext, serverConfigurationManager, streamingRequest.DeviceId, stre
 259
 0260        return state;
 0261    }
 262
 263    /// <summary>
 264    /// Parses query parameters as StreamOptions.
 265    /// </summary>
 266    /// <param name="queryString">The query string.</param>
 267    /// <returns>A <see cref="Dictionary{String,String}"/> containing the stream options.</returns>
 268    private static Dictionary<string, string?> ParseStreamOptions(IQueryCollection queryString)
 269    {
 0270        Dictionary<string, string?> streamOptions = new Dictionary<string, string?>();
 0271        foreach (var param in queryString)
 272        {
 0273            if (param.Key.Length > 0 && char.IsLower(param.Key[0]))
 274            {
 275                // This was probably not parsed initially and should be a StreamOptions
 276                // or the generated URL should correctly serialize it
 277                // TODO: This should be incorporated either in the lower framework for parsing requests
 0278                streamOptions[param.Key] = param.Value;
 279            }
 280        }
 281
 0282        return streamOptions;
 283    }
 284
 285    /// <summary>
 286    /// Gets the output file extension.
 287    /// </summary>
 288    /// <param name="state">The state.</param>
 289    /// <param name="mediaSource">The mediaSource.</param>
 290    /// <returns>System.String.</returns>
 291    private static string GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource)
 292    {
 0293        var ext = Path.GetExtension(state.RequestedUrl);
 0294        if (!string.IsNullOrEmpty(ext))
 295        {
 0296            return ext;
 297        }
 298
 299        // Try to infer based on the desired video codec
 0300        if (state.IsVideoRequest)
 301        {
 0302            var videoCodec = state.Request.VideoCodec;
 303
 0304            if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 305            {
 0306                return ".ts";
 307            }
 308
 0309            if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 0310                || string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 311            {
 0312                return ".mp4";
 313            }
 314
 0315            if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
 316            {
 0317                return ".ogv";
 318            }
 319
 0320            if (string.Equals(videoCodec, "vp8", StringComparison.OrdinalIgnoreCase)
 0321                || string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 0322                || string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
 323            {
 0324                return ".webm";
 325            }
 326
 0327            if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
 328            {
 0329                return ".asf";
 330            }
 331        }
 332        else
 333        {
 334            // Try to infer based on the desired audio codec
 0335            var audioCodec = state.Request.AudioCodec;
 336
 0337            if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
 338            {
 0339                return ".aac";
 340            }
 341
 0342            if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
 343            {
 0344                return ".mp3";
 345            }
 346
 0347            if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
 348            {
 0349                return ".ogg";
 350            }
 351
 0352            if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
 353            {
 0354                return ".wma";
 355            }
 356        }
 357
 358        // Fallback to the container of mediaSource
 0359        if (!string.IsNullOrEmpty(mediaSource?.Container))
 360        {
 0361            var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase);
 0362            return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim();
 363        }
 364
 0365        throw new InvalidOperationException("Failed to find an appropriate file extension");
 366    }
 367
 368    /// <summary>
 369    /// Gets the output file path for transcoding.
 370    /// </summary>
 371    /// <param name="state">The current <see cref="StreamState"/>.</param>
 372    /// <param name="outputFileExtension">The file extension of the output file.</param>
 373    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 374    /// <param name="deviceId">The device id.</param>
 375    /// <param name="playSessionId">The play session id.</param>
 376    /// <returns>The complete file path, including the folder, for the transcoding file.</returns>
 377    private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager s
 378    {
 0379        var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}";
 380
 0381        var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
 0382        var ext = outputFileExtension.ToLowerInvariant();
 0383        var folder = serverConfigurationManager.GetTranscodePath();
 384
 0385        return Path.Combine(folder, filename + ext);
 386    }
 387
 388    /// <summary>
 389    /// Parses the parameters.
 390    /// </summary>
 391    /// <param name="request">The request.</param>
 392    private static void ParseParams(StreamingRequestDto request)
 393    {
 0394        if (string.IsNullOrEmpty(request.Params))
 395        {
 0396            return;
 397        }
 398
 0399        var vals = request.Params.Split(';');
 400
 0401        var videoRequest = request as VideoRequestDto;
 402
 0403        for (var i = 0; i < vals.Length; i++)
 404        {
 0405            var val = vals[i];
 406
 0407            if (string.IsNullOrWhiteSpace(val))
 408            {
 409                continue;
 410            }
 411
 412            switch (i)
 413            {
 414                case 0:
 415                    // DeviceProfileId
 416                    break;
 417                case 1:
 0418                    request.DeviceId = val;
 0419                    break;
 420                case 2:
 0421                    request.MediaSourceId = val;
 0422                    break;
 423                case 3:
 0424                    request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 0425                    break;
 426                case 4:
 0427                    if (videoRequest is not null && IsValidCodecName(val))
 428                    {
 0429                        videoRequest.VideoCodec = val;
 430                    }
 431
 0432                    break;
 433                case 5:
 0434                    if (IsValidCodecName(val))
 435                    {
 0436                        request.AudioCodec = val;
 437                    }
 438
 0439                    break;
 440                case 6:
 0441                    if (videoRequest is not null)
 442                    {
 0443                        videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
 444                    }
 445
 0446                    break;
 447                case 7:
 0448                    if (videoRequest is not null)
 449                    {
 0450                        videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
 451                    }
 452
 0453                    break;
 454                case 8:
 0455                    if (videoRequest is not null)
 456                    {
 0457                        videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
 458                    }
 459
 0460                    break;
 461                case 9:
 0462                    request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
 0463                    break;
 464                case 10:
 0465                    request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
 0466                    break;
 467                case 11:
 0468                    if (videoRequest is not null)
 469                    {
 0470                        videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
 471                    }
 472
 0473                    break;
 474                case 12:
 0475                    if (videoRequest is not null)
 476                    {
 0477                        videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
 478                    }
 479
 0480                    break;
 481                case 13:
 0482                    if (videoRequest is not null)
 483                    {
 0484                        videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
 485                    }
 486
 0487                    break;
 488                case 14:
 0489                    request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
 0490                    break;
 491                case 15:
 0492                    if (videoRequest is not null && EncodingHelper.LevelValidationRegex().IsMatch(val))
 493                    {
 0494                        videoRequest.Level = val;
 495                    }
 496
 0497                    break;
 498                case 16:
 0499                    if (videoRequest is not null)
 500                    {
 0501                        videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
 502                    }
 503
 0504                    break;
 505                case 17:
 0506                    if (videoRequest is not null)
 507                    {
 0508                        videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
 509                    }
 510
 0511                    break;
 512                case 18:
 0513                    if (videoRequest is not null && IsValidCodecName(val))
 514                    {
 0515                        videoRequest.Profile = val;
 516                    }
 517
 0518                    break;
 519                case 19:
 520                    // cabac no longer used
 521                    break;
 522                case 20:
 0523                    request.PlaySessionId = val;
 0524                    break;
 525                case 21:
 526                    // api_key
 527                    break;
 528                case 22:
 0529                    request.LiveStreamId = val;
 0530                    break;
 531                case 23:
 532                    // Duplicating ItemId because of MediaMonkey
 533                    break;
 534                case 24:
 0535                    if (videoRequest is not null)
 536                    {
 0537                        videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 538                    }
 539
 0540                    break;
 541                case 25:
 0542                    if (!string.IsNullOrWhiteSpace(val) && videoRequest is not null)
 543                    {
 0544                        if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
 545                        {
 0546                            videoRequest.SubtitleMethod = method;
 547                        }
 548                    }
 549
 0550                    break;
 551                case 26:
 0552                    request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
 0553                    break;
 554                case 27:
 0555                    if (videoRequest is not null)
 556                    {
 0557                        videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgno
 558                    }
 559
 0560                    break;
 561                case 28:
 0562                    request.Tag = val;
 0563                    break;
 564                case 29:
 0565                    if (videoRequest is not null)
 566                    {
 0567                        videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 568                    }
 569
 0570                    break;
 571                case 30:
 0572                    if (IsValidCodecName(val))
 573                    {
 0574                        request.SubtitleCodec = val;
 575                    }
 576
 0577                    break;
 578                case 31:
 0579                    if (videoRequest is not null)
 580                    {
 0581                        videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCas
 582                    }
 583
 0584                    break;
 585                case 32:
 0586                    if (videoRequest is not null)
 587                    {
 0588                        videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 589                    }
 590
 0591                    break;
 592                case 33:
 0593                    request.TranscodeReasons = val;
 594                    break;
 595            }
 596        }
 0597    }
 598
 599    private static bool IsValidCodecName(string val)
 600    {
 0601        return EncodingHelper.ContainerValidationRegex().IsMatch(val);
 602    }
 603
 604    /// <summary>
 605    /// Parses the container into its file extension.
 606    /// </summary>
 607    /// <param name="container">The container.</param>
 608    private static string? GetContainerFileExtension(string? container)
 609    {
 0610        if (string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase))
 611        {
 0612            return "ts";
 613        }
 614
 0615        if (string.Equals(container, "matroska", StringComparison.OrdinalIgnoreCase))
 616        {
 0617            return "mkv";
 618        }
 619
 0620        return container;
 621    }
 622}