< 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: 618
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 1/23/2026 - 12:11:06 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: 6184/19/2026 - 12:14:27 AM Line coverage: 0% (0/245) Branch coverage: 0% (0/235) Total lines: 618 1/23/2026 - 12:11:06 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: 6184/19/2026 - 12:14:27 AM Line coverage: 0% (0/245) Branch coverage: 0% (0/235) Total lines: 618

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            // Cap the max bitrate when it is too high. This is usually due to ffmpeg is unable to probe the source live
 0148            if (mediaSource.FallbackMaxStreamingBitrate is not null && streamingRequest.VideoBitRate is not null)
 149            {
 0150                streamingRequest.VideoBitRate = Math.Min(streamingRequest.VideoBitRate.Value, mediaSource.FallbackMaxStr
 151            }
 152        }
 153
 0154        var encodingOptions = serverConfigurationManager.GetEncodingOptions();
 155
 0156        encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url);
 157
 0158        string? containerInternal = Path.GetExtension(state.RequestedUrl);
 159
 0160        if (string.IsNullOrEmpty(containerInternal)
 0161            && (!string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)
 0162                || (mediaSource != null && mediaSource.IsInfiniteStream)))
 163        {
 0164            containerInternal = ".ts";
 165        }
 166
 0167        if (!string.IsNullOrEmpty(streamingRequest.Container))
 168        {
 0169            containerInternal = streamingRequest.Container;
 170        }
 171
 0172        if (string.IsNullOrEmpty(containerInternal))
 173        {
 0174            containerInternal = streamingRequest.Static ?
 0175                StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.
 0176                : GetOutputFileExtension(state, mediaSource);
 177        }
 178
 0179        var outputAudioCodec = streamingRequest.AudioCodec;
 0180        state.OutputAudioCodec = outputAudioCodec;
 0181        state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
 0182        state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioC
 0183        if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec))
 184        {
 0185            state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0;
 186        }
 187        else
 188        {
 0189            state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingReque
 190        }
 191
 0192        if (outputAudioCodec.StartsWith("pcm_", StringComparison.Ordinal))
 193        {
 0194            containerInternal = ".pcm";
 195        }
 196
 0197        if (state.VideoRequest is not null)
 198        {
 0199            state.OutputVideoCodec = state.Request.VideoCodec;
 0200            state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, s
 201
 0202            encodingHelper.TryStreamCopy(state, encodingOptions);
 203
 0204            if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue)
 205            {
 0206                var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue
 0207                    && !state.VideoRequest.Height.HasValue
 0208                    && !state.VideoRequest.MaxWidth.HasValue
 0209                    && !state.VideoRequest.MaxHeight.HasValue;
 210
 0211                if (isVideoResolutionNotRequested
 0212                    && state.VideoStream is not null
 0213                    && state.VideoRequest.VideoBitRate.HasValue
 0214                    && state.VideoStream.BitRate.HasValue
 0215                    && 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.
 0219                    if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue)
 220                    {
 0221                        state.VideoRequest.MaxWidth = state.VideoStream?.Width;
 0222                        state.VideoRequest.MaxHeight = state.VideoStream?.Height;
 223                    }
 224                }
 225                else
 226                {
 0227                    var h264EquivalentBitrate = EncodingHelper.ScaleBitrate(
 0228                        state.OutputVideoBitrate.Value,
 0229                        state.ActualOutputVideoCodec,
 0230                        "h264");
 0231                    var resolution = ResolutionNormalizer.Normalize(
 0232                        state.VideoStream?.BitRate,
 0233                        state.OutputVideoBitrate.Value,
 0234                        h264EquivalentBitrate,
 0235                        state.VideoRequest.MaxWidth,
 0236                        state.VideoRequest.MaxHeight,
 0237                        state.TargetFramerate);
 238
 0239                    state.VideoRequest.MaxWidth = resolution.MaxWidth;
 0240                    state.VideoRequest.MaxHeight = resolution.MaxHeight;
 241                }
 242            }
 243
 0244            if (state.AudioStream is not null && !EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(st
 245            {
 0246                state.OutputAudioCodec = state.SupportedAudioCodecs.Where(c => !EncodingHelper.LosslessAudioCodecs.Conta
 247            }
 248        }
 249
 0250        var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
 0251            ? GetOutputFileExtension(state, mediaSource)
 0252            : ("." + GetContainerFileExtension(state.OutputContainer));
 253
 0254        state.OutputFilePath = GetOutputFilePath(state, ext, serverConfigurationManager, streamingRequest.DeviceId, stre
 255
 0256        return state;
 0257    }
 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}