< 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: 247
Coverable lines: 247
Total lines: 627
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 237
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/26/2026 - 12:14:14 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: 6187/3/2026 - 12:15:32 AM Line coverage: 0% (0/247) Branch coverage: 0% (0/237) Total lines: 627 3/26/2026 - 12:14:14 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: 6187/3/2026 - 12:15:32 AM Line coverage: 0% (0/247) Branch coverage: 0% (0/237) Total lines: 627

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetStreamingState()0%109201040%
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 (outputAudioCodec.StartsWith("pcm_", StringComparison.Ordinal))
 202        {
 0203            containerInternal = ".pcm";
 204        }
 205
 0206        if (state.VideoRequest is not null)
 207        {
 0208            state.OutputVideoCodec = state.Request.VideoCodec;
 0209            state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, s
 210
 0211            encodingHelper.TryStreamCopy(state, encodingOptions);
 212
 0213            if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue)
 214            {
 0215                var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue
 0216                    && !state.VideoRequest.Height.HasValue
 0217                    && !state.VideoRequest.MaxWidth.HasValue
 0218                    && !state.VideoRequest.MaxHeight.HasValue;
 219
 0220                if (isVideoResolutionNotRequested
 0221                    && state.VideoStream is not null
 0222                    && state.VideoRequest.VideoBitRate.HasValue
 0223                    && state.VideoStream.BitRate.HasValue
 0224                    && state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value)
 225                {
 226                    // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested,
 227                    // and the requested video bitrate is greater than source video bitrate.
 0228                    if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue)
 229                    {
 0230                        state.VideoRequest.MaxWidth = state.VideoStream?.Width;
 0231                        state.VideoRequest.MaxHeight = state.VideoStream?.Height;
 232                    }
 233                }
 234                else
 235                {
 0236                    var h264EquivalentBitrate = EncodingHelper.ScaleBitrate(
 0237                        state.OutputVideoBitrate.Value,
 0238                        state.ActualOutputVideoCodec,
 0239                        "h264");
 0240                    var resolution = ResolutionNormalizer.Normalize(
 0241                        state.VideoStream?.BitRate,
 0242                        state.OutputVideoBitrate.Value,
 0243                        h264EquivalentBitrate,
 0244                        state.VideoRequest.MaxWidth,
 0245                        state.VideoRequest.MaxHeight,
 0246                        state.TargetFramerate);
 247
 0248                    state.VideoRequest.MaxWidth = resolution.MaxWidth;
 0249                    state.VideoRequest.MaxHeight = resolution.MaxHeight;
 250                }
 251            }
 252
 0253            if (state.AudioStream is not null && !EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(st
 254            {
 0255                state.OutputAudioCodec = state.SupportedAudioCodecs.Where(c => !EncodingHelper.LosslessAudioCodecs.Conta
 256            }
 257        }
 258
 0259        var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
 0260            ? GetOutputFileExtension(state, mediaSource)
 0261            : ("." + GetContainerFileExtension(state.OutputContainer));
 262
 0263        state.OutputFilePath = GetOutputFilePath(state, ext, serverConfigurationManager, streamingRequest.DeviceId, stre
 264
 0265        return state;
 0266    }
 267
 268    /// <summary>
 269    /// Parses query parameters as StreamOptions.
 270    /// </summary>
 271    /// <param name="queryString">The query string.</param>
 272    /// <returns>A <see cref="Dictionary{String,String}"/> containing the stream options.</returns>
 273    private static Dictionary<string, string?> ParseStreamOptions(IQueryCollection queryString)
 274    {
 0275        Dictionary<string, string?> streamOptions = new Dictionary<string, string?>();
 0276        foreach (var param in queryString)
 277        {
 0278            if (param.Key.Length > 0 && char.IsLower(param.Key[0]))
 279            {
 280                // This was probably not parsed initially and should be a StreamOptions
 281                // or the generated URL should correctly serialize it
 282                // TODO: This should be incorporated either in the lower framework for parsing requests
 0283                streamOptions[param.Key] = param.Value;
 284            }
 285        }
 286
 0287        return streamOptions;
 288    }
 289
 290    /// <summary>
 291    /// Gets the output file extension.
 292    /// </summary>
 293    /// <param name="state">The state.</param>
 294    /// <param name="mediaSource">The mediaSource.</param>
 295    /// <returns>System.String.</returns>
 296    private static string GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource)
 297    {
 0298        var ext = Path.GetExtension(state.RequestedUrl);
 0299        if (!string.IsNullOrEmpty(ext))
 300        {
 0301            return ext;
 302        }
 303
 304        // Try to infer based on the desired video codec
 0305        if (state.IsVideoRequest)
 306        {
 0307            var videoCodec = state.Request.VideoCodec;
 308
 0309            if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 310            {
 0311                return ".ts";
 312            }
 313
 0314            if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 0315                || string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 316            {
 0317                return ".mp4";
 318            }
 319
 0320            if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
 321            {
 0322                return ".ogv";
 323            }
 324
 0325            if (string.Equals(videoCodec, "vp8", StringComparison.OrdinalIgnoreCase)
 0326                || string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 0327                || string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
 328            {
 0329                return ".webm";
 330            }
 331
 0332            if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
 333            {
 0334                return ".asf";
 335            }
 336        }
 337        else
 338        {
 339            // Try to infer based on the desired audio codec
 0340            var audioCodec = state.Request.AudioCodec;
 341
 0342            if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
 343            {
 0344                return ".aac";
 345            }
 346
 0347            if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
 348            {
 0349                return ".mp3";
 350            }
 351
 0352            if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
 353            {
 0354                return ".ogg";
 355            }
 356
 0357            if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
 358            {
 0359                return ".wma";
 360            }
 361        }
 362
 363        // Fallback to the container of mediaSource
 0364        if (!string.IsNullOrEmpty(mediaSource?.Container))
 365        {
 0366            var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase);
 0367            return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim();
 368        }
 369
 0370        throw new InvalidOperationException("Failed to find an appropriate file extension");
 371    }
 372
 373    /// <summary>
 374    /// Gets the output file path for transcoding.
 375    /// </summary>
 376    /// <param name="state">The current <see cref="StreamState"/>.</param>
 377    /// <param name="outputFileExtension">The file extension of the output file.</param>
 378    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 379    /// <param name="deviceId">The device id.</param>
 380    /// <param name="playSessionId">The play session id.</param>
 381    /// <returns>The complete file path, including the folder, for the transcoding file.</returns>
 382    private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager s
 383    {
 0384        var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}";
 385
 0386        var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
 0387        var ext = outputFileExtension.ToLowerInvariant();
 0388        var folder = serverConfigurationManager.GetTranscodePath();
 389
 0390        return Path.Combine(folder, filename + ext);
 391    }
 392
 393    /// <summary>
 394    /// Parses the parameters.
 395    /// </summary>
 396    /// <param name="request">The request.</param>
 397    private static void ParseParams(StreamingRequestDto request)
 398    {
 0399        if (string.IsNullOrEmpty(request.Params))
 400        {
 0401            return;
 402        }
 403
 0404        var vals = request.Params.Split(';');
 405
 0406        var videoRequest = request as VideoRequestDto;
 407
 0408        for (var i = 0; i < vals.Length; i++)
 409        {
 0410            var val = vals[i];
 411
 0412            if (string.IsNullOrWhiteSpace(val))
 413            {
 414                continue;
 415            }
 416
 417            switch (i)
 418            {
 419                case 0:
 420                    // DeviceProfileId
 421                    break;
 422                case 1:
 0423                    request.DeviceId = val;
 0424                    break;
 425                case 2:
 0426                    request.MediaSourceId = val;
 0427                    break;
 428                case 3:
 0429                    request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 0430                    break;
 431                case 4:
 0432                    if (videoRequest is not null && IsValidCodecName(val))
 433                    {
 0434                        videoRequest.VideoCodec = val;
 435                    }
 436
 0437                    break;
 438                case 5:
 0439                    if (IsValidCodecName(val))
 440                    {
 0441                        request.AudioCodec = val;
 442                    }
 443
 0444                    break;
 445                case 6:
 0446                    if (videoRequest is not null)
 447                    {
 0448                        videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
 449                    }
 450
 0451                    break;
 452                case 7:
 0453                    if (videoRequest is not null)
 454                    {
 0455                        videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
 456                    }
 457
 0458                    break;
 459                case 8:
 0460                    if (videoRequest is not null)
 461                    {
 0462                        videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
 463                    }
 464
 0465                    break;
 466                case 9:
 0467                    request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
 0468                    break;
 469                case 10:
 0470                    request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
 0471                    break;
 472                case 11:
 0473                    if (videoRequest is not null)
 474                    {
 0475                        videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
 476                    }
 477
 0478                    break;
 479                case 12:
 0480                    if (videoRequest is not null)
 481                    {
 0482                        videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
 483                    }
 484
 0485                    break;
 486                case 13:
 0487                    if (videoRequest is not null)
 488                    {
 0489                        videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
 490                    }
 491
 0492                    break;
 493                case 14:
 0494                    request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
 0495                    break;
 496                case 15:
 0497                    if (videoRequest is not null && EncodingHelper.LevelValidationRegex().IsMatch(val))
 498                    {
 0499                        videoRequest.Level = val;
 500                    }
 501
 0502                    break;
 503                case 16:
 0504                    if (videoRequest is not null)
 505                    {
 0506                        videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
 507                    }
 508
 0509                    break;
 510                case 17:
 0511                    if (videoRequest is not null)
 512                    {
 0513                        videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
 514                    }
 515
 0516                    break;
 517                case 18:
 0518                    if (videoRequest is not null && IsValidCodecName(val))
 519                    {
 0520                        videoRequest.Profile = val;
 521                    }
 522
 0523                    break;
 524                case 19:
 525                    // cabac no longer used
 526                    break;
 527                case 20:
 0528                    request.PlaySessionId = val;
 0529                    break;
 530                case 21:
 531                    // api_key
 532                    break;
 533                case 22:
 0534                    request.LiveStreamId = val;
 0535                    break;
 536                case 23:
 537                    // Duplicating ItemId because of MediaMonkey
 538                    break;
 539                case 24:
 0540                    if (videoRequest is not null)
 541                    {
 0542                        videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 543                    }
 544
 0545                    break;
 546                case 25:
 0547                    if (!string.IsNullOrWhiteSpace(val) && videoRequest is not null)
 548                    {
 0549                        if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
 550                        {
 0551                            videoRequest.SubtitleMethod = method;
 552                        }
 553                    }
 554
 0555                    break;
 556                case 26:
 0557                    request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
 0558                    break;
 559                case 27:
 0560                    if (videoRequest is not null)
 561                    {
 0562                        videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgno
 563                    }
 564
 0565                    break;
 566                case 28:
 0567                    request.Tag = val;
 0568                    break;
 569                case 29:
 0570                    if (videoRequest is not null)
 571                    {
 0572                        videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 573                    }
 574
 0575                    break;
 576                case 30:
 0577                    if (IsValidCodecName(val))
 578                    {
 0579                        request.SubtitleCodec = val;
 580                    }
 581
 0582                    break;
 583                case 31:
 0584                    if (videoRequest is not null)
 585                    {
 0586                        videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCas
 587                    }
 588
 0589                    break;
 590                case 32:
 0591                    if (videoRequest is not null)
 592                    {
 0593                        videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
 594                    }
 595
 0596                    break;
 597                case 33:
 0598                    request.TranscodeReasons = val;
 599                    break;
 600            }
 601        }
 0602    }
 603
 604    private static bool IsValidCodecName(string val)
 605    {
 0606        return EncodingHelper.ContainerValidationRegex().IsMatch(val);
 607    }
 608
 609    /// <summary>
 610    /// Parses the container into its file extension.
 611    /// </summary>
 612    /// <param name="container">The container.</param>
 613    private static string? GetContainerFileExtension(string? container)
 614    {
 0615        if (string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase))
 616        {
 0617            return "ts";
 618        }
 619
 0620        if (string.Equals(container, "matroska", StringComparison.OrdinalIgnoreCase))
 621        {
 0622            return "mkv";
 623        }
 624
 0625        return container;
 626    }
 627}